microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
fix/issue-274-skipauth-unauthorized

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

233lines · 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 });
68
69 return tokenResponse.Token;
70 }
71 catch { }
72
73 var tokenExchangeState = new Api.TokenExchange.State()
74 {
75 ConnectionName = options.ConnectionName ?? ConnectionName,
76 Conversation = reference,
77 RelatesTo = Activity.RelatesTo,
78 MsAppId = AppId
79 };
80
81 if (Activity.Conversation.IsGroup == true)
82 {
83 // create new 1:1 conversation with user to do SSO
84 // because groupchats don't support it.
85 var (id, _, _) = await api.Conversations.CreateAsync(new()
86 {
87 TenantId = Ref.Conversation.TenantId,
88 IsGroup = false,
89 Bot = Ref.Bot,
90 Members = [Activity.From]
91 });
92
93 reference.Conversation.Id = id;
94 reference.Conversation.IsGroup = false;
95
96 var oauthCardActivity = await Sender.Send(new MessageActivity(options.OAuthCardText), reference, token);
97 await OnActivitySent(oauthCardActivity, ToActivityType());
98 }
99
100 var state = Convert.ToBase64String(JsonSerializer.SerializeToUtf8Bytes(tokenExchangeState));
101 var resource = await api.Bots.SignIn.GetResourceAsync(new() { State = state });
102 var activity = new MessageActivity();
103
104 activity.InputHint = InputHint.AcceptingInput;
105 activity.Recipient = Activity.From;
106 activity.Conversation = reference.Conversation;
107 activity.AddAttachment(new Api.Cards.OAuthCard()
108 {
109 Text = options.OAuthCardText,
110 ConnectionName = options.ConnectionName ?? ConnectionName,
111 TokenExchangeResource = resource.TokenExchangeResource,
112 TokenPostResource = resource.TokenPostResource,
113 Buttons = [
114 new(Teams.Api.Cards.ActionType.SignIn)
115 {
116 Title = options.SignInButtonText,
117 Value = resource.SignInLink
118 }
119 ]
120 });
121
122 var res = await Sender.Send(activity, reference, token);
123 await OnActivitySent(res, ToActivityType());
124 return null;
125 }
126
127 public async Task SignIn(SSOOptions options, CancellationToken cancellationToken = default)
128 {
129 var token = cancellationToken == default ? CancellationToken : cancellationToken;
130 var signInLink = $"{options.SignInLink}?scope={Uri.EscapeDataString(string.Join(" ", options.Scopes))}&clientId={AppId}&tenantId={TenantId}";
131 var reference = Ref.Copy();
132
133 if (Activity.Conversation.IsGroup == true)
134 {
135 // create new 1:1 conversation with user to do SSO
136 // because groupchats don't support it.
137 var (id, _, _) = await Api.Conversations.CreateAsync(new()
138 {
139 TenantId = Ref.Conversation.TenantId,
140 IsGroup = false,
141 Bot = Ref.Bot,
142 Members = [Activity.From]
143 });
144
145 reference.Conversation.Id = id;
146 reference.Conversation.IsGroup = false;
147
148 var oauthCardActivity = await Sender.Send(new MessageActivity(options.OAuthCardText), reference, token);
149 await OnActivitySent(oauthCardActivity, ToActivityType());
150 }
151
152 var activity = new MessageActivity();
153
154 activity.InputHint = InputHint.AcceptingInput;
155 activity.Recipient = Activity.From;
156 activity.Conversation = reference.Conversation;
157 activity.AddAttachment(new Api.Cards.OAuthCard()
158 {
159 Text = options.OAuthCardText,
160 TokenExchangeResource = new()
161 {
162 Id = Guid.NewGuid().ToString()
163 },
164 Buttons = [
165 new(Teams.Api.Cards.ActionType.SignIn)
166 {
167 Title = options.SignInButtonText,
168 Value = options.SignInLink
169 }
170 ]
171 });
172
173 var res = await Sender.Send(activity, reference, token);
174 await OnActivitySent(res, ToActivityType());
175 }
176
177 public async Task SignOut(string? connectionName = null, CancellationToken cancellationToken = default)
178 {
179 var token = cancellationToken == default ? CancellationToken : cancellationToken;
180 var api = new ApiClient(Api, token);
181 await api.Users.Token.SignOutAsync(new()
182 {
183 ChannelId = Ref.ChannelId,
184 UserId = Activity.From.Id,
185 ConnectionName = connectionName ?? ConnectionName,
186 });
187 }
188}
189
190/// <summary>
191/// base sign in options type
192/// </summary>
193public abstract class SignInOptions
194{
195 /// <summary>
196 /// the oauth card text
197 /// </summary>
198 public string OAuthCardText { get; set; } = "Please Sign In...";
199
200 /// <summary>
201 /// the sign in button text
202 /// </summary>
203 public string SignInButtonText { get; set; } = "Sign In";
204}
205
206/// <summary>
207/// OAuth sign in options
208/// </summary>
209public class OAuthOptions : SignInOptions
210{
211 /// <summary>
212 /// the auth connection name to use, defaults
213 /// to the default connection name of the app
214 /// </summary>
215 public string? ConnectionName { get; set; }
216}
217
218/// <summary>
219/// SSO sign in options
220/// </summary>
221public class SSOOptions : SignInOptions
222{
223 /// <summary>
224 /// the scopes to request consent for
225 /// </summary>
226 public required string[] Scopes { get; set; }
227
228 /// <summary>
229 /// the sign in link to use, defaults to
230 /// the link returned by the sign in resource
231 /// </summary>
232 public required string SignInLink { get; set; }
233}