-
Notifications
You must be signed in to change notification settings - Fork 486
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
…oftAppCredentials (never CertificateAppCredentials) -- certificate auth flow broken (#6631) * Add functionality to auth with a certificate * Add unit tests * Apply Visual Studio suggestion Co-authored-by: Cecilia Avila <44245136+ceciliaavila@users.noreply.github.com> * Fix indentation --------- Co-authored-by: Cecilia Avila <44245136+ceciliaavila@users.noreply.github.com>
- Loading branch information
1 parent
68e0c42
commit 0cda584
Showing
2 changed files
with
159 additions
and
0 deletions.
There are no files selected for viewing
74 changes: 74 additions & 0 deletions
74
...ries/Microsoft.Bot.Connector/Authentication/CertificateServiceClientCredentialsFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Net.Http; | ||
using System.Security.Cryptography.X509Certificates; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Rest; | ||
|
||
namespace Microsoft.Bot.Connector.Authentication | ||
{ | ||
/// <summary> | ||
/// A Managed Identity implementation of the <see cref="ServiceClientCredentialsFactory"/> interface. | ||
/// </summary> | ||
public class CertificateServiceClientCredentialsFactory : ServiceClientCredentialsFactory | ||
{ | ||
private readonly X509Certificate2 _certificate; | ||
private readonly string _appId; | ||
private readonly string _tenantId; | ||
private readonly HttpClient _httpClient; | ||
private readonly ILogger _logger; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="CertificateServiceClientCredentialsFactory"/> class. | ||
/// </summary> | ||
/// <param name="certificate">The certificate to use for authentication.</param> | ||
/// <param name="appId">Microsoft application Id related to the certificate.</param> | ||
/// <param name="tenantId">The oauth token tenant.</param> | ||
/// <param name="httpClient">A custom httpClient to use.</param> | ||
/// <param name="logger">A logger instance to use.</param> | ||
public CertificateServiceClientCredentialsFactory(X509Certificate2 certificate, string appId, string tenantId = null, HttpClient httpClient = null, ILogger logger = null) | ||
: base() | ||
{ | ||
if (string.IsNullOrWhiteSpace(appId)) | ||
{ | ||
throw new ArgumentNullException(nameof(appId)); | ||
} | ||
|
||
_certificate = certificate ?? throw new ArgumentNullException(nameof(certificate)); | ||
_appId = appId; | ||
_tenantId = tenantId; | ||
_httpClient = httpClient; | ||
_logger = logger; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override Task<bool> IsValidAppIdAsync(string appId, CancellationToken cancellationToken) | ||
{ | ||
return Task.FromResult(appId == _appId); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override Task<bool> IsAuthenticationDisabledAsync(CancellationToken cancellationToken) | ||
{ | ||
// Auth is always enabled for Certificate. | ||
return Task.FromResult(false); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override Task<ServiceClientCredentials> CreateCredentialsAsync( | ||
string appId, string audience, string loginEndpoint, bool validateAuthority, CancellationToken cancellationToken) | ||
{ | ||
if (appId != _appId) | ||
{ | ||
throw new InvalidOperationException("Invalid Managed ID."); | ||
} | ||
|
||
return Task.FromResult<ServiceClientCredentials>( | ||
new CertificateAppCredentials(_certificate, _appId, _tenantId, _httpClient, _logger)); | ||
} | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
...oft.Bot.Connector.Tests/Authentication/CertificateServiceClientCredentialsFactoryTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Net.Http; | ||
using System.Security.Cryptography.X509Certificates; | ||
using System.Threading; | ||
using Microsoft.Bot.Connector.Authentication; | ||
using Microsoft.Extensions.Logging; | ||
using Moq; | ||
using Xunit; | ||
|
||
namespace Microsoft.Bot.Connector.Tests.Authentication | ||
{ | ||
public class CertificateServiceClientCredentialsFactoryTests | ||
{ | ||
private const string TestAppId = nameof(TestAppId); | ||
private const string TestTenantId = nameof(TestTenantId); | ||
private const string TestAudience = nameof(TestAudience); | ||
private readonly Mock<ILogger> logger = new Mock<ILogger>(); | ||
private readonly Mock<X509Certificate2> certificate = new Mock<X509Certificate2>(); | ||
|
||
[Fact] | ||
public void ConstructorTests() | ||
{ | ||
_ = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId); | ||
_ = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId, tenantId: TestTenantId); | ||
_ = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId, logger: logger.Object); | ||
_ = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId, httpClient: new HttpClient()); | ||
} | ||
|
||
[Fact] | ||
public void CannotCreateCredentialsFactoryWithoutCertificate() | ||
{ | ||
Assert.Throws<ArgumentNullException>(() => new CertificateServiceClientCredentialsFactory(null, TestAppId)); | ||
} | ||
|
||
[Theory] | ||
[InlineData(null)] | ||
[InlineData("")] | ||
[InlineData(" ")] | ||
public void CannotCreateCredentialsFactoryWithoutAppId(string appId) | ||
{ | ||
Assert.Throws<ArgumentNullException>(() => new CertificateServiceClientCredentialsFactory(certificate.Object, appId)); | ||
} | ||
|
||
[Fact] | ||
public void IsValidAppIdTest() | ||
{ | ||
var factory = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId); | ||
|
||
Assert.True(factory.IsValidAppIdAsync(TestAppId, CancellationToken.None).GetAwaiter().GetResult()); | ||
Assert.False(factory.IsValidAppIdAsync("InvalidAppId", CancellationToken.None).GetAwaiter().GetResult()); | ||
} | ||
|
||
[Fact] | ||
public void IsAuthenticationDisabledTest() | ||
{ | ||
var factory = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId); | ||
|
||
Assert.False(factory.IsAuthenticationDisabledAsync(CancellationToken.None).GetAwaiter().GetResult()); | ||
} | ||
|
||
[Fact] | ||
public async void CanCreateCredentials() | ||
{ | ||
var factory = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId); | ||
|
||
var credentials = await factory.CreateCredentialsAsync( | ||
TestAppId, TestAudience, "https://login.microsoftonline.com", true, CancellationToken.None); | ||
|
||
Assert.NotNull(credentials); | ||
Assert.IsType<CertificateAppCredentials>(credentials); | ||
} | ||
|
||
[Fact] | ||
public void CannotCreateCredentialsWithInvalidAppId() | ||
{ | ||
var factory = new CertificateServiceClientCredentialsFactory(certificate.Object, TestAppId); | ||
|
||
Assert.ThrowsAsync<InvalidOperationException>(() => factory.CreateCredentialsAsync( | ||
"InvalidAppId", TestAudience, "https://login.microsoftonline.com", true, CancellationToken.None)); | ||
} | ||
} | ||
} |