/// <summary>
 /// Fire and forget the fetch action on a background thread.
 /// Do not change to Task and do not await it.
 /// </summary>
 internal static void ProcessFetchInBackground(
     MsalAccessTokenCacheItem oldAccessToken,
     Func <Task <AuthenticationResult> > fetchAction,
     ICoreLogger logger)
 {
     _ = Task.Run(async() =>
     {
         try
         {
             await fetchAction().ConfigureAwait(false);
         }
         catch (MsalServiceException ex)
         {
             string logMsg = $"Background fetch failed with MsalServiceException. Is AAD down? { ex.IsAadUnavailable()}";
             if (ex.StatusCode == 400)
             {
                 logger.ErrorPiiWithPrefix(ex, logMsg);
             }
             else
             {
                 logger.WarningPiiWithPrefix(ex, logMsg);
             }
         }
         catch (Exception ex)
         {
             string logMsg = $"Background fetch failed with exception.";
             logger.WarningPiiWithPrefix(ex, logMsg);
         }
     });
 }
예제 #2
0
        byte[] ILegacyCachePersistence.LoadCache()
        {
            try
            {
                SecStatusCode res;
                var           rec = new SecRecord(SecKind.GenericPassword)
                {
                    Generic     = NSData.FromString(LocalSettingsContainerName),
                    Accessible  = SecAccessible.Always,
                    Service     = NAME + " Service",
                    Account     = NAME + " cache",
                    Label       = NAME + " Label",
                    Comment     = NAME + " Cache",
                    Description = "Storage for cache"
                };

                if (_keychainGroup != null)
                {
                    rec.AccessGroup = _keychainGroup;
                }

                var match = SecKeyChain.QueryAsRecord(rec, out res);
                if (res == SecStatusCode.Success && match != null && match.ValueData != null)
                {
                    return(match.ValueData.ToArray());
                }
            }
            catch (Exception ex)
            {
                _logger.WarningPiiWithPrefix(ex, "Failed to load adal cache: ");
                // Ignore as the cache seems to be corrupt
            }
            return(null);
        }
예제 #3
0
        /// <summary>
        /// Returns a tuple where
        ///
        /// Item1 is a map of ClientInfo -> AdalUserInfo for those users that have ClientInfo
        /// Item2 is a list of AdalUserInfo for those users that do not have ClientInfo
        /// </summary>
        public static AdalUsersForMsal GetAllAdalUsersForMsal(
            ICoreLogger logger,
            ILegacyCachePersistence legacyCachePersistence,
            string clientId)
        {
            var userEntries = new List <AdalUserForMsalEntry>();

            try
            {
                IDictionary <AdalTokenCacheKey, AdalResultWrapper> dictionary =
                    AdalCacheOperations.Deserialize(logger, legacyCachePersistence.LoadCache());

                // filter by client id
                dictionary.Where(p =>
                                 p.Key.ClientId.Equals(clientId, StringComparison.OrdinalIgnoreCase) &&
                                 !string.IsNullOrEmpty(p.Key.Authority))
                .ToList()
                .ForEach(kvp =>
                {
                    userEntries.Add(new AdalUserForMsalEntry(
                                        authority: kvp.Key.Authority,
                                        clientId: clientId,
                                        clientInfo: kvp.Value.RawClientInfo, // optional, missing in ADAL v3
                                        userInfo: kvp.Value.Result.UserInfo));
                });
            }
            catch (Exception ex)
            {
                logger.WarningPiiWithPrefix(ex, "An error occurred while reading accounts in ADAL format from the cache for MSAL. " +
                                            "For details please see https://aka.ms/net-cache-persistence-errors. ");
            }

            return(new AdalUsersForMsal(userEntries));
        }
예제 #4
0
        /// <summary>
        /// Algorithm to delete:
        ///
        /// DisplayableId cannot be null
        /// Removal is scoped by enviroment and clientId;
        ///
        /// If accountId != null then delete everything with the same clientInfo
        /// otherwise, delete everything with the same displayableId
        ///
        /// Notes:
        /// - displayableId can change rarely
        /// - ClientCredential Grant uses the app token cache, not the user token cache, so this algorithm does not apply
        /// (nor will GetAccounts / RemoveAccount work)
        ///
        /// </summary>
        public static void RemoveAdalUser(
            ICoreLogger logger,
            ILegacyCachePersistence legacyCachePersistence,
            string clientId,
            string displayableId,
            string accountOrUserId)
        {
            try
            {
                IDictionary <AdalTokenCacheKey, AdalResultWrapper> adalCache =
                    AdalCacheOperations.Deserialize(logger, legacyCachePersistence.LoadCache());

                if (!string.IsNullOrEmpty(accountOrUserId))
                {
                    RemoveEntriesWithMatchingId(clientId, accountOrUserId, adalCache);
                }

                RemoveEntriesWithMatchingName(logger, clientId, displayableId, adalCache);
                legacyCachePersistence.WriteCache(AdalCacheOperations.Serialize(logger, adalCache));
            }
            catch (Exception ex)
            {
                logger.WarningPiiWithPrefix(ex, "An error occurred while deleting account in ADAL format from the cache. " +
                                            "For details please see https://aka.ms/net-cache-persistence-errors. ");
            }
        }
        byte[] ILegacyCachePersistence.LoadCache()
        {
            try
            {
                var localSettings = ApplicationData.Current.LocalSettings;
                localSettings.CreateContainer(LocalSettingsContainerName,
                                              ApplicationDataCreateDisposition.Always);
                return(GetCacheValue(localSettings.Containers[LocalSettingsContainerName].Values));
            }
            catch (Exception ex)
            {
                _logger.WarningPiiWithPrefix(ex, "Failed to load adal cache: ");
                // Ignore as the cache seems to be corrupt
            }

            return(null);
        }
예제 #6
0
        public static MsalRefreshTokenCacheItem GetRefreshToken(
            ICoreLogger logger,
            ILegacyCachePersistence legacyCachePersistence,
            IEnumerable <string> environmentAliases,
            string clientId,
            IAccount account)
        {
            try
            {
                IDictionary <AdalTokenCacheKey, AdalResultWrapper> dictionary =
                    AdalCacheOperations.Deserialize(logger, legacyCachePersistence.LoadCache());

                IEnumerable <KeyValuePair <AdalTokenCacheKey, AdalResultWrapper> > listToProcess =
                    dictionary.Where(p =>
                                     p.Key.ClientId.Equals(clientId, StringComparison.OrdinalIgnoreCase) &&
                                     environmentAliases.Contains(new Uri(p.Key.Authority).Host));

                bool filtered = false;

                if (!string.IsNullOrEmpty(account?.Username))
                {
                    listToProcess =
                        listToProcess.Where(p => account.Username.Equals(
                                                p.Key.DisplayableId, StringComparison.OrdinalIgnoreCase));

                    filtered = true;
                }

                if (!string.IsNullOrEmpty(account?.HomeAccountId?.ObjectId))
                {
                    listToProcess =
                        listToProcess.Where(p => account.HomeAccountId.ObjectId.Equals(
                                                p.Key.UniqueId, StringComparison.OrdinalIgnoreCase)).ToList();

                    filtered = true;
                }

                // We should filter at leasts by one criteria to ensure we retrun adequate RT
                if (!filtered)
                {
                    logger.Warning("Could not filter ADAL entries by either UPN or unique ID, skipping.");
                    return(null);
                }

                return(listToProcess.Select(adalEntry => new MsalRefreshTokenCacheItem(
                                                new Uri(adalEntry.Key.Authority).Host,
                                                adalEntry.Key.ClientId,
                                                adalEntry.Value.RefreshToken,
                                                adalEntry.Value.RawClientInfo,
                                                familyId: null,
                                                homeAccountId: GetHomeAccountId(adalEntry.Value)))
                       .FirstOrDefault());
            }
            catch (Exception ex)
            {
                logger.WarningPiiWithPrefix(ex, "An error occurred while searching for refresh tokens in ADAL format in the cache for MSAL. " +
                                            "For details please see https://aka.ms/net-cache-persistence-errors. ");

                return(null);
            }
        }
예제 #7
0
        public static void WriteAdalRefreshToken(
            ICoreLogger logger,
            ILegacyCachePersistence legacyCachePersistence,
            MsalRefreshTokenCacheItem rtItem,
            MsalIdTokenCacheItem idItem,
            string authority,
            string uniqueId,
            string scope)
        {
            try
            {
                if (rtItem == null)
                {
                    logger.Info("No refresh token available. Skipping writing to ADAL legacy cache.");
                    return;
                }

                if (!string.IsNullOrEmpty(rtItem.FamilyId))
                {
                    logger.Info("Not writing FRT in ADAL legacy cache");
                    return;
                }

                //Using scope instead of resource because that value does not exist. STS should return it.
                AdalTokenCacheKey key = new AdalTokenCacheKey(authority, scope, rtItem.ClientId, TokenSubjectType.User,
                                                              uniqueId, idItem.IdToken.PreferredUsername);
                AdalResultWrapper wrapper = new AdalResultWrapper()
                {
                    Result = new AdalResult()
                    {
                        UserInfo = new AdalUserInfo()
                        {
                            UniqueId      = uniqueId,
                            DisplayableId = idItem.IdToken.PreferredUsername
                        }
                    },
                    RefreshToken  = rtItem.Secret,
                    RawClientInfo = rtItem.RawClientInfo,
                    //ResourceInResponse is needed to treat RT as an MRRT. See IsMultipleResourceRefreshToken
                    //property in AdalResultWrapper and its usage. Stronger design would be for the STS to return resource
                    //for which the token was issued as well on v2 endpoint.
                    ResourceInResponse = scope
                };

                IDictionary <AdalTokenCacheKey, AdalResultWrapper> dictionary = AdalCacheOperations.Deserialize(
                    logger,
                    legacyCachePersistence.LoadCache());

                dictionary[key] = wrapper;
                legacyCachePersistence.WriteCache(AdalCacheOperations.Serialize(logger, dictionary));
            }
            catch (Exception ex)
            {
                if (!string.Equals(rtItem?.Environment, idItem?.Environment, StringComparison.OrdinalIgnoreCase))
                {
                    logger.Error(DifferentEnvError);
                }

                if (!string.Equals(rtItem?.Environment, new Uri(authority).Host, StringComparison.OrdinalIgnoreCase))
                {
                    logger.Error(DifferentAuthorityError);
                }

                logger.WarningPiiWithPrefix(ex, "An error occurred while writing MSAL refresh token to the cache in ADAL format. " +
                                            "For details please see https://aka.ms/net-cache-persistence-errors. ");
            }
        }
        private static List <MsalRefreshTokenCacheItem> GetAllAdalEntriesForMsal(
            ICoreLogger logger,
            ILegacyCachePersistence legacyCachePersistence,
            IEnumerable <string> environmentAliases,
            string clientId,
            string upn,
            string uniqueId)
        {
            try
            {
                IDictionary <AdalTokenCacheKey, AdalResultWrapper> dictionary =
                    AdalCacheOperations.Deserialize(logger, legacyCachePersistence.LoadCache());
                // filter by client id and environment first
                // TODO - authority check needs to be updated for alias check
                List <KeyValuePair <AdalTokenCacheKey, AdalResultWrapper> > listToProcess =
                    dictionary.Where(p =>
                                     p.Key.ClientId.Equals(clientId, StringComparison.OrdinalIgnoreCase) &&
                                     environmentAliases.Contains(new Uri(p.Key.Authority).Host)).ToList();

                // if upn is provided then use it to filter
                if (!string.IsNullOrEmpty(upn))
                {
                    List <KeyValuePair <AdalTokenCacheKey, AdalResultWrapper> > upnEntries =
                        listToProcess.Where(p => upn.Equals(p.Key.DisplayableId, StringComparison.OrdinalIgnoreCase)).ToList();

                    listToProcess = upnEntries;
                }

                // if uniqueId is provided then use it to filter
                if (!string.IsNullOrEmpty(uniqueId))
                {
                    List <KeyValuePair <AdalTokenCacheKey, AdalResultWrapper> > uniqueIdEntries =
                        listToProcess.Where(p => uniqueId.Equals(p.Key.UniqueId, StringComparison.OrdinalIgnoreCase)).ToList();

                    listToProcess = uniqueIdEntries;
                }

                List <MsalRefreshTokenCacheItem> list = new List <MsalRefreshTokenCacheItem>();
                foreach (KeyValuePair <AdalTokenCacheKey, AdalResultWrapper> pair in listToProcess)
                {
                    string homeAccountId = null;
                    if (!string.IsNullOrEmpty(pair.Value.RawClientInfo))
                    {
                        homeAccountId = ClientInfo.CreateFromJson(pair.Value.RawClientInfo).ToAccountIdentifier();
                    }

                    list.Add(new MsalRefreshTokenCacheItem(
                                 new Uri(pair.Key.Authority).Host,
                                 pair.Key.ClientId,
                                 pair.Value.RefreshToken,
                                 pair.Value.RawClientInfo,
                                 familyId: null,
                                 homeAccountId: homeAccountId));
                }

                return(list);
            }
            catch (Exception ex)
            {
                logger.WarningPiiWithPrefix(ex, "An error occurred while searching for refresh tokens in ADAL format in the cache for MSAL. " +
                                            "For details please see https://aka.ms/net-cache-persistence-errors. ");

                return(new List <MsalRefreshTokenCacheItem>());
            }
        }