// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.Bot.Schema; using Microsoft.Teams.Core; namespace Microsoft.Teams.Apps.BotBuilder; /// /// Provides a compatibility layer that adapts the Teams Bot Core to the Bot Framework's /// interface. /// /// /// This adapter enables legacy Bot Framework bots to use the new Teams Bot Core token management system /// without code changes. It converts between the two different token result formats and delegates all operations /// to the underlying Core UserTokenClient. /// /// The underlying Teams Bot Core UserTokenClient that performs the actual token operations. internal sealed class CompatUserTokenClient(UserTokenClient utc) : Microsoft.Bot.Connector.Authentication.UserTokenClient { /// /// Gets the status of all tokens for a specific user across all configured OAuth connections. /// /// The unique identifier of the user. Cannot be null or empty. /// The channel identifier where the user is interacting. Cannot be null or empty. /// Optional filter to limit which token statuses are returned. Pass null or empty to include all. /// A cancellation token that can be used to cancel the asynchronous operation. /// /// A task that represents the asynchronous operation. The task result contains an array of /// objects representing the status of each configured connection for the user. /// public async override Task GetTokenStatusAsync(string userId, string channelId, string includeFilter, CancellationToken cancellationToken) { GetTokenStatusResult[] res = await utc.GetTokenStatusAsync(userId, channelId, includeFilter, cancellationToken).ConfigureAwait(false); return res.Select(t => new TokenStatus { ChannelId = channelId, ConnectionName = t.ConnectionName, HasToken = t.HasToken, ServiceProviderDisplayName = t.ServiceProviderDisplayName, }).ToArray(); } /// /// Retrieves an OAuth token for a user from a specific connection. /// /// The unique identifier of the user requesting the token. Cannot be null or empty. /// The name of the OAuth connection configured in Azure Bot Service. Cannot be null or empty. /// The channel identifier where the user is interacting. Cannot be null or empty. /// Optional magic code from the OAuth callback. Used to complete the OAuth flow when provided. /// A cancellation token that can be used to cancel the asynchronous operation. /// /// A task that represents the asynchronous operation. The task result contains a with /// the OAuth token if available, or null if the user has not completed authentication for this connection. /// public async override Task GetUserTokenAsync(string userId, string connectionName, string channelId, string magicCode, CancellationToken cancellationToken) { GetTokenResult? res = await utc.GetTokenAsync(userId, connectionName, channelId, magicCode, cancellationToken).ConfigureAwait(false); if (res == null) { return null; } return new TokenResponse { ChannelId = channelId, ConnectionName = res.ConnectionName, Token = res.Token }; } /// /// Retrieves the sign-in resource (URL and exchange resources) needed to initiate an OAuth flow for a user. /// /// The name of the OAuth connection configured in Azure Bot Service. Cannot be null or empty. /// The activity associated with the sign-in request. Used to extract user and channel information. Cannot be null. /// Optional URL to redirect the user to after completing authentication. /// A cancellation token that can be used to cancel the asynchronous operation. /// /// A task that represents the asynchronous operation. The task result contains a /// with the sign-in link and optional token exchange or post resources for completing the OAuth flow. /// /// Thrown when is null. public async override Task GetSignInResourceAsync(string connectionName, Activity activity, string finalRedirect, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(activity); GetSignInResourceResult res = await utc.GetSignInResourceAsync(activity.From.Id, connectionName, activity.ChannelId, finalRedirect, cancellationToken).ConfigureAwait(false); SignInResource signInResource = new() { SignInLink = res!.SignInLink }; if (res.TokenExchangeResource != null) { signInResource.TokenExchangeResource = new Microsoft.Bot.Schema.TokenExchangeResource { Id = res.TokenExchangeResource.Id, Uri = res.TokenExchangeResource.Uri?.ToString(), ProviderId = res.TokenExchangeResource.ProviderId }; } if (res.TokenPostResource != null) { signInResource.TokenPostResource = new Microsoft.Bot.Schema.TokenPostResource { SasUrl = res.TokenPostResource.SasUrl?.ToString() }; } return signInResource; } /// /// Exchanges a token from one OAuth connection for a token from another connection using single sign-on (SSO). /// /// The unique identifier of the user whose token is being exchanged. Cannot be null or empty. /// The name of the target OAuth connection to exchange to. Cannot be null or empty. /// The channel identifier where the user is interacting. Cannot be null or empty. /// The token exchange request containing the source token. Cannot be null. /// A cancellation token that can be used to cancel the asynchronous operation. /// /// A task that represents the asynchronous operation. The task result contains a /// with the exchanged token for the target connection. /// public async override Task ExchangeTokenAsync(string userId, string connectionName, string channelId, TokenExchangeRequest exchangeRequest, CancellationToken cancellationToken) { GetTokenResult resp = await utc.ExchangeTokenAsync(userId, connectionName, channelId, exchangeRequest.Token, cancellationToken).ConfigureAwait(false); return new TokenResponse { ChannelId = channelId, ConnectionName = resp.ConnectionName, Token = resp.Token }; } /// /// Signs out a user from a specific OAuth connection, revoking their stored token. /// /// The unique identifier of the user to sign out. Cannot be null or empty. /// The name of the OAuth connection to sign out from. Cannot be null or empty. /// The channel identifier where the user is interacting. Cannot be null or empty. /// A cancellation token that can be used to cancel the asynchronous operation. /// A task that represents the asynchronous sign-out operation. public async override Task SignOutUserAsync(string userId, string connectionName, string channelId, CancellationToken cancellationToken) { await utc.SignOutUserAsync(userId, connectionName, channelId, cancellationToken).ConfigureAwait(false); } /// /// Retrieves Azure Active Directory (Azure AD) tokens for multiple resource URLs in a single request. /// /// The unique identifier of the user requesting the tokens. Cannot be null or empty. /// The name of the OAuth connection configured for Azure AD. Cannot be null or empty. /// An array of resource URLs (e.g., "https://graph.microsoft.com") to request tokens for. Cannot be null. /// The channel identifier where the user is interacting. Cannot be null or empty. /// A cancellation token that can be used to cancel the asynchronous operation. /// /// A task that represents the asynchronous operation. The task result contains a dictionary mapping each /// resource URL to its corresponding . Returns an empty dictionary if no tokens are available. /// public async override Task> GetAadTokensAsync(string userId, string connectionName, string[] resourceUrls, string channelId, CancellationToken cancellationToken) { IDictionary res = await utc.GetAadTokensAsync(userId, connectionName, channelId, resourceUrls, cancellationToken).ConfigureAwait(false); return res?.ToDictionary(kvp => kvp.Key, kvp => new TokenResponse { ChannelId = channelId, ConnectionName = kvp.Value.ConnectionName, Token = kvp.Value.Token }) ?? new Dictionary(); } }