diff --git a/change/@azure-msal-browser-ec422c16-887b-4752-8ba4-7d499a201fe1.json b/change/@azure-msal-browser-ec422c16-887b-4752-8ba4-7d499a201fe1.json new file mode 100644 index 0000000000..c801b92b25 --- /dev/null +++ b/change/@azure-msal-browser-ec422c16-887b-4752-8ba4-7d499a201fe1.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Refactor AuthorityMetadataEntity into type", + "packageName": "@azure/msal-browser", + "email": "thomas.norling@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-msal-common-9527971d-12e8-459d-95da-4a81ed67eeb9.json b/change/@azure-msal-common-9527971d-12e8-459d-95da-4a81ed67eeb9.json new file mode 100644 index 0000000000..f562a87c35 --- /dev/null +++ b/change/@azure-msal-common-9527971d-12e8-459d-95da-4a81ed67eeb9.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Refactor AuthorityMetadataEntity into type", + "packageName": "@azure/msal-common", + "email": "thomas.norling@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-msal-node-32e7a850-e8e6-46ca-bc70-b99eed7ef131.json b/change/@azure-msal-node-32e7a850-e8e6-46ca-bc70-b99eed7ef131.json new file mode 100644 index 0000000000..3822679842 --- /dev/null +++ b/change/@azure-msal-node-32e7a850-e8e6-46ca-bc70-b99eed7ef131.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Refactor AuthorityMetadataEntity into type", + "packageName": "@azure/msal-node", + "email": "thomas.norling@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/lib/msal-browser/src/cache/BrowserCacheManager.ts b/lib/msal-browser/src/cache/BrowserCacheManager.ts index ab9c3e56c1..c959d2f192 100644 --- a/lib/msal-browser/src/cache/BrowserCacheManager.ts +++ b/lib/msal-browser/src/cache/BrowserCacheManager.ts @@ -950,18 +950,12 @@ export class BrowserCacheManager extends CacheManager { const parsedMetadata = this.validateAndParseJson(value); if ( parsedMetadata && - AuthorityMetadataEntity.isAuthorityMetadataEntity( - key, - parsedMetadata - ) + CacheHelpers.isAuthorityMetadataEntity(key, parsedMetadata) ) { this.logger.trace( "BrowserCacheManager.getAuthorityMetadata: cache hit" ); - return CacheManager.toObject( - new AuthorityMetadataEntity(), - parsedMetadata - ); + return parsedMetadata as AuthorityMetadataEntity; } return null; } diff --git a/lib/msal-browser/test/app/PublicClientApplication.spec.ts b/lib/msal-browser/test/app/PublicClientApplication.spec.ts index e7adc2aa68..3a1f14a20e 100644 --- a/lib/msal-browser/test/app/PublicClientApplication.spec.ts +++ b/lib/msal-browser/test/app/PublicClientApplication.spec.ts @@ -184,6 +184,27 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { NavigationClient.prototype, "navigateInternal" ).mockImplementation(); + + jest.spyOn( + CacheManager.prototype, + "getAuthorityMetadataByAlias" + ).mockImplementation((host) => { + const authorityMetadata: AuthorityMetadataEntity = { + aliases: [host], + preferred_cache: host, + preferred_network: host, + aliasesFromNetwork: false, + canonical_authority: host, + authorization_endpoint: "", + token_endpoint: "", + end_session_endpoint: "", + issuer: "", + jwks_uri: "", + endpointsFromNetwork: false, + expiresAt: CacheHelpers.generateAuthorityMetadataExpiresAt(), + }; + return authorityMetadata; + }); }); afterEach(() => { @@ -5088,21 +5109,6 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { pca = (pca as any).controller; await pca.initialize(); - sinon - .stub(CacheManager.prototype, "getAuthorityMetadataByAlias") - .callsFake((host) => { - const authorityMetadata = new AuthorityMetadataEntity(); - authorityMetadata.updateCloudDiscoveryMetadata( - { - aliases: [host], - preferred_cache: host, - preferred_network: host, - }, - false - ); - return authorityMetadata; - }); - // @ts-ignore pca.getBrowserStorage().setAccount(testAccount); // @ts-ignore @@ -5172,20 +5178,6 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { beforeEach(async () => { pca = (pca as any).controller; await pca.initialize(); - sinon - .stub(CacheManager.prototype, "getAuthorityMetadataByAlias") - .callsFake((host) => { - const authorityMetadata = new AuthorityMetadataEntity(); - authorityMetadata.updateCloudDiscoveryMetadata( - { - aliases: [host], - preferred_cache: host, - preferred_network: host, - }, - false - ); - return authorityMetadata; - }); // @ts-ignore pca.getBrowserStorage().setAccount(testAccount1); @@ -5418,21 +5410,6 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { pca.getBrowserStorage().setIdTokenCredential(idToken1); // @ts-ignore pca.getBrowserStorage().setIdTokenCredential(idToken2); - - sinon - .stub(CacheManager.prototype, "getAuthorityMetadataByAlias") - .callsFake((host) => { - const authorityMetadata = new AuthorityMetadataEntity(); - authorityMetadata.updateCloudDiscoveryMetadata( - { - aliases: [host], - preferred_cache: host, - preferred_network: host, - }, - false - ); - return authorityMetadata; - }); }); afterEach(() => { diff --git a/lib/msal-browser/test/cache/BrowserCacheManager.spec.ts b/lib/msal-browser/test/cache/BrowserCacheManager.spec.ts index 40d284e611..be26093b02 100644 --- a/lib/msal-browser/test/cache/BrowserCacheManager.spec.ts +++ b/lib/msal-browser/test/cache/BrowserCacheManager.spec.ts @@ -1404,27 +1404,33 @@ describe("BrowserCacheManager tests", () => { describe("AuthorityMetadata", () => { const key = `authority-metadata-${TEST_CONFIG.MSAL_CLIENT_ID}-${Constants.DEFAULT_AUTHORITY_HOST}`; - const testObj: AuthorityMetadataEntity = - new AuthorityMetadataEntity(); - testObj.aliases = [Constants.DEFAULT_AUTHORITY_HOST]; - testObj.preferred_cache = Constants.DEFAULT_AUTHORITY_HOST; - testObj.preferred_network = Constants.DEFAULT_AUTHORITY_HOST; - testObj.canonical_authority = Constants.DEFAULT_AUTHORITY; - testObj.authorization_endpoint = - //@ts-ignore - DEFAULT_OPENID_CONFIG_RESPONSE.body.authorization_endpoint; - testObj.token_endpoint = - //@ts-ignore - DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint; - testObj.end_session_endpoint = - //@ts-ignore - DEFAULT_OPENID_CONFIG_RESPONSE.body.end_session_endpoint; - //@ts-ignore - testObj.issuer = DEFAULT_OPENID_CONFIG_RESPONSE.body.issuer; - //@ts-ignore - testObj.jwks_uri = DEFAULT_OPENID_CONFIG_RESPONSE.body.jwks_uri; - testObj.aliasesFromNetwork = false; - testObj.endpointsFromNetwork = false; + const testObj: AuthorityMetadataEntity = { + aliases: [Constants.DEFAULT_AUTHORITY_HOST], + preferred_cache: Constants.DEFAULT_AUTHORITY_HOST, + preferred_network: Constants.DEFAULT_AUTHORITY_HOST, + canonical_authority: Constants.DEFAULT_AUTHORITY, + authorization_endpoint: + //@ts-ignore + DEFAULT_OPENID_CONFIG_RESPONSE.body + .authorization_endpoint, + token_endpoint: + //@ts-ignore + DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint, + end_session_endpoint: + //@ts-ignore + DEFAULT_OPENID_CONFIG_RESPONSE.body + .end_session_endpoint, + issuer: + //@ts-ignore + DEFAULT_OPENID_CONFIG_RESPONSE.body.issuer, + jwks_uri: + //@ts-ignore + DEFAULT_OPENID_CONFIG_RESPONSE.body.jwks_uri, + aliasesFromNetwork: false, + endpointsFromNetwork: false, + expiresAt: + CacheHelpers.generateAuthorityMetadataExpiresAt(), + }; it("getAuthorityMetadata() returns null if key is not in cache", () => { expect( @@ -1436,14 +1442,14 @@ describe("BrowserCacheManager tests", () => { }); it("getAuthorityMetadata() returns null if isAuthorityMetadataEntity returns false", () => { - sinon - .stub( - AuthorityMetadataEntity, - "isAuthorityMetadataEntity" - ) - .returns(false); - browserSessionStorage.setAuthorityMetadata(key, testObj); - browserLocalStorage.setAuthorityMetadata(key, testObj); + browserSessionStorage.setAuthorityMetadata(key, { + // @ts-ignore + invalidKey: "invalidValue", + }); + browserLocalStorage.setAuthorityMetadata(key, { + // @ts-ignore + invalidKey: "invalidValue", + }); expect( browserSessionStorage.getAuthorityMetadata(key) ).toBeNull(); @@ -2308,27 +2314,33 @@ describe("BrowserCacheManager tests", () => { describe("AuthorityMetadata", () => { const key = `authority-metadata-${TEST_CONFIG.MSAL_CLIENT_ID}-${Constants.DEFAULT_AUTHORITY_HOST}`; - const testObj: AuthorityMetadataEntity = - new AuthorityMetadataEntity(); - testObj.aliases = [Constants.DEFAULT_AUTHORITY_HOST]; - testObj.preferred_cache = Constants.DEFAULT_AUTHORITY_HOST; - testObj.preferred_network = Constants.DEFAULT_AUTHORITY_HOST; - testObj.canonical_authority = Constants.DEFAULT_AUTHORITY; - testObj.authorization_endpoint = - // @ts-ignore - DEFAULT_OPENID_CONFIG_RESPONSE.body.authorization_endpoint; - testObj.token_endpoint = - // @ts-ignore - DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint; - testObj.end_session_endpoint = - // @ts-ignore - DEFAULT_OPENID_CONFIG_RESPONSE.body.end_session_endpoint; - // @ts-ignore - testObj.issuer = DEFAULT_OPENID_CONFIG_RESPONSE.body.issuer; - // @ts-ignore - testObj.jwks_uri = DEFAULT_OPENID_CONFIG_RESPONSE.body.jwks_uri; - testObj.aliasesFromNetwork = false; - testObj.endpointsFromNetwork = false; + const testObj: AuthorityMetadataEntity = { + aliases: [Constants.DEFAULT_AUTHORITY_HOST], + preferred_cache: Constants.DEFAULT_AUTHORITY_HOST, + preferred_network: Constants.DEFAULT_AUTHORITY_HOST, + canonical_authority: Constants.DEFAULT_AUTHORITY, + authorization_endpoint: + //@ts-ignore + DEFAULT_OPENID_CONFIG_RESPONSE.body + .authorization_endpoint, + token_endpoint: + //@ts-ignore + DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint, + end_session_endpoint: + //@ts-ignore + DEFAULT_OPENID_CONFIG_RESPONSE.body + .end_session_endpoint, + issuer: + //@ts-ignore + DEFAULT_OPENID_CONFIG_RESPONSE.body.issuer, + jwks_uri: + //@ts-ignore + DEFAULT_OPENID_CONFIG_RESPONSE.body.jwks_uri, + aliasesFromNetwork: false, + endpointsFromNetwork: false, + expiresAt: + CacheHelpers.generateAuthorityMetadataExpiresAt(), + }; it("getAuthorityMetadata() returns null if key is not in cache", () => { expect( @@ -2340,14 +2352,14 @@ describe("BrowserCacheManager tests", () => { }); it("getAuthorityMetadata() returns null if isAuthorityMetadataEntity returns false", () => { - sinon - .stub( - AuthorityMetadataEntity, - "isAuthorityMetadataEntity" - ) - .returns(false); - browserSessionStorage.setAuthorityMetadata(key, testObj); - browserLocalStorage.setAuthorityMetadata(key, testObj); + browserSessionStorage.setAuthorityMetadata(key, { + // @ts-ignore + invalidKey: "invalidValue", + }); + browserLocalStorage.setAuthorityMetadata(key, { + // @ts-ignore + invalidKey: "invalidValue", + }); expect( browserSessionStorage.getAuthorityMetadata(key) diff --git a/lib/msal-browser/test/interaction_client/BaseInteractionClient.spec.ts b/lib/msal-browser/test/interaction_client/BaseInteractionClient.spec.ts index 628efcefcc..5424711385 100644 --- a/lib/msal-browser/test/interaction_client/BaseInteractionClient.spec.ts +++ b/lib/msal-browser/test/interaction_client/BaseInteractionClient.spec.ts @@ -12,7 +12,7 @@ import { ClientConfigurationErrorCodes, CacheManager, IdTokenEntity, - AuthorityMetadataEntity, + CacheHelpers, } from "@azure/msal-common"; import { TEST_DATA_CLIENT_INFO, @@ -25,6 +25,7 @@ import { } from "../utils/StringConstants"; import { BaseInteractionClient } from "../../src/interaction_client/BaseInteractionClient"; import { EndSessionRequest, PublicClientApplication } from "../../src"; +import { OpenIdConfigResponse } from "@azure/msal-common/dist/authority/OpenIdConfigResponse"; class testInteractionClient extends BaseInteractionClient { acquireToken(): Promise { @@ -154,15 +155,24 @@ describe("BaseInteractionClient", () => { const metadata = DEFAULT_TENANT_DISCOVERY_RESPONSE.body.metadata[0]; const openIdConfigResponse = - DEFAULT_OPENID_CONFIG_RESPONSE.body; - const authorityMetadata = new AuthorityMetadataEntity(); - authorityMetadata.updateCloudDiscoveryMetadata(metadata, true); - authorityMetadata.updateEndpointMetadata( - // @ts-ignore - openIdConfigResponse, - true - ); - return authorityMetadata; + DEFAULT_OPENID_CONFIG_RESPONSE.body as OpenIdConfigResponse; + return { + aliases: [], + preferred_cache: metadata.preferred_cache, + preferred_network: metadata.preferred_network, + canonical_authority: host, + authorization_endpoint: + openIdConfigResponse.authorization_endpoint, + token_endpoint: openIdConfigResponse.token_endpoint, + end_session_endpoint: + openIdConfigResponse.end_session_endpoint, + issuer: openIdConfigResponse.issuer, + aliasesFromNetwork: true, + endpointsFromNetwork: true, + expiresAt: + CacheHelpers.generateAuthorityMetadataExpiresAt(), + jwks_uri: openIdConfigResponse.jwks_uri, + }; }); }); diff --git a/lib/msal-common/src/authority/Authority.ts b/lib/msal-common/src/authority/Authority.ts index 4d246cfbf7..1bc69e5119 100644 --- a/lib/msal-common/src/authority/Authority.ts +++ b/lib/msal-common/src/authority/Authority.ts @@ -57,6 +57,7 @@ import { AuthError } from "../error/AuthError"; import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient"; import { PerformanceEvents } from "../telemetry/performance/PerformanceEvent"; import { invokeAsync } from "../utils/FunctionWrappers"; +import * as CacheHelpers from "../cache/utils/CacheHelpers"; /** * The authority class validates the authority URIs used by the user, and retrieves the OpenID Configuration Data from the @@ -429,13 +430,24 @@ export class Authority { * @returns */ private getCurrentMetadataEntity(): AuthorityMetadataEntity { - let metadataEntity = this.cacheManager.getAuthorityMetadataByAlias( - this.hostnameAndPort - ); + let metadataEntity: AuthorityMetadataEntity | null = + this.cacheManager.getAuthorityMetadataByAlias(this.hostnameAndPort); if (!metadataEntity) { - metadataEntity = new AuthorityMetadataEntity(); - metadataEntity.updateCanonicalAuthority(this.canonicalAuthority); + metadataEntity = { + aliases: [], + preferred_cache: this.hostnameAndPort, + preferred_network: this.hostnameAndPort, + canonical_authority: this.canonicalAuthority, + authorization_endpoint: "", + token_endpoint: "", + end_session_endpoint: "", + issuer: "", + aliasesFromNetwork: false, + endpointsFromNetwork: false, + expiresAt: CacheHelpers.generateAuthorityMetadataExpiresAt(), + jwks_uri: "", + }; } return metadataEntity; } @@ -460,8 +472,9 @@ export class Authority { endpointMetadataResult?.source !== AuthorityMetadataSource.CACHE ) { // Reset the expiration time unless both values came from a successful cache lookup - metadataEntity.resetExpiresAt(); - metadataEntity.updateCanonicalAuthority(this.canonicalAuthority); + metadataEntity.expiresAt = + CacheHelpers.generateAuthorityMetadataExpiresAt(); + metadataEntity.canonical_authority = this.canonicalAuthority; } const cacheKey = this.cacheManager.generateAuthorityMetadataCacheKey( @@ -506,10 +519,13 @@ export class Authority { this.performanceClient, this.correlationId )(localMetadata.metadata); - metadataEntity.updateEndpointMetadata( + CacheHelpers.updateAuthorityEndpointMetadata( + metadataEntity, hardcodedMetadata, false ); + metadataEntity.canonical_authority = + this.canonicalAuthority; } } } @@ -536,7 +552,11 @@ export class Authority { )(metadata); } - metadataEntity.updateEndpointMetadata(metadata, true); + CacheHelpers.updateAuthorityEndpointMetadata( + metadataEntity, + metadata, + true + ); return AuthorityMetadataSource.NETWORK; } else { // Metadata could not be obtained from the config, cache, network or hardcoded values @@ -567,7 +587,11 @@ export class Authority { this.logger.verbose( "Found endpoint metadata in authority configuration" ); - metadataEntity.updateEndpointMetadata(configMetadata, false); + CacheHelpers.updateAuthorityEndpointMetadata( + metadataEntity, + configMetadata, + false + ); return { source: AuthorityMetadataSource.CONFIG, }; @@ -586,7 +610,11 @@ export class Authority { const hardcodedMetadata = this.getEndpointMetadataFromHardcodedValues(); if (hardcodedMetadata) { - metadataEntity.updateEndpointMetadata(hardcodedMetadata, false); + CacheHelpers.updateAuthorityEndpointMetadata( + metadataEntity, + hardcodedMetadata, + false + ); return { source: AuthorityMetadataSource.HARDCODED_VALUES, metadata: hardcodedMetadata, @@ -599,7 +627,8 @@ export class Authority { } // Check cached metadata entity expiration status - const metadataEntityExpired = metadataEntity.isExpired(); + const metadataEntityExpired = + CacheHelpers.isAuthorityMetadataExpired(metadataEntity); if ( this.isAuthoritySameType(metadataEntity) && metadataEntity.endpointsFromNetwork && @@ -801,7 +830,11 @@ export class Authority { )(); if (metadata) { - metadataEntity.updateCloudDiscoveryMetadata(metadata, true); + CacheHelpers.updateCloudDiscoveryMetadata( + metadataEntity, + metadata, + true + ); return AuthorityMetadataSource.NETWORK; } @@ -839,7 +872,11 @@ export class Authority { this.logger.verbose( "Found cloud discovery metadata in authority configuration" ); - metadataEntity.updateCloudDiscoveryMetadata(metadata, false); + CacheHelpers.updateCloudDiscoveryMetadata( + metadataEntity, + metadata, + false + ); return AuthorityMetadataSource.CONFIG; } @@ -861,7 +898,8 @@ export class Authority { this.logger.verbose( "Found cloud discovery metadata from hardcoded values." ); - metadataEntity.updateCloudDiscoveryMetadata( + CacheHelpers.updateCloudDiscoveryMetadata( + metadataEntity, hardcodedMetadata, false ); @@ -873,7 +911,8 @@ export class Authority { ); } - const metadataEntityExpired = metadataEntity.isExpired(); + const metadataEntityExpired = + CacheHelpers.isAuthorityMetadataExpired(metadataEntity); if ( this.isAuthoritySameType(metadataEntity) && metadataEntity.aliasesFromNetwork && diff --git a/lib/msal-common/src/cache/entities/AuthorityMetadataEntity.ts b/lib/msal-common/src/cache/entities/AuthorityMetadataEntity.ts index 2e2583bdda..3f17cb7b8b 100644 --- a/lib/msal-common/src/cache/entities/AuthorityMetadataEntity.ts +++ b/lib/msal-common/src/cache/entities/AuthorityMetadataEntity.ts @@ -3,13 +3,8 @@ * Licensed under the MIT License. */ -import { CloudDiscoveryMetadata } from "../../authority/CloudDiscoveryMetadata"; -import { OpenIdConfigResponse } from "../../authority/OpenIdConfigResponse"; -import { AUTHORITY_METADATA_CONSTANTS } from "../../utils/Constants"; -import { TimeUtils } from "../../utils/TimeUtils"; - /** @internal */ -export class AuthorityMetadataEntity { +export type AuthorityMetadataEntity = { aliases: Array; preferred_cache: string; preferred_network: string; @@ -22,91 +17,4 @@ export class AuthorityMetadataEntity { endpointsFromNetwork: boolean; expiresAt: number; jwks_uri: string; - - constructor() { - this.expiresAt = - TimeUtils.nowSeconds() + - AUTHORITY_METADATA_CONSTANTS.REFRESH_TIME_SECONDS; - } - - /** - * Update the entity with new aliases, preferred_cache and preferred_network values - * @param metadata - * @param fromNetwork - */ - updateCloudDiscoveryMetadata( - metadata: CloudDiscoveryMetadata, - fromNetwork: boolean - ): void { - this.aliases = metadata.aliases; - this.preferred_cache = metadata.preferred_cache; - this.preferred_network = metadata.preferred_network; - this.aliasesFromNetwork = fromNetwork; - } - - /** - * Update the entity with new endpoints - * @param metadata - * @param fromNetwork - */ - updateEndpointMetadata( - metadata: OpenIdConfigResponse, - fromNetwork: boolean - ): void { - this.authorization_endpoint = metadata.authorization_endpoint; - this.token_endpoint = metadata.token_endpoint; - this.end_session_endpoint = metadata.end_session_endpoint; - this.issuer = metadata.issuer; - this.endpointsFromNetwork = fromNetwork; - this.jwks_uri = metadata.jwks_uri; - } - - /** - * Save the authority that was used to create this cache entry - * @param authority - */ - updateCanonicalAuthority(authority: string): void { - this.canonical_authority = authority; - } - - /** - * Reset the exiresAt value - */ - resetExpiresAt(): void { - this.expiresAt = - TimeUtils.nowSeconds() + - AUTHORITY_METADATA_CONSTANTS.REFRESH_TIME_SECONDS; - } - - /** - * Returns whether or not the data needs to be refreshed - */ - isExpired(): boolean { - return this.expiresAt <= TimeUtils.nowSeconds(); - } - - /** - * Validates an entity: checks for all expected params - * @param entity - */ - static isAuthorityMetadataEntity(key: string, entity: object): boolean { - if (!entity) { - return false; - } - - return ( - key.indexOf(AUTHORITY_METADATA_CONSTANTS.CACHE_KEY) === 0 && - entity.hasOwnProperty("aliases") && - entity.hasOwnProperty("preferred_cache") && - entity.hasOwnProperty("preferred_network") && - entity.hasOwnProperty("canonical_authority") && - entity.hasOwnProperty("authorization_endpoint") && - entity.hasOwnProperty("token_endpoint") && - entity.hasOwnProperty("issuer") && - entity.hasOwnProperty("aliasesFromNetwork") && - entity.hasOwnProperty("endpointsFromNetwork") && - entity.hasOwnProperty("expiresAt") && - entity.hasOwnProperty("jwks_uri") - ); - } -} +}; diff --git a/lib/msal-common/src/cache/utils/CacheHelpers.ts b/lib/msal-common/src/cache/utils/CacheHelpers.ts index 83c9029373..f19d84caf5 100644 --- a/lib/msal-common/src/cache/utils/CacheHelpers.ts +++ b/lib/msal-common/src/cache/utils/CacheHelpers.ts @@ -5,11 +5,14 @@ import { extractTokenClaims } from "../../account/AuthToken"; import { TokenClaims } from "../../account/TokenClaims"; +import { CloudDiscoveryMetadata } from "../../authority/CloudDiscoveryMetadata"; +import { OpenIdConfigResponse } from "../../authority/OpenIdConfigResponse"; import { ClientAuthErrorCodes, createClientAuthError, } from "../../error/ClientAuthError"; import { + AUTHORITY_METADATA_CONSTANTS, AuthenticationScheme, CredentialType, SERVER_TELEM_CONSTANTS, @@ -18,6 +21,7 @@ import { } from "../../utils/Constants"; import { TimeUtils } from "../../utils/TimeUtils"; import { AccessTokenEntity } from "../entities/AccessTokenEntity"; +import { AuthorityMetadataEntity } from "../entities/AuthorityMetadataEntity"; import { CredentialEntity } from "../entities/CredentialEntity"; import { IdTokenEntity } from "../entities/IdTokenEntity"; import { RefreshTokenEntity } from "../entities/RefreshTokenEntity"; @@ -350,3 +354,75 @@ export function isThrottlingEntity(key: string, entity?: object): boolean { return validateKey && validateEntity; } + +/** + * Validates an entity: checks for all expected params + * @param entity + */ +export function isAuthorityMetadataEntity( + key: string, + entity: object +): boolean { + if (!entity) { + return false; + } + + return ( + key.indexOf(AUTHORITY_METADATA_CONSTANTS.CACHE_KEY) === 0 && + entity.hasOwnProperty("aliases") && + entity.hasOwnProperty("preferred_cache") && + entity.hasOwnProperty("preferred_network") && + entity.hasOwnProperty("canonical_authority") && + entity.hasOwnProperty("authorization_endpoint") && + entity.hasOwnProperty("token_endpoint") && + entity.hasOwnProperty("issuer") && + entity.hasOwnProperty("aliasesFromNetwork") && + entity.hasOwnProperty("endpointsFromNetwork") && + entity.hasOwnProperty("expiresAt") && + entity.hasOwnProperty("jwks_uri") + ); +} + +/** + * Reset the exiresAt value + */ +export function generateAuthorityMetadataExpiresAt(): number { + return ( + TimeUtils.nowSeconds() + + AUTHORITY_METADATA_CONSTANTS.REFRESH_TIME_SECONDS + ); +} + +export function updateAuthorityEndpointMetadata( + authorityMetadata: AuthorityMetadataEntity, + updatedValues: OpenIdConfigResponse, + fromNetwork: boolean +): void { + authorityMetadata.authorization_endpoint = + updatedValues.authorization_endpoint; + authorityMetadata.token_endpoint = updatedValues.token_endpoint; + authorityMetadata.end_session_endpoint = updatedValues.end_session_endpoint; + authorityMetadata.issuer = updatedValues.issuer; + authorityMetadata.endpointsFromNetwork = fromNetwork; + authorityMetadata.jwks_uri = updatedValues.jwks_uri; +} + +export function updateCloudDiscoveryMetadata( + authorityMetadata: AuthorityMetadataEntity, + updatedValues: CloudDiscoveryMetadata, + fromNetwork: boolean +): void { + authorityMetadata.aliases = updatedValues.aliases; + authorityMetadata.preferred_cache = updatedValues.preferred_cache; + authorityMetadata.preferred_network = updatedValues.preferred_network; + authorityMetadata.aliasesFromNetwork = fromNetwork; +} + +/** + * Returns whether or not the data needs to be refreshed + */ +export function isAuthorityMetadataExpired( + metadata: AuthorityMetadataEntity +): boolean { + return metadata.expiresAt <= TimeUtils.nowSeconds(); +} diff --git a/lib/msal-common/test/authority/Authority.spec.ts b/lib/msal-common/test/authority/Authority.spec.ts index ccc6dd0411..ab58915cd6 100644 --- a/lib/msal-common/test/authority/Authority.spec.ts +++ b/lib/msal-common/test/authority/Authority.spec.ts @@ -36,7 +36,13 @@ import { import { ProtocolMode } from "../../src/authority/ProtocolMode"; import { AuthorityMetadataEntity } from "../../src/cache/entities/AuthorityMetadataEntity"; import { OpenIdConfigResponse } from "../../src/authority/OpenIdConfigResponse"; -import { Logger, LogLevel, UrlString } from "../../src"; +import { + CacheHelpers, + Logger, + LogLevel, + TimeUtils, + UrlString, +} from "../../src"; import { RegionDiscovery } from "../../src/authority/RegionDiscovery"; import { InstanceDiscoveryMetadata } from "../../src/authority/AuthorityMetadata"; import * as authorityMetadata from "../../src/authority/AuthorityMetadata"; @@ -57,6 +63,25 @@ const loggerOptions = { }; const logger = new Logger(loggerOptions); +const authorityMetadataCacheValue: AuthorityMetadataEntity = { + authorization_endpoint: + DEFAULT_OPENID_CONFIG_RESPONSE.body.authorization_endpoint, + token_endpoint: DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint, + end_session_endpoint: + DEFAULT_OPENID_CONFIG_RESPONSE.body.end_session_endpoint, + issuer: DEFAULT_OPENID_CONFIG_RESPONSE.body.issuer, + jwks_uri: DEFAULT_OPENID_CONFIG_RESPONSE.body.jwks_uri, + endpointsFromNetwork: true, + aliases: DEFAULT_TENANT_DISCOVERY_RESPONSE.body.metadata[0].aliases, + preferred_cache: + DEFAULT_TENANT_DISCOVERY_RESPONSE.body.metadata[0].preferred_cache, + preferred_network: + DEFAULT_TENANT_DISCOVERY_RESPONSE.body.metadata[0].preferred_network, + aliasesFromNetwork: true, + canonical_authority: Constants.DEFAULT_AUTHORITY, + expiresAt: CacheHelpers.generateAuthorityMetadataExpiresAt(), +}; + describe("Authority.ts Class Unit Tests", () => { beforeEach(() => { mockStorage = new MockStorageClass( @@ -1216,17 +1241,10 @@ describe("Authority.ts Class Unit Tests", () => { it("Gets endpoints from cache if not present in configuration or hardcoded metadata", async () => { const key = `authority-metadata-${TEST_CONFIG.MSAL_CLIENT_ID}-${Constants.DEFAULT_AUTHORITY_HOST}`; - const value = new AuthorityMetadataEntity(); - value.updateCloudDiscoveryMetadata( - DEFAULT_TENANT_DISCOVERY_RESPONSE.body.metadata[0], - true - ); - value.updateEndpointMetadata( - DEFAULT_OPENID_CONFIG_RESPONSE.body, - true + mockStorage.setAuthorityMetadata( + key, + authorityMetadataCacheValue ); - value.updateCanonicalAuthority(Constants.DEFAULT_AUTHORITY); - mockStorage.setAuthorityMetadata(key, value); authority = new Authority( Constants.DEFAULT_AUTHORITY, @@ -1307,17 +1325,10 @@ describe("Authority.ts Class Unit Tests", () => { it("Gets endpoints from cache skipping hardcoded metadata if skipAuthorityMetadataCache is set to true", async () => { const key = `authority-metadata-${TEST_CONFIG.MSAL_CLIENT_ID}-${Constants.DEFAULT_AUTHORITY_HOST}`; - const value = new AuthorityMetadataEntity(); - value.updateCloudDiscoveryMetadata( - DEFAULT_TENANT_DISCOVERY_RESPONSE.body.metadata[0], - true - ); - value.updateEndpointMetadata( - DEFAULT_OPENID_CONFIG_RESPONSE.body, - true + mockStorage.setAuthorityMetadata( + key, + authorityMetadataCacheValue ); - value.updateCanonicalAuthority(Constants.DEFAULT_AUTHORITY); - mockStorage.setAuthorityMetadata(key, value); authority = new Authority( Constants.DEFAULT_AUTHORITY, @@ -1398,22 +1409,10 @@ describe("Authority.ts Class Unit Tests", () => { it("Gets endpoints from network if cached metadata is expired and metadata was not included in configuration or hardcoded values", async () => { const key = `authority-metadata-${TEST_CONFIG.MSAL_CLIENT_ID}-${Constants.DEFAULT_AUTHORITY_HOST}`; - const value = new AuthorityMetadataEntity(); - value.updateCloudDiscoveryMetadata( - DEFAULT_TENANT_DISCOVERY_RESPONSE.body.metadata[0], - true - ); - value.updateEndpointMetadata( - DEFAULT_OPENID_CONFIG_RESPONSE.body, - true - ); - value.updateCanonicalAuthority(Constants.DEFAULT_AUTHORITY); - mockStorage.setAuthorityMetadata(key, value); - - jest.spyOn( - AuthorityMetadataEntity.prototype, - "isExpired" - ).mockReturnValue(true); + mockStorage.setAuthorityMetadata(key, { + ...authorityMetadataCacheValue, + expiresAt: TimeUtils.nowSeconds() - 1000, + }); networkInterface.sendGetRequestAsync = ( url: string, @@ -1936,13 +1935,10 @@ describe("Authority.ts Class Unit Tests", () => { expectedCloudDiscoveryMetadata.aliases; const key = `authority-metadata-${TEST_CONFIG.MSAL_CLIENT_ID}-sts.windows.net`; - const value = new AuthorityMetadataEntity(); - value.updateCloudDiscoveryMetadata( - expectedCloudDiscoveryMetadata, - true + mockStorage.setAuthorityMetadata( + key, + authorityMetadataCacheValue ); - value.updateCanonicalAuthority(Constants.DEFAULT_AUTHORITY); - mockStorage.setAuthorityMetadata(key, value); jest.spyOn( Authority.prototype, "updateEndpointMetadata" @@ -2032,13 +2028,10 @@ describe("Authority.ts Class Unit Tests", () => { expectedCloudDiscoveryMetadata.aliases; const key = `authority-metadata-${TEST_CONFIG.MSAL_CLIENT_ID}-sts.windows.net`; - const value = new AuthorityMetadataEntity(); - value.updateCloudDiscoveryMetadata( - expectedCloudDiscoveryMetadata, - true + mockStorage.setAuthorityMetadata( + key, + authorityMetadataCacheValue ); - value.updateCanonicalAuthority(Constants.DEFAULT_AUTHORITY); - mockStorage.setAuthorityMetadata(key, value); jest.spyOn( Authority.prototype, "updateEndpointMetadata" @@ -2156,17 +2149,10 @@ describe("Authority.ts Class Unit Tests", () => { }; const key = `authority-metadata-${TEST_CONFIG.MSAL_CLIENT_ID}-sts.windows.net`; - const value = new AuthorityMetadataEntity(); - value.updateCloudDiscoveryMetadata( - DEFAULT_TENANT_DISCOVERY_RESPONSE.body.metadata[0], - true - ); - value.updateCanonicalAuthority(Constants.DEFAULT_AUTHORITY); - mockStorage.setAuthorityMetadata(key, value); - jest.spyOn( - AuthorityMetadataEntity.prototype, - "isExpired" - ).mockReturnValue(true); + mockStorage.setAuthorityMetadata(key, { + ...authorityMetadataCacheValue, + expiresAt: TimeUtils.nowSeconds() - 1000, + }); jest.spyOn( Authority.prototype, "updateEndpointMetadata" diff --git a/lib/msal-common/test/cache/CacheManager.spec.ts b/lib/msal-common/test/cache/CacheManager.spec.ts index 1874e6dec5..a8607e6490 100644 --- a/lib/msal-common/test/cache/CacheManager.spec.ts +++ b/lib/msal-common/test/cache/CacheManager.spec.ts @@ -61,15 +61,21 @@ describe("CacheManager.ts test cases", () => { authorityMetadataStub = sinon .stub(CacheManager.prototype, "getAuthorityMetadataByAlias") .callsFake((host) => { - const authorityMetadata = new AuthorityMetadataEntity(); - authorityMetadata.updateCloudDiscoveryMetadata( - { - aliases: [host], - preferred_cache: host, - preferred_network: host, - }, - false - ); + const authorityMetadata: AuthorityMetadataEntity = { + aliases: [host], + preferred_cache: host, + preferred_network: host, + aliasesFromNetwork: false, + canonical_authority: host, + authorization_endpoint: "", + token_endpoint: "", + end_session_endpoint: "", + issuer: "", + jwks_uri: "", + endpointsFromNetwork: false, + expiresAt: + CacheHelpers.generateAuthorityMetadataExpiresAt(), + }; return authorityMetadata; }); }); @@ -2202,16 +2208,20 @@ describe("CacheManager.ts test cases", () => { it("getRefreshToken with environment aliases", () => { authorityMetadataStub.callsFake((host) => { - const authorityMetadata = new AuthorityMetadataEntity(); - authorityMetadata.updateCloudDiscoveryMetadata( - { - aliases: ["login.microsoftonline.com", "login.windows.net"], - preferred_network: host, - preferred_cache: host, - }, - false - ); - + const authorityMetadata: AuthorityMetadataEntity = { + aliases: ["login.microsoftonline.com", "login.windows.net"], + preferred_cache: host, + preferred_network: host, + aliasesFromNetwork: false, + canonical_authority: host, + authorization_endpoint: "", + token_endpoint: "", + end_session_endpoint: "", + issuer: "", + jwks_uri: "", + endpointsFromNetwork: false, + expiresAt: CacheHelpers.generateAuthorityMetadataExpiresAt(), + }; return authorityMetadata; }); const mockedAccountInfo: AccountInfo = { diff --git a/lib/msal-common/test/cache/MockCache.ts b/lib/msal-common/test/cache/MockCache.ts index 0e44b2de36..de50f2c31d 100644 --- a/lib/msal-common/test/cache/MockCache.ts +++ b/lib/msal-common/test/cache/MockCache.ts @@ -5,7 +5,6 @@ import { AppMetadataEntity, - AuthorityMetadataEntity, CacheManager, ICrypto, RefreshTokenEntity, @@ -248,7 +247,7 @@ export class MockCache { // create authorityMetadata entries createAuthorityMetadataEntries(): void { - const authorityMetadata_data = { + const authorityMetadata = { aliases: [ "login.microsoftonline.com", "login.windows.net", @@ -258,7 +257,7 @@ export class MockCache { aliasesFromNetwork: false, authorization_endpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", - canonicalAuthority: "https://login.microsoftonline.com/common", + canonical_authority: "https://login.microsoftonline.com/common", end_session_endpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/logout", endpointsFromNetwork: false, @@ -267,14 +266,11 @@ export class MockCache { jwks_uri: "https://login.microsoftonline.com/common/discovery/v2.0/keys", preferred_cache: "login.windows.net", + preferred_network: "login.microsoftonline.com", token_endpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/token", }; - const authorityMetadata = CacheManager.toObject( - new AuthorityMetadataEntity(), - authorityMetadata_data - ); const cacheKey = this.cacheManager.generateAuthorityMetadataCacheKey( authorityMetadata.preferred_cache ); diff --git a/lib/msal-common/test/cache/entities/AuthorityMetadataEntity.spec.ts b/lib/msal-common/test/cache/entities/AuthorityMetadataEntity.spec.ts index e2cd40bfc1..5110d8aaa7 100644 --- a/lib/msal-common/test/cache/entities/AuthorityMetadataEntity.spec.ts +++ b/lib/msal-common/test/cache/entities/AuthorityMetadataEntity.spec.ts @@ -1,10 +1,10 @@ -import { AuthorityMetadataEntity } from "../../../src/cache/entities/AuthorityMetadataEntity"; import { DEFAULT_OPENID_CONFIG_RESPONSE, TEST_CONFIG, } from "../../test_kit/StringConstants"; import { Constants } from "../../../src/utils/Constants"; import { TimeUtils } from "../../../src/utils/TimeUtils"; +import { CacheHelpers } from "../../../src"; describe("AuthorityMetadataEntity.ts Unit Tests", () => { const key = `authority-metadata-${TEST_CONFIG.MSAL_CLIENT_ID}-${Constants.DEFAULT_AUTHORITY_HOST}`; @@ -26,9 +26,7 @@ describe("AuthorityMetadataEntity.ts Unit Tests", () => { }; it("Verify if an object is a AuthorityMetadataEntity", () => { - expect( - AuthorityMetadataEntity.isAuthorityMetadataEntity(key, testObj) - ).toBe(true); + expect(CacheHelpers.isAuthorityMetadataEntity(key, testObj)).toBe(true); }); it("Verify if an object is a AuthorityMetadataEntity (without end_session_endpoint)", () => { @@ -36,24 +34,19 @@ describe("AuthorityMetadataEntity.ts Unit Tests", () => { ...testObj, }; delete metadata["end_session_endpoint"]; - expect( - AuthorityMetadataEntity.isAuthorityMetadataEntity(key, metadata) - ).toBe(true); + expect(CacheHelpers.isAuthorityMetadataEntity(key, metadata)).toBe( + true + ); }); it("Verify an object is not a AuthorityMetadataEntity", () => { expect( // @ts-ignore - AuthorityMetadataEntity.isAuthorityMetadataEntity(key, null) + CacheHelpers.isAuthorityMetadataEntity(key, null) ).toBe(false); - expect(AuthorityMetadataEntity.isAuthorityMetadataEntity(key, {})).toBe( - false - ); + expect(CacheHelpers.isAuthorityMetadataEntity(key, {})).toBe(false); expect( - AuthorityMetadataEntity.isAuthorityMetadataEntity( - "not-a-real-key", - testObj - ) + CacheHelpers.isAuthorityMetadataEntity("not-a-real-key", testObj) ).toBe(false); Object.keys(testObj).forEach((key) => { @@ -61,7 +54,7 @@ describe("AuthorityMetadataEntity.ts Unit Tests", () => { delete incompleteTestObject[key]; expect( - AuthorityMetadataEntity.isAuthorityMetadataEntity( + CacheHelpers.isAuthorityMetadataEntity( key, incompleteTestObject ) diff --git a/lib/msal-common/test/client/ClientTestUtils.ts b/lib/msal-common/test/client/ClientTestUtils.ts index 0a0ae3c4d2..3ffcfd2ed9 100644 --- a/lib/msal-common/test/client/ClientTestUtils.ts +++ b/lib/msal-common/test/client/ClientTestUtils.ts @@ -159,7 +159,7 @@ export class MockStorageClass extends CacheManager { return this.store[key] as AuthorityMetadataEntity; } setAuthorityMetadata(key: string, value: AuthorityMetadataEntity): void { - this.store[key] = value; + this.store[key] = { ...value }; } // Throttling cache diff --git a/lib/msal-node/src/cache/NodeStorage.ts b/lib/msal-node/src/cache/NodeStorage.ts index d06ebbb048..0e84e24e2c 100644 --- a/lib/msal-node/src/cache/NodeStorage.ts +++ b/lib/msal-node/src/cache/NodeStorage.ts @@ -383,10 +383,7 @@ export class NodeStorage extends CacheManager { ) as AuthorityMetadataEntity; if ( authorityMetadataEntity && - AuthorityMetadataEntity.isAuthorityMetadataEntity( - key, - authorityMetadataEntity - ) + CacheHelpers.isAuthorityMetadataEntity(key, authorityMetadataEntity) ) { return authorityMetadataEntity; } diff --git a/lib/msal-node/test/cache/Storage.spec.ts b/lib/msal-node/test/cache/Storage.spec.ts index d9fc75a9aa..69faf85b3b 100644 --- a/lib/msal-node/test/cache/Storage.spec.ts +++ b/lib/msal-node/test/cache/Storage.spec.ts @@ -7,6 +7,7 @@ import { AccessTokenEntity, IdTokenEntity, RefreshTokenEntity, + CacheHelpers, } from "@azure/msal-common"; import { JsonCache, @@ -421,22 +422,23 @@ describe("Storage tests for msal-node: ", () => { describe("AuthorityMetadata", () => { const host = "login.microsoftonline.com"; const key = `authority-metadata-${clientId}-${host}`; - const testObj: AuthorityMetadataEntity = - new AuthorityMetadataEntity(); - testObj.aliases = [host]; - testObj.preferred_cache = host; - testObj.preferred_network = host; - testObj.canonical_authority = TEST_CONSTANTS.DEFAULT_AUTHORITY; - testObj.authorization_endpoint = - DEFAULT_OPENID_CONFIG_RESPONSE.body.authorization_endpoint; - testObj.token_endpoint = - DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint; - testObj.end_session_endpoint = - DEFAULT_OPENID_CONFIG_RESPONSE.body.end_session_endpoint; - testObj.issuer = DEFAULT_OPENID_CONFIG_RESPONSE.body.issuer; - testObj.jwks_uri = DEFAULT_OPENID_CONFIG_RESPONSE.body.jwks_uri; - testObj.aliasesFromNetwork = false; - testObj.endpointsFromNetwork = false; + const testObj: AuthorityMetadataEntity = { + aliases: [host], + preferred_cache: host, + preferred_network: host, + canonical_authority: TEST_CONSTANTS.DEFAULT_AUTHORITY, + authorization_endpoint: + DEFAULT_OPENID_CONFIG_RESPONSE.body.authorization_endpoint, + token_endpoint: + DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint, + end_session_endpoint: + DEFAULT_OPENID_CONFIG_RESPONSE.body.end_session_endpoint, + issuer: DEFAULT_OPENID_CONFIG_RESPONSE.body.issuer, + jwks_uri: DEFAULT_OPENID_CONFIG_RESPONSE.body.jwks_uri, + aliasesFromNetwork: false, + endpointsFromNetwork: false, + expiresAt: CacheHelpers.generateAuthorityMetadataExpiresAt(), + }; it("getAuthorityMetadata() returns null if key is not in cache", () => { const nodeStorage = new NodeStorage(