microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/port-similar-changes-409

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

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