Contains parameters used by the ADAL call accessing the cache.
        public void AfterAccess(TokenCacheNotificationArgs args)
        {
            if (args.TokenCache.HasStateChanged)
            {
                try
                {
                    ISharedPreferences preferences = Application.Context.GetSharedPreferences(SharedPreferencesName, FileCreationMode.Private);
                    ISharedPreferencesEditor editor = preferences.Edit();
                    editor.Remove(SharedPreferencesKey);

                    if (args.TokenCache.Count > 0)
                    {
                        byte[] state = args.TokenCache.Serialize();
                        string stateString = Convert.ToBase64String(state);
                        editor.PutString(SharedPreferencesKey, stateString);
                    }

                    editor.Apply();
                    args.TokenCache.HasStateChanged = false;
                }
                catch (Exception ex)
                {
                    PlatformPlugin.Logger.Warning(null, "Failed to save cache: " + ex);
                }
            }
        }
 // Triggered right after MSAL accessed the cache.
 private void AfterAccessNotification(TokenCacheNotificationArgs args)
 {
     // if the access operation resulted in a cache update
     if (HasStateChanged)
     {
         Persist();
     }
 }
        public void BeforeAccess(TokenCacheNotificationArgs args)
        {
            if (args.TokenCache.Count > 0)
            {
                // We assume that the cache has not changed since last write
                return;
            }

            try
            {
                ISharedPreferences preferences = Application.Context.GetSharedPreferences(SharedPreferencesName, FileCreationMode.Private);
                string stateString = preferences.GetString(SharedPreferencesKey, null);
                if (stateString != null)
                {
                    byte[] state = Convert.FromBase64String(stateString);
                    args.TokenCache.Deserialize(state);
                }
            }
            catch (Exception ex)
            {
                PlatformPlugin.Logger.Warning(null, "Failed to load cache: " + ex);
                // Ignore as the cache seems to be corrupt
            }
        }
        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
                    {
                        TokenCache = new NoLockTokenCacheProxy(this),
                        ClientId   = ClientId,
                        Account    = requestParams.Account
                    };

                    // 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();
                }
            }
        }
        async Task <MsalAccessTokenCacheItem> ITokenCacheInternal.FindAccessTokenAsync(AuthenticationRequestParameters requestParams)
        {
            ISet <string> environmentAliases        = new HashSet <string>(StringComparer.OrdinalIgnoreCase);
            string        preferredEnvironmentAlias = null;

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

                environmentAliases.UnionWith(GetEnvironmentAliases(
                                                 requestParams.AuthorityInfo.CanonicalAuthority,
                                                 instanceDiscoveryMetadataEntry));

                if (requestParams.AuthorityInfo.AuthorityType == AuthorityType.Adfs)
                {
                    preferredEnvironmentAlias = requestParams.AuthorityInfo.CanonicalAuthority;
                }
                else if (requestParams.AuthorityInfo.AuthorityType != AuthorityType.B2C)
                {
                    preferredEnvironmentAlias = instanceDiscoveryMetadataEntry.PreferredCache;
                }
            }

            // no authority passed
            if (!environmentAliases.Any())
            {
                requestParams.RequestContext.Logger.Warning("No authority provided. Skipping cache lookup ");
                return(null);
            }

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

            try
            {
                requestParams.RequestContext.Logger.Info("Looking up access token in the cache.");
                MsalAccessTokenCacheItem   msalAccessTokenCacheItem;
                TokenCacheNotificationArgs args = new TokenCacheNotificationArgs
                {
                    TokenCache = new NoLockTokenCacheProxy(this),
                    ClientId   = ClientId,
                    Account    = requestParams.Account
                };

                List <MsalAccessTokenCacheItem> tokenCacheItems;

                await OnBeforeAccessAsync(args).ConfigureAwait(false);

                try
                {
                    // filtered by client id.
                    tokenCacheItems = GetAllAccessTokensWithNoLocks(true).ToList();
                }
                finally
                {
                    await OnAfterAccessAsync(args).ConfigureAwait(false);
                }

                tokenCacheItems = FilterByHomeAccountTenantOrAssertion(requestParams, tokenCacheItems);

                // no match found after initial filtering
                if (!tokenCacheItems.Any())
                {
                    requestParams.RequestContext.Logger.Info("No matching entry found for user or assertion");
                    return(null);
                }

                requestParams.RequestContext.Logger.Info("Matching entry count -" + tokenCacheItems.Count);

                IEnumerable <MsalAccessTokenCacheItem> filteredItems =
                    tokenCacheItems.Where(item => ScopeHelper.ScopeContains(item.ScopeSet, requestParams.Scope)).ToList();

                requestParams.RequestContext.Logger.Info("Matching entry count after filtering by scopes - " + filteredItems.Count());

                // filter by authority
                var filteredByPreferredAlias =
                    filteredItems.Where
                        (item => item.Environment.Equals(preferredEnvironmentAlias, StringComparison.OrdinalIgnoreCase)).ToList();

                if (filteredByPreferredAlias.Any())
                {
                    filteredItems = filteredByPreferredAlias;
                }
                else
                {
                    filteredItems = filteredItems.Where(
                        item => environmentAliases.Contains(item.Environment) &&
                        (item.IsAdfs || item.TenantId.Equals(requestParams.Authority.GetTenantId(), StringComparison.OrdinalIgnoreCase)));
                }

                // no match
                if (!filteredItems.Any())
                {
                    requestParams.RequestContext.Logger.Info("No tokens found for matching authority, client_id, user and scopes.");
                    return(null);
                }

                // if only one cached token found
                if (filteredItems.Count() == 1)
                {
                    msalAccessTokenCacheItem = filteredItems.First();
                }
                else
                {
                    requestParams.RequestContext.Logger.Error("Multiple tokens found for matching authority, client_id, user and scopes.");

                    throw new MsalClientException(
                              MsalError.MultipleTokensMatchedError,
                              MsalErrorMessage.MultipleTokensMatched);
                }

                if (msalAccessTokenCacheItem != null)
                {
                    if (msalAccessTokenCacheItem.ExpiresOn >
                        DateTime.UtcNow + TimeSpan.FromMinutes(DefaultExpirationBufferInMinutes))
                    {
                        requestParams.RequestContext.Logger.Info(
                            "Access token is not expired. Returning the found cache entry. " +
                            GetAccessTokenExpireLogMessageContent(msalAccessTokenCacheItem));
                        return(msalAccessTokenCacheItem);
                    }

                    if (ServiceBundle.Config.IsExtendedTokenLifetimeEnabled && msalAccessTokenCacheItem.ExtendedExpiresOn >
                        DateTime.UtcNow + TimeSpan.FromMinutes(DefaultExpirationBufferInMinutes))
                    {
                        requestParams.RequestContext.Logger.Info(
                            "Access token is expired.  IsExtendedLifeTimeEnabled=TRUE and ExtendedExpiresOn is not exceeded.  Returning the found cache entry. " +
                            GetAccessTokenExpireLogMessageContent(msalAccessTokenCacheItem));

                        msalAccessTokenCacheItem.IsExtendedLifeTimeToken = true;
                        return(msalAccessTokenCacheItem);
                    }

                    requestParams.RequestContext.Logger.Info(
                        "Access token has expired or about to expire. " +
                        GetAccessTokenExpireLogMessageContent(msalAccessTokenCacheItem));
                }

                return(null);
            }
            finally
            {
                _semaphoreSlim.Release();
            }
        }
        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
                    {
                        TokenCache      = new NoLockTokenCacheProxy(this),
                        ClientId        = ClientId,
                        Account         = account,
                        HasStateChanged = 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();
            }
        }
        /// <summary>
        ///     Reads a copy of the list of all items in the cache.
        /// </summary>
        /// <returns>The items in the cache</returns>
        public IEnumerable<TokenCacheItem> ReadItems(string clientId)
        {
            lock (lockObject)
            {
                TokenCacheNotificationArgs args = new TokenCacheNotificationArgs {TokenCache = this};
                this.OnBeforeAccess(args);

                List<TokenCacheItem> items = ReadItemsFromCache(clientId);

                this.OnAfterAccess(args);

                return items;
            }
        }
 public void OnBeforeAccess(TokenCacheNotificationArgs args)
 {
     BeforeAccess?.Invoke(args);
 }
 protected abstract Task <byte[]> GetMsalCacheData(TokenCacheNotificationArgs context);
 internal void OnBeforeWrite(TokenCacheNotificationArgs args)
 {
     if (BeforeWrite != null)
     {
         BeforeWrite(args);
     }
 }
 internal void OnBeforeAccess(TokenCacheNotificationArgs args)
 {
     if (BeforeAccess != null)
     {
         BeforeAccess(args);
     }
 }
 internal void OnAfterAccess(TokenCacheNotificationArgs args)
 {
     if (AfterAccess != null)
     {
         AfterAccess(args);
     }
 }
        internal IEnumerable<User> GetUsers(string clientId)
        {
            lock (lockObject)
            {
                TokenCacheNotificationArgs args = new TokenCacheNotificationArgs { TokenCache = this };
                this.OnBeforeAccess(args);
                this.OnBeforeWrite(args);

                List<User> users = new List<User>();
                IEnumerable<string> homeOids = this.GetHomeObjectIdsFromCache(clientId);
                foreach (string homeOid in homeOids)
                {
                    User localUser = this.ReadItems(clientId).First(item => !string.IsNullOrEmpty(item.HomeObjectId) && item.HomeObjectId.Equals(homeOid)).User;
                    localUser.ClientId = clientId;
                    localUser.TokenCache = this;
                    users.Add(localUser);
                }

                this.HasStateChanged = true;
                this.OnAfterAccess(args);

                return users;
            }
        }
        /// <summary>
        ///     Clears the cache by deleting all the items. Note that if the cache is the default shared cache, clearing it would
        ///     impact all the instances of <see cref="PublicClientApplication" /> which share that cache.
        /// </summary>
        public virtual void Clear(string clientId)
        {
            lock (lockObject)
            {
                TokenCacheNotificationArgs args = new TokenCacheNotificationArgs {TokenCache = this};
                this.OnBeforeAccess(args);
                this.OnBeforeWrite(args);

                foreach (var item in this.ReadItemsFromCache(clientId))
                {
                    this.DeleteItemFromCache(item);
                }

                this.HasStateChanged = true;
                this.OnAfterAccess(args);
            }
        }
        /// <summary>
        ///     Deletes an item from the cache.
        /// </summary>
        /// <param name="item">The item to delete from the cache</param>
        internal void DeleteItem(TokenCacheItem item)
        {
            lock (lockObject)
            {
                if (item == null)
                {
                    throw new ArgumentNullException("item");
                }

                PlatformPlugin.Logger.Information(null, "Deleting token in the cache");

                TokenCacheNotificationArgs args = new TokenCacheNotificationArgs
                {
                    TokenCache = this,
                    Scope = item.Scope.AsArray(),
                    ClientId = item.ClientId,
                    User = item.User,
                    Policy = item.Policy
                };

                this.OnBeforeAccess(args);
                this.OnBeforeWrite(args);

                DeleteItemFromCache(item);

                this.HasStateChanged = true;
                this.OnAfterAccess(args);
            }
        }
        /// <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
                {
                    TokenCache = new NoLockTokenCacheProxy(this),
                    ClientId   = ClientId,
                    Account    = null
                };

                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);
        }
 public void OnAfterAccess(TokenCacheNotificationArgs args)
 {
     AfterAccess?.Invoke(args);
 }
 protected abstract Task StoreMsalCacheData(
     TokenCacheNotificationArgs context,
     byte[] msalTokenCacheData);
 public void OnBeforeWrite(TokenCacheNotificationArgs args)
 {
     BeforeWrite?.Invoke(args);
 }
        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);

#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 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);
#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();
            }
        }
 public void BeforeAccess(TokenCacheNotificationArgs args)
 {
 }
 public void AfterAccess(TokenCacheNotificationArgs args)
 {
 }
 // Triggered right before MSAL needs to access the cache.
 // Reload the cache from the persistent store in case it changed since the last access.
 private void BeforeAccessNotification(TokenCacheNotificationArgs args)
 {
     Load();
 }
Exemplo n.º 24
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 = null;
            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);

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

            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(),
                            suggestedCacheKey: suggestedWebCacheKey);

                        Stopwatch sw = new Stopwatch();
                        sw.Start();

                        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(),
                            suggestedCacheKey: suggestedWebCacheKey);

                        Stopwatch sw = new Stopwatch();
                        sw.Start();
                        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();
            }
        }