UNPKG

msal

Version:
233 lines (204 loc) 8.34 kB
/* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ import { ClientConfigurationError } from "./error/ClientConfigurationError"; import { Constants } from "./utils/Constants"; export class ScopeSet { /** * Check if there are dup scopes in a given request * * @param cachedScopes * @param scopes */ // TODO: Rename this, intersecting scopes isn't a great name for duplicate checker static isIntersectingScopes(cachedScopes: Array<string>, scopes: Array<string>): boolean { const convertedCachedScopes = this.trimAndConvertArrayToLowerCase([...cachedScopes]); const requestScopes = this.trimAndConvertArrayToLowerCase([...scopes]); for (let i = 0; i < requestScopes.length; i++) { if (convertedCachedScopes.indexOf(requestScopes[i].toLowerCase()) > -1) { return true; } } return false; } /** * Check if a given scope is present in the request * * @param cachedScopes * @param scopes */ static containsScope(cachedScopes: Array<string>, scopes: Array<string>): boolean { const convertedCachedScopes = this.trimAndConvertArrayToLowerCase([...cachedScopes]); const requestScopes = this.trimAndConvertArrayToLowerCase([...scopes]); return requestScopes.every((value: string): boolean => convertedCachedScopes.indexOf(value.toString().toLowerCase()) >= 0); } /** * Trims and converts string to lower case * * @param scopes */ // TODO: Rename this, too generic name for a function that only deals with scopes static trimAndConvertToLowerCase(scope: string): string { return scope.trim().toLowerCase(); } /** * Performs trimAndConvertToLowerCase on string array * @param scopes */ static trimAndConvertArrayToLowerCase(scopes: Array<string>): Array<string> { return scopes.map(scope => this.trimAndConvertToLowerCase(scope)); } /** * Trims each scope in scopes array * @param scopes */ static trimScopes(scopes: Array<string>): Array<string> { return scopes.map(scope => scope.trim()); } /** * Remove one element from a scope array * * @param scopes * @param scope */ // TODO: Rename this, too generic name for a function that only deals with scopes static removeElement(scopes: Array<string>, scope: string): Array<string> { const scopeVal = this.trimAndConvertToLowerCase(scope); return scopes.filter(value => value !== scopeVal); } /** * Parse the scopes into a formatted scopeList * @param scopes */ static parseScope(scopes: Array<string>): string { let scopeList: string = ""; if (scopes) { for (let i: number = 0; i < scopes.length; ++i) { scopeList += (i !== scopes.length - 1) ? scopes[i] + " " : scopes[i]; } } return scopeList; } /** * @hidden * * Used to validate the scopes input parameter requested by the developer. * @param {Array<string>} scopes - Developer requested permissions. Not all scopes are guaranteed to be included in the access token returned. * @param {boolean} scopesRequired - Boolean indicating whether the scopes array is required or not * @ignore */ static validateInputScope(scopes: Array<string>, scopesRequired: boolean): void { if (!scopes) { if (scopesRequired) { throw ClientConfigurationError.createScopesRequiredError(scopes); } else { return; } } // Check that scopes is an array object (also throws error if scopes == null) if (!Array.isArray(scopes)) { throw ClientConfigurationError.createScopesNonArrayError(scopes); } // Check that scopes is not an empty array if (scopes.length < 1 && scopesRequired) { throw ClientConfigurationError.createEmptyScopesArrayError(scopes.toString()); } } /** * @hidden * * Extracts scope value from the state sent with the authentication request. * @param {string} state * @returns {string} scope. * @ignore */ static getScopeFromState(state: string): string { if (state) { const splitIndex = state.indexOf(Constants.resourceDelimiter); if (splitIndex > -1 && splitIndex + 1 < state.length) { return state.substring(splitIndex + 1); } } return ""; } /** * @ignore * Appends extraScopesToConsent if passed * @param {@link AuthenticationParameters} */ static appendScopes(reqScopes: Array<string>, reqExtraScopesToConsent: Array<string>): Array<string> { if (reqScopes) { const convertedExtraScopes = reqExtraScopesToConsent ? this.trimAndConvertArrayToLowerCase([...reqExtraScopesToConsent]) : null; const convertedReqScopes = this.trimAndConvertArrayToLowerCase([...reqScopes]); return convertedExtraScopes ? [...convertedReqScopes, ...convertedExtraScopes] : convertedReqScopes; } return null; } // #endregion /** * @ignore * Returns true if the scopes array only contains openid and/or profile */ static onlyContainsOidcScopes(scopes: Array<string>): boolean { const scopesCount = scopes.length; let oidcScopesFound = 0; if (scopes.indexOf(Constants.openidScope) > -1) { oidcScopesFound += 1; } if (scopes.indexOf(Constants.profileScope) > -1) { oidcScopesFound += 1; } return (scopesCount > 0 && scopesCount === oidcScopesFound); } /** * @ignore * Returns true if the scopes array only contains openid and/or profile */ static containsAnyOidcScopes(scopes: Array<string>): boolean { const containsOpenIdScope = scopes.indexOf(Constants.openidScope) > -1; const containsProfileScope = scopes.indexOf(Constants.profileScope) > -1; return (containsOpenIdScope || containsProfileScope); } /** * @ignore * Returns true if the clientId is the only scope in the array */ static onlyContainsClientId(scopes: Array<String>, clientId: string): boolean { // Double negation to force false value returned in case scopes is null return !!scopes && (scopes.indexOf(clientId) > -1 && scopes.length === 1); } /** * @ignore * Adds missing OIDC scopes to scopes array without duplication. Since STS requires OIDC scopes for * all implicit flow requests, 'openid' and 'profile' should always be included in the final request */ static appendDefaultScopes(scopes: Array<string>): Array<string> { const extendedScopes = scopes; if (extendedScopes.indexOf(Constants.openidScope) === -1) { extendedScopes.push(Constants.openidScope); } if(extendedScopes.indexOf(Constants.profileScope) === -1) { extendedScopes.push(Constants.profileScope); } return extendedScopes; } /** * @ignore * Removes present OIDC scopes from scopes array. */ static removeDefaultScopes(scopes: Array<string>): Array<string> { return scopes.filter(scope => { return (scope !== Constants.openidScope && scope !== Constants.profileScope); }); } /** * @ignore * Removes clientId from scopes array if included as only scope. If it's not the only scope, it is treated as a resource scope. * @param scopes Array<string>: Pre-normalized scopes array * @param clientId string: The application's clientId that is searched for in the scopes array */ static translateClientIdIfSingleScope(scopes: Array<string>, clientId: string): Array<string> { return this.onlyContainsClientId(scopes, clientId) ? Constants.oidcScopes : scopes; } }