public void WriteAdalRefreshToken_ErrorLog()
        {
            // Arrange
            _legacyCachePersistence.ThrowOnWrite = true;

            var rtItem = new MsalRefreshTokenCacheItem(
                CoreTestConstants.ProductionPrefNetworkEnvironment,
                CoreTestConstants.ClientId,
                "someRT",
                MockHelpers.CreateClientInfo("u1", "ut1"));

            var idTokenCacheItem = new MsalIdTokenCacheItem(
                CoreTestConstants.ProductionPrefCacheEnvironment, // different env
                CoreTestConstants.ClientId,
                MockHelpers.CreateIdToken("u1", "username"),
                MockHelpers.CreateClientInfo("u1", "ut1"),
                "ut1");

            // Act
            CacheFallbackOperations.WriteAdalRefreshToken(
                _legacyCachePersistence,
                rtItem,
                idTokenCacheItem,
                "https://some_env.com/common", // yet another env
                "uid",
                "scope1");

            // Assert
            MsalLogger.Default.Received().Error(Arg.Is <string>(CacheFallbackOperations.DifferentAuthorityError));

            MsalLogger.Default.Received().Error(Arg.Is <string>(CacheFallbackOperations.DifferentEnvError));
        }
Example #2
0
        public void UnifiedCache_ProcessAdalDictionaryForDuplicateKeyTest()
        {
            using (var harness = CreateTestHarness())
            {
                var app = PublicClientApplicationBuilder
                          .Create(MsalTestConstants.ClientId)
                          .WithAuthority(new Uri(ClientApplicationBase.DefaultAuthority), true)
                          .WithUserTokenLegacyCachePersistenceForTest(new TestLegacyCachePersistance())
                          .WithTelemetry(new TraceTelemetryConfig())
                          .BuildConcrete();

                CreateAdalCache(harness.ServiceBundle.DefaultLogger, app.UserTokenCacheInternal.LegacyPersistence, MsalTestConstants.Scope.ToString());

                var adalUsers =
                    CacheFallbackOperations.GetAllAdalUsersForMsal(
                        harness.ServiceBundle.DefaultLogger,
                        app.UserTokenCacheInternal.LegacyPersistence,
                        MsalTestConstants.ClientId);

                CreateAdalCache(harness.ServiceBundle.DefaultLogger, app.UserTokenCacheInternal.LegacyPersistence, "user.read");

                var adalUsers2 =
                    CacheFallbackOperations.GetAllAdalUsersForMsal(
                        harness.ServiceBundle.DefaultLogger,
                        app.UserTokenCacheInternal.LegacyPersistence,
                        MsalTestConstants.ClientId);

                Assert.AreEqual(
                    adalUsers.GetUsersWithClientInfo(null).Single().Key,
                    adalUsers2.GetUsersWithClientInfo(null).Single().Key);

                app.UserTokenCacheInternal.Accessor.ClearAccessTokens();
                app.UserTokenCacheInternal.Accessor.ClearRefreshTokens();
            }
        }
Example #3
0
        public void DoNotWriteFRTs()
        {
            // Arrange
            _legacyCachePersistence.ThrowOnWrite = true;
            string clientInfo    = MockHelpers.CreateClientInfo("u1", "ut1");
            string homeAccountId = ClientInfo.CreateFromJson(clientInfo).ToAccountIdentifier();

            var rtItem = new MsalRefreshTokenCacheItem(
                TestConstants.ProductionPrefNetworkEnvironment,
                TestConstants.ClientId,
                "someRT",
                clientInfo,
                "familyId",
                homeAccountId);

            var idTokenCacheItem = new MsalIdTokenCacheItem(
                TestConstants.ProductionPrefNetworkEnvironment, // different env
                TestConstants.ClientId,
                MockHelpers.CreateIdToken("u1", "username"),
                clientInfo,
                tenantId: "ut1",
                homeAccountId: homeAccountId);

            // Act
            CacheFallbackOperations.WriteAdalRefreshToken(
                _logger,
                _legacyCachePersistence,
                rtItem,
                idTokenCacheItem,
                "https://some_env.com/common", // yet another env
                "uid",
                "scope1");

            AssertCacheEntryCount(0);
        }
Example #4
0
        public void GetAllAdalEntriesForMsal_FilterBy_Upn()
        {
            // Arrange
            PopulateLegacyCache(_legacyCachePersistence);

            // Act - query Adal Entries For Msal with valid Upn as a filter
            var rt =
                CacheFallbackOperations.GetAdalEntryForMsal(
                    _logger,
                    _legacyCachePersistence,
                    new[] {
                TestConstants.ProductionPrefNetworkEnvironment,
                TestConstants.SovereignNetworkEnvironment
            },
                    TestConstants.ClientId,
                    "User1", null);

            Assert.AreEqual("uid1.tenantId1", rt.HomeAccountId);

            // Act - query Adal Entries For Msal with invalid Upn as a filter
            rt =
                CacheFallbackOperations.GetAdalEntryForMsal(
                    _logger,
                    _legacyCachePersistence,
                    new[] {
                TestConstants.ProductionPrefNetworkEnvironment,
                TestConstants.SovereignNetworkEnvironment
            },
                    TestConstants.ClientId,
                    "UserX", null);

            Assert.IsNull(rt, "Expected to find no items");
        }
Example #5
0
        public void GetAllAdalEntriesForMsal_MultipleRTsPerEnv()
        {
            // Arrange
            PopulateLegacyWithRtAndId(
                _legacyCachePersistence,
                TestConstants.ClientId,
                TestConstants.ProductionPrefNetworkEnvironment,
                "uid",
                "tenantId",
                "user1");

            PopulateLegacyWithRtAndId(
                _legacyCachePersistence,
                TestConstants.ClientId,
                TestConstants.ProductionPrefNetworkEnvironment,
                "uid",
                "tenantId",
                "user2");

            // Act
            var rt = CacheFallbackOperations.GetAdalEntryForMsal(
                _logger,
                _legacyCachePersistence,
                new[] { TestConstants.ProductionPrefNetworkEnvironment },
                TestConstants.ClientId,
                null,
                "uid");

            // Assert
            Assert.IsNull(rt, "Null should be returned.");
        }
        public void GetAllAdalEntriesForMsal_MultipleRTsPerEnv()
        {
            // Arrange
            PopulateLegacyWithRtAndId(
                _legacyCachePersistence,
                TestConstants.ClientId,
                TestConstants.ProductionPrefNetworkEnvironment,
                "uid",
                "tenantId1",
                "user1");

            PopulateLegacyWithRtAndId(
                _legacyCachePersistence,
                TestConstants.ClientId,
                TestConstants.ProductionPrefNetworkEnvironment,
                "uid",
                "tenantId2",
                "user2");

            // Act
            var rt = CacheFallbackOperations.GetRefreshToken(
                _logger,
                _legacyCachePersistence,
                new[] { TestConstants.ProductionPrefNetworkEnvironment },
                TestConstants.ClientId,
                new Account("uid.b", null, null));

            // Assert
            Assert.IsNotNull(rt, "One of the tokens should be returned");
        }
        public void GetAllAdalEntriesForMsal_FilterBy_UniqueId()
        {
            // Arrange
            LegacyTokenCacheHelper.PopulateLegacyCache(_logger, _legacyCachePersistence);

            // Act - query Adal Entries For Msal with valid UniqueId as a filter
            var rt =
                CacheFallbackOperations.GetRefreshToken(
                    _logger,
                    _legacyCachePersistence,
                    new[] {
                TestConstants.ProductionPrefNetworkEnvironment,
                TestConstants.SovereignNetworkEnvironment
            },
                    TestConstants.ClientId,
                    new Account("uid2.tenant", null, null));

            Assert.AreEqual("uid2.tenantId2", rt.HomeAccountId);

            // Act - query Adal Entries For Msal with invalid UniqueId as a filter
            rt =
                CacheFallbackOperations.GetRefreshToken(
                    _logger,
                    _legacyCachePersistence,
                    new[] {
                TestConstants.ProductionPrefNetworkEnvironment,
                TestConstants.SovereignNetworkEnvironment
            },
                    TestConstants.ClientId,
                    new Account("udiX.tenant", null, null));

            Assert.IsNull(rt, "Expected to find no items");
        }
        async Task <MsalRefreshTokenCacheItem> ITokenCacheInternal.FindRefreshTokenAsync(
            AuthenticationRequestParameters requestParams,
            string familyId)
        {
            if (requestParams.Authority == null)
            {
                return(null);
            }

            IEnumerable <MsalRefreshTokenCacheItem> allRts = _accessor.GetAllRefreshTokens();

            var metadata =
                await ServiceBundle.InstanceDiscoveryManager.GetMetadataEntryTryAvoidNetworkAsync(
                    requestParams.AuthorityInfo.CanonicalAuthority,
                    allRts.Select(rt => rt.Environment),  // if all environments are known, a network call can be avoided
                    requestParams.RequestContext)
                .ConfigureAwait(false);

            var aliases = metadata.Aliases;

            IEnumerable <MsalRefreshTokenCacheKey> candidateRtKeys = aliases.Select(
                al => new MsalRefreshTokenCacheKey(
                    al,
                    requestParams.ClientId,
                    requestParams.Account?.HomeAccountId?.Identifier,
                    familyId));

            MsalRefreshTokenCacheItem candidateRt = allRts.FirstOrDefault(
                rt => candidateRtKeys.Any(
                    candidateKey => object.Equals(rt.GetKey(), candidateKey)));

            requestParams.RequestContext.Logger.Info("Refresh token found in the cache? - " + (candidateRt != null));

            if (candidateRt != null)
            {
                return(candidateRt);
            }

            requestParams.RequestContext.Logger.Info("Checking ADAL cache for matching RT");

            string upn = string.IsNullOrWhiteSpace(requestParams.LoginHint)
                ? requestParams.Account?.Username
                : requestParams.LoginHint;

            // ADAL legacy cache does not store FRTs
            if (requestParams.Account != null && string.IsNullOrEmpty(familyId))
            {
                return(CacheFallbackOperations.GetAdalEntryForMsal(
                           Logger,
                           LegacyCachePersistence,
                           metadata.PreferredCache,
                           aliases,
                           requestParams.ClientId,
                           upn,
                           requestParams.Account.HomeAccountId?.Identifier));
            }

            return(null);
        }
Example #9
0
        public void GetAllAdalUsersForMsal_ScopedBy_ClientIdAndEnv()
        {
            // Arrange
            PopulateLegacyCache(_legacyCachePersistence);


            // Act - query users by env and clientId
            var adalUsers =
                CacheFallbackOperations.GetAllAdalUsersForMsal(
                    _logger,
                    _legacyCachePersistence,
                    TestConstants.ClientId);

            AssertByUsername(
                adalUsers,
                new[] {
                TestConstants.ProductionPrefNetworkEnvironment,
                TestConstants.SovereignNetworkEnvironment
            },
                new[]
            {
                "user1",
                "user2",
                "sovereign_user5"
            },
                new[]
            {
                "no_client_info_user3",
                "no_client_info_user4"
            });

            AssertByUsername(
                adalUsers,
                new[] {
                TestConstants.SovereignNetworkEnvironment
            },
                new[]
            {
                "sovereign_user5"
            },
                Enumerable.Empty <string>());

            // Act - query users for different clientId and env
            adalUsers = CacheFallbackOperations.GetAllAdalUsersForMsal(
                _logger,
                _legacyCachePersistence,
                "other_client_id");

            // Assert
            AssertByUsername(
                adalUsers,
                null,
                new[]
            {
                "user6"
            },
                Enumerable.Empty <string>());
        }
Example #10
0
 private void RemoveAdalUser(IAccount account)
 {
     CacheFallbackOperations.RemoveAdalUser(
         Logger,
         LegacyCachePersistence,
         ClientId,
         account.Username,
         account.HomeAccountId?.Identifier);
 }
        /// <remarks>
        /// Get accounts should not make a network call, if possible. This can be achieved if
        /// all the environments in the token cache are known to MSAL, as MSAL keeps a list of
        /// known environments in <see cref="KnownMetadataProvider"/>
        /// </remarks>
        async Task <IEnumerable <IAccount> > ITokenCacheInternal.GetAccountsAsync(string authority, RequestContext requestContext)
        {
            var  environment      = Authority.GetEnviroment(authority);
            bool filterByClientId = !_featureFlags.IsFociEnabled;

            IEnumerable <MsalRefreshTokenCacheItem> rtCacheItems      = GetAllRefreshTokensWithNoLocks(filterByClientId);
            IEnumerable <MsalAccountCacheItem>      accountCacheItems = _accessor.GetAllAccounts();

            AdalUsersForMsal adalUsersResult = CacheFallbackOperations.GetAllAdalUsersForMsal(
                Logger,
                LegacyCachePersistence,
                ClientId);

            // Multi-cloud support - must filter by env.
            ISet <string> allEnvironmentsInCache = new HashSet <string>(
                accountCacheItems.Select(aci => aci.Environment),
                StringComparer.OrdinalIgnoreCase);

            allEnvironmentsInCache.UnionWith(rtCacheItems.Select(rt => rt.Environment));
            allEnvironmentsInCache.UnionWith(adalUsersResult.GetAdalUserEnviroments());

            var instanceMetadata = await ServiceBundle.InstanceDiscoveryManager.GetMetadataEntryTryAvoidNetworkAsync(
                authority,
                allEnvironmentsInCache,
                requestContext).ConfigureAwait(false);

            rtCacheItems      = rtCacheItems.Where(rt => instanceMetadata.Aliases.ContainsOrdinalIgnoreCase(rt.Environment));
            accountCacheItems = accountCacheItems.Where(acc => instanceMetadata.Aliases.ContainsOrdinalIgnoreCase(acc.Environment));

            IDictionary <string, Account> clientInfoToAccountMap = new Dictionary <string, Account>();

            foreach (MsalRefreshTokenCacheItem rtItem in rtCacheItems)
            {
                foreach (MsalAccountCacheItem account in accountCacheItems)
                {
                    if (RtMatchesAccount(rtItem, account))
                    {
                        clientInfoToAccountMap[rtItem.HomeAccountId] = new Account(
                            account.HomeAccountId,
                            account.PreferredUsername,
                            environment);  // Preserve the env passed in by the user

                        break;
                    }
                }
            }

            IEnumerable <IAccount> accounts = UpdateWithAdalAccounts(
                environment,
                instanceMetadata.Aliases,
                adalUsersResult,
                clientInfoToAccountMap);

            return(accounts);
        }
        public void RemoveAdalUser_RemovesUserWithSameId()
        {
            // Arrange
            LegacyTokenCacheHelper.PopulateLegacyCache(_logger, _legacyCachePersistence);

            LegacyTokenCacheHelper.PopulateLegacyWithRtAndId( // different clientId -> should not be deleted
                _logger,
                _legacyCachePersistence,
                "other_client_id",
                TestConstants.ProductionPrefNetworkEnvironment,
                "uid1",
                "tenantId1",
                "user1_other_client_id");

            LegacyTokenCacheHelper.PopulateLegacyWithRtAndId( // different env -> should be deleted
                _logger,
                _legacyCachePersistence,
                TestConstants.ClientId,
                "other_env",
                "uid1",
                "tenantId1",
                "user1_other_env");

            // Act - delete with id and displayname
            CacheFallbackOperations.RemoveAdalUser(
                _logger,
                _legacyCachePersistence,
                TestConstants.ClientId,
                "username_does_not_matter",
                "uid1.tenantId1");

            // Assert
            var adalUsers =
                CacheFallbackOperations.GetAllAdalUsersForMsal(
                    _logger,
                    _legacyCachePersistence,
                    TestConstants.ClientId);

            AssertByUsername(
                adalUsers,
                new[] { TestConstants.ProductionPrefNetworkEnvironment },
                new[]
            {
                "user2",
            },
                new[]
            {
                "no_client_info_user3",
                "no_client_info_user4"
            });
        }
        public void RemoveAdalUser_RemovesUserWithSameId()
        {
            // Arrange
            PopulateLegacyCache(_legacyCachePersistence);

            PopulateLegacyWithRtAndId( // different clientId -> should not be deleted
                _legacyCachePersistence,
                "other_client_id",
                CoreTestConstants.ProductionPrefNetworkEnvironment,
                "uid1",
                "tenantId1",
                "user1_other_client_id");

            PopulateLegacyWithRtAndId( // different env -> should be deleted
                _legacyCachePersistence,
                CoreTestConstants.ClientId,
                "other_env",
                "uid1",
                "tenantId1",
                "user1_other_env");

            // Act - delete with id and displayname
            CacheFallbackOperations.RemoveAdalUser(
                _legacyCachePersistence,
                CoreTestConstants.ClientId,
                "username_does_not_matter",
                "uid1.tenantId1");

            // Assert
            Tuple <Dictionary <string, AdalUserInfo>, List <AdalUserInfo> > userTuple =
                CacheFallbackOperations.GetAllAdalUsersForMsal(
                    _legacyCachePersistence,
                    CoreTestConstants.ClientId);

            AssertByUsername(
                userTuple,
                new[]
            {
                "user2",
                "sovereign_user5"      // this user has different environment but same client id
            },
                new[]
            {
                "no_client_info_user3",
                "no_client_info_user4"
            });
        }
        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);
        }
        public void GetAllAdalEntriesForMsal_NoFilter()
        {
            // Arrange
            PopulateLegacyCache(_legacyCachePersistence);

            // Act - query Adal Entries For Msal with valid Upn and UniqueId as a filter
            var rt =
                CacheFallbackOperations.GetRefreshToken(
                    _logger,
                    _legacyCachePersistence,
                    new[] {
                TestConstants.ProductionPrefNetworkEnvironment,
                TestConstants.SovereignNetworkEnvironment
            },
                    TestConstants.ClientId,
                    new Account(null, null, null)); // too litle info here, do not return RT

            Assert.IsNull(rt, "Expected to find no items");
        }
        [TestCategory(TestCategories.Regression)] // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/1815

        public async Task UnifiedCache_ProcessAdalDictionaryForDuplicateKey_Async()
        {
            using (var harness = CreateTestHarness())
            {
                var app = PublicClientApplicationBuilder
                          .Create(TestConstants.ClientId)
                          .WithAuthority(new Uri(ClientApplicationBase.DefaultAuthority), true)
                          .WithUserTokenLegacyCachePersistenceForTest(new TestLegacyCachePersistance())
                          .WithHttpManager(harness.HttpManager)
                          .BuildConcrete();

                CreateAdalCache(harness.ServiceBundle.ApplicationLogger, app.UserTokenCacheInternal.LegacyPersistence, TestConstants.s_scope.ToString());

                var adalUsers =
                    CacheFallbackOperations.GetAllAdalUsersForMsal(
                        harness.ServiceBundle.ApplicationLogger,
                        app.UserTokenCacheInternal.LegacyPersistence,
                        TestConstants.ClientId);

                CreateAdalCache(harness.ServiceBundle.ApplicationLogger, app.UserTokenCacheInternal.LegacyPersistence, "user.read");

                AdalUsersForMsal adalUsers2 =
                    CacheFallbackOperations.GetAllAdalUsersForMsal(
                        harness.ServiceBundle.ApplicationLogger,
                        app.UserTokenCacheInternal.LegacyPersistence,
                        TestConstants.ClientId);

                Assert.AreEqual(
                    adalUsers.GetUsersWithClientInfo(null).Single().Key,
                    adalUsers2.GetUsersWithClientInfo(null).Single().Key);

                var accounts = await app.GetAccountsAsync().ConfigureAwait(false);

                Assert.AreEqual(1, accounts.Count(), "The 2 RTs belong to the same user");

                // The ADAL cache contains access tokens, but these are NOT usable by MSAL / v2 endpoint.
                // MSAL will however use the RT from ADAL to fetch new access tokens...
                harness.HttpManager.AddAllMocks(TokenResponseType.Valid);
                var res = await app.AcquireTokenSilent(TestConstants.s_scope, accounts.First()).ExecuteAsync().ConfigureAwait(false);

                Assert.IsNotNull(res);
            }
        }
        public void RemoveAdalUser_RemovesUserNoClientInfo_And_NoDisplayName()
        {
            // Arrange
            PopulateLegacyCache(_legacyCachePersistence);
            IDictionary <AdalTokenCacheKey, AdalResultWrapper> adalCacheBeforeDelete =
                AdalCacheOperations.Deserialize(_legacyCachePersistence.LoadCache());

            Assert.AreEqual(6, adalCacheBeforeDelete.Count);

            // Act - nothing happens and a message is logged
            CacheFallbackOperations.RemoveAdalUser(
                _legacyCachePersistence,
                CoreTestConstants.ClientId,
                "",
                "");

            // Assert
            AssertCacheEntryCount(6);

            MsalLogger.Default.Received().Error(Arg.Is <string>(CoreErrorMessages.InternalErrorCacheEmptyUsername));
        }
        public void GetAllAdalUsersForMsal_ScopedBy_ClientIdAndEnv()
        {
            // Arrange
            PopulateLegacyCache(_legacyCachePersistence);

            // Act - query users by env and clientId
            var adalUsers =
                CacheFallbackOperations.GetAllAdalUsersForMsal(
                    _legacyCachePersistence,
                    CoreTestConstants.ClientId);

            AssertByUsername(
                adalUsers,
                new[]
            {
                "user1",
                "user2",
                "sovereign_user5"      // this user has different environment but same client id
            },
                new[]
            {
                "no_client_info_user3",
                "no_client_info_user4"
            });

            // Act - query users for different clientId and env
            adalUsers = CacheFallbackOperations.GetAllAdalUsersForMsal(
                _legacyCachePersistence,
                "other_client_id");

            // Assert
            AssertByUsername(
                adalUsers,
                new[]
            {
                "user6"
            },
                Enumerable.Empty <string>());
        }
        public void RemoveAdalUser_RemovesAdalEntitiesWithClientInfoAndWithout()
        {
            // in case of adalv3 -> adalv4 -> msal2 migration
            // adal cache can have different cache entities for the
            // same user/account with client info and wihout
            // CacheFallbackOperations.RemoveAdalUser should remove both
            LegacyTokenCacheHelper.PopulateLegacyWithRtAndId(
                _logger,
                _legacyCachePersistence,
                TestConstants.ClientId,
                TestConstants.ProductionPrefNetworkEnvironment,
                TestConstants.Uid,
                TestConstants.Utid,
                TestConstants.DisplayableId,
                TestConstants.ScopeStr);

            AssertCacheEntryCount(1);

            LegacyTokenCacheHelper.PopulateLegacyWithRtAndId(
                _logger,
                _legacyCachePersistence,
                TestConstants.ClientId,
                TestConstants.ProductionPrefNetworkEnvironment,
                null,
                null,
                TestConstants.DisplayableId,
                TestConstants.ScopeForAnotherResourceStr);

            AssertCacheEntryCount(2);

            CacheFallbackOperations.RemoveAdalUser(
                _logger,
                _legacyCachePersistence,
                TestConstants.ClientId,
                TestConstants.DisplayableId,
                TestConstants.Uid + "." + TestConstants.Utid);

            AssertCacheEntryCount(0);
        }
Example #20
0
        public void WriteAdalRefreshToken_ErrorLog()
        {
            // Arrange
            _legacyCachePersistence.ThrowOnWrite = true;
            string clientInfo    = MockHelpers.CreateClientInfo("u1", "ut1");
            string homeAccountId = ClientInfo.CreateFromJson(clientInfo).ToAccountIdentifier();

            var rtItem = new MsalRefreshTokenCacheItem(
                TestConstants.ProductionPrefNetworkEnvironment,
                TestConstants.ClientId,
                "someRT",
                clientInfo,
                null,
                homeAccountId);

            var idTokenCacheItem = new MsalIdTokenCacheItem(
                TestConstants.ProductionPrefCacheEnvironment, // different env
                TestConstants.ClientId,
                MockHelpers.CreateIdToken("u1", "username"),
                clientInfo,
                tenantId: "ut1",
                homeAccountId: homeAccountId);

            // Act
            CacheFallbackOperations.WriteAdalRefreshToken(
                _logger,
                _legacyCachePersistence,
                rtItem,
                idTokenCacheItem,
                "https://some_env.com/common", // yet another env
                "uid",
                "scope1");

            // Assert
            _logger.Received().Error(Arg.Is <string>(CacheFallbackOperations.DifferentAuthorityError));

            _logger.Received().Error(Arg.Is <string>(CacheFallbackOperations.DifferentEnvError));
        }
        private void PopulateLegacyWithRtAndId(
            ILegacyCachePersistence legacyCachePersistence,
            string clientId,
            string env,
            string uid,
            string uniqueTenantId,
            string username,
            string scope)
        {
            string clientInfoString;

            if (string.IsNullOrEmpty(uid) || string.IsNullOrEmpty(uniqueTenantId))
            {
                clientInfoString = null;
            }
            else
            {
                clientInfoString = MockHelpers.CreateClientInfo(uid, uniqueTenantId);
            }

            var rtItem = new MsalRefreshTokenCacheItem(env, clientId, "someRT", clientInfoString);

            var idTokenCacheItem = new MsalIdTokenCacheItem(
                env,
                clientId,
                MockHelpers.CreateIdToken(uid, username),
                clientInfoString,
                uniqueTenantId);

            CacheFallbackOperations.WriteAdalRefreshToken(
                _logger,
                legacyCachePersistence,
                rtItem,
                idTokenCacheItem,
                "https://" + env + "/common",
                "uid",
                scope);
        }
Example #22
0
        public void UnifiedCache_ProcessAdalDictionaryForDuplicateKeyTest()
        {
            using (var httpManager = new MockHttpManager())
            {
                var serviceBundle = ServiceBundle.CreateWithCustomHttpManager(httpManager);
                var app           = new PublicClientApplication(
                    serviceBundle,
                    MsalTestConstants.ClientId,
                    ClientApplicationBase.DefaultAuthority)
                {
                    UserTokenCache =
                    {
                        LegacyCachePersistence = new TestLegacyCachePersistance()
                    }
                };

                CreateAdalCache(app.UserTokenCache.LegacyCachePersistence, MsalTestConstants.Scope.ToString());

                var adalUsers =
                    CacheFallbackOperations.GetAllAdalUsersForMsal(
                        app.UserTokenCache.LegacyCachePersistence,
                        MsalTestConstants.ClientId);

                CreateAdalCache(app.UserTokenCache.LegacyCachePersistence, "user.read");

                var adalUsers2 =
                    CacheFallbackOperations.GetAllAdalUsersForMsal(
                        app.UserTokenCache.LegacyCachePersistence,
                        MsalTestConstants.ClientId);

                Assert.AreEqual(adalUsers.ClientInfoUsers.Keys.First(), adalUsers2.ClientInfoUsers.Keys.First());

                app.UserTokenCache.TokenCacheAccessor.ClearAccessTokens();
                app.UserTokenCache.TokenCacheAccessor.ClearRefreshTokens();
            }
        }
        async Task <Tuple <MsalAccessTokenCacheItem, MsalIdTokenCacheItem> > ITokenCacheInternal.SaveTokenResponseAsync(
            AuthenticationRequestParameters requestParams,
            MsalTokenResponse response)
        {
            var tenantId = Authority
                           .CreateAuthority(requestParams.TenantUpdatedCanonicalAuthority.AuthorityInfo.CanonicalAuthority)
                           .GetTenantId();

            bool isAdfsAuthority = requestParams.AuthorityInfo.AuthorityType == AuthorityType.Adfs;

            IdToken idToken = IdToken.Parse(response.IdToken);

            string subject = idToken?.Subject;

            if (idToken != null && string.IsNullOrEmpty(subject))
            {
                requestParams.RequestContext.Logger.Warning("Subject not present in Id token");
            }

            string preferredUsername = GetPreferredUsernameFromIdToken(isAdfsAuthority, idToken);

            // Do a full instance discovery when saving tokens (if not cached),
            // so that the PreferredNetwork environment is up to date.
            var instanceDiscoveryMetadata = await ServiceBundle.InstanceDiscoveryManager
                                            .GetMetadataEntryAsync(
                requestParams.TenantUpdatedCanonicalAuthority.AuthorityInfo.CanonicalAuthority,
                requestParams.RequestContext)
                                            .ConfigureAwait(false);

            var msalAccessTokenCacheItem =
                new MsalAccessTokenCacheItem(
                    instanceDiscoveryMetadata.PreferredCache,
                    requestParams.ClientId,
                    response,
                    tenantId,
                    subject,
                    requestParams.AuthenticationScheme.KeyId)
            {
                UserAssertionHash = requestParams.UserAssertion?.AssertionHash,
                IsAdfs            = isAdfsAuthority
            };

            MsalRefreshTokenCacheItem msalRefreshTokenCacheItem = null;
            MsalIdTokenCacheItem      msalIdTokenCacheItem      = null;

            if (idToken != null)
            {
                msalIdTokenCacheItem = new MsalIdTokenCacheItem(
                    instanceDiscoveryMetadata.PreferredCache,
                    requestParams.ClientId,
                    response,
                    tenantId,
                    subject)
                {
                    IsAdfs = isAdfsAuthority
                };
            }

            await _semaphoreSlim.WaitAsync().ConfigureAwait(false);

            try
            {
                try
                {
                    Account account  = null;
                    string  username = isAdfsAuthority ? idToken?.Upn : preferredUsername;
                    if (msalAccessTokenCacheItem.HomeAccountId != null)
                    {
                        account = new Account(
                            msalAccessTokenCacheItem.HomeAccountId,
                            username,
                            instanceDiscoveryMetadata.PreferredCache);
                    }
                    var args = new TokenCacheNotificationArgs(this, ClientId, account, true, (this as ITokenCacheInternal).IsApplicationCache);

#pragma warning disable CS0618 // Type or member is obsolete
                    HasStateChanged = true;
#pragma warning restore CS0618 // Type or member is obsolete

                    await(this as ITokenCacheInternal).OnBeforeAccessAsync(args).ConfigureAwait(false);
                    try
                    {
                        await(this as ITokenCacheInternal).OnBeforeWriteAsync(args).ConfigureAwait(false);

                        DeleteAccessTokensWithIntersectingScopes(
                            requestParams,
                            instanceDiscoveryMetadata.Aliases,
                            tenantId,
                            msalAccessTokenCacheItem.ScopeSet,
                            msalAccessTokenCacheItem.HomeAccountId,
                            msalAccessTokenCacheItem.TokenType);

                        _accessor.SaveAccessToken(msalAccessTokenCacheItem);

                        if (idToken != null)
                        {
                            _accessor.SaveIdToken(msalIdTokenCacheItem);
                            var msalAccountCacheItem = new MsalAccountCacheItem(
                                instanceDiscoveryMetadata.PreferredCache,
                                response,
                                preferredUsername,
                                tenantId);

                            //The ADFS direct scenario does not return client info so the home account id is acquired from the subject
                            if (isAdfsAuthority && String.IsNullOrEmpty(msalAccountCacheItem.HomeAccountId))
                            {
                                msalAccountCacheItem.HomeAccountId = idToken.Subject;
                            }

                            _accessor.SaveAccount(msalAccountCacheItem);
                        }

                        // if server returns the refresh token back, save it in the cache.
                        if (response.RefreshToken != null)
                        {
                            msalRefreshTokenCacheItem = new MsalRefreshTokenCacheItem(
                                instanceDiscoveryMetadata.PreferredCache,
                                requestParams.ClientId,
                                response,
                                subject);

                            if (!_featureFlags.IsFociEnabled)
                            {
                                msalRefreshTokenCacheItem.FamilyId = null;
                            }

                            requestParams.RequestContext.Logger.Info("Saving RT in cache...");
                            _accessor.SaveRefreshToken(msalRefreshTokenCacheItem);
                        }

                        UpdateAppMetadata(requestParams.ClientId, instanceDiscoveryMetadata.PreferredCache, response.FamilyId);

                        // save RT in ADAL cache for public clients
                        // do not save RT in ADAL cache for MSAL B2C scenarios
                        if (!requestParams.IsClientCredentialRequest && !requestParams.AuthorityInfo.AuthorityType.Equals(AuthorityType.B2C))
                        {
                            var authorityWithPrefferedCache = Authority.CreateAuthorityWithEnvironment(
                                requestParams.TenantUpdatedCanonicalAuthority.AuthorityInfo,
                                instanceDiscoveryMetadata.PreferredCache);

                            CacheFallbackOperations.WriteAdalRefreshToken(
                                Logger,
                                LegacyCachePersistence,
                                msalRefreshTokenCacheItem,
                                msalIdTokenCacheItem,
                                authorityWithPrefferedCache.AuthorityInfo.CanonicalAuthority,
                                msalIdTokenCacheItem.IdToken.ObjectId, response.Scope);
                        }
                    }
                    finally
                    {
                        await(this as ITokenCacheInternal).OnAfterAccessAsync(args).ConfigureAwait(false);
                    }

                    return(Tuple.Create(msalAccessTokenCacheItem, msalIdTokenCacheItem));
                }
                finally
                {
#pragma warning disable CS0618 // Type or member is obsolete
                    HasStateChanged = false;
#pragma warning restore CS0618 // Type or member is obsolete
                }
            }
            finally
            {
                _semaphoreSlim.Release();
            }
        }
Example #24
0
        async Task <Tuple <MsalAccessTokenCacheItem, MsalIdTokenCacheItem> > ITokenCacheInternal.SaveTokenResponseAsync(
            AuthenticationRequestParameters requestParams,
            MsalTokenResponse response)
        {
            var tenantId = Authority
                           .CreateAuthority(ServiceBundle, requestParams.TenantUpdatedCanonicalAuthority)
                           .GetTenantId();

            bool isAdfsAuthority = requestParams.AuthorityInfo.AuthorityType == AuthorityType.Adfs;

            IdToken idToken = IdToken.Parse(response.IdToken);

            string subject = idToken?.Subject;

            if (idToken != null && string.IsNullOrEmpty(subject))
            {
                requestParams.RequestContext.Logger.Warning("Subject not present in Id token");
            }

            string preferredUsername;

            // The preferred_username value cannot be null or empty in order to comply with the ADAL/MSAL Unified cache schema.
            // It will be set to "preferred_username not in idtoken"
            if (idToken == null)
            {
                preferredUsername = NullPreferredUsernameDisplayLabel;
            }
            else if (string.IsNullOrWhiteSpace(idToken.PreferredUsername))
            {
                if (isAdfsAuthority)
                {
                    //The direct to adfs scenario does not return preferred_username in the id token so it needs to be set to the upn
                    preferredUsername = !string.IsNullOrEmpty(idToken.Upn)
                        ? idToken.Upn
                        : NullPreferredUsernameDisplayLabel;
                }
                else
                {
                    preferredUsername = NullPreferredUsernameDisplayLabel;
                }
            }
            else
            {
                preferredUsername = idToken.PreferredUsername;
            }



            var instanceDiscoveryMetadataEntry = GetCachedAuthorityMetaData(requestParams.TenantUpdatedCanonicalAuthority);

            var environmentAliases = GetEnvironmentAliases(
                requestParams.TenantUpdatedCanonicalAuthority,
                instanceDiscoveryMetadataEntry);

            var preferredEnvironmentHost = GetPreferredEnvironmentHost(
                requestParams.AuthorityInfo.Host,
                instanceDiscoveryMetadataEntry);

            var msalAccessTokenCacheItem =
                new MsalAccessTokenCacheItem(preferredEnvironmentHost, requestParams.ClientId, response, tenantId, subject)
            {
                UserAssertionHash = requestParams.UserAssertion?.AssertionHash,
                IsAdfs            = isAdfsAuthority
            };

            MsalRefreshTokenCacheItem msalRefreshTokenCacheItem = null;
            MsalIdTokenCacheItem      msalIdTokenCacheItem      = null;

            if (idToken != null)
            {
                msalIdTokenCacheItem = new MsalIdTokenCacheItem(
                    preferredEnvironmentHost,
                    requestParams.ClientId,
                    response,
                    tenantId,
                    subject)
                {
                    IsAdfs = isAdfsAuthority
                };
            }

            await _semaphoreSlim.WaitAsync().ConfigureAwait(false);

            try
            {
                try
                {
                    Account account  = null;
                    string  username = isAdfsAuthority ? idToken?.Upn : preferredUsername;
                    if (msalAccessTokenCacheItem.HomeAccountId != null)
                    {
                        account = new Account(
                            msalAccessTokenCacheItem.HomeAccountId,
                            username,
                            preferredEnvironmentHost);
                    }
                    var args = new TokenCacheNotificationArgs(this, ClientId, account, true);

#pragma warning disable CS0618 // Type or member is obsolete
                    HasStateChanged = true;
#pragma warning restore CS0618 // Type or member is obsolete

                    await OnBeforeAccessAsync(args).ConfigureAwait(false);

                    try
                    {
                        await OnBeforeWriteAsync(args).ConfigureAwait(false);

                        DeleteAccessTokensWithIntersectingScopes(
                            requestParams,
                            environmentAliases,
                            tenantId,
                            msalAccessTokenCacheItem.ScopeSet,
                            msalAccessTokenCacheItem.HomeAccountId);

                        _accessor.SaveAccessToken(msalAccessTokenCacheItem);

                        if (idToken != null)
                        {
                            _accessor.SaveIdToken(msalIdTokenCacheItem);
                            var msalAccountCacheItem = new MsalAccountCacheItem(
                                preferredEnvironmentHost,
                                response,
                                preferredUsername,
                                tenantId);

                            //The ADFS direct scenrio does not return client info so the home account id is acquired from the subject
                            if (isAdfsAuthority && String.IsNullOrEmpty(msalAccountCacheItem.HomeAccountId))
                            {
                                msalAccountCacheItem.HomeAccountId = idToken.Subject;
                            }

                            _accessor.SaveAccount(msalAccountCacheItem);
                        }

                        // if server returns the refresh token back, save it in the cache.
                        if (response.RefreshToken != null)
                        {
                            msalRefreshTokenCacheItem = new MsalRefreshTokenCacheItem(
                                preferredEnvironmentHost,
                                requestParams.ClientId,
                                response,
                                subject);

                            if (!_featureFlags.IsFociEnabled)
                            {
                                msalRefreshTokenCacheItem.FamilyId = null;
                            }

                            requestParams.RequestContext.Logger.Info("Saving RT in cache...");
                            _accessor.SaveRefreshToken(msalRefreshTokenCacheItem);
                        }

                        UpdateAppMetadata(requestParams.ClientId, preferredEnvironmentHost, response.FamilyId);

                        // save RT in ADAL cache for public clients
                        // do not save RT in ADAL cache for MSAL B2C scenarios
                        if (!requestParams.IsClientCredentialRequest && !requestParams.AuthorityInfo.AuthorityType.Equals(AuthorityType.B2C))
                        {
                            CacheFallbackOperations.WriteAdalRefreshToken(
                                Logger,
                                LegacyCachePersistence,
                                msalRefreshTokenCacheItem,
                                msalIdTokenCacheItem,
                                Authority.CreateAuthorityUriWithHost(
                                    requestParams.TenantUpdatedCanonicalAuthority,
                                    preferredEnvironmentHost),
                                msalIdTokenCacheItem.IdToken.ObjectId, response.Scope);
                        }
                    }
                    finally
                    {
                        await OnAfterAccessAsync(args).ConfigureAwait(false);
                    }

                    return(Tuple.Create(msalAccessTokenCacheItem, msalIdTokenCacheItem));
                }
                finally
                {
#pragma warning disable CS0618 // Type or member is obsolete
                    HasStateChanged = false;
#pragma warning restore CS0618 // Type or member is obsolete
                }
            }
            finally
            {
                _semaphoreSlim.Release();
            }
        }
        public void RemoveAdalUser_RemovesUserNoClientInfo()
        {
            // Arrange
            PopulateLegacyCache(_legacyCachePersistence);

            PopulateLegacyWithRtAndId(
                _legacyCachePersistence,
                "other_client_id",
                CoreTestConstants.ProductionPrefNetworkEnvironment,
                null,
                null,
                "no_client_info_user3"); // no client info, different client id -> won't be deleted

            PopulateLegacyWithRtAndId(
                _legacyCachePersistence,
                CoreTestConstants.ClientId,
                "other_env",
                null,
                null,
                "no_client_info_user3"); // no client info, different env -> won't be deleted

            AssertCacheEntryCount(8);

            var adalUsers =
                CacheFallbackOperations.GetAllAdalUsersForMsal(
                    _legacyCachePersistence,
                    CoreTestConstants.ClientId);

            AssertByUsername(
                adalUsers,
                new[]
            {
                "user2",
                "user1",
                "sovereign_user5"      // this user has different environment but same client id
            },
                new[]
            {
                "no_client_info_user3",
                "no_client_info_user3",
                "no_client_info_user4"
            });

            // Act - delete with no client info -> displayable id is used
            CacheFallbackOperations.RemoveAdalUser(
                _legacyCachePersistence,
                CoreTestConstants.ClientId,
                "no_client_info_user3",
                "");

            AssertCacheEntryCount(6);

            // Assert
            adalUsers = CacheFallbackOperations.GetAllAdalUsersForMsal(
                _legacyCachePersistence,
                CoreTestConstants.ClientId);

            AssertByUsername(
                adalUsers,
                new[]
            {
                "user2",
                "user1",
                "sovereign_user5"      // this user has different environment but same client id
            },
                new[]
            {
                "no_client_info_user4"
            });
        }
Example #26
0
        /// <remarks>
        /// Get accounts should not make a network call, if possible. This can be achieved if
        /// all the environments in the token cache are known to MSAL, as MSAL keeps a list of
        /// known environments in <see cref="KnownMetadataProvider"/>
        /// </remarks>
        async Task <IEnumerable <IAccount> > ITokenCacheInternal.GetAccountsAsync(AuthenticationRequestParameters requestParameters)
        {
            var  logger           = requestParameters.RequestContext.Logger;
            var  environment      = requestParameters.AuthorityInfo.Host;
            bool filterByClientId = !_featureFlags.IsFociEnabled;

            IEnumerable <MsalRefreshTokenCacheItem> rtCacheItems      = GetAllRefreshTokensWithNoLocks(filterByClientId);
            IEnumerable <MsalAccountCacheItem>      accountCacheItems = _accessor.GetAllAccounts();

            if (logger.IsLoggingEnabled(LogLevel.Verbose))
            {
                logger.Verbose($"GetAccounts found {rtCacheItems.Count()} RTs and {accountCacheItems.Count()} accounts in MSAL cache. ");
            }

            // Multi-cloud support - must filter by env.
            ISet <string> allEnvironmentsInCache = new HashSet <string>(
                accountCacheItems.Select(aci => aci.Environment),
                StringComparer.OrdinalIgnoreCase);

            allEnvironmentsInCache.UnionWith(rtCacheItems.Select(rt => rt.Environment));

            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,
                allEnvironmentsInCache,
                requestParameters.RequestContext).ConfigureAwait(false);

            rtCacheItems      = rtCacheItems.Where(rt => instanceMetadata.Aliases.ContainsOrdinalIgnoreCase(rt.Environment));
            accountCacheItems = accountCacheItems.Where(acc => instanceMetadata.Aliases.ContainsOrdinalIgnoreCase(acc.Environment));

            if (logger.IsLoggingEnabled(LogLevel.Verbose))
            {
                logger.Verbose($"GetAccounts found {rtCacheItems.Count()} RTs and {accountCacheItems.Count()} accounts in MSAL cache after environment filtering. ");
            }

            IDictionary <string, Account> clientInfoToAccountMap = new Dictionary <string, Account>();

            foreach (MsalRefreshTokenCacheItem rtItem in rtCacheItems)
            {
                foreach (MsalAccountCacheItem account in accountCacheItems)
                {
                    if (RtMatchesAccount(rtItem, account))
                    {
                        clientInfoToAccountMap[rtItem.HomeAccountId] = new Account(
                            account.HomeAccountId,
                            account.PreferredUsername,
                            environment, // Preserve the env passed in by the user
                            account.WamAccountIds);

                        break;
                    }
                }
            }

            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.AppConfig.IsBrokerEnabled && ServiceBundle.PlatformProxy.BrokerSupportsWamAccounts)
            {
                foreach (MsalAccountCacheItem wamAccountCache in accountCacheItems.Where(
                             acc => acc.WamAccountIds != null &&
                             acc.WamAccountIds.ContainsKey(requestParameters.AppConfig.ClientId)))
                {
                    var wamAccount = new Account(
                        wamAccountCache.HomeAccountId,
                        wamAccountCache.PreferredUsername,
                        environment,
                        wamAccountCache.WamAccountIds);

                    clientInfoToAccountMap[wamAccountCache.HomeAccountId] = wamAccount;
                }
            }

            IEnumerable <IAccount> accounts = UpdateWithAdalAccountsWithoutClientInfo(environment,
                                                                                      instanceMetadata.Aliases,
                                                                                      adalUsersResult,
                                                                                      clientInfoToAccountMap);

            if (!string.IsNullOrEmpty(requestParameters.HomeAccountId))
            {
                accounts = accounts.Where(acc => acc.HomeAccountId.Identifier.Equals(
                                              requestParameters.HomeAccountId,
                                              StringComparison.OrdinalIgnoreCase));

                if (logger.IsLoggingEnabled(LogLevel.Verbose))
                {
                    logger.Verbose($"Filtered by home account id. Remaining accounts {accounts.Count()} ");
                }
            }

            return(accounts);
        }
Example #27
0
        async Task <Tuple <MsalAccessTokenCacheItem, MsalIdTokenCacheItem, Account> > ITokenCacheInternal.SaveTokenResponseAsync(
            AuthenticationRequestParameters requestParams,
            MsalTokenResponse response)
        {
            response.Log(requestParams.RequestContext.Logger, LogLevel.Verbose);

            MsalAccessTokenCacheItem  msalAccessTokenCacheItem  = null;
            MsalRefreshTokenCacheItem msalRefreshTokenCacheItem = null;
            MsalIdTokenCacheItem      msalIdTokenCacheItem      = null;
            MsalAccountCacheItem      msalAccountCacheItem      = null;

            IdToken idToken = IdToken.Parse(response.IdToken);

            if (idToken == null)
            {
                requestParams.RequestContext.Logger.Info("ID Token not present in response. ");
            }

            var tenantId = GetTenantId(idToken, requestParams);

            bool   isAdfsAuthority      = requestParams.AuthorityInfo.AuthorityType == AuthorityType.Adfs;
            string preferredUsername    = GetPreferredUsernameFromIdToken(isAdfsAuthority, idToken);
            string username             = isAdfsAuthority ? idToken?.Upn : preferredUsername;
            string homeAccountId        = GetHomeAccountId(requestParams, response, idToken);
            string suggestedWebCacheKey = SuggestedWebCacheKeyFactory.GetKeyFromResponse(requestParams, homeAccountId);

            // Do a full instance discovery when saving tokens (if not cached),
            // so that the PreferredNetwork environment is up to date.
            var instanceDiscoveryMetadata = await ServiceBundle.InstanceDiscoveryManager
                                            .GetMetadataEntryAsync(
                requestParams.Authority.AuthorityInfo,
                requestParams.RequestContext)
                                            .ConfigureAwait(false);

            #region Create Cache Objects
            if (!string.IsNullOrEmpty(response.AccessToken))
            {
                msalAccessTokenCacheItem =
                    new MsalAccessTokenCacheItem(
                        instanceDiscoveryMetadata.PreferredCache,
                        requestParams.AppConfig.ClientId,
                        response,
                        tenantId,
                        homeAccountId,
                        requestParams.AuthenticationScheme.KeyId)
                {
                    UserAssertionHash = requestParams.UserAssertion?.AssertionHash,
                    IsAdfs            = isAdfsAuthority
                };
            }

            if (!string.IsNullOrEmpty(response.RefreshToken))
            {
                msalRefreshTokenCacheItem = new MsalRefreshTokenCacheItem(
                    instanceDiscoveryMetadata.PreferredCache,
                    requestParams.AppConfig.ClientId,
                    response,
                    homeAccountId);

                if (!_featureFlags.IsFociEnabled)
                {
                    msalRefreshTokenCacheItem.FamilyId = null;
                }
            }

            Dictionary <string, string> wamAccountIds = GetWamAccountIds(requestParams, response);
            Account account;
            if (idToken != null)
            {
                msalIdTokenCacheItem = new MsalIdTokenCacheItem(
                    instanceDiscoveryMetadata.PreferredCache,
                    requestParams.AppConfig.ClientId,
                    response,
                    tenantId,
                    homeAccountId)
                {
                    IsAdfs = isAdfsAuthority
                };

                msalAccountCacheItem = new MsalAccountCacheItem(
                    instanceDiscoveryMetadata.PreferredCache,
                    response.ClientInfo,
                    homeAccountId,
                    idToken,
                    preferredUsername,
                    tenantId,
                    wamAccountIds);
            }

            #endregion

            account = new Account(
                homeAccountId,
                username,
                instanceDiscoveryMetadata.PreferredNetwork,
                wamAccountIds);
            requestParams.RequestContext.Logger.Verbose("[SaveTokenResponseAsync] Entering token cache semaphore. ");
            await _semaphoreSlim.WaitAsync().ConfigureAwait(false);

            requestParams.RequestContext.Logger.Verbose("[SaveTokenResponseAsync] Entered token cache semaphore. ");

            try
            {
#pragma warning disable CS0618 // Type or member is obsolete
                HasStateChanged = true;
#pragma warning restore CS0618 // Type or member is obsolete

                try
                {
                    ITokenCacheInternal tokenCacheInternal = this;
                    if (tokenCacheInternal.IsTokenCacheSerialized())
                    {
                        var args = new TokenCacheNotificationArgs(
                            this,
                            ClientId,
                            account,
                            hasStateChanged: true,
                            tokenCacheInternal.IsApplicationCache,
                            hasTokens: tokenCacheInternal.HasTokensNoLocks(),
                            requestParams.RequestContext.UserCancellationToken,
                            suggestedCacheKey: suggestedWebCacheKey);

                        Stopwatch sw = Stopwatch.StartNew();

                        await tokenCacheInternal.OnBeforeAccessAsync(args).ConfigureAwait(false);

                        await tokenCacheInternal.OnBeforeWriteAsync(args).ConfigureAwait(false);

                        requestParams.RequestContext.ApiEvent.DurationInCacheInMs += sw.ElapsedMilliseconds;
                    }

                    if (msalAccessTokenCacheItem != null)
                    {
                        requestParams.RequestContext.Logger.Info("Saving AT in cache and removing overlapping ATs...");

                        DeleteAccessTokensWithIntersectingScopes(
                            requestParams,
                            instanceDiscoveryMetadata.Aliases,
                            tenantId,
                            msalAccessTokenCacheItem.ScopeSet,
                            msalAccessTokenCacheItem.HomeAccountId,
                            msalAccessTokenCacheItem.TokenType);

                        _accessor.SaveAccessToken(msalAccessTokenCacheItem);
                    }

                    if (idToken != null)
                    {
                        requestParams.RequestContext.Logger.Info("Saving Id Token and Account in cache ...");
                        _accessor.SaveIdToken(msalIdTokenCacheItem);
                        MergeWamAccountIds(msalAccountCacheItem);
                        _accessor.SaveAccount(msalAccountCacheItem);
                    }

                    // if server returns the refresh token back, save it in the cache.
                    if (msalRefreshTokenCacheItem != null)
                    {
                        requestParams.RequestContext.Logger.Info("Saving RT in cache...");
                        _accessor.SaveRefreshToken(msalRefreshTokenCacheItem);
                    }

                    UpdateAppMetadata(requestParams.AppConfig.ClientId, instanceDiscoveryMetadata.PreferredCache, response.FamilyId);

                    // 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 tenatedAuthority            = Authority.CreateAuthorityWithTenant(requestParams.AuthorityInfo, tenantId);
                        var authorityWithPreferredCache = Authority.CreateAuthorityWithEnvironment(
                            tenatedAuthority.AuthorityInfo,
                            instanceDiscoveryMetadata.PreferredCache);

                        CacheFallbackOperations.WriteAdalRefreshToken(
                            Logger,
                            LegacyCachePersistence,
                            msalRefreshTokenCacheItem,
                            msalIdTokenCacheItem,
                            authorityWithPreferredCache.AuthorityInfo.CanonicalAuthority,
                            msalIdTokenCacheItem.IdToken.ObjectId,
                            response.Scope);
                    }
                }
                finally
                {
                    ITokenCacheInternal tokenCacheInternal = this;
                    if (tokenCacheInternal.IsTokenCacheSerialized())
                    {
                        var args = new TokenCacheNotificationArgs(
                            this,
                            ClientId,
                            account,
                            hasStateChanged: true,
                            tokenCacheInternal.IsApplicationCache,
                            tokenCacheInternal.HasTokensNoLocks(),
                            requestParams.RequestContext.UserCancellationToken,
                            suggestedCacheKey: suggestedWebCacheKey);

                        Stopwatch sw = Stopwatch.StartNew();
                        await tokenCacheInternal.OnAfterAccessAsync(args).ConfigureAwait(false);

                        requestParams.RequestContext.ApiEvent.DurationInCacheInMs += sw.ElapsedMilliseconds;
                    }
#pragma warning disable CS0618 // Type or member is obsolete
                    HasStateChanged = false;
#pragma warning restore CS0618 // Type or member is obsolete
                }

                return(Tuple.Create(msalAccessTokenCacheItem, msalIdTokenCacheItem, account));
            }
            finally
            {
                _semaphoreSlim.Release();
                requestParams.RequestContext.Logger.Verbose("[SaveTokenResponseAsync] Released token cache semaphore. ");
            }
        }
Example #28
0
        async Task <Tuple <MsalAccessTokenCacheItem, MsalIdTokenCacheItem> > ITokenCacheInternal.SaveTokenResponseAsync(
            AuthenticationRequestParameters requestParams,
            MsalTokenResponse response)
        {
            MsalAccessTokenCacheItem  msalAccessTokenCacheItem  = null;
            MsalRefreshTokenCacheItem msalRefreshTokenCacheItem = null;
            MsalIdTokenCacheItem      msalIdTokenCacheItem      = null;
            MsalAccountCacheItem      msalAccountCacheItem      = null;

            IdToken idToken = IdToken.Parse(response.IdToken);

            var tenantId = Authority
                           .CreateAuthority(requestParams.TenantUpdatedCanonicalAuthority.AuthorityInfo.CanonicalAuthority)
                           .TenantId;

            bool   isAdfsAuthority   = requestParams.AuthorityInfo.AuthorityType == AuthorityType.Adfs;
            string preferredUsername = GetPreferredUsernameFromIdToken(isAdfsAuthority, idToken);
            string username          = isAdfsAuthority ? idToken?.Upn : preferredUsername;
            string homeAccountId     = GetHomeAccountId(requestParams, response, idToken);

            // Do a full instance discovery when saving tokens (if not cached),
            // so that the PreferredNetwork environment is up to date.
            var instanceDiscoveryMetadata = await ServiceBundle.InstanceDiscoveryManager
                                            .GetMetadataEntryAsync(
                requestParams.TenantUpdatedCanonicalAuthority.AuthorityInfo.CanonicalAuthority,
                requestParams.RequestContext)
                                            .ConfigureAwait(false);

            #region Create Cache Objects
            if (!string.IsNullOrEmpty(response.AccessToken))
            {
                msalAccessTokenCacheItem =
                    new MsalAccessTokenCacheItem(
                        instanceDiscoveryMetadata.PreferredCache,
                        requestParams.ClientId,
                        response,
                        tenantId,
                        homeAccountId,
                        requestParams.AuthenticationScheme.KeyId)
                {
                    UserAssertionHash = requestParams.UserAssertion?.AssertionHash,
                    IsAdfs            = isAdfsAuthority
                };
            }

            if (!string.IsNullOrEmpty(response.RefreshToken))
            {
                msalRefreshTokenCacheItem = new MsalRefreshTokenCacheItem(
                    instanceDiscoveryMetadata.PreferredCache,
                    requestParams.ClientId,
                    response,
                    homeAccountId);

                if (!_featureFlags.IsFociEnabled)
                {
                    msalRefreshTokenCacheItem.FamilyId = null;
                }
            }

            if (idToken != null)
            {
                msalIdTokenCacheItem = new MsalIdTokenCacheItem(
                    instanceDiscoveryMetadata.PreferredCache,
                    requestParams.ClientId,
                    response,
                    tenantId,
                    homeAccountId)
                {
                    IsAdfs = isAdfsAuthority
                };

                msalAccountCacheItem = new MsalAccountCacheItem(
                    instanceDiscoveryMetadata.PreferredCache,
                    response.ClientInfo,
                    homeAccountId,
                    idToken,
                    preferredUsername,
                    tenantId);
            }

            #endregion

            Account account = new Account(
                homeAccountId,
                username,
                instanceDiscoveryMetadata.PreferredCache);

            await _semaphoreSlim.WaitAsync().ConfigureAwait(false);

            try
            {
                var args = new TokenCacheNotificationArgs(
                    this,
                    ClientId,
                    account,
                    hasStateChanged: true,
                    (this as ITokenCacheInternal).IsApplicationCache,
                    hasTokens: (this as ITokenCacheInternal).HasTokensNoLocks(),
                    requestParams.SuggestedWebAppCacheKey);

#pragma warning disable CS0618 // Type or member is obsolete
                HasStateChanged = true;
#pragma warning restore CS0618 // Type or member is obsolete

                try
                {
                    await(this as ITokenCacheInternal).OnBeforeAccessAsync(args).ConfigureAwait(false);
                    await(this as ITokenCacheInternal).OnBeforeWriteAsync(args).ConfigureAwait(false);

                    if (msalAccessTokenCacheItem != null)
                    {
                        requestParams.RequestContext.Logger.Info("Saving AT in cache and removing overlapping ATs...");

                        DeleteAccessTokensWithIntersectingScopes(
                            requestParams,
                            instanceDiscoveryMetadata.Aliases,
                            tenantId,
                            msalAccessTokenCacheItem.ScopeSet,
                            msalAccessTokenCacheItem.HomeAccountId,
                            msalAccessTokenCacheItem.TokenType);

                        _accessor.SaveAccessToken(msalAccessTokenCacheItem);
                    }

                    if (idToken != null)
                    {
                        requestParams.RequestContext.Logger.Info("Saving Id Token and Account in cache ...");
                        _accessor.SaveIdToken(msalIdTokenCacheItem);
                        _accessor.SaveAccount(msalAccountCacheItem);
                    }

                    // if server returns the refresh token back, save it in the cache.
                    if (msalRefreshTokenCacheItem != null)
                    {
                        requestParams.RequestContext.Logger.Info("Saving RT in cache...");
                        _accessor.SaveRefreshToken(msalRefreshTokenCacheItem);
                    }

                    UpdateAppMetadata(requestParams.ClientId, instanceDiscoveryMetadata.PreferredCache, response.FamilyId);

                    // Do not save RT in ADAL cache for confidential client or B2C
                    if (!requestParams.IsClientCredentialRequest &&
                        requestParams.AuthorityInfo.AuthorityType != AuthorityType.B2C)
                    {
                        var authorityWithPreferredCache = Authority.CreateAuthorityWithEnvironment(
                            requestParams.TenantUpdatedCanonicalAuthority.AuthorityInfo,
                            instanceDiscoveryMetadata.PreferredCache);

                        CacheFallbackOperations.WriteAdalRefreshToken(
                            Logger,
                            LegacyCachePersistence,
                            msalRefreshTokenCacheItem,
                            msalIdTokenCacheItem,
                            authorityWithPreferredCache.AuthorityInfo.CanonicalAuthority,
                            msalIdTokenCacheItem.IdToken.ObjectId,
                            response.Scope);
                    }
                }
                finally
                {
                    var args2 = new TokenCacheNotificationArgs(
                        this,
                        ClientId,
                        account,
                        hasStateChanged: true,
                        (this as ITokenCacheInternal).IsApplicationCache,
                        (this as ITokenCacheInternal).HasTokensNoLocks(),
                        requestParams.SuggestedWebAppCacheKey);

                    await(this as ITokenCacheInternal).OnAfterAccessAsync(args2).ConfigureAwait(false);
#pragma warning disable CS0618 // Type or member is obsolete
                    HasStateChanged = false;
#pragma warning restore CS0618 // Type or member is obsolete
                }

                return(Tuple.Create(msalAccessTokenCacheItem, msalIdTokenCacheItem));
            }
            finally
            {
                _semaphoreSlim.Release();
            }
        }
Example #29
0
        /// <remarks>
        /// Get accounts should not make a network call, if possible.
        /// </remarks>
        async Task <IEnumerable <IAccount> > ITokenCacheInternal.GetAccountsAsync(string authority, RequestContext requestContext)
        {
            var environment = Authority.GetEnviroment(authority);

            // FetchAllAccountItemsFromCacheAsync...
            IEnumerable <MsalRefreshTokenCacheItem> rtCacheItems;
            IEnumerable <MsalAccountCacheItem>      accountCacheItems;
            AdalUsersForMsal adalUsersResult;

            bool filterByClientId = !_featureFlags.IsFociEnabled;

            await _semaphoreSlim.WaitAsync().ConfigureAwait(false);

            try
            {
                var args = new TokenCacheNotificationArgs(this, ClientId, null, false);

                await OnBeforeAccessAsync(args).ConfigureAwait(false);

                try
                {
                    rtCacheItems      = GetAllRefreshTokensWithNoLocks(filterByClientId);
                    accountCacheItems = _accessor.GetAllAccounts();

                    adalUsersResult = CacheFallbackOperations.GetAllAdalUsersForMsal(
                        Logger,
                        LegacyCachePersistence,
                        ClientId);
                }
                finally
                {
                    await OnAfterAccessAsync(args).ConfigureAwait(false);
                }
            }
            finally
            {
                _semaphoreSlim.Release();
            }

            // Multi-cloud support - must filter by env.
            // Use all env aliases to filter, in case PreferredCacheEnv changes in the future
            ISet <string> existingEnvs = new HashSet <string>(
                accountCacheItems.Select(aci => aci.Environment),
                StringComparer.OrdinalIgnoreCase);

            var aliases = await GetEnvAliasesTryAvoidNetworkCallAsync(
                authority,
                adalUsersResult.GetAdalUserEnviroments(),
                existingEnvs,
                requestContext)
                          .ConfigureAwait(false);

            rtCacheItems      = rtCacheItems.Where(rt => aliases.ContainsOrdinalIgnoreCase(rt.Environment));
            accountCacheItems = accountCacheItems.Where(acc => aliases.ContainsOrdinalIgnoreCase(acc.Environment));

            IDictionary <string, Account> clientInfoToAccountMap = new Dictionary <string, Account>();

            foreach (MsalRefreshTokenCacheItem rtItem in rtCacheItems)
            {
                foreach (MsalAccountCacheItem account in accountCacheItems)
                {
                    if (RtMatchesAccount(rtItem, account))
                    {
                        clientInfoToAccountMap[rtItem.HomeAccountId] = new Account(
                            account.HomeAccountId,
                            account.PreferredUsername,
                            environment);  // Preserve the env passed in by the user

                        break;
                    }
                }
            }

            List <IAccount> accounts = UpdateWithAdalAccounts(
                environment,
                aliases,
                adalUsersResult,
                clientInfoToAccountMap);

            return(accounts);
        }
Example #30
0
        async Task <MsalRefreshTokenCacheItem> ITokenCacheInternal.FindRefreshTokenAsync(
            AuthenticationRequestParameters requestParams,
            string familyId)
        {
            var cacheEvent = new CacheEvent(
                CacheEvent.TokenCacheLookup,
                requestParams.RequestContext.TelemetryCorrelationId)
            {
                TokenType = CacheEvent.TokenTypes.RT
            };

            using (ServiceBundle.TelemetryManager.CreateTelemetryHelper(cacheEvent))
            {
                if (requestParams.Authority == null)
                {
                    return(null);
                }

                var instanceDiscoveryMetadataEntry = await GetCachedOrDiscoverAuthorityMetaDataAsync(
                    requestParams.AuthorityInfo.CanonicalAuthority,
                    requestParams.RequestContext).ConfigureAwait(false);

                var environmentAliases = GetEnvironmentAliases(
                    requestParams.AuthorityInfo.CanonicalAuthority,
                    instanceDiscoveryMetadataEntry);

                var preferredEnvironmentHost = GetPreferredEnvironmentHost(
                    requestParams.AuthorityInfo.Host,
                    instanceDiscoveryMetadataEntry);

                await _semaphoreSlim.WaitAsync().ConfigureAwait(false);

                try
                {
                    requestParams.RequestContext.Logger.Info("Looking up refresh token in the cache..");

                    TokenCacheNotificationArgs args = new TokenCacheNotificationArgs(this, ClientId, requestParams.Account, false);

                    // make sure to check preferredEnvironmentHost first
                    var allEnvAliases = new List <string>()
                    {
                        preferredEnvironmentHost
                    };
                    allEnvAliases.AddRange(environmentAliases);

                    var keysAcrossEnvs = allEnvAliases.Select(ea => new MsalRefreshTokenCacheKey(
                                                                  ea,
                                                                  requestParams.ClientId,
                                                                  requestParams.Account?.HomeAccountId?.Identifier,
                                                                  familyId));

                    await OnBeforeAccessAsync(args).ConfigureAwait(false);

                    try
                    {
                        // Try to load from all env aliases, but stop at the first valid one
                        MsalRefreshTokenCacheItem msalRefreshTokenCacheItem = keysAcrossEnvs
                                                                              .Select(key => _accessor.GetRefreshToken(key))
                                                                              .FirstOrDefault(item => item != null);

                        requestParams.RequestContext.Logger.Info("Refresh token found in the cache? - " + (msalRefreshTokenCacheItem != null));

                        if (msalRefreshTokenCacheItem != null)
                        {
                            return(msalRefreshTokenCacheItem);
                        }

                        requestParams.RequestContext.Logger.Info("Checking ADAL cache for matching RT");

                        string upn = string.IsNullOrWhiteSpace(requestParams.LoginHint)
                            ? requestParams.Account?.Username
                            : requestParams.LoginHint;

                        // ADAL legacy cache does not store FRTs
                        if (requestParams.Account != null && string.IsNullOrEmpty(familyId))
                        {
                            return(CacheFallbackOperations.GetAdalEntryForMsal(
                                       Logger,
                                       LegacyCachePersistence,
                                       preferredEnvironmentHost,
                                       environmentAliases,
                                       requestParams.ClientId,
                                       upn,
                                       requestParams.Account.HomeAccountId?.Identifier));
                        }

                        return(null);
                    }
                    finally
                    {
                        await OnAfterAccessAsync(args).ConfigureAwait(false);
                    }
                }
                finally
                {
                    _semaphoreSlim.Release();
                }
            }
        }