Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.

Commit 074b7db

Browse files
tracyboehrerTracy Boehrer
andauthored
CertificateServiceClientCredentialsFactory handles public, Gov, and private clouds (#6806)
* Added CertificateGovernmentAppCredentials * CertificateServiceClientCredentialsFactory handles private clouds * Fixed CertificateServiceClientCredentialsFactory formatting * CertificateServiceClientCredentialsFactory test updates --------- Co-authored-by: Tracy Boehrer <[email protected]>
1 parent 3a59c0b commit 074b7db

File tree

4 files changed

+188
-18
lines changed

4 files changed

+188
-18
lines changed

libraries/Microsoft.Bot.Connector/Authentication/CertificateAppCredentials.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public CertificateAppCredentials(CertificateAppCredentialsOptions options)
4343
/// Initializes a new instance of the <see cref="CertificateAppCredentials"/> class.
4444
/// </summary>
4545
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
46-
/// <param name="appId">Microsoft application Id related to the certifiacte.</param>
46+
/// <param name="appId">Microsoft application Id related to the certificate.</param>
4747
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
4848
/// <param name="customHttpClient">Optional <see cref="HttpClient"/> to be used when acquiring tokens.</param>
4949
/// <param name="logger">Optional <see cref="ILogger"/> to gather telemetry data while acquiring and managing credentials.</param>
@@ -57,7 +57,7 @@ public CertificateAppCredentials(X509Certificate2 clientCertificate, string appI
5757
/// </summary>
5858
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
5959
/// <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>
60-
/// <param name="appId">Microsoft application Id related to the certifiacte.</param>
60+
/// <param name="appId">Microsoft application Id related to the certificate.</param>
6161
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
6262
/// <param name="customHttpClient">Optional <see cref="HttpClient"/> to be used when acquiring tokens.</param>
6363
/// <param name="logger">Optional <see cref="ILogger"/> to gather telemetry data while acquiring and managing credentials.</param>
@@ -70,7 +70,7 @@ public CertificateAppCredentials(X509Certificate2 clientCertificate, bool sendX5
7070
/// Initializes a new instance of the <see cref="CertificateAppCredentials"/> class.
7171
/// </summary>
7272
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
73-
/// <param name="appId">Microsoft application Id related to the certifiacte.</param>
73+
/// <param name="appId">Microsoft application Id related to the certificate.</param>
7474
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
7575
/// <param name="oAuthScope">Optional. The scope for the token.</param>
7676
/// <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>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Net.Http;
5+
using System.Security.Cryptography.X509Certificates;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Microsoft.Bot.Connector.Authentication
9+
{
10+
/// <summary>
11+
/// CertificateGovAppCredentials auth implementation for Gov Cloud.
12+
/// </summary>
13+
public class CertificateGovernmentAppCredentials : CertificateAppCredentials
14+
{
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="CertificateGovernmentAppCredentials"/> class.
17+
/// </summary>
18+
/// <param name="options">Options for this CertificateAppCredentials.</param>
19+
public CertificateGovernmentAppCredentials(CertificateAppCredentialsOptions options)
20+
: base(options)
21+
{
22+
}
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="CertificateGovernmentAppCredentials"/> class.
26+
/// </summary>
27+
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
28+
/// <param name="appId">Microsoft application Id related to the certificate.</param>
29+
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
30+
/// <param name="customHttpClient">Optional <see cref="HttpClient"/> to be used when acquiring tokens.</param>
31+
/// <param name="logger">Optional <see cref="ILogger"/> to gather telemetry data while acquiring and managing credentials.</param>
32+
public CertificateGovernmentAppCredentials(X509Certificate2 clientCertificate, string appId, string channelAuthTenant = null, HttpClient customHttpClient = null, ILogger logger = null)
33+
: base(clientCertificate, appId, channelAuthTenant, customHttpClient, logger)
34+
{
35+
}
36+
37+
/// <summary>
38+
/// Initializes a new instance of the <see cref="CertificateGovernmentAppCredentials"/> class.
39+
/// </summary>
40+
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
41+
/// <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>
42+
/// <param name="appId">Microsoft application Id related to the certificate.</param>
43+
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
44+
/// <param name="customHttpClient">Optional <see cref="HttpClient"/> to be used when acquiring tokens.</param>
45+
/// <param name="logger">Optional <see cref="ILogger"/> to gather telemetry data while acquiring and managing credentials.</param>
46+
public CertificateGovernmentAppCredentials(X509Certificate2 clientCertificate, bool sendX5c, string appId, string channelAuthTenant = null, HttpClient customHttpClient = null, ILogger logger = null)
47+
: base(clientCertificate, sendX5c, appId, channelAuthTenant, customHttpClient, logger)
48+
{
49+
}
50+
51+
/// <summary>
52+
/// Initializes a new instance of the <see cref="CertificateGovernmentAppCredentials"/> class.
53+
/// </summary>
54+
/// <param name="clientCertificate">Client certificate to be presented for authentication.</param>
55+
/// <param name="appId">Microsoft application Id related to the certificate.</param>
56+
/// <param name="channelAuthTenant">Optional. The oauth token tenant.</param>
57+
/// <param name="oAuthScope">Optional. The scope for the token.</param>
58+
/// <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>
59+
/// <param name="customHttpClient">Optional <see cref="HttpClient"/> to be used when acquiring tokens.</param>
60+
/// <param name="logger">Optional <see cref="ILogger"/> to gather telemetry data while acquiring and managing credentials.</param>
61+
public CertificateGovernmentAppCredentials(X509Certificate2 clientCertificate, string appId, string channelAuthTenant = null, string oAuthScope = null, bool sendX5c = false, HttpClient customHttpClient = null, ILogger logger = null)
62+
: base(clientCertificate, appId, channelAuthTenant, oAuthScope, sendX5c, customHttpClient, logger)
63+
{
64+
}
65+
66+
/// <inheritdoc/>
67+
protected override string DefaultChannelAuthTenant => GovernmentAuthenticationConstants.DefaultChannelAuthTenant;
68+
69+
/// <inheritdoc/>
70+
protected override string ToChannelFromBotOAuthScope => GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope;
71+
72+
/// <inheritdoc/>
73+
protected override string ToChannelFromBotLoginUrlTemplate => GovernmentAuthenticationConstants.ToChannelFromBotLoginUrlTemplate;
74+
}
75+
}

libraries/Microsoft.Bot.Connector/Authentication/CertificateServiceClientCredentialsFactory.cs

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class CertificateServiceClientCredentialsFactory : ServiceClientCredentia
2323
private readonly bool _sendX5c;
2424
private readonly HttpClient _httpClient;
2525
private readonly ILogger _logger;
26-
private readonly ConcurrentDictionary<string, CertificateAppCredentials> _certificateAppCredentialsByAudience = new ConcurrentDictionary<string, CertificateAppCredentials>();
26+
private readonly ConcurrentDictionary<string, CertificateAppCredentials> _certificateAppCredentialsByAudience = new ();
2727

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

83-
// Instance must be reused per audience, otherwise it will cause throttling on AAD.
84-
var certificateAppCredentials = _certificateAppCredentialsByAudience.GetOrAdd(audience, (audience) =>
83+
if (loginEndpoint.Equals(AuthenticationConstants.ToChannelFromBotLoginUrlTemplate, StringComparison.OrdinalIgnoreCase))
8584
{
86-
return new CertificateAppCredentials(
87-
_certificate,
88-
_appId,
89-
_tenantId,
90-
audience,
91-
_sendX5c,
92-
_httpClient,
93-
_logger);
94-
});
85+
return Task.FromResult<ServiceClientCredentials>(_certificateAppCredentialsByAudience.GetOrAdd(audience, (audience) =>
86+
{
87+
return new CertificateAppCredentials(
88+
_certificate,
89+
_appId,
90+
_tenantId,
91+
audience,
92+
_sendX5c,
93+
_httpClient,
94+
_logger);
95+
}));
96+
}
97+
else if (loginEndpoint.Equals(GovernmentAuthenticationConstants.ToChannelFromBotLoginUrlTemplate, StringComparison.OrdinalIgnoreCase))
98+
{
99+
return Task.FromResult<ServiceClientCredentials>(_certificateAppCredentialsByAudience.GetOrAdd(audience, (audience) =>
100+
{
101+
return new CertificateGovernmentAppCredentials(
102+
_certificate,
103+
_appId,
104+
_tenantId,
105+
audience,
106+
_sendX5c,
107+
_httpClient,
108+
_logger);
109+
}));
110+
}
111+
else
112+
{
113+
return Task.FromResult<ServiceClientCredentials>(_certificateAppCredentialsByAudience.GetOrAdd(audience, (audience) =>
114+
{
115+
return new CertificatePrivateCloudAppCredentials(
116+
_certificate,
117+
_appId,
118+
_tenantId,
119+
audience,
120+
_sendX5c,
121+
loginEndpoint,
122+
validateAuthority,
123+
_httpClient,
124+
_logger);
125+
}));
126+
}
127+
}
95128

96-
return Task.FromResult<ServiceClientCredentials>(certificateAppCredentials);
129+
private class CertificatePrivateCloudAppCredentials : CertificateAppCredentials
130+
{
131+
private readonly string _oAuthEndpoint;
132+
private readonly bool _validateAuthority;
133+
134+
public CertificatePrivateCloudAppCredentials(CertificateAppCredentialsOptions options)
135+
: base(options)
136+
{
137+
}
138+
139+
public CertificatePrivateCloudAppCredentials(X509Certificate2 clientCertificate, string appId, string channelAuthTenant = null, HttpClient customHttpClient = null, ILogger logger = null)
140+
: base(clientCertificate, appId, channelAuthTenant, customHttpClient, logger)
141+
{
142+
}
143+
144+
public CertificatePrivateCloudAppCredentials(X509Certificate2 clientCertificate, bool sendX5c, string appId, string channelAuthTenant = null, HttpClient customHttpClient = null, ILogger logger = null)
145+
: base(clientCertificate, sendX5c, appId, channelAuthTenant, customHttpClient, logger)
146+
{
147+
}
148+
149+
public CertificatePrivateCloudAppCredentials(X509Certificate2 clientCertificate, string appId, string channelAuthTenant, string oAuthScope, bool sendX5c, string oAuthEndpoint, bool validateAuthority, HttpClient customHttpClient = null, ILogger logger = null)
150+
: base(clientCertificate, appId, channelAuthTenant, oAuthScope, sendX5c, customHttpClient, logger)
151+
{
152+
_oAuthEndpoint = oAuthEndpoint;
153+
_validateAuthority = validateAuthority;
154+
}
155+
156+
public override string OAuthEndpoint
157+
{
158+
get { return _oAuthEndpoint; }
159+
}
160+
161+
public override bool ValidateAuthority
162+
{
163+
get { return _validateAuthority; }
164+
}
97165
}
98166
}
99167
}

tests/Microsoft.Bot.Connector.Tests/Authentication/CertificateServiceClientCredentialsFactoryTests.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ public class CertificateServiceClientCredentialsFactoryTests
1717
private const string TestAppId = nameof(TestAppId);
1818
private const string TestTenantId = nameof(TestTenantId);
1919
private const string TestAudience = nameof(TestAudience);
20-
private const string LoginEndpoint = "https://login.microsoftonline.com";
20+
private const string LoginEndpoint = AuthenticationConstants.ToChannelFromBotLoginUrlTemplate;
21+
private const string GovLoginEndpoint = GovernmentAuthenticationConstants.ToChannelFromBotLoginUrlTemplate;
22+
private const string PrivateLoginEndpoint = "https://login.privatecloud.com";
2123
private readonly Mock<ILogger> logger = new Mock<ILogger>();
2224
private readonly Mock<X509Certificate2> certificate = new Mock<X509Certificate2>();
2325

@@ -64,7 +66,7 @@ public void IsAuthenticationDisabledTest()
6466
}
6567

6668
[Fact]
67-
public async void CanCreateCredentials()
69+
public async void CanCreatePublicCredentials()
6870
{
6971
var factory = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId);
7072

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

80+
[Fact]
81+
public async void CanCreateGovCredentials()
82+
{
83+
var factory = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId);
84+
85+
var credentials = await factory.CreateCredentialsAsync(
86+
TestAppId, TestAudience, GovLoginEndpoint, true, CancellationToken.None);
87+
88+
Assert.NotNull(credentials);
89+
Assert.IsType<CertificateGovernmentAppCredentials>(credentials);
90+
}
91+
92+
[Fact]
93+
public async void CanCreatePrivateCredentials()
94+
{
95+
var factory = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId);
96+
97+
var credentials = await factory.CreateCredentialsAsync(
98+
TestAppId, TestAudience, PrivateLoginEndpoint, true, CancellationToken.None);
99+
100+
Assert.NotNull(credentials);
101+
Assert.IsAssignableFrom<CertificateAppCredentials>(credentials);
102+
Assert.Equal(PrivateLoginEndpoint, ((CertificateAppCredentials)credentials).OAuthEndpoint);
103+
}
104+
78105
[Fact]
79106
public async void ShouldCreateUniqueCredentialsByAudience()
80107
{

0 commit comments

Comments
 (0)