diff --git a/lib/msal-browser/src/app/PublicClientApplication.ts b/lib/msal-browser/src/app/PublicClientApplication.ts index 57703e52e5..8b2495e6fc 100644 --- a/lib/msal-browser/src/app/PublicClientApplication.ts +++ b/lib/msal-browser/src/app/PublicClientApplication.ts @@ -530,7 +530,7 @@ export class PublicClientApplication { * @returns {@link IAccount[]} - Array of account objects in cache */ public getAllAccounts(): IAccount[] { - return this.authModule.getAllAccounts(); + return this.browserStorage.getAllAccounts(); } /** diff --git a/lib/msal-browser/src/cache/BrowserStorage.ts b/lib/msal-browser/src/cache/BrowserStorage.ts index 9028cb0a3d..ec47ee9547 100644 --- a/lib/msal-browser/src/cache/BrowserStorage.ts +++ b/lib/msal-browser/src/cache/BrowserStorage.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { ICacheStorage, Constants, PersistentCacheKeys, StringUtils, AuthorizationCodeRequest, ICrypto, CacheSchemaType, AccountEntity, IdTokenEntity, CacheHelper, CredentialType, AccessTokenEntity, RefreshTokenEntity, AppMetadataEntity } from "@azure/msal-common"; +import { Constants, PersistentCacheKeys, StringUtils, AuthorizationCodeRequest, ICrypto, CacheSchemaType, AccountEntity, IdTokenEntity, CacheHelper, CredentialType, AccessTokenEntity, RefreshTokenEntity, AppMetadataEntity, CacheManager } from "@azure/msal-common"; import { CacheOptions } from "../config/Configuration"; import { BrowserAuthError } from "../error/BrowserAuthError"; import { BrowserConfigurationAuthError } from "../error/BrowserConfigurationAuthError"; @@ -16,7 +16,7 @@ const COOKIE_LIFE_MULTIPLIER = 24 * 60 * 60 * 1000; * Cookies are only used if storeAuthStateInCookie is true, and are only used for * parameters such as state and nonce, generally. */ -export class BrowserStorage implements ICacheStorage { +export class BrowserStorage extends CacheManager { // Cache configuration, either set by user or default values. private cacheConfig: CacheOptions; @@ -26,6 +26,7 @@ export class BrowserStorage implements ICacheStorage { private clientId: string; constructor(clientId: string, cacheConfig: CacheOptions) { + super(); // Validate cache location this.validateWindowStorage(cacheConfig.cacheLocation); diff --git a/lib/msal-common/src/cache/UnifiedCacheManager.ts b/lib/msal-common/src/cache/CacheManager.ts similarity index 57% rename from lib/msal-common/src/cache/UnifiedCacheManager.ts rename to lib/msal-common/src/cache/CacheManager.ts index a78c0c1822..e97d1e6bff 100644 --- a/lib/msal-common/src/cache/UnifiedCacheManager.ts +++ b/lib/msal-common/src/cache/CacheManager.ts @@ -3,87 +3,116 @@ * Licensed under the MIT License. */ -import { - InMemoryCache, - JsonCache, - AccountFilter, - CredentialFilter, -} from "./utils/CacheTypes"; +import { AccountCache, AccountFilter, CredentialFilter, CredentialCache } from "./utils/CacheTypes"; +import { CacheRecord } from "./entities/CacheRecord"; +import { CacheSchemaType, CredentialType, Constants } from "../utils/Constants"; +import { CredentialEntity } from "./entities/CredentialEntity"; +import { ScopeSet } from "../request/ScopeSet"; import { AccountEntity } from "./entities/AccountEntity"; -import { ICacheStorage } from "./interface/ICacheStorage"; -import { Deserializer } from "./serialize/Deserializer"; -import { Serializer } from "./serialize/Serializer"; -import { Credential } from "./entities/Credential"; -import { CredentialType, CacheSchemaType, Constants } from "../utils/Constants"; -import { AccountCache, CredentialCache } from "./utils/CacheTypes"; -import { ICacheManager } from "./interface/ICacheManager"; +import { AccessTokenEntity } from "./entities/AccessTokenEntity"; import { CacheHelper } from "./utils/CacheHelper"; -import { CacheRecord } from "./entities/CacheRecord"; import { StringUtils } from "../utils/StringUtils"; import { IdTokenEntity } from "./entities/IdTokenEntity"; -import { AccessTokenEntity } from "./entities/AccessTokenEntity"; import { RefreshTokenEntity } from "./entities/RefreshTokenEntity"; -import { ScopeSet } from "../request/ScopeSet"; +import { AuthError } from "../error/AuthError"; +import { ICacheManager } from "./interface/ICacheManager"; +import { IAccount } from "../account/IAccount"; +import { ClientAuthError } from "../error/ClientAuthError"; -export class UnifiedCacheManager implements ICacheManager { - // Storage interface - private cacheStorage: ICacheStorage; - private inMemory: boolean; - private clientId: string; +/** + * Interface class which implement cache storage functions used by MSAL to perform validity checks, and store tokens. + */ +export abstract class CacheManager implements ICacheManager { - constructor(cacheImpl: ICacheStorage, clientId: string, storeInMemory: boolean) { - this.cacheStorage = cacheImpl; - this.clientId = clientId; - this.inMemory = storeInMemory; - } + /** + * Function to set item in cache. + * @param key + * @param value + */ + abstract setItem(key: string, value: string | object, type?: string): void; + + /** + * Function which retrieves item from cache. + * @param key + */ + abstract getItem(key: string, type?: string): string | object; /** - * Initialize in memory cache from an exisiting cache vault - * @param cache + * Function to remove an item from cache given its key. + * @param key */ - generateInMemoryCache(cache: string): InMemoryCache { - return Deserializer.deserializeAllCache( - Deserializer.deserializeJSONBlob(cache) - ); - } + abstract removeItem(key: string, type?: string): boolean; + + /** + * Function which returns boolean whether cache contains a specific key. + * @param key + */ + abstract containsKey(key: string, type?: string): boolean; /** - * retrieves the final JSON - * @param inMemoryCache + * Function which retrieves all current keys from the cache. */ - generateJsonCache(inMemoryCache: InMemoryCache): JsonCache { - return Serializer.serializeAllCache(inMemoryCache); - } + abstract getKeys(): string[]; + + /** + * Function which clears cache. + */ + abstract clear(): void; /** - * Returns all accounts in memory + * Returns all accounts in cache */ - getAllAccounts(): AccountCache { - return this.getAccountsFilteredBy(); + getAllAccounts(): IAccount[] { + const currentAccounts: AccountCache = this.getAccountsFilteredBy(); + const accountValues: AccountEntity[] = Object.values(currentAccounts); + const numAccounts = accountValues.length; + if (numAccounts < 1) { + return null; + } else { + const allAccounts = accountValues.map((value) => { + const accountObj: AccountEntity = JSON.parse(JSON.stringify(value)); + return CacheHelper.toIAccount(accountObj); + }); + return allAccounts; + } } /** * saves a cache record * @param cacheRecord */ - saveCacheRecord(cacheRecord: CacheRecord, responseScopes: ScopeSet): void { - this.saveAccount(cacheRecord.account); - this.saveCredential(cacheRecord.idToken); - this.saveAccessToken(cacheRecord.accessToken, responseScopes); - this.saveCredential(cacheRecord.refreshToken); + saveCacheRecord(cacheRecord: CacheRecord, responseScopes?: ScopeSet): void { + if (!cacheRecord) { + throw ClientAuthError.createNullOrUndefinedCacheRecord(); + } + + if (!!cacheRecord.account) { + this.saveAccount(cacheRecord.account); + } + + if (!!cacheRecord.idToken) { + this.saveCredential(cacheRecord.idToken); + } + + if (!!cacheRecord.accessToken) { + this.saveAccessToken(cacheRecord.accessToken, responseScopes); + } + + if (!!cacheRecord.refreshToken) { + this.saveCredential(cacheRecord.refreshToken); + } } /** * saves account into cache * @param account */ - saveAccount(account: AccountEntity): void { + private saveAccount(account: AccountEntity): void { const key = account.generateAccountKey(); - this.cacheStorage.setItem( + this.setItem( key, account, - CacheSchemaType.ACCOUNT, - this.inMemory + CacheSchemaType.ACCOUNT ); } @@ -91,13 +120,12 @@ export class UnifiedCacheManager implements ICacheManager { * saves credential - accessToken, idToken or refreshToken into cache * @param credential */ - saveCredential(credential: Credential): void { + private saveCredential(credential: CredentialEntity): void { const key = credential.generateCredentialKey(); - this.cacheStorage.setItem( + this.setItem( key, credential, - CacheSchemaType.CREDENTIAL, - this.inMemory + CacheSchemaType.CREDENTIAL ); } @@ -105,7 +133,7 @@ export class UnifiedCacheManager implements ICacheManager { * saves access token credential * @param credential */ - saveAccessToken(credential: AccessTokenEntity, responseScopes: ScopeSet): void { + private saveAccessToken(credential: AccessTokenEntity, responseScopes: ScopeSet): void { const currentTokenCache = this.getCredentialsFilteredBy({ clientId: credential.clientId, credentialType: CredentialType.ACCESS_TOKEN, @@ -130,11 +158,7 @@ export class UnifiedCacheManager implements ICacheManager { * @param key */ getAccount(key: string): AccountEntity { - const account = this.cacheStorage.getItem( - key, - CacheSchemaType.ACCOUNT, - this.inMemory - ) as AccountEntity; + const account = this.getItem(key, CacheSchemaType.ACCOUNT) as AccountEntity; return account; } @@ -142,12 +166,8 @@ export class UnifiedCacheManager implements ICacheManager { * retrieve a credential - accessToken, idToken or refreshToken; given the cache key * @param key */ - getCredential(key: string): Credential { - return this.cacheStorage.getItem( - key, - CacheSchemaType.CREDENTIAL, - this.inMemory - ) as Credential; + getCredential(key: string): CredentialEntity { + return this.getItem(key, CacheSchemaType.CREDENTIAL) as CredentialEntity; } /** @@ -177,25 +197,23 @@ export class UnifiedCacheManager implements ICacheManager { environment?: string, realm?: string ): AccountCache { - const allCacheKeys = this.cacheStorage.getKeys(); + const allCacheKeys = this.getKeys(); const matchingAccounts: AccountCache = {}; allCacheKeys.forEach((cacheKey) => { let matches: boolean = true; - // don't parse any non-credential type cache entities + // don't parse any non-account type cache entities if (CacheHelper.getCredentialType(cacheKey) !== Constants.NOT_DEFINED || CacheHelper.isAppMetadata(cacheKey)) { return; } - const entity: AccountEntity = this.cacheStorage.getItem(cacheKey, CacheSchemaType.ACCOUNT) as AccountEntity; + const entity: AccountEntity = this.getItem(cacheKey, CacheSchemaType.ACCOUNT) as AccountEntity; if (!StringUtils.isEmpty(homeAccountId)) { matches = CacheHelper.matchHomeAccountId(entity, homeAccountId); } if (!StringUtils.isEmpty(environment)) { - matches = - matches && - CacheHelper.matchEnvironment(entity, environment); + matches = matches && CacheHelper.matchEnvironment(entity, environment); } if (!StringUtils.isEmpty(realm)) { @@ -247,7 +265,7 @@ export class UnifiedCacheManager implements ICacheManager { realm?: string, target?: string ): CredentialCache { - const allCacheKeys = this.cacheStorage.getKeys(); + const allCacheKeys = this.getKeys(); const matchingCredentials: CredentialCache = { idTokens: {}, accessTokens: {}, @@ -262,19 +280,14 @@ export class UnifiedCacheManager implements ICacheManager { return; } - const entity: Credential = this.cacheStorage.getItem(cacheKey, CacheSchemaType.CREDENTIAL) as Credential; + const entity: CredentialEntity = this.getItem(cacheKey, CacheSchemaType.CREDENTIAL) as CredentialEntity; if (!StringUtils.isEmpty(homeAccountId)) { - matches = CacheHelper.matchHomeAccountId( - entity, - homeAccountId - ); + matches = CacheHelper.matchHomeAccountId(entity, homeAccountId); } if (!StringUtils.isEmpty(environment)) { - matches = - matches && - CacheHelper.matchEnvironment(entity, environment); + matches = matches && CacheHelper.matchEnvironment(entity, environment); } if (!StringUtils.isEmpty(realm)) { @@ -282,14 +295,11 @@ export class UnifiedCacheManager implements ICacheManager { } if (!StringUtils.isEmpty(credentialType)) { - matches = - matches && - CacheHelper.matchCredentialType(entity, credentialType); + matches = matches && CacheHelper.matchCredentialType(entity, credentialType); } if (!StringUtils.isEmpty(clientId)) { - matches = - matches && CacheHelper.matchClientId(entity, clientId); + matches = matches && CacheHelper.matchClientId(entity, clientId); } // idTokens do not have "target", target specific refreshTokens do exist for some types of authentication @@ -322,22 +332,18 @@ export class UnifiedCacheManager implements ICacheManager { */ removeAccount(accountKey: string): boolean { const account = this.getAccount(accountKey) as AccountEntity; - return ( - this.removeAccountContext(account) && - this.cacheStorage.removeItem( - accountKey, - CacheSchemaType.ACCOUNT, - this.inMemory - ) - ); + if (!account) { + throw ClientAuthError.createNoAccountFoundError(); + } + return (this.removeAccountContext(account) && this.removeItem(accountKey, CacheSchemaType.ACCOUNT)); } /** * returns a boolean if the given account is removed * @param account */ - private removeAccountContext(account: AccountEntity): boolean { - const allCacheKeys = this.cacheStorage.getKeys(); + removeAccountContext(account: AccountEntity): boolean { + const allCacheKeys = this.getKeys(); const accountId = account.generateAccountId(); allCacheKeys.forEach((cacheKey) => { @@ -346,16 +352,9 @@ export class UnifiedCacheManager implements ICacheManager { return; } - const cacheEntity: Credential = this.cacheStorage.getItem( - cacheKey, - CacheSchemaType.CREDENTIAL, - this.inMemory - ) as Credential; + const cacheEntity: CredentialEntity = this.getItem(cacheKey, CacheSchemaType.CREDENTIAL) as CredentialEntity; - if ( - !!cacheEntity && - accountId === cacheEntity.generateAccountId() - ) { + if (!!cacheEntity && accountId === cacheEntity.generateAccountId()) { this.removeCredential(cacheEntity); } }); @@ -367,12 +366,35 @@ export class UnifiedCacheManager implements ICacheManager { * returns a boolean if the given credential is removed * @param credential */ - removeCredential(credential: Credential): boolean { + removeCredential(credential: CredentialEntity): boolean { const key = credential.generateCredentialKey(); - return this.cacheStorage.removeItem( - key, - CacheSchemaType.CREDENTIAL, - this.inMemory - ); + return this.removeItem(key, CacheSchemaType.CREDENTIAL); + } +} + +export class DefaultStorageClass extends CacheManager { + setItem(key: string, value: string | object, type?: string): void { + const notImplErr = "Storage interface - setItem() has not been implemented for the cacheStorage interface."; + throw AuthError.createUnexpectedError(notImplErr); + } + getItem(key: string, type?: string): string | object { + const notImplErr = "Storage interface - getItem() has not been implemented for the cacheStorage interface."; + throw AuthError.createUnexpectedError(notImplErr); + } + removeItem(key: string, type?: string): boolean { + const notImplErr = "Storage interface - removeItem() has not been implemented for the cacheStorage interface."; + throw AuthError.createUnexpectedError(notImplErr); + } + containsKey(key: string, type?: string): boolean { + const notImplErr = "Storage interface - containsKey() has not been implemented for the cacheStorage interface."; + throw AuthError.createUnexpectedError(notImplErr); + } + getKeys(): string[] { + const notImplErr = "Storage interface - getKeys() has not been implemented for the cacheStorage interface."; + throw AuthError.createUnexpectedError(notImplErr); + } + clear(): void { + const notImplErr = "Storage interface - clear() has not been implemented for the cacheStorage interface."; + throw AuthError.createUnexpectedError(notImplErr); } } diff --git a/lib/msal-common/src/cache/entities/AccessTokenEntity.ts b/lib/msal-common/src/cache/entities/AccessTokenEntity.ts index 42445f3394..cbbdf80e34 100644 --- a/lib/msal-common/src/cache/entities/AccessTokenEntity.ts +++ b/lib/msal-common/src/cache/entities/AccessTokenEntity.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. */ -import { Credential } from "./Credential"; +import { CredentialEntity } from "./CredentialEntity"; import { CredentialType } from "../../utils/Constants"; import { TimeUtils } from "../../utils/TimeUtils"; /** * ACCESS_TOKEN Credential Type */ -export class AccessTokenEntity extends Credential { +export class AccessTokenEntity extends CredentialEntity { realm: string; target: string; cachedAt: string; diff --git a/lib/msal-common/src/cache/entities/CacheRecord.ts b/lib/msal-common/src/cache/entities/CacheRecord.ts index 58486b019e..9e4fd2427a 100644 --- a/lib/msal-common/src/cache/entities/CacheRecord.ts +++ b/lib/msal-common/src/cache/entities/CacheRecord.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. */ -import { AccountEntity } from "./AccountEntity"; import { IdTokenEntity } from "./IdTokenEntity"; import { AccessTokenEntity } from "./AccessTokenEntity"; import { RefreshTokenEntity } from "./RefreshTokenEntity"; +import { AccountEntity } from "./AccountEntity"; export class CacheRecord { account: AccountEntity; @@ -14,7 +14,7 @@ export class CacheRecord { accessToken: AccessTokenEntity; refreshToken: RefreshTokenEntity; - constructor(accountEntity: AccountEntity, idTokenEntity: IdTokenEntity, accessTokenEntity: AccessTokenEntity, refreshTokenEntity: RefreshTokenEntity) { + constructor(accountEntity?: AccountEntity, idTokenEntity?: IdTokenEntity, accessTokenEntity?: AccessTokenEntity, refreshTokenEntity?: RefreshTokenEntity) { this.account = accountEntity; this.idToken = idTokenEntity; this.accessToken = accessTokenEntity; diff --git a/lib/msal-common/src/cache/entities/Credential.ts b/lib/msal-common/src/cache/entities/CredentialEntity.ts similarity index 98% rename from lib/msal-common/src/cache/entities/Credential.ts rename to lib/msal-common/src/cache/entities/CredentialEntity.ts index 4e6592b7b9..3601ab6c09 100644 --- a/lib/msal-common/src/cache/entities/Credential.ts +++ b/lib/msal-common/src/cache/entities/CredentialEntity.ts @@ -8,7 +8,7 @@ import { Separators, CredentialType, CacheType } from "../../utils/Constants"; /** * Base type for credentials to be stored in the cache: eg: ACCESS_TOKEN, ID_TOKEN etc */ -export class Credential { +export class CredentialEntity { homeAccountId: string; environment: string; credentialType: CredentialType; diff --git a/lib/msal-common/src/cache/entities/IdTokenEntity.ts b/lib/msal-common/src/cache/entities/IdTokenEntity.ts index d7765e77be..0f8558e44f 100644 --- a/lib/msal-common/src/cache/entities/IdTokenEntity.ts +++ b/lib/msal-common/src/cache/entities/IdTokenEntity.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. */ -import { Credential } from "./Credential"; +import { CredentialEntity } from "./CredentialEntity"; import { CredentialType } from "../../utils/Constants"; /** * ID_TOKEN Cache */ -export class IdTokenEntity extends Credential { +export class IdTokenEntity extends CredentialEntity { realm: string; /** diff --git a/lib/msal-common/src/cache/entities/RefreshTokenEntity.ts b/lib/msal-common/src/cache/entities/RefreshTokenEntity.ts index ac7ae2e08c..6fb81ad6e5 100644 --- a/lib/msal-common/src/cache/entities/RefreshTokenEntity.ts +++ b/lib/msal-common/src/cache/entities/RefreshTokenEntity.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. */ -import { Credential } from "./Credential"; +import { CredentialEntity } from "./CredentialEntity"; import { CredentialType } from "../../utils/Constants"; /** * REFRESH_TOKEN Cache */ -export class RefreshTokenEntity extends Credential { +export class RefreshTokenEntity extends CredentialEntity { familyId?: string; /** diff --git a/lib/msal-common/src/cache/interface/ICacheManager.ts b/lib/msal-common/src/cache/interface/ICacheManager.ts index e593cd8634..d66b21ea8e 100644 --- a/lib/msal-common/src/cache/interface/ICacheManager.ts +++ b/lib/msal-common/src/cache/interface/ICacheManager.ts @@ -3,27 +3,29 @@ * Licensed under the MIT License. */ -import { AccountEntity } from "../entities/AccountEntity"; -import { Credential } from "../entities/Credential"; +import { CredentialEntity } from "../entities/CredentialEntity"; import { AccountCache, CredentialCache, AccountFilter, - CredentialFilter, + CredentialFilter } from "../utils/CacheTypes"; +import { CacheRecord } from "../entities/CacheRecord"; +import { ScopeSet } from "../../request/ScopeSet"; +import { IAccount } from "../../account/IAccount"; +import { AccountEntity } from "../entities/AccountEntity"; export interface ICacheManager { /** - * saves account into cache - * @param account + * Returns all accounts in cache */ - saveAccount(account: AccountEntity): void; + getAllAccounts(): IAccount[]; /** - * saves credential - accessToken, idToken or refreshToken into cache - * @param credential + * saves a cache record + * @param cacheRecord */ - saveCredential(credential: Credential): void; + saveCacheRecord(cacheRecord: CacheRecord, responseScopes: ScopeSet): void; /** * Given account key retrieve an account @@ -35,7 +37,7 @@ export interface ICacheManager { * retrieve a credential - accessToken, idToken or refreshToken; given the cache key * @param key */ - getCredential(key: string): Credential; + getCredential(key: string): CredentialEntity; /** * retrieve accounts matching all provided filters; if no filter is set, get all accounts @@ -62,9 +64,15 @@ export interface ICacheManager { */ removeAccount(accountKey: string): boolean; + /** + * returns a boolean if the given account is removed + * @param account + */ + removeAccountContext(account: AccountEntity): boolean; + /** * returns a boolean if the given credential is removed * @param credential */ - removeCredential(credential: Credential): boolean; + removeCredential(credential: CredentialEntity): boolean; } diff --git a/lib/msal-common/src/cache/interface/ICacheStorage.ts b/lib/msal-common/src/cache/interface/ICacheStorage.ts deleted file mode 100644 index 9afaf429c8..0000000000 --- a/lib/msal-common/src/cache/interface/ICacheStorage.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -import { InMemoryCache } from "../utils/CacheTypes"; - -/** - * Interface class which implement cache storage functions used by MSAL to perform validity checks, and store tokens. - */ -export interface ICacheStorage { - /** - * Function to read serialized Cache from disk - * @param key - * @param value - */ - getCache(): object; - - /** - * Function to write serialized Cache to disk - * @param cache - */ - setCache(cache: InMemoryCache): void; - - /** - * Function to set item in cache. - * @param key - * @param value - */ - setItem(key: string, value: string | object, type?: string, inMemory?: boolean): void; - - /** - * Function which retrieves item from cache. - * @param key - */ - getItem(key: string, type?: string, inMemory?: boolean): string | object; - - /** - * Function to remove an item from cache given its key. - * @param key - */ - removeItem(key: string, type?: string, inMemory?: boolean): boolean; - - /** - * Function which returns boolean whether cache contains a specific key. - * @param key - */ - containsKey(key: string, type?: string): boolean; - - /** - * Function which retrieves all current keys from the cache. - */ - getKeys(): string[]; - - /** - * Function which clears cache. - */ - clear(): void; -} diff --git a/lib/msal-common/src/cache/serialize/Deserializer.ts b/lib/msal-common/src/cache/serialize/Deserializer.ts deleted file mode 100644 index 8ffbb1449f..0000000000 --- a/lib/msal-common/src/cache/serialize/Deserializer.ts +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -import { AccountEntity } from "../entities/AccountEntity"; -import { IdTokenEntity } from "../entities/IdTokenEntity"; -import { AccessTokenEntity } from "../entities/AccessTokenEntity"; -import { RefreshTokenEntity } from "../entities/RefreshTokenEntity"; -import { AppMetadataEntity } from "../entities/AppMetadataEntity"; -import { CacheHelper } from "../utils/CacheHelper"; -import { AccountCacheMaps, IdTokenCacheMaps, AccessTokenCacheMaps, RefreshTokenCacheMaps, AppMetadataCacheMaps } from "./JsonKeys"; -import { AccountCache, IdTokenCache, AccessTokenCache, RefreshTokenCache, AppMetadataCache, InMemoryCache, JsonCache } from "../utils/CacheTypes"; -import { StringDict } from "../../utils/MsalTypes"; -import { StringUtils } from "../../utils/StringUtils"; - -// TODO: Can we write this with Generics? -export class Deserializer { - - /** - * Parse the JSON blob in memory and deserialize the content - * @param cachedJson - */ - static deserializeJSONBlob(jsonFile: string): JsonCache { - const deserializedCache = StringUtils.isEmpty(jsonFile) - ? {} - : JSON.parse(jsonFile); - return deserializedCache; - } - - /** - * Deserializes accounts to AccountEntity objects - * @param accounts - */ - static deserializeAccounts(accounts: StringDict): AccountCache { - const accountObjects = {}; - Object.keys(accounts).map(function (key) { - const mappedAcc = CacheHelper.renameKeys( - accounts[key], - AccountCacheMaps.fromCacheMap - ); - const account: AccountEntity = new AccountEntity(); - CacheHelper.toObject(account, mappedAcc); - accountObjects[key] = account; - }); - - return accountObjects; - } - - /** - * Deserializes id tokens to IdTokenEntity objects - * @param idTokens - */ - static deserializeIdTokens(idTokens: StringDict): IdTokenCache { - const idObjects = {}; - Object.keys(idTokens).map(function (key) { - const mappedIdT = CacheHelper.renameKeys( - idTokens[key], - IdTokenCacheMaps.fromCacheMap - ); - const idToken: IdTokenEntity = new IdTokenEntity(); - CacheHelper.toObject(idToken, mappedIdT); - idObjects[key] = idToken; - }); - - return idObjects; - } - - /** - * Deserializes access tokens to AccessTokenEntity objects - * @param accessTokens - */ - static deserializeAccessTokens(accessTokens: StringDict): AccessTokenCache { - const atObjects = {}; - Object.keys(accessTokens).map(function (key) { - const mappedAT = CacheHelper.renameKeys( - accessTokens[key], - AccessTokenCacheMaps.fromCacheMap - ); - const accessToken: AccessTokenEntity = new AccessTokenEntity(); - CacheHelper.toObject(accessToken, mappedAT); - atObjects[key] = accessToken; - }); - - return atObjects; - } - - /** - * Deserializes refresh tokens to RefreshTokenEntity objects - * @param refreshTokens - */ - static deserializeRefreshTokens(refreshTokens: StringDict): RefreshTokenCache { - const rtObjects = {}; - Object.keys(refreshTokens).map(function (key) { - const mappedRT = CacheHelper.renameKeys( - refreshTokens[key], - RefreshTokenCacheMaps.fromCacheMap - ); - const refreshToken: RefreshTokenEntity = new RefreshTokenEntity(); - CacheHelper.toObject(refreshToken, mappedRT); - rtObjects[key] = refreshToken; - }); - - return rtObjects; - } - - /** - * Deserializes appMetadata to AppMetaData objects - * @param appMetadata - */ - static deserializeAppMetadata(appMetadata: StringDict): AppMetadataCache { - const appMetadataObjects = {}; - Object.keys(appMetadata).map(function (key) { - const mappedAmd = CacheHelper.renameKeys( - appMetadata[key], - AppMetadataCacheMaps.fromCacheMap - ); - const amd: AppMetadataEntity = new AppMetadataEntity(); - CacheHelper.toObject(amd, mappedAmd); - appMetadataObjects[key] = amd; - }); - - return appMetadataObjects; - } - - /** - * Deserialize an inMemory Cache - * @param jsonCache - */ - static deserializeAllCache(jsonCache: JsonCache): InMemoryCache { - return { - accounts: jsonCache.Account? this.deserializeAccounts(jsonCache.Account): {}, - idTokens: jsonCache.IdToken? this.deserializeIdTokens(jsonCache.IdToken): {}, - accessTokens: jsonCache.AccessToken? this.deserializeAccessTokens(jsonCache.AccessToken) : {}, - refreshTokens: jsonCache.RefreshToken? this.deserializeRefreshTokens(jsonCache.RefreshToken): {}, - appMetadata: jsonCache.AppMetadata? this.deserializeAppMetadata(jsonCache.AppMetadata): {} - }; - } -} diff --git a/lib/msal-common/src/cache/utils/CacheHelper.ts b/lib/msal-common/src/cache/utils/CacheHelper.ts index d308af6315..3b22315cc8 100644 --- a/lib/msal-common/src/cache/utils/CacheHelper.ts +++ b/lib/msal-common/src/cache/utils/CacheHelper.ts @@ -12,7 +12,7 @@ import { } from "../../utils/Constants"; import { IAccount } from "../../account/IAccount"; import { AccountEntity } from "../entities/AccountEntity"; -import { Credential } from "../entities/Credential"; +import { CredentialEntity } from "../entities/CredentialEntity"; import { ScopeSet } from "../../request/ScopeSet"; export class CacheHelper { @@ -62,7 +62,7 @@ export class CacheHelper { * @param homeAccountId */ static matchHomeAccountId( - entity: AccountEntity | Credential, + entity: AccountEntity | CredentialEntity, homeAccountId: string ): boolean { return homeAccountId === entity.homeAccountId; @@ -75,7 +75,7 @@ export class CacheHelper { * // TODO: Add Cloud specific aliases based on current cloud */ static matchEnvironment( - entity: AccountEntity | Credential, + entity: AccountEntity | CredentialEntity, environment: string ): boolean { if ( @@ -93,7 +93,7 @@ export class CacheHelper { * @param entity * @param credentialType */ - static matchCredentialType(entity: Credential, credentialType: string): boolean { + static matchCredentialType(entity: CredentialEntity, credentialType: string): boolean { return credentialType.toLowerCase() === entity.credentialType.toLowerCase(); } @@ -102,7 +102,7 @@ export class CacheHelper { * @param entity * @param clientId */ - static matchClientId(entity: Credential, clientId: string): boolean { + static matchClientId(entity: CredentialEntity, clientId: string): boolean { return clientId === entity.clientId; } @@ -111,7 +111,7 @@ export class CacheHelper { * @param entity * @param realm */ - static matchRealm(entity: AccountEntity | Credential, realm: string): boolean { + static matchRealm(entity: AccountEntity | CredentialEntity, realm: string): boolean { return realm === entity.realm; } @@ -120,7 +120,7 @@ export class CacheHelper { * @param entity * @param target */ - static matchTarget(entity: Credential, target: string): boolean { + static matchTarget(entity: CredentialEntity, target: string): boolean { const entityScopeSet: ScopeSet = ScopeSet.fromString(entity.target); const requestTargetScopeSet: ScopeSet = ScopeSet.fromString(target); return entityScopeSet.containsScopeSet(requestTargetScopeSet); diff --git a/lib/msal-common/src/cache/utils/CacheTypes.ts b/lib/msal-common/src/cache/utils/CacheTypes.ts index d102d9fd70..85e2b7894e 100644 --- a/lib/msal-common/src/cache/utils/CacheTypes.ts +++ b/lib/msal-common/src/cache/utils/CacheTypes.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -import { StringDict } from "../../utils/MsalTypes"; - import { AccountEntity } from "../entities/AccountEntity"; import { IdTokenEntity } from "../entities/IdTokenEntity"; import { AccessTokenEntity } from "../entities/AccessTokenEntity"; @@ -22,22 +20,6 @@ export type CredentialCache = { refreshTokens: RefreshTokenCache; }; -export type JsonCache = { - Account?: StringDict; - IdToken?: StringDict; - AccessToken?: StringDict; - RefreshToken?: StringDict; - AppMetadata?: StringDict; -}; - -export type InMemoryCache = { - accounts: AccountCache; - idTokens: IdTokenCache; - accessTokens: AccessTokenCache; - refreshTokens: RefreshTokenCache; - appMetadata: AppMetadataCache; -}; - /** * Account: -- */ diff --git a/lib/msal-common/src/client/AuthorizationCodeClient.ts b/lib/msal-common/src/client/AuthorizationCodeClient.ts index 2ec6707d79..6bce7a1862 100644 --- a/lib/msal-common/src/client/AuthorizationCodeClient.ts +++ b/lib/msal-common/src/client/AuthorizationCodeClient.ts @@ -36,7 +36,6 @@ export class AuthorizationCodeClient extends BaseClient { * @param request */ async getAuthCodeUrl(request: AuthorizationUrlRequest): Promise { - const queryString = this.createAuthCodeUrlQueryString(request); return `${this.defaultAuthority.authorizationEndpoint}?${queryString}`; } @@ -54,7 +53,7 @@ export class AuthorizationCodeClient extends BaseClient { const responseHandler = new ResponseHandler( this.config.authOptions.clientId, - this.unifiedCacheManager, + this.cacheManager, this.cryptoUtils, this.logger ); diff --git a/lib/msal-common/src/client/BaseClient.ts b/lib/msal-common/src/client/BaseClient.ts index ae9df5727f..bcd7ebdb48 100644 --- a/lib/msal-common/src/client/BaseClient.ts +++ b/lib/msal-common/src/client/BaseClient.ts @@ -4,7 +4,6 @@ */ import { ClientConfiguration, buildClientConfiguration } from "../config/ClientConfiguration"; -import { ICacheStorage } from "../cache/interface/ICacheStorage"; import { INetworkModule } from "../network/INetworkModule"; import { ICrypto } from "../crypto/ICrypto"; import { Authority } from "../authority/Authority"; @@ -13,11 +12,7 @@ import { AADServerParamKeys, Constants, HeaderNames } from "../utils/Constants"; import { NetworkResponse } from "../network/NetworkManager"; import { ServerAuthorizationTokenResponse } from "../server/ServerAuthorizationTokenResponse"; import { B2cAuthority } from "../authority/B2cAuthority"; -import { UnifiedCacheManager } from "../cache/UnifiedCacheManager"; -import { AccountEntity } from "../cache/entities/AccountEntity"; -import { IAccount } from "../account/IAccount"; -import { AccountCache } from "../cache/utils/CacheTypes"; -import { CacheHelper } from "../cache/utils/CacheHelper"; +import { CacheManager } from "../cache/CacheManager"; /** * Base application class which will construct requests to send to and handle responses from the Microsoft STS using the authorization code flow. @@ -33,17 +28,11 @@ export abstract class BaseClient { protected cryptoUtils: ICrypto; // Storage Interface - protected cacheStorage: ICacheStorage; + protected cacheManager: CacheManager; // Network Interface protected networkClient: INetworkModule; - // Helper API object for serialized cache operations - protected unifiedCacheManager: UnifiedCacheManager; - - // Account object - protected account: AccountEntity; - // Default authority object protected defaultAuthority: Authority; @@ -58,21 +47,12 @@ export abstract class BaseClient { this.cryptoUtils = this.config.cryptoInterface; // Initialize storage interface - this.cacheStorage = this.config.storageInterface; - - // Initialize serialized cache manager - this.unifiedCacheManager = new UnifiedCacheManager( - this.cacheStorage, - this.config.authOptions.clientId, - this.config.systemOptions.storeInMemory - ); + this.cacheManager = this.config.storageInterface; // Set the network interface this.networkClient = this.config.networkInterface; - B2cAuthority.setKnownAuthorities( - this.config.authOptions.knownAuthorities - ); + B2cAuthority.setKnownAuthorities(this.config.authOptions.knownAuthorities); this.defaultAuthority = this.config.authOptions.authority; } @@ -116,22 +96,4 @@ export abstract class BaseClient { headers: headers, }); } - - /** - * Get all currently signed in accounts. - */ - public getAllAccounts(): IAccount[] { - const currentAccounts: AccountCache = this.unifiedCacheManager.getAllAccounts(); - const accountValues: AccountEntity[] = Object.values(currentAccounts); - const numAccounts = accountValues.length; - if (numAccounts < 1) { - return null; - } else { - const allAccounts = accountValues.map((value) => { - const accountObj: AccountEntity = JSON.parse(JSON.stringify(value)); - return CacheHelper.toIAccount(accountObj); - }); - return allAccounts; - } - } } diff --git a/lib/msal-common/src/client/RefreshTokenClient.ts b/lib/msal-common/src/client/RefreshTokenClient.ts index 9e35f96a79..41606ef53c 100644 --- a/lib/msal-common/src/client/RefreshTokenClient.ts +++ b/lib/msal-common/src/client/RefreshTokenClient.ts @@ -28,7 +28,7 @@ export class RefreshTokenClient extends BaseClient { const responseHandler = new ResponseHandler( this.config.authOptions.clientId, - this.unifiedCacheManager, + this.cacheManager, this.cryptoUtils, this.logger ); diff --git a/lib/msal-common/src/client/SPAClient.ts b/lib/msal-common/src/client/SPAClient.ts index 1c890cf4f7..a1ddad3d56 100644 --- a/lib/msal-common/src/client/SPAClient.ts +++ b/lib/msal-common/src/client/SPAClient.ts @@ -26,12 +26,12 @@ import { AuthenticationResult } from "../response/AuthenticationResult"; import { Authority } from "../authority/Authority"; import { SilentFlowRequest } from "../request/SilentFlowRequest"; import { CacheHelper } from "../cache/utils/CacheHelper"; -import { AccountEntity } from "../cache/entities/AccountEntity"; import { IdTokenEntity } from "../cache/entities/IdTokenEntity"; import { RefreshTokenEntity } from "../cache/entities/RefreshTokenEntity"; import { AccessTokenEntity } from "../cache/entities/AccessTokenEntity"; import { IAccount } from "../account/IAccount"; import { CredentialFilter, CredentialCache } from "../cache/utils/CacheTypes"; +import { AccountEntity } from "../cache/entities/AccountEntity"; /** * SPAClient class @@ -55,9 +55,7 @@ export class SPAClient extends BaseClient { * Including any SSO parameters (account, sid, login_hint) will short circuit the authentication and allow you to retrieve a code without interaction. * @param request */ - async createUrl( - request: AuthorizationUrlRequest - ): Promise { + async createUrl(request: AuthorizationUrlRequest): Promise { // Initialize authority or use default, and perform discovery endpoint check. const acquireTokenAuthority = request && request.authority @@ -216,7 +214,7 @@ export class SPAClient extends BaseClient { const requestScopes = new ScopeSet(request.scopes || []); // Get current cached tokens - const cachedAccount = this.unifiedCacheManager.getAccount(CacheHelper.generateAccountCacheKey(request.account)); + const cachedAccount = this.cacheManager.getAccount(CacheHelper.generateAccountCacheKey(request.account)); const homeAccountId = cachedAccount.homeAccountId; const env = cachedAccount.environment; @@ -288,7 +286,7 @@ export class SPAClient extends BaseClient { */ async logout(account: IAccount, acquireTokenAuthority: Authority): Promise { // Clear current account. - this.unifiedCacheManager.removeAccount(CacheHelper.generateAccountCacheKey(account)); + this.cacheManager.removeAccount(CacheHelper.generateAccountCacheKey(account)); // Get postLogoutRedirectUri. let postLogoutRedirectUri = ""; try { @@ -331,8 +329,7 @@ export class SPAClient extends BaseClient { */ public handleFragmentResponse(hashFragment: string, cachedState: string): string { // Handle responses. - const responseHandler = new ResponseHandler(this.config.authOptions.clientId, this.unifiedCacheManager, this.cryptoUtils, this.logger); - + const responseHandler = new ResponseHandler(this.config.authOptions.clientId, this.cacheManager, this.cryptoUtils, this.logger); // Deserialize hash fragment response parameters. const hashUrlString = new UrlString(hashFragment); const serverParams = hashUrlString.getDeserializedHash(); @@ -358,7 +355,7 @@ export class SPAClient extends BaseClient { this.config.authOptions.clientId, inputRealm ); - return this.unifiedCacheManager.getCredential(idTokenKey) as IdTokenEntity; + return this.cacheManager.getCredential(idTokenKey) as IdTokenEntity; } /** @@ -375,7 +372,7 @@ export class SPAClient extends BaseClient { realm: inputRealm, target: scopes.printScopes() }; - const credentialCache: CredentialCache = this.unifiedCacheManager.getCredentialsFilteredBy(accessTokenFilter); + const credentialCache: CredentialCache = this.cacheManager.getCredentialsFilteredBy(accessTokenFilter); const accessTokens = Object.values(credentialCache.accessTokens); if (accessTokens.length > 1) { // TODO: Figure out what to throw or return here. @@ -396,7 +393,7 @@ export class SPAClient extends BaseClient { CredentialType.REFRESH_TOKEN, this.config.authOptions.clientId ); - return this.unifiedCacheManager.getCredential(refreshTokenKey) as RefreshTokenEntity; + return this.cacheManager.getCredential(refreshTokenKey) as RefreshTokenEntity; } /** @@ -431,7 +428,7 @@ export class SPAClient extends BaseClient { }); // Create response handler - const responseHandler = new ResponseHandler(this.config.authOptions.clientId, this.unifiedCacheManager, this.cryptoUtils, this.logger); + const responseHandler = new ResponseHandler(this.config.authOptions.clientId, this.cacheManager, this.cryptoUtils, this.logger); // Validate response. This function throws a server error if an error is returned by the server. responseHandler.validateTokenResponse(acquiredTokenResponse.body); // Return token response with given parameters @@ -520,7 +517,7 @@ export class SPAClient extends BaseClient { * @returns {@link Account} - the account object stored in MSAL */ getAccount(homeAccountIdentifier: string, env?: string, rlm?: string): AccountEntity { - const accountCache = this.unifiedCacheManager.getAccountsFilteredBy({ + const accountCache = this.cacheManager.getAccountsFilteredBy({ homeAccountId: homeAccountIdentifier, environment: env, realm: rlm diff --git a/lib/msal-common/src/client/SilentFlowClient.ts b/lib/msal-common/src/client/SilentFlowClient.ts index 6b486c1b23..1eb4fc4def 100644 --- a/lib/msal-common/src/client/SilentFlowClient.ts +++ b/lib/msal-common/src/client/SilentFlowClient.ts @@ -40,7 +40,7 @@ export class SilentFlowClient extends BaseClient { const requestScopes = new ScopeSet(request.scopes || []); // fetch account const accountKey: string = CacheHelper.generateAccountCacheKey(request.account); - const cachedAccount = this.unifiedCacheManager.getAccount(accountKey); + const cachedAccount = this.cacheManager.getAccount(accountKey); const homeAccountId = cachedAccount.homeAccountId; const environment = cachedAccount.environment; @@ -96,7 +96,7 @@ export class SilentFlowClient extends BaseClient { this.config.authOptions.clientId, inputRealm ); - return this.unifiedCacheManager.getCredential(idTokenKey) as IdTokenEntity; + return this.cacheManager.getCredential(idTokenKey) as IdTokenEntity; } /** @@ -113,7 +113,7 @@ export class SilentFlowClient extends BaseClient { realm: inputRealm, target: scopes.printScopes() }; - const credentialCache: CredentialCache = this.unifiedCacheManager.getCredentialsFilteredBy(accessTokenFilter); + const credentialCache: CredentialCache = this.cacheManager.getCredentialsFilteredBy(accessTokenFilter); const accessTokens = Object.values(credentialCache.accessTokens); if (accessTokens.length > 1) { // TODO: Figure out what to throw or return here. @@ -134,7 +134,7 @@ export class SilentFlowClient extends BaseClient { CredentialType.REFRESH_TOKEN, this.config.authOptions.clientId ); - return this.unifiedCacheManager.getCredential(refreshTokenKey) as RefreshTokenEntity; + return this.cacheManager.getCredential(refreshTokenKey) as RefreshTokenEntity; } /** diff --git a/lib/msal-common/src/config/ClientConfiguration.ts b/lib/msal-common/src/config/ClientConfiguration.ts index 3664de6498..4eb7710383 100644 --- a/lib/msal-common/src/config/ClientConfiguration.ts +++ b/lib/msal-common/src/config/ClientConfiguration.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -import { ICacheStorage } from "../cache/interface/ICacheStorage"; import { INetworkModule } from "../network/INetworkModule"; import { ICrypto, PkceCodes } from "../crypto/ICrypto"; import { AuthError } from "../error/AuthError"; @@ -11,6 +10,7 @@ import { ILoggerCallback, LogLevel } from "../logger/Logger"; import { Constants } from "../utils/Constants"; import { version } from "../../package.json"; import { Authority } from "../authority/Authority"; +import { CacheManager, DefaultStorageClass } from "../cache/CacheManager"; // Token renewal offset default in seconds const DEFAULT_TOKEN_RENEWAL_OFFSET_SEC = 300; @@ -28,7 +28,7 @@ export type ClientConfiguration = { authOptions: AuthOptions, systemOptions?: SystemOptions, loggerOptions?: LoggerOptions, - storageInterface?: ICacheStorage, + storageInterface?: CacheManager, networkInterface?: INetworkModule, cryptoInterface?: ICrypto, libraryInfo?: LibraryInfo @@ -67,7 +67,6 @@ export type TelemetryOptions = { * - telemetry - Telemetry options for library network requests */ export type SystemOptions = { - storeInMemory?: boolean; tokenRenewalOffsetSeconds?: number; telemetry?: TelemetryOptions; }; @@ -100,7 +99,6 @@ const DEFAULT_AUTH_OPTIONS: AuthOptions = { }; export const DEFAULT_SYSTEM_OPTIONS: SystemOptions = { - storeInMemory: true, tokenRenewalOffsetSeconds: DEFAULT_TOKEN_RENEWAL_OFFSET_SEC, telemetry: null }; @@ -113,41 +111,6 @@ const DEFAULT_LOGGER_IMPLEMENTATION: LoggerOptions = { logLevel: LogLevel.Info }; -const DEFAULT_STORAGE_IMPLEMENTATION: ICacheStorage = { - clear: () => { - const notImplErr = "Storage interface - clear() has not been implemented for the cacheStorage interface."; - throw AuthError.createUnexpectedError(notImplErr); - }, - containsKey: (): boolean => { - const notImplErr = "Storage interface - containsKey() has not been implemented for the cacheStorage interface."; - throw AuthError.createUnexpectedError(notImplErr); - }, - getItem: (): object => { - const notImplErr = "Storage interface - getItem() has not been implemented for the cacheStorage interface."; - throw AuthError.createUnexpectedError(notImplErr); - }, - getKeys: (): string[] => { - const notImplErr = "Storage interface - getKeys() has not been implemented for the cacheStorage interface."; - throw AuthError.createUnexpectedError(notImplErr); - }, - removeItem: () => { - const notImplErr = "Storage interface - removeItem() has not been implemented for the cacheStorage interface."; - throw AuthError.createUnexpectedError(notImplErr); - }, - setItem: () => { - const notImplErr = "Storage interface - setItem() has not been implemented for the cacheStorage interface."; - throw AuthError.createUnexpectedError(notImplErr); - }, - getCache: (): object => { - const notImplErr = "Storage interface - getCache() has not been implemented for the cacheStorage interface."; - throw AuthError.createUnexpectedError(notImplErr); - }, - setCache: () => { - const notImplErr = "Storage interface - setCache() has not been implemented for the cacheStorage interface."; - throw AuthError.createUnexpectedError(notImplErr); - } -}; - const DEFAULT_NETWORK_IMPLEMENTATION: INetworkModule = { async sendGetRequestAsync(): Promise { const notImplErr = "Network interface - sendGetRequestAsync() has not been implemented"; @@ -206,7 +169,7 @@ export function buildClientConfiguration( authOptions: { ...DEFAULT_AUTH_OPTIONS, ...userAuthOptions }, systemOptions: { ...DEFAULT_SYSTEM_OPTIONS, ...userSystemOptions }, loggerOptions: { ...DEFAULT_LOGGER_IMPLEMENTATION, ...userLoggerOption }, - storageInterface: storageImplementation || DEFAULT_STORAGE_IMPLEMENTATION, + storageInterface: storageImplementation || new DefaultStorageClass(), networkInterface: networkImplementation || DEFAULT_NETWORK_IMPLEMENTATION, cryptoInterface: cryptoImplementation || DEFAULT_CRYPTO_IMPLEMENTATION, libraryInfo: { ...DEFAULT_LIBRARY_INFO, ...libraryInfo } diff --git a/lib/msal-common/src/error/ClientAuthError.ts b/lib/msal-common/src/error/ClientAuthError.ts index d941aef4aa..761a407105 100644 --- a/lib/msal-common/src/error/ClientAuthError.ts +++ b/lib/msal-common/src/error/ClientAuthError.ts @@ -110,6 +110,14 @@ export const ClientAuthErrorMessage = { NoAccountInSilentRequest: { code: "no_account_in_silent_request", desc: "Please pass an account object, silent flow is not supported without account information" + }, + invalidCacheRecord: { + code: "invalid_cache_record", + desc: "Cache record object was null or undefined." + }, + noAccountFound: { + code: "no_account_found", + desc: "No account found in cache for given key." } }; @@ -320,4 +328,18 @@ export class ClientAuthError extends AuthError { static createNoAccountInSilentRequestError(): ClientAuthError { return new ClientAuthError(ClientAuthErrorMessage.NoAccountInSilentRequest.code, `${ClientAuthErrorMessage.NoAccountInSilentRequest.desc}`); } + + /** + * Throws error when cache record is null or undefined. + */ + static createNullOrUndefinedCacheRecord(): ClientAuthError { + return new ClientAuthError(ClientAuthErrorMessage.invalidCacheRecord.code, ClientAuthErrorMessage.invalidCacheRecord.desc); + } + + /** + * Throws error when account is not found in cache. + */ + static createNoAccountFoundError(): ClientAuthError { + return new ClientAuthError(ClientAuthErrorMessage.noAccountFound.code, ClientAuthErrorMessage.noAccountFound.desc); + } } diff --git a/lib/msal-common/src/index.ts b/lib/msal-common/src/index.ts index c469c28cbd..b2cf37e8cb 100644 --- a/lib/msal-common/src/index.ts +++ b/lib/msal-common/src/index.ts @@ -18,13 +18,10 @@ export { B2cAuthority } from "./authority/B2cAuthority"; export { AuthorityFactory } from "./authority/AuthorityFactory"; export { AuthorityType } from "./authority/AuthorityType"; // Cache -export { ICacheStorage } from "./cache/interface/ICacheStorage"; -export { UnifiedCacheManager } from "./cache/UnifiedCacheManager"; -export { JsonCache, InMemoryCache } from "./cache/utils/CacheTypes"; -export { Serializer } from "./cache/serialize/Serializer"; -export { Deserializer } from "./cache/serialize/Deserializer"; +export { CacheManager } from "./cache/CacheManager"; +export { AccountCache, AccessTokenCache, IdTokenCache, RefreshTokenCache, AppMetadataCache } from "./cache/utils/CacheTypes"; export { CacheHelper } from "./cache/utils/CacheHelper"; -export { Credential } from "./cache/entities/Credential"; +export { CredentialEntity } from "./cache/entities/CredentialEntity"; export { AppMetadataEntity } from "./cache/entities/AppMetadataEntity"; export { AccountEntity } from "./cache/entities/AccountEntity"; export { IdTokenEntity } from "./cache/entities/IdTokenEntity"; diff --git a/lib/msal-common/src/request/ScopeSet.ts b/lib/msal-common/src/request/ScopeSet.ts index af35c949b0..ace8a35bf7 100644 --- a/lib/msal-common/src/request/ScopeSet.ts +++ b/lib/msal-common/src/request/ScopeSet.ts @@ -5,7 +5,6 @@ import { ClientConfigurationError } from "../error/ClientConfigurationError"; import { StringUtils } from "../utils/StringUtils"; -import { Constants } from "../utils/Constants"; import { ClientAuthError } from "../error/ClientAuthError"; /** diff --git a/lib/msal-common/src/response/ResponseHandler.ts b/lib/msal-common/src/response/ResponseHandler.ts index eae8ae2f35..bc2b2b5405 100644 --- a/lib/msal-common/src/response/ResponseHandler.ts +++ b/lib/msal-common/src/response/ResponseHandler.ts @@ -11,7 +11,6 @@ import { ServerAuthorizationCodeResponse } from "../server/ServerAuthorizationCo import { Logger } from "../logger/Logger"; import { ServerError } from "../error/ServerError"; import { IdToken } from "../account/IdToken"; -import { UnifiedCacheManager } from "../cache/UnifiedCacheManager"; import { ScopeSet } from "../request/ScopeSet"; import { TimeUtils } from "../utils/TimeUtils"; import { AuthenticationResult } from "./AuthenticationResult"; @@ -25,21 +24,22 @@ import { InteractionRequiredAuthError } from "../error/InteractionRequiredAuthEr import { CacheRecord } from "../cache/entities/CacheRecord"; import { CacheHelper } from "../cache/utils/CacheHelper"; import { EnvironmentAliases, PreferredCacheEnvironment } from "../utils/Constants"; +import { CacheManager } from "../cache/CacheManager"; /** * Class that handles response parsing. */ export class ResponseHandler { private clientId: string; - private uCacheManager: UnifiedCacheManager; + private cacheStorage: CacheManager; private cryptoObj: ICrypto; private logger: Logger; private clientInfo: ClientInfo; private homeAccountIdentifier: string; - constructor(clientId: string, unifiedCacheManager: UnifiedCacheManager, cryptoObj: ICrypto, logger: Logger) { + constructor(clientId: string, cacheStorage: CacheManager, cryptoObj: ICrypto, logger: Logger) { this.clientId = clientId; - this.uCacheManager = unifiedCacheManager; + this.cacheStorage = cacheStorage; this.cryptoObj = cryptoObj; this.logger = logger; } @@ -105,7 +105,6 @@ export class ResponseHandler { * @param authority */ generateAuthenticationResult(serverTokenResponse: ServerAuthorizationTokenResponse, authority: Authority, cachedNonce?: string): AuthenticationResult { - // create an idToken object (not entity) const idTokenObj = new IdToken(serverTokenResponse.id_token, this.cryptoObj); @@ -119,7 +118,7 @@ export class ResponseHandler { // save the response tokens const cacheRecord = this.generateCacheRecord(serverTokenResponse, idTokenObj, authority); const responseScopes = ScopeSet.fromString(serverTokenResponse.scope); - this.uCacheManager.saveCacheRecord(cacheRecord, responseScopes); + this.cacheStorage.saveCacheRecord(cacheRecord, responseScopes); const authenticationResult: AuthenticationResult = { uniqueId: idTokenObj.claims.oid || idTokenObj.claims.sub, diff --git a/lib/msal-common/test/cache/UnifiedCacheManager.spec.ts b/lib/msal-common/test/cache/UnifiedCacheManager.spec.ts deleted file mode 100644 index 14afb11440..0000000000 --- a/lib/msal-common/test/cache/UnifiedCacheManager.spec.ts +++ /dev/null @@ -1,370 +0,0 @@ -import { expect } from "chai"; -import { UnifiedCacheManager } from "../../src/cache/UnifiedCacheManager"; -import { mockCache } from "./entities/cacheConstants"; -import { InMemoryCache, CredentialFilter, AccountFilter} from "../../src/cache/utils/CacheTypes"; -import { ICacheStorage } from "../../src/cache/interface/ICacheStorage"; -import { Deserializer } from "../../src/cache/serialize/Deserializer"; -import { AccountEntity } from "../../src/cache/entities/AccountEntity"; -import { AccessTokenEntity } from "../../src/cache/entities/AccessTokenEntity"; -import { CacheSchemaType, CacheHelper, CredentialType } from "../../src"; -import { IdTokenEntity } from "../../src/cache/entities/IdTokenEntity"; -import { RefreshTokenEntity } from "../../src/cache/entities/RefreshTokenEntity"; -import { AppMetadataEntity } from "../../src/cache/entities/AppMetadataEntity"; -import { TEST_CONFIG } from "../utils/StringConstants"; - -const cacheJson = require("./serialize/cache.json"); - -describe("UnifiedCacheManager test cases", () => { - let store = {}; - let storageInterface: ICacheStorage; - const cache = JSON.stringify(cacheJson); - const inMemCache: InMemoryCache = Deserializer.deserializeAllCache( - Deserializer.deserializeJSONBlob(cache) - ); - - beforeEach(() => { - storageInterface = { - getCache(): InMemoryCache { - return inMemCache; - }, - setCache(inMemCache): void { - this.inMemCache = inMemCache; - }, - setItem( - key: string, - value: string | object, - type?: string, - inMemory?: boolean - ): void { - // read inMemoryCache - const cache = this.getCache() as InMemoryCache; - - // save the cacheItem - switch (type) { - case CacheSchemaType.ACCOUNT: { - cache.accounts[key] = value as AccountEntity; - break; - } - case CacheSchemaType.CREDENTIAL: { - const credentialType = CacheHelper.getCredentialType( - key - ); - switch (credentialType) { - case CredentialType.ID_TOKEN: { - cache.idTokens[key] = value as IdTokenEntity; - break; - } - case CredentialType.ACCESS_TOKEN: { - cache.accessTokens[ - key - ] = value as AccessTokenEntity; - break; - } - case CredentialType.REFRESH_TOKEN: { - cache.refreshTokens[ - key - ] = value as RefreshTokenEntity; - break; - } - } - break; - } - case CacheSchemaType.APP_META_DATA: { - cache.appMetadata[key] = value as AppMetadataEntity; - break; - } - default: { - console.log("Invalid Cache Type"); - return; - } - } - - // update inMemoryCache - this.setCache(cache); - }, - getItem( - key: string, - type?: string, - inMemory?: boolean - ): string | object { - - // read inMemoryCache - const cache = this.getCache() as InMemoryCache; - - // save the cacheItem - switch (type!) { - case CacheSchemaType.ACCOUNT: { - return (cache.accounts[key] as AccountEntity) || null; - } - case CacheSchemaType.CREDENTIAL: { - const credentialType = CacheHelper.getCredentialType( - key - ); - let credential = null; - switch (credentialType) { - case CredentialType.ID_TOKEN: { - credential = - (cache.idTokens[key] as IdTokenEntity) || - null; - break; - } - case CredentialType.ACCESS_TOKEN: { - credential = - (cache.accessTokens[ - key - ] as AccessTokenEntity) || null; - break; - } - case CredentialType.REFRESH_TOKEN: { - credential = - (cache.refreshTokens[ - key - ] as RefreshTokenEntity) || null; - break; - } - } - return credential!; - } - case CacheSchemaType.APP_META_DATA: { - return ( - (cache.appMetadata[key] as AppMetadataEntity) || - null - ); - } - default: { - console.log("Invalid Cache Type"); - return {}; - } - } - }, - removeItem( - key: string, - type?: string, - inMemory?: boolean - ): boolean { - // read inMemoryCache - const cache = this.getCache() as InMemoryCache; - let result: boolean = false; - - // save the cacheItem - switch (type) { - case CacheSchemaType.ACCOUNT: { - if (!!cache.accounts[key]) { - delete cache.accounts[key]; - result = true; - } - break; - } - case CacheSchemaType.CREDENTIAL: { - const credentialType = CacheHelper.getCredentialType( - key - ); - switch (credentialType) { - case CredentialType.ID_TOKEN: { - if (!!cache.idTokens[key]) { - delete cache.idTokens[key]; - result = true; - } - break; - } - case CredentialType.ACCESS_TOKEN: { - if (!!cache.accessTokens[key]) { - delete cache.accessTokens[key]; - result = true; - } - break; - } - case CredentialType.REFRESH_TOKEN: { - if (!!cache.refreshTokens[key]) { - delete cache.refreshTokens[key]; - result = true; - } - break; - } - } - break; - } - case CacheSchemaType.APP_META_DATA: { - if (!!cache.appMetadata[key]) { - delete cache.appMetadata[key]; - result = true; - } - break; - } - default: { - console.log("Invalid Cache Type"); - break; - } - } - - // write to the cache after removal - if (result) { - this.setCache(cache); - } - return result; - }, - containsKey(key: string): boolean { - const cache = this.getCache() as InMemoryCache; - return !!cache.accounts[key] || !!cache.idTokens[key] || !!cache.accessTokens[key] || !!cache.refreshTokens[key] || !!cache.appMetadata[key]; - }, - getKeys(): string[] { - const cache = this.getCache() as InMemoryCache; - return [...Object.keys(cache.accounts), ...Object.keys(cache.idTokens), ...Object.keys(cache.accessTokens), ...Object.keys(cache.refreshTokens), ...Object.keys(cache.appMetadata)]; - }, - clear(): void { - const cache = this.getCache() as InMemoryCache; - cache.accounts = {}; - cache.idTokens = {}; - cache.accessTokens = {}; - cache.refreshTokens = {}; - cache.appMetadata = {}; - } - }; - }); - - it("initCache", () => { - let unifiedCacheManager = new UnifiedCacheManager(storageInterface, TEST_CONFIG.MSAL_CLIENT_ID, true); - - // create mock AccessToken - const atOne = mockCache.createMockATOne(); - const atOneKey = atOne.generateCredentialKey(); - const atTwo = mockCache.createMockATTwo(); - const atTwoKey = atTwo.generateCredentialKey(); - - const cache = storageInterface.getCache() as InMemoryCache; - expect(Object.keys(cache.accessTokens).length).to.equal(2); - expect(cache.accessTokens[atOneKey]).to.eql(atOne); - expect(cache.accessTokens[atTwoKey]).to.eql(atTwo); - }); - - it("save account", () => { - let ac = new AccountEntity(); - Object.assign(ac, { - homeAccountId: "someUid.someUtid", - environment: "login.microsoftonline.com", - realm: "microsoft", - localAccountId: "object1234", - username: "Jane Goodman", - authorityType: "MSSTS", - clientInfo: "eyJ1aWQiOiJzb21lVWlkIiwgInV0aWQiOiJzb21lVXRpZCJ9", - }); - - let unifiedCacheManager = new UnifiedCacheManager(storageInterface, TEST_CONFIG.MSAL_CLIENT_ID, true); - - const accountKey = ac.generateAccountKey(); - const cache = storageInterface.getCache() as InMemoryCache; - unifiedCacheManager.saveAccount(ac); - expect(cache.accounts[accountKey].homeAccountId).to.eql("someUid.someUtid"); - }); - - it("save credential", () => { - let at = new AccessTokenEntity(); - Object.assign(at, { - homeAccountId: "someUid.someUtid", - environment: "login.microsoftonline.com", - credentialType: "AccessToken", - clientId: "mock_client_id", - secret: "an access token sample", - realm: "microsoft", - target: "scope6 scope7", - cachedAt: "1000", - expiresOn: "4600", - extendedExpiresOn: "4600", - }); - - let unifiedCacheManager = new UnifiedCacheManager(storageInterface, TEST_CONFIG.MSAL_CLIENT_ID, true); - const atKey = at.generateCredentialKey(); - unifiedCacheManager.saveCredential(at); - const cache = storageInterface.getCache() as InMemoryCache; - expect(cache.accessTokens[atKey].homeAccountId).to.eql("someUid.someUtid"); - }); - - it("getAccount", () => { - let unifiedCacheManager = new UnifiedCacheManager(storageInterface, TEST_CONFIG.MSAL_CLIENT_ID, true); - - expect(unifiedCacheManager.getAccount("someuid.someutid-login.microsoftonline.com-microsoft").homeAccountId).to.eql("someUid.someUtid"); - }); - - it("getCredential", () => { - let unifiedCacheManager = new UnifiedCacheManager(storageInterface, TEST_CONFIG.MSAL_CLIENT_ID, true); - - expect(unifiedCacheManager.getCredential("someuid.someutid-login.microsoftonline.com-accesstoken-mock_client_id-microsoft-scope6 scope7").homeAccountId).to.eql("someUid.someUtid"); - }); - - it("getAccountsFilteredBy", () => { - let unifiedCacheManager = new UnifiedCacheManager(storageInterface, TEST_CONFIG.MSAL_CLIENT_ID, true); - - const filterOne: AccountFilter = { homeAccountId: "uid.utid" }; - let accounts = unifiedCacheManager.getAccountsFilteredBy(filterOne); - expect(Object.keys(accounts).length).to.eql(1); - - const filterTwo: AccountFilter = { homeAccountId: "Wrong Id" }; - accounts = unifiedCacheManager.getAccountsFilteredBy(filterTwo); - expect(Object.keys(accounts).length).to.eql(0); - }); - - it("getCredentials", () => { - let unifiedCacheManager = new UnifiedCacheManager(storageInterface, TEST_CONFIG.MSAL_CLIENT_ID, true); - - // filter by homeAccountId - const filterOne: CredentialFilter = { homeAccountId: "uid.utid" }; - let credentials = unifiedCacheManager.getCredentialsFilteredBy(filterOne); - expect(Object.keys(credentials.idTokens).length).to.eql(1); - expect(Object.keys(credentials.accessTokens).length).to.eql(2); - expect(Object.keys(credentials.refreshTokens).length).to.eql(2); - - // filter by homeAccountId - const filterTwo: CredentialFilter = { homeAccountId: "someuid.someutid" }; - credentials = unifiedCacheManager.getCredentialsFilteredBy(filterTwo); - expect(Object.keys(credentials.idTokens).length).to.eql(0); - expect(Object.keys(credentials.accessTokens).length).to.eql(0); - expect(Object.keys(credentials.refreshTokens).length).to.eql(0); - - // filter by target - const filterThree = { target: "scope1 scope2 scope3" }; - credentials = unifiedCacheManager.getCredentialsFilteredBy(filterThree); - }); - - it("removeAccount", () => { - let unifiedCacheManager = new UnifiedCacheManager(storageInterface, TEST_CONFIG.MSAL_CLIENT_ID, true); - - let ac = new AccountEntity(); - Object.assign(ac, { - homeAccountId: "someUid.someUtid", - environment: "login.microsoftonline.com", - realm: "microsoft", - localAccountId: "object1234", - username: "Jane Goodman", - authorityType: "MSSTS", - clientInfo: "eyJ1aWQiOiJzb21lVWlkIiwgInV0aWQiOiJzb21lVXRpZCJ9", - }); - - unifiedCacheManager.removeAccount(ac.generateAccountKey()); - const accountKey = ac.generateAccountKey(); - const cache = storageInterface.getCache() as InMemoryCache; - expect(cache.accounts[accountKey]).to.eql(undefined); - }); - - it("removeCredential", () => { - let unifiedCacheManager = new UnifiedCacheManager(storageInterface, TEST_CONFIG.MSAL_CLIENT_ID, true); - - let at = new AccessTokenEntity(); - Object.assign(at, { - homeAccountId: "someUid.someUtid", - environment: "login.microsoftonline.com", - credentialType: "AccessToken", - clientId: "mock_client_id", - secret: "an access token sample", - realm: "microsoft", - target: "scope6 scope7", - cachedAt: "1000", - expiresOn: "4600", - extendedExpiresOn: "4600", - }); - - unifiedCacheManager.removeCredential(at); - const atKey = at.generateCredentialKey(); - const cache = storageInterface.getCache() as InMemoryCache; - expect(cache.accessTokens[atKey]).to.eql(undefined); - }); -}); diff --git a/lib/msal-common/test/cache/cache.json b/lib/msal-common/test/cache/cache.json new file mode 100644 index 0000000000..7887840ab4 --- /dev/null +++ b/lib/msal-common/test/cache/cache.json @@ -0,0 +1,63 @@ +{ + "uid.utid-login.microsoftonline.com-microsoft": { + "username": "John Doe", + "localAccountId": "object1234", + "realm": "microsoft", + "environment": "login.microsoftonline.com", + "homeAccountId": "uid.utid", + "authorityType": "MSSTS", + "clientInfo": "eyJ1aWQiOiJ1aWQiLCAidXRpZCI6InV0aWQifQ==" + }, + "uid.utid-login.microsoftonline.com-refreshtoken-mock_client_id--": { + "environment": "login.microsoftonline.com", + "credentialType": "RefreshToken", + "secret": "a refresh token", + "clientId": "mock_client_id", + "homeAccountId": "uid.utid" + }, + "uid.utid-login.microsoftonline.com-refreshtoken-1--": { + "environment": "login.microsoftonline.com", + "credentialType": "RefreshToken", + "secret": "a refresh token", + "clientId": "mock_client_id", + "homeAccountId": "uid.utid", + "familyId": "1" + }, + "uid.utid-login.microsoftonline.com-accesstoken-mock_client_id-microsoft-scope1 scope2 scope3": { + "environment": "login.microsoftonline.com", + "credentialType": "AccessToken", + "secret": "an access token", + "realm": "microsoft", + "target": "scope1 scope2 scope3", + "clientId": "mock_client_id", + "cachedAt": "1000", + "homeAccountId": "uid.utid", + "extendedExpiresOn": "4600", + "expiresOn": "4600" + }, + "uid.utid-login.microsoftonline.com-accesstoken-mock_client_id-microsoft-scope4 scope5": { + "environment": "login.microsoftonline.com", + "credentialType": "AccessToken", + "secret": "an access token", + "realm": "microsoft", + "target": "scope4 scope5", + "clientId": "mock_client_id", + "cachedAt": "1000", + "homeAccountId": "uid.utid", + "extendedExpiresOn": "4600", + "expiresOn": "4600" + }, + "uid.utid-login.microsoftonline.com-idtoken-mock_client_id-microsoft-": { + "realm": "microsoft", + "environment": "login.microsoftonline.com", + "credentialType": "IdToken", + "secret": "header.eyJvaWQiOiAib2JqZWN0MTIzNCIsICJwcmVmZXJyZWRfdXNlcm5hbWUiOiAiSm9obiBEb2UiLCAic3ViIjogInN1YiJ9.signature", + "clientId": "mock_client_id", + "homeAccountId": "uid.utid" + }, + "appmetadata-login.microsoftonline.com-mock_client_id": { + "environment": "login.microsoftonline.com", + "familyId": "1", + "clientId": "mock_client_id" + } +} diff --git a/lib/msal-common/test/cache/interface/CacheManager.spec.ts b/lib/msal-common/test/cache/interface/CacheManager.spec.ts new file mode 100644 index 0000000000..7e9e650d4b --- /dev/null +++ b/lib/msal-common/test/cache/interface/CacheManager.spec.ts @@ -0,0 +1,295 @@ +import { expect } from "chai"; +import { CacheManager } from "../../../src/cache/CacheManager"; +import { CacheSchemaType, CredentialType } from "../../../src/utils/Constants"; +import { IdTokenEntity } from "../../../src/cache/entities/IdTokenEntity"; +import { AccountEntity } from "../../../src/cache/entities/AccountEntity"; +import { CacheHelper } from "../../../src/cache/utils/CacheHelper"; +import { AccessTokenEntity } from "../../../src/cache/entities/AccessTokenEntity"; +import { RefreshTokenEntity } from "../../../src/cache/entities/RefreshTokenEntity"; +import { AppMetadataEntity } from "../../../src/cache/entities/AppMetadataEntity"; +import { mockCache } from "../entities/cacheConstants"; +import { CacheRecord } from "../../../src/cache/entities/CacheRecord"; +import { ScopeSet } from "../../../src/request/ScopeSet"; +import { AccountFilter, CredentialFilter } from "../../../src/cache/utils/CacheTypes"; + +const cacheJson = require("../cache.json"); + +let store = {}; +class TestStorageManager extends CacheManager { + setItem(key: string, value: string | object, type?: string): void { + // save the cacheItem + switch (type) { + case CacheSchemaType.ACCOUNT: { + store[key] = value as AccountEntity; + break; + } + case CacheSchemaType.CREDENTIAL: { + const credentialType = CacheHelper.getCredentialType( + key + ); + switch (credentialType) { + case CredentialType.ID_TOKEN: { + store[key] = value as IdTokenEntity; + break; + } + case CredentialType.ACCESS_TOKEN: { + store[key] = value as AccessTokenEntity; + break; + } + case CredentialType.REFRESH_TOKEN: { + store[key] = value as RefreshTokenEntity; + break; + } + } + break; + } + case CacheSchemaType.APP_META_DATA: { + store[key] = value as AppMetadataEntity; + break; + } + default: { + console.log("Invalid Cache Type"); + return; + } + } + } + getItem(key: string, type?: string): string | object { + // save the cacheItem + switch (type) { + case CacheSchemaType.ACCOUNT: { + return CacheHelper.toObject(new AccountEntity(), store[key]) as AccountEntity; + } + case CacheSchemaType.CREDENTIAL: { + const credentialType = CacheHelper.getCredentialType( + key + ); + switch (credentialType) { + case CredentialType.ID_TOKEN: { + return CacheHelper.toObject(new IdTokenEntity(), store[key]) as IdTokenEntity; + } + case CredentialType.ACCESS_TOKEN: { + return CacheHelper.toObject(new AccessTokenEntity(), store[key]) as AccessTokenEntity; + } + case CredentialType.REFRESH_TOKEN: { + return CacheHelper.toObject(new RefreshTokenEntity(), store[key]) as RefreshTokenEntity; + } + } + break; + } + case CacheSchemaType.APP_META_DATA: { + return CacheHelper.toObject(new AppMetadataEntity(), store[key]) as AppMetadataEntity; + } + default: { + console.log("Invalid Cache Type"); + return; + } + } + } + removeItem(key: string, type?: string): boolean { + let result: boolean = false; + + // save the cacheItem + switch (type) { + case CacheSchemaType.ACCOUNT: { + if (!!store[key]) { + delete store[key]; + result = true; + } + break; + } + case CacheSchemaType.CREDENTIAL: { + const credentialType = CacheHelper.getCredentialType( + key + ); + switch (credentialType) { + case CredentialType.ID_TOKEN: { + if (!!store[key]) { + delete store[key]; + result = true; + } + break; + } + case CredentialType.ACCESS_TOKEN: { + if (!!store[key]) { + delete store[key]; + result = true; + } + break; + } + case CredentialType.REFRESH_TOKEN: { + if (!!store[key]) { + delete store[key]; + result = true; + } + break; + } + } + break; + } + case CacheSchemaType.APP_META_DATA: { + if (!!store[key]) { + delete store[key]; + result = true; + } + break; + } + default: { + console.log("Invalid Cache Type"); + break; + } + } + + return result; + } + containsKey(key: string, type?: string): boolean { + return !!store[key]; + } + getKeys(): string[] { + return Object.keys(store); + } + clear(): void { + store = {}; + } +} + +describe("CacheManager.ts test cases", () => { + + let cacheManager: TestStorageManager; + beforeEach(() => { + store = { + ...cacheJson + }; + cacheManager = new TestStorageManager(); + }); + + it("save account", () => { + let ac = new AccountEntity(); + ac.homeAccountId = "someUid.someUtid"; + ac.environment = "login.microsoftonline.com"; + ac.realm = "microsoft"; + ac.localAccountId = "object1234"; + ac.username = "Jane Goodman"; + ac.authorityType = "MSSTS"; + ac.clientInfo = "eyJ1aWQiOiJzb21lVWlkIiwgInV0aWQiOiJzb21lVXRpZCJ9"; + + const accountKey = ac.generateAccountKey(); + const cacheRecord = new CacheRecord(); + cacheRecord.account = ac; + cacheManager.saveCacheRecord(cacheRecord); + expect(store[accountKey].homeAccountId).to.eql("someUid.someUtid"); + }); + + it("save credential", () => { + let at = new AccessTokenEntity(); + Object.assign(at, { + homeAccountId: "someUid.someUtid", + environment: "login.microsoftonline.com", + credentialType: "AccessToken", + clientId: "mock_client_id", + secret: "an access token sample", + realm: "microsoft", + target: "scope6 scope7", + cachedAt: "1000", + expiresOn: "4600", + extendedExpiresOn: "4600", + }); + + const atKey = at.generateCredentialKey(); + const cacheRecord = new CacheRecord(); + cacheRecord.accessToken = at; + cacheManager.saveCacheRecord(cacheRecord, new ScopeSet(["scope6", "scope7"])); + const accountObj = store[atKey] as AccessTokenEntity; + expect(store[atKey].homeAccountId).to.eql("someUid.someUtid"); + }); + + it("getAccount", () => { + let ac = new AccountEntity(); + ac.homeAccountId = "someUid.someUtid"; + ac.environment = "login.microsoftonline.com"; + ac.realm = "microsoft"; + ac.localAccountId = "object1234"; + ac.username = "Jane Goodman"; + ac.authorityType = "MSSTS"; + ac.clientInfo = "eyJ1aWQiOiJzb21lVWlkIiwgInV0aWQiOiJzb21lVXRpZCJ9"; + + const accountKey = ac.generateAccountKey(); + const cacheRecord = new CacheRecord(); + cacheRecord.account = ac; + cacheManager.saveCacheRecord(cacheRecord); + + expect(cacheManager.getAccount(accountKey).homeAccountId).to.eql("someUid.someUtid"); + }); + + it("getCredential", () => { + let accessTokenEntity = new AccessTokenEntity(); + accessTokenEntity.homeAccountId = "someUid.someUtid"; + accessTokenEntity.environment = "login.microsoftonline.com"; + accessTokenEntity.realm = "microsoft"; + accessTokenEntity.clientId = "mock_client_id"; + accessTokenEntity.credentialType = CredentialType.ACCESS_TOKEN; + accessTokenEntity.target = "scope6 scope7"; + + const credKey = accessTokenEntity.generateCredentialKey(); + const cacheRecord = new CacheRecord(); + cacheRecord.accessToken = accessTokenEntity; + cacheManager.saveCacheRecord(cacheRecord); + expect(cacheManager.getCredential(credKey).homeAccountId).to.eql("someUid.someUtid"); + }); + + it("getAccountsFilteredBy", () => { + const filterOne: AccountFilter = { homeAccountId: "uid.utid" }; + let accounts = cacheManager.getAccountsFilteredBy(filterOne); + expect(Object.keys(accounts).length).to.eql(1); + + const filterTwo: AccountFilter = { homeAccountId: "Wrong Id" }; + accounts = cacheManager.getAccountsFilteredBy(filterTwo); + expect(Object.keys(accounts).length).to.eql(0); + }); + + it("getCredentials", () => { + // filter by homeAccountId + const filterOne: CredentialFilter = { homeAccountId: "uid.utid" }; + let credentials = cacheManager.getCredentialsFilteredBy(filterOne); + expect(Object.keys(credentials.idTokens).length).to.eql(1); + expect(Object.keys(credentials.accessTokens).length).to.eql(2); + expect(Object.keys(credentials.refreshTokens).length).to.eql(2); + + // filter by homeAccountId + const filterTwo: CredentialFilter = { homeAccountId: "someuid.someutid" }; + credentials = cacheManager.getCredentialsFilteredBy(filterTwo); + expect(Object.keys(credentials.idTokens).length).to.eql(0); + expect(Object.keys(credentials.accessTokens).length).to.eql(0); + expect(Object.keys(credentials.refreshTokens).length).to.eql(0); + + // filter by target + const filterThree = { target: "scope1 scope2 scope3" }; + credentials = cacheManager.getCredentialsFilteredBy(filterThree); + }); + + it.skip("removeAccount", () => { + const account: AccountEntity = cacheManager.getAccount("uid.utid-login.microsoftonline.com-microsoft"); + console.log(account.generateAccountKey); + cacheManager.removeAccount("uid.utid-login.microsoftonline.com-microsoft"); + expect(store["uid.utid-login.microsoftonline.com-microsoft"]).to.eql(undefined); + }); + + it("removeCredential", () => { + let at = new AccessTokenEntity(); + Object.assign(at, { + homeAccountId: "someUid.someUtid", + environment: "login.microsoftonline.com", + credentialType: "AccessToken", + clientId: "mock_client_id", + secret: "an access token sample", + realm: "microsoft", + target: "scope6 scope7", + cachedAt: "1000", + expiresOn: "4600", + extendedExpiresOn: "4600", + }); + + cacheManager.removeCredential(at); + const atKey = at.generateCredentialKey(); + expect(store[atKey]).to.eql(undefined); + }); +}); diff --git a/lib/msal-common/test/cache/mockStorage.ts b/lib/msal-common/test/cache/mockStorage.ts deleted file mode 100644 index a7c4c758a0..0000000000 --- a/lib/msal-common/test/cache/mockStorage.ts +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -import { ICacheStorage } from "../../src/cache/interface/ICacheStorage"; -import { InMemoryCache } from "../../src/cache/utils/CacheTypes"; -import { CacheSchemaType, CredentialType } from "../../src/utils/Constants"; -import { AccountEntity } from "../../src/cache/entities/AccountEntity"; -import { CacheHelper } from "../../src/cache/utils/CacheHelper"; -import { IdTokenEntity } from "../../src/cache/entities/IdTokenEntity"; -import { AccessTokenEntity } from "../../src/cache/entities/AccessTokenEntity"; -import { RefreshTokenEntity } from "../../src/cache/entities/RefreshTokenEntity"; -import { AppMetadataEntity } from "../../src/cache/entities/AppMetadataEntity"; - -/** - * This class implements Storage for node, reading cache from user specified storage location or an extension library - */ -export class Storage implements ICacheStorage { - - private inMemoryCache: InMemoryCache; - - /** - * gets the current in memory cache for the client - */ - getCache(): object { - return { - accounts: {}, - accessTokens: {}, - idTokens: {}, - refreshTokens: {}, - appMetadata: {} - }; - } - - /** - * sets the current in memory cache for the client - * @param inMemoryCache - */ - setCache(inMemoryCache: InMemoryCache) { - this.inMemoryCache = inMemoryCache; - } - - /** - * Set Item in memory - * @param key - * @param value - * @param type - * @param inMemory - */ - setItem( - key: string, - value: string | object, - type?: string, - inMemory?: boolean - ): void { - // check memory type - if (!inMemory) { - console.log("Node doesn't support granular cache persistence yet"); - return; - } - - // read inMemoryCache - const cache = this.getCache() as InMemoryCache; - - // save the cacheItem - switch (type) { - case CacheSchemaType.ACCOUNT: { - cache.accounts[key] = value as AccountEntity; - break; - } - case CacheSchemaType.CREDENTIAL: { - const credentialType = CacheHelper.getCredentialType(key); - switch (credentialType) { - case CredentialType.ID_TOKEN: { - cache.idTokens[key] = value as IdTokenEntity; - break; - } - case CredentialType.ACCESS_TOKEN: { - cache.accessTokens[key] = value as AccessTokenEntity; - break; - } - case CredentialType.REFRESH_TOKEN: { - cache.refreshTokens[key] = value as RefreshTokenEntity; - break; - } - } - break; - } - case CacheSchemaType.APP_META_DATA: { - cache.appMetadata[key] = value as AppMetadataEntity; - break; - } - default: { - console.log("Invalid Cache Type"); - return; - } - } - - // update inMemoryCache - this.setCache(cache); - } - - /** - * Gets cache item with given key. - * Will retrieve frm cookies if storeAuthStateInCookie is set to true. - * @param key - * @param type - * @param inMemory - */ - getItem(key: string, type?: string, inMemory?: boolean): string | object { - // check memory type - if (!inMemory) { - console.log("Node doesn't support granular cache persistence yet"); - return {}; - } - - // read inMemoryCache - const cache = this.getCache() as InMemoryCache; - - // save the cacheItem - switch (type!) { - case CacheSchemaType.ACCOUNT: { - return (cache.accounts[key] as AccountEntity) || null; - } - case CacheSchemaType.CREDENTIAL: { - const credentialType = CacheHelper.getCredentialType(key); - let credential = null; - switch (credentialType) { - case CredentialType.ID_TOKEN: { - credential = - (cache.idTokens[key] as IdTokenEntity) || null; - break; - } - case CredentialType.ACCESS_TOKEN: { - credential = - (cache.accessTokens[key] as AccessTokenEntity) || - null; - break; - } - case CredentialType.REFRESH_TOKEN: { - credential = - (cache.refreshTokens[key] as RefreshTokenEntity) || - null; - break; - } - } - return credential!; - } - case CacheSchemaType.APP_META_DATA: { - return (cache.appMetadata[key] as AppMetadataEntity) || null; - } - default: { - console.log("Invalid Cache Type"); - return {}; - } - } - } - - /** - * Removes the cache item from memory with the given key. - * @param key - * @param type - * @param inMemory - */ - removeItem(key: string, type?: string, inMemory?: boolean): boolean { - // check memory type - if (!inMemory) { - console.log("Node doesn't support granular cache persistence yet"); - return false; - } - - // read inMemoryCache - const cache = this.getCache() as InMemoryCache; - let result: boolean = false; - - // save the cacheItem - switch (type) { - case CacheSchemaType.ACCOUNT: { - if (!!cache.accounts[key]) { - delete cache.accounts[key]; - result = true; - } - break; - } - case CacheSchemaType.CREDENTIAL: { - const credentialType = CacheHelper.getCredentialType(key); - switch (credentialType) { - case CredentialType.ID_TOKEN: { - if (!!cache.idTokens[key]) { - delete cache.idTokens[key]; - result = true; - } - break; - } - case CredentialType.ACCESS_TOKEN: { - if (!!cache.accessTokens[key]) { - delete cache.accessTokens[key]; - result = true; - } - break; - } - case CredentialType.REFRESH_TOKEN: { - if (!!cache.refreshTokens[key]) { - delete cache.refreshTokens[key]; - result = true; - } - break; - } - } - break; - } - case CacheSchemaType.APP_META_DATA: { - if (!!cache.appMetadata[key]) { - delete cache.appMetadata[key]; - result = true; - } - break; - } - default: { - console.log("Invalid Cache Type"); - break; - } - } - - // write to the cache after removal - if (result) { - this.setCache(cache); - } - return result; - } - - /** - * Checks whether key is in cache. - * @param key - * TODO: implement after the lookup implementation - */ - containsKey(key: string): boolean { - return key ? true : false; - } - - /** - * Gets all keys in window. - * TODO: implement after the lookup implementation - */ - getKeys(inMemory?: boolean): string[] { - // check memory type - if (!inMemory) { - console.log("Node doesn't support granular cache persistence yet"); - return []; - } - - // read inMemoryCache - const cache = this.getCache(); - let cacheKeys: string[] = []; - - // read all keys - Object.keys(cache).forEach((key) => { - Object.keys(key).forEach((internalKey) => { - cacheKeys.push(internalKey); - }); - }); - - return cacheKeys; - } - - /** - * Clears all cache entries created by MSAL (except tokens). - */ - clear(inMemory?: boolean): void { - // check memory type - if (!inMemory) { - console.log("Node doesn't support granular cache persistence yet"); - return; - } - - // read inMemoryCache - const cache = this.getCache(); - - // read all keys - Object.keys(cache).forEach((key) => { - Object.keys(key).forEach((internalKey) => { - this.removeItem(internalKey); - }); - }); - } -} diff --git a/lib/msal-common/test/cache/serialize/JsonKeys.spec.ts b/lib/msal-common/test/cache/serialize/JsonKeys.spec.ts deleted file mode 100644 index de5b372b31..0000000000 --- a/lib/msal-common/test/cache/serialize/JsonKeys.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { expect } from "chai"; -import { - AccessTokenCacheMaps, - IdTokenCacheMaps, - RefreshTokenCacheMaps, - AccountCacheMaps, - AppMetadataCacheMaps -} from "../../../src/cache/serialize/JsonKeys"; - - -describe("JsonKets.ts Unit Tests", () => { - - it("Verify an AccessTokenCacheMaps", () => { - expect(AccessTokenCacheMaps.toCacheMap["homeAccountId"]).to.equal("home_account_id"); - expect(AccessTokenCacheMaps.fromCacheMap["home_account_id"]).to.equal("homeAccountId"); - }); - - it("Verify an IdTokenCacheMaps", () => { - expect(IdTokenCacheMaps.toCacheMap["clientId"]).to.equal("client_id"); - expect(IdTokenCacheMaps.fromCacheMap["client_id"]).to.equal("clientId"); - }); - - it("Verify an RefreshTokenCacheMaps", () => { - expect(RefreshTokenCacheMaps.toCacheMap["credentialType"]).to.equal("credential_type"); - expect(RefreshTokenCacheMaps.fromCacheMap["credential_type"]).to.equal("credentialType"); - }); - - it("Verify an AccountCacheMaps", () => { - expect(AccountCacheMaps.toCacheMap["clientInfo"]).to.equal("client_info"); - expect(AccountCacheMaps.fromCacheMap["client_info"]).to.equal("clientInfo"); - }); - - it("Verify an AppMetadataCacheMaps", () => { - expect(AppMetadataCacheMaps.toCacheMap["familyId"]).to.equal("family_id"); - expect(AppMetadataCacheMaps.fromCacheMap["family_id"]).to.equal("familyId"); - }); -}); diff --git a/lib/msal-common/test/cache/serialize/Serializer.spec.ts b/lib/msal-common/test/cache/serialize/Serializer.spec.ts deleted file mode 100644 index 7380256f68..0000000000 --- a/lib/msal-common/test/cache/serialize/Serializer.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { expect } from "chai"; -import { MockCache } from "../entities/cacheConstants"; -import { Serializer } from "../../../src/cache/serialize/Serializer"; -import { Deserializer } from "../../../src/cache/serialize/Deserializer"; -import { JsonCache, InMemoryCache } from "../../../src/cache/utils/CacheTypes"; - -const cachedJson = require("./cache.json"); - -describe("Serializer test cases", () => { - const cache = JSON.stringify(cachedJson); - const jsonCache: JsonCache = Deserializer.deserializeJSONBlob(cache); - - it("serializeJSONBlob", () => { - const json = Serializer.serializeJSONBlob(cachedJson); - expect(JSON.parse(json)).to.eql(cachedJson); - }); - - it("serializeAccountCacheEntity", () => { - // create mock Account - const acc = { [MockCache.accKey]: MockCache.acc }; - - // serialize the mock Account and Test equivalency with the cache.json provided - const serializedAcc = Serializer.serializeAccounts(acc); - expect(serializedAcc[MockCache.accKey]).to.eql(jsonCache.Account[MockCache.accKey]); - }); - - it("serializeIdTokenCacheEntity", () => { - // create mock IdToken - const idt = { [MockCache.idTKey]: MockCache.idT }; - - // serialize the mock IdToken and Test equivalency with the cache.json provided - const serializedIdT = Serializer.serializeIdTokens(idt); - expect(serializedIdT[MockCache.idTKey]).to.eql(jsonCache.IdToken[MockCache.idTKey]); - }); - - it("serializeAccessTokenEntity", () => { - // create mock AccessToken - const at = { [MockCache.atOneKey]: MockCache.atOne }; - - // serialize the mock AccessToken and Test equivalency with the cache.json provided - const serializedAt = Serializer.serializeAccessTokens(at); - expect(serializedAt[MockCache.atOneKey]).to.eql(jsonCache.AccessToken[MockCache.atOneKey]); - }); - - it("serializeRefreshTokenCacheEntity", () => { - // create mock Refresh Token - const rt = { [MockCache.rtKey]: MockCache.rt }; - - // serialize the mock RefreshToken and Test equivalency with the cache.json provided - const serializedRT = Serializer.serializeRefreshTokens(rt); - expect(serializedRT[MockCache.rtKey]).to.eql(jsonCache.RefreshToken[MockCache.rtKey]); - }); - - it("serializeAppMetadataCacheEntity", () => { - // create mock AppMetadata - const amdt = { [MockCache.amdtKey]: MockCache.amdt }; - - // serialize the mock AppMetadata and Test equivalency with the cache.json provided - const serializedAmdt = Serializer.serializeAppMetadata(amdt); - expect(serializedAmdt[MockCache.amdtKey]).to.eql(jsonCache.AppMetadata[MockCache.amdtKey]); - }); - - it("serializeAll", () => { - - // deserialize the cache from memory and Test equivalency with the generated mock cache - const inMemoryCache: InMemoryCache = Deserializer.deserializeAllCache(jsonCache); - const jCache: JsonCache = Serializer.serializeAllCache(inMemoryCache); - - expect(jCache.Account[MockCache.accKey]).to.eql(jsonCache.Account[MockCache.accKey]); - expect(jCache.IdToken[MockCache.idTKey]).to.eql(jsonCache.IdToken[MockCache.idTKey]); - expect(jCache.AccessToken[MockCache.atOneKey]).to.eql(jsonCache.AccessToken[MockCache.atOneKey]); - expect(jCache.AccessToken[MockCache.atTwoKey]).to.eql(jsonCache.AccessToken[MockCache.atTwoKey]); - expect(jCache.RefreshToken[MockCache.rtKey]).to.eql(jsonCache.RefreshToken[MockCache.rtKey]); - expect(jCache.RefreshToken[MockCache.rtFKey]).to.eql(jsonCache.RefreshToken[MockCache.rtFKey]); - expect(jCache.AppMetadata[MockCache.amdtKey]).to.eql(jsonCache.AppMetadata[MockCache.amdtKey]); - }); - -}); diff --git a/lib/msal-common/test/client/ClientTestUtils.ts b/lib/msal-common/test/client/ClientTestUtils.ts index a8dddb3ebf..223fa1cfeb 100644 --- a/lib/msal-common/test/client/ClientTestUtils.ts +++ b/lib/msal-common/test/client/ClientTestUtils.ts @@ -1,7 +1,30 @@ -import { ClientConfiguration, Constants, LogLevel, NetworkRequestOptions, PkceCodes, ClientAuthError} from "../../src"; +import { ClientConfiguration, Constants, LogLevel, NetworkRequestOptions, PkceCodes, ClientAuthError, CacheManager} from "../../src"; import { RANDOM_TEST_GUID, TEST_CONFIG } from "../utils/StringConstants"; import { AuthorityFactory } from "../../src"; +export class MockStorageClass extends CacheManager { + store = {}; + setItem(key: string, value: string | object, type?: string): void { + this.store[key] = value; + } + getItem(key: string, type?: string): string | object { + return this.store[key]; + } + removeItem(key: string, type?: string): boolean { + delete this.store[key]; + return true; + } + containsKey(key: string, type?: string): boolean { + return !!this.store[key]; + } + getKeys(): string[] { + return Object.keys(this.store); + } + clear(): void { + this.store = {}; + } +} + export class ClientTestUtils { static async createTestClientConfiguration(): Promise{ @@ -34,33 +57,7 @@ export class ClientTestUtils { authority: authority, knownAuthorities: [], }, - storageInterface: { - getCache(): object { - return {}; - }, - setCache(): void { - // do nothing - }, - setItem(key: string, value: string): void { - store[key] = value; - }, - getItem(key: string): string { - return store[key]; - }, - removeItem(key: string): boolean { - delete store[key]; - return true; - }, - containsKey(key: string): boolean { - return !!store[key]; - }, - getKeys(): string[] { - return Object.keys(store); - }, - clear(): void { - store = {}; - }, - }, + storageInterface: new MockStorageClass(), networkInterface: { sendGetRequestAsync( url: string, diff --git a/lib/msal-common/test/client/SPAClient.spec.ts b/lib/msal-common/test/client/SPAClient.spec.ts index 523dfc68cf..75a88d7af9 100644 --- a/lib/msal-common/test/client/SPAClient.spec.ts +++ b/lib/msal-common/test/client/SPAClient.spec.ts @@ -15,7 +15,6 @@ import { NetworkRequestOptions } from "../../src/network/INetworkModule"; import { Authority } from "../../src/authority/Authority"; import { PkceCodes } from "../../src/crypto/ICrypto"; import { ClientAuthErrorMessage } from "../../src/error/ClientAuthError"; -import { AuthError } from "../../src/error/AuthError"; import { buildClientInfo, ClientInfo } from "../../src/account/ClientInfo"; import { BaseClient } from "../../src/client/BaseClient"; import { AuthorityFactory } from "../../src/authority/AuthorityFactory"; @@ -27,9 +26,9 @@ import { AadAuthority } from "../../src/authority/AadAuthority"; import { AuthenticationResult } from "../../src/response/AuthenticationResult"; import { SilentFlowRequest } from "../../src/request/SilentFlowRequest"; import { IAccount } from "../../src/account/IAccount"; -import { ICacheStorage } from "../../src/cache/interface/ICacheStorage"; import { AccountEntity } from "../../src/cache/entities/AccountEntity"; -import { UnifiedCacheManager } from "../../src/cache/UnifiedCacheManager"; +import { CacheManager } from "../../src"; +import { MockStorageClass } from "./ClientTestUtils"; describe("SPAClient.ts Class Unit Tests", () => { @@ -39,35 +38,9 @@ describe("SPAClient.ts Class Unit Tests", () => { } }; - let store = {}; + let defaultAuthConfig: ClientConfiguration; - let cacheStorageMock: ICacheStorage = { - setItem(key: string, value: string): void { - store[key] = value; - }, - getItem(key: string): string { - return store[key]; - }, - removeItem(key: string): boolean { - delete store[key]; - return true; - }, - containsKey(key: string): boolean { - return !!store[key]; - }, - getKeys(): string[] { - return Object.keys(store); - }, - clear(): void { - store = {}; - }, - getCache(): object { - return null; - }, - setCache(): void { - return null; - }, - }; + let cacheStorageMock: MockStorageClass = new MockStorageClass(); beforeEach(() => { @@ -148,7 +121,7 @@ describe("SPAClient.ts Class Unit Tests", () => { afterEach(() => { sinon.restore(); - store = {}; + defaultAuthConfig.storageInterface.clear(); }); it("Creates a login URL", async () => { @@ -245,7 +218,7 @@ describe("SPAClient.ts Class Unit Tests", () => { afterEach(() => { sinon.restore(); - store = {}; + defaultAuthConfig.storageInterface.clear(); }); describe("Error Cases", () => { @@ -394,7 +367,7 @@ describe("SPAClient.ts Class Unit Tests", () => { afterEach(() => { sinon.restore(); - store = {}; + defaultAuthConfig.storageInterface.clear(); }); describe("Error cases", () => { @@ -428,7 +401,7 @@ describe("SPAClient.ts Class Unit Tests", () => { testAccountEntity.realm = "testTenantId"; testAccountEntity.username = "username@contoso.com"; testAccountEntity.authorityType = "MSSTS"; - sinon.stub(UnifiedCacheManager.prototype, "getAccount").returns(testAccountEntity); + sinon.stub(MockStorageClass.prototype, "getAccount").returns(testAccountEntity); sinon.stub(Authority.prototype, "discoverEndpoints").resolves(DEFAULT_OPENID_CONFIG_RESPONSE); const tokenRequest: SilentFlowRequest = { scopes: [testScope2], @@ -448,7 +421,7 @@ describe("SPAClient.ts Class Unit Tests", () => { afterEach(() => { sinon.restore(); - store = {}; + defaultAuthConfig.storageInterface.clear(); }); it("returns valid server code response", () => { @@ -480,10 +453,10 @@ describe("SPAClient.ts Class Unit Tests", () => { const testErrorHash = `#error=error_code&error_description=msal+error+description&state=${RANDOM_TEST_GUID}`; expect(() => authModule.handleFragmentResponse(testErrorHash, RANDOM_TEST_GUID)).to.throw("msal error description"); - expect(store).to.be.empty; + expect(cacheStorageMock.store).to.be.empty; expect(() => authModule.handleFragmentResponse(testErrorHash, RANDOM_TEST_GUID)).to.throw(ServerError); - expect(store).to.be.empty; + expect(cacheStorageMock.store).to.be.empty; }); }); }); @@ -556,14 +529,12 @@ describe("SPAClient.ts Class Unit Tests", () => { }); describe("getAccount()", () => { - let store; let config: ClientConfiguration; let client: SPAClient; let idToken: IdToken; let clientInfo: ClientInfo; let testAccount: IAccount; beforeEach(() => { - store = {}; config = { authOptions: { clientId: RANDOM_TEST_GUID, @@ -624,7 +595,7 @@ describe("SPAClient.ts Class Unit Tests", () => { afterEach(() => { sinon.restore(); - store = {}; + cacheStorageMock.clear(); }); it("returns null if nothing is in the cache", () => { diff --git a/lib/msal-common/test/client/SilentFlowClient.spec.ts b/lib/msal-common/test/client/SilentFlowClient.spec.ts index cb44dc8048..6027218bc1 100644 --- a/lib/msal-common/test/client/SilentFlowClient.spec.ts +++ b/lib/msal-common/test/client/SilentFlowClient.spec.ts @@ -18,7 +18,7 @@ import { IdToken } from "../../src/account/IdToken"; import { RefreshTokenRequest } from "../../src/request/RefreshTokenRequest"; import { AuthenticationResult } from "../../src/response/AuthenticationResult"; import { IAccount } from "../../src/account/IAccount"; -import { SilentFlowRequest, AccountEntity, UnifiedCacheManager, IdTokenEntity, AccessTokenEntity, RefreshTokenEntity } from "../../src"; +import { SilentFlowRequest, AccountEntity, IdTokenEntity, AccessTokenEntity, RefreshTokenEntity, CacheManager } from "../../src"; describe("SilentFlowClient unit tests", () => { afterEach(() => { @@ -87,7 +87,7 @@ describe("SilentFlowClient unit tests", () => { AUTHENTICATION_RESULT.body.client_info = TEST_DATA_CLIENT_INFO.TEST_DECODED_CLIENT_INFO; sinon.stub(RefreshTokenClient.prototype, "executePostToTokenEndpoint").resolves(AUTHENTICATION_RESULT); sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); - sinon.stub(UnifiedCacheManager.prototype, "getAccount").returns(testAccountEntity); + sinon.stub(CacheManager.prototype, "getAccount").returns(testAccountEntity); const createTokenRequestBodySpy = sinon.spy(RefreshTokenClient.prototype, "createTokenRequestBody"); sinon.stub(SilentFlowClient.prototype, "readIdTokenFromCache").returns(testIdToken); diff --git a/lib/msal-common/test/config/ClientConfiguration.spec.ts b/lib/msal-common/test/config/ClientConfiguration.spec.ts index 71b8049d3d..882dc9f276 100644 --- a/lib/msal-common/test/config/ClientConfiguration.spec.ts +++ b/lib/msal-common/test/config/ClientConfiguration.spec.ts @@ -4,9 +4,10 @@ import { PkceCodes } from "../../src/crypto/ICrypto"; import { AuthError } from "../../src/error/AuthError"; import { NetworkRequestOptions } from "../../src/network/INetworkModule"; import { LogLevel } from "../../src/logger/Logger"; -import { Constants, ICacheStorage } from "../../src"; +import { Constants } from "../../src"; import { version } from "../../package.json"; import {TEST_CONFIG} from "../utils/StringConstants"; +import { MockStorageClass } from "../client/ClientTestUtils"; describe("ClientConfiguration.ts Class Unit Tests", () => { @@ -65,34 +66,7 @@ describe("ClientConfiguration.ts Class Unit Tests", () => { expect(emptyConfig.libraryInfo.cpu).to.be.empty; }); - let store = {}; - const cacheStorageMock: ICacheStorage = { - setItem(key: string, value: string): void { - store[key] = value; - }, - getItem(key: string): string { - return store[key]; - }, - removeItem(key: string): boolean { - delete store[key]; - return true; - }, - containsKey(key: string): boolean { - return !!store[key]; - }, - getKeys(): string[] { - return Object.keys(store); - }, - clear(): void { - store = {}; - }, - getCache(): object { - return null; - }, - setCache(): void { - return null; - } - }; + const cacheStorageMock = new MockStorageClass(); const testPkceCodes = { challenge: "TestChallenge", diff --git a/lib/msal-common/tsconfig.json b/lib/msal-common/tsconfig.json index fbf9440c15..9d1a20d55f 100644 --- a/lib/msal-common/tsconfig.json +++ b/lib/msal-common/tsconfig.json @@ -16,7 +16,7 @@ "resolveJsonModule": true }, "include": [ - "src/**/*", "src/cache/serialize/.ts" + "src/**/*" ], "exclude": [ "node_modules" diff --git a/lib/msal-node/src/cache/CacheContext.ts b/lib/msal-node/src/cache/CacheContext.ts index a563f34eca..52d8de544d 100644 --- a/lib/msal-node/src/cache/CacheContext.ts +++ b/lib/msal-node/src/cache/CacheContext.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. */ -import { JsonCache, Deserializer } from '@azure/msal-common'; import { Storage } from '../cache/Storage'; +import { JsonCache } from "./serializer/SerializerTypes"; +import { Deserializer } from "./serializer/Deserializer"; /** * class managing sync between the persistent cache blob in the disk and the in memory cache of the node diff --git a/lib/msal-node/src/cache/Storage.ts b/lib/msal-node/src/cache/Storage.ts index 54b2857ff6..eee1b86ee4 100644 --- a/lib/msal-node/src/cache/Storage.ts +++ b/lib/msal-node/src/cache/Storage.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ import { - ICacheStorage, - InMemoryCache, CredentialType, CacheSchemaType, CacheHelper, @@ -12,19 +10,24 @@ import { AccessTokenEntity, RefreshTokenEntity, IdTokenEntity, - AppMetadataEntity + AppMetadataEntity, + CacheManager } from '@azure/msal-common'; import { CacheOptions } from '../config/Configuration'; +import { Deserializer } from "./serializer/Deserializer"; +import { Serializer } from "./serializer/Serializer"; +import { InMemoryCache, JsonCache } from "./serializer/SerializerTypes"; /** * This class implements Storage for node, reading cache from user specified storage location or an extension library */ -export class Storage implements ICacheStorage { +export class Storage extends CacheManager { // Cache configuration, either set by user or default values. private cacheConfig: CacheOptions; private inMemoryCache: InMemoryCache; constructor(cacheConfig: CacheOptions) { + super(); this.cacheConfig = cacheConfig; if (this.cacheConfig.cacheLocation! === 'fileCache') this.inMemoryCache = this.cacheConfig.cacheInMemory!; @@ -55,15 +58,8 @@ export class Storage implements ICacheStorage { setItem( key: string, value: string | object, - type?: string, - inMemory?: boolean + type?: string ): void { - // check memory type - if (!inMemory) { - console.log("Node doesn't support granular cache persistence yet"); - return; - } - // read inMemoryCache const cache = this.getCache() as InMemoryCache; @@ -112,13 +108,7 @@ export class Storage implements ICacheStorage { * @param type * @param inMemory */ - getItem(key: string, type?: string, inMemory?: boolean): string | object { - // check memory type - if (!inMemory) { - console.log("Node doesn't support granular cache persistence yet"); - return {}; - } - + getItem(key: string, type?: string): string | object { // read inMemoryCache const cache = this.getCache() as InMemoryCache; @@ -132,20 +122,15 @@ export class Storage implements ICacheStorage { let credential = null; switch (credentialType) { case CredentialType.ID_TOKEN: { - credential = - (cache.idTokens[key] as IdTokenEntity) || null; + credential = (cache.idTokens[key] as IdTokenEntity) || null; break; } case CredentialType.ACCESS_TOKEN: { - credential = - (cache.accessTokens[key] as AccessTokenEntity) || - null; + credential = (cache.accessTokens[key] as AccessTokenEntity) || null; break; } case CredentialType.REFRESH_TOKEN: { - credential = - (cache.refreshTokens[key] as RefreshTokenEntity) || - null; + credential = (cache.refreshTokens[key] as RefreshTokenEntity) || null; break; } } @@ -167,13 +152,7 @@ export class Storage implements ICacheStorage { * @param type * @param inMemory */ - removeItem(key: string, type?: string, inMemory?: boolean): boolean { - // check memory type - if (!inMemory) { - console.log("Node doesn't support granular cache persistence yet"); - return false; - } - + removeItem(key: string, type?: string): boolean { // read inMemoryCache const cache = this.getCache() as InMemoryCache; let result: boolean = false; @@ -246,13 +225,7 @@ export class Storage implements ICacheStorage { /** * Gets all keys in window. */ - getKeys(inMemory?: boolean): string[] { - // check memory type - if (!inMemory) { - console.log("Node doesn't support granular cache persistence yet"); - return []; - } - + getKeys(): string[] { // read inMemoryCache const cache = this.getCache(); let cacheKeys: string[] = []; @@ -270,13 +243,7 @@ export class Storage implements ICacheStorage { /** * Clears all cache entries created by MSAL (except tokens). */ - clear(inMemory?: boolean): void { - // check memory type - if (!inMemory) { - console.log("Node doesn't support granular cache persistence yet"); - return; - } - + clear(): void { // read inMemoryCache const cache = this.getCache(); @@ -287,4 +254,22 @@ export class Storage implements ICacheStorage { }); }); } + + /** + * Initialize in memory cache from an exisiting cache vault + * @param cache + */ + static generateInMemoryCache(cache: string): InMemoryCache { + return Deserializer.deserializeAllCache( + Deserializer.deserializeJSONBlob(cache) + ); + } + + /** + * retrieves the final JSON + * @param inMemoryCache + */ + static generateJsonCache(inMemoryCache: InMemoryCache): JsonCache { + return Serializer.serializeAllCache(inMemoryCache); + } } diff --git a/lib/msal-node/src/cache/serializer/Deserializer.ts b/lib/msal-node/src/cache/serializer/Deserializer.ts new file mode 100644 index 0000000000..0880d461a3 --- /dev/null +++ b/lib/msal-node/src/cache/serializer/Deserializer.ts @@ -0,0 +1,140 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ +import { AccountCacheMaps, IdTokenCacheMaps, AccessTokenCacheMaps, RefreshTokenCacheMaps, AppMetadataCacheMaps } from "./JsonKeys"; +import { StringUtils, AccountCache, IdTokenCache, AccessTokenCache, RefreshTokenCache, AppMetadataCache, StringDict, CacheHelper, AccountEntity, IdTokenEntity, AccessTokenEntity, RefreshTokenEntity, AppMetadataEntity } from "@azure/msal-common"; +import { JsonCache, InMemoryCache } from "./SerializerTypes"; + +// TODO: Can we write this with Generics? +export class Deserializer { + + /** + * Parse the JSON blob in memory and deserialize the content + * @param cachedJson + */ + static deserializeJSONBlob(jsonFile: string): JsonCache { + const deserializedCache = StringUtils.isEmpty(jsonFile) + ? {} + : JSON.parse(jsonFile); + return deserializedCache; + } + + /** + * Deserializes accounts to AccountEntity objects + * @param accounts + */ + static deserializeAccounts(accounts: StringDict | undefined): AccountCache { + const accountObjects: AccountCache = {}; + if (accounts) { + Object.keys(accounts).map(function (key) { + const mappedAcc = CacheHelper.renameKeys( + accounts[key], + AccountCacheMaps.fromCacheMap + ); + const account: AccountEntity = new AccountEntity(); + CacheHelper.toObject(account, mappedAcc); + accountObjects[key] = account; + }); + } + + return accountObjects; + } + + /** + * Deserializes id tokens to IdTokenEntity objects + * @param idTokens + */ + static deserializeIdTokens(idTokens: StringDict | undefined): IdTokenCache { + const idObjects: IdTokenCache = {}; + if (idTokens) { + Object.keys(idTokens).map(function (key) { + const mappedIdT = CacheHelper.renameKeys( + idTokens[key], + IdTokenCacheMaps.fromCacheMap + ); + const idToken: IdTokenEntity = new IdTokenEntity(); + CacheHelper.toObject(idToken, mappedIdT); + idObjects[key] = idToken; + }); + } + return idObjects; + } + + /** + * Deserializes access tokens to AccessTokenEntity objects + * @param accessTokens + */ + static deserializeAccessTokens(accessTokens: StringDict | undefined): AccessTokenCache { + const atObjects: AccessTokenCache = {}; + if (accessTokens) { + Object.keys(accessTokens).map(function (key) { + const mappedAT = CacheHelper.renameKeys( + accessTokens[key], + AccessTokenCacheMaps.fromCacheMap + ); + const accessToken: AccessTokenEntity = new AccessTokenEntity(); + CacheHelper.toObject(accessToken, mappedAT); + atObjects[key] = accessToken; + }); + } + + return atObjects; + } + + /** + * Deserializes refresh tokens to RefreshTokenEntity objects + * @param refreshTokens + */ + static deserializeRefreshTokens(refreshTokens: StringDict | undefined): RefreshTokenCache { + const rtObjects: RefreshTokenCache = {}; + if (refreshTokens) { + Object.keys(refreshTokens).map(function (key) { + const mappedRT = CacheHelper.renameKeys( + refreshTokens[key], + RefreshTokenCacheMaps.fromCacheMap + ); + const refreshToken: RefreshTokenEntity = new RefreshTokenEntity(); + CacheHelper.toObject(refreshToken, mappedRT); + rtObjects[key] = refreshToken; + }); + } + + return rtObjects; + } + + /** + * Deserializes appMetadata to AppMetaData objects + * @param appMetadata + */ + static deserializeAppMetadata(appMetadata: StringDict | undefined): AppMetadataCache { + const appMetadataObjects: AppMetadataCache = {}; + if (appMetadata) { + Object.keys(appMetadata).map(function (key) { + const mappedAmd = CacheHelper.renameKeys( + appMetadata[key], + AppMetadataCacheMaps.fromCacheMap + ); + const amd: AppMetadataEntity = new AppMetadataEntity(); + CacheHelper.toObject(amd, mappedAmd); + appMetadataObjects[key] = amd; + }); + } + + return appMetadataObjects; + } + + /** + * Deserialize an inMemory Cache + * @param jsonCache + */ + static deserializeAllCache(jsonCache: JsonCache): InMemoryCache { + return { + accounts: jsonCache.Account? this.deserializeAccounts(jsonCache.Account): {}, + idTokens: jsonCache.IdToken? this.deserializeIdTokens(jsonCache.IdToken): {}, + accessTokens: jsonCache.AccessToken? this.deserializeAccessTokens(jsonCache.AccessToken) : {}, + refreshTokens: jsonCache.RefreshToken? this.deserializeRefreshTokens(jsonCache.RefreshToken): {}, + appMetadata: jsonCache.AppMetadata? this.deserializeAppMetadata(jsonCache.AppMetadata): {} + }; + } +} diff --git a/lib/msal-common/src/cache/serialize/EntitySerializer.ts b/lib/msal-node/src/cache/serializer/EntitySerializer.ts similarity index 91% rename from lib/msal-common/src/cache/serialize/EntitySerializer.ts rename to lib/msal-node/src/cache/serializer/EntitySerializer.ts index 5ff648d1aa..d9b67d3b73 100644 --- a/lib/msal-common/src/cache/serialize/EntitySerializer.ts +++ b/lib/msal-node/src/cache/serializer/EntitySerializer.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. */ import { AccountCacheMaps, AccessTokenCacheMaps, IdTokenCacheMaps, RefreshTokenCacheMaps, AppMetadataCacheMaps } from "./JsonKeys"; -import { AccountCache, AccessTokenCache, IdTokenCache, RefreshTokenCache, AppMetadataCache } from "../utils/CacheTypes"; -import { CacheHelper } from "../utils/CacheHelper"; +import { AccountCache, CacheHelper, IdTokenCache, AccessTokenCache, RefreshTokenCache, AppMetadataCache } from "@azure/msal-common"; export class EntitySerializer { /** diff --git a/lib/msal-common/src/cache/serialize/JsonKeys.ts b/lib/msal-node/src/cache/serializer/JsonKeys.ts similarity index 97% rename from lib/msal-common/src/cache/serialize/JsonKeys.ts rename to lib/msal-node/src/cache/serializer/JsonKeys.ts index e2b930383a..a48d93e432 100644 --- a/lib/msal-common/src/cache/serialize/JsonKeys.ts +++ b/lib/msal-node/src/cache/serializer/JsonKeys.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { CacheHelper } from "../utils/CacheHelper"; +import { CacheHelper } from "@azure/msal-common"; /** * Map for AccountCache entity serialization and deserialization diff --git a/lib/msal-common/src/cache/serialize/Serializer.ts b/lib/msal-node/src/cache/serializer/Serializer.ts similarity index 69% rename from lib/msal-common/src/cache/serialize/Serializer.ts rename to lib/msal-node/src/cache/serializer/Serializer.ts index d9792ff565..add43937a3 100644 --- a/lib/msal-common/src/cache/serialize/Serializer.ts +++ b/lib/msal-node/src/cache/serializer/Serializer.ts @@ -4,8 +4,8 @@ */ import { EntitySerializer } from "./EntitySerializer"; -import { AccountCache, AccessTokenCache, IdTokenCache, RefreshTokenCache, AppMetadataCache, JsonCache, InMemoryCache } from "../utils/CacheTypes"; -import { StringDict } from "../../utils/MsalTypes"; +import { StringDict, AccountCache, IdTokenCache, AccessTokenCache, RefreshTokenCache, AppMetadataCache } from "@azure/msal-common"; +import { InMemoryCache, JsonCache } from "./SerializerTypes"; export class Serializer { @@ -22,9 +22,9 @@ export class Serializer { * @param accCache */ static serializeAccounts(accCache: AccountCache): StringDict { - const accounts = {}; + const accounts: StringDict = {}; Object.keys(accCache).map(function (key) { - accounts[key] = EntitySerializer.mapAccountKeys(accCache, key); + accounts[key] = JSON.stringify(EntitySerializer.mapAccountKeys(accCache, key)); }); return accounts; @@ -35,9 +35,9 @@ export class Serializer { * @param idTCache */ static serializeIdTokens(idTCache: IdTokenCache): StringDict{ - const idTokens = {}; + const idTokens: StringDict = {}; Object.keys(idTCache).map(function (key) { - idTokens[key] = EntitySerializer.mapIdTokenKeys(idTCache, key); + idTokens[key] = JSON.stringify(EntitySerializer.mapIdTokenKeys(idTCache, key)); }); return idTokens; @@ -48,9 +48,9 @@ export class Serializer { * @param atCache */ static serializeAccessTokens(atCache: AccessTokenCache): StringDict { - const accessTokens = {}; + const accessTokens: StringDict = {}; Object.keys(atCache).map(function (key) { - accessTokens[key] = EntitySerializer.mapAccessTokenKeys(atCache, key); + accessTokens[key] = JSON.stringify(EntitySerializer.mapAccessTokenKeys(atCache, key)); }); return accessTokens; @@ -60,10 +60,10 @@ export class Serializer { * Serialize refreshTokens * @param rtCache */ - static serializeRefreshTokens(rtCache: RefreshTokenCache): StringDict{ - const refreshTokens = {}; + static serializeRefreshTokens(rtCache: RefreshTokenCache): StringDict { + const refreshTokens: StringDict = {}; Object.keys(rtCache).map(function (key) { - refreshTokens[key] = EntitySerializer.mapRefreshTokenKeys(rtCache, key); + refreshTokens[key] = JSON.stringify(EntitySerializer.mapRefreshTokenKeys(rtCache, key)); }); return refreshTokens; @@ -74,9 +74,9 @@ export class Serializer { * @param amdtCache */ static serializeAppMetadata(amdtCache: AppMetadataCache): StringDict { - const appMetadata = {}; + const appMetadata: StringDict = {}; Object.keys(amdtCache).map(function (key) { - appMetadata[key] = EntitySerializer.mapAppMetadataKeys(amdtCache, key); + appMetadata[key] = JSON.stringify(EntitySerializer.mapAppMetadataKeys(amdtCache, key)); }); return appMetadata; diff --git a/lib/msal-node/src/cache/serializer/SerializerTypes.ts b/lib/msal-node/src/cache/serializer/SerializerTypes.ts new file mode 100644 index 0000000000..91d6991e95 --- /dev/null +++ b/lib/msal-node/src/cache/serializer/SerializerTypes.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { StringDict, AccountCache, IdTokenCache, AccessTokenCache, RefreshTokenCache, AppMetadataCache } from "@azure/msal-common"; + +export type JsonCache = { + Account: StringDict; + IdToken: StringDict; + AccessToken: StringDict; + RefreshToken: StringDict; + AppMetadata: StringDict; +}; + +export type InMemoryCache = { + accounts: AccountCache; + idTokens: IdTokenCache; + accessTokens: AccessTokenCache; + refreshTokens: RefreshTokenCache; + appMetadata: AppMetadataCache; +}; diff --git a/lib/msal-node/src/client/ClientApplication.ts b/lib/msal-node/src/client/ClientApplication.ts index 2de076051f..78e8c6c51d 100644 --- a/lib/msal-node/src/client/ClientApplication.ts +++ b/lib/msal-node/src/client/ClientApplication.ts @@ -16,10 +16,8 @@ import { ClientAuthError, Constants, B2cAuthority, - JsonCache, - Serializer, - InMemoryCache, - BaseAuthRequest, + IAccount, + BaseAuthRequest } from '@azure/msal-common'; import { Configuration, buildAppConfiguration } from '../config/Configuration'; import { CryptoProvider } from '../crypto/CryptoProvider'; @@ -27,6 +25,8 @@ import { Storage } from '../cache/Storage'; import { version } from '../../package.json'; import { Constants as NodeConstants } from './../utils/Constants'; import { CacheContext } from '../cache/CacheContext'; +import { JsonCache, InMemoryCache } from "../cache/serializer/SerializerTypes"; +import { Serializer } from "../cache/serializer/Serializer"; export abstract class ClientApplication { private config: Configuration; @@ -199,4 +199,8 @@ export abstract class ClientApplication { this.storage.getCache() as InMemoryCache ); } + + getAllAccounts(): IAccount[] { + return this.storage.getAllAccounts(); + } } diff --git a/lib/msal-node/src/config/Configuration.ts b/lib/msal-node/src/config/Configuration.ts index 1461d862d1..4f47d22d26 100644 --- a/lib/msal-node/src/config/Configuration.ts +++ b/lib/msal-node/src/config/Configuration.ts @@ -5,12 +5,12 @@ import { LoggerOptions, INetworkModule, - LogLevel, - InMemoryCache, + LogLevel } from '@azure/msal-common'; import { NetworkUtils } from '../utils/NetworkUtils'; import { CACHE } from '../utils/Constants'; import debug from 'debug'; +import { InMemoryCache } from "cache/serializer/SerializerTypes"; /** * - clientId - Client id of the application. diff --git a/lib/msal-node/test/cache/cacheConstants.ts b/lib/msal-node/test/cache/cacheConstants.ts new file mode 100644 index 0000000000..7c06e8a97f --- /dev/null +++ b/lib/msal-node/test/cache/cacheConstants.ts @@ -0,0 +1,141 @@ +import { AccessTokenEntity, IdTokenEntity, RefreshTokenEntity, AccountEntity, AppMetadataEntity } from "@azure/msal-common"; + +// mock tokens +export const mockAccessTokenEntity_1 = { + homeAccountId: "uid.utid", + environment: "login.microsoftonline.com", + credentialType: "AccessToken", + clientId: "mock_client_id", + secret: "an access token", + realm: "microsoft", + target: "scope1 scope2 scope3", + cachedAt: "1000", + expiresOn: "4600", + extendedExpiresOn: "4600", +}; + +export const mockAccessTokenEntity_2 = { + homeAccountId: "uid.utid", + environment: "login.microsoftonline.com", + credentialType: "AccessToken", + clientId: "mock_client_id", + secret: "an access token", + realm: "microsoft", + target: "scope4 scope5", + cachedAt: "1000", + expiresOn: "4600", + extendedExpiresOn: "4600" +}; + +export const mockIdTokenEntity = { + homeAccountId: "uid.utid", + environment: "login.microsoftonline.com", + credentialType: "IdToken", + clientId: "mock_client_id", + secret: "header.eyJvaWQiOiAib2JqZWN0MTIzNCIsICJwcmVmZXJyZWRfdXNlcm5hbWUiOiAiSm9obiBEb2UiLCAic3ViIjogInN1YiJ9.signature", + realm: "microsoft" +}; + +export const mockRefreshTokenEntity = { + homeAccountId: "uid.utid", + environment: "login.microsoftonline.com", + credentialType: "RefreshToken", + clientId: "mock_client_id", + secret: "a refresh token" +}; + +export const mockRefreshTokenEntityWithFamilyId = { + homeAccountId: "uid.utid", + environment: "login.microsoftonline.com", + credentialType: "RefreshToken", + clientId: "mock_client_id", + secret: "a refresh token", + familyId: "1" +}; + + +export const mockAccountEntity = { + homeAccountId: "uid.utid", + environment: "login.microsoftonline.com", + realm: "microsoft", + localAccountId: "object1234", + username: "John Doe", + authorityType: "MSSTS", + clientInfo: "eyJ1aWQiOiJ1aWQiLCAidXRpZCI6InV0aWQifQ==", +}; + +export const mockAppMetaDataEntity = { + clientId: "mock_client_id", + environment: "login.microsoftonline.com", + familyId: "1" +} + +// generate mockCache +export class mockCache { + + static createMockATOne(): AccessTokenEntity { + const at = new AccessTokenEntity(); + Object.assign(at, mockAccessTokenEntity_1); + + return at; + } + + static createMockATTwo(): AccessTokenEntity { + const at = new AccessTokenEntity(); + Object.assign(at, mockAccessTokenEntity_2); + + return at; + } + + static createMockIdT(): IdTokenEntity { + let idt = new IdTokenEntity(); + Object.assign(idt, mockIdTokenEntity); + + return idt; + } + + static createMockRT(): RefreshTokenEntity { + let rt = new RefreshTokenEntity(); + Object.assign(rt, mockRefreshTokenEntity); + + return rt; + } + + static createMockRTWithFamilyId(): RefreshTokenEntity { + let rt = new RefreshTokenEntity(); + Object.assign(rt, mockRefreshTokenEntityWithFamilyId); + + return rt; + } + + static createMockAcc(): AccountEntity { + let acc = new AccountEntity(); + Object.assign(acc, mockAccountEntity); + + return acc; + } + + static createMockAmdt(): AppMetadataEntity { + let amdt = new AppMetadataEntity(); + Object.assign(amdt, mockAppMetaDataEntity); + + return amdt; + } +} + +export const MockCache = { + atOne: mockCache.createMockATOne(), + atOneKey: mockCache.createMockATOne().generateCredentialKey(), + atTwo: mockCache.createMockATTwo(), + atTwoKey: mockCache.createMockATTwo().generateCredentialKey(), + idT: mockCache.createMockIdT(), + idTKey: mockCache.createMockIdT().generateCredentialKey(), + rt: mockCache.createMockRT(), + rtKey: mockCache.createMockRT().generateCredentialKey(), + rtF: mockCache.createMockRTWithFamilyId(), + rtFKey: mockCache.createMockRTWithFamilyId().generateCredentialKey(), + acc: mockCache.createMockAcc(), + accKey: mockCache.createMockAcc().generateAccountKey(), + amdt: mockCache.createMockAmdt(), + amdtKey: mockCache.createMockAmdt().generateAppMetaDataEntityKey() +} diff --git a/lib/msal-common/test/cache/serialize/Account.json b/lib/msal-node/test/cache/serializer/Account.json similarity index 100% rename from lib/msal-common/test/cache/serialize/Account.json rename to lib/msal-node/test/cache/serializer/Account.json diff --git a/lib/msal-common/test/cache/serialize/Deserializer.spec.ts b/lib/msal-node/test/cache/serializer/Deserializer.spec.ts similarity index 61% rename from lib/msal-common/test/cache/serialize/Deserializer.spec.ts rename to lib/msal-node/test/cache/serializer/Deserializer.spec.ts index 31d29db106..ee7df9a569 100644 --- a/lib/msal-common/test/cache/serialize/Deserializer.spec.ts +++ b/lib/msal-node/test/cache/serializer/Deserializer.spec.ts @@ -1,7 +1,6 @@ -import { expect } from "chai"; -import { MockCache } from "../entities/cacheConstants"; -import { Deserializer } from "../../../src/cache/serialize/Deserializer"; -import { InMemoryCache, JsonCache } from "../../../src/cache/utils/CacheTypes"; +import { JsonCache, InMemoryCache } from "../../../src/cache/serializer/SerializerTypes"; +import { Deserializer } from "../../../src/cache/serializer/Deserializer"; +import { MockCache } from "../cacheConstants"; const cacheJson = require("./cache.json"); @@ -10,7 +9,7 @@ describe("Deserializer test cases", () => { const cache = JSON.stringify(cacheJson); const jsonCache: JsonCache = Deserializer.deserializeJSONBlob(cache); - it("deserializeJSONBlob", () => { + test("deserializeJSONBlob", () => { const mockAccount = { "uid.utid-login.microsoftonline.com-microsoft": { username: "John Doe", @@ -23,51 +22,51 @@ describe("Deserializer test cases", () => { }, }; const acc = Deserializer.deserializeJSONBlob(cache); - expect(acc.Account).to.eql(mockAccount); + expect(acc.Account).toMatchObject(mockAccount); }); - it("deSerializeAccounts", () => { + test("deSerializeAccounts", () => { // serialize the mock Account and Test equivalency with the cache.json provided const accCache = Deserializer.deserializeAccounts(jsonCache.Account); - expect(accCache[MockCache.accKey]).to.eql(MockCache.acc); + expect(accCache[MockCache.accKey]).toEqual(MockCache.acc); }); - it("deSerializeIdTokens", () => { + test("deSerializeIdTokens", () => { // serialize the mock IdToken and Test equivalency with the cache.json provided const idTCache = Deserializer.deserializeIdTokens(jsonCache.IdToken); - expect(idTCache[MockCache.idTKey]).to.eql(MockCache.idT); + expect(idTCache[MockCache.idTKey]).toEqual(MockCache.idT); }); - it("deSerializeAccessTokens", () => { + test("deSerializeAccessTokens", () => { // serialize the mock AccessToken and Test equivalency with the cache.json provided const atCache = Deserializer.deserializeAccessTokens(jsonCache.AccessToken); - expect(atCache[MockCache.atOneKey]).to.eql(MockCache.atOne); + expect(atCache[MockCache.atOneKey]).toEqual(MockCache.atOne); }); - it("deSerializeRefreshTokens", () => { + test("deSerializeRefreshTokens", () => { // serialize the mock RefreshToken and Test equivalency with the cache.json provided const rtCache = Deserializer.deserializeRefreshTokens(jsonCache.RefreshToken); - expect(rtCache[MockCache.rtKey]).to.eql(MockCache.rt); + expect(rtCache[MockCache.rtKey]).toEqual(MockCache.rt); }); - it("deserializeAppMetadata", () => { + test("deserializeAppMetadata", () => { // serialize the mock AppMetadata and Test equivalency with the cache.json provided const amdtCache = Deserializer.deserializeAppMetadata(jsonCache.AppMetadata); - expect(amdtCache[MockCache.amdtKey]).to.eql(MockCache.amdt); + expect(amdtCache[MockCache.amdtKey]).toEqual(MockCache.amdt); }); - it("deserializeAll", () => { + test("deserializeAll", () => { // deserialize the cache from memory and Test equivalency with the generated mock cache const inMemoryCache: InMemoryCache = Deserializer.deserializeAllCache(jsonCache); - expect(inMemoryCache.accounts[MockCache.accKey]).to.eql(MockCache.acc); - expect(inMemoryCache.idTokens[MockCache.idTKey]).to.eql(MockCache.idT); - expect(inMemoryCache.accessTokens[MockCache.atOneKey]).to.eql(MockCache.atOne); - expect(inMemoryCache.accessTokens[MockCache.atTwoKey]).to.eql(MockCache.atTwo); - expect(inMemoryCache.refreshTokens[MockCache.rtKey]).to.eql(MockCache.rt); - expect(inMemoryCache.refreshTokens[MockCache.rtFKey]).to.eql(MockCache.rtF); - expect(inMemoryCache.appMetadata[MockCache.amdtKey]).to.eql(MockCache.amdt); + expect(inMemoryCache.accounts[MockCache.accKey]).toEqual(MockCache.acc); + expect(inMemoryCache.idTokens[MockCache.idTKey]).toEqual(MockCache.idT); + expect(inMemoryCache.accessTokens[MockCache.atOneKey]).toEqual(MockCache.atOne); + expect(inMemoryCache.accessTokens[MockCache.atTwoKey]).toEqual(MockCache.atTwo); + expect(inMemoryCache.refreshTokens[MockCache.rtKey]).toEqual(MockCache.rt); + expect(inMemoryCache.refreshTokens[MockCache.rtFKey]).toEqual(MockCache.rtF); + expect(inMemoryCache.appMetadata[MockCache.amdtKey]).toEqual(MockCache.amdt); }); }); diff --git a/lib/msal-node/test/cache/serializer/JsonKeys.spec.ts b/lib/msal-node/test/cache/serializer/JsonKeys.spec.ts new file mode 100644 index 0000000000..14b65dfb68 --- /dev/null +++ b/lib/msal-node/test/cache/serializer/JsonKeys.spec.ts @@ -0,0 +1,34 @@ +import { AccessTokenCacheMaps, AccountCacheMaps, RefreshTokenCacheMaps, IdTokenCacheMaps, AppMetadataCacheMaps } from "../../../src/cache/serializer/JsonKeys"; + +describe("JsonKeys.ts Unit Tests", () => { + + test("Verify an AccessTokenCacheMaps", () => { + expect(AccessTokenCacheMaps.toCacheMap["homeAccountId"]).toEqual("home_account_id"); + expect(Object.keys(AccessTokenCacheMaps.fromCacheMap)).toContain("home_account_id"); + expect(Object.values(AccessTokenCacheMaps.fromCacheMap)).toContain("homeAccountId"); + }); + + test("Verify an IdTokenCacheMaps", () => { + expect(IdTokenCacheMaps.toCacheMap["clientId"]).toEqual("client_id"); + expect(Object.keys(IdTokenCacheMaps.fromCacheMap)).toContain("client_id"); + expect(Object.values(IdTokenCacheMaps.fromCacheMap)).toContain("clientId"); + }); + + test("Verify an RefreshTokenCacheMaps", () => { + expect(RefreshTokenCacheMaps.toCacheMap["credentialType"]).toEqual("credential_type"); + expect(Object.keys(RefreshTokenCacheMaps.fromCacheMap)).toContain("credential_type"); + expect(Object.values(RefreshTokenCacheMaps.fromCacheMap)).toContain("credentialType"); + }); + + test("Verify an AccountCacheMaps", () => { + expect(AccountCacheMaps.toCacheMap["clientInfo"]).toEqual("client_info"); + expect(Object.keys(AccountCacheMaps.fromCacheMap)).toContain("client_info"); + expect(Object.values(AccountCacheMaps.fromCacheMap)).toContain("clientInfo"); + }); + + test("Verify an AppMetadataCacheMaps", () => { + expect(AppMetadataCacheMaps.toCacheMap["familyId"]).toEqual("family_id"); + expect(Object.keys(AppMetadataCacheMaps.fromCacheMap)).toContain("family_id"); + expect(Object.values(AppMetadataCacheMaps.fromCacheMap)).toContain("familyId"); + }); +}); diff --git a/lib/msal-node/test/cache/serializer/Serializer.spec.ts b/lib/msal-node/test/cache/serializer/Serializer.spec.ts new file mode 100644 index 0000000000..b9a82d7776 --- /dev/null +++ b/lib/msal-node/test/cache/serializer/Serializer.spec.ts @@ -0,0 +1,77 @@ +import { JsonCache, InMemoryCache } from "../../../src/cache/serializer/SerializerTypes"; +import { Serializer } from "../../../src/cache/serializer/Serializer"; +import { Deserializer } from "../../../src/cache/serializer/Deserializer"; +import { MockCache } from "../cacheConstants"; + +const cachedJson = require("./cache.json"); + +describe("Serializer test cases", () => { + + const cache = JSON.stringify(cachedJson); + const jsonCache: JsonCache = Deserializer.deserializeJSONBlob(cache); + + test("serializeJSONBlob", () => { + const json = Serializer.serializeJSONBlob(cachedJson); + expect(JSON.parse(json)).toMatchObject(cachedJson); + }); + + test("serializeAccountCacheEntity", () => { + // create mock Account + const acc = { [MockCache.accKey]: MockCache.acc }; + + // serialize the mock Account and Test equivalency with the cache.json provided + const serializedAcc = Serializer.serializeAccounts(acc); + expect(JSON.parse(serializedAcc[MockCache.accKey])).toMatchObject(jsonCache.Account[MockCache.accKey]); + }); + + test("serializeIdTokenCacheEntity", () => { + // create mock IdToken + const idt = { [MockCache.idTKey]: MockCache.idT }; + + // serialize the mock IdToken and Test equivalency with the cache.json provided + const serializedIdT = Serializer.serializeIdTokens(idt); + expect(JSON.parse(serializedIdT[MockCache.idTKey])).toMatchObject(jsonCache.IdToken[MockCache.idTKey]); + }); + + test("serializeAccessTokenEntity", () => { + // create mock AccessToken + const at = { [MockCache.atOneKey]: MockCache.atOne }; + + // serialize the mock AccessToken and Test equivalency with the cache.json provided + const serializedAt = Serializer.serializeAccessTokens(at); + expect(JSON.parse(serializedAt[MockCache.atOneKey])).toMatchObject(jsonCache.AccessToken[MockCache.atOneKey]); + }); + + test("serializeRefreshTokenCacheEntity", () => { + // create mock Refresh Token + const rt = { [MockCache.rtKey]: MockCache.rt }; + + // serialize the mock RefreshToken and Test equivalency with the cache.json provided + const serializedRT = Serializer.serializeRefreshTokens(rt); + expect(JSON.parse(serializedRT[MockCache.rtKey])).toMatchObject(jsonCache.RefreshToken[MockCache.rtKey]); + }); + + test("serializeAppMetadataCacheEntity", () => { + // create mock AppMetadata + const amdt = { [MockCache.amdtKey]: MockCache.amdt }; + + // serialize the mock AppMetadata and Test equivalency with the cache.json provided + const serializedAmdt = Serializer.serializeAppMetadata(amdt); + expect(JSON.parse(serializedAmdt[MockCache.amdtKey])).toMatchObject(jsonCache.AppMetadata[MockCache.amdtKey]); + }); + + test("serializeAll", () => { + + // deserialize the cache from memory and Test equivalency with the generated mock cache + const inMemoryCache: InMemoryCache = Deserializer.deserializeAllCache(jsonCache); + const jCache: JsonCache = Serializer.serializeAllCache(inMemoryCache); + + expect(JSON.parse(jCache.Account[MockCache.accKey])).toMatchObject(jsonCache.Account[MockCache.accKey]); + expect(JSON.parse(jCache.IdToken[MockCache.idTKey])).toMatchObject(jsonCache.IdToken[MockCache.idTKey]); + expect(JSON.parse(jCache.AccessToken[MockCache.atOneKey])).toMatchObject(jsonCache.AccessToken[MockCache.atOneKey]); + expect(JSON.parse(jCache.AccessToken[MockCache.atTwoKey])).toMatchObject(jsonCache.AccessToken[MockCache.atTwoKey]); + expect(JSON.parse(jCache.RefreshToken[MockCache.rtKey])).toMatchObject(jsonCache.RefreshToken[MockCache.rtKey]); + expect(JSON.parse(jCache.RefreshToken[MockCache.rtFKey])).toMatchObject(jsonCache.RefreshToken[MockCache.rtFKey]); + expect(JSON.parse(jCache.AppMetadata[MockCache.amdtKey])).toMatchObject(jsonCache.AppMetadata[MockCache.amdtKey]); + }); +}); diff --git a/lib/msal-common/test/cache/serialize/cache.json b/lib/msal-node/test/cache/serializer/cache.json similarity index 100% rename from lib/msal-common/test/cache/serialize/cache.json rename to lib/msal-node/test/cache/serializer/cache.json