diff --git a/libraries/Microsoft.Bot.Connector/Authentication/MsalAppCredentials.cs b/libraries/Microsoft.Bot.Connector/Authentication/MsalAppCredentials.cs index 63524e8194..aaa048d5f6 100644 --- a/libraries/Microsoft.Bot.Connector/Authentication/MsalAppCredentials.cs +++ b/libraries/Microsoft.Bot.Connector/Authentication/MsalAppCredentials.cs @@ -106,6 +106,33 @@ public MsalAppCredentials(string appId, X509Certificate2 certificate, string aut .Build(); } + /// + /// Initializes a new instance of the class. + /// + /// The Microsoft application id. + /// The certificate to use for authentication. + /// If 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. + /// Optional switch for whether to validate the authority. + /// Optional authority. + /// Optional custom scope. + /// Optional . + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2234:Pass system uri objects instead of strings", Justification = "Using string overload for legacy compatibility.")] + public MsalAppCredentials(string appId, X509Certificate2 certificate, bool sendX5c, string authority = null, string scope = null, bool validateAuthority = true, ILogger logger = null) + : this( + clientApplication: null, + appId: appId, + authority: authority, + scope: scope, + validateAuthority: validateAuthority, + logger: logger) + { + _clientApplication = ConfidentialClientApplicationBuilder.Create(appId) + .WithAuthority(authority ?? OAuthEndpoint, validateAuthority) + .WithCertificate(certificate, sendX5c) + .Build(); + } + async Task IAuthenticator.GetTokenAsync(bool forceRefresh) { var watch = Stopwatch.StartNew(); diff --git a/tests/Microsoft.Bot.Connector.Tests/Authentication/MsalServiceClientCredentialsFactoryTests.cs b/tests/Microsoft.Bot.Connector.Tests/Authentication/MsalServiceClientCredentialsFactoryTests.cs new file mode 100644 index 0000000000..bd89bffb04 --- /dev/null +++ b/tests/Microsoft.Bot.Connector.Tests/Authentication/MsalServiceClientCredentialsFactoryTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Identity.Client; +using Moq; +using Xunit; + +namespace Microsoft.Bot.Connector.Tests.Authentication +{ + public class MsalServiceClientCredentialsFactoryTests + { + 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 LoginEndpointGov = "https://login.microsoftonline.us/MicrosoftServices.onmicrosoft.us"; + private readonly Mock logger = new Mock(); + private readonly Mock configuration = new Mock(); + private readonly Mock clientApplication = new Mock(); + + [Fact] + public void ConstructorTests() + { + var factory = new MsalServiceClientCredentialsFactory(configuration.Object, clientApplication.Object, logger.Object); + + Assert.NotNull(factory); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public async Task ShouldReturnEmptyCredentialsWithoutAppId(string appId) + { + var factory = new MsalServiceClientCredentialsFactory(configuration.Object, clientApplication.Object, logger.Object); + var credentials = await factory.CreateCredentialsAsync(appId, TestAudience, LoginEndpoint, true, CancellationToken.None); + + Assert.Equal(MsalAppCredentials.Empty, credentials); + } + + [Fact] + public void ShouldThrowIfAppIdDoesNotMatch() + { + configuration.Setup(x => x.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey).Value).Returns(TestAppId); + var factory = new MsalServiceClientCredentialsFactory(configuration.Object, clientApplication.Object, logger.Object); + + Assert.ThrowsAsync(() => factory.CreateCredentialsAsync( + "InvalidAppId", TestAudience, LoginEndpoint, true, CancellationToken.None)); + } + + [Fact] + public void ShouldCreateCredentials() + { + configuration.Setup(x => x.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey).Value).Returns(TestAppId); + var factory = new MsalServiceClientCredentialsFactory(configuration.Object, clientApplication.Object, logger.Object); + var credentials = factory.CreateCredentialsAsync(TestAppId, TestAudience, LoginEndpoint, true, CancellationToken.None).GetAwaiter().GetResult(); + + Assert.NotNull(credentials); + Assert.IsType(credentials); + } + + [Fact] + public void ShouldCreateCredentialsForGoverment() + { + configuration.Setup(x => x.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey).Value).Returns(TestAppId); + var factory = new MsalServiceClientCredentialsFactory(configuration.Object, clientApplication.Object, logger.Object); + var credentials = factory.CreateCredentialsAsync(TestAppId, TestAudience, LoginEndpointGov, true, CancellationToken.None).GetAwaiter().GetResult(); + + Assert.NotNull(credentials); + Assert.IsType(credentials); + } + + [Fact] + public void IsValidAppIdTest() + { + configuration.Setup(x => x.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey).Value).Returns(TestAppId); + var factory = new MsalServiceClientCredentialsFactory(configuration.Object, clientApplication.Object, logger.Object); + + Assert.True(factory.IsValidAppIdAsync(TestAppId, CancellationToken.None).GetAwaiter().GetResult()); + Assert.False(factory.IsValidAppIdAsync("InvalidAppId", CancellationToken.None).GetAwaiter().GetResult()); + } + + [Fact] + public void IsAuthenticationDisabledTest() + { + configuration.Setup(x => x.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey).Value).Returns(string.Empty); + var factory = new MsalServiceClientCredentialsFactory(configuration.Object, clientApplication.Object, logger.Object); + + Assert.True(factory.IsAuthenticationDisabledAsync(CancellationToken.None).GetAwaiter().GetResult()); + } + } +}