/// <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); } }); }
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); }
/// <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)); }
/// <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); }
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); } }
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>()); } }