From a4bde02483a792bc0f3af68f2da702be88175e88 Mon Sep 17 00:00:00 2001 From: jennyf19 Date: Tue, 1 Dec 2020 13:09:38 -0800 Subject: [PATCH] fix tfp double up in legacy b2c domains (#796) * check for tfp * add more tests * fix null ref in test --- .../TokenAcquisition.cs | 14 +++- .../Microsoft.Identity.Web.Test.Common.csproj | 2 + .../Mocks/MockHttpContextAccessor.cs | 22 ++++++ .../TestConstants.cs | 1 + .../AcquireTokenForAppIntegrationTests.cs | 41 +++------- .../TokenAcquisitionAuthorityTests.cs | 75 +++++++++++++++---- 6 files changed, 109 insertions(+), 46 deletions(-) create mode 100644 tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpContextAccessor.cs diff --git a/src/Microsoft.Identity.Web/TokenAcquisition.cs b/src/Microsoft.Identity.Web/TokenAcquisition.cs index b01513cbe..4845e0cd9 100644 --- a/src/Microsoft.Identity.Web/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web/TokenAcquisition.cs @@ -421,7 +421,7 @@ public async Task RemoveAccountAsync(RedirectContext context) /// /// Creates an MSAL confidential client application, if needed. /// - private async Task GetOrBuildConfidentialClientApplicationAsync() + internal /* for testing */ async Task GetOrBuildConfidentialClientApplicationAsync() { if (_application == null) { @@ -447,7 +447,7 @@ private async Task BuildConfidentialClientApplic _microsoftIdentityOptions.CallbackPath.Value ?? string.Empty); } - _applicationOptions.Instance = _applicationOptions.Instance.TrimEnd('/') + "/"; + PrepareAuthorityInstanceForMsal(); if (!string.IsNullOrEmpty(_microsoftIdentityOptions.ClientSecret)) { @@ -505,6 +505,16 @@ private async Task BuildConfidentialClientApplic } } + private void PrepareAuthorityInstanceForMsal() + { + if (_microsoftIdentityOptions.IsB2C && _applicationOptions.Instance.EndsWith("/tfp/")) + { + _applicationOptions.Instance = _applicationOptions.Instance.Replace("/tfp/", string.Empty).Trim(); + } + + _applicationOptions.Instance = _applicationOptions.Instance.TrimEnd('/') + "/"; + } + private async Task GetAuthenticationResultForWebApiToCallDownstreamApiAsync( IConfidentialClientApplication application, string authority, diff --git a/tests/Microsoft.Identity.Web.Test.Common/Microsoft.Identity.Web.Test.Common.csproj b/tests/Microsoft.Identity.Web.Test.Common/Microsoft.Identity.Web.Test.Common.csproj index 5a6b82ae9..6a3ee77ab 100644 --- a/tests/Microsoft.Identity.Web.Test.Common/Microsoft.Identity.Web.Test.Common.csproj +++ b/tests/Microsoft.Identity.Web.Test.Common/Microsoft.Identity.Web.Test.Common.csproj @@ -16,6 +16,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -24,6 +25,7 @@ + diff --git a/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpContextAccessor.cs b/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpContextAccessor.cs new file mode 100644 index 000000000..49f384e1d --- /dev/null +++ b/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpContextAccessor.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Http; +using NSubstitute; + +namespace Microsoft.Identity.Web.Test.Common.Mocks +{ + public static class MockHttpContextAccessor + { + public static IHttpContextAccessor CreateMockHttpContextAccessor() + { + var mockHttpContextAccessor = Substitute.For(); + mockHttpContextAccessor.HttpContext = new DefaultHttpContext(); + mockHttpContextAccessor.HttpContext.Request.Scheme = "https"; + mockHttpContextAccessor.HttpContext.Request.Host = new HostString("IdentityDotNetSDKAutomation"); + mockHttpContextAccessor.HttpContext.Request.PathBase = "/"; + + return mockHttpContextAccessor; + } + } +} diff --git a/tests/Microsoft.Identity.Web.Test.Common/TestConstants.cs b/tests/Microsoft.Identity.Web.Test.Common/TestConstants.cs index 7f87869bf..eb934b4c7 100644 --- a/tests/Microsoft.Identity.Web.Test.Common/TestConstants.cs +++ b/tests/Microsoft.Identity.Web.Test.Common/TestConstants.cs @@ -59,6 +59,7 @@ public static class TestConstants public const string B2CInstance = "https://fabrikamb2c.b2clogin.com"; public const string B2CInstance2 = "https://catb2c.b2clogin.com"; public const string B2CCustomDomainInstance = "https://catsAreAmazing.com"; + public const string B2CLoginMicrosoft = "https://login.microsoftonline.com"; public const string ClientSecret = "catsarecool"; public const string B2CAuthority = B2CInstance + "/" + B2CTenant + "/" + B2CSignUpSignInUserFlow; diff --git a/tests/Microsoft.Identity.Web.Test.Integration/AcquireTokenForAppIntegrationTests.cs b/tests/Microsoft.Identity.Web.Test.Integration/AcquireTokenForAppIntegrationTests.cs index a1fb7bec1..925aa779f 100644 --- a/tests/Microsoft.Identity.Web.Test.Integration/AcquireTokenForAppIntegrationTests.cs +++ b/tests/Microsoft.Identity.Web.Test.Integration/AcquireTokenForAppIntegrationTests.cs @@ -5,20 +5,18 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Identity.Client; using Microsoft.Identity.Web.Test.Common; +using Microsoft.Identity.Web.Test.Common.Mocks; using Microsoft.Identity.Web.Test.Common.TestHelpers; using Microsoft.Identity.Web.Test.LabInfrastructure; using Microsoft.Identity.Web.TokenCacheProviders.InMemory; -using NSubstitute; using Xunit; using Xunit.Abstractions; -using IHttpContextAccessor = Microsoft.AspNetCore.Http.IHttpContextAccessor; namespace Microsoft.Identity.Web.Test.Integration { @@ -106,37 +104,18 @@ async Task result() => private void InitializeTokenAcquisitionObjects() { - IOptions microsoftIdentityOptions = _provider.GetService>(); - IOptions tokenOptions = _provider.GetService>(); - IOptions ccOptions = _provider.GetService>(); - ILogger logger = _provider.GetService>(); - IHttpClientFactory httpClientFactory = _provider.GetService(); - - IHttpContextAccessor httpContextAccessor = CreateMockHttpContextAccessor(); - _msalTestTokenCacheProvider = new MsalTestTokenCacheProvider( - _provider.GetService(), - tokenOptions); + _provider.GetService(), + _provider.GetService>()); _tokenAcquisition = new TokenAcquisition( - _msalTestTokenCacheProvider, - httpContextAccessor, - microsoftIdentityOptions, - ccOptions, - httpClientFactory, - logger, - _provider); - } - - private static IHttpContextAccessor CreateMockHttpContextAccessor() - { - var mockHttpContextAccessor = Substitute.For(); - mockHttpContextAccessor.HttpContext = new DefaultHttpContext(); - mockHttpContextAccessor.HttpContext.Request.Scheme = "https"; - mockHttpContextAccessor.HttpContext.Request.Host = new HostString("IdentityDotNetSDKAutomation"); - mockHttpContextAccessor.HttpContext.Request.PathBase = "/"; - - return mockHttpContextAccessor; + _msalTestTokenCacheProvider, + MockHttpContextAccessor.CreateMockHttpContextAccessor(), + _provider.GetService>(), + _provider.GetService>(), + _provider.GetService(), + _provider.GetService>(), + _provider); } private void BuildTheRequiredServices() diff --git a/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAuthorityTests.cs b/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAuthorityTests.cs index 763921f7b..ac4a74907 100644 --- a/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAuthorityTests.cs +++ b/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAuthorityTests.cs @@ -2,10 +2,16 @@ // Licensed under the MIT License. using System.Globalization; +using System.Net.Http; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Identity.Client; using Microsoft.Identity.Web.Test.Common; +using Microsoft.Identity.Web.Test.Common.Mocks; +using Microsoft.Identity.Web.Test.Common.TestHelpers; +using Microsoft.Identity.Web.TokenCacheProviders.InMemory; using Xunit; namespace Microsoft.Identity.Web.Test @@ -15,38 +21,36 @@ public class TokenAcquisitionAuthorityTests private TokenAcquisition _tokenAcquisition; private ServiceProvider _provider; private ConfidentialClientApplicationOptions _applicationOptions; + private MicrosoftIdentityOptions _microsoftIdentityOptions; private void InitializeTokenAcquisitionObjects() { _tokenAcquisition = new TokenAcquisition( - null, - null, + new MsalTestTokenCacheProvider( + _provider.GetService(), + _provider.GetService>()), + MockHttpContextAccessor.CreateMockHttpContextAccessor(), _provider.GetService>(), _provider.GetService>(), - null, - null, + _provider.GetService(), + _provider.GetService>(), _provider); } - private void BuildTheRequiredServices() + private void BuildTheRequiredServices(string instance = TestConstants.AadInstance) { var services = new ServiceCollection(); _applicationOptions = new ConfidentialClientApplicationOptions { - Instance = TestConstants.AadInstance, + Instance = instance, ClientId = TestConstants.ConfidentialClientId, - ClientSecret = "cats", + ClientSecret = TestConstants.ClientSecret, }; services.AddTokenAcquisition(); services.AddTransient( - provider => Options.Create(new MicrosoftIdentityOptions - { - Authority = TestConstants.AuthorityCommonTenant, - ClientId = TestConstants.ConfidentialClientId, - CallbackPath = string.Empty, - })); + provider => Options.Create(_microsoftIdentityOptions)); services.AddTransient( provider => Options.Create(_applicationOptions)); _provider = services.BuildServiceProvider(); @@ -59,6 +63,13 @@ private void BuildTheRequiredServices() [InlineData("")] public void VerifyCorrectAuthorityUsedInTokenAcquisitionTests(string tenant) { + _microsoftIdentityOptions = new MicrosoftIdentityOptions + { + Authority = TestConstants.AuthorityCommonTenant, + ClientId = TestConstants.ConfidentialClientId, + CallbackPath = string.Empty, + }; + BuildTheRequiredServices(); InitializeTokenAcquisitionObjects(); IConfidentialClientApplication app = ConfidentialClientApplicationBuilder @@ -79,5 +90,43 @@ public void VerifyCorrectAuthorityUsedInTokenAcquisitionTests(string tenant) Assert.Equal(app.Authority, _tokenAcquisition.CreateAuthorityBasedOnTenantIfProvided(app, tenant)); } } + + [Theory] + [InlineData(TestConstants.B2CInstance)] + [InlineData(TestConstants.B2CLoginMicrosoft)] + [InlineData(TestConstants.B2CInstance, true)] + [InlineData(TestConstants.B2CLoginMicrosoft, true)] + public async void VerifyCorrectAuthorityUsedInTokenAcquisition_B2CAuthorityTestsAsync( + string authorityInstance, + bool withTfp = false) + { + _microsoftIdentityOptions = new MicrosoftIdentityOptions + { + SignUpSignInPolicyId = TestConstants.B2CSignUpSignInUserFlow, + Domain = TestConstants.B2CTenant, + }; + + if (withTfp) + { + BuildTheRequiredServices(authorityInstance + "/tfp/"); + } + else + { + BuildTheRequiredServices(authorityInstance); + } + + InitializeTokenAcquisitionObjects(); + + IConfidentialClientApplication app = await _tokenAcquisition.GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); + + string expectedAuthority = string.Format( + CultureInfo.InvariantCulture, + "{0}/tfp/{1}/{2}/", + authorityInstance, + TestConstants.B2CTenant, + TestConstants.B2CSignUpSignInUserFlow); + + Assert.Equal(expectedAuthority, app.Authority); + } } }