Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CertificateServiceClientCredentialsFactory handles public, Gov, and private clouds #6806

Merged
merged 4 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading