Skip to content

Commit

Permalink
Merge pull request #1655 from AzureAD/msal-node-cache-lookup-interface
Browse files Browse the repository at this point in the history
[msal-node] Cache Lookup - 2: Save, lookup and delete entities
  • Loading branch information
sameerag committed Jun 4, 2020
2 parents 4dcd69d + 3da2e57 commit 4a4dc16
Show file tree
Hide file tree
Showing 21 changed files with 1,312 additions and 226 deletions.
32 changes: 31 additions & 1 deletion lib/msal-browser/src/cache/BrowserStorage.ts
Expand Up @@ -152,12 +152,13 @@ export class BrowserStorage implements ICacheStorage {
* Will also clear the cookie item if storeAuthStateInCookie is set to true.
* @param key
*/
removeItem(key: string): void {
removeItem(key: string): boolean {
const msalKey = this.generateCacheKey(key);
this.windowStorage.removeItem(msalKey);
if (this.cacheConfig.storeAuthStateInCookie) {
this.clearItemCookie(msalKey);
}
return true;
}

/**
Expand Down Expand Up @@ -271,4 +272,33 @@ export class BrowserStorage implements ICacheStorage {
setCache(): void {
// sets nothing
}

/**
* Dummy implementation for interface compat - will change after BrowserCacheMigration
* @param key
* @param value
* @param type
*/
setItemInMemory(key: string, value: object, type?: string): void {
if (key && value && type)
return;
}

/**
* Dummy implementation for interface compat - will change after BrowserCacheMigration
* @param key
* @param type
*/
getItemFromMemory(key: string, type?: string): object {
return key && type ? {} : {};
};

/**
* Dummy implementation for interface compat - will change after BrowserCacheMigration
* @param key
* @param type
*/
removeItemFromMemory(key: string, type?: string): boolean {
return key && type ? true : false;
};
}
26 changes: 24 additions & 2 deletions lib/msal-common/src/cache/ICacheStorage.ts
Expand Up @@ -29,17 +29,39 @@ export interface ICacheStorage {
*/
setItem(key: string, value: string): void;

/**
* Function to set item in Memory
* @param key
* @param value
* @param type
*/
setItemInMemory(key: string, value: object, type?: string): void;

/**
* Function which retrieves item from cache.
* @param key
*/
getItem(key: string): string;

/**
* Function which removes item from cache.
* Function to get an item from memory
* @param key
* @param type
*/
getItemFromMemory(key: string, type?: string): object;

/**
* Function to remove an item from cache given its key.
* @param key
*/
removeItem(key: string): boolean;

/**
* Function to remove an item from memory given its key
* @param key
* @param type
*/
removeItem(key: string): void;
removeItemFromMemory(key: string, type?: string): boolean;

/**
* Function which returns boolean whether cache contains a specific key.
Expand Down
12 changes: 12 additions & 0 deletions lib/msal-common/src/config/ClientConfiguration.ts
Expand Up @@ -125,6 +125,10 @@ const DEFAULT_STORAGE_IMPLEMENTATION: ICacheStorage = {
const notImplErr = "Storage interface - getItem() has not been implemented for the cacheStorage interface.";
throw AuthError.createUnexpectedError(notImplErr);
},
getItemFromMemory: (): object => {
const notImplErr = "Storage interface - getItemFromMemory() 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);
Expand All @@ -133,10 +137,18 @@ const DEFAULT_STORAGE_IMPLEMENTATION: ICacheStorage = {
const notImplErr = "Storage interface - removeItem() has not been implemented for the cacheStorage interface.";
throw AuthError.createUnexpectedError(notImplErr);
},
removeItemFromMemory: () => {
const notImplErr = "Storage interface - removeItemFromMemory() 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);
},
setItemInMemory: () => {
const notImplErr = "Storage interface - setItemInMemory() has not been implemented for the cacheStorage interface.";
throw AuthError.createUnexpectedError(notImplErr);
},
getCache: (): InMemoryCache => {
const notImplErr = "Storage interface - getCache() has not been implemented for the cacheStorage interface.";
throw AuthError.createUnexpectedError(notImplErr);
Expand Down
3 changes: 2 additions & 1 deletion lib/msal-common/src/index.ts
Expand Up @@ -21,6 +21,7 @@ export { UnifiedCacheManager } from "./unifiedCache/UnifiedCacheManager";
export { JsonCache, InMemoryCache } from "./unifiedCache/utils/CacheTypes";
export { Serializer } from "./unifiedCache/serialize/Serializer";
export { Deserializer } from "./unifiedCache/serialize/Deserializer";
export { CacheHelper } from "./unifiedCache/utils/CacheHelper";
// Network Interface
export { INetworkModule, NetworkRequestOptions } from "./network/INetworkModule";
export { NetworkResponse } from "./network/NetworkManager";
Expand Down Expand Up @@ -50,7 +51,7 @@ export { ClientAuthError, ClientAuthErrorMessage } from "./error/ClientAuthError
export { ClientConfigurationError, ClientConfigurationErrorMessage } from "./error/ClientConfigurationError";
// Constants and Utils
export {
Constants, PromptValue, TemporaryCacheKeys, PersistentCacheKeys, Prompt, ResponseMode
Constants, PromptValue, TemporaryCacheKeys, PersistentCacheKeys, Prompt, ResponseMode, CacheSchemaType, CredentialType
} from "./utils/Constants";
export { StringUtils } from "./utils/StringUtils";
export { StringDict } from "./utils/MsalTypes";
13 changes: 5 additions & 8 deletions lib/msal-common/src/response/AuthenticationResult.ts
Expand Up @@ -8,18 +8,15 @@ import { StringDict } from "../utils/MsalTypes";
/**
* Result returned from the authority's token endpoint.
*/
// TODO: Also consider making an external type and use this as internal
export class AuthenticationResult {
// TODO this is temp class, it will be updated.
uniqueId: string; // TODO: Check applicability
tenantId: string; // TODO: Check applicability
uniqueId: string;
tenantId: string;
scopes: Array<string>;
tokenType: string; // TODO: get rid of this if we can
idToken: string;
idTokenClaims: StringDict;
accessToken: string;
expiresOn: Date;
extExpiresOn?: Date; // TODO: Check what this maps to in other libraries
userRequestState?: string; // TODO: remove, just check how state is handled in other libraries
familyId?: string; // TODO: Check wider audience
extExpiresOn?: Date;
state?: string;
familyId?: string;
}
122 changes: 46 additions & 76 deletions lib/msal-common/src/response/ResponseHandler.ts
Expand Up @@ -22,6 +22,7 @@ import { IdTokenEntity } from "../unifiedCache/entities/IdTokenEntity";
import { AccessTokenEntity } from "../unifiedCache/entities/AccessTokenEntity";
import { RefreshTokenEntity } from "../unifiedCache/entities/RefreshTokenEntity";
import { InteractionRequiredAuthError } from "../error/InteractionRequiredAuthError";
import { CacheRecord } from "../unifiedCache/entities/CacheRecord";

/**
* Class that handles response parsing.
Expand Down Expand Up @@ -104,81 +105,32 @@ export class ResponseHandler {
* @param state
*/
generateAuthenticationResult(serverTokenResponse: ServerAuthorizationTokenResponse, authority: Authority): AuthenticationResult {
// Retrieve current account if in Cache
// TODO: add this once the req for cache look up for tokens is confirmed

const authenticationResult = this.processTokenResponse(serverTokenResponse, authority);

const environment = authority.canonicalAuthorityUrlComponents.HostNameAndPort;
this.addCredentialsToCache(authenticationResult, environment, serverTokenResponse.refresh_token);

return authenticationResult;
}

/**
* Returns a new AuthenticationResult with the data from original result filled with the relevant data.
* @param authenticationResult
* @param idTokenString(raw idToken in the server response)
*/
processTokenResponse(serverTokenResponse: ServerAuthorizationTokenResponse, authority: Authority): AuthenticationResult {
const authenticationResult: AuthenticationResult = {
uniqueId: "",
tenantId: "",
tokenType: "",
idToken: null,
idTokenClaims: null,
accessToken: "",
scopes: [],
expiresOn: null,
familyId: null
};

// IdToken
// create an idToken object (not entity)
const idTokenObj = new IdToken(serverTokenResponse.id_token, this.cryptoObj);

// if account is not in cache, append it to the cache
this.addAccountToCache(serverTokenResponse, idTokenObj, authority);

// TODO: Check how this changes for auth code response
const expiresSeconds = Number(idTokenObj.claims.exp);
if (expiresSeconds && !authenticationResult.expiresOn) {
authenticationResult.expiresOn = new Date(expiresSeconds * 1000);
}
// save the response tokens
const cacheRecord = this.generateCacheRecord(serverTokenResponse, idTokenObj, authority);
this.uCacheManager.saveCacheRecord(cacheRecord);

// Expiration calculation
const expiresInSeconds = TimeUtils.nowSeconds() + serverTokenResponse.expires_in;
const extendedExpiresInSeconds = expiresInSeconds + serverTokenResponse.ext_expires_in;
// Set consented scopes in response
const responseScopes = ScopeSet.fromString(serverTokenResponse.scope, this.clientId, true);

return {
...authenticationResult,
const responseScopes = ScopeSet.fromString(serverTokenResponse.scope, this.clientId, true);
const authenticationResult: AuthenticationResult = {
uniqueId: idTokenObj.claims.oid || idTokenObj.claims.sub,
tenantId: idTokenObj.claims.tid,
scopes: responseScopes.asArray(),
idToken: idTokenObj.rawIdToken,
idTokenClaims: idTokenObj.claims,
accessToken: serverTokenResponse.access_token,
expiresOn: new Date(expiresInSeconds),
extExpiresOn: new Date(extendedExpiresInSeconds),
scopes: responseScopes.asArray(),
familyId: serverTokenResponse.foci,
familyId: serverTokenResponse.foci || null,
};
}

/**
* if Account is not in the cache, generateAccount and append it to the cache
* @param serverTokenResponse
* @param idToken
* @param authority
*/
addAccountToCache(serverTokenResponse: ServerAuthorizationTokenResponse, idToken: IdToken, authority: Authority): void {
const environment = authority.canonicalAuthorityUrlComponents.HostNameAndPort;
let accountEntity: AccountEntity;
const cachedAccount: AccountEntity = this.uCacheManager.getAccount(this.homeAccountIdentifier, environment, idToken.claims.tid);
if (!cachedAccount) {
accountEntity = this.generateAccountEntity(serverTokenResponse, idToken, authority);
this.uCacheManager.addAccountEntity(accountEntity);
}
return authenticationResult;
}

/**
Expand All @@ -205,35 +157,53 @@ export class ResponseHandler {
}

/**
* Appends the minted tokens to the in-memory cache
* @param authenticationResult
* Generates CacheRecord
* @param serverTokenResponse
* @param idTokenObj
* @param authority
*/
addCredentialsToCache(
authenticationResult: AuthenticationResult,
authority: string,
refreshToken: string
): void {
const idTokenEntity = IdTokenEntity.createIdTokenEntity(
generateCacheRecord(serverTokenResponse: ServerAuthorizationTokenResponse, idTokenObj: IdToken, authority: Authority): CacheRecord {

const cacheRecord = new CacheRecord();

// Account
cacheRecord.account = this.generateAccountEntity(
serverTokenResponse,
idTokenObj,
authority
);

// IdToken
cacheRecord.idToken = IdTokenEntity.createIdTokenEntity(
this.homeAccountIdentifier,
authenticationResult,
authority.canonicalAuthorityUrlComponents.HostNameAndPort,
serverTokenResponse.id_token,
this.clientId,
authority
idTokenObj.claims.tid
);
const accessTokenEntity = AccessTokenEntity.createAccessTokenEntity(

// AccessToken
const responseScopes = ScopeSet.fromString(serverTokenResponse.scope, this.clientId, true);
cacheRecord.accessToken = AccessTokenEntity.createAccessTokenEntity(
this.homeAccountIdentifier,
authenticationResult,
authority.canonicalAuthorityUrlComponents.HostNameAndPort,
serverTokenResponse.access_token,
this.clientId,
authority
idTokenObj.claims.tid,
responseScopes.asArray().join(" "),
serverTokenResponse.expires_in,
serverTokenResponse.ext_expires_in
);
const refreshTokenEntity = RefreshTokenEntity.createRefreshTokenEntity(

// refreshToken
cacheRecord.refreshToken = RefreshTokenEntity.createRefreshTokenEntity(
this.homeAccountIdentifier,
authenticationResult,
refreshToken,
authority.canonicalAuthorityUrlComponents.HostNameAndPort,
serverTokenResponse.refresh_token,
this.clientId,
authority
serverTokenResponse.foci
);

this.uCacheManager.addCredentialCache(accessTokenEntity, idTokenEntity, refreshTokenEntity);
return cacheRecord;
}
}

0 comments on commit 4a4dc16

Please sign in to comment.