CredentialProvider.Microsoft/CredentialProviders/Vsts/VstsSessionTokenClient.cs (99 lines of code) (raw):

// Copyright (c) Microsoft. All rights reserved. // // Licensed under the MIT license. using System; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using NuGetCredentialProvider.Logging; using NuGetCredentialProvider.Util; namespace NuGetCredentialProvider.CredentialProviders.Vsts { public class VstsSessionTokenClient : IVstsSessionTokenClient { private const string TokenScope = "vso.packaging_write vso.drop_write"; private static readonly JsonSerializerOptions options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true }; private readonly Uri vstsUri; private readonly string bearerToken; private readonly IAuthUtil authUtil; private readonly ILogger logger; public VstsSessionTokenClient(Uri vstsUri, string bearerToken, IAuthUtil authUtil, ILogger logger) { this.vstsUri = vstsUri ?? throw new ArgumentNullException(nameof(vstsUri)); this.bearerToken = bearerToken ?? throw new ArgumentNullException(nameof(bearerToken)); this.authUtil = authUtil ?? throw new ArgumentNullException(nameof(authUtil)); this.logger = logger ?? throw new ArgumentException(nameof(logger)); } private HttpRequestMessage CreateRequest(Uri uri, DateTime? validTo) { var request = new HttpRequestMessage(HttpMethod.Post, uri); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken); var tokenRequest = new VstsSessionToken() { DisplayName = "Azure DevOps Artifacts Credential Provider", Scope = TokenScope, ValidTo = validTo }; request.Content = new StringContent( JsonSerializer.Serialize(tokenRequest, options), Encoding.UTF8, "application/json"); return request; } public async Task<string> CreateSessionTokenAsync(VstsTokenType tokenType, DateTime validTo, CancellationToken cancellationToken) { var spsEndpoint = await authUtil.GetAuthorizationEndpoint(vstsUri, cancellationToken); if (spsEndpoint == null) { return null; } var uriBuilder = new UriBuilder(spsEndpoint) { Query = $"tokenType={tokenType}&api-version=5.0-preview.1" }; uriBuilder.Path = uriBuilder.Path.TrimEnd('/') + "/_apis/Token/SessionTokens"; var httpClient = HttpClientFactory.Default.GetHttpClient(); using (var request = CreateRequest(uriBuilder.Uri, validTo)) using (var response = await httpClient.SendAsync(request, cancellationToken)) { logger.LogResponse(NuGet.Common.LogLevel.Verbose, true, response); string serializedResponse; if (response.StatusCode == System.Net.HttpStatusCode.BadRequest) { request.Dispose(); response.Dispose(); logger.Log(NuGet.Common.LogLevel.Verbose, true, "Re-trying with service-defined valid-time."); using (var request2 = CreateRequest(uriBuilder.Uri, validTo: null)) using(var response2 = await httpClient.SendAsync(request2, cancellationToken)) { response2.EnsureSuccessStatusCode(); logger.LogResponse(NuGet.Common.LogLevel.Verbose, true, response2); serializedResponse = await response2.Content.ReadAsStringAsync(); } } else { response.EnsureSuccessStatusCode(); serializedResponse = await response.Content.ReadAsStringAsync(); } var responseToken = JsonSerializer.Deserialize<VstsSessionToken>(serializedResponse, options); if (validTo.Subtract(responseToken.ValidTo.Value).TotalHours > 1.0) { logger.Log(NuGet.Common.LogLevel.Information, true, $"Requested {validTo} but received {responseToken.ValidTo}"); } return responseToken.Token; } } } public enum VstsTokenType { Compact, // Personal Access Token (PAT) SelfDescribing // Session Token (JWT) } }