<commit_before>/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */

import { AccessTokenEntity, ICrypto, IdTokenEntity, Logger, ScopeSet, Authority, AuthorityOptions, ExternalTokenResponse, AccountEntity, AuthToken, RefreshTokenEntity , AuthorityType, Constants, CacheRecord } from "@azure/msal-common";
import { BrowserConfiguration } from "../config/Configuration";
import { SilentRequest } from "../request/SilentRequest";
import { BrowserCacheManager } from "./BrowserCacheManager";
import { ITokenCache } from "./ITokenCache";
import { BrowserAuthError } from "../error/BrowserAuthError";

export type LoadTokenOptions = {
    clientInfo?: string,
    expiresOn?: number,
    extendedExpiresOn?: number
};

/**
 * Token cache manager
 */
export class TokenCache implements ITokenCache {
    // Flag to indicate if in browser environment
    public isBrowserEnvironment: boolean;
    // Input configuration by developer/user
    protected config: BrowserConfiguration;
    // Browser cache storage
    private storage: BrowserCacheManager;
    // Logger
    private logger: Logger;
    // Crypto class
    private cryptoObj: ICrypto;

    constructor(configuration: BrowserConfiguration, storage: BrowserCacheManager, logger: Logger, cryptoObj: ICrypto) {
        this.isBrowserEnvironment = typeof window !== "undefined";
        this.config = configuration;
        this.storage = storage;
        this.logger = logger;
        this.cryptoObj = cryptoObj;
    }

    // Move getAllAccounts here and cache utility APIs

    /**
     * API to load tokens to msal-browser cache.
     * @param request
     * @param response
     * @param options
     * @returns A `CacheRecord` containing the entities that were loaded.
     */
    loadExternalTokens(request: SilentRequest, response: ExternalTokenResponse, options: LoadTokenOptions): CacheRecord {
        this.logger.info("TokenCache - loadExternalTokens called");

        if (!response.id_token) {
            throw BrowserAuthError.createUnableToLoadTokenError("Please ensure server response includes id token.");
        }

        if (request.account) {
            const cacheRecord = this.loadIdToken(response.id_token, Constants.EMPTY_STRING, request.account.environment, request.account.tenantId, options, undefined, request.account.homeAccountId);
            this.loadAccessToken(request, response, cacheRecord, request.account.environment, request.account.tenantId, options);
            this.loadRefreshToken(request, response, cacheRecord, request.account.environment);
            return cacheRecord;
        } else if (request.authority) {

            const authorityUrl = Authority.generateAuthority(request.authority, request.azureCloudOptions);
            const authorityOptions: AuthorityOptions = {
                protocolMode: this.config.auth.protocolMode,
                knownAuthorities: this.config.auth.knownAuthorities,
                cloudDiscoveryMetadata: this.config.auth.cloudDiscoveryMetadata,
                authorityMetadata: this.config.auth.authorityMetadata,
                skipAuthorityMetadataCache: this.config.auth.skipAuthorityMetadataCache,
            };
            const authority = new Authority(authorityUrl, this.config.system.networkClient, this.storage, authorityOptions);

            // "clientInfo" from options takes precedence over "clientInfo" in response
            if (options.clientInfo) {
                this.logger.trace("TokenCache - homeAccountId from options");
                const cacheRecord = this.loadIdToken(response.id_token, options.clientInfo, authority.hostnameAndPort, authority.tenant, options, authority.authorityType);
                this.loadAccessToken(request, response, cacheRecord, authority.hostnameAndPort, authority.tenant, options);
                this.loadRefreshToken(request, response, cacheRecord, authority.hostnameAndPort);
                return cacheRecord;
            } else if (response.client_info) {
                this.logger.trace("TokenCache - homeAccountId from response");
                const cacheRecord = this.loadIdToken(response.id_token, response.client_info, authority.hostnameAndPort, authority.tenant, options, authority.authorityType);
                this.loadAccessToken(request, response, cacheRecord, authority.hostnameAndPort, authority.tenant, options);
                this.loadRefreshToken(request, response, cacheRecord, authority.hostnameAndPort);
                return cacheRecord;
            } else {
                throw BrowserAuthError.createUnableToLoadTokenError("Please provide clientInfo in the response or options.");
            }
        } else {
            throw BrowserAuthError.createUnableToLoadTokenError("Please provide a request with an account or a request with authority.");
        }
    }

    /**
     * Helper function to load id tokens to msal-browser cache
     * @param idToken
     * @param clientInfo
     * @param environment
     * @param tenantId
     * @param options
     * @param authorityType
     * @param homeAccountId
     * @returns `CacheRecord` containing the id and account information that was loaded.
     */
    private loadIdToken(idToken: string, clientInfo: string, environment: string, tenantId: string, options: LoadTokenOptions, authorityType?: AuthorityType, homeAccountId?: string): CacheRecord {

        const idAuthToken = new AuthToken(idToken, this.cryptoObj);
        const idTokenHomeAccountId = homeAccountId ?
            homeAccountId :
            authorityType !== undefined ?
                AccountEntity.generateHomeAccountId(clientInfo, authorityType, this.logger, this.cryptoObj, idAuthToken) :
                clientInfo;

        const idTokenEntity = IdTokenEntity.createIdTokenEntity(idTokenHomeAccountId, environment, idToken, this.config.auth.clientId, tenantId);
        const accountEntity = options.clientInfo ?
            AccountEntity.createAccount(options.clientInfo, idTokenHomeAccountId, idAuthToken, undefined, undefined, undefined, environment) :
            AccountEntity.createGenericAccount(idTokenHomeAccountId, idAuthToken, undefined, undefined, undefined, environment);<commit_msg>PR feedback: prefer not to use nested ternary operators<commit_after>/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */

import { AccessTokenEntity, ICrypto, IdTokenEntity, Logger, ScopeSet, Authority, AuthorityOptions, ExternalTokenResponse, AccountEntity, AuthToken, RefreshTokenEntity , AuthorityType, Constants, CacheRecord } from "@azure/msal-common";
import { BrowserConfiguration } from "../config/Configuration";
import { SilentRequest } from "../request/SilentRequest";
import { BrowserCacheManager } from "./BrowserCacheManager";
import { ITokenCache } from "./ITokenCache";
import { BrowserAuthError } from "../error/BrowserAuthError";

export type LoadTokenOptions = {
    clientInfo?: string,
    expiresOn?: number,
    extendedExpiresOn?: number
};

/**
 * Token cache manager
 */
export class TokenCache implements ITokenCache {
    // Flag to indicate if in browser environment
    public isBrowserEnvironment: boolean;
    // Input configuration by developer/user
    protected config: BrowserConfiguration;
    // Browser cache storage
    private storage: BrowserCacheManager;
    // Logger
    private logger: Logger;
    // Crypto class
    private cryptoObj: ICrypto;

    constructor(configuration: BrowserConfiguration, storage: BrowserCacheManager, logger: Logger, cryptoObj: ICrypto) {
        this.isBrowserEnvironment = typeof window !== "undefined";
        this.config = configuration;
        this.storage = storage;
        this.logger = logger;
        this.cryptoObj = cryptoObj;
    }

    // Move getAllAccounts here and cache utility APIs

    /**
     * API to load tokens to msal-browser cache.
     * @param request
     * @param response
     * @param options
     * @returns A `CacheRecord` containing the entities that were loaded.
     */
    loadExternalTokens(request: SilentRequest, response: ExternalTokenResponse, options: LoadTokenOptions): CacheRecord {
        this.logger.info("TokenCache - loadExternalTokens called");

        if (!response.id_token) {
            throw BrowserAuthError.createUnableToLoadTokenError("Please ensure server response includes id token.");
        }

        if (request.account) {
            const cacheRecord = this.loadIdToken(response.id_token, Constants.EMPTY_STRING, request.account.environment, request.account.tenantId, options, undefined, request.account.homeAccountId);
            this.loadAccessToken(request, response, cacheRecord, request.account.environment, request.account.tenantId, options);
            this.loadRefreshToken(request, response, cacheRecord, request.account.environment);
            return cacheRecord;
        } else if (request.authority) {

            const authorityUrl = Authority.generateAuthority(request.authority, request.azureCloudOptions);
            const authorityOptions: AuthorityOptions = {
                protocolMode: this.config.auth.protocolMode,
                knownAuthorities: this.config.auth.knownAuthorities,
                cloudDiscoveryMetadata: this.config.auth.cloudDiscoveryMetadata,
                authorityMetadata: this.config.auth.authorityMetadata,
                skipAuthorityMetadataCache: this.config.auth.skipAuthorityMetadataCache,
            };
            const authority = new Authority(authorityUrl, this.config.system.networkClient, this.storage, authorityOptions);

            // "clientInfo" from options takes precedence over "clientInfo" in response
            if (options.clientInfo) {
                this.logger.trace("TokenCache - homeAccountId from options");
                const cacheRecord = this.loadIdToken(response.id_token, options.clientInfo, authority.hostnameAndPort, authority.tenant, options, authority.authorityType);
                this.loadAccessToken(request, response, cacheRecord, authority.hostnameAndPort, authority.tenant, options);
                this.loadRefreshToken(request, response, cacheRecord, authority.hostnameAndPort);
                return cacheRecord;
            } else if (response.client_info) {
                this.logger.trace("TokenCache - homeAccountId from response");
                const cacheRecord = this.loadIdToken(response.id_token, response.client_info, authority.hostnameAndPort, authority.tenant, options, authority.authorityType);
                this.loadAccessToken(request, response, cacheRecord, authority.hostnameAndPort, authority.tenant, options);
                this.loadRefreshToken(request, response, cacheRecord, authority.hostnameAndPort);
                return cacheRecord;
            } else {
                throw BrowserAuthError.createUnableToLoadTokenError("Please provide clientInfo in the response or options.");
            }
        } else {
            throw BrowserAuthError.createUnableToLoadTokenError("Please provide a request with an account or a request with authority.");
        }
    }

    /**
     * Helper function to load id tokens to msal-browser cache
     * @param idToken
     * @param clientInfo
     * @param environment
     * @param tenantId
     * @param options
     * @param authorityType
     * @param homeAccountId
     * @returns `CacheRecord` containing the id and account information that was loaded.
     */
    private loadIdToken(idToken: string, clientInfo: string, environment: string, tenantId: string, options: LoadTokenOptions, authorityType?: AuthorityType, homeAccountId?: string): CacheRecord {

        const idAuthToken = new AuthToken(idToken, this.cryptoObj);

        let idTokenHomeAccountId;
        if (homeAccountId) {
            idTokenHomeAccountId = homeAccountId;
        } else if (authorityType !== undefined) {
            idTokenHomeAccountId = AccountEntity.generateHomeAccountId(clientInfo, authorityType, this.logger, this.cryptoObj, idAuthToken);
        } else {
            idTokenHomeAccountId = clientInfo;
        }

        const idTokenEntity = IdTokenEntity.createIdTokenEntity(idTokenHomeAccountId, environment, idToken, this.config.auth.clientId, tenantId);
        const accountEntity = options.clientInfo ?
            AccountEntity.createAccount(options.clientInfo, idTokenHomeAccountId, idAuthToken, undefined, undefined, undefined, environment) :
            AccountEntity.createGenericAccount(idTokenHomeAccountId, idAuthToken, undefined, undefined, undefined, environment);
{'fraction_non_alphanumeric': 0.08409361364537882, 'fraction_numerical': 0.0, 'mean_word_length': 3.265989847715736, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4139521', 'n_tokens_mistral': 3033, 'n_tokens_neox': 2985, 'n_words': 802}