microsoft/teams.net

Public

mirrored from https://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feature/user-agent-header

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/src/Microsoft.Teams.Bot.Compat/CompatUserTokenClient.cs

195lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using Microsoft.Bot.Schema;
5using Microsoft.Teams.Bot.Core;
6using CompatAssemblyInfo;
7
8namespace Microsoft.Teams.Bot.Compat;
9
10/// <summary>
11/// Provides a compatibility layer that adapts the Teams Bot Core <see cref="UserTokenClient"/> to the Bot Framework's
12/// <see cref="Microsoft.Bot.Connector.Authentication.UserTokenClient"/> interface.
13/// </summary>
14/// <remarks>
15/// This adapter enables legacy Bot Framework bots to use the new Teams Bot Core token management system
16/// without code changes. It converts between the two different token result formats and delegates all operations
17/// to the underlying Core UserTokenClient.
18/// </remarks>
19internal sealed class CompatUserTokenClient : Microsoft.Bot.Connector.Authentication.UserTokenClient
20{
21 private readonly UserTokenClient _utc;
22
23 /// <summary>
24 /// Initializes a new instance of the <see cref="CompatUserTokenClient"/> class.
25 /// </summary>
26 /// <param name="utc">The underlying Teams Bot Core UserTokenClient that performs the actual token operations.</param>
27 public CompatUserTokenClient(UserTokenClient utc)
28 {
29 _utc = utc;
30
31 // Append Compat layer info to the wrapped client's User-Agent
32 if (_utc.DefaultCustomHeaders.TryGetValue("User-Agent", out var existingUA))
33 {
34 _utc.DefaultCustomHeaders["User-Agent"] = $"{ThisAssembly.AssemblyName}/{ThisAssembly.AssemblyInformationalVersion} {existingUA}";
35 }
36 else
37 {
38 _utc.DefaultCustomHeaders["User-Agent"] = $"{ThisAssembly.AssemblyName}/{ThisAssembly.AssemblyInformationalVersion}";
39 }
40 }
41
42 /// <summary>
43 /// Gets the status of all tokens for a specific user across all configured OAuth connections.
44 /// </summary>
45 /// <param name="userId">The unique identifier of the user. Cannot be null or empty.</param>
46 /// <param name="channelId">The channel identifier where the user is interacting. Cannot be null or empty.</param>
47 /// <param name="includeFilter">Optional filter to limit which token statuses are returned. Pass null or empty to include all.</param>
48 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
49 /// <returns>
50 /// A task that represents the asynchronous operation. The task result contains an array of <see cref="TokenStatus"/>
51 /// objects representing the status of each configured connection for the user.
52 /// </returns>
53 public async override Task<TokenStatus[]> GetTokenStatusAsync(string userId, string channelId, string includeFilter, CancellationToken cancellationToken)
54 {
55 GetTokenStatusResult[] res = await _utc.GetTokenStatusAsync(userId, channelId, includeFilter, cancellationToken).ConfigureAwait(false);
56 return res.Select(t => new TokenStatus
57 {
58 ChannelId = channelId,
59 ConnectionName = t.ConnectionName,
60 HasToken = t.HasToken,
61 ServiceProviderDisplayName = t.ServiceProviderDisplayName,
62 }).ToArray();
63 }
64
65 /// <summary>
66 /// Retrieves an OAuth token for a user from a specific connection.
67 /// </summary>
68 /// <param name="userId">The unique identifier of the user requesting the token. Cannot be null or empty.</param>
69 /// <param name="connectionName">The name of the OAuth connection configured in Azure Bot Service. Cannot be null or empty.</param>
70 /// <param name="channelId">The channel identifier where the user is interacting. Cannot be null or empty.</param>
71 /// <param name="magicCode">Optional magic code from the OAuth callback. Used to complete the OAuth flow when provided.</param>
72 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
73 /// <returns>
74 /// A task that represents the asynchronous operation. The task result contains a <see cref="TokenResponse"/> with
75 /// the OAuth token if available, or null if the user has not completed authentication for this connection.
76 /// </returns>
77 public async override Task<TokenResponse?> GetUserTokenAsync(string userId, string connectionName, string channelId, string magicCode, CancellationToken cancellationToken)
78 {
79 GetTokenResult? res = await _utc.GetTokenAsync(userId, connectionName, channelId, magicCode, cancellationToken).ConfigureAwait(false);
80 if (res == null)
81 {
82 return null;
83 }
84
85 return new TokenResponse
86 {
87 ChannelId = channelId,
88 ConnectionName = res.ConnectionName,
89 Token = res.Token
90 };
91 }
92
93 /// <summary>
94 /// Retrieves the sign-in resource (URL and exchange resources) needed to initiate an OAuth flow for a user.
95 /// </summary>
96 /// <param name="connectionName">The name of the OAuth connection configured in Azure Bot Service. Cannot be null or empty.</param>
97 /// <param name="activity">The activity associated with the sign-in request. Used to extract user and channel information. Cannot be null.</param>
98 /// <param name="finalRedirect">Optional URL to redirect the user to after completing authentication.</param>
99 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
100 /// <returns>
101 /// A task that represents the asynchronous operation. The task result contains a <see cref="SignInResource"/>
102 /// with the sign-in link and optional token exchange or post resources for completing the OAuth flow.
103 /// </returns>
104 /// <exception cref="ArgumentNullException">Thrown when <paramref name="activity"/> is null.</exception>
105 public async override Task<SignInResource> GetSignInResourceAsync(string connectionName, Activity activity, string finalRedirect, CancellationToken cancellationToken)
106 {
107 ArgumentNullException.ThrowIfNull(activity);
108 GetSignInResourceResult res = await _utc.GetSignInResource(activity.From.Id, connectionName, activity.ChannelId, finalRedirect, cancellationToken).ConfigureAwait(false);
109 SignInResource signInResource = new()
110 {
111 SignInLink = res!.SignInLink
112 };
113
114 if (res.TokenExchangeResource != null)
115 {
116 signInResource.TokenExchangeResource = new Microsoft.Bot.Schema.TokenExchangeResource
117 {
118 Id = res.TokenExchangeResource.Id,
119 Uri = res.TokenExchangeResource.Uri?.ToString(),
120 ProviderId = res.TokenExchangeResource.ProviderId
121 };
122 }
123
124 if (res.TokenPostResource != null)
125 {
126 signInResource.TokenPostResource = new Microsoft.Bot.Schema.TokenPostResource
127 {
128 SasUrl = res.TokenPostResource.SasUrl?.ToString()
129 };
130 }
131
132 return signInResource;
133 }
134
135 /// <summary>
136 /// Exchanges a token from one OAuth connection for a token from another connection using single sign-on (SSO).
137 /// </summary>
138 /// <param name="userId">The unique identifier of the user whose token is being exchanged. Cannot be null or empty.</param>
139 /// <param name="connectionName">The name of the target OAuth connection to exchange to. Cannot be null or empty.</param>
140 /// <param name="channelId">The channel identifier where the user is interacting. Cannot be null or empty.</param>
141 /// <param name="exchangeRequest">The token exchange request containing the source token. Cannot be null.</param>
142 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
143 /// <returns>
144 /// A task that represents the asynchronous operation. The task result contains a <see cref="TokenResponse"/>
145 /// with the exchanged token for the target connection.
146 /// </returns>
147 public async override Task<TokenResponse> ExchangeTokenAsync(string userId, string connectionName, string channelId,
148 TokenExchangeRequest exchangeRequest, CancellationToken cancellationToken)
149 {
150 GetTokenResult resp = await _utc.ExchangeTokenAsync(userId, connectionName, channelId, exchangeRequest.Token,
151 cancellationToken).ConfigureAwait(false);
152 return new TokenResponse
153 {
154 ChannelId = channelId,
155 ConnectionName = resp.ConnectionName,
156 Token = resp.Token
157 };
158 }
159
160 /// <summary>
161 /// Signs out a user from a specific OAuth connection, revoking their stored token.
162 /// </summary>
163 /// <param name="userId">The unique identifier of the user to sign out. Cannot be null or empty.</param>
164 /// <param name="connectionName">The name of the OAuth connection to sign out from. Cannot be null or empty.</param>
165 /// <param name="channelId">The channel identifier where the user is interacting. Cannot be null or empty.</param>
166 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
167 /// <returns>A task that represents the asynchronous sign-out operation.</returns>
168 public async override Task SignOutUserAsync(string userId, string connectionName, string channelId, CancellationToken cancellationToken)
169 {
170 await _utc.SignOutUserAsync(userId, connectionName, channelId, cancellationToken).ConfigureAwait(false);
171 }
172
173 /// <summary>
174 /// Retrieves Azure Active Directory (Azure AD) tokens for multiple resource URLs in a single request.
175 /// </summary>
176 /// <param name="userId">The unique identifier of the user requesting the tokens. Cannot be null or empty.</param>
177 /// <param name="connectionName">The name of the OAuth connection configured for Azure AD. Cannot be null or empty.</param>
178 /// <param name="resourceUrls">An array of resource URLs (e.g., "https://graph.microsoft.com") to request tokens for. Cannot be null.</param>
179 /// <param name="channelId">The channel identifier where the user is interacting. Cannot be null or empty.</param>
180 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
181 /// <returns>
182 /// A task that represents the asynchronous operation. The task result contains a dictionary mapping each
183 /// resource URL to its corresponding <see cref="TokenResponse"/>. Returns an empty dictionary if no tokens are available.
184 /// </returns>
185 public async override Task<Dictionary<string, TokenResponse>> GetAadTokensAsync(string userId, string connectionName, string[] resourceUrls, string channelId, CancellationToken cancellationToken)
186 {
187 IDictionary<string, GetTokenResult> res = await _utc.GetAadTokensAsync(userId, connectionName, channelId, resourceUrls, cancellationToken).ConfigureAwait(false);
188 return res?.ToDictionary(kvp => kvp.Key, kvp => new TokenResponse
189 {
190 ChannelId = channelId,
191 ConnectionName = kvp.Value.ConnectionName,
192 Token = kvp.Value.Token
193 }) ?? new Dictionary<string, TokenResponse>();
194 }
195}
196