Skip to content

Commit

Permalink
Merge pull request #1789 from AzureAD/msal-common-account-interface
Browse files Browse the repository at this point in the history
Changing IAccount Interface to AccountInfo
  • Loading branch information
Prithvi Kanherkar committed Jun 23, 2020
2 parents 5330176 + eb29bfa commit f842023
Show file tree
Hide file tree
Showing 30 changed files with 442 additions and 402 deletions.
9 changes: 5 additions & 4 deletions lib/msal-browser/src/app/PublicClientApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
CacheSchemaType,
AuthenticationResult,
SilentFlowRequest,
IAccount
AccountInfo
} from "@azure/msal-common";
import { buildConfiguration, Configuration } from "../config/Configuration";
import { BrowserStorage } from "../cache/BrowserStorage";
Expand Down Expand Up @@ -487,11 +487,12 @@ export class PublicClientApplication {
* Use to log out the current user, and redirect the user to the postLogoutRedirectUri.
* Default behaviour is to redirect the user to `window.location.href`.
*/
logout(account: IAccount, authorityString?: string): void {
logout(account: AccountInfo, authorityString?: string): void {
const authorityObj = StringUtils.isEmpty(authorityString) ? this.defaultAuthorityInstance : AuthorityFactory.createInstance(
this.config.auth.authority,
this.config.system.networkClient
);

// create logout string and navigate user window to logout. Auth module will clear cache.
this.authModule.logout(account, authorityObj).then((logoutUri: string) => {
BrowserUtils.navigateWindow(logoutUri);
Expand Down Expand Up @@ -529,7 +530,7 @@ export class PublicClientApplication {
* or null when no state is found
* @returns {@link IAccount[]} - Array of account objects in cache
*/
public getAllAccounts(): IAccount[] {
public getAllAccounts(): AccountInfo[] {
return this.browserStorage.getAllAccounts();
}

Expand All @@ -539,7 +540,7 @@ export class PublicClientApplication {
* or null when no state is found
* @returns {@link IAccount} - the account object stored in MSAL
*/
public getAccountByUsername(userName: string): IAccount {
public getAccountByUsername(userName: string): AccountInfo {
const allAccounts = this.getAllAccounts();
return allAccounts.filter(accountObj => accountObj.username === userName)[0];
}
Expand Down
12 changes: 6 additions & 6 deletions lib/msal-browser/src/cache/BrowserStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { Constants, PersistentCacheKeys, StringUtils, AuthorizationCodeRequest, ICrypto, CacheSchemaType, AccountEntity, IdTokenEntity, CacheHelper, CredentialType, AccessTokenEntity, RefreshTokenEntity, AppMetadataEntity, CacheManager } from "@azure/msal-common";
import { Constants, PersistentCacheKeys, StringUtils, AuthorizationCodeRequest, ICrypto, CacheSchemaType, AccountEntity, IdTokenEntity, CredentialType, AccessTokenEntity, RefreshTokenEntity, AppMetadataEntity, CacheManager, CredentialEntity } from "@azure/msal-common";
import { CacheOptions } from "../config/Configuration";
import { BrowserAuthError } from "../error/BrowserAuthError";
import { BrowserConfigurationAuthError } from "../error/BrowserConfigurationAuthError";
Expand Down Expand Up @@ -144,22 +144,22 @@ export class BrowserStorage extends CacheManager {
switch (type) {
case CacheSchemaType.ACCOUNT: {
const account = new AccountEntity();
return (CacheHelper.toObject(account, JSON.parse(value)) as AccountEntity);
return (CacheManager.toObject(account, JSON.parse(value)) as AccountEntity);
}
case CacheSchemaType.CREDENTIAL: {
const credentialType = CacheHelper.getCredentialType(key);
const credentialType = CredentialEntity.getCredentialType(key);
switch (credentialType) {
case CredentialType.ID_TOKEN: {
const idTokenEntity: IdTokenEntity = new IdTokenEntity();
return (CacheHelper.toObject(idTokenEntity, JSON.parse(value)) as IdTokenEntity);
return (CacheManager.toObject(idTokenEntity, JSON.parse(value)) as IdTokenEntity);
}
case CredentialType.ACCESS_TOKEN: {
const accessTokenEntity: AccessTokenEntity = new AccessTokenEntity();
return (CacheHelper.toObject(accessTokenEntity, JSON.parse(value)) as AccessTokenEntity);
return (CacheManager.toObject(accessTokenEntity, JSON.parse(value)) as AccessTokenEntity);
}
case CredentialType.REFRESH_TOKEN: {
const refreshTokenEntity: RefreshTokenEntity = new RefreshTokenEntity();
return (CacheHelper.toObject(refreshTokenEntity, JSON.parse(value)) as RefreshTokenEntity);
return (CacheManager.toObject(refreshTokenEntity, JSON.parse(value)) as RefreshTokenEntity);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* Licensed under the MIT License.
*/

export interface IAccount {
export type AccountInfo = {
homeAccountId: string;
environment: string;
tenantId: string;
username: string;
}
};
149 changes: 126 additions & 23 deletions lib/msal-common/src/cache/CacheManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@

import { AccountCache, AccountFilter, CredentialFilter, CredentialCache } from "./utils/CacheTypes";
import { CacheRecord } from "./entities/CacheRecord";
import { CacheSchemaType, CredentialType, Constants } from "../utils/Constants";
import { CacheSchemaType, CredentialType, Constants, EnvironmentAliases, APP_META_DATA } from "../utils/Constants";
import { CredentialEntity } from "./entities/CredentialEntity";
import { ScopeSet } from "../request/ScopeSet";
import { AccountEntity } from "./entities/AccountEntity";
import { AccessTokenEntity } from "./entities/AccessTokenEntity";
import { CacheHelper } from "./utils/CacheHelper";
import { StringUtils } from "../utils/StringUtils";
import { IdTokenEntity } from "./entities/IdTokenEntity";
import { RefreshTokenEntity } from "./entities/RefreshTokenEntity";
import { AuthError } from "../error/AuthError";
import { ICacheManager } from "./interface/ICacheManager";
import { IAccount } from "../account/IAccount";
import { ClientAuthError } from "../error/ClientAuthError";
import { AccountInfo } from "../account/AccountInfo";

/**
* Interface class which implement cache storage functions used by MSAL to perform validity checks, and store tokens.
Expand Down Expand Up @@ -62,16 +61,17 @@ export abstract class CacheManager implements ICacheManager {
/**
* Returns all accounts in cache
*/
getAllAccounts(): IAccount[] {
getAllAccounts(): AccountInfo[] {
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<IAccount>((value) => {
const accountObj: AccountEntity = JSON.parse(JSON.stringify(value));
return CacheHelper.toIAccount(accountObj);
const allAccounts = accountValues.map<AccountInfo>((value) => {
const accountEntity: AccountEntity = new AccountEntity();
const accountObj = CacheManager.toObject(accountEntity, value) as AccountEntity;
return accountObj.getAccountInfo();
});
return allAccounts;
}
Expand Down Expand Up @@ -131,7 +131,7 @@ export abstract class CacheManager implements ICacheManager {

/**
* saves access token credential
* @param credential
* @param credential
*/
private saveAccessToken(credential: AccessTokenEntity, responseScopes: ScopeSet): void {
const currentTokenCache = this.getCredentialsFilteredBy({
Expand Down Expand Up @@ -199,25 +199,32 @@ export abstract class CacheManager implements ICacheManager {
): AccountCache {
const allCacheKeys = this.getKeys();
const matchingAccounts: AccountCache = {};
let entity: AccountEntity;

allCacheKeys.forEach((cacheKey) => {
let matches: boolean = true;
// don't parse any non-account type cache entities
if (CacheHelper.getCredentialType(cacheKey) !== Constants.NOT_DEFINED || CacheHelper.isAppMetadata(cacheKey)) {
if (CredentialEntity.getCredentialType(cacheKey) !== Constants.NOT_DEFINED || this.isAppMetadata(cacheKey)) {
return;
}

// Attempt retrieval
try {
entity = this.getItem(cacheKey, CacheSchemaType.ACCOUNT) as AccountEntity;
} catch (e) {
return;
}
const entity: AccountEntity = this.getItem(cacheKey, CacheSchemaType.ACCOUNT) as AccountEntity;

if (!StringUtils.isEmpty(homeAccountId)) {
matches = CacheHelper.matchHomeAccountId(entity, homeAccountId);
matches = this.matchHomeAccountId(entity, homeAccountId);
}

if (!StringUtils.isEmpty(environment)) {
matches = matches && CacheHelper.matchEnvironment(entity, environment);
matches = matches && this.matchEnvironment(entity, environment);
}

if (!StringUtils.isEmpty(realm)) {
matches = matches && CacheHelper.matchRealm(entity, realm);
matches = matches && this.matchRealm(entity, realm);
}

if (matches) {
Expand Down Expand Up @@ -274,38 +281,44 @@ export abstract class CacheManager implements ICacheManager {

allCacheKeys.forEach((cacheKey) => {
let matches: boolean = true;
let entity: CredentialEntity;
// don't parse any non-credential type cache entities
const credType = CacheHelper.getCredentialType(cacheKey);
const credType = CredentialEntity.getCredentialType(cacheKey);
if (credType === Constants.NOT_DEFINED) {
return;
}

const entity: CredentialEntity = this.getItem(cacheKey, CacheSchemaType.CREDENTIAL) as CredentialEntity;
// Attempt retrieval
try {
entity = this.getItem(cacheKey, CacheSchemaType.CREDENTIAL) as CredentialEntity;
} catch (e) {
return;
}

if (!StringUtils.isEmpty(homeAccountId)) {
matches = CacheHelper.matchHomeAccountId(entity, homeAccountId);
matches = this.matchHomeAccountId(entity, homeAccountId);
}

if (!StringUtils.isEmpty(environment)) {
matches = matches && CacheHelper.matchEnvironment(entity, environment);
matches = matches && this.matchEnvironment(entity, environment);
}

if (!StringUtils.isEmpty(realm)) {
matches = matches && CacheHelper.matchRealm(entity, realm);
matches = matches && this.matchRealm(entity, realm);
}

if (!StringUtils.isEmpty(credentialType)) {
matches = matches && CacheHelper.matchCredentialType(entity, credentialType);
matches = matches && this.matchCredentialType(entity, credentialType);
}

if (!StringUtils.isEmpty(clientId)) {
matches = matches && CacheHelper.matchClientId(entity, clientId);
matches = matches && this.matchClientId(entity, clientId);
}

// idTokens do not have "target", target specific refreshTokens do exist for some types of authentication
// TODO: Add case for target specific refresh tokens
if (!StringUtils.isEmpty(target) && credType === CredentialType.ACCESS_TOKEN) {
matches = matches && CacheHelper.matchTarget(entity, target);
matches = matches && this.matchTarget(entity, target);
}

if (matches) {
Expand Down Expand Up @@ -348,7 +361,7 @@ export abstract class CacheManager implements ICacheManager {

allCacheKeys.forEach((cacheKey) => {
// don't parse any non-credential type cache entities
if (CacheHelper.getCredentialType(cacheKey) === Constants.NOT_DEFINED) {
if (CredentialEntity.getCredentialType(cacheKey) === Constants.NOT_DEFINED) {
return;
}

Expand All @@ -370,6 +383,96 @@ export abstract class CacheManager implements ICacheManager {
const key = credential.generateCredentialKey();
return this.removeItem(key, CacheSchemaType.CREDENTIAL);
}

/**
*
* @param value
* @param homeAccountId
*/
private matchHomeAccountId(
entity: AccountEntity | CredentialEntity,
homeAccountId: string
): boolean {
return homeAccountId === entity.homeAccountId;
}

/**
*
* @param value
* @param environment
* // TODO: Add Cloud specific aliases based on current cloud
*/
private matchEnvironment(
entity: AccountEntity | CredentialEntity,
environment: string
): boolean {
if (
EnvironmentAliases.includes(environment) &&
EnvironmentAliases.includes(entity.environment)
) {
return true;
}

return false;
}

/**
*
* @param entity
* @param credentialType
*/
private matchCredentialType(entity: CredentialEntity, credentialType: string): boolean {
return credentialType.toLowerCase() === entity.credentialType.toLowerCase();
}

/**
*
* @param entity
* @param clientId
*/
private matchClientId(entity: CredentialEntity, clientId: string): boolean {
return clientId === entity.clientId;
}

/**
*
* @param entity
* @param realm
*/
private matchRealm(entity: AccountEntity | CredentialEntity, realm: string): boolean {
return realm === entity.realm;
}

/**
* Returns true if the target scopes are a subset of the current entity's scopes, false otherwise.
* @param entity
* @param target
*/
private matchTarget(entity: CredentialEntity, target: string): boolean {
const entityScopeSet: ScopeSet = ScopeSet.fromString(entity.target);
const requestTargetScopeSet: ScopeSet = ScopeSet.fromString(target);
return entityScopeSet.containsScopeSet(requestTargetScopeSet);
}

/**
* returns if a given cache entity is of the type appmetadata
* @param key
*/
private isAppMetadata(key: string): boolean {
return key.indexOf(APP_META_DATA) !== -1;
}

/**
* Helper to convert serialized data to object
* @param obj
* @param json
*/
static toObject<T>(obj: T, json: object): T {
for (const propertyName in json) {
obj[propertyName] = json[propertyName];
}
return obj;
}
}

export class DefaultStorageClass extends CacheManager {
Expand All @@ -395,6 +498,6 @@ export class DefaultStorageClass extends CacheManager {
}
clear(): void {
const notImplErr = "Storage interface - clear() has not been implemented for the cacheStorage interface.";
throw AuthError.createUnexpectedError(notImplErr);
throw AuthError.createUnexpectedError(notImplErr);
}
}
21 changes: 21 additions & 0 deletions lib/msal-common/src/cache/entities/AccessTokenEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ import { TimeUtils } from "../../utils/TimeUtils";

/**
* ACCESS_TOKEN Credential Type
*
* Key:Value Schema:
*
* Key Example: uid.utid-login.microsoftonline.com-accesstoken-clientId-contoso.com-user.read
*
* Value Schema:
* {
* homeAccountId: home account identifier for the auth scheme,
* environment: entity that issued the token, represented as a full host
* credentialType: Type of credential as a string, can be one of the following: RefreshToken, AccessToken, IdToken, Password, Cookie, Certificate, Other
* clientId: client ID of the application
* secret: Actual credential as a string
* familyId: Family ID identifier, usually only used for refresh tokens
* realm: Full tenant or organizational identifier that the account belongs to
* target: Permissions that are included in the token, or for refresh tokens, the resource identifier.
* cachedAt: Absolute device time when entry was created in the cache.
* expiresOn: Token expiry time, calculated based on current UTC time in seconds. Represented as a string.
* extendedExpiresOn: Additional extended expiry time until when token is valid in case of server-side outage. Represented as string in UTC seconds.
* keyId: used for POP and SSH tokenTypes
* tokenType: Type of the token issued. Usually "Bearer"
* }
*/
export class AccessTokenEntity extends CredentialEntity {
realm: string;
Expand Down

0 comments on commit f842023

Please sign in to comment.