From a40e810be9a6d19df45c8d3f38345db64a0bf60b Mon Sep 17 00:00:00 2001 From: Craig Edmunds Date: Mon, 9 Dec 2024 13:41:56 +0000 Subject: [PATCH] CDMS-181 provides the TokenCredential by using ConfidentialClientApplicationBuilder --- Btms.Azure/AzureService.cs | 15 +++---- ...dentialClientApplicationTokenCredential.cs | 45 +++++++++++++++++++ Btms.Azure/Extensions/IServiceExtensions.cs | 2 +- Btms.Backend/Config/ApiOptions.cs | 3 +- Btms.BlobService/BlobService.cs | 3 +- Btms.BlobService/BlobServiceClientFactory.cs | 3 +- 6 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 Btms.Azure/ConfidentialClientApplicationTokenCredential.cs diff --git a/Btms.Azure/AzureService.cs b/Btms.Azure/AzureService.cs index 955741c9..036ec6f3 100644 --- a/Btms.Azure/AzureService.cs +++ b/Btms.Azure/AzureService.cs @@ -4,18 +4,20 @@ using Azure.Core.Pipeline; using Azure.Identity; using Btms.Azure.Extensions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Identity.Client; namespace Btms.Azure; + public abstract class AzureService { protected readonly TokenCredential Credentials; protected readonly HttpClientTransport? Transport; protected readonly ILogger Logger; - protected AzureService(ILogger logger, IAzureConfig config, IHttpClientFactory? clientFactory = null) + protected AzureService(IServiceProvider serviceProvider, ILogger logger, IAzureConfig config, IHttpClientFactory? clientFactory = null) { Logger = logger; using AzureEventSourceListener listener = AzureEventSourceListener.CreateConsoleLogger(EventLevel.Verbose); @@ -25,16 +27,9 @@ protected AzureService(ILogger logger, IAzureConfig config, IHttpClientFactory? logger.LogDebug("Creating azure credentials based on config vars for {AzureClientId}", config.AzureClientId); - var httpClientFactory = serviceProvider.GetRequiredService(); - - var app = PublicClientApplicationBuilder.Create("") - // [...] Your MSAL app setup - .WithHttpClientFactory(httpClientFactory) - .Build(); - Credentials = - new ClientSecretCredential(config.AzureTenantId, config.AzureClientId, config.AzureClientSecret); - + new ConfidentialClientApplicationTokenCredential(serviceProvider, config); + logger.LogDebug("Created azure credentials"); } else diff --git a/Btms.Azure/ConfidentialClientApplicationTokenCredential.cs b/Btms.Azure/ConfidentialClientApplicationTokenCredential.cs new file mode 100644 index 00000000..db8df43e --- /dev/null +++ b/Btms.Azure/ConfidentialClientApplicationTokenCredential.cs @@ -0,0 +1,45 @@ +using Azure.Core; +using Btms.Azure.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Identity.Client; + +namespace Btms.Azure; + +/// +/// Takes care of retriving a token via ConfidentialClientApplicationBuilder +/// which allows us to inject our CDP_HTTPS_PROXY based http client. +/// +/// It's unclear why this isn't available out of the box! +/// - IMsalHttpClientFactory isn't used by ClientSecretCredential +/// - The ClientSecretCredential has an internal constructor accepting MsalConfidentialClient but nothing seems to use it +/// - MsalConfidentialClient is itself internal +/// +/// +/// +public class ConfidentialClientApplicationTokenCredential : TokenCredential +{ + private readonly string[] _scopes = { "https://storage.azure.com/.default" }; + + private readonly IConfidentialClientApplication _app; + public ConfidentialClientApplicationTokenCredential(IServiceProvider serviceProvider, IAzureConfig config) + { + var httpClientFactory = serviceProvider.GetRequiredService(); + + _app = ConfidentialClientApplicationBuilder.Create(config.AzureClientId) + .WithHttpClientFactory(httpClientFactory) + .WithTenantId(config.AzureTenantId) + .WithClientSecret(config.AzureClientSecret) + .Build(); + } + public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + var authResult = _app.AcquireTokenForClient(_scopes).ExecuteAsync(cancellationToken).Result; + return ValueTask.FromResult(new AccessToken(authResult.AccessToken, authResult.ExpiresOn)); + } + + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + var authResult = _app.AcquireTokenForClient(_scopes).ExecuteAsync(cancellationToken).Result; + return new AccessToken(authResult.AccessToken, authResult.ExpiresOn); + } +} \ No newline at end of file diff --git a/Btms.Azure/Extensions/IServiceExtensions.cs b/Btms.Azure/Extensions/IServiceExtensions.cs index 876de462..98535021 100644 --- a/Btms.Azure/Extensions/IServiceExtensions.cs +++ b/Btms.Azure/Extensions/IServiceExtensions.cs @@ -10,7 +10,7 @@ namespace Btms.Azure.Extensions; /// /// The Azure SDK doesn't use the CDP proxied Http Client by default, so previously we used the HTTPS_CLIENT env var to /// send the requests via CDPs squid proxy. This code is intended to use the http client we already setup that uses the proxy -/// when the CDP_HTTPS_PROXY is set. +/// when the CDP_HTTPS_PROXY env var is set. /// Code borrowed from https://anthonysimmon.com/overriding-msal-httpclient-with-ihttpclientfactory/ /// /// diff --git a/Btms.Backend/Config/ApiOptions.cs b/Btms.Backend/Config/ApiOptions.cs index 788734bf..d4a722a5 100644 --- a/Btms.Backend/Config/ApiOptions.cs +++ b/Btms.Backend/Config/ApiOptions.cs @@ -16,7 +16,8 @@ public class ApiOptions // This is used by the azure library when connecting to auth related services // when connecting to blob storage [ConfigurationKeyName("HTTPS_PROXY")] - public string? HttpsProxy => CdpHttpsProxy?.Contains("://") == true ? CdpHttpsProxy[(CdpHttpsProxy.IndexOf("://", StringComparison.Ordinal) + 3)..] : null; + public string? HttpsProxy { get; set; } + // public string? HttpsProxy => CdpHttpsProxy?.Contains("://") == true ? CdpHttpsProxy[(CdpHttpsProxy.IndexOf("://", StringComparison.Ordinal) + 3)..] : null; public Dictionary Credentials { get; set; } = []; diff --git a/Btms.BlobService/BlobService.cs b/Btms.BlobService/BlobService.cs index f85195e5..d746639e 100644 --- a/Btms.BlobService/BlobService.cs +++ b/Btms.BlobService/BlobService.cs @@ -11,11 +11,12 @@ namespace Btms.BlobService; public class BlobService( + IServiceProvider serviceProvider, IBlobServiceClientFactory blobServiceClientFactory, ILogger logger, IOptions options, IHttpClientFactory clientFactory) - : AzureService(logger, options.Value, clientFactory), IBlobService + : AzureService(serviceProvider, logger, options.Value, clientFactory), IBlobService { private BlobContainerClient CreateBlobClient(int timeout = default, int retries = default) { diff --git a/Btms.BlobService/BlobServiceClientFactory.cs b/Btms.BlobService/BlobServiceClientFactory.cs index 9e70c0d0..7ce84d49 100644 --- a/Btms.BlobService/BlobServiceClientFactory.cs +++ b/Btms.BlobService/BlobServiceClientFactory.cs @@ -6,10 +6,11 @@ namespace Btms.BlobService; public class BlobServiceClientFactory( + IServiceProvider serviceProvider, IOptions options, ILogger logger, IHttpClientFactory? clientFactory = null) - : AzureService(logger, options.Value, clientFactory), IBlobServiceClientFactory + : AzureService(serviceProvider, logger, options.Value, clientFactory), IBlobServiceClientFactory { public BlobServiceClient CreateBlobServiceClient(int timeout = default, int retries = default) {