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