@azure/msal-common 14.14.0 → 14.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/dist/account/AccountInfo.d.ts +66 -66
  2. package/dist/account/AccountInfo.mjs +77 -77
  3. package/dist/account/AuthToken.d.ts +17 -17
  4. package/dist/account/AuthToken.mjs +58 -58
  5. package/dist/account/CcsCredential.d.ts +9 -9
  6. package/dist/account/CcsCredential.mjs +8 -8
  7. package/dist/account/ClientCredentials.d.ts +19 -19
  8. package/dist/account/ClientInfo.d.ts +18 -18
  9. package/dist/account/ClientInfo.mjs +37 -37
  10. package/dist/account/TokenClaims.d.ts +83 -83
  11. package/dist/account/TokenClaims.mjs +20 -20
  12. package/dist/authority/Authority.d.ts +254 -254
  13. package/dist/authority/Authority.mjs +836 -836
  14. package/dist/authority/AuthorityFactory.d.ts +18 -18
  15. package/dist/authority/AuthorityFactory.mjs +28 -28
  16. package/dist/authority/AuthorityMetadata.d.ts +43 -43
  17. package/dist/authority/AuthorityMetadata.mjs +137 -137
  18. package/dist/authority/AuthorityOptions.d.ts +27 -27
  19. package/dist/authority/AuthorityOptions.mjs +18 -18
  20. package/dist/authority/AuthorityType.d.ts +10 -10
  21. package/dist/authority/AuthorityType.mjs +13 -13
  22. package/dist/authority/AzureRegion.d.ts +1 -1
  23. package/dist/authority/AzureRegionConfiguration.d.ts +5 -5
  24. package/dist/authority/CloudDiscoveryMetadata.d.ts +5 -5
  25. package/dist/authority/CloudInstanceDiscoveryErrorResponse.d.ts +13 -13
  26. package/dist/authority/CloudInstanceDiscoveryErrorResponse.mjs +8 -8
  27. package/dist/authority/CloudInstanceDiscoveryResponse.d.ts +9 -9
  28. package/dist/authority/CloudInstanceDiscoveryResponse.mjs +8 -8
  29. package/dist/authority/ImdsOptions.d.ts +5 -5
  30. package/dist/authority/OIDCOptions.d.ts +8 -8
  31. package/dist/authority/OpenIdConfigResponse.d.ts +11 -11
  32. package/dist/authority/OpenIdConfigResponse.mjs +10 -10
  33. package/dist/authority/ProtocolMode.d.ts +8 -8
  34. package/dist/authority/ProtocolMode.mjs +11 -11
  35. package/dist/authority/RegionDiscovery.d.ts +32 -32
  36. package/dist/authority/RegionDiscovery.mjs +106 -106
  37. package/dist/authority/RegionDiscoveryMetadata.d.ts +6 -6
  38. package/dist/broker/nativeBroker/INativeBrokerPlugin.d.ts +15 -15
  39. package/dist/cache/CacheManager.d.ts +497 -497
  40. package/dist/cache/CacheManager.mjs +1249 -1249
  41. package/dist/cache/entities/AccessTokenEntity.d.ts +25 -25
  42. package/dist/cache/entities/AccountEntity.d.ts +106 -106
  43. package/dist/cache/entities/AccountEntity.mjs +240 -240
  44. package/dist/cache/entities/AppMetadataEntity.d.ts +11 -11
  45. package/dist/cache/entities/AuthorityMetadataEntity.d.ts +15 -15
  46. package/dist/cache/entities/CacheRecord.d.ts +13 -13
  47. package/dist/cache/entities/CredentialEntity.d.ts +30 -30
  48. package/dist/cache/entities/IdTokenEntity.d.ts +8 -8
  49. package/dist/cache/entities/RefreshTokenEntity.d.ts +7 -7
  50. package/dist/cache/entities/ServerTelemetryEntity.d.ts +6 -6
  51. package/dist/cache/entities/ThrottlingEntity.d.ts +7 -7
  52. package/dist/cache/interface/ICacheManager.d.ts +166 -166
  53. package/dist/cache/interface/ICachePlugin.d.ts +5 -5
  54. package/dist/cache/interface/ISerializableTokenCache.d.ts +4 -4
  55. package/dist/cache/persistence/TokenCacheContext.d.ts +23 -23
  56. package/dist/cache/persistence/TokenCacheContext.mjs +25 -25
  57. package/dist/cache/utils/CacheHelpers.d.ts +94 -94
  58. package/dist/cache/utils/CacheHelpers.mjs +324 -324
  59. package/dist/cache/utils/CacheTypes.d.ts +69 -69
  60. package/dist/client/AuthorizationCodeClient.d.ts +74 -74
  61. package/dist/client/AuthorizationCodeClient.mjs +420 -420
  62. package/dist/client/BaseClient.d.ts +51 -51
  63. package/dist/client/BaseClient.mjs +100 -100
  64. package/dist/client/RefreshTokenClient.d.ts +35 -35
  65. package/dist/client/RefreshTokenClient.mjs +211 -211
  66. package/dist/client/SilentFlowClient.d.ts +27 -27
  67. package/dist/client/SilentFlowClient.mjs +133 -133
  68. package/dist/config/AppTokenProvider.d.ts +38 -38
  69. package/dist/config/ClientConfiguration.d.ts +150 -150
  70. package/dist/config/ClientConfiguration.mjs +95 -95
  71. package/dist/constants/AADServerParamKeys.d.ts +53 -53
  72. package/dist/constants/AADServerParamKeys.mjs +57 -57
  73. package/dist/crypto/ICrypto.d.ts +68 -68
  74. package/dist/crypto/ICrypto.mjs +36 -36
  75. package/dist/crypto/IGuidGenerator.d.ts +4 -4
  76. package/dist/crypto/JoseHeader.d.ts +22 -22
  77. package/dist/crypto/JoseHeader.mjs +37 -37
  78. package/dist/crypto/PopTokenGenerator.d.ts +59 -59
  79. package/dist/crypto/PopTokenGenerator.mjs +81 -81
  80. package/dist/crypto/SignedHttpRequest.d.ts +15 -15
  81. package/dist/error/AuthError.d.ts +44 -44
  82. package/dist/error/AuthError.mjs +46 -46
  83. package/dist/error/AuthErrorCodes.d.ts +5 -5
  84. package/dist/error/AuthErrorCodes.mjs +9 -9
  85. package/dist/error/CacheError.d.ts +20 -20
  86. package/dist/error/CacheError.mjs +24 -24
  87. package/dist/error/CacheErrorCodes.d.ts +2 -2
  88. package/dist/error/CacheErrorCodes.mjs +6 -6
  89. package/dist/error/ClientAuthError.d.ts +237 -237
  90. package/dist/error/ClientAuthError.mjs +249 -249
  91. package/dist/error/ClientAuthErrorCodes.d.ts +44 -44
  92. package/dist/error/ClientAuthErrorCodes.mjs +48 -48
  93. package/dist/error/ClientConfigurationError.d.ts +128 -128
  94. package/dist/error/ClientConfigurationError.mjs +135 -135
  95. package/dist/error/ClientConfigurationErrorCodes.d.ts +22 -22
  96. package/dist/error/ClientConfigurationErrorCodes.mjs +26 -26
  97. package/dist/error/InteractionRequiredAuthError.d.ts +65 -65
  98. package/dist/error/InteractionRequiredAuthError.mjs +85 -85
  99. package/dist/error/InteractionRequiredAuthErrorCodes.d.ts +7 -7
  100. package/dist/error/InteractionRequiredAuthErrorCodes.mjs +13 -13
  101. package/dist/error/JoseHeaderError.d.ts +15 -15
  102. package/dist/error/JoseHeaderError.mjs +22 -22
  103. package/dist/error/JoseHeaderErrorCodes.d.ts +2 -2
  104. package/dist/error/JoseHeaderErrorCodes.mjs +6 -6
  105. package/dist/error/ServerError.d.ts +15 -15
  106. package/dist/error/ServerError.mjs +16 -16
  107. package/dist/index.cjs +8662 -8658
  108. package/dist/index.cjs.map +1 -1
  109. package/dist/index.d.ts +107 -107
  110. package/dist/index.mjs +1 -1
  111. package/dist/logger/Logger.d.ts +95 -95
  112. package/dist/logger/Logger.mjs +188 -188
  113. package/dist/network/INetworkModule.d.ts +29 -29
  114. package/dist/network/INetworkModule.mjs +12 -12
  115. package/dist/network/NetworkManager.d.ts +33 -33
  116. package/dist/network/NetworkManager.mjs +34 -34
  117. package/dist/network/RequestThumbprint.d.ts +18 -18
  118. package/dist/network/ThrottlingUtils.d.ts +42 -42
  119. package/dist/network/ThrottlingUtils.mjs +95 -95
  120. package/dist/packageMetadata.d.ts +2 -2
  121. package/dist/packageMetadata.mjs +4 -4
  122. package/dist/request/AuthenticationHeaderParser.d.ts +19 -19
  123. package/dist/request/AuthenticationHeaderParser.mjs +55 -55
  124. package/dist/request/BaseAuthRequest.d.ts +47 -47
  125. package/dist/request/CommonAuthorizationCodeRequest.d.ts +27 -27
  126. package/dist/request/CommonAuthorizationUrlRequest.d.ts +50 -50
  127. package/dist/request/CommonClientCredentialRequest.d.ts +17 -17
  128. package/dist/request/CommonDeviceCodeRequest.d.ts +21 -21
  129. package/dist/request/CommonEndSessionRequest.d.ts +21 -21
  130. package/dist/request/CommonOnBehalfOfRequest.d.ts +13 -13
  131. package/dist/request/CommonRefreshTokenRequest.d.ts +22 -22
  132. package/dist/request/CommonSilentFlowRequest.d.ts +27 -27
  133. package/dist/request/CommonUsernamePasswordRequest.d.ts +17 -17
  134. package/dist/request/NativeRequest.d.ts +19 -19
  135. package/dist/request/NativeSignOutRequest.d.ts +5 -5
  136. package/dist/request/RequestParameterBuilder.d.ts +217 -217
  137. package/dist/request/RequestParameterBuilder.mjs +382 -382
  138. package/dist/request/RequestValidator.d.ts +27 -27
  139. package/dist/request/RequestValidator.mjs +64 -64
  140. package/dist/request/ScopeSet.d.ts +88 -88
  141. package/dist/request/ScopeSet.mjs +197 -197
  142. package/dist/request/StoreInCache.d.ts +8 -8
  143. package/dist/response/AuthenticationResult.d.ts +41 -41
  144. package/dist/response/AuthorizationCodePayload.d.ts +13 -13
  145. package/dist/response/DeviceCodeResponse.d.ts +25 -25
  146. package/dist/response/ExternalTokenResponse.d.ts +15 -15
  147. package/dist/response/IMDSBadResponse.d.ts +4 -4
  148. package/dist/response/ResponseHandler.d.ts +69 -69
  149. package/dist/response/ResponseHandler.mjs +374 -374
  150. package/dist/response/ServerAuthorizationCodeResponse.d.ts +26 -26
  151. package/dist/response/ServerAuthorizationTokenResponse.d.ts +47 -47
  152. package/dist/telemetry/performance/IPerformanceClient.d.ts +57 -57
  153. package/dist/telemetry/performance/IPerformanceMeasurement.d.ts +5 -5
  154. package/dist/telemetry/performance/PerformanceClient.d.ts +242 -242
  155. package/dist/telemetry/performance/PerformanceClient.mjs +587 -587
  156. package/dist/telemetry/performance/PerformanceEvent.d.ts +515 -505
  157. package/dist/telemetry/performance/PerformanceEvent.d.ts.map +1 -1
  158. package/dist/telemetry/performance/PerformanceEvent.mjs +484 -480
  159. package/dist/telemetry/performance/PerformanceEvent.mjs.map +1 -1
  160. package/dist/telemetry/performance/StubPerformanceClient.d.ts +24 -24
  161. package/dist/telemetry/performance/StubPerformanceClient.mjs +76 -76
  162. package/dist/telemetry/server/ServerTelemetryManager.d.ts +78 -78
  163. package/dist/telemetry/server/ServerTelemetryManager.mjs +260 -260
  164. package/dist/telemetry/server/ServerTelemetryRequest.d.ts +8 -8
  165. package/dist/url/IUri.d.ts +12 -12
  166. package/dist/url/UrlString.d.ts +48 -48
  167. package/dist/url/UrlString.mjs +161 -161
  168. package/dist/utils/ClientAssertionUtils.d.ts +2 -2
  169. package/dist/utils/ClientAssertionUtils.mjs +16 -16
  170. package/dist/utils/Constants.d.ts +309 -309
  171. package/dist/utils/Constants.mjs +322 -322
  172. package/dist/utils/FunctionWrappers.d.ts +27 -27
  173. package/dist/utils/FunctionWrappers.mjs +94 -94
  174. package/dist/utils/MsalTypes.d.ts +6 -6
  175. package/dist/utils/ProtocolUtils.d.ts +42 -42
  176. package/dist/utils/ProtocolUtils.mjs +69 -69
  177. package/dist/utils/StringUtils.d.ts +40 -40
  178. package/dist/utils/StringUtils.mjs +95 -95
  179. package/dist/utils/TimeUtils.d.ts +25 -25
  180. package/dist/utils/TimeUtils.mjs +43 -43
  181. package/dist/utils/UrlUtils.d.ts +10 -10
  182. package/dist/utils/UrlUtils.mjs +44 -44
  183. package/package.json +1 -1
  184. package/src/packageMetadata.ts +1 -1
  185. package/src/telemetry/performance/PerformanceEvent.ts +12 -0
@@ -1,4 +1,4 @@
1
- /*! @azure/msal-common v14.14.0 2024-07-23 */
1
+ /*! @azure/msal-common v14.14.1 2024-08-13 */
2
2
  'use strict';
3
3
  import { Separators, CredentialType, AuthenticationScheme, THE_FAMILY_ID, APP_METADATA, AUTHORITY_METADATA_CONSTANTS } from '../utils/Constants.mjs';
4
4
  import { generateCredentialKey } from './utils/CacheHelpers.mjs';
@@ -13,1254 +13,1254 @@ import { CacheError } from '../error/CacheError.mjs';
13
13
  import { invalidCacheRecord, bindingKeyNotRemoved, multipleMatchingAppMetadata, methodNotImplemented } from '../error/ClientAuthErrorCodes.mjs';
14
14
  import { cacheQuotaExceededErrorCode, cacheUnknownErrorCode } from '../error/CacheErrorCodes.mjs';
15
15
 
16
- /*
17
- * Copyright (c) Microsoft Corporation. All rights reserved.
18
- * Licensed under the MIT License.
19
- */
20
- /**
21
- * Interface class which implement cache storage functions used by MSAL to perform validity checks, and store tokens.
22
- * @internal
23
- */
24
- class CacheManager {
25
- constructor(clientId, cryptoImpl, logger, staticAuthorityOptions) {
26
- this.clientId = clientId;
27
- this.cryptoImpl = cryptoImpl;
28
- this.commonLogger = logger.clone(name, version);
29
- this.staticAuthorityOptions = staticAuthorityOptions;
30
- }
31
- /**
32
- * Returns all the accounts in the cache that match the optional filter. If no filter is provided, all accounts are returned.
33
- * @param accountFilter - (Optional) filter to narrow down the accounts returned
34
- * @returns Array of AccountInfo objects in cache
35
- */
36
- getAllAccounts(accountFilter) {
37
- return this.buildTenantProfiles(this.getAccountsFilteredBy(accountFilter || {}), accountFilter);
38
- }
39
- /**
40
- * Gets first tenanted AccountInfo object found based on provided filters
41
- */
42
- getAccountInfoFilteredBy(accountFilter) {
43
- const allAccounts = this.getAllAccounts(accountFilter);
44
- if (allAccounts.length > 1) {
45
- // If one or more accounts are found, prioritize accounts that have an ID token
46
- const sortedAccounts = allAccounts.sort((account) => {
47
- return account.idTokenClaims ? -1 : 1;
48
- });
49
- return sortedAccounts[0];
50
- }
51
- else if (allAccounts.length === 1) {
52
- // If only one account is found, return it regardless of whether a matching ID token was found
53
- return allAccounts[0];
54
- }
55
- else {
56
- return null;
57
- }
58
- }
59
- /**
60
- * Returns a single matching
61
- * @param accountFilter
62
- * @returns
63
- */
64
- getBaseAccountInfo(accountFilter) {
65
- const accountEntities = this.getAccountsFilteredBy(accountFilter);
66
- if (accountEntities.length > 0) {
67
- return accountEntities[0].getAccountInfo();
68
- }
69
- else {
70
- return null;
71
- }
72
- }
73
- /**
74
- * Matches filtered account entities with cached ID tokens that match the tenant profile-specific account filters
75
- * and builds the account info objects from the matching ID token's claims
76
- * @param cachedAccounts
77
- * @param accountFilter
78
- * @returns Array of AccountInfo objects that match account and tenant profile filters
79
- */
80
- buildTenantProfiles(cachedAccounts, accountFilter) {
81
- return cachedAccounts.flatMap((accountEntity) => {
82
- return this.getTenantProfilesFromAccountEntity(accountEntity, accountFilter?.tenantId, accountFilter);
83
- });
84
- }
85
- getTenantedAccountInfoByFilter(accountInfo, tokenKeys, tenantProfile, tenantProfileFilter) {
86
- let tenantedAccountInfo = null;
87
- let idTokenClaims;
88
- if (tenantProfileFilter) {
89
- if (!this.tenantProfileMatchesFilter(tenantProfile, tenantProfileFilter)) {
90
- return null;
91
- }
92
- }
93
- const idToken = this.getIdToken(accountInfo, tokenKeys, tenantProfile.tenantId);
94
- if (idToken) {
95
- idTokenClaims = extractTokenClaims(idToken.secret, this.cryptoImpl.base64Decode);
96
- if (!this.idTokenClaimsMatchTenantProfileFilter(idTokenClaims, tenantProfileFilter)) {
97
- // ID token sourced claims don't match so this tenant profile is not a match
98
- return null;
99
- }
100
- }
101
- // Expand tenant profile into account info based on matching tenant profile and if available matching ID token claims
102
- tenantedAccountInfo = updateAccountTenantProfileData(accountInfo, tenantProfile, idTokenClaims, idToken?.secret);
103
- return tenantedAccountInfo;
104
- }
105
- getTenantProfilesFromAccountEntity(accountEntity, targetTenantId, tenantProfileFilter) {
106
- const accountInfo = accountEntity.getAccountInfo();
107
- let searchTenantProfiles = accountInfo.tenantProfiles || new Map();
108
- const tokenKeys = this.getTokenKeys();
109
- // If a tenant ID was provided, only return the tenant profile for that tenant ID if it exists
110
- if (targetTenantId) {
111
- const tenantProfile = searchTenantProfiles.get(targetTenantId);
112
- if (tenantProfile) {
113
- // Reduce search field to just this tenant profile
114
- searchTenantProfiles = new Map([
115
- [targetTenantId, tenantProfile],
116
- ]);
117
- }
118
- else {
119
- // No tenant profile for search tenant ID, return empty array
120
- return [];
121
- }
122
- }
123
- const matchingTenantProfiles = [];
124
- searchTenantProfiles.forEach((tenantProfile) => {
125
- const tenantedAccountInfo = this.getTenantedAccountInfoByFilter(accountInfo, tokenKeys, tenantProfile, tenantProfileFilter);
126
- if (tenantedAccountInfo) {
127
- matchingTenantProfiles.push(tenantedAccountInfo);
128
- }
129
- });
130
- return matchingTenantProfiles;
131
- }
132
- tenantProfileMatchesFilter(tenantProfile, tenantProfileFilter) {
133
- if (!!tenantProfileFilter.localAccountId &&
134
- !this.matchLocalAccountIdFromTenantProfile(tenantProfile, tenantProfileFilter.localAccountId)) {
135
- return false;
136
- }
137
- if (!!tenantProfileFilter.name &&
138
- !(tenantProfile.name === tenantProfileFilter.name)) {
139
- return false;
140
- }
141
- if (tenantProfileFilter.isHomeTenant !== undefined &&
142
- !(tenantProfile.isHomeTenant === tenantProfileFilter.isHomeTenant)) {
143
- return false;
144
- }
145
- return true;
146
- }
147
- idTokenClaimsMatchTenantProfileFilter(idTokenClaims, tenantProfileFilter) {
148
- // Tenant Profile filtering
149
- if (tenantProfileFilter) {
150
- if (!!tenantProfileFilter.localAccountId &&
151
- !this.matchLocalAccountIdFromTokenClaims(idTokenClaims, tenantProfileFilter.localAccountId)) {
152
- return false;
153
- }
154
- if (!!tenantProfileFilter.loginHint &&
155
- !this.matchLoginHintFromTokenClaims(idTokenClaims, tenantProfileFilter.loginHint)) {
156
- return false;
157
- }
158
- if (!!tenantProfileFilter.username &&
159
- !this.matchUsername(idTokenClaims.preferred_username, tenantProfileFilter.username)) {
160
- return false;
161
- }
162
- if (!!tenantProfileFilter.name &&
163
- !this.matchName(idTokenClaims, tenantProfileFilter.name)) {
164
- return false;
165
- }
166
- if (!!tenantProfileFilter.sid &&
167
- !this.matchSid(idTokenClaims, tenantProfileFilter.sid)) {
168
- return false;
169
- }
170
- }
171
- return true;
172
- }
173
- /**
174
- * saves a cache record
175
- * @param cacheRecord {CacheRecord}
176
- * @param storeInCache {?StoreInCache}
177
- * @param correlationId {?string} correlation id
178
- */
179
- async saveCacheRecord(cacheRecord, storeInCache, correlationId) {
180
- if (!cacheRecord) {
181
- throw createClientAuthError(invalidCacheRecord);
182
- }
183
- try {
184
- if (!!cacheRecord.account) {
185
- this.setAccount(cacheRecord.account);
186
- }
187
- if (!!cacheRecord.idToken && storeInCache?.idToken !== false) {
188
- this.setIdTokenCredential(cacheRecord.idToken);
189
- }
190
- if (!!cacheRecord.accessToken &&
191
- storeInCache?.accessToken !== false) {
192
- await this.saveAccessToken(cacheRecord.accessToken);
193
- }
194
- if (!!cacheRecord.refreshToken &&
195
- storeInCache?.refreshToken !== false) {
196
- this.setRefreshTokenCredential(cacheRecord.refreshToken);
197
- }
198
- if (!!cacheRecord.appMetadata) {
199
- this.setAppMetadata(cacheRecord.appMetadata);
200
- }
201
- }
202
- catch (e) {
203
- this.commonLogger?.error(`CacheManager.saveCacheRecord: failed`);
204
- if (e instanceof Error) {
205
- this.commonLogger?.errorPii(`CacheManager.saveCacheRecord: ${e.message}`, correlationId);
206
- if (e.name === "QuotaExceededError" ||
207
- e.name === "NS_ERROR_DOM_QUOTA_REACHED" ||
208
- e.message.includes("exceeded the quota")) {
209
- this.commonLogger?.error(`CacheManager.saveCacheRecord: exceeded storage quota`, correlationId);
210
- throw new CacheError(cacheQuotaExceededErrorCode);
211
- }
212
- else {
213
- throw new CacheError(e.name, e.message);
214
- }
215
- }
216
- else {
217
- this.commonLogger?.errorPii(`CacheManager.saveCacheRecord: ${e}`, correlationId);
218
- throw new CacheError(cacheUnknownErrorCode);
219
- }
220
- }
221
- }
222
- /**
223
- * saves access token credential
224
- * @param credential
225
- */
226
- async saveAccessToken(credential) {
227
- const accessTokenFilter = {
228
- clientId: credential.clientId,
229
- credentialType: credential.credentialType,
230
- environment: credential.environment,
231
- homeAccountId: credential.homeAccountId,
232
- realm: credential.realm,
233
- tokenType: credential.tokenType,
234
- requestedClaimsHash: credential.requestedClaimsHash,
235
- };
236
- const tokenKeys = this.getTokenKeys();
237
- const currentScopes = ScopeSet.fromString(credential.target);
238
- const removedAccessTokens = [];
239
- tokenKeys.accessToken.forEach((key) => {
240
- if (!this.accessTokenKeyMatchesFilter(key, accessTokenFilter, false)) {
241
- return;
242
- }
243
- const tokenEntity = this.getAccessTokenCredential(key);
244
- if (tokenEntity &&
245
- this.credentialMatchesFilter(tokenEntity, accessTokenFilter)) {
246
- const tokenScopeSet = ScopeSet.fromString(tokenEntity.target);
247
- if (tokenScopeSet.intersectingScopeSets(currentScopes)) {
248
- removedAccessTokens.push(this.removeAccessToken(key));
249
- }
250
- }
251
- });
252
- await Promise.all(removedAccessTokens);
253
- this.setAccessTokenCredential(credential);
254
- }
255
- /**
256
- * Retrieve account entities matching all provided tenant-agnostic filters; if no filter is set, get all account entities in the cache
257
- * Not checking for casing as keys are all generated in lower case, remember to convert to lower case if object properties are compared
258
- * @param accountFilter - An object containing Account properties to filter by
259
- */
260
- getAccountsFilteredBy(accountFilter) {
261
- const allAccountKeys = this.getAccountKeys();
262
- const matchingAccounts = [];
263
- allAccountKeys.forEach((cacheKey) => {
264
- if (!this.isAccountKey(cacheKey, accountFilter.homeAccountId)) {
265
- // Don't parse value if the key doesn't match the account filters
266
- return;
267
- }
268
- const entity = this.getAccount(cacheKey, this.commonLogger);
269
- // Match base account fields
270
- if (!entity) {
271
- return;
272
- }
273
- if (!!accountFilter.homeAccountId &&
274
- !this.matchHomeAccountId(entity, accountFilter.homeAccountId)) {
275
- return;
276
- }
277
- if (!!accountFilter.username &&
278
- !this.matchUsername(entity.username, accountFilter.username)) {
279
- return;
280
- }
281
- if (!!accountFilter.environment &&
282
- !this.matchEnvironment(entity, accountFilter.environment)) {
283
- return;
284
- }
285
- if (!!accountFilter.realm &&
286
- !this.matchRealm(entity, accountFilter.realm)) {
287
- return;
288
- }
289
- if (!!accountFilter.nativeAccountId &&
290
- !this.matchNativeAccountId(entity, accountFilter.nativeAccountId)) {
291
- return;
292
- }
293
- if (!!accountFilter.authorityType &&
294
- !this.matchAuthorityType(entity, accountFilter.authorityType)) {
295
- return;
296
- }
297
- // If at least one tenant profile matches the tenant profile filter, add the account to the list of matching accounts
298
- const tenantProfileFilter = {
299
- localAccountId: accountFilter?.localAccountId,
300
- name: accountFilter?.name,
301
- };
302
- const matchingTenantProfiles = entity.tenantProfiles?.filter((tenantProfile) => {
303
- return this.tenantProfileMatchesFilter(tenantProfile, tenantProfileFilter);
304
- });
305
- if (matchingTenantProfiles && matchingTenantProfiles.length === 0) {
306
- // No tenant profile for this account matches filter, don't add to list of matching accounts
307
- return;
308
- }
309
- matchingAccounts.push(entity);
310
- });
311
- return matchingAccounts;
312
- }
313
- /**
314
- * Returns true if the given key matches our account key schema. Also matches homeAccountId and/or tenantId if provided
315
- * @param key
316
- * @param homeAccountId
317
- * @param tenantId
318
- * @returns
319
- */
320
- isAccountKey(key, homeAccountId, tenantId) {
321
- if (key.split(Separators.CACHE_KEY_SEPARATOR).length < 3) {
322
- // Account cache keys contain 3 items separated by '-' (each item may also contain '-')
323
- return false;
324
- }
325
- if (homeAccountId &&
326
- !key.toLowerCase().includes(homeAccountId.toLowerCase())) {
327
- return false;
328
- }
329
- if (tenantId && !key.toLowerCase().includes(tenantId.toLowerCase())) {
330
- return false;
331
- }
332
- // Do not check environment as aliasing can cause false negatives
333
- return true;
334
- }
335
- /**
336
- * Returns true if the given key matches our credential key schema.
337
- * @param key
338
- */
339
- isCredentialKey(key) {
340
- if (key.split(Separators.CACHE_KEY_SEPARATOR).length < 6) {
341
- // Credential cache keys contain 6 items separated by '-' (each item may also contain '-')
342
- return false;
343
- }
344
- const lowerCaseKey = key.toLowerCase();
345
- // Credential keys must indicate what credential type they represent
346
- if (lowerCaseKey.indexOf(CredentialType.ID_TOKEN.toLowerCase()) ===
347
- -1 &&
348
- lowerCaseKey.indexOf(CredentialType.ACCESS_TOKEN.toLowerCase()) ===
349
- -1 &&
350
- lowerCaseKey.indexOf(CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME.toLowerCase()) === -1 &&
351
- lowerCaseKey.indexOf(CredentialType.REFRESH_TOKEN.toLowerCase()) ===
352
- -1) {
353
- return false;
354
- }
355
- if (lowerCaseKey.indexOf(CredentialType.REFRESH_TOKEN.toLowerCase()) >
356
- -1) {
357
- // Refresh tokens must contain the client id or family id
358
- const clientIdValidation = `${CredentialType.REFRESH_TOKEN}${Separators.CACHE_KEY_SEPARATOR}${this.clientId}${Separators.CACHE_KEY_SEPARATOR}`;
359
- const familyIdValidation = `${CredentialType.REFRESH_TOKEN}${Separators.CACHE_KEY_SEPARATOR}${THE_FAMILY_ID}${Separators.CACHE_KEY_SEPARATOR}`;
360
- if (lowerCaseKey.indexOf(clientIdValidation.toLowerCase()) === -1 &&
361
- lowerCaseKey.indexOf(familyIdValidation.toLowerCase()) === -1) {
362
- return false;
363
- }
364
- }
365
- else if (lowerCaseKey.indexOf(this.clientId.toLowerCase()) === -1) {
366
- // Tokens must contain the clientId
367
- return false;
368
- }
369
- return true;
370
- }
371
- /**
372
- * Returns whether or not the given credential entity matches the filter
373
- * @param entity
374
- * @param filter
375
- * @returns
376
- */
377
- credentialMatchesFilter(entity, filter) {
378
- if (!!filter.clientId && !this.matchClientId(entity, filter.clientId)) {
379
- return false;
380
- }
381
- if (!!filter.userAssertionHash &&
382
- !this.matchUserAssertionHash(entity, filter.userAssertionHash)) {
383
- return false;
384
- }
385
- /*
386
- * homeAccountId can be undefined, and we want to filter out cached items that have a homeAccountId of ""
387
- * because we don't want a client_credential request to return a cached token that has a homeAccountId
388
- */
389
- if (typeof filter.homeAccountId === "string" &&
390
- !this.matchHomeAccountId(entity, filter.homeAccountId)) {
391
- return false;
392
- }
393
- if (!!filter.environment &&
394
- !this.matchEnvironment(entity, filter.environment)) {
395
- return false;
396
- }
397
- if (!!filter.realm && !this.matchRealm(entity, filter.realm)) {
398
- return false;
399
- }
400
- if (!!filter.credentialType &&
401
- !this.matchCredentialType(entity, filter.credentialType)) {
402
- return false;
403
- }
404
- if (!!filter.familyId && !this.matchFamilyId(entity, filter.familyId)) {
405
- return false;
406
- }
407
- /*
408
- * idTokens do not have "target", target specific refreshTokens do exist for some types of authentication
409
- * Resource specific refresh tokens case will be added when the support is deemed necessary
410
- */
411
- if (!!filter.target && !this.matchTarget(entity, filter.target)) {
412
- return false;
413
- }
414
- // If request OR cached entity has requested Claims Hash, check if they match
415
- if (filter.requestedClaimsHash || entity.requestedClaimsHash) {
416
- // Don't match if either is undefined or they are different
417
- if (entity.requestedClaimsHash !== filter.requestedClaimsHash) {
418
- return false;
419
- }
420
- }
421
- // Access Token with Auth Scheme specific matching
422
- if (entity.credentialType ===
423
- CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME) {
424
- if (!!filter.tokenType &&
425
- !this.matchTokenType(entity, filter.tokenType)) {
426
- return false;
427
- }
428
- // KeyId (sshKid) in request must match cached SSH certificate keyId because SSH cert is bound to a specific key
429
- if (filter.tokenType === AuthenticationScheme.SSH) {
430
- if (filter.keyId && !this.matchKeyId(entity, filter.keyId)) {
431
- return false;
432
- }
433
- }
434
- }
435
- return true;
436
- }
437
- /**
438
- * retrieve appMetadata matching all provided filters; if no filter is set, get all appMetadata
439
- * @param filter
440
- */
441
- getAppMetadataFilteredBy(filter) {
442
- const allCacheKeys = this.getKeys();
443
- const matchingAppMetadata = {};
444
- allCacheKeys.forEach((cacheKey) => {
445
- // don't parse any non-appMetadata type cache entities
446
- if (!this.isAppMetadata(cacheKey)) {
447
- return;
448
- }
449
- // Attempt retrieval
450
- const entity = this.getAppMetadata(cacheKey);
451
- if (!entity) {
452
- return;
453
- }
454
- if (!!filter.environment &&
455
- !this.matchEnvironment(entity, filter.environment)) {
456
- return;
457
- }
458
- if (!!filter.clientId &&
459
- !this.matchClientId(entity, filter.clientId)) {
460
- return;
461
- }
462
- matchingAppMetadata[cacheKey] = entity;
463
- });
464
- return matchingAppMetadata;
465
- }
466
- /**
467
- * retrieve authorityMetadata that contains a matching alias
468
- * @param filter
469
- */
470
- getAuthorityMetadataByAlias(host) {
471
- const allCacheKeys = this.getAuthorityMetadataKeys();
472
- let matchedEntity = null;
473
- allCacheKeys.forEach((cacheKey) => {
474
- // don't parse any non-authorityMetadata type cache entities
475
- if (!this.isAuthorityMetadata(cacheKey) ||
476
- cacheKey.indexOf(this.clientId) === -1) {
477
- return;
478
- }
479
- // Attempt retrieval
480
- const entity = this.getAuthorityMetadata(cacheKey);
481
- if (!entity) {
482
- return;
483
- }
484
- if (entity.aliases.indexOf(host) === -1) {
485
- return;
486
- }
487
- matchedEntity = entity;
488
- });
489
- return matchedEntity;
490
- }
491
- /**
492
- * Removes all accounts and related tokens from cache.
493
- */
494
- async removeAllAccounts() {
495
- const allAccountKeys = this.getAccountKeys();
496
- const removedAccounts = [];
497
- allAccountKeys.forEach((cacheKey) => {
498
- removedAccounts.push(this.removeAccount(cacheKey));
499
- });
500
- await Promise.all(removedAccounts);
501
- }
502
- /**
503
- * Removes the account and related tokens for a given account key
504
- * @param account
505
- */
506
- async removeAccount(accountKey) {
507
- const account = this.getAccount(accountKey, this.commonLogger);
508
- if (!account) {
509
- return;
510
- }
511
- await this.removeAccountContext(account);
512
- this.removeItem(accountKey);
513
- }
514
- /**
515
- * Removes credentials associated with the provided account
516
- * @param account
517
- */
518
- async removeAccountContext(account) {
519
- const allTokenKeys = this.getTokenKeys();
520
- const accountId = account.generateAccountId();
521
- const removedCredentials = [];
522
- allTokenKeys.idToken.forEach((key) => {
523
- if (key.indexOf(accountId) === 0) {
524
- this.removeIdToken(key);
525
- }
526
- });
527
- allTokenKeys.accessToken.forEach((key) => {
528
- if (key.indexOf(accountId) === 0) {
529
- removedCredentials.push(this.removeAccessToken(key));
530
- }
531
- });
532
- allTokenKeys.refreshToken.forEach((key) => {
533
- if (key.indexOf(accountId) === 0) {
534
- this.removeRefreshToken(key);
535
- }
536
- });
537
- await Promise.all(removedCredentials);
538
- }
539
- /**
540
- * Migrates a single-tenant account and all it's associated alternate cross-tenant account objects in the
541
- * cache into a condensed multi-tenant account object with tenant profiles.
542
- * @param accountKey
543
- * @param accountEntity
544
- * @param logger
545
- * @returns
546
- */
547
- updateOutdatedCachedAccount(accountKey, accountEntity, logger) {
548
- // Only update if account entity is defined and has no tenantProfiles object (is outdated)
549
- if (accountEntity && accountEntity.isSingleTenant()) {
550
- this.commonLogger?.verbose("updateOutdatedCachedAccount: Found a single-tenant (outdated) account entity in the cache, migrating to multi-tenant account entity");
551
- // Get keys of all accounts belonging to user
552
- const matchingAccountKeys = this.getAccountKeys().filter((key) => {
553
- return key.startsWith(accountEntity.homeAccountId);
554
- });
555
- // Get all account entities belonging to user
556
- const accountsToMerge = [];
557
- matchingAccountKeys.forEach((key) => {
558
- const account = this.getCachedAccountEntity(key);
559
- if (account) {
560
- accountsToMerge.push(account);
561
- }
562
- });
563
- // Set base account to home account if available, any account if not
564
- const baseAccount = accountsToMerge.find((account) => {
565
- return tenantIdMatchesHomeTenant(account.realm, account.homeAccountId);
566
- }) || accountsToMerge[0];
567
- // Populate tenant profiles built from each account entity belonging to the user
568
- baseAccount.tenantProfiles = accountsToMerge.map((account) => {
569
- return {
570
- tenantId: account.realm,
571
- localAccountId: account.localAccountId,
572
- name: account.name,
573
- isHomeTenant: tenantIdMatchesHomeTenant(account.realm, account.homeAccountId),
574
- };
575
- });
576
- const updatedAccount = CacheManager.toObject(new AccountEntity(), {
577
- ...baseAccount,
578
- });
579
- const newAccountKey = updatedAccount.generateAccountKey();
580
- // Clear cache of legacy account objects that have been collpsed into tenant profiles
581
- matchingAccountKeys.forEach((key) => {
582
- if (key !== newAccountKey) {
583
- this.removeOutdatedAccount(accountKey);
584
- }
585
- });
586
- // Cache updated account object
587
- this.setAccount(updatedAccount);
588
- logger?.verbose("Updated an outdated account entity in the cache");
589
- return updatedAccount;
590
- }
591
- // No update is necessary
592
- return accountEntity;
593
- }
594
- /**
595
- * returns a boolean if the given credential is removed
596
- * @param credential
597
- */
598
- async removeAccessToken(key) {
599
- const credential = this.getAccessTokenCredential(key);
600
- if (!credential) {
601
- return;
602
- }
603
- // Remove Token Binding Key from key store for PoP Tokens Credentials
604
- if (credential.credentialType.toLowerCase() ===
605
- CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME.toLowerCase()) {
606
- if (credential.tokenType === AuthenticationScheme.POP) {
607
- const accessTokenWithAuthSchemeEntity = credential;
608
- const kid = accessTokenWithAuthSchemeEntity.keyId;
609
- if (kid) {
610
- try {
611
- await this.cryptoImpl.removeTokenBindingKey(kid);
612
- }
613
- catch (error) {
614
- throw createClientAuthError(bindingKeyNotRemoved);
615
- }
616
- }
617
- }
618
- }
619
- return this.removeItem(key);
620
- }
621
- /**
622
- * Removes all app metadata objects from cache.
623
- */
624
- removeAppMetadata() {
625
- const allCacheKeys = this.getKeys();
626
- allCacheKeys.forEach((cacheKey) => {
627
- if (this.isAppMetadata(cacheKey)) {
628
- this.removeItem(cacheKey);
629
- }
630
- });
631
- return true;
632
- }
633
- /**
634
- * Retrieve AccountEntity from cache
635
- * @param account
636
- */
637
- readAccountFromCache(account) {
638
- const accountKey = AccountEntity.generateAccountCacheKey(account);
639
- return this.getAccount(accountKey, this.commonLogger);
640
- }
641
- /**
642
- * Retrieve IdTokenEntity from cache
643
- * @param account {AccountInfo}
644
- * @param tokenKeys {?TokenKeys}
645
- * @param targetRealm {?string}
646
- * @param performanceClient {?IPerformanceClient}
647
- * @param correlationId {?string}
648
- */
649
- getIdToken(account, tokenKeys, targetRealm, performanceClient, correlationId) {
650
- this.commonLogger.trace("CacheManager - getIdToken called");
651
- const idTokenFilter = {
652
- homeAccountId: account.homeAccountId,
653
- environment: account.environment,
654
- credentialType: CredentialType.ID_TOKEN,
655
- clientId: this.clientId,
656
- realm: targetRealm,
657
- };
658
- const idTokenMap = this.getIdTokensByFilter(idTokenFilter, tokenKeys);
659
- const numIdTokens = idTokenMap.size;
660
- if (numIdTokens < 1) {
661
- this.commonLogger.info("CacheManager:getIdToken - No token found");
662
- return null;
663
- }
664
- else if (numIdTokens > 1) {
665
- let tokensToBeRemoved = idTokenMap;
666
- // Multiple tenant profiles and no tenant specified, pick home account
667
- if (!targetRealm) {
668
- const homeIdTokenMap = new Map();
669
- idTokenMap.forEach((idToken, key) => {
670
- if (idToken.realm === account.tenantId) {
671
- homeIdTokenMap.set(key, idToken);
672
- }
673
- });
674
- const numHomeIdTokens = homeIdTokenMap.size;
675
- if (numHomeIdTokens < 1) {
676
- this.commonLogger.info("CacheManager:getIdToken - Multiple ID tokens found for account but none match account entity tenant id, returning first result");
677
- return idTokenMap.values().next().value;
678
- }
679
- else if (numHomeIdTokens === 1) {
680
- this.commonLogger.info("CacheManager:getIdToken - Multiple ID tokens found for account, defaulting to home tenant profile");
681
- return homeIdTokenMap.values().next().value;
682
- }
683
- else {
684
- // Multiple ID tokens for home tenant profile, remove all and return null
685
- tokensToBeRemoved = homeIdTokenMap;
686
- }
687
- }
688
- // Multiple tokens for a single tenant profile, remove all and return null
689
- this.commonLogger.info("CacheManager:getIdToken - Multiple matching ID tokens found, clearing them");
690
- tokensToBeRemoved.forEach((idToken, key) => {
691
- this.removeIdToken(key);
692
- });
693
- if (performanceClient && correlationId) {
694
- performanceClient.addFields({ multiMatchedID: idTokenMap.size }, correlationId);
695
- }
696
- return null;
697
- }
698
- this.commonLogger.info("CacheManager:getIdToken - Returning ID token");
699
- return idTokenMap.values().next().value;
700
- }
701
- /**
702
- * Gets all idTokens matching the given filter
703
- * @param filter
704
- * @returns
705
- */
706
- getIdTokensByFilter(filter, tokenKeys) {
707
- const idTokenKeys = (tokenKeys && tokenKeys.idToken) || this.getTokenKeys().idToken;
708
- const idTokens = new Map();
709
- idTokenKeys.forEach((key) => {
710
- if (!this.idTokenKeyMatchesFilter(key, {
711
- clientId: this.clientId,
712
- ...filter,
713
- })) {
714
- return;
715
- }
716
- const idToken = this.getIdTokenCredential(key);
717
- if (idToken && this.credentialMatchesFilter(idToken, filter)) {
718
- idTokens.set(key, idToken);
719
- }
720
- });
721
- return idTokens;
722
- }
723
- /**
724
- * Validate the cache key against filter before retrieving and parsing cache value
725
- * @param key
726
- * @param filter
727
- * @returns
728
- */
729
- idTokenKeyMatchesFilter(inputKey, filter) {
730
- const key = inputKey.toLowerCase();
731
- if (filter.clientId &&
732
- key.indexOf(filter.clientId.toLowerCase()) === -1) {
733
- return false;
734
- }
735
- if (filter.homeAccountId &&
736
- key.indexOf(filter.homeAccountId.toLowerCase()) === -1) {
737
- return false;
738
- }
739
- return true;
740
- }
741
- /**
742
- * Removes idToken from the cache
743
- * @param key
744
- */
745
- removeIdToken(key) {
746
- this.removeItem(key);
747
- }
748
- /**
749
- * Removes refresh token from the cache
750
- * @param key
751
- */
752
- removeRefreshToken(key) {
753
- this.removeItem(key);
754
- }
755
- /**
756
- * Retrieve AccessTokenEntity from cache
757
- * @param account {AccountInfo}
758
- * @param request {BaseAuthRequest}
759
- * @param tokenKeys {?TokenKeys}
760
- * @param performanceClient {?IPerformanceClient}
761
- * @param correlationId {?string}
762
- */
763
- getAccessToken(account, request, tokenKeys, targetRealm, performanceClient, correlationId) {
764
- this.commonLogger.trace("CacheManager - getAccessToken called");
765
- const scopes = ScopeSet.createSearchScopes(request.scopes);
766
- const authScheme = request.authenticationScheme || AuthenticationScheme.BEARER;
767
- /*
768
- * Distinguish between Bearer and PoP/SSH token cache types
769
- * Cast to lowercase to handle "bearer" from ADFS
770
- */
771
- const credentialType = authScheme &&
772
- authScheme.toLowerCase() !==
773
- AuthenticationScheme.BEARER.toLowerCase()
774
- ? CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME
775
- : CredentialType.ACCESS_TOKEN;
776
- const accessTokenFilter = {
777
- homeAccountId: account.homeAccountId,
778
- environment: account.environment,
779
- credentialType: credentialType,
780
- clientId: this.clientId,
781
- realm: targetRealm || account.tenantId,
782
- target: scopes,
783
- tokenType: authScheme,
784
- keyId: request.sshKid,
785
- requestedClaimsHash: request.requestedClaimsHash,
786
- };
787
- const accessTokenKeys = (tokenKeys && tokenKeys.accessToken) ||
788
- this.getTokenKeys().accessToken;
789
- const accessTokens = [];
790
- accessTokenKeys.forEach((key) => {
791
- // Validate key
792
- if (this.accessTokenKeyMatchesFilter(key, accessTokenFilter, true)) {
793
- const accessToken = this.getAccessTokenCredential(key);
794
- // Validate value
795
- if (accessToken &&
796
- this.credentialMatchesFilter(accessToken, accessTokenFilter)) {
797
- accessTokens.push(accessToken);
798
- }
799
- }
800
- });
801
- const numAccessTokens = accessTokens.length;
802
- if (numAccessTokens < 1) {
803
- this.commonLogger.info("CacheManager:getAccessToken - No token found");
804
- return null;
805
- }
806
- else if (numAccessTokens > 1) {
807
- this.commonLogger.info("CacheManager:getAccessToken - Multiple access tokens found, clearing them");
808
- accessTokens.forEach((accessToken) => {
809
- void this.removeAccessToken(generateCredentialKey(accessToken));
810
- });
811
- if (performanceClient && correlationId) {
812
- performanceClient.addFields({ multiMatchedAT: accessTokens.length }, correlationId);
813
- }
814
- return null;
815
- }
816
- this.commonLogger.info("CacheManager:getAccessToken - Returning access token");
817
- return accessTokens[0];
818
- }
819
- /**
820
- * Validate the cache key against filter before retrieving and parsing cache value
821
- * @param key
822
- * @param filter
823
- * @param keyMustContainAllScopes
824
- * @returns
825
- */
826
- accessTokenKeyMatchesFilter(inputKey, filter, keyMustContainAllScopes) {
827
- const key = inputKey.toLowerCase();
828
- if (filter.clientId &&
829
- key.indexOf(filter.clientId.toLowerCase()) === -1) {
830
- return false;
831
- }
832
- if (filter.homeAccountId &&
833
- key.indexOf(filter.homeAccountId.toLowerCase()) === -1) {
834
- return false;
835
- }
836
- if (filter.realm && key.indexOf(filter.realm.toLowerCase()) === -1) {
837
- return false;
838
- }
839
- if (filter.requestedClaimsHash &&
840
- key.indexOf(filter.requestedClaimsHash.toLowerCase()) === -1) {
841
- return false;
842
- }
843
- if (filter.target) {
844
- const scopes = filter.target.asArray();
845
- for (let i = 0; i < scopes.length; i++) {
846
- if (keyMustContainAllScopes &&
847
- !key.includes(scopes[i].toLowerCase())) {
848
- // When performing a cache lookup a missing scope would be a cache miss
849
- return false;
850
- }
851
- else if (!keyMustContainAllScopes &&
852
- key.includes(scopes[i].toLowerCase())) {
853
- // When performing a cache write, any token with a subset of requested scopes should be replaced
854
- return true;
855
- }
856
- }
857
- }
858
- return true;
859
- }
860
- /**
861
- * Gets all access tokens matching the filter
862
- * @param filter
863
- * @returns
864
- */
865
- getAccessTokensByFilter(filter) {
866
- const tokenKeys = this.getTokenKeys();
867
- const accessTokens = [];
868
- tokenKeys.accessToken.forEach((key) => {
869
- if (!this.accessTokenKeyMatchesFilter(key, filter, true)) {
870
- return;
871
- }
872
- const accessToken = this.getAccessTokenCredential(key);
873
- if (accessToken &&
874
- this.credentialMatchesFilter(accessToken, filter)) {
875
- accessTokens.push(accessToken);
876
- }
877
- });
878
- return accessTokens;
879
- }
880
- /**
881
- * Helper to retrieve the appropriate refresh token from cache
882
- * @param account {AccountInfo}
883
- * @param familyRT {boolean}
884
- * @param tokenKeys {?TokenKeys}
885
- * @param performanceClient {?IPerformanceClient}
886
- * @param correlationId {?string}
887
- */
888
- getRefreshToken(account, familyRT, tokenKeys, performanceClient, correlationId) {
889
- this.commonLogger.trace("CacheManager - getRefreshToken called");
890
- const id = familyRT ? THE_FAMILY_ID : undefined;
891
- const refreshTokenFilter = {
892
- homeAccountId: account.homeAccountId,
893
- environment: account.environment,
894
- credentialType: CredentialType.REFRESH_TOKEN,
895
- clientId: this.clientId,
896
- familyId: id,
897
- };
898
- const refreshTokenKeys = (tokenKeys && tokenKeys.refreshToken) ||
899
- this.getTokenKeys().refreshToken;
900
- const refreshTokens = [];
901
- refreshTokenKeys.forEach((key) => {
902
- // Validate key
903
- if (this.refreshTokenKeyMatchesFilter(key, refreshTokenFilter)) {
904
- const refreshToken = this.getRefreshTokenCredential(key);
905
- // Validate value
906
- if (refreshToken &&
907
- this.credentialMatchesFilter(refreshToken, refreshTokenFilter)) {
908
- refreshTokens.push(refreshToken);
909
- }
910
- }
911
- });
912
- const numRefreshTokens = refreshTokens.length;
913
- if (numRefreshTokens < 1) {
914
- this.commonLogger.info("CacheManager:getRefreshToken - No refresh token found.");
915
- return null;
916
- }
917
- // address the else case after remove functions address environment aliases
918
- if (numRefreshTokens > 1 && performanceClient && correlationId) {
919
- performanceClient.addFields({ multiMatchedRT: numRefreshTokens }, correlationId);
920
- }
921
- this.commonLogger.info("CacheManager:getRefreshToken - returning refresh token");
922
- return refreshTokens[0];
923
- }
924
- /**
925
- * Validate the cache key against filter before retrieving and parsing cache value
926
- * @param key
927
- * @param filter
928
- */
929
- refreshTokenKeyMatchesFilter(inputKey, filter) {
930
- const key = inputKey.toLowerCase();
931
- if (filter.familyId &&
932
- key.indexOf(filter.familyId.toLowerCase()) === -1) {
933
- return false;
934
- }
935
- // If familyId is used, clientId is not in the key
936
- if (!filter.familyId &&
937
- filter.clientId &&
938
- key.indexOf(filter.clientId.toLowerCase()) === -1) {
939
- return false;
940
- }
941
- if (filter.homeAccountId &&
942
- key.indexOf(filter.homeAccountId.toLowerCase()) === -1) {
943
- return false;
944
- }
945
- return true;
946
- }
947
- /**
948
- * Retrieve AppMetadataEntity from cache
949
- */
950
- readAppMetadataFromCache(environment) {
951
- const appMetadataFilter = {
952
- environment,
953
- clientId: this.clientId,
954
- };
955
- const appMetadata = this.getAppMetadataFilteredBy(appMetadataFilter);
956
- const appMetadataEntries = Object.keys(appMetadata).map((key) => appMetadata[key]);
957
- const numAppMetadata = appMetadataEntries.length;
958
- if (numAppMetadata < 1) {
959
- return null;
960
- }
961
- else if (numAppMetadata > 1) {
962
- throw createClientAuthError(multipleMatchingAppMetadata);
963
- }
964
- return appMetadataEntries[0];
965
- }
966
- /**
967
- * Return the family_id value associated with FOCI
968
- * @param environment
969
- * @param clientId
970
- */
971
- isAppMetadataFOCI(environment) {
972
- const appMetadata = this.readAppMetadataFromCache(environment);
973
- return !!(appMetadata && appMetadata.familyId === THE_FAMILY_ID);
974
- }
975
- /**
976
- * helper to match account ids
977
- * @param value
978
- * @param homeAccountId
979
- */
980
- matchHomeAccountId(entity, homeAccountId) {
981
- return !!(typeof entity.homeAccountId === "string" &&
982
- homeAccountId === entity.homeAccountId);
983
- }
984
- /**
985
- * helper to match account ids
986
- * @param entity
987
- * @param localAccountId
988
- * @returns
989
- */
990
- matchLocalAccountIdFromTokenClaims(tokenClaims, localAccountId) {
991
- const idTokenLocalAccountId = tokenClaims.oid || tokenClaims.sub;
992
- return localAccountId === idTokenLocalAccountId;
993
- }
994
- matchLocalAccountIdFromTenantProfile(tenantProfile, localAccountId) {
995
- return tenantProfile.localAccountId === localAccountId;
996
- }
997
- /**
998
- * helper to match names
999
- * @param entity
1000
- * @param name
1001
- * @returns true if the downcased name properties are present and match in the filter and the entity
1002
- */
1003
- matchName(claims, name) {
1004
- return !!(name.toLowerCase() === claims.name?.toLowerCase());
1005
- }
1006
- /**
1007
- * helper to match usernames
1008
- * @param entity
1009
- * @param username
1010
- * @returns
1011
- */
1012
- matchUsername(cachedUsername, filterUsername) {
1013
- return !!(cachedUsername &&
1014
- typeof cachedUsername === "string" &&
1015
- filterUsername?.toLowerCase() === cachedUsername.toLowerCase());
1016
- }
1017
- /**
1018
- * helper to match assertion
1019
- * @param value
1020
- * @param oboAssertion
1021
- */
1022
- matchUserAssertionHash(entity, userAssertionHash) {
1023
- return !!(entity.userAssertionHash &&
1024
- userAssertionHash === entity.userAssertionHash);
1025
- }
1026
- /**
1027
- * helper to match environment
1028
- * @param value
1029
- * @param environment
1030
- */
1031
- matchEnvironment(entity, environment) {
1032
- // Check static authority options first for cases where authority metadata has not been resolved and cached yet
1033
- if (this.staticAuthorityOptions) {
1034
- const staticAliases = getAliasesFromStaticSources(this.staticAuthorityOptions, this.commonLogger);
1035
- if (staticAliases.includes(environment) &&
1036
- staticAliases.includes(entity.environment)) {
1037
- return true;
1038
- }
1039
- }
1040
- // Query metadata cache if no static authority configuration has aliases that match enviroment
1041
- const cloudMetadata = this.getAuthorityMetadataByAlias(environment);
1042
- if (cloudMetadata &&
1043
- cloudMetadata.aliases.indexOf(entity.environment) > -1) {
1044
- return true;
1045
- }
1046
- return false;
1047
- }
1048
- /**
1049
- * helper to match credential type
1050
- * @param entity
1051
- * @param credentialType
1052
- */
1053
- matchCredentialType(entity, credentialType) {
1054
- return (entity.credentialType &&
1055
- credentialType.toLowerCase() === entity.credentialType.toLowerCase());
1056
- }
1057
- /**
1058
- * helper to match client ids
1059
- * @param entity
1060
- * @param clientId
1061
- */
1062
- matchClientId(entity, clientId) {
1063
- return !!(entity.clientId && clientId === entity.clientId);
1064
- }
1065
- /**
1066
- * helper to match family ids
1067
- * @param entity
1068
- * @param familyId
1069
- */
1070
- matchFamilyId(entity, familyId) {
1071
- return !!(entity.familyId && familyId === entity.familyId);
1072
- }
1073
- /**
1074
- * helper to match realm
1075
- * @param entity
1076
- * @param realm
1077
- */
1078
- matchRealm(entity, realm) {
1079
- return !!(entity.realm?.toLowerCase() === realm.toLowerCase());
1080
- }
1081
- /**
1082
- * helper to match nativeAccountId
1083
- * @param entity
1084
- * @param nativeAccountId
1085
- * @returns boolean indicating the match result
1086
- */
1087
- matchNativeAccountId(entity, nativeAccountId) {
1088
- return !!(entity.nativeAccountId && nativeAccountId === entity.nativeAccountId);
1089
- }
1090
- /**
1091
- * helper to match loginHint which can be either:
1092
- * 1. login_hint ID token claim
1093
- * 2. username in cached account object
1094
- * 3. upn in ID token claims
1095
- * @param entity
1096
- * @param loginHint
1097
- * @returns
1098
- */
1099
- matchLoginHintFromTokenClaims(tokenClaims, loginHint) {
1100
- if (tokenClaims.login_hint === loginHint) {
1101
- return true;
1102
- }
1103
- if (tokenClaims.preferred_username === loginHint) {
1104
- return true;
1105
- }
1106
- if (tokenClaims.upn === loginHint) {
1107
- return true;
1108
- }
1109
- return false;
1110
- }
1111
- /**
1112
- * Helper to match sid
1113
- * @param entity
1114
- * @param sid
1115
- * @returns true if the sid claim is present and matches the filter
1116
- */
1117
- matchSid(idTokenClaims, sid) {
1118
- return idTokenClaims.sid === sid;
1119
- }
1120
- matchAuthorityType(entity, authorityType) {
1121
- return !!(entity.authorityType &&
1122
- authorityType.toLowerCase() === entity.authorityType.toLowerCase());
1123
- }
1124
- /**
1125
- * Returns true if the target scopes are a subset of the current entity's scopes, false otherwise.
1126
- * @param entity
1127
- * @param target
1128
- */
1129
- matchTarget(entity, target) {
1130
- const isNotAccessTokenCredential = entity.credentialType !== CredentialType.ACCESS_TOKEN &&
1131
- entity.credentialType !==
1132
- CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME;
1133
- if (isNotAccessTokenCredential || !entity.target) {
1134
- return false;
1135
- }
1136
- const entityScopeSet = ScopeSet.fromString(entity.target);
1137
- return entityScopeSet.containsScopeSet(target);
1138
- }
1139
- /**
1140
- * Returns true if the credential's tokenType or Authentication Scheme matches the one in the request, false otherwise
1141
- * @param entity
1142
- * @param tokenType
1143
- */
1144
- matchTokenType(entity, tokenType) {
1145
- return !!(entity.tokenType && entity.tokenType === tokenType);
1146
- }
1147
- /**
1148
- * Returns true if the credential's keyId matches the one in the request, false otherwise
1149
- * @param entity
1150
- * @param keyId
1151
- */
1152
- matchKeyId(entity, keyId) {
1153
- return !!(entity.keyId && entity.keyId === keyId);
1154
- }
1155
- /**
1156
- * returns if a given cache entity is of the type appmetadata
1157
- * @param key
1158
- */
1159
- isAppMetadata(key) {
1160
- return key.indexOf(APP_METADATA) !== -1;
1161
- }
1162
- /**
1163
- * returns if a given cache entity is of the type authoritymetadata
1164
- * @param key
1165
- */
1166
- isAuthorityMetadata(key) {
1167
- return key.indexOf(AUTHORITY_METADATA_CONSTANTS.CACHE_KEY) !== -1;
1168
- }
1169
- /**
1170
- * returns cache key used for cloud instance metadata
1171
- */
1172
- generateAuthorityMetadataCacheKey(authority) {
1173
- return `${AUTHORITY_METADATA_CONSTANTS.CACHE_KEY}-${this.clientId}-${authority}`;
1174
- }
1175
- /**
1176
- * Helper to convert serialized data to object
1177
- * @param obj
1178
- * @param json
1179
- */
1180
- static toObject(obj, json) {
1181
- for (const propertyName in json) {
1182
- obj[propertyName] = json[propertyName];
1183
- }
1184
- return obj;
1185
- }
1186
- }
1187
- /** @internal */
1188
- class DefaultStorageClass extends CacheManager {
1189
- setAccount() {
1190
- throw createClientAuthError(methodNotImplemented);
1191
- }
1192
- getAccount() {
1193
- throw createClientAuthError(methodNotImplemented);
1194
- }
1195
- getCachedAccountEntity() {
1196
- throw createClientAuthError(methodNotImplemented);
1197
- }
1198
- setIdTokenCredential() {
1199
- throw createClientAuthError(methodNotImplemented);
1200
- }
1201
- getIdTokenCredential() {
1202
- throw createClientAuthError(methodNotImplemented);
1203
- }
1204
- setAccessTokenCredential() {
1205
- throw createClientAuthError(methodNotImplemented);
1206
- }
1207
- getAccessTokenCredential() {
1208
- throw createClientAuthError(methodNotImplemented);
1209
- }
1210
- setRefreshTokenCredential() {
1211
- throw createClientAuthError(methodNotImplemented);
1212
- }
1213
- getRefreshTokenCredential() {
1214
- throw createClientAuthError(methodNotImplemented);
1215
- }
1216
- setAppMetadata() {
1217
- throw createClientAuthError(methodNotImplemented);
1218
- }
1219
- getAppMetadata() {
1220
- throw createClientAuthError(methodNotImplemented);
1221
- }
1222
- setServerTelemetry() {
1223
- throw createClientAuthError(methodNotImplemented);
1224
- }
1225
- getServerTelemetry() {
1226
- throw createClientAuthError(methodNotImplemented);
1227
- }
1228
- setAuthorityMetadata() {
1229
- throw createClientAuthError(methodNotImplemented);
1230
- }
1231
- getAuthorityMetadata() {
1232
- throw createClientAuthError(methodNotImplemented);
1233
- }
1234
- getAuthorityMetadataKeys() {
1235
- throw createClientAuthError(methodNotImplemented);
1236
- }
1237
- setThrottlingCache() {
1238
- throw createClientAuthError(methodNotImplemented);
1239
- }
1240
- getThrottlingCache() {
1241
- throw createClientAuthError(methodNotImplemented);
1242
- }
1243
- removeItem() {
1244
- throw createClientAuthError(methodNotImplemented);
1245
- }
1246
- getKeys() {
1247
- throw createClientAuthError(methodNotImplemented);
1248
- }
1249
- getAccountKeys() {
1250
- throw createClientAuthError(methodNotImplemented);
1251
- }
1252
- getTokenKeys() {
1253
- throw createClientAuthError(methodNotImplemented);
1254
- }
1255
- async clear() {
1256
- throw createClientAuthError(methodNotImplemented);
1257
- }
1258
- updateCredentialCacheKey() {
1259
- throw createClientAuthError(methodNotImplemented);
1260
- }
1261
- removeOutdatedAccount() {
1262
- throw createClientAuthError(methodNotImplemented);
1263
- }
16
+ /*
17
+ * Copyright (c) Microsoft Corporation. All rights reserved.
18
+ * Licensed under the MIT License.
19
+ */
20
+ /**
21
+ * Interface class which implement cache storage functions used by MSAL to perform validity checks, and store tokens.
22
+ * @internal
23
+ */
24
+ class CacheManager {
25
+ constructor(clientId, cryptoImpl, logger, staticAuthorityOptions) {
26
+ this.clientId = clientId;
27
+ this.cryptoImpl = cryptoImpl;
28
+ this.commonLogger = logger.clone(name, version);
29
+ this.staticAuthorityOptions = staticAuthorityOptions;
30
+ }
31
+ /**
32
+ * Returns all the accounts in the cache that match the optional filter. If no filter is provided, all accounts are returned.
33
+ * @param accountFilter - (Optional) filter to narrow down the accounts returned
34
+ * @returns Array of AccountInfo objects in cache
35
+ */
36
+ getAllAccounts(accountFilter) {
37
+ return this.buildTenantProfiles(this.getAccountsFilteredBy(accountFilter || {}), accountFilter);
38
+ }
39
+ /**
40
+ * Gets first tenanted AccountInfo object found based on provided filters
41
+ */
42
+ getAccountInfoFilteredBy(accountFilter) {
43
+ const allAccounts = this.getAllAccounts(accountFilter);
44
+ if (allAccounts.length > 1) {
45
+ // If one or more accounts are found, prioritize accounts that have an ID token
46
+ const sortedAccounts = allAccounts.sort((account) => {
47
+ return account.idTokenClaims ? -1 : 1;
48
+ });
49
+ return sortedAccounts[0];
50
+ }
51
+ else if (allAccounts.length === 1) {
52
+ // If only one account is found, return it regardless of whether a matching ID token was found
53
+ return allAccounts[0];
54
+ }
55
+ else {
56
+ return null;
57
+ }
58
+ }
59
+ /**
60
+ * Returns a single matching
61
+ * @param accountFilter
62
+ * @returns
63
+ */
64
+ getBaseAccountInfo(accountFilter) {
65
+ const accountEntities = this.getAccountsFilteredBy(accountFilter);
66
+ if (accountEntities.length > 0) {
67
+ return accountEntities[0].getAccountInfo();
68
+ }
69
+ else {
70
+ return null;
71
+ }
72
+ }
73
+ /**
74
+ * Matches filtered account entities with cached ID tokens that match the tenant profile-specific account filters
75
+ * and builds the account info objects from the matching ID token's claims
76
+ * @param cachedAccounts
77
+ * @param accountFilter
78
+ * @returns Array of AccountInfo objects that match account and tenant profile filters
79
+ */
80
+ buildTenantProfiles(cachedAccounts, accountFilter) {
81
+ return cachedAccounts.flatMap((accountEntity) => {
82
+ return this.getTenantProfilesFromAccountEntity(accountEntity, accountFilter?.tenantId, accountFilter);
83
+ });
84
+ }
85
+ getTenantedAccountInfoByFilter(accountInfo, tokenKeys, tenantProfile, tenantProfileFilter) {
86
+ let tenantedAccountInfo = null;
87
+ let idTokenClaims;
88
+ if (tenantProfileFilter) {
89
+ if (!this.tenantProfileMatchesFilter(tenantProfile, tenantProfileFilter)) {
90
+ return null;
91
+ }
92
+ }
93
+ const idToken = this.getIdToken(accountInfo, tokenKeys, tenantProfile.tenantId);
94
+ if (idToken) {
95
+ idTokenClaims = extractTokenClaims(idToken.secret, this.cryptoImpl.base64Decode);
96
+ if (!this.idTokenClaimsMatchTenantProfileFilter(idTokenClaims, tenantProfileFilter)) {
97
+ // ID token sourced claims don't match so this tenant profile is not a match
98
+ return null;
99
+ }
100
+ }
101
+ // Expand tenant profile into account info based on matching tenant profile and if available matching ID token claims
102
+ tenantedAccountInfo = updateAccountTenantProfileData(accountInfo, tenantProfile, idTokenClaims, idToken?.secret);
103
+ return tenantedAccountInfo;
104
+ }
105
+ getTenantProfilesFromAccountEntity(accountEntity, targetTenantId, tenantProfileFilter) {
106
+ const accountInfo = accountEntity.getAccountInfo();
107
+ let searchTenantProfiles = accountInfo.tenantProfiles || new Map();
108
+ const tokenKeys = this.getTokenKeys();
109
+ // If a tenant ID was provided, only return the tenant profile for that tenant ID if it exists
110
+ if (targetTenantId) {
111
+ const tenantProfile = searchTenantProfiles.get(targetTenantId);
112
+ if (tenantProfile) {
113
+ // Reduce search field to just this tenant profile
114
+ searchTenantProfiles = new Map([
115
+ [targetTenantId, tenantProfile],
116
+ ]);
117
+ }
118
+ else {
119
+ // No tenant profile for search tenant ID, return empty array
120
+ return [];
121
+ }
122
+ }
123
+ const matchingTenantProfiles = [];
124
+ searchTenantProfiles.forEach((tenantProfile) => {
125
+ const tenantedAccountInfo = this.getTenantedAccountInfoByFilter(accountInfo, tokenKeys, tenantProfile, tenantProfileFilter);
126
+ if (tenantedAccountInfo) {
127
+ matchingTenantProfiles.push(tenantedAccountInfo);
128
+ }
129
+ });
130
+ return matchingTenantProfiles;
131
+ }
132
+ tenantProfileMatchesFilter(tenantProfile, tenantProfileFilter) {
133
+ if (!!tenantProfileFilter.localAccountId &&
134
+ !this.matchLocalAccountIdFromTenantProfile(tenantProfile, tenantProfileFilter.localAccountId)) {
135
+ return false;
136
+ }
137
+ if (!!tenantProfileFilter.name &&
138
+ !(tenantProfile.name === tenantProfileFilter.name)) {
139
+ return false;
140
+ }
141
+ if (tenantProfileFilter.isHomeTenant !== undefined &&
142
+ !(tenantProfile.isHomeTenant === tenantProfileFilter.isHomeTenant)) {
143
+ return false;
144
+ }
145
+ return true;
146
+ }
147
+ idTokenClaimsMatchTenantProfileFilter(idTokenClaims, tenantProfileFilter) {
148
+ // Tenant Profile filtering
149
+ if (tenantProfileFilter) {
150
+ if (!!tenantProfileFilter.localAccountId &&
151
+ !this.matchLocalAccountIdFromTokenClaims(idTokenClaims, tenantProfileFilter.localAccountId)) {
152
+ return false;
153
+ }
154
+ if (!!tenantProfileFilter.loginHint &&
155
+ !this.matchLoginHintFromTokenClaims(idTokenClaims, tenantProfileFilter.loginHint)) {
156
+ return false;
157
+ }
158
+ if (!!tenantProfileFilter.username &&
159
+ !this.matchUsername(idTokenClaims.preferred_username, tenantProfileFilter.username)) {
160
+ return false;
161
+ }
162
+ if (!!tenantProfileFilter.name &&
163
+ !this.matchName(idTokenClaims, tenantProfileFilter.name)) {
164
+ return false;
165
+ }
166
+ if (!!tenantProfileFilter.sid &&
167
+ !this.matchSid(idTokenClaims, tenantProfileFilter.sid)) {
168
+ return false;
169
+ }
170
+ }
171
+ return true;
172
+ }
173
+ /**
174
+ * saves a cache record
175
+ * @param cacheRecord {CacheRecord}
176
+ * @param storeInCache {?StoreInCache}
177
+ * @param correlationId {?string} correlation id
178
+ */
179
+ async saveCacheRecord(cacheRecord, storeInCache, correlationId) {
180
+ if (!cacheRecord) {
181
+ throw createClientAuthError(invalidCacheRecord);
182
+ }
183
+ try {
184
+ if (!!cacheRecord.account) {
185
+ this.setAccount(cacheRecord.account);
186
+ }
187
+ if (!!cacheRecord.idToken && storeInCache?.idToken !== false) {
188
+ this.setIdTokenCredential(cacheRecord.idToken);
189
+ }
190
+ if (!!cacheRecord.accessToken &&
191
+ storeInCache?.accessToken !== false) {
192
+ await this.saveAccessToken(cacheRecord.accessToken);
193
+ }
194
+ if (!!cacheRecord.refreshToken &&
195
+ storeInCache?.refreshToken !== false) {
196
+ this.setRefreshTokenCredential(cacheRecord.refreshToken);
197
+ }
198
+ if (!!cacheRecord.appMetadata) {
199
+ this.setAppMetadata(cacheRecord.appMetadata);
200
+ }
201
+ }
202
+ catch (e) {
203
+ this.commonLogger?.error(`CacheManager.saveCacheRecord: failed`);
204
+ if (e instanceof Error) {
205
+ this.commonLogger?.errorPii(`CacheManager.saveCacheRecord: ${e.message}`, correlationId);
206
+ if (e.name === "QuotaExceededError" ||
207
+ e.name === "NS_ERROR_DOM_QUOTA_REACHED" ||
208
+ e.message.includes("exceeded the quota")) {
209
+ this.commonLogger?.error(`CacheManager.saveCacheRecord: exceeded storage quota`, correlationId);
210
+ throw new CacheError(cacheQuotaExceededErrorCode);
211
+ }
212
+ else {
213
+ throw new CacheError(e.name, e.message);
214
+ }
215
+ }
216
+ else {
217
+ this.commonLogger?.errorPii(`CacheManager.saveCacheRecord: ${e}`, correlationId);
218
+ throw new CacheError(cacheUnknownErrorCode);
219
+ }
220
+ }
221
+ }
222
+ /**
223
+ * saves access token credential
224
+ * @param credential
225
+ */
226
+ async saveAccessToken(credential) {
227
+ const accessTokenFilter = {
228
+ clientId: credential.clientId,
229
+ credentialType: credential.credentialType,
230
+ environment: credential.environment,
231
+ homeAccountId: credential.homeAccountId,
232
+ realm: credential.realm,
233
+ tokenType: credential.tokenType,
234
+ requestedClaimsHash: credential.requestedClaimsHash,
235
+ };
236
+ const tokenKeys = this.getTokenKeys();
237
+ const currentScopes = ScopeSet.fromString(credential.target);
238
+ const removedAccessTokens = [];
239
+ tokenKeys.accessToken.forEach((key) => {
240
+ if (!this.accessTokenKeyMatchesFilter(key, accessTokenFilter, false)) {
241
+ return;
242
+ }
243
+ const tokenEntity = this.getAccessTokenCredential(key);
244
+ if (tokenEntity &&
245
+ this.credentialMatchesFilter(tokenEntity, accessTokenFilter)) {
246
+ const tokenScopeSet = ScopeSet.fromString(tokenEntity.target);
247
+ if (tokenScopeSet.intersectingScopeSets(currentScopes)) {
248
+ removedAccessTokens.push(this.removeAccessToken(key));
249
+ }
250
+ }
251
+ });
252
+ await Promise.all(removedAccessTokens);
253
+ this.setAccessTokenCredential(credential);
254
+ }
255
+ /**
256
+ * Retrieve account entities matching all provided tenant-agnostic filters; if no filter is set, get all account entities in the cache
257
+ * Not checking for casing as keys are all generated in lower case, remember to convert to lower case if object properties are compared
258
+ * @param accountFilter - An object containing Account properties to filter by
259
+ */
260
+ getAccountsFilteredBy(accountFilter) {
261
+ const allAccountKeys = this.getAccountKeys();
262
+ const matchingAccounts = [];
263
+ allAccountKeys.forEach((cacheKey) => {
264
+ if (!this.isAccountKey(cacheKey, accountFilter.homeAccountId)) {
265
+ // Don't parse value if the key doesn't match the account filters
266
+ return;
267
+ }
268
+ const entity = this.getAccount(cacheKey, this.commonLogger);
269
+ // Match base account fields
270
+ if (!entity) {
271
+ return;
272
+ }
273
+ if (!!accountFilter.homeAccountId &&
274
+ !this.matchHomeAccountId(entity, accountFilter.homeAccountId)) {
275
+ return;
276
+ }
277
+ if (!!accountFilter.username &&
278
+ !this.matchUsername(entity.username, accountFilter.username)) {
279
+ return;
280
+ }
281
+ if (!!accountFilter.environment &&
282
+ !this.matchEnvironment(entity, accountFilter.environment)) {
283
+ return;
284
+ }
285
+ if (!!accountFilter.realm &&
286
+ !this.matchRealm(entity, accountFilter.realm)) {
287
+ return;
288
+ }
289
+ if (!!accountFilter.nativeAccountId &&
290
+ !this.matchNativeAccountId(entity, accountFilter.nativeAccountId)) {
291
+ return;
292
+ }
293
+ if (!!accountFilter.authorityType &&
294
+ !this.matchAuthorityType(entity, accountFilter.authorityType)) {
295
+ return;
296
+ }
297
+ // If at least one tenant profile matches the tenant profile filter, add the account to the list of matching accounts
298
+ const tenantProfileFilter = {
299
+ localAccountId: accountFilter?.localAccountId,
300
+ name: accountFilter?.name,
301
+ };
302
+ const matchingTenantProfiles = entity.tenantProfiles?.filter((tenantProfile) => {
303
+ return this.tenantProfileMatchesFilter(tenantProfile, tenantProfileFilter);
304
+ });
305
+ if (matchingTenantProfiles && matchingTenantProfiles.length === 0) {
306
+ // No tenant profile for this account matches filter, don't add to list of matching accounts
307
+ return;
308
+ }
309
+ matchingAccounts.push(entity);
310
+ });
311
+ return matchingAccounts;
312
+ }
313
+ /**
314
+ * Returns true if the given key matches our account key schema. Also matches homeAccountId and/or tenantId if provided
315
+ * @param key
316
+ * @param homeAccountId
317
+ * @param tenantId
318
+ * @returns
319
+ */
320
+ isAccountKey(key, homeAccountId, tenantId) {
321
+ if (key.split(Separators.CACHE_KEY_SEPARATOR).length < 3) {
322
+ // Account cache keys contain 3 items separated by '-' (each item may also contain '-')
323
+ return false;
324
+ }
325
+ if (homeAccountId &&
326
+ !key.toLowerCase().includes(homeAccountId.toLowerCase())) {
327
+ return false;
328
+ }
329
+ if (tenantId && !key.toLowerCase().includes(tenantId.toLowerCase())) {
330
+ return false;
331
+ }
332
+ // Do not check environment as aliasing can cause false negatives
333
+ return true;
334
+ }
335
+ /**
336
+ * Returns true if the given key matches our credential key schema.
337
+ * @param key
338
+ */
339
+ isCredentialKey(key) {
340
+ if (key.split(Separators.CACHE_KEY_SEPARATOR).length < 6) {
341
+ // Credential cache keys contain 6 items separated by '-' (each item may also contain '-')
342
+ return false;
343
+ }
344
+ const lowerCaseKey = key.toLowerCase();
345
+ // Credential keys must indicate what credential type they represent
346
+ if (lowerCaseKey.indexOf(CredentialType.ID_TOKEN.toLowerCase()) ===
347
+ -1 &&
348
+ lowerCaseKey.indexOf(CredentialType.ACCESS_TOKEN.toLowerCase()) ===
349
+ -1 &&
350
+ lowerCaseKey.indexOf(CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME.toLowerCase()) === -1 &&
351
+ lowerCaseKey.indexOf(CredentialType.REFRESH_TOKEN.toLowerCase()) ===
352
+ -1) {
353
+ return false;
354
+ }
355
+ if (lowerCaseKey.indexOf(CredentialType.REFRESH_TOKEN.toLowerCase()) >
356
+ -1) {
357
+ // Refresh tokens must contain the client id or family id
358
+ const clientIdValidation = `${CredentialType.REFRESH_TOKEN}${Separators.CACHE_KEY_SEPARATOR}${this.clientId}${Separators.CACHE_KEY_SEPARATOR}`;
359
+ const familyIdValidation = `${CredentialType.REFRESH_TOKEN}${Separators.CACHE_KEY_SEPARATOR}${THE_FAMILY_ID}${Separators.CACHE_KEY_SEPARATOR}`;
360
+ if (lowerCaseKey.indexOf(clientIdValidation.toLowerCase()) === -1 &&
361
+ lowerCaseKey.indexOf(familyIdValidation.toLowerCase()) === -1) {
362
+ return false;
363
+ }
364
+ }
365
+ else if (lowerCaseKey.indexOf(this.clientId.toLowerCase()) === -1) {
366
+ // Tokens must contain the clientId
367
+ return false;
368
+ }
369
+ return true;
370
+ }
371
+ /**
372
+ * Returns whether or not the given credential entity matches the filter
373
+ * @param entity
374
+ * @param filter
375
+ * @returns
376
+ */
377
+ credentialMatchesFilter(entity, filter) {
378
+ if (!!filter.clientId && !this.matchClientId(entity, filter.clientId)) {
379
+ return false;
380
+ }
381
+ if (!!filter.userAssertionHash &&
382
+ !this.matchUserAssertionHash(entity, filter.userAssertionHash)) {
383
+ return false;
384
+ }
385
+ /*
386
+ * homeAccountId can be undefined, and we want to filter out cached items that have a homeAccountId of ""
387
+ * because we don't want a client_credential request to return a cached token that has a homeAccountId
388
+ */
389
+ if (typeof filter.homeAccountId === "string" &&
390
+ !this.matchHomeAccountId(entity, filter.homeAccountId)) {
391
+ return false;
392
+ }
393
+ if (!!filter.environment &&
394
+ !this.matchEnvironment(entity, filter.environment)) {
395
+ return false;
396
+ }
397
+ if (!!filter.realm && !this.matchRealm(entity, filter.realm)) {
398
+ return false;
399
+ }
400
+ if (!!filter.credentialType &&
401
+ !this.matchCredentialType(entity, filter.credentialType)) {
402
+ return false;
403
+ }
404
+ if (!!filter.familyId && !this.matchFamilyId(entity, filter.familyId)) {
405
+ return false;
406
+ }
407
+ /*
408
+ * idTokens do not have "target", target specific refreshTokens do exist for some types of authentication
409
+ * Resource specific refresh tokens case will be added when the support is deemed necessary
410
+ */
411
+ if (!!filter.target && !this.matchTarget(entity, filter.target)) {
412
+ return false;
413
+ }
414
+ // If request OR cached entity has requested Claims Hash, check if they match
415
+ if (filter.requestedClaimsHash || entity.requestedClaimsHash) {
416
+ // Don't match if either is undefined or they are different
417
+ if (entity.requestedClaimsHash !== filter.requestedClaimsHash) {
418
+ return false;
419
+ }
420
+ }
421
+ // Access Token with Auth Scheme specific matching
422
+ if (entity.credentialType ===
423
+ CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME) {
424
+ if (!!filter.tokenType &&
425
+ !this.matchTokenType(entity, filter.tokenType)) {
426
+ return false;
427
+ }
428
+ // KeyId (sshKid) in request must match cached SSH certificate keyId because SSH cert is bound to a specific key
429
+ if (filter.tokenType === AuthenticationScheme.SSH) {
430
+ if (filter.keyId && !this.matchKeyId(entity, filter.keyId)) {
431
+ return false;
432
+ }
433
+ }
434
+ }
435
+ return true;
436
+ }
437
+ /**
438
+ * retrieve appMetadata matching all provided filters; if no filter is set, get all appMetadata
439
+ * @param filter
440
+ */
441
+ getAppMetadataFilteredBy(filter) {
442
+ const allCacheKeys = this.getKeys();
443
+ const matchingAppMetadata = {};
444
+ allCacheKeys.forEach((cacheKey) => {
445
+ // don't parse any non-appMetadata type cache entities
446
+ if (!this.isAppMetadata(cacheKey)) {
447
+ return;
448
+ }
449
+ // Attempt retrieval
450
+ const entity = this.getAppMetadata(cacheKey);
451
+ if (!entity) {
452
+ return;
453
+ }
454
+ if (!!filter.environment &&
455
+ !this.matchEnvironment(entity, filter.environment)) {
456
+ return;
457
+ }
458
+ if (!!filter.clientId &&
459
+ !this.matchClientId(entity, filter.clientId)) {
460
+ return;
461
+ }
462
+ matchingAppMetadata[cacheKey] = entity;
463
+ });
464
+ return matchingAppMetadata;
465
+ }
466
+ /**
467
+ * retrieve authorityMetadata that contains a matching alias
468
+ * @param filter
469
+ */
470
+ getAuthorityMetadataByAlias(host) {
471
+ const allCacheKeys = this.getAuthorityMetadataKeys();
472
+ let matchedEntity = null;
473
+ allCacheKeys.forEach((cacheKey) => {
474
+ // don't parse any non-authorityMetadata type cache entities
475
+ if (!this.isAuthorityMetadata(cacheKey) ||
476
+ cacheKey.indexOf(this.clientId) === -1) {
477
+ return;
478
+ }
479
+ // Attempt retrieval
480
+ const entity = this.getAuthorityMetadata(cacheKey);
481
+ if (!entity) {
482
+ return;
483
+ }
484
+ if (entity.aliases.indexOf(host) === -1) {
485
+ return;
486
+ }
487
+ matchedEntity = entity;
488
+ });
489
+ return matchedEntity;
490
+ }
491
+ /**
492
+ * Removes all accounts and related tokens from cache.
493
+ */
494
+ async removeAllAccounts() {
495
+ const allAccountKeys = this.getAccountKeys();
496
+ const removedAccounts = [];
497
+ allAccountKeys.forEach((cacheKey) => {
498
+ removedAccounts.push(this.removeAccount(cacheKey));
499
+ });
500
+ await Promise.all(removedAccounts);
501
+ }
502
+ /**
503
+ * Removes the account and related tokens for a given account key
504
+ * @param account
505
+ */
506
+ async removeAccount(accountKey) {
507
+ const account = this.getAccount(accountKey, this.commonLogger);
508
+ if (!account) {
509
+ return;
510
+ }
511
+ await this.removeAccountContext(account);
512
+ this.removeItem(accountKey);
513
+ }
514
+ /**
515
+ * Removes credentials associated with the provided account
516
+ * @param account
517
+ */
518
+ async removeAccountContext(account) {
519
+ const allTokenKeys = this.getTokenKeys();
520
+ const accountId = account.generateAccountId();
521
+ const removedCredentials = [];
522
+ allTokenKeys.idToken.forEach((key) => {
523
+ if (key.indexOf(accountId) === 0) {
524
+ this.removeIdToken(key);
525
+ }
526
+ });
527
+ allTokenKeys.accessToken.forEach((key) => {
528
+ if (key.indexOf(accountId) === 0) {
529
+ removedCredentials.push(this.removeAccessToken(key));
530
+ }
531
+ });
532
+ allTokenKeys.refreshToken.forEach((key) => {
533
+ if (key.indexOf(accountId) === 0) {
534
+ this.removeRefreshToken(key);
535
+ }
536
+ });
537
+ await Promise.all(removedCredentials);
538
+ }
539
+ /**
540
+ * Migrates a single-tenant account and all it's associated alternate cross-tenant account objects in the
541
+ * cache into a condensed multi-tenant account object with tenant profiles.
542
+ * @param accountKey
543
+ * @param accountEntity
544
+ * @param logger
545
+ * @returns
546
+ */
547
+ updateOutdatedCachedAccount(accountKey, accountEntity, logger) {
548
+ // Only update if account entity is defined and has no tenantProfiles object (is outdated)
549
+ if (accountEntity && accountEntity.isSingleTenant()) {
550
+ this.commonLogger?.verbose("updateOutdatedCachedAccount: Found a single-tenant (outdated) account entity in the cache, migrating to multi-tenant account entity");
551
+ // Get keys of all accounts belonging to user
552
+ const matchingAccountKeys = this.getAccountKeys().filter((key) => {
553
+ return key.startsWith(accountEntity.homeAccountId);
554
+ });
555
+ // Get all account entities belonging to user
556
+ const accountsToMerge = [];
557
+ matchingAccountKeys.forEach((key) => {
558
+ const account = this.getCachedAccountEntity(key);
559
+ if (account) {
560
+ accountsToMerge.push(account);
561
+ }
562
+ });
563
+ // Set base account to home account if available, any account if not
564
+ const baseAccount = accountsToMerge.find((account) => {
565
+ return tenantIdMatchesHomeTenant(account.realm, account.homeAccountId);
566
+ }) || accountsToMerge[0];
567
+ // Populate tenant profiles built from each account entity belonging to the user
568
+ baseAccount.tenantProfiles = accountsToMerge.map((account) => {
569
+ return {
570
+ tenantId: account.realm,
571
+ localAccountId: account.localAccountId,
572
+ name: account.name,
573
+ isHomeTenant: tenantIdMatchesHomeTenant(account.realm, account.homeAccountId),
574
+ };
575
+ });
576
+ const updatedAccount = CacheManager.toObject(new AccountEntity(), {
577
+ ...baseAccount,
578
+ });
579
+ const newAccountKey = updatedAccount.generateAccountKey();
580
+ // Clear cache of legacy account objects that have been collpsed into tenant profiles
581
+ matchingAccountKeys.forEach((key) => {
582
+ if (key !== newAccountKey) {
583
+ this.removeOutdatedAccount(accountKey);
584
+ }
585
+ });
586
+ // Cache updated account object
587
+ this.setAccount(updatedAccount);
588
+ logger?.verbose("Updated an outdated account entity in the cache");
589
+ return updatedAccount;
590
+ }
591
+ // No update is necessary
592
+ return accountEntity;
593
+ }
594
+ /**
595
+ * returns a boolean if the given credential is removed
596
+ * @param credential
597
+ */
598
+ async removeAccessToken(key) {
599
+ const credential = this.getAccessTokenCredential(key);
600
+ if (!credential) {
601
+ return;
602
+ }
603
+ // Remove Token Binding Key from key store for PoP Tokens Credentials
604
+ if (credential.credentialType.toLowerCase() ===
605
+ CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME.toLowerCase()) {
606
+ if (credential.tokenType === AuthenticationScheme.POP) {
607
+ const accessTokenWithAuthSchemeEntity = credential;
608
+ const kid = accessTokenWithAuthSchemeEntity.keyId;
609
+ if (kid) {
610
+ try {
611
+ await this.cryptoImpl.removeTokenBindingKey(kid);
612
+ }
613
+ catch (error) {
614
+ throw createClientAuthError(bindingKeyNotRemoved);
615
+ }
616
+ }
617
+ }
618
+ }
619
+ return this.removeItem(key);
620
+ }
621
+ /**
622
+ * Removes all app metadata objects from cache.
623
+ */
624
+ removeAppMetadata() {
625
+ const allCacheKeys = this.getKeys();
626
+ allCacheKeys.forEach((cacheKey) => {
627
+ if (this.isAppMetadata(cacheKey)) {
628
+ this.removeItem(cacheKey);
629
+ }
630
+ });
631
+ return true;
632
+ }
633
+ /**
634
+ * Retrieve AccountEntity from cache
635
+ * @param account
636
+ */
637
+ readAccountFromCache(account) {
638
+ const accountKey = AccountEntity.generateAccountCacheKey(account);
639
+ return this.getAccount(accountKey, this.commonLogger);
640
+ }
641
+ /**
642
+ * Retrieve IdTokenEntity from cache
643
+ * @param account {AccountInfo}
644
+ * @param tokenKeys {?TokenKeys}
645
+ * @param targetRealm {?string}
646
+ * @param performanceClient {?IPerformanceClient}
647
+ * @param correlationId {?string}
648
+ */
649
+ getIdToken(account, tokenKeys, targetRealm, performanceClient, correlationId) {
650
+ this.commonLogger.trace("CacheManager - getIdToken called");
651
+ const idTokenFilter = {
652
+ homeAccountId: account.homeAccountId,
653
+ environment: account.environment,
654
+ credentialType: CredentialType.ID_TOKEN,
655
+ clientId: this.clientId,
656
+ realm: targetRealm,
657
+ };
658
+ const idTokenMap = this.getIdTokensByFilter(idTokenFilter, tokenKeys);
659
+ const numIdTokens = idTokenMap.size;
660
+ if (numIdTokens < 1) {
661
+ this.commonLogger.info("CacheManager:getIdToken - No token found");
662
+ return null;
663
+ }
664
+ else if (numIdTokens > 1) {
665
+ let tokensToBeRemoved = idTokenMap;
666
+ // Multiple tenant profiles and no tenant specified, pick home account
667
+ if (!targetRealm) {
668
+ const homeIdTokenMap = new Map();
669
+ idTokenMap.forEach((idToken, key) => {
670
+ if (idToken.realm === account.tenantId) {
671
+ homeIdTokenMap.set(key, idToken);
672
+ }
673
+ });
674
+ const numHomeIdTokens = homeIdTokenMap.size;
675
+ if (numHomeIdTokens < 1) {
676
+ this.commonLogger.info("CacheManager:getIdToken - Multiple ID tokens found for account but none match account entity tenant id, returning first result");
677
+ return idTokenMap.values().next().value;
678
+ }
679
+ else if (numHomeIdTokens === 1) {
680
+ this.commonLogger.info("CacheManager:getIdToken - Multiple ID tokens found for account, defaulting to home tenant profile");
681
+ return homeIdTokenMap.values().next().value;
682
+ }
683
+ else {
684
+ // Multiple ID tokens for home tenant profile, remove all and return null
685
+ tokensToBeRemoved = homeIdTokenMap;
686
+ }
687
+ }
688
+ // Multiple tokens for a single tenant profile, remove all and return null
689
+ this.commonLogger.info("CacheManager:getIdToken - Multiple matching ID tokens found, clearing them");
690
+ tokensToBeRemoved.forEach((idToken, key) => {
691
+ this.removeIdToken(key);
692
+ });
693
+ if (performanceClient && correlationId) {
694
+ performanceClient.addFields({ multiMatchedID: idTokenMap.size }, correlationId);
695
+ }
696
+ return null;
697
+ }
698
+ this.commonLogger.info("CacheManager:getIdToken - Returning ID token");
699
+ return idTokenMap.values().next().value;
700
+ }
701
+ /**
702
+ * Gets all idTokens matching the given filter
703
+ * @param filter
704
+ * @returns
705
+ */
706
+ getIdTokensByFilter(filter, tokenKeys) {
707
+ const idTokenKeys = (tokenKeys && tokenKeys.idToken) || this.getTokenKeys().idToken;
708
+ const idTokens = new Map();
709
+ idTokenKeys.forEach((key) => {
710
+ if (!this.idTokenKeyMatchesFilter(key, {
711
+ clientId: this.clientId,
712
+ ...filter,
713
+ })) {
714
+ return;
715
+ }
716
+ const idToken = this.getIdTokenCredential(key);
717
+ if (idToken && this.credentialMatchesFilter(idToken, filter)) {
718
+ idTokens.set(key, idToken);
719
+ }
720
+ });
721
+ return idTokens;
722
+ }
723
+ /**
724
+ * Validate the cache key against filter before retrieving and parsing cache value
725
+ * @param key
726
+ * @param filter
727
+ * @returns
728
+ */
729
+ idTokenKeyMatchesFilter(inputKey, filter) {
730
+ const key = inputKey.toLowerCase();
731
+ if (filter.clientId &&
732
+ key.indexOf(filter.clientId.toLowerCase()) === -1) {
733
+ return false;
734
+ }
735
+ if (filter.homeAccountId &&
736
+ key.indexOf(filter.homeAccountId.toLowerCase()) === -1) {
737
+ return false;
738
+ }
739
+ return true;
740
+ }
741
+ /**
742
+ * Removes idToken from the cache
743
+ * @param key
744
+ */
745
+ removeIdToken(key) {
746
+ this.removeItem(key);
747
+ }
748
+ /**
749
+ * Removes refresh token from the cache
750
+ * @param key
751
+ */
752
+ removeRefreshToken(key) {
753
+ this.removeItem(key);
754
+ }
755
+ /**
756
+ * Retrieve AccessTokenEntity from cache
757
+ * @param account {AccountInfo}
758
+ * @param request {BaseAuthRequest}
759
+ * @param tokenKeys {?TokenKeys}
760
+ * @param performanceClient {?IPerformanceClient}
761
+ * @param correlationId {?string}
762
+ */
763
+ getAccessToken(account, request, tokenKeys, targetRealm, performanceClient, correlationId) {
764
+ this.commonLogger.trace("CacheManager - getAccessToken called");
765
+ const scopes = ScopeSet.createSearchScopes(request.scopes);
766
+ const authScheme = request.authenticationScheme || AuthenticationScheme.BEARER;
767
+ /*
768
+ * Distinguish between Bearer and PoP/SSH token cache types
769
+ * Cast to lowercase to handle "bearer" from ADFS
770
+ */
771
+ const credentialType = authScheme &&
772
+ authScheme.toLowerCase() !==
773
+ AuthenticationScheme.BEARER.toLowerCase()
774
+ ? CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME
775
+ : CredentialType.ACCESS_TOKEN;
776
+ const accessTokenFilter = {
777
+ homeAccountId: account.homeAccountId,
778
+ environment: account.environment,
779
+ credentialType: credentialType,
780
+ clientId: this.clientId,
781
+ realm: targetRealm || account.tenantId,
782
+ target: scopes,
783
+ tokenType: authScheme,
784
+ keyId: request.sshKid,
785
+ requestedClaimsHash: request.requestedClaimsHash,
786
+ };
787
+ const accessTokenKeys = (tokenKeys && tokenKeys.accessToken) ||
788
+ this.getTokenKeys().accessToken;
789
+ const accessTokens = [];
790
+ accessTokenKeys.forEach((key) => {
791
+ // Validate key
792
+ if (this.accessTokenKeyMatchesFilter(key, accessTokenFilter, true)) {
793
+ const accessToken = this.getAccessTokenCredential(key);
794
+ // Validate value
795
+ if (accessToken &&
796
+ this.credentialMatchesFilter(accessToken, accessTokenFilter)) {
797
+ accessTokens.push(accessToken);
798
+ }
799
+ }
800
+ });
801
+ const numAccessTokens = accessTokens.length;
802
+ if (numAccessTokens < 1) {
803
+ this.commonLogger.info("CacheManager:getAccessToken - No token found");
804
+ return null;
805
+ }
806
+ else if (numAccessTokens > 1) {
807
+ this.commonLogger.info("CacheManager:getAccessToken - Multiple access tokens found, clearing them");
808
+ accessTokens.forEach((accessToken) => {
809
+ void this.removeAccessToken(generateCredentialKey(accessToken));
810
+ });
811
+ if (performanceClient && correlationId) {
812
+ performanceClient.addFields({ multiMatchedAT: accessTokens.length }, correlationId);
813
+ }
814
+ return null;
815
+ }
816
+ this.commonLogger.info("CacheManager:getAccessToken - Returning access token");
817
+ return accessTokens[0];
818
+ }
819
+ /**
820
+ * Validate the cache key against filter before retrieving and parsing cache value
821
+ * @param key
822
+ * @param filter
823
+ * @param keyMustContainAllScopes
824
+ * @returns
825
+ */
826
+ accessTokenKeyMatchesFilter(inputKey, filter, keyMustContainAllScopes) {
827
+ const key = inputKey.toLowerCase();
828
+ if (filter.clientId &&
829
+ key.indexOf(filter.clientId.toLowerCase()) === -1) {
830
+ return false;
831
+ }
832
+ if (filter.homeAccountId &&
833
+ key.indexOf(filter.homeAccountId.toLowerCase()) === -1) {
834
+ return false;
835
+ }
836
+ if (filter.realm && key.indexOf(filter.realm.toLowerCase()) === -1) {
837
+ return false;
838
+ }
839
+ if (filter.requestedClaimsHash &&
840
+ key.indexOf(filter.requestedClaimsHash.toLowerCase()) === -1) {
841
+ return false;
842
+ }
843
+ if (filter.target) {
844
+ const scopes = filter.target.asArray();
845
+ for (let i = 0; i < scopes.length; i++) {
846
+ if (keyMustContainAllScopes &&
847
+ !key.includes(scopes[i].toLowerCase())) {
848
+ // When performing a cache lookup a missing scope would be a cache miss
849
+ return false;
850
+ }
851
+ else if (!keyMustContainAllScopes &&
852
+ key.includes(scopes[i].toLowerCase())) {
853
+ // When performing a cache write, any token with a subset of requested scopes should be replaced
854
+ return true;
855
+ }
856
+ }
857
+ }
858
+ return true;
859
+ }
860
+ /**
861
+ * Gets all access tokens matching the filter
862
+ * @param filter
863
+ * @returns
864
+ */
865
+ getAccessTokensByFilter(filter) {
866
+ const tokenKeys = this.getTokenKeys();
867
+ const accessTokens = [];
868
+ tokenKeys.accessToken.forEach((key) => {
869
+ if (!this.accessTokenKeyMatchesFilter(key, filter, true)) {
870
+ return;
871
+ }
872
+ const accessToken = this.getAccessTokenCredential(key);
873
+ if (accessToken &&
874
+ this.credentialMatchesFilter(accessToken, filter)) {
875
+ accessTokens.push(accessToken);
876
+ }
877
+ });
878
+ return accessTokens;
879
+ }
880
+ /**
881
+ * Helper to retrieve the appropriate refresh token from cache
882
+ * @param account {AccountInfo}
883
+ * @param familyRT {boolean}
884
+ * @param tokenKeys {?TokenKeys}
885
+ * @param performanceClient {?IPerformanceClient}
886
+ * @param correlationId {?string}
887
+ */
888
+ getRefreshToken(account, familyRT, tokenKeys, performanceClient, correlationId) {
889
+ this.commonLogger.trace("CacheManager - getRefreshToken called");
890
+ const id = familyRT ? THE_FAMILY_ID : undefined;
891
+ const refreshTokenFilter = {
892
+ homeAccountId: account.homeAccountId,
893
+ environment: account.environment,
894
+ credentialType: CredentialType.REFRESH_TOKEN,
895
+ clientId: this.clientId,
896
+ familyId: id,
897
+ };
898
+ const refreshTokenKeys = (tokenKeys && tokenKeys.refreshToken) ||
899
+ this.getTokenKeys().refreshToken;
900
+ const refreshTokens = [];
901
+ refreshTokenKeys.forEach((key) => {
902
+ // Validate key
903
+ if (this.refreshTokenKeyMatchesFilter(key, refreshTokenFilter)) {
904
+ const refreshToken = this.getRefreshTokenCredential(key);
905
+ // Validate value
906
+ if (refreshToken &&
907
+ this.credentialMatchesFilter(refreshToken, refreshTokenFilter)) {
908
+ refreshTokens.push(refreshToken);
909
+ }
910
+ }
911
+ });
912
+ const numRefreshTokens = refreshTokens.length;
913
+ if (numRefreshTokens < 1) {
914
+ this.commonLogger.info("CacheManager:getRefreshToken - No refresh token found.");
915
+ return null;
916
+ }
917
+ // address the else case after remove functions address environment aliases
918
+ if (numRefreshTokens > 1 && performanceClient && correlationId) {
919
+ performanceClient.addFields({ multiMatchedRT: numRefreshTokens }, correlationId);
920
+ }
921
+ this.commonLogger.info("CacheManager:getRefreshToken - returning refresh token");
922
+ return refreshTokens[0];
923
+ }
924
+ /**
925
+ * Validate the cache key against filter before retrieving and parsing cache value
926
+ * @param key
927
+ * @param filter
928
+ */
929
+ refreshTokenKeyMatchesFilter(inputKey, filter) {
930
+ const key = inputKey.toLowerCase();
931
+ if (filter.familyId &&
932
+ key.indexOf(filter.familyId.toLowerCase()) === -1) {
933
+ return false;
934
+ }
935
+ // If familyId is used, clientId is not in the key
936
+ if (!filter.familyId &&
937
+ filter.clientId &&
938
+ key.indexOf(filter.clientId.toLowerCase()) === -1) {
939
+ return false;
940
+ }
941
+ if (filter.homeAccountId &&
942
+ key.indexOf(filter.homeAccountId.toLowerCase()) === -1) {
943
+ return false;
944
+ }
945
+ return true;
946
+ }
947
+ /**
948
+ * Retrieve AppMetadataEntity from cache
949
+ */
950
+ readAppMetadataFromCache(environment) {
951
+ const appMetadataFilter = {
952
+ environment,
953
+ clientId: this.clientId,
954
+ };
955
+ const appMetadata = this.getAppMetadataFilteredBy(appMetadataFilter);
956
+ const appMetadataEntries = Object.keys(appMetadata).map((key) => appMetadata[key]);
957
+ const numAppMetadata = appMetadataEntries.length;
958
+ if (numAppMetadata < 1) {
959
+ return null;
960
+ }
961
+ else if (numAppMetadata > 1) {
962
+ throw createClientAuthError(multipleMatchingAppMetadata);
963
+ }
964
+ return appMetadataEntries[0];
965
+ }
966
+ /**
967
+ * Return the family_id value associated with FOCI
968
+ * @param environment
969
+ * @param clientId
970
+ */
971
+ isAppMetadataFOCI(environment) {
972
+ const appMetadata = this.readAppMetadataFromCache(environment);
973
+ return !!(appMetadata && appMetadata.familyId === THE_FAMILY_ID);
974
+ }
975
+ /**
976
+ * helper to match account ids
977
+ * @param value
978
+ * @param homeAccountId
979
+ */
980
+ matchHomeAccountId(entity, homeAccountId) {
981
+ return !!(typeof entity.homeAccountId === "string" &&
982
+ homeAccountId === entity.homeAccountId);
983
+ }
984
+ /**
985
+ * helper to match account ids
986
+ * @param entity
987
+ * @param localAccountId
988
+ * @returns
989
+ */
990
+ matchLocalAccountIdFromTokenClaims(tokenClaims, localAccountId) {
991
+ const idTokenLocalAccountId = tokenClaims.oid || tokenClaims.sub;
992
+ return localAccountId === idTokenLocalAccountId;
993
+ }
994
+ matchLocalAccountIdFromTenantProfile(tenantProfile, localAccountId) {
995
+ return tenantProfile.localAccountId === localAccountId;
996
+ }
997
+ /**
998
+ * helper to match names
999
+ * @param entity
1000
+ * @param name
1001
+ * @returns true if the downcased name properties are present and match in the filter and the entity
1002
+ */
1003
+ matchName(claims, name) {
1004
+ return !!(name.toLowerCase() === claims.name?.toLowerCase());
1005
+ }
1006
+ /**
1007
+ * helper to match usernames
1008
+ * @param entity
1009
+ * @param username
1010
+ * @returns
1011
+ */
1012
+ matchUsername(cachedUsername, filterUsername) {
1013
+ return !!(cachedUsername &&
1014
+ typeof cachedUsername === "string" &&
1015
+ filterUsername?.toLowerCase() === cachedUsername.toLowerCase());
1016
+ }
1017
+ /**
1018
+ * helper to match assertion
1019
+ * @param value
1020
+ * @param oboAssertion
1021
+ */
1022
+ matchUserAssertionHash(entity, userAssertionHash) {
1023
+ return !!(entity.userAssertionHash &&
1024
+ userAssertionHash === entity.userAssertionHash);
1025
+ }
1026
+ /**
1027
+ * helper to match environment
1028
+ * @param value
1029
+ * @param environment
1030
+ */
1031
+ matchEnvironment(entity, environment) {
1032
+ // Check static authority options first for cases where authority metadata has not been resolved and cached yet
1033
+ if (this.staticAuthorityOptions) {
1034
+ const staticAliases = getAliasesFromStaticSources(this.staticAuthorityOptions, this.commonLogger);
1035
+ if (staticAliases.includes(environment) &&
1036
+ staticAliases.includes(entity.environment)) {
1037
+ return true;
1038
+ }
1039
+ }
1040
+ // Query metadata cache if no static authority configuration has aliases that match enviroment
1041
+ const cloudMetadata = this.getAuthorityMetadataByAlias(environment);
1042
+ if (cloudMetadata &&
1043
+ cloudMetadata.aliases.indexOf(entity.environment) > -1) {
1044
+ return true;
1045
+ }
1046
+ return false;
1047
+ }
1048
+ /**
1049
+ * helper to match credential type
1050
+ * @param entity
1051
+ * @param credentialType
1052
+ */
1053
+ matchCredentialType(entity, credentialType) {
1054
+ return (entity.credentialType &&
1055
+ credentialType.toLowerCase() === entity.credentialType.toLowerCase());
1056
+ }
1057
+ /**
1058
+ * helper to match client ids
1059
+ * @param entity
1060
+ * @param clientId
1061
+ */
1062
+ matchClientId(entity, clientId) {
1063
+ return !!(entity.clientId && clientId === entity.clientId);
1064
+ }
1065
+ /**
1066
+ * helper to match family ids
1067
+ * @param entity
1068
+ * @param familyId
1069
+ */
1070
+ matchFamilyId(entity, familyId) {
1071
+ return !!(entity.familyId && familyId === entity.familyId);
1072
+ }
1073
+ /**
1074
+ * helper to match realm
1075
+ * @param entity
1076
+ * @param realm
1077
+ */
1078
+ matchRealm(entity, realm) {
1079
+ return !!(entity.realm?.toLowerCase() === realm.toLowerCase());
1080
+ }
1081
+ /**
1082
+ * helper to match nativeAccountId
1083
+ * @param entity
1084
+ * @param nativeAccountId
1085
+ * @returns boolean indicating the match result
1086
+ */
1087
+ matchNativeAccountId(entity, nativeAccountId) {
1088
+ return !!(entity.nativeAccountId && nativeAccountId === entity.nativeAccountId);
1089
+ }
1090
+ /**
1091
+ * helper to match loginHint which can be either:
1092
+ * 1. login_hint ID token claim
1093
+ * 2. username in cached account object
1094
+ * 3. upn in ID token claims
1095
+ * @param entity
1096
+ * @param loginHint
1097
+ * @returns
1098
+ */
1099
+ matchLoginHintFromTokenClaims(tokenClaims, loginHint) {
1100
+ if (tokenClaims.login_hint === loginHint) {
1101
+ return true;
1102
+ }
1103
+ if (tokenClaims.preferred_username === loginHint) {
1104
+ return true;
1105
+ }
1106
+ if (tokenClaims.upn === loginHint) {
1107
+ return true;
1108
+ }
1109
+ return false;
1110
+ }
1111
+ /**
1112
+ * Helper to match sid
1113
+ * @param entity
1114
+ * @param sid
1115
+ * @returns true if the sid claim is present and matches the filter
1116
+ */
1117
+ matchSid(idTokenClaims, sid) {
1118
+ return idTokenClaims.sid === sid;
1119
+ }
1120
+ matchAuthorityType(entity, authorityType) {
1121
+ return !!(entity.authorityType &&
1122
+ authorityType.toLowerCase() === entity.authorityType.toLowerCase());
1123
+ }
1124
+ /**
1125
+ * Returns true if the target scopes are a subset of the current entity's scopes, false otherwise.
1126
+ * @param entity
1127
+ * @param target
1128
+ */
1129
+ matchTarget(entity, target) {
1130
+ const isNotAccessTokenCredential = entity.credentialType !== CredentialType.ACCESS_TOKEN &&
1131
+ entity.credentialType !==
1132
+ CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME;
1133
+ if (isNotAccessTokenCredential || !entity.target) {
1134
+ return false;
1135
+ }
1136
+ const entityScopeSet = ScopeSet.fromString(entity.target);
1137
+ return entityScopeSet.containsScopeSet(target);
1138
+ }
1139
+ /**
1140
+ * Returns true if the credential's tokenType or Authentication Scheme matches the one in the request, false otherwise
1141
+ * @param entity
1142
+ * @param tokenType
1143
+ */
1144
+ matchTokenType(entity, tokenType) {
1145
+ return !!(entity.tokenType && entity.tokenType === tokenType);
1146
+ }
1147
+ /**
1148
+ * Returns true if the credential's keyId matches the one in the request, false otherwise
1149
+ * @param entity
1150
+ * @param keyId
1151
+ */
1152
+ matchKeyId(entity, keyId) {
1153
+ return !!(entity.keyId && entity.keyId === keyId);
1154
+ }
1155
+ /**
1156
+ * returns if a given cache entity is of the type appmetadata
1157
+ * @param key
1158
+ */
1159
+ isAppMetadata(key) {
1160
+ return key.indexOf(APP_METADATA) !== -1;
1161
+ }
1162
+ /**
1163
+ * returns if a given cache entity is of the type authoritymetadata
1164
+ * @param key
1165
+ */
1166
+ isAuthorityMetadata(key) {
1167
+ return key.indexOf(AUTHORITY_METADATA_CONSTANTS.CACHE_KEY) !== -1;
1168
+ }
1169
+ /**
1170
+ * returns cache key used for cloud instance metadata
1171
+ */
1172
+ generateAuthorityMetadataCacheKey(authority) {
1173
+ return `${AUTHORITY_METADATA_CONSTANTS.CACHE_KEY}-${this.clientId}-${authority}`;
1174
+ }
1175
+ /**
1176
+ * Helper to convert serialized data to object
1177
+ * @param obj
1178
+ * @param json
1179
+ */
1180
+ static toObject(obj, json) {
1181
+ for (const propertyName in json) {
1182
+ obj[propertyName] = json[propertyName];
1183
+ }
1184
+ return obj;
1185
+ }
1186
+ }
1187
+ /** @internal */
1188
+ class DefaultStorageClass extends CacheManager {
1189
+ setAccount() {
1190
+ throw createClientAuthError(methodNotImplemented);
1191
+ }
1192
+ getAccount() {
1193
+ throw createClientAuthError(methodNotImplemented);
1194
+ }
1195
+ getCachedAccountEntity() {
1196
+ throw createClientAuthError(methodNotImplemented);
1197
+ }
1198
+ setIdTokenCredential() {
1199
+ throw createClientAuthError(methodNotImplemented);
1200
+ }
1201
+ getIdTokenCredential() {
1202
+ throw createClientAuthError(methodNotImplemented);
1203
+ }
1204
+ setAccessTokenCredential() {
1205
+ throw createClientAuthError(methodNotImplemented);
1206
+ }
1207
+ getAccessTokenCredential() {
1208
+ throw createClientAuthError(methodNotImplemented);
1209
+ }
1210
+ setRefreshTokenCredential() {
1211
+ throw createClientAuthError(methodNotImplemented);
1212
+ }
1213
+ getRefreshTokenCredential() {
1214
+ throw createClientAuthError(methodNotImplemented);
1215
+ }
1216
+ setAppMetadata() {
1217
+ throw createClientAuthError(methodNotImplemented);
1218
+ }
1219
+ getAppMetadata() {
1220
+ throw createClientAuthError(methodNotImplemented);
1221
+ }
1222
+ setServerTelemetry() {
1223
+ throw createClientAuthError(methodNotImplemented);
1224
+ }
1225
+ getServerTelemetry() {
1226
+ throw createClientAuthError(methodNotImplemented);
1227
+ }
1228
+ setAuthorityMetadata() {
1229
+ throw createClientAuthError(methodNotImplemented);
1230
+ }
1231
+ getAuthorityMetadata() {
1232
+ throw createClientAuthError(methodNotImplemented);
1233
+ }
1234
+ getAuthorityMetadataKeys() {
1235
+ throw createClientAuthError(methodNotImplemented);
1236
+ }
1237
+ setThrottlingCache() {
1238
+ throw createClientAuthError(methodNotImplemented);
1239
+ }
1240
+ getThrottlingCache() {
1241
+ throw createClientAuthError(methodNotImplemented);
1242
+ }
1243
+ removeItem() {
1244
+ throw createClientAuthError(methodNotImplemented);
1245
+ }
1246
+ getKeys() {
1247
+ throw createClientAuthError(methodNotImplemented);
1248
+ }
1249
+ getAccountKeys() {
1250
+ throw createClientAuthError(methodNotImplemented);
1251
+ }
1252
+ getTokenKeys() {
1253
+ throw createClientAuthError(methodNotImplemented);
1254
+ }
1255
+ async clear() {
1256
+ throw createClientAuthError(methodNotImplemented);
1257
+ }
1258
+ updateCredentialCacheKey() {
1259
+ throw createClientAuthError(methodNotImplemented);
1260
+ }
1261
+ removeOutdatedAccount() {
1262
+ throw createClientAuthError(methodNotImplemented);
1263
+ }
1264
1264
  }
1265
1265
 
1266
1266
  export { CacheManager, DefaultStorageClass };