Skip to content

Commit

Permalink
CertificateServiceClientCredentialsFactory handles public, Gov, and p…
Browse files Browse the repository at this point in the history
…rivate clouds (#6806)

* Added CertificateGovernmentAppCredentials

* CertificateServiceClientCredentialsFactory handles private clouds

* Fixed CertificateServiceClientCredentialsFactory formatting

* CertificateServiceClientCredentialsFactory test updates

---------

Co-authored-by: Tracy Boehrer <trboehre@microsoft.com>
  • Loading branch information
tracyboehrer and Tracy Boehrer authored Jun 24, 2024
1 parent 3a59c0b commit 074b7db
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public CertificateAppCredentials(CertificateAppCredentialsOptions options)
/// Initializes a new instance of the <see cref="CertificateAppCredentials"/> class.
/// </summary>
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
/// <param name="appId">Microsoft application Id related to the certifiacte.</param>
/// <param name="appId">Microsoft application Id related to the certificate.</param>
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
/// <param name="customHttpClient">Optional <see cref="HttpClient"/> to be used when acquiring tokens.</param>
/// <param name="logger">Optional <see cref="ILogger"/> to gather telemetry data while acquiring and managing credentials.</param>
Expand All @@ -57,7 +57,7 @@ public CertificateAppCredentials(X509Certificate2 clientCertificate, string appI
/// </summary>
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
/// <param name="sendX5c">This parameter, if true, enables application developers to achieve easy certificates roll-over in Azure AD: setting this parameter to true will send the public certificate to Azure AD along with the token request, so that Azure AD can use it to validate the subject name based on a trusted issuer policy. </param>
/// <param name="appId">Microsoft application Id related to the certifiacte.</param>
/// <param name="appId">Microsoft application Id related to the certificate.</param>
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
/// <param name="customHttpClient">Optional <see cref="HttpClient"/> to be used when acquiring tokens.</param>
/// <param name="logger">Optional <see cref="ILogger"/> to gather telemetry data while acquiring and managing credentials.</param>
Expand All @@ -70,7 +70,7 @@ public CertificateAppCredentials(X509Certificate2 clientCertificate, bool sendX5
/// Initializes a new instance of the <see cref="CertificateAppCredentials"/> class.
/// </summary>
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
/// <param name="appId">Microsoft application Id related to the certifiacte.</param>
/// <param name="appId">Microsoft application Id related to the certificate.</param>
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
/// <param name="oAuthScope">Optional. The scope for the token.</param>
/// <param name="sendX5c">Optional. This parameter, if true, enables application developers to achieve easy certificates roll-over in Azure AD: setting this parameter to true will send the public certificate to Azure AD along with the token request, so that Azure AD can use it to validate the subject name based on a trusted issuer policy. </param>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Logging;

namespace Microsoft.Bot.Connector.Authentication
{
/// <summary>
/// CertificateGovAppCredentials auth implementation for Gov Cloud.
/// </summary>
public class CertificateGovernmentAppCredentials : CertificateAppCredentials
{
/// <summary>
/// Initializes a new instance of the <see cref="CertificateGovernmentAppCredentials"/> class.
/// </summary>
/// <param name="options">Options for this CertificateAppCredentials.</param>
public CertificateGovernmentAppCredentials(CertificateAppCredentialsOptions options)
: base(options)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CertificateGovernmentAppCredentials"/> class.
/// </summary>
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
/// <param name="appId">Microsoft application Id related to the certificate.</param>
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
/// <param name="customHttpClient">Optional <see cref="HttpClient"/> to be used when acquiring tokens.</param>
/// <param name="logger">Optional <see cref="ILogger"/> to gather telemetry data while acquiring and managing credentials.</param>
public CertificateGovernmentAppCredentials(X509Certificate2 clientCertificate, string appId, string channelAuthTenant = null, HttpClient customHttpClient = null, ILogger logger = null)
: base(clientCertificate, appId, channelAuthTenant, customHttpClient, logger)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CertificateGovernmentAppCredentials"/> class.
/// </summary>
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
/// <param name="sendX5c">This parameter, if true, enables application developers to achieve easy certificates roll-over in Azure AD: setting this parameter to true will send the public certificate to Azure AD along with the token request, so that Azure AD can use it to validate the subject name based on a trusted issuer policy. </param>
/// <param name="appId">Microsoft application Id related to the certificate.</param>
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
/// <param name="customHttpClient">Optional <see cref="HttpClient"/> to be used when acquiring tokens.</param>
/// <param name="logger">Optional <see cref="ILogger"/> to gather telemetry data while acquiring and managing credentials.</param>
public CertificateGovernmentAppCredentials(X509Certificate2 clientCertificate, bool sendX5c, string appId, string channelAuthTenant = null, HttpClient customHttpClient = null, ILogger logger = null)
: base(clientCertificate, sendX5c, appId, channelAuthTenant, customHttpClient, logger)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CertificateGovernmentAppCredentials"/> class.
/// </summary>
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
/// <param name="appId">Microsoft application Id related to the certificate.</param>
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
/// <param name="oAuthScope">Optional. The scope for the token.</param>
/// <param name="sendX5c">Optional. This parameter, if true, enables application developers to achieve easy certificates roll-over in Azure AD: setting this parameter to true will send the public certificate to Azure AD along with the token request, so that Azure AD can use it to validate the subject name based on a trusted issuer policy. </param>
/// <param name="customHttpClient">Optional <see cref="HttpClient"/> to be used when acquiring tokens.</param>
/// <param name="logger">Optional <see cref="ILogger"/> to gather telemetry data while acquiring and managing credentials.</param>
public CertificateGovernmentAppCredentials(X509Certificate2 clientCertificate, string appId, string channelAuthTenant = null, string oAuthScope = null, bool sendX5c = false, HttpClient customHttpClient = null, ILogger logger = null)
: base(clientCertificate, appId, channelAuthTenant, oAuthScope, sendX5c, customHttpClient, logger)
{
}

/// <inheritdoc/>
protected override string DefaultChannelAuthTenant => GovernmentAuthenticationConstants.DefaultChannelAuthTenant;

/// <inheritdoc/>
protected override string ToChannelFromBotOAuthScope => GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope;

/// <inheritdoc/>
protected override string ToChannelFromBotLoginUrlTemplate => GovernmentAuthenticationConstants.ToChannelFromBotLoginUrlTemplate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class CertificateServiceClientCredentialsFactory : ServiceClientCredentia
private readonly bool _sendX5c;
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
private readonly ConcurrentDictionary<string, CertificateAppCredentials> _certificateAppCredentialsByAudience = new ConcurrentDictionary<string, CertificateAppCredentials>();
private readonly ConcurrentDictionary<string, CertificateAppCredentials> _certificateAppCredentialsByAudience = new ();

/// <summary>
/// Initializes a new instance of the <see cref="CertificateServiceClientCredentialsFactory"/> class.
Expand Down Expand Up @@ -80,20 +80,88 @@ public override Task<ServiceClientCredentials> CreateCredentialsAsync(
throw new InvalidOperationException("Invalid Managed ID.");
}

// Instance must be reused per audience, otherwise it will cause throttling on AAD.
var certificateAppCredentials = _certificateAppCredentialsByAudience.GetOrAdd(audience, (audience) =>
if (loginEndpoint.Equals(AuthenticationConstants.ToChannelFromBotLoginUrlTemplate, StringComparison.OrdinalIgnoreCase))
{
return new CertificateAppCredentials(
_certificate,
_appId,
_tenantId,
audience,
_sendX5c,
_httpClient,
_logger);
});
return Task.FromResult<ServiceClientCredentials>(_certificateAppCredentialsByAudience.GetOrAdd(audience, (audience) =>
{
return new CertificateAppCredentials(
_certificate,
_appId,
_tenantId,
audience,
_sendX5c,
_httpClient,
_logger);
}));
}
else if (loginEndpoint.Equals(GovernmentAuthenticationConstants.ToChannelFromBotLoginUrlTemplate, StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult<ServiceClientCredentials>(_certificateAppCredentialsByAudience.GetOrAdd(audience, (audience) =>
{
return new CertificateGovernmentAppCredentials(
_certificate,
_appId,
_tenantId,
audience,
_sendX5c,
_httpClient,
_logger);
}));
}
else
{
return Task.FromResult<ServiceClientCredentials>(_certificateAppCredentialsByAudience.GetOrAdd(audience, (audience) =>
{
return new CertificatePrivateCloudAppCredentials(
_certificate,
_appId,
_tenantId,
audience,
_sendX5c,
loginEndpoint,
validateAuthority,
_httpClient,
_logger);
}));
}
}

return Task.FromResult<ServiceClientCredentials>(certificateAppCredentials);
private class CertificatePrivateCloudAppCredentials : CertificateAppCredentials
{
private readonly string _oAuthEndpoint;
private readonly bool _validateAuthority;

public CertificatePrivateCloudAppCredentials(CertificateAppCredentialsOptions options)
: base(options)
{
}

public CertificatePrivateCloudAppCredentials(X509Certificate2 clientCertificate, string appId, string channelAuthTenant = null, HttpClient customHttpClient = null, ILogger logger = null)
: base(clientCertificate, appId, channelAuthTenant, customHttpClient, logger)
{
}

public CertificatePrivateCloudAppCredentials(X509Certificate2 clientCertificate, bool sendX5c, string appId, string channelAuthTenant = null, HttpClient customHttpClient = null, ILogger logger = null)
: base(clientCertificate, sendX5c, appId, channelAuthTenant, customHttpClient, logger)
{
}

public CertificatePrivateCloudAppCredentials(X509Certificate2 clientCertificate, string appId, string channelAuthTenant, string oAuthScope, bool sendX5c, string oAuthEndpoint, bool validateAuthority, HttpClient customHttpClient = null, ILogger logger = null)
: base(clientCertificate, appId, channelAuthTenant, oAuthScope, sendX5c, customHttpClient, logger)
{
_oAuthEndpoint = oAuthEndpoint;
_validateAuthority = validateAuthority;
}

public override string OAuthEndpoint
{
get { return _oAuthEndpoint; }
}

public override bool ValidateAuthority
{
get { return _validateAuthority; }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public class CertificateServiceClientCredentialsFactoryTests
private const string TestAppId = nameof(TestAppId);
private const string TestTenantId = nameof(TestTenantId);
private const string TestAudience = nameof(TestAudience);
private const string LoginEndpoint = "https://login.microsoftonline.com";
private const string LoginEndpoint = AuthenticationConstants.ToChannelFromBotLoginUrlTemplate;
private const string GovLoginEndpoint = GovernmentAuthenticationConstants.ToChannelFromBotLoginUrlTemplate;
private const string PrivateLoginEndpoint = "https://login.privatecloud.com";
private readonly Mock<ILogger> logger = new Mock<ILogger>();
private readonly Mock<X509Certificate2> certificate = new Mock<X509Certificate2>();

Expand Down Expand Up @@ -64,7 +66,7 @@ public void IsAuthenticationDisabledTest()
}

[Fact]
public async void CanCreateCredentials()
public async void CanCreatePublicCredentials()
{
var factory = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId);

Expand All @@ -75,6 +77,31 @@ public async void CanCreateCredentials()
Assert.IsType<CertificateAppCredentials>(credentials);
}

[Fact]
public async void CanCreateGovCredentials()
{
var factory = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId);

var credentials = await factory.CreateCredentialsAsync(
TestAppId, TestAudience, GovLoginEndpoint, true, CancellationToken.None);

Assert.NotNull(credentials);
Assert.IsType<CertificateGovernmentAppCredentials>(credentials);
}

[Fact]
public async void CanCreatePrivateCredentials()
{
var factory = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId);

var credentials = await factory.CreateCredentialsAsync(
TestAppId, TestAudience, PrivateLoginEndpoint, true, CancellationToken.None);

Assert.NotNull(credentials);
Assert.IsAssignableFrom<CertificateAppCredentials>(credentials);
Assert.Equal(PrivateLoginEndpoint, ((CertificateAppCredentials)credentials).OAuthEndpoint);
}

[Fact]
public async void ShouldCreateUniqueCredentialsByAudience()
{
Expand Down

0 comments on commit 074b7db

Please sign in to comment.