microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
core/sso-in-channels

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

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