From 6b1cff85c10786244f186a48f5b25b78663dc578 Mon Sep 17 00:00:00 2001 From: pmaytak <34331512+pmaytak@users.noreply.github.com> Date: Fri, 8 Jan 2021 23:44:15 -0800 Subject: [PATCH] Disable ADAL cache (#2309) * Add public API method to disable ADAL cache. Add if checks around ADAL cache operations. * Update if AdalCacheEnabled checks. * Add benchmark for SaveToken method. * Renaming and comment updates. Add app option. * Add GetRefreshToken test. * Fix FindToken test. Move helper methods into LegacyTokenCacheHelper for reusability. * Cleanup. * Add RemoveUser and GetAllUsers benchmarks. * Add unit tests. * Update XML comment. * Rename AdalCache to LegacyCache --- .../AppConfig/AbstractApplicationBuilder.cs | 19 ++ .../AppConfig/ApplicationConfiguration.cs | 2 + .../AppConfig/ApplicationOptions.cs | 5 + .../AppConfig/IAppConfig.cs | 4 + .../TokenCache.ITokenCacheInternal.cs | 45 +++-- .../Microsoft.Identity.Client/TokenCache.cs | 19 +- .../LegacyCacheOperationsTests.cs | 100 ++++++++++ .../Program.cs | 2 +- .../Core/Mocks/LegacyTokenCacheHelper.cs | 141 ++++++++++++++ .../Core/Mocks/TokenCacheHelper.cs | 12 +- .../TestCommon.cs | 6 +- ...nfidentialClientApplicationBuilderTests.cs | 12 ++ .../CacheFallbackOperationsTests.cs | 182 ++++-------------- .../CacheTests/TokenCacheTests.cs | 39 ++++ 14 files changed, 419 insertions(+), 169 deletions(-) create mode 100644 tests/Microsoft.Identity.Client.Performance/LegacyCacheOperationsTests.cs create mode 100644 tests/Microsoft.Identity.Test.Common/Core/Mocks/LegacyTokenCacheHelper.cs diff --git a/src/client/Microsoft.Identity.Client/AppConfig/AbstractApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/AbstractApplicationBuilder.cs index 7b581100d6..df4b941544 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/AbstractApplicationBuilder.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/AbstractApplicationBuilder.cs @@ -190,6 +190,24 @@ internal T WithUserTokenCacheInternalForTest(ITokenCacheInternal tokenCacheInter return (T)this; } + /// + /// Enables legacy ADAL cache serialization and deserialization. + /// + /// Enable legacy ADAL cache compatibility. + /// The builder to chain the .With methods. + /// + /// ADAL is a previous legacy generation of MSAL.NET authentication library. + /// If you don't use .WithLegacyCacheCompatibility(false), then by default, the ADAL cache is used + /// (along with MSAL cache). true flag is only needed for specific migration scenarios + /// from ADAL.NET to MSAL.NET when both library versions are running side-by-side. + /// To improve performance add .WithLegacyCacheCompatibility(false) unless you care about migration scenarios. + /// + public T WithLegacyCacheCompatibility(bool enableLegacyCacheCompatibility = true) + { + Config.LegacyCacheCompatibilityEnabled = enableLegacyCacheCompatibility; + return (T)this; + } + /// /// Sets the logging callback. For details see https://aka.ms/msal-net-logging /// @@ -356,6 +374,7 @@ protected T WithOptions(ApplicationOptions applicationOptions) WithClientName(applicationOptions.ClientName); WithClientVersion(applicationOptions.ClientVersion); WithClientCapabilities(applicationOptions.ClientCapabilities); + WithLegacyCacheCompatibility(applicationOptions.LegacyCacheCompatibilityEnabled); WithLogging( null, diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs index d10e742f23..d025df9ddb 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs @@ -81,6 +81,8 @@ public string ClientVersion public bool MergeWithDefaultClaims { get; internal set; } internal int ConfidentialClientCredentialCount; + public bool LegacyCacheCompatibilityEnabled { get; internal set; } = true; + #region Authority public InstanceDiscoveryResponse CustomInstanceDiscoveryMetadata { get; set; } diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationOptions.cs b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationOptions.cs index 728d2e4cd5..2824e6c056 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationOptions.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationOptions.cs @@ -121,5 +121,10 @@ public abstract class ApplicationOptions /// For more details see https://aka.ms/msal-net-claims-request /// public IEnumerable ClientCapabilities { get; set; } + + /// + /// Enables legacy ADAL cache serialization and deserialization. + /// + public bool LegacyCacheCompatibilityEnabled { get; set; } } } diff --git a/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs b/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs index 2ea2583a83..1e1d5dbdff 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs @@ -103,6 +103,10 @@ public interface IAppConfig /// IEnumerable ClientCapabilities { get; } + /// + /// Enables legacy ADAL cache serialization and deserialization. + /// + bool LegacyCacheCompatibilityEnabled { get; } /// /// diff --git a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs index dad01e92fb..8cf4ce7b29 100644 --- a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs +++ b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs @@ -177,8 +177,9 @@ public sealed partial class TokenCache : ITokenCacheInternal UpdateAppMetadata(requestParams.ClientId, instanceDiscoveryMetadata.PreferredCache, response.FamilyId); - // Do not save RT in ADAL cache for confidential client or B2C - if (!requestParams.IsClientCredentialRequest && + // Do not save RT in ADAL cache for client credentials flow or B2C + if (ServiceBundle.Config.LegacyCacheCompatibilityEnabled && + !requestParams.IsClientCredentialRequest && requestParams.AuthorityInfo.AuthorityType != AuthorityType.B2C) { var authorityWithPreferredCache = Authority.CreateAuthorityWithEnvironment( @@ -560,9 +561,10 @@ private MsalAccessTokenCacheItem FilterByKeyId(MsalAccessTokenCacheItem item, Au requestParams.RequestContext.Logger.Info("Checking ADAL cache for matching RT. "); // ADAL legacy cache does not store FRTs - if (requestParams.Account != null && string.IsNullOrEmpty(familyId)) + if (ServiceBundle.Config.LegacyCacheCompatibilityEnabled && + requestParams.Account != null && + string.IsNullOrEmpty(familyId)) { - return CacheFallbackOperations.GetRefreshToken( Logger, LegacyCachePersistence, @@ -631,17 +633,22 @@ async Task> ITokenCacheInternal.GetAccountsAsync(Authentic if (logger.IsLoggingEnabled(LogLevel.Verbose)) logger.Verbose($"GetAccounts found {rtCacheItems.Count()} RTs and {accountCacheItems.Count()} accounts in MSAL cache. "); - AdalUsersForMsal adalUsersResult = CacheFallbackOperations.GetAllAdalUsersForMsal( - Logger, - LegacyCachePersistence, - ClientId); - // Multi-cloud support - must filter by env. ISet allEnvironmentsInCache = new HashSet( accountCacheItems.Select(aci => aci.Environment), StringComparer.OrdinalIgnoreCase); allEnvironmentsInCache.UnionWith(rtCacheItems.Select(rt => rt.Environment)); - allEnvironmentsInCache.UnionWith(adalUsersResult.GetAdalUserEnviroments()); + + AdalUsersForMsal adalUsersResult = null; + + if (ServiceBundle.Config.LegacyCacheCompatibilityEnabled) + { + adalUsersResult = CacheFallbackOperations.GetAllAdalUsersForMsal( + Logger, + LegacyCachePersistence, + ClientId); + allEnvironmentsInCache.UnionWith(adalUsersResult.GetAdalUserEnviroments()); + } var instanceMetadata = await ServiceBundle.InstanceDiscoveryManager.GetMetadataEntryTryAvoidNetworkAsync( requestParameters.AuthorityInfo.CanonicalAuthority, @@ -671,11 +678,14 @@ async Task> ITokenCacheInternal.GetAccountsAsync(Authentic } } - UpdateMapWithAdalAccountsWithClientInfo( - environment, - instanceMetadata.Aliases, - adalUsersResult, - clientInfoToAccountMap); + if (ServiceBundle.Config.LegacyCacheCompatibilityEnabled) + { + UpdateMapWithAdalAccountsWithClientInfo( + environment, + instanceMetadata.Aliases, + adalUsersResult, + clientInfoToAccountMap); + } // Add WAM accounts stored in MSAL's cache - for which we do not have an RT if (requestParameters.IsBrokerConfigured && ServiceBundle.PlatformProxy.BrokerSupportsWamAccounts) @@ -792,7 +802,10 @@ async Task ITokenCacheInternal.RemoveAccountAsync(IAccount account, RequestConte } tokenCacheInternal.RemoveMsalAccountWithNoLocks(account, requestContext); - RemoveAdalUser(account); + if (ServiceBundle.Config.LegacyCacheCompatibilityEnabled) + { + RemoveAdalUser(account); + } } finally { diff --git a/src/client/Microsoft.Identity.Client/TokenCache.cs b/src/client/Microsoft.Identity.Client/TokenCache.cs index 4bdd348c0f..7236d888b8 100644 --- a/src/client/Microsoft.Identity.Client/TokenCache.cs +++ b/src/client/Microsoft.Identity.Client/TokenCache.cs @@ -42,7 +42,7 @@ public sealed partial class TokenCache : ITokenCacheInternal private ICoreLogger Logger => ServiceBundle.DefaultLogger; internal IServiceBundle ServiceBundle { get; } - internal ILegacyCachePersistence LegacyCachePersistence { get; } + internal ILegacyCachePersistence LegacyCachePersistence { get; set; } internal string ClientId => ServiceBundle.Config.ClientId; ITokenCacheAccessor ITokenCacheInternal.Accessor => _accessor; @@ -206,23 +206,26 @@ private bool RtMatchesAccount(MsalRefreshTokenCacheItem rtItem, MsalAccountCache } } - private static List UpdateWithAdalAccountsWithoutClientInfo( + private List UpdateWithAdalAccountsWithoutClientInfo( string envFromRequest, IEnumerable envAliases, AdalUsersForMsal adalUsers, IDictionary clientInfoToAccountMap) { var accounts = new List(); - accounts.AddRange(clientInfoToAccountMap.Values); - var uniqueUserNames = clientInfoToAccountMap.Values.Select(o => o.Username).Distinct().ToList(); - foreach (AdalUserInfo user in adalUsers.GetUsersWithoutClientInfo(envAliases)) + if (ServiceBundle.Config.LegacyCacheCompatibilityEnabled) { - if (!string.IsNullOrEmpty(user.DisplayableId) && !uniqueUserNames.Contains(user.DisplayableId)) + var uniqueUserNames = clientInfoToAccountMap.Values.Select(o => o.Username).Distinct().ToList(); + + foreach (AdalUserInfo user in adalUsers.GetUsersWithoutClientInfo(envAliases)) { - accounts.Add(new Account(null, user.DisplayableId, envFromRequest)); - uniqueUserNames.Add(user.DisplayableId); + if (!string.IsNullOrEmpty(user.DisplayableId) && !uniqueUserNames.Contains(user.DisplayableId)) + { + accounts.Add(new Account(null, user.DisplayableId, envFromRequest)); + uniqueUserNames.Add(user.DisplayableId); + } } } diff --git a/tests/Microsoft.Identity.Client.Performance/LegacyCacheOperationsTests.cs b/tests/Microsoft.Identity.Client.Performance/LegacyCacheOperationsTests.cs new file mode 100644 index 0000000000..f6778ec186 --- /dev/null +++ b/tests/Microsoft.Identity.Client.Performance/LegacyCacheOperationsTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Engines; +using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Instance; +using Microsoft.Identity.Client.Instance.Discovery; +using Microsoft.Identity.Client.Internal; +using Microsoft.Identity.Client.Internal.Requests; +using Microsoft.Identity.Client.OAuth2; +using Microsoft.Identity.Test.Common; +using Microsoft.Identity.Test.Common.Core.Mocks; +using Microsoft.Identity.Test.Unit; + +namespace Microsoft.Identity.Test.Performance +{ + [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByMethod)] + public class LegacyCacheOperationsTests + { + private ITokenCacheInternal _cache; + private MsalTokenResponse _response; + private AuthenticationRequestParameters _requestParams; + private RequestContext _requestContext; + private readonly Consumer _consumer = new Consumer(); + + [Params(1, 100, 1000)] + public int TokenCacheSize { get; set; } + + [ParamsAllValues] + public bool EnableLegacyCache { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + var serviceBundle = TestCommon.CreateServiceBundleWithCustomHttpManager(null, isLegacyCacheEnabled: EnableLegacyCache); + + _requestContext = new RequestContext(serviceBundle, Guid.NewGuid()); + _cache = new TokenCache(serviceBundle, false); + _response = TestConstants.CreateMsalTokenResponse(); + + _requestParams = TestCommon.CreateAuthenticationRequestParameters(serviceBundle); + _requestParams.TenantUpdatedCanonicalAuthority = Authority.CreateAuthorityWithTenant( + _requestParams.AuthorityInfo, + TestConstants.Utid); + _requestParams.Account = new Account(TestConstants.s_userIdentifier, $"1{TestConstants.DisplayableId}", TestConstants.ProductionPrefNetworkEnvironment); + + AddHostToInstanceCache(serviceBundle, TestConstants.ProductionPrefCacheEnvironment); + + LegacyTokenCacheHelper.PopulateLegacyCache(serviceBundle.DefaultLogger, _cache.LegacyPersistence, TokenCacheSize); + TokenCacheHelper.AddRefreshTokensToCache(_cache.Accessor, TokenCacheSize); + } + + [Benchmark(Description = "SaveToken")] + public async Task SaveTokenResponseTestAsync() + { + var result = await _cache.SaveTokenResponseAsync(_requestParams, _response).ConfigureAwait(true); + return result.Item1.ClientId; + } + + [Benchmark(Description = "FindToken")] + public async Task FindRefreshTokenTestAsync() + { + var result = await _cache.FindRefreshTokenAsync(_requestParams).ConfigureAwait(true); + return result?.ClientId; + } + + [Benchmark(Description = "GetAllUsers")] + public async Task GetAllAdalUsersTestAsync() + { + var result = await _cache.GetAccountsAsync(_requestParams).ConfigureAwait(true); + result.Consume(_consumer); + } + + [Benchmark(Description = "RemoveUser")] + public async Task RemoveAdalUserTestAsync() + { + await _cache.RemoveAccountAsync(_requestParams.Account, _requestContext).ConfigureAwait(true); + } + + private void AddHostToInstanceCache(IServiceBundle serviceBundle, string host) + { + (serviceBundle.InstanceDiscoveryManager as InstanceDiscoveryManager) + .AddTestValueToStaticProvider( + host, + new InstanceDiscoveryMetadataEntry + { + PreferredNetwork = host, + PreferredCache = host, + Aliases = new string[] + { + host + } + }); + } + } +} diff --git a/tests/Microsoft.Identity.Client.Performance/Program.cs b/tests/Microsoft.Identity.Client.Performance/Program.cs index 0b6565a5ad..b7cb0f6d0a 100644 --- a/tests/Microsoft.Identity.Client.Performance/Program.cs +++ b/tests/Microsoft.Identity.Client.Performance/Program.cs @@ -12,7 +12,7 @@ class Program { static void Main(string[] args) { - BenchmarkRunner.Run( + BenchmarkRunner.Run( DefaultConfig.Instance .WithOptions(ConfigOptions.DontOverwriteResults) .AddJob( diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/LegacyTokenCacheHelper.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/LegacyTokenCacheHelper.cs new file mode 100644 index 0000000000..a45b31ea53 --- /dev/null +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/LegacyTokenCacheHelper.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Identity.Client.Cache; +using Microsoft.Identity.Client.Cache.Items; +using Microsoft.Identity.Client.Core; +using Microsoft.Identity.Client.Internal; +using Microsoft.Identity.Test.Unit; + +namespace Microsoft.Identity.Test.Common.Core.Mocks +{ + internal class LegacyTokenCacheHelper + { + internal static void PopulateLegacyCache(ICoreLogger logger, ILegacyCachePersistence legacyCachePersistence, int tokenQuantity = 1) + { + for (int i = 1; i <= tokenQuantity; i++) + { + PopulateLegacyWithRtAndId( + logger, + legacyCachePersistence, + TestConstants.ClientId, + TestConstants.ProductionPrefCacheEnvironment, + TestConstants.Uid, + TestConstants.Utid, + $"{i}{TestConstants.DisplayableId}"); + } + } + + internal static void PopulateLegacyCache(ICoreLogger logger, ILegacyCachePersistence legacyCachePersistence) + { + PopulateLegacyWithRtAndId( + logger, + legacyCachePersistence, + TestConstants.ClientId, + TestConstants.ProductionPrefNetworkEnvironment, + "uid1", + "tenantId1", + "user1"); + + PopulateLegacyWithRtAndId( + logger, + legacyCachePersistence, + TestConstants.ClientId, + TestConstants.ProductionPrefNetworkEnvironment, + "uid2", + "tenantId2", + "user2"); + + PopulateLegacyWithRtAndId( + logger, + legacyCachePersistence, + TestConstants.ClientId, + TestConstants.ProductionPrefNetworkEnvironment, + null, + null, + "no_client_info_user3"); + + PopulateLegacyWithRtAndId( + logger, + legacyCachePersistence, + TestConstants.ClientId, + TestConstants.ProductionPrefNetworkEnvironment, + null, + null, + "no_client_info_user4"); + + PopulateLegacyWithRtAndId( + logger, + legacyCachePersistence, + TestConstants.ClientId, + TestConstants.SovereignNetworkEnvironment, // different env + "uid4", + "tenantId4", + "sovereign_user5"); + + PopulateLegacyWithRtAndId( + logger, + legacyCachePersistence, + "other_client_id", // different client id + TestConstants.SovereignNetworkEnvironment, + "uid5", + "tenantId5", + "user6"); + } + + internal static void PopulateLegacyWithRtAndId( + ICoreLogger logger, + ILegacyCachePersistence legacyCachePersistence, + string clientId, + string env, + string uid, + string uniqueTenantId, + string username) + { + PopulateLegacyWithRtAndId(logger, legacyCachePersistence, clientId, env, uid, uniqueTenantId, username, "scope1"); + } + + internal static void PopulateLegacyWithRtAndId( + ICoreLogger logger, + ILegacyCachePersistence legacyCachePersistence, + string clientId, + string env, + string uid, + string uniqueTenantId, + string username, + string scope) + { + string clientInfoString; + string homeAccountId; + if (string.IsNullOrEmpty(uid) || string.IsNullOrEmpty(uniqueTenantId)) + { + clientInfoString = null; + homeAccountId = null; + } + else + { + clientInfoString = MockHelpers.CreateClientInfo(uid, uniqueTenantId); + homeAccountId = ClientInfo.CreateFromJson(clientInfoString).ToAccountIdentifier(); + } + + var rtItem = new MsalRefreshTokenCacheItem(env, clientId, "someRT", clientInfoString, null, homeAccountId); + + var idTokenCacheItem = new MsalIdTokenCacheItem( + env, + clientId, + MockHelpers.CreateIdToken(uid, username), + clientInfoString, + homeAccountId, + tenantId: uniqueTenantId); + + CacheFallbackOperations.WriteAdalRefreshToken( + logger, + legacyCachePersistence, + rtItem, + idTokenCacheItem, + "https://" + env + "/common", + uid, + scope); + } + } +} diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/TokenCacheHelper.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/TokenCacheHelper.cs index 59549b2024..f2e8c9bda9 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/TokenCacheHelper.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/TokenCacheHelper.cs @@ -15,9 +15,9 @@ internal class TokenCacheHelper public static long ValidExpiresIn = 28800; public static long ValidExtendedExpiresIn = 57600; - internal void PopulateCacheForClientCredential(ITokenCacheAccessor accessor, int tokensQuantity = 1) + internal void PopulateCacheForClientCredential(ITokenCacheAccessor accessor, int tokenQuantity = 1) { - for (int i = 1; i <= tokensQuantity; i++) + for (int i = 1; i <= tokenQuantity; i++) { var atItem = CreateAccessTokenItem(string.Format(System.Globalization.CultureInfo.InvariantCulture, TestConstants.ScopeStrFormat, i)); accessor.SaveAccessToken(atItem); @@ -190,6 +190,14 @@ internal void PopulateCacheWithOneAccessToken(ITokenCacheAccessor accessor) accessor.SaveRefreshToken(rtItem); } + public static void AddRefreshTokensToCache(ITokenCacheAccessor cacheAccessor, int tokensQuantity = 1) + { + for (int i = 1; i <= tokensQuantity; i++) + { + AddRefreshTokenToCache(cacheAccessor, Guid.NewGuid().ToString(), TestConstants.Utid); + } + } + public static void AddAccountToCache( ITokenCacheAccessor accessor, string uid, diff --git a/tests/Microsoft.Identity.Test.Common/TestCommon.cs b/tests/Microsoft.Identity.Test.Common/TestCommon.cs index 938d84fe22..bfd97a92aa 100644 --- a/tests/Microsoft.Identity.Test.Common/TestCommon.cs +++ b/tests/Microsoft.Identity.Test.Common/TestCommon.cs @@ -56,7 +56,8 @@ public static object GetPropValue(object src, string propName) bool enablePiiLogging = false, string clientId = TestConstants.ClientId, bool clearCaches = true, - bool validateAuthority = true) + bool validateAuthority = true, + bool isLegacyCacheEnabled = true) { var appConfig = new ApplicationConfiguration() @@ -69,7 +70,8 @@ public static object GetPropValue(object src, string propName) LogLevel = LogLevel.Verbose, EnablePiiLogging = enablePiiLogging, IsExtendedTokenLifetimeEnabled = isExtendedTokenLifetimeEnabled, - AuthorityInfo = AuthorityInfo.FromAuthorityUri(authority, validateAuthority) + AuthorityInfo = AuthorityInfo.FromAuthorityUri(authority, validateAuthority), + LegacyCacheCompatibilityEnabled = isLegacyCacheEnabled }; return new ServiceBundle(appConfig, clearCaches); } diff --git a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs index 5282eeb865..06617dfcbe 100644 --- a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs @@ -338,5 +338,17 @@ public void TestConstructor_BadInstanceMetadata() Assert.AreEqual(ex.ErrorCode, MsalError.InvalidUserInstanceMetadata); } + + [TestMethod] + public void TestConstructor_WithLegacyCacheCompatibility() + { + var cca = ConfidentialClientApplicationBuilder + .Create(TestConstants.ClientId) + .WithClientSecret(TestConstants.ClientSecret) + .WithLegacyCacheCompatibility(true) + .Build(); + + Assert.AreEqual(true, cca.AppConfig.LegacyCacheCompatibilityEnabled); + } } } diff --git a/tests/Microsoft.Identity.Test.Unit/CacheTests/CacheFallbackOperationsTests.cs b/tests/Microsoft.Identity.Test.Unit/CacheTests/CacheFallbackOperationsTests.cs index ce400e4f58..4f7c815933 100644 --- a/tests/Microsoft.Identity.Test.Unit/CacheTests/CacheFallbackOperationsTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/CacheTests/CacheFallbackOperationsTests.cs @@ -39,8 +39,7 @@ public void TestInitialize() public void GetAllAdalUsersForMsal_ScopedBy_ClientIdAndEnv() { // Arrange - PopulateLegacyCache(_legacyCachePersistence); - + LegacyTokenCacheHelper.PopulateLegacyCache(_logger, _legacyCachePersistence); // Act - query users by env and clientId var adalUsers = @@ -97,7 +96,7 @@ public void GetAllAdalUsersForMsal_ScopedBy_ClientIdAndEnv() public void GetAllAdalEntriesForMsal_FilterBy_Upn() { // Arrange - PopulateLegacyCache(_legacyCachePersistence); + LegacyTokenCacheHelper.PopulateLegacyCache(_logger, _legacyCachePersistence); // Act - query Adal Entries For Msal with valid Upn as a filter var rt = @@ -109,7 +108,6 @@ public void GetAllAdalEntriesForMsal_FilterBy_Upn() TestConstants.SovereignNetworkEnvironment }, TestConstants.ClientId, new Account(null, "User1", null)); - Assert.AreEqual("uid1.tenantId1", rt.HomeAccountId); @@ -131,7 +129,7 @@ public void GetAllAdalEntriesForMsal_FilterBy_Upn() public void GetAllAdalEntriesForMsal_FilterBy_UniqueId() { // Arrange - PopulateLegacyCache(_legacyCachePersistence); + LegacyTokenCacheHelper.PopulateLegacyCache(_logger, _legacyCachePersistence); // Act - query Adal Entries For Msal with valid UniqueId as a filter var rt = @@ -164,7 +162,7 @@ public void GetAllAdalEntriesForMsal_FilterBy_UniqueId() public void GetAllAdalEntriesForMsal_NoFilter() { // Arrange - PopulateLegacyCache(_legacyCachePersistence); + LegacyTokenCacheHelper.PopulateLegacyCache(_logger, _legacyCachePersistence); // Act - query Adal Entries For Msal with valid Upn and UniqueId as a filter var rt = @@ -184,7 +182,7 @@ public void GetAllAdalEntriesForMsal_NoFilter() public void GetAllAdalEntriesForMsal_FilterBy_UniqueIdAndUpn() { // Arrange - PopulateLegacyCache(_legacyCachePersistence); + LegacyTokenCacheHelper.PopulateLegacyCache(_logger, _legacyCachePersistence); // Act - query Adal Entries For Msal with valid Upn and UniqueId as a filter var rt = @@ -200,7 +198,7 @@ public void GetAllAdalEntriesForMsal_FilterBy_UniqueIdAndUpn() Assert.AreEqual("uid1.tenantId1", rt.HomeAccountId); // Act - query Adal Entries For Msal with invalid Upn and valid UniqueId as a filter - rt = + rt = CacheFallbackOperations.GetRefreshToken( _logger, _legacyCachePersistence, @@ -230,15 +228,17 @@ public void GetAllAdalEntriesForMsal_FilterBy_UniqueIdAndUpn() public void GetAllAdalEntriesForMsal_MultipleRTsPerEnv() { // Arrange - PopulateLegacyWithRtAndId( - _legacyCachePersistence, - TestConstants.ClientId, - TestConstants.ProductionPrefNetworkEnvironment, - "uid", - "tenantId1", - "user1"); - - PopulateLegacyWithRtAndId( + LegacyTokenCacheHelper.PopulateLegacyWithRtAndId( + _logger, + _legacyCachePersistence, + TestConstants.ClientId, + TestConstants.ProductionPrefNetworkEnvironment, + "uid", + "tenantId1", + "user1"); + + LegacyTokenCacheHelper.PopulateLegacyWithRtAndId( + _logger, _legacyCachePersistence, TestConstants.ClientId, TestConstants.ProductionPrefNetworkEnvironment, @@ -262,9 +262,10 @@ public void GetAllAdalEntriesForMsal_MultipleRTsPerEnv() public void RemoveAdalUser_RemovesUserWithSameId() { // Arrange - PopulateLegacyCache(_legacyCachePersistence); + LegacyTokenCacheHelper.PopulateLegacyCache(_logger, _legacyCachePersistence); - PopulateLegacyWithRtAndId( // different clientId -> should not be deleted + LegacyTokenCacheHelper.PopulateLegacyWithRtAndId( // different clientId -> should not be deleted + _logger, _legacyCachePersistence, "other_client_id", TestConstants.ProductionPrefNetworkEnvironment, @@ -272,7 +273,8 @@ public void RemoveAdalUser_RemovesUserWithSameId() "tenantId1", "user1_other_client_id"); - PopulateLegacyWithRtAndId( // different env -> should be deleted + LegacyTokenCacheHelper.PopulateLegacyWithRtAndId( // different env -> should be deleted + _logger, _legacyCachePersistence, TestConstants.ClientId, "other_env", @@ -297,7 +299,7 @@ public void RemoveAdalUser_RemovesUserWithSameId() AssertByUsername( adalUsers, - new[] { TestConstants.ProductionPrefNetworkEnvironment}, + new[] { TestConstants.ProductionPrefNetworkEnvironment }, new[] { "user2", @@ -313,9 +315,10 @@ public void RemoveAdalUser_RemovesUserWithSameId() public void RemoveAdalUser_RemovesUserNoClientInfo() { // Arrange - PopulateLegacyCache(_legacyCachePersistence); + LegacyTokenCacheHelper.PopulateLegacyCache(_logger, _legacyCachePersistence); - PopulateLegacyWithRtAndId( + LegacyTokenCacheHelper.PopulateLegacyWithRtAndId( + _logger, _legacyCachePersistence, "other_client_id", TestConstants.ProductionPrefNetworkEnvironment, @@ -323,7 +326,8 @@ public void RemoveAdalUser_RemovesUserNoClientInfo() null, "no_client_info_user3"); // no client info, different client id -> won't be deleted - PopulateLegacyWithRtAndId( + LegacyTokenCacheHelper.PopulateLegacyWithRtAndId( + _logger, _legacyCachePersistence, TestConstants.ClientId, "other_env", @@ -394,7 +398,7 @@ private void AssertCacheEntryCount(int expectedEntryCount) public void RemoveAdalUser_RemovesUserNoClientInfo_And_NoDisplayName() { // Arrange - PopulateLegacyCache(_legacyCachePersistence); + LegacyTokenCacheHelper.PopulateLegacyCache(_logger, _legacyCachePersistence); IDictionary adalCacheBeforeDelete = AdalCacheOperations.Deserialize(_logger, _legacyCachePersistence.LoadCache()); Assert.AreEqual(6, adalCacheBeforeDelete.Count); @@ -420,7 +424,8 @@ public void RemoveAdalUser_RemovesAdalEntitiesWithClientInfoAndWithout() // adal cache can have different cache entities for the // same user/account with client info and wihout // CacheFallbackOperations.RemoveAdalUser should remove both - PopulateLegacyWithRtAndId( + LegacyTokenCacheHelper.PopulateLegacyWithRtAndId( + _logger, _legacyCachePersistence, TestConstants.ClientId, TestConstants.ProductionPrefNetworkEnvironment, @@ -431,7 +436,8 @@ public void RemoveAdalUser_RemovesAdalEntitiesWithClientInfoAndWithout() AssertCacheEntryCount(1); - PopulateLegacyWithRtAndId( + LegacyTokenCacheHelper.PopulateLegacyWithRtAndId( + _logger, _legacyCachePersistence, TestConstants.ClientId, TestConstants.ProductionPrefNetworkEnvironment, @@ -463,17 +469,17 @@ public void WriteAdalRefreshToken_ErrorLog() var rtItem = new MsalRefreshTokenCacheItem( TestConstants.ProductionPrefNetworkEnvironment, TestConstants.ClientId, - "someRT", - clientInfo, - null, + "someRT", + clientInfo, + null, homeAccountId); var idTokenCacheItem = new MsalIdTokenCacheItem( TestConstants.ProductionPrefCacheEnvironment, // different env TestConstants.ClientId, MockHelpers.CreateIdToken("u1", "username"), - clientInfo, - tenantId: "ut1", + clientInfo, + tenantId: "ut1", homeAccountId: homeAccountId); // Act @@ -505,16 +511,16 @@ public void DoNotWriteFRTs() TestConstants.ProductionPrefNetworkEnvironment, TestConstants.ClientId, "someRT", - clientInfo, - "familyId", + clientInfo, + "familyId", homeAccountId); var idTokenCacheItem = new MsalIdTokenCacheItem( TestConstants.ProductionPrefNetworkEnvironment, // different env TestConstants.ClientId, MockHelpers.CreateIdToken("u1", "username"), - clientInfo, - tenantId: "ut1", + clientInfo, + tenantId: "ut1", homeAccountId: homeAccountId); // Act @@ -530,57 +536,6 @@ public void DoNotWriteFRTs() AssertCacheEntryCount(0); } - private void PopulateLegacyCache(ILegacyCachePersistence legacyCachePersistence) - { - PopulateLegacyWithRtAndId( - legacyCachePersistence, - TestConstants.ClientId, - TestConstants.ProductionPrefNetworkEnvironment, - "uid1", - "tenantId1", - "user1"); - - PopulateLegacyWithRtAndId( - legacyCachePersistence, - TestConstants.ClientId, - TestConstants.ProductionPrefNetworkEnvironment, - "uid2", - "tenantId2", - "user2"); - - PopulateLegacyWithRtAndId( - legacyCachePersistence, - TestConstants.ClientId, - TestConstants.ProductionPrefNetworkEnvironment, - null, - null, - "no_client_info_user3"); - - PopulateLegacyWithRtAndId( - legacyCachePersistence, - TestConstants.ClientId, - TestConstants.ProductionPrefNetworkEnvironment, - null, - null, - "no_client_info_user4"); - - PopulateLegacyWithRtAndId( - legacyCachePersistence, - TestConstants.ClientId, - TestConstants.SovereignNetworkEnvironment, // different env - "uid4", - "tenantId4", - "sovereign_user5"); - - PopulateLegacyWithRtAndId( - legacyCachePersistence, - "other_client_id", // different client id - TestConstants.SovereignNetworkEnvironment, - "uid5", - "tenantId5", - "user6"); - } - private static void AssertUsersByDisplayName( IEnumerable expectedUsernames, IEnumerable adalUserInfos, @@ -591,59 +546,6 @@ private void PopulateLegacyCache(ILegacyCachePersistence legacyCachePersistence) CollectionAssert.AreEquivalent(expectedUsernames.ToArray(), actualUsernames, errorMessage); } - private void PopulateLegacyWithRtAndId( - ILegacyCachePersistence legacyCachePersistence, - string clientId, - string env, - string uid, - string uniqueTenantId, - string username) - { - PopulateLegacyWithRtAndId(legacyCachePersistence, clientId, env, uid, uniqueTenantId, username, "scope1"); - } - - private void PopulateLegacyWithRtAndId( - ILegacyCachePersistence legacyCachePersistence, - string clientId, - string env, - string uid, - string uniqueTenantId, - string username, - string scope) - { - string clientInfoString; - string homeAccountId; - if (string.IsNullOrEmpty(uid) || string.IsNullOrEmpty(uniqueTenantId)) - { - clientInfoString = null; - homeAccountId = null; - } - else - { - clientInfoString = MockHelpers.CreateClientInfo(uid, uniqueTenantId); - homeAccountId = ClientInfo.CreateFromJson(clientInfoString).ToAccountIdentifier(); - } - - var rtItem = new MsalRefreshTokenCacheItem(env, clientId, "someRT", clientInfoString, null, homeAccountId); - - var idTokenCacheItem = new MsalIdTokenCacheItem( - env, - clientId, - MockHelpers.CreateIdToken(uid, username), - clientInfoString, - homeAccountId, - tenantId: uniqueTenantId); - - CacheFallbackOperations.WriteAdalRefreshToken( - _logger, - legacyCachePersistence, - rtItem, - idTokenCacheItem, - "https://" + env + "/common", - uid, - scope); - } - private static void AssertByUsername( AdalUsersForMsal adalUsers, IEnumerable enviroments, diff --git a/tests/Microsoft.Identity.Test.Unit/CacheTests/TokenCacheTests.cs b/tests/Microsoft.Identity.Test.Unit/CacheTests/TokenCacheTests.cs index d69e2f93da..bde0199f9b 100644 --- a/tests/Microsoft.Identity.Test.Unit/CacheTests/TokenCacheTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/CacheTests/TokenCacheTests.cs @@ -44,6 +44,45 @@ public override void TestInitialize() base.TestInitialize(); } + [DataTestMethod] + [DataRow(true)] + [DataRow(false)] + public async Task WithLegacyCacheCompatibilityTest_Async(bool enableLegacyCacheCompatibility) + { + // Arrange + var legacyCachePersistence = Substitute.For(); + var serviceBundle = TestCommon.CreateServiceBundleWithCustomHttpManager(null, isLegacyCacheEnabled: enableLegacyCacheCompatibility); + var requestContext = new RequestContext(serviceBundle, Guid.NewGuid()); + var response = TestConstants.CreateMsalTokenResponse(); + + ITokenCacheInternal cache = new TokenCache(serviceBundle, false); + ((TokenCache)cache).LegacyCachePersistence = legacyCachePersistence; + + var requestParams = TestCommon.CreateAuthenticationRequestParameters(serviceBundle); + requestParams.TenantUpdatedCanonicalAuthority = Authority.CreateAuthorityWithTenant( + requestParams.AuthorityInfo, + TestConstants.Utid); + requestParams.Account = new Account(TestConstants.s_userIdentifier, $"1{TestConstants.DisplayableId}", TestConstants.ProductionPrefNetworkEnvironment); + + // Act + await cache.FindRefreshTokenAsync(requestParams).ConfigureAwait(true); + await cache.SaveTokenResponseAsync(requestParams, response).ConfigureAwait(true); + await cache.GetAccountsAsync(requestParams).ConfigureAwait(true); + await cache.RemoveAccountAsync(requestParams.Account, requestContext).ConfigureAwait(true); + + // Assert + if (enableLegacyCacheCompatibility) + { + legacyCachePersistence.ReceivedWithAnyArgs().LoadCache(); + legacyCachePersistence.ReceivedWithAnyArgs().WriteCache(Arg.Any()); + } + else + { + legacyCachePersistence.DidNotReceiveWithAnyArgs().LoadCache(); + legacyCachePersistence.DidNotReceiveWithAnyArgs().WriteCache(Arg.Any()); + } + } + [TestMethod] public void GetExactScopesMatchedAccessTokenTest() {