@azure/msal-common 14.5.0 → 14.6.0-alpha.0

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