// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Teams.Common.Http; namespace Microsoft.Teams.Api.Clients; public class UserTokenClient : Client { public string TokenServiceUrl { get; set; } = "https://token.botframework.com"; private readonly JsonSerializerOptions _jsonSerializerOptions = new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; public UserTokenClient(CancellationToken cancellationToken = default) : base(cancellationToken) { } public UserTokenClient(IHttpClient client, CancellationToken cancellationToken = default) : base(client, cancellationToken) { } public UserTokenClient(IHttpClientOptions options, CancellationToken cancellationToken = default) : base(options, cancellationToken) { } public UserTokenClient(IHttpClientFactory factory, CancellationToken cancellationToken = default) : base(factory, cancellationToken) { } public async Task GetAsync(GetTokenRequest request, CancellationToken cancellationToken = default) { var token = cancellationToken != default ? cancellationToken : _cancellationToken; var query = QueryString.Serialize(request); var req = HttpRequest.Get($"{TokenServiceUrl}/api/usertoken/GetToken?{query}"); var res = await _http.SendAsync(req, token).ConfigureAwait(false); return res.Body; } public async Task> GetAadAsync(GetAadTokenRequest request, CancellationToken cancellationToken = default) { var token = cancellationToken != default ? cancellationToken : _cancellationToken; var query = QueryString.Serialize(request); var req = HttpRequest.Post($"{TokenServiceUrl}/api/usertoken/GetAadTokens?{query}", body: request); var res = await _http.SendAsync>(req, token).ConfigureAwait(false); return res.Body; } public async Task> GetStatusAsync(GetTokenStatusRequest request, CancellationToken cancellationToken = default) { var token = cancellationToken != default ? cancellationToken : _cancellationToken; var query = QueryString.Serialize(request); var req = HttpRequest.Get($"{TokenServiceUrl}/api/usertoken/GetTokenStatus?{query}"); var res = await _http.SendAsync>(req, token).ConfigureAwait(false); return res.Body; } public async Task SignOutAsync(SignOutRequest request, CancellationToken cancellationToken = default) { var token = cancellationToken != default ? cancellationToken : _cancellationToken; var query = QueryString.Serialize(request); var req = HttpRequest.Delete($"{TokenServiceUrl}/api/usertoken/SignOut?{query}"); await _http.SendAsync(req, token).ConfigureAwait(false); } public async Task ExchangeAsync(ExchangeTokenRequest request, CancellationToken cancellationToken = default) { var token = cancellationToken != default ? cancellationToken : _cancellationToken; var query = QueryString.Serialize(new { userId = request.UserId, connectionName = request.ConnectionName, channelId = request.ChannelId }); // This ensures that the request body is buffered so that when sent the `Content-Length` header is set. // This is required for the Bot Framework Token Service to process the request correctly. var body = JsonSerializer.Serialize(request.GetBody(), _jsonSerializerOptions); var req = HttpRequest.Post($"{TokenServiceUrl}/api/usertoken/exchange?{query}", body); req.Headers.Add("Content-Type", new List() { "application/json" }); var res = await _http.SendAsync(req, token).ConfigureAwait(false); return res.Body; } public class GetTokenRequest { [JsonPropertyName("userId")] [JsonPropertyOrder(0)] public required string UserId { get; set; } [JsonPropertyName("connectionName")] [JsonPropertyOrder(1)] public required string ConnectionName { get; set; } [JsonPropertyName("channelId")] [JsonPropertyOrder(2)] public ChannelId? ChannelId { get; set; } [JsonPropertyName("code")] [JsonPropertyOrder(3)] public string? Code { get; set; } } public class GetAadTokenRequest { [JsonPropertyName("userId")] [JsonPropertyOrder(0)] public required string UserId { get; set; } [JsonPropertyName("connectionName")] [JsonPropertyOrder(1)] public required string ConnectionName { get; set; } [JsonPropertyName("channelId")] [JsonPropertyOrder(2)] public required ChannelId ChannelId { get; set; } [JsonPropertyName("resourceUrls")] [JsonPropertyOrder(3)] public IList ResourceUrls { get; set; } = []; } public class GetTokenStatusRequest { [JsonPropertyName("userId")] [JsonPropertyOrder(0)] public required string UserId { get; set; } [JsonPropertyName("channelId")] [JsonPropertyOrder(1)] public required ChannelId ChannelId { get; set; } [JsonPropertyName("includeFilter")] [JsonPropertyOrder(2)] public string? IncludeFilter { get; set; } } public class SignOutRequest { [JsonPropertyName("userId")] [JsonPropertyOrder(0)] public required string UserId { get; set; } [JsonPropertyName("connectionName")] [JsonPropertyOrder(1)] public required string ConnectionName { get; set; } [JsonPropertyName("channelId")] [JsonPropertyOrder(2)] public required ChannelId ChannelId { get; set; } } public class ExchangeTokenRequest { [JsonPropertyName("userId")] [JsonPropertyOrder(0)] public required string UserId { get; set; } [JsonPropertyName("connectionName")] [JsonPropertyOrder(1)] public required string ConnectionName { get; set; } [JsonPropertyName("channelId")] [JsonPropertyOrder(2)] public required ChannelId ChannelId { get; set; } [JsonPropertyName("exchangeRequest")] [JsonPropertyOrder(3)] public required TokenExchange.Request ExchangeRequest { get; set; } internal TokenExchange.Request GetBody() => ExchangeRequest; } }