microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
fix/msal-cache

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

Libraries/Microsoft.Teams.Apps/Contexts/Context.SignIn.cs

229lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4using System.Text.Json;
5
6using Microsoft.Teams.Api;
7using Microsoft.Teams.Api.Activities;
8using Microsoft.Teams.Api.Clients;
9
10namespace Microsoft.Teams.Apps;
11
12public partial interface IContext<TActivity>
13{
14 /// <summary>
15 /// is the activity sender signed in?
16 /// </summary>
17 public bool IsSignedIn { get; set; }
18
19 /// <summary>
20 /// the default connection name to use for the app.
21 /// by default it is "graph".
22 /// </summary>
23 public string ConnectionName { get; set; }
24
25 /// <summary>
26 /// trigger user OAuth signin flow for the activity sender
27 /// </summary>
28 /// <param name="options">option overrides</param>
29 /// <param name="cancellationToken">optional cancellation token</param>
30 /// <returns>the existing user token if found</returns>
31 public Task<string?> SignIn(OAuthOptions? options = null, CancellationToken cancellationToken = default);
32
33 /// <summary>
34 /// trigger user SSO signin flow for the activity sender
35 /// </summary>
36 /// <param name="options">option overrides</param>
37 /// <param name="cancellationToken">optional cancellation token</param>
38 public Task SignIn(SSOOptions options, CancellationToken cancellationToken = default);
39
40 /// <summary>
41 /// trigger user signin flow for the activity sender
42 /// </summary>
43 /// <param name="connectionName">the connection name</param>
44 /// <param name="cancellationToken">optional cancellation token</param>
45 public Task SignOut(string? connectionName = null, CancellationToken cancellationToken = default);
46}
47
48public partial class Context<TActivity> : IContext<TActivity>
49{
50 public bool IsSignedIn { get; set; } = false;
51 public required string ConnectionName { get; set; }
52
53 public async Task<string?> SignIn(OAuthOptions? options = null, CancellationToken cancellationToken = default)
54 {
55 options ??= new OAuthOptions();
56 var reference = Ref.Copy();
57 var token = cancellationToken == default ? CancellationToken : cancellationToken;
58 var api = new ApiClient(Api, token);
59
60 try
61 {
62 var tokenResponse = await api.Users.Token.GetAsync(new()
63 {
64 UserId = Activity.From.Id,
65 ChannelId = Activity.ChannelId,
66 ConnectionName = options.ConnectionName ?? ConnectionName,
67 }).ConfigureAwait(false);
68
69 return tokenResponse.Token;
70 }
71 catch (Exception ex)
72 {
73 Log.Debug("Existing token retrieval failed, proceeding to token exchange", ex);
74 }
75
76 var tokenExchangeState = new Api.TokenExchange.State()
77 {
78 ConnectionName = options.ConnectionName ?? ConnectionName,
79 Conversation = reference,
80 MsAppId = AppId
81 };
82
83 if (Activity.Conversation.IsGroup == true)
84 {
85 // create new 1:1 conversation with user to do SSO
86 // because groupchats don't support it.
87 var (id, _, _) = await api.Conversations.CreateAsync(new()
88 {
89 TenantId = Ref.Conversation.TenantId,
90 Members = [Activity.From]
91 }).ConfigureAwait(false);
92
93 reference.Conversation.Id = id;
94 reference.Conversation.IsGroup = false;
95
96 var oauthCardActivity = await Sender.Send(new MessageActivity(options.OAuthCardText), reference, token).ConfigureAwait(false);
97 await OnActivitySent(oauthCardActivity, ToActivityType()).ConfigureAwait(false);
98 }
99
100 var state = Convert.ToBase64String(JsonSerializer.SerializeToUtf8Bytes(tokenExchangeState));
101 var resource = await api.Bots.SignIn.GetResourceAsync(new() { State = state }).ConfigureAwait(false);
102 var activity = new MessageActivity();
103
104 activity.Recipient = Activity.From;
105 activity.Conversation = reference.Conversation;
106 activity.AddAttachment(new Api.Cards.OAuthCard()
107 {
108 Text = options.OAuthCardText,
109 ConnectionName = options.ConnectionName ?? ConnectionName,
110 TokenExchangeResource = resource.TokenExchangeResource,
111 TokenPostResource = resource.TokenPostResource,
112 Buttons = [
113 new(Teams.Api.Cards.ActionType.SignIn)
114 {
115 Title = options.SignInButtonText,
116 Value = resource.SignInLink
117 }
118 ]
119 });
120
121 var res = await Sender.Send(activity, reference, token).ConfigureAwait(false);
122 await OnActivitySent(res, ToActivityType()).ConfigureAwait(false);
123 return null;
124 }
125
126 public async Task SignIn(SSOOptions options, CancellationToken cancellationToken = default)
127 {
128 var token = cancellationToken == default ? CancellationToken : cancellationToken;
129 var signInLink = $"{options.SignInLink}?scope={Uri.EscapeDataString(string.Join(" ", options.Scopes))}&clientId={AppId}&tenantId={TenantId}";
130 var reference = Ref.Copy();
131
132 if (Activity.Conversation.IsGroup == true)
133 {
134 // create new 1:1 conversation with user to do SSO
135 // because groupchats don't support it.
136 var (id, _, _) = await Api.Conversations.CreateAsync(new()
137 {
138 TenantId = Ref.Conversation.TenantId,
139 Members = [Activity.From]
140 }).ConfigureAwait(false);
141
142 reference.Conversation.Id = id;
143 reference.Conversation.IsGroup = false;
144
145 var oauthCardActivity = await Sender.Send(new MessageActivity(options.OAuthCardText), reference, token).ConfigureAwait(false);
146 await OnActivitySent(oauthCardActivity, ToActivityType()).ConfigureAwait(false);
147 }
148
149 var activity = new MessageActivity();
150
151 activity.Recipient = Activity.From;
152 activity.Conversation = reference.Conversation;
153 activity.AddAttachment(new Api.Cards.OAuthCard()
154 {
155 Text = options.OAuthCardText,
156 TokenExchangeResource = new()
157 {
158 Id = Guid.NewGuid().ToString()
159 },
160 Buttons = [
161 new(Teams.Api.Cards.ActionType.SignIn)
162 {
163 Title = options.SignInButtonText,
164 Value = options.SignInLink
165 }
166 ]
167 });
168
169 var res = await Sender.Send(activity, reference, token).ConfigureAwait(false);
170 await OnActivitySent(res, ToActivityType()).ConfigureAwait(false);
171 }
172
173 public async Task SignOut(string? connectionName = null, CancellationToken cancellationToken = default)
174 {
175 var token = cancellationToken == default ? CancellationToken : cancellationToken;
176 var api = new ApiClient(Api, token);
177 await api.Users.Token.SignOutAsync(new()
178 {
179 ChannelId = Ref.ChannelId,
180 UserId = Activity.From.Id,
181 ConnectionName = connectionName ?? ConnectionName,
182 }).ConfigureAwait(false);
183 }
184}
185
186/// <summary>
187/// base sign in options type
188/// </summary>
189public abstract class SignInOptions
190{
191 /// <summary>
192 /// the oauth card text
193 /// </summary>
194 public string OAuthCardText { get; set; } = "Please Sign In...";
195
196 /// <summary>
197 /// the sign in button text
198 /// </summary>
199 public string SignInButtonText { get; set; } = "Sign In";
200}
201
202/// <summary>
203/// OAuth sign in options
204/// </summary>
205public class OAuthOptions : SignInOptions
206{
207 /// <summary>
208 /// the auth connection name to use, defaults
209 /// to the default connection name of the app
210 /// </summary>
211 public string? ConnectionName { get; set; }
212}
213
214/// <summary>
215/// SSO sign in options
216/// </summary>
217public class SSOOptions : SignInOptions
218{
219 /// <summary>
220 /// the scopes to request consent for
221 /// </summary>
222 public required string[] Scopes { get; set; }
223
224 /// <summary>
225 /// the sign in link to use, defaults to
226 /// the link returned by the sign in resource
227 /// </summary>
228 public required string SignInLink { get; set; }
229}