Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changing IAccount Interface to AccountInfo #1789

Merged
merged 18 commits into from
Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
pkanher617 marked this conversation as resolved.
Show resolved Hide resolved
}
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