angular-auth-oidc-client
Version:
Angular Lib for OpenID Connect & OAuth2
438 lines • 72.7 kB
JavaScript
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { base64url } from 'rfc4648';
import { from, of } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
import * as i0 from "@angular/core";
import * as i1 from "../utils/tokenHelper/token-helper.service";
import * as i2 from "../logging/logger.service";
import * as i3 from "../extractors/jwk.extractor";
import * as i4 from "./jwk-window-crypto.service";
import * as i5 from "./jwt-window-crypto.service";
// http://openid.net/specs/openid-connect-implicit-1_0.html
// id_token
// id_token C1: The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
// MUST exactly match the value of the iss (issuer) Claim.
//
// id_token C2: The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified
// by the iss (issuer) Claim as an audience.The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience,
// or if it contains additional audiences not trusted by the Client.
//
// id_token C3: If the ID Token contains multiple audiences, the Client SHOULD verify that an azp Claim is present.
//
// id_token C4: If an azp (authorized party) Claim is present, the Client SHOULD verify that its client_id is the Claim Value.
//
// id_token C5: The Client MUST validate the signature of the ID Token according to JWS [JWS] using the algorithm specified in the
// alg Header Parameter of the JOSE Header.The Client MUST use the keys provided by the Issuer.
//
// id_token C6: The alg value SHOULD be RS256. Validation of tokens using other signing algorithms is described in the OpenID Connect
// Core 1.0
// [OpenID.Core] specification.
//
// id_token C7: The current time MUST be before the time represented by the exp Claim (possibly allowing for some small leeway to account
// for clock skew).
//
// id_token C8: The iat Claim can be used to reject tokens that were issued too far away from the current time,
// limiting the amount of time that nonces need to be stored to prevent attacks.The acceptable range is Client specific.
//
// id_token C9: The value of the nonce Claim MUST be checked to verify that it is the same value as the one that was sent
// in the Authentication Request.The Client SHOULD check the nonce value for replay attacks.The precise method for detecting replay attacks
// is Client specific.
//
// id_token C10: If the acr Claim was requested, the Client SHOULD check that the asserted Claim Value is appropriate.
// The meaning and processing of acr Claim Values is out of scope for this document.
//
// id_token C11: When a max_age request is made, the Client SHOULD check the auth_time Claim value and request re- authentication
// if it determines too much time has elapsed since the last End- User authentication.
// Access Token Validation
// access_token C1: Hash the octets of the ASCII representation of the access_token with the hash algorithm specified in JWA[JWA]
// for the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, the hash algorithm used is SHA-256.
// access_token C2: Take the left- most half of the hash and base64url- encode it.
// access_token C3: The value of at_hash in the ID Token MUST match the value produced in the previous step if at_hash is present
// in the ID Token.
export class TokenValidationService {
constructor(tokenHelperService, loggerService, jwkExtractor, jwkWindowCryptoService, jwtWindowCryptoService, document) {
this.tokenHelperService = tokenHelperService;
this.loggerService = loggerService;
this.jwkExtractor = jwkExtractor;
this.jwkWindowCryptoService = jwkWindowCryptoService;
this.jwtWindowCryptoService = jwtWindowCryptoService;
this.document = document;
this.keyAlgorithms = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'PS256', 'PS384', 'PS512'];
}
// id_token C7: The current time MUST be before the time represented by the exp Claim
// (possibly allowing for some small leeway to account for clock skew).
hasIdTokenExpired(token, configuration, offsetSeconds, disableIdTokenValidation) {
const decoded = this.tokenHelperService.getPayloadFromToken(token, false, configuration);
return !this.validateIdTokenExpNotExpired(decoded, configuration, offsetSeconds, disableIdTokenValidation);
}
// id_token C7: The current time MUST be before the time represented by the exp Claim
// (possibly allowing for some small leeway to account for clock skew).
validateIdTokenExpNotExpired(decodedIdToken, configuration, offsetSeconds, disableIdTokenValidation) {
if (disableIdTokenValidation) {
return true;
}
const tokenExpirationDate = this.tokenHelperService.getTokenExpirationDate(decodedIdToken);
offsetSeconds = offsetSeconds || 0;
if (!tokenExpirationDate) {
return false;
}
const tokenExpirationValue = tokenExpirationDate.valueOf();
const nowWithOffset = this.calculateNowWithOffset(offsetSeconds);
const tokenNotExpired = tokenExpirationValue > nowWithOffset;
this.loggerService.logDebug(configuration, `Has idToken expired: ${!tokenNotExpired} --> expires in ${this.millisToMinutesAndSeconds(tokenExpirationValue - nowWithOffset)} , ${new Date(tokenExpirationValue).toLocaleTimeString()} > ${new Date(nowWithOffset).toLocaleTimeString()}`);
return tokenNotExpired;
}
validateAccessTokenNotExpired(accessTokenExpiresAt, configuration, offsetSeconds) {
// value is optional, so if it does not exist, then it has not expired
if (!accessTokenExpiresAt) {
return true;
}
offsetSeconds = offsetSeconds || 0;
const accessTokenExpirationValue = accessTokenExpiresAt.valueOf();
const nowWithOffset = this.calculateNowWithOffset(offsetSeconds);
const tokenNotExpired = accessTokenExpirationValue > nowWithOffset;
this.loggerService.logDebug(configuration, `Has accessToken expired: ${!tokenNotExpired} --> expires in ${this.millisToMinutesAndSeconds(accessTokenExpirationValue - nowWithOffset)} , ${new Date(accessTokenExpirationValue).toLocaleTimeString()} > ${new Date(nowWithOffset).toLocaleTimeString()}`);
return tokenNotExpired;
}
// iss
// REQUIRED. Issuer Identifier for the Issuer of the response.The iss value is a case-sensitive URL using the
// https scheme that contains scheme, host,
// and optionally, port number and path components and no query or fragment components.
//
// sub
// REQUIRED. Subject Identifier.Locally unique and never reassigned identifier within the Issuer for the End- User,
// which is intended to be consumed by the Client, e.g., 24400320 or AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4.
// It MUST NOT exceed 255 ASCII characters in length.The sub value is a case-sensitive string.
//
// aud
// REQUIRED. Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an
// audience value.
// It MAY also contain identifiers for other audiences.In the general case, the aud value is an array of case-sensitive strings.
// In the common special case when there is one audience, the aud value MAY be a single case-sensitive string.
//
// exp
// REQUIRED. Expiration time on or after which the ID Token MUST NOT be accepted for processing.
// The processing of this parameter requires that the current date/ time MUST be before the expiration date/ time listed in the value.
// Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew.
// Its value is a JSON [RFC7159] number representing the number of seconds from 1970- 01 - 01T00: 00:00Z as measured in UTC until
// the date/ time.
// See RFC 3339 [RFC3339] for details regarding date/ times in general and UTC in particular.
//
// iat
// REQUIRED. Time at which the JWT was issued. Its value is a JSON number representing the number of seconds from
// 1970- 01 - 01T00: 00: 00Z as measured
// in UTC until the date/ time.
validateRequiredIdToken(dataIdToken, configuration) {
let validated = true;
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'iss')) {
validated = false;
this.loggerService.logWarning(configuration, 'iss is missing, this is required in the id_token');
}
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'sub')) {
validated = false;
this.loggerService.logWarning(configuration, 'sub is missing, this is required in the id_token');
}
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'aud')) {
validated = false;
this.loggerService.logWarning(configuration, 'aud is missing, this is required in the id_token');
}
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'exp')) {
validated = false;
this.loggerService.logWarning(configuration, 'exp is missing, this is required in the id_token');
}
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'iat')) {
validated = false;
this.loggerService.logWarning(configuration, 'iat is missing, this is required in the id_token');
}
return validated;
}
// id_token C8: The iat Claim can be used to reject tokens that were issued too far away from the current time,
// limiting the amount of time that nonces need to be stored to prevent attacks.The acceptable range is Client specific.
validateIdTokenIatMaxOffset(dataIdToken, maxOffsetAllowedInSeconds, disableIatOffsetValidation, configuration) {
if (disableIatOffsetValidation) {
return true;
}
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'iat')) {
return false;
}
const dateTimeIatIdToken = new Date(0); // The 0 here is the key, which sets the date to the epoch
dateTimeIatIdToken.setUTCSeconds(dataIdToken.iat);
maxOffsetAllowedInSeconds = maxOffsetAllowedInSeconds || 0;
const nowInUtc = new Date(new Date().toUTCString());
const diff = nowInUtc.valueOf() - dateTimeIatIdToken.valueOf();
const maxOffsetAllowedInMilliseconds = maxOffsetAllowedInSeconds * 1000;
this.loggerService.logDebug(configuration, `validate id token iat max offset ${diff} < ${maxOffsetAllowedInMilliseconds}`);
if (diff > 0) {
return diff < maxOffsetAllowedInMilliseconds;
}
return -diff < maxOffsetAllowedInMilliseconds;
}
// id_token C9: The value of the nonce Claim MUST be checked to verify that it is the same value as the one
// that was sent in the Authentication Request.The Client SHOULD check the nonce value for replay attacks.
// The precise method for detecting replay attacks is Client specific.
// However the nonce claim SHOULD not be present for the refresh_token grant type
// https://bitbucket.org/openid/connect/issues/1025/ambiguity-with-how-nonce-is-handled-on
// The current spec is ambiguous and KeyCloak does send it.
validateIdTokenNonce(dataIdToken, localNonce, ignoreNonceAfterRefresh, configuration) {
const isFromRefreshToken = (dataIdToken.nonce === undefined || ignoreNonceAfterRefresh) && localNonce === TokenValidationService.refreshTokenNoncePlaceholder;
if (!isFromRefreshToken && dataIdToken.nonce !== localNonce) {
this.loggerService.logDebug(configuration, 'Validate_id_token_nonce failed, dataIdToken.nonce: ' + dataIdToken.nonce + ' local_nonce:' + localNonce);
return false;
}
return true;
}
// id_token C1: The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
// MUST exactly match the value of the iss (issuer) Claim.
validateIdTokenIss(dataIdToken, authWellKnownEndpointsIssuer, configuration) {
if (dataIdToken.iss !== authWellKnownEndpointsIssuer) {
this.loggerService.logDebug(configuration, 'Validate_id_token_iss failed, dataIdToken.iss: ' +
dataIdToken.iss +
' authWellKnownEndpoints issuer:' +
authWellKnownEndpointsIssuer);
return false;
}
return true;
}
// id_token C2: The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified
// by the iss (issuer) Claim as an audience.
// The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences
// not trusted by the Client.
validateIdTokenAud(dataIdToken, aud, configuration) {
if (Array.isArray(dataIdToken.aud)) {
const result = dataIdToken.aud.includes(aud);
if (!result) {
this.loggerService.logDebug(configuration, 'Validate_id_token_aud array failed, dataIdToken.aud: ' + dataIdToken.aud + ' client_id:' + aud);
return false;
}
return true;
}
else if (dataIdToken.aud !== aud) {
this.loggerService.logDebug(configuration, 'Validate_id_token_aud failed, dataIdToken.aud: ' + dataIdToken.aud + ' client_id:' + aud);
return false;
}
return true;
}
validateIdTokenAzpExistsIfMoreThanOneAud(dataIdToken) {
if (!dataIdToken) {
return false;
}
if (Array.isArray(dataIdToken.aud) && dataIdToken.aud.length > 1 && !dataIdToken.azp) {
return false;
}
return true;
}
// If an azp (authorized party) Claim is present, the Client SHOULD verify that its client_id is the Claim Value.
validateIdTokenAzpValid(dataIdToken, clientId) {
if (!dataIdToken?.azp) {
return true;
}
if (dataIdToken.azp === clientId) {
return true;
}
return false;
}
validateStateFromHashCallback(state, localState, configuration) {
if (state !== localState) {
this.loggerService.logDebug(configuration, 'ValidateStateFromHashCallback failed, state: ' + state + ' local_state:' + localState);
return false;
}
return true;
}
// id_token C5: The Client MUST validate the signature of the ID Token according to JWS [JWS] using the algorithm specified in the alg
// Header Parameter of the JOSE Header.The Client MUST use the keys provided by the Issuer.
// id_token C6: The alg value SHOULD be RS256. Validation of tokens using other signing algorithms is described in the
// OpenID Connect Core 1.0 [OpenID.Core] specification.
validateSignatureIdToken(idToken, jwtkeys, configuration) {
if (!jwtkeys || !jwtkeys.keys) {
return of(false);
}
const headerData = this.tokenHelperService.getHeaderFromToken(idToken, false, configuration);
if (Object.keys(headerData).length === 0 && headerData.constructor === Object) {
this.loggerService.logWarning(configuration, 'id token has no header data');
return of(false);
}
const kid = headerData.kid;
let alg = headerData.alg;
let keys = jwtkeys.keys;
let foundKeys;
let key;
if (!this.keyAlgorithms.includes(alg)) {
this.loggerService.logWarning(configuration, 'alg not supported', alg);
return of(false);
}
const kty = this.alg2kty(alg);
const use = 'sig';
try {
foundKeys = kid ?
this.jwkExtractor.extractJwk(keys, { kid, kty, use }, false) :
this.jwkExtractor.extractJwk(keys, { kty, use }, false);
if (foundKeys.length === 0) {
foundKeys = kid ?
this.jwkExtractor.extractJwk(keys, { kid, kty }) :
this.jwkExtractor.extractJwk(keys, { kty });
}
key = foundKeys[0];
}
catch (e) {
this.loggerService.logError(configuration, e);
return of(false);
}
const algorithm = this.getImportAlg(alg);
const signingInput = this.tokenHelperService.getSigningInputFromToken(idToken, true, configuration);
const rawSignature = this.tokenHelperService.getSignatureFromToken(idToken, true, configuration);
const agent = this.document.defaultView.navigator.userAgent.toLowerCase();
if (agent.indexOf('firefox') > -1 && key.kty === 'EC') {
key.alg = '';
}
return from(this.jwkWindowCryptoService.importVerificationKey(key, algorithm)).pipe(mergeMap((cryptoKey) => {
const signature = base64url.parse(rawSignature, { loose: true });
const verifyAlgorithm = this.getVerifyAlg(alg);
return from(this.jwkWindowCryptoService.verifyKey(verifyAlgorithm, cryptoKey, signature, signingInput));
}), tap((isValid) => {
if (!isValid) {
this.loggerService.logWarning(configuration, 'incorrect Signature, validation failed for id_token');
}
}));
}
getImportAlg(alg) {
switch (alg.charAt(0)) {
case 'R':
if (alg.includes('256')) {
return {
name: 'RSASSA-PKCS1-v1_5',
hash: 'SHA-256',
};
}
else if (alg.includes('384')) {
return {
name: 'RSASSA-PKCS1-v1_5',
hash: 'SHA-384',
};
}
else if (alg.includes('512')) {
return {
name: 'RSASSA-PKCS1-v1_5',
hash: 'SHA-512',
};
}
else {
return null;
}
case 'E':
if (alg.includes('256')) {
return {
name: 'ECDSA',
namedCurve: 'P-256',
};
}
else if (alg.includes('384')) {
return {
name: 'ECDSA',
namedCurve: 'P-384',
};
}
else {
return null;
}
default:
return null;
}
}
getVerifyAlg(alg) {
switch (alg.charAt(0)) {
case 'R':
return {
name: 'RSASSA-PKCS1-v1_5',
hash: 'SHA-256',
};
case 'E':
if (alg.includes('256')) {
return {
name: 'ECDSA',
hash: 'SHA-256',
};
}
else if (alg.includes('384')) {
return {
name: 'ECDSA',
hash: 'SHA-384',
};
}
else {
return null;
}
default:
return null;
}
}
alg2kty(alg) {
switch (alg.charAt(0)) {
case 'R':
return 'RSA';
case 'E':
return 'EC';
default:
throw new Error('Cannot infer kty from alg: ' + alg);
}
}
// Accepts ID Token without 'kid' claim in JOSE header if only one JWK supplied in 'jwks_url'
//// private validate_no_kid_in_header_only_one_allowed_in_jwtkeys(header_data: any, jwtkeys: any): boolean {
//// this.oidcSecurityCommon.logDebug('amount of jwtkeys.keys: ' + jwtkeys.keys.length);
//// if (!header_data.hasOwnProperty('kid')) {
//// // no kid defined in Jose header
//// if (jwtkeys.keys.length != 1) {
//// this.oidcSecurityCommon.logDebug('jwtkeys.keys.length != 1 and no kid in header');
//// return false;
//// }
//// }
//// return true;
//// }
// Access Token Validation
// access_token C1: Hash the octets of the ASCII representation of the access_token with the hash algorithm specified in JWA[JWA]
// for the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, the hash algorithm used is SHA-256.
// access_token C2: Take the left- most half of the hash and base64url- encode it.
// access_token C3: The value of at_hash in the ID Token MUST match the value produced in the previous step if at_hash
// is present in the ID Token.
validateIdTokenAtHash(accessToken, atHash, idTokenAlg, configuration) {
this.loggerService.logDebug(configuration, 'at_hash from the server:' + atHash);
// 'sha256' 'sha384' 'sha512'
let sha = 'SHA-256';
if (idTokenAlg.includes('384')) {
sha = 'SHA-384';
}
else if (idTokenAlg.includes('512')) {
sha = 'SHA-512';
}
return this.jwtWindowCryptoService.generateAtHash('' + accessToken, sha).pipe(mergeMap((hash) => {
this.loggerService.logDebug(configuration, 'at_hash client validation not decoded:' + hash);
if (hash === atHash) {
return of(true); // isValid;
}
else {
return this.jwtWindowCryptoService.generateAtHash('' + decodeURIComponent(accessToken), sha).pipe(map((newHash) => {
this.loggerService.logDebug(configuration, '-gen access--' + hash);
return newHash === atHash;
}));
}
}));
}
millisToMinutesAndSeconds(millis) {
const minutes = Math.floor(millis / 60000);
const seconds = ((millis % 60000) / 1000).toFixed(0);
return minutes + ':' + (+seconds < 10 ? '0' : '') + seconds;
}
calculateNowWithOffset(offsetSeconds) {
return new Date(new Date().toUTCString()).valueOf() + offsetSeconds * 1000;
}
}
TokenValidationService.refreshTokenNoncePlaceholder = '--RefreshToken--';
TokenValidationService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: TokenValidationService, deps: [{ token: i1.TokenHelperService }, { token: i2.LoggerService }, { token: i3.JwkExtractor }, { token: i4.JwkWindowCryptoService }, { token: i5.JwtWindowCryptoService }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
TokenValidationService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: TokenValidationService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: TokenValidationService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: i1.TokenHelperService }, { type: i2.LoggerService }, { type: i3.JwkExtractor }, { type: i4.JwkWindowCryptoService }, { type: i5.JwtWindowCryptoService }, { type: undefined, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }]; } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW4tdmFsaWRhdGlvbi5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvYW5ndWxhci1hdXRoLW9pZGMtY2xpZW50L3NyYy9saWIvdmFsaWRhdGlvbi90b2tlbi12YWxpZGF0aW9uLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzNDLE9BQU8sRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ25ELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDcEMsT0FBTyxFQUFFLElBQUksRUFBYyxFQUFFLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDNUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsR0FBRyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7Ozs7Ozs7QUFRcEQsMkRBQTJEO0FBRTNELFdBQVc7QUFDWCw0R0FBNEc7QUFDNUcsMERBQTBEO0FBQzFELEVBQUU7QUFDRix1SUFBdUk7QUFDdkksdUlBQXVJO0FBQ3ZJLG9FQUFvRTtBQUNwRSxFQUFFO0FBQ0YsbUhBQW1IO0FBQ25ILEVBQUU7QUFDRiw4SEFBOEg7QUFDOUgsRUFBRTtBQUNGLGtJQUFrSTtBQUNsSSwrRkFBK0Y7QUFDL0YsRUFBRTtBQUNGLHFJQUFxSTtBQUNySSxXQUFXO0FBQ1gsK0JBQStCO0FBQy9CLEVBQUU7QUFDRix5SUFBeUk7QUFDekksbUJBQW1CO0FBQ25CLEVBQUU7QUFDRiwrR0FBK0c7QUFDL0csd0hBQXdIO0FBQ3hILEVBQUU7QUFDRix5SEFBeUg7QUFDekgsMklBQTJJO0FBQzNJLHNCQUFzQjtBQUN0QixFQUFFO0FBQ0Ysc0hBQXNIO0FBQ3RILG9GQUFvRjtBQUNwRixFQUFFO0FBQ0YsaUlBQWlJO0FBQ2pJLHNGQUFzRjtBQUV0RiwwQkFBMEI7QUFDMUIsaUlBQWlJO0FB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QUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLEdBQUcsYUFBYSxHQUFHLElBQUksQ0FBQztJQUM3RSxDQUFDOztBQTdlTSxtREFBNEIsR0FBRyxrQkFBa0IsQ0FBQzttSEFEOUMsc0JBQXNCLHlMQVd2QixRQUFRO3VIQVhQLHNCQUFzQjsyRkFBdEIsc0JBQXNCO2tCQURsQyxVQUFVOzswQkFZTixNQUFNOzJCQUFDLFFBQVEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBET0NVTUVOVCB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbic7XHJcbmltcG9ydCB7IEluamVjdCwgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5pbXBvcnQgeyBiYXNlNjR1cmwgfSBmcm9tICdyZmM0NjQ4JztcclxuaW1wb3J0IHsgZnJvbSwgT2JzZXJ2YWJsZSwgb2YgfSBmcm9tICdyeGpzJztcclxuaW1wb3J0IHsgbWFwLCBtZXJnZU1hcCwgdGFwIH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xyXG5pbXBvcnQgeyBPcGVuSWRDb25maWd1cmF0aW9uIH0gZnJvbSAnLi4vY29uZmlnL29wZW5pZC1jb25maWd1cmF0aW9uJztcclxuaW1wb3J0IHsgTG9nZ2VyU2VydmljZSB9IGZyb20gJy4uL2xvZ2dpbmcvbG9nZ2VyLnNlcnZpY2UnO1xyXG5pbXBvcnQgeyBUb2tlbkhlbHBlclNlcnZpY2UgfSBmcm9tICcuLi91dGlscy90b2tlbkhlbHBlci90b2tlbi1oZWxwZXIuc2VydmljZSc7XHJcbmltcG9ydCB7IEp3dFdpbmRvd0NyeXB0b1NlcnZpY2UgfSBmcm9tICcuL2p3dC13aW5kb3ctY3J5cHRvLnNlcnZpY2UnO1xyXG5pbXBvcnQgeyBKd2tFeHRyYWN0b3IgfSBmcm9tICcuLi9leHRyYWN0b3JzL2p3ay5leHRyYWN0b3InO1xyXG5pbXBvcnQgeyBKd2tXaW5kb3dDcnlwdG9TZXJ2aWNlIH0gZnJvbSAnLi9qd2std2luZG93LWNyeXB0by5zZXJ2aWNlJztcclxuXHJcbi8vIGh0dHA6Ly9vcGVuaWQubmV0L3NwZWNzL29wZW5pZC1jb25uZWN0LWltcGxpY2l0LTFfMC5odG1sXHJcblxyXG4vLyBpZF90b2tlblxyXG4vLyBpZF90b2tlbiBDMTogVGhlIElzc3VlciBJZGVudGlmaWVyIGZvciB0aGUgT3BlbklEIFByb3ZpZGVyICh3aGljaCBpcyB0eXBpY2FsbHkgb2J0YWluZWQgZHVyaW5nIERpc2NvdmVyeSlcclxuLy8gTVVTVCBleGFjdGx5IG1hdGNoIHRoZSB2YWx1ZSBvZiB0aGUgaXNzIChpc3N1ZXIpIENsYWltLlxyXG4vL1xyXG4vLyBpZF90b2tlbiBDMjogVGhlIENsaWVudCBNVVNUIHZhbGlkYXRlIHRoYXQgdGhlIGF1ZCAoYXVkaWVuY2UpIENsYWltIGNvbnRhaW5zIGl0cyBjbGllbnRfaWQgdmFsdWUgcmVnaXN0ZXJlZCBhdCB0aGUgSXNzdWVyIGlkZW50aWZpZWRcclxuLy8gYnkgdGhlIGlzcyAoaXNzdWVyKSBDbGFpbSBhcyBhbiBhdWRpZW5jZS5UaGUgSUQgVG9rZW4gTVVTVCBiZSByZWplY3RlZCBpZiB0aGUgSUQgVG9rZW4gZG9lcyBub3QgbGlzdCB0aGUgQ2xpZW50IGFzIGEgdmFsaWQgYXVkaWVuY2UsXHJcbi8vIG9yIGlmIGl0IGNvbnRhaW5zIGFkZGl0aW9uYWwgYXVkaWVuY2VzIG5vdCB0cnVzdGVkIGJ5IHRoZSBDbGllbnQuXHJcbi8vXHJcbi8vIGlkX3Rva2VuIEMzOiBJZiB0aGUgSUQgVG9rZW4gY29udGFpbnMgbXVsdGlwbGUgYXVkaWVuY2VzLCB0aGUgQ2xpZW50IFNIT1VMRCB2ZXJpZnkgdGhhdCBhbiBhenAgQ2xhaW0gaXMgcHJlc2VudC5cclxuLy9cclxuLy8gaWRfdG9rZW4gQzQ6IElmIGFuIGF6cCAoYXV0aG9yaXplZCBwYXJ0eSkgQ2xhaW0gaXMgcHJlc2VudCwgdGhlIENsaWVudCBTSE9VTEQgdmVyaWZ5IHRoYXQgaXRzIGNsaWVudF9pZCBpcyB0aGUgQ2xhaW0gVmFsdWUuXHJcbi8vXHJcbi8vIGlkX3Rva2VuIEM1OiBUaGUgQ2xpZW50IE1VU1QgdmFsaWRhdGUgdGhlIHNpZ25hdHVyZSBvZiB0aGUgSUQgVG9rZW4gYWNjb3JkaW5nIHRvIEpXUyBbSldTXSB1c2luZyB0aGUgYWxnb3JpdGhtIHNwZWNpZmllZCBpbiB0aGVcclxuLy8gYWxnIEhlYWRlciBQYXJhbWV0ZXIgb2YgdGhlIEpPU0UgSGVhZGVyLlRoZSBDbGllbnQgTVVTVCB1c2UgdGhlIGtleXMgcHJvdmlkZWQgYnkgdGhlIElzc3Vlci5cclxuLy9cclxuLy8gaWRfdG9rZW4gQzY6IFRoZSBhbGcgdmFsdWUgU0hPVUxEIGJlIFJTMjU2LiBWYWxpZGF0aW9uIG9mIHRva2VucyB1c2luZyBvdGhlciBzaWduaW5nIGFsZ29yaXRobXMgaXMgZGVzY3JpYmVkIGluIHRoZSBPcGVuSUQgQ29ubmVjdFxyXG4vLyBDb3JlIDEuMFxyXG4vLyBbT3BlbklELkNvcmVdIHNwZWNpZmljYXRpb24uXHJcbi8vXHJcbi8vIGlkX3Rva2VuIEM3OiBUaGUgY3VycmVudCB0aW1lIE1VU1QgYmUgYmVmb3JlIHRoZSB0aW1lIHJlcHJlc2VudGVkIGJ5IHRoZSBleHAgQ2xhaW0gKHBvc3NpYmx5IGFsbG93aW5nIGZvciBzb21lIHNtYWxsIGxlZXdheSB0byBhY2NvdW50XHJcbi8vIGZvciBjbG9jayBza2V3KS5cclxuLy9cclxuLy8gaWRfdG9rZW4gQzg6IFRoZSBpYXQgQ2xhaW0gY2FuIGJlIHVzZWQgdG8gcmVqZWN0IHRva2VucyB0aGF0IHdlcmUgaXNzdWVkIHRvbyBmYXIgYXdheSBmcm9tIHRoZSBjdXJyZW50IHRpbWUsXHJcbi8vIGxpbWl0aW5nIHRoZSBhbW91bnQgb2YgdGltZSB0aGF0IG5vbmNlcyBuZWVkIHRvIGJlIHN0b3JlZCB0byBwcmV2ZW50IGF0dGFja3MuVGhlIGFjY2VwdGFibGUgcmFuZ2UgaXMgQ2xpZW50IHNwZWNpZmljLlxyXG4vL1xyXG4vLyBpZF90b2tlbiBDOTogVGhlIHZhbHVlIG9mIHRoZSBub25jZSBDbGFpbSBNVVNUIGJlIGNoZWNrZWQgdG8gdmVyaWZ5IHRoYXQgaXQgaXMgdGhlIHNhbWUgdmFsdWUgYXMgdGhlIG9uZSB0aGF0IHdhcyBzZW50XHJcbi8vIGluIHRoZSBBdXRoZW50aWNhdGlvbiBSZXF1ZXN0LlRoZSBDbGllbnQgU0hPVUxEIGNoZWNrIHRoZSBub25jZSB2YWx1ZSBmb3IgcmVwbGF5IGF0dGFja3MuVGhlIHByZWNpc2UgbWV0aG9kIGZvciBkZXRlY3RpbmcgcmVwbGF5IGF0dGFja3NcclxuLy8gaXMgQ2xpZW50IHNwZWNpZmljLlxyXG4vL1xyXG4vLyBpZF90b2tlbiBDMTA6IElmIHRoZSBhY3IgQ2xhaW0gd2FzIHJlcXVlc3RlZCwgdGhlIENsaWVudCBTSE9VTEQgY2hlY2sgdGhhdCB0aGUgYXNzZXJ0ZWQgQ2xhaW0gVmFsdWUgaXMgYXBwcm9wcmlhdGUuXHJcbi8vIFRoZSBtZWFuaW5nIGFuZCBwcm9jZXNzaW5nIG9mIGFjciBDbGFpbSBWYWx1ZXMgaXMgb3V0IG9mIHNjb3BlIGZvciB0aGlzIGRvY3VtZW50LlxyXG4vL1xyXG4vLyBpZF90b2tlbiBDMTE6IFdoZW4gYSBtYXhfYWdlIHJlcXVlc3QgaXMgbWFkZSwgdGhlIENsaWVudCBTSE9VTEQgY2hlY2sgdGhlIGF1dGhfdGltZSBDbGFpbSB2YWx1ZSBhbmQgcmVxdWVzdCByZS0gYXV0aGVudGljYXRpb25cclxuLy8gaWYgaXQgZGV0ZXJtaW5lcyB0b28gbXVjaCB0aW1lIGhhcyBlbGFwc2VkIHNpbmNlIHRoZSBsYXN0IEVuZC0gVXNlciBhdXRoZW50aWNhdGlvbi5cclxuXHJcbi8vIEFjY2VzcyBUb2tlbiBWYWxpZGF0aW9uXHJcbi8vIGFjY2Vzc190b2tlbiBDMTogSGFzaCB0aGUgb2N0ZXRzIG9mIHRoZSBBU0NJSSByZXByZXNlbnRhdGlvbiBvZiB0aGUgYWNjZXNzX3Rva2VuIHdpdGggdGhlIGhhc2ggYWxnb3JpdGhtIHNwZWNpZmllZCBpbiBKV0FbSldBXVxyXG4vLyBmb3IgdGhlIGFsZyBIZWFkZXIgUGFyYW1ldGVyIG9mIHRoZSBJRCBUb2tlbidzIEpPU0UgSGVhZGVyLiBGb3IgaW5zdGFuY2UsIGlmIHRoZSBhbGcgaXMgUlMyNTYsIHRoZSBoYXNoIGFsZ29yaXRobSB1c2VkIGlzIFNIQS0yNTYuXHJcbi8vIGFjY2Vzc190b2tlbiBDMjogVGFrZSB0aGUgbGVmdC0gbW9zdCBoYWxmIG9mIHRoZSBoYXNoIGFuZCBiYXNlNjR1cmwtIGVuY29kZSBpdC5cclxuLy8gYWNjZXNzX3Rva2VuIEMzOiBUaGUgdmFsdWUgb2YgYXRfaGFzaCBpbiB0aGUgSUQgVG9rZW4gTVVTVCBtYXRjaCB0aGUgdmFsdWUgcHJvZHVjZWQgaW4gdGhlIHByZXZpb3VzIHN0ZXAgaWYgYXRfaGFzaCBpcyBwcmVzZW50XHJcbi8vIGluIHRoZSBJRCBUb2tlbi5cclxuXHJcbkBJbmplY3RhYmxlKClcclxuZXhwb3J0IGNsYXNzIFRva2VuVmFsaWRhdGlvblNlcnZpY2Uge1xyXG4gIHN0YXRpYyByZWZyZXNoVG9rZW5Ob25jZVBsYWNlaG9sZGVyID0gJy0tUmVmcmVzaFRva2VuLS0nO1xyXG5cclxuICBrZXlBbGdvcml0aG1zOiBzdHJpbmdbXSA9IFsnSFMyNTYnLCAnSFMzODQnLCAnSFM1MTInLCAnUlMyNTYnLCAnUlMzODQnLCAnUlM1MTInLCAnRVMyNTYnLCAnRVMzODQnLCAnUFMyNTYnLCAnUFMzODQnLCAnUFM1MTInXTtcclxuXHJcbiAgY29uc3RydWN0b3IoXHJcbiAgICBwcml2YXRlIHJlYWRvbmx5IHRva2VuSGVscGVyU2VydmljZTogVG9rZW5IZWxwZXJTZXJ2aWNlLFxyXG4gICAgcHJpdmF0ZSByZWFkb25seSBsb2dnZXJTZXJ2aWNlOiBMb2dnZXJTZXJ2aWNlLFxyXG4gICAgcHJpdmF0ZSByZWFkb25seSBqd2tFeHRyYWN0b3I6IEp3a0V4dHJhY3RvcixcclxuICAgIHByaXZhdGUgcmVhZG9ubHkgandrV2luZG93Q3J5cHRvU2VydmljZTogSndrV2luZG93Q3J5cHRvU2VydmljZSxcclxuICAgIHByaXZhdGUgcmVhZG9ubHkgand0V2luZG93Q3J5cHRvU2VydmljZTogSnd0V2luZG93Q3J5cHRvU2VydmljZSxcclxuICAgIEBJbmplY3QoRE9DVU1FTlQpIHByaXZhdGUgcmVhZG9ubHkgZG9jdW1lbnQ6IGFueVxyXG4gICkge31cclxuXHJcbiAgLy8gaWRfdG9rZW4gQzc6IFRoZSBjdXJyZW50IHRpbWUgTVVTVCBiZSBiZWZvcmUgdGhlIHRpbWUgcmVwcmVzZW50ZWQgYnkgdGhlIGV4cCBDbGFpbVxyXG4gIC8vIChwb3NzaWJseSBhbGxvd2luZyBmb3Igc29tZSBzbWFsbCBsZWV3YXkgdG8gYWNjb3VudCBmb3IgY2xvY2sgc2tldykuXHJcbiAgaGFzSWRUb2tlbkV4cGlyZWQoXHJcbiAgICB0b2tlbjogc3RyaW5nLFxyXG4gICAgY29uZmlndXJhdGlvbjogT3BlbklkQ29uZmlndXJhdGlvbixcclxuICAgIG9mZnNldFNlY29uZHM/OiBudW1iZXIsXHJcbiAgICBkaXNhYmxlSWRUb2tlblZhbGlkYXRpb24/OiBib29sZWFuXHJcbiAgKTogYm9vbGVhbiB7XHJcbiAgICBjb25zdCBkZWNvZGVkID0gdGhpcy50b2tlbkhlbHBlclNlcnZpY2UuZ2V0UGF5bG9hZEZyb21Ub2tlbih0b2tlbiwgZmFsc2UsIGNvbmZpZ3VyYXRpb24pO1xyXG5cclxuICAgIHJldHVybiAhdGhpcy52YWxpZGF0ZUlkVG9rZW5FeHBOb3RFeHBpcmVkKGRlY29kZWQsIGNvbmZpZ3VyYXRpb24sIG9mZnNldFNlY29uZHMsIGRpc2FibGVJZFRva2VuVmFsaWRhdGlvbik7XHJcbiAgfVxyXG5cclxuICAvLyBpZF90b2tlbiBDNzogVGhlIGN1cnJlbnQgdGltZSBNVVNUIGJlIGJlZm9yZSB0aGUgdGltZSByZXByZXNlbnRlZCBieSB0aGUgZXhwIENsYWltXHJcbiAgLy8gKHBvc3NpYmx5IGFsbG93aW5nIGZvciBzb21lIHNtYWxsIGxlZXdheSB0byBhY2NvdW50IGZvciBjbG9jayBza2V3KS5cclxuICB2YWxpZGF0ZUlkVG9rZW5FeHBOb3RFeHBpcmVkKFxyXG4gICAgZGVjb2RlZElkVG9rZW46IHN0cmluZyxcclxuICAgIGNvbmZpZ3VyYXRpb246IE9wZW5JZENvbmZpZ3VyYXRpb24sXHJcbiAgICBvZmZzZXRTZWNvbmRzPzogbnVtYmVyLFxyXG4gICAgZGlzYWJsZUlkVG9rZW5WYWxpZGF0aW9uPzogYm9vbGVhblxyXG4gICk6IGJvb2xlYW4ge1xyXG4gICAgaWYgKGRpc2FibGVJZFRva2VuVmFsaWRhdGlvbikge1xyXG4gICAgICByZXR1cm4gdHJ1ZTtcclxuICAgIH1cclxuXHJcbiAgICBjb25zdCB0b2tlbkV4cGlyYXRpb25EYXRlID0gdGhpcy50b2tlbkhlbHBlclNlcnZpY2UuZ2V0VG9rZW5FeHBpcmF0aW9uRGF0ZShkZWNvZGVkSWRUb2tlbik7XHJcblxyXG4gICAgb2Zmc2V0U2Vjb25kcyA9IG9mZnNldFNlY29uZHMgfHwgMDtcclxuXHJcbiAgICBpZiAoIXRva2VuRXhwaXJhdGlvbkRhdGUpIHtcclxuICAgICAgcmV0dXJuIGZhbHNlO1xyXG4gICAgfVxyXG5cclxuICAgIGNvbnN0IHRva2VuRXhwaXJhdGlvblZhbHVlID0gdG9rZW5FeHBpcmF0aW9uRGF0ZS52YWx1ZU9mKCk7XHJcbiAgICBjb25zdCBub3dXaXRoT2Zmc2V0ID0gdGhpcy5jYWxjdWxhdGVOb3dXaXRoT2Zmc2V0KG9mZnNldFNlY29uZHMpO1xyXG4gICAgY29uc3QgdG9rZW5Ob3RFeHBpcmVkID0gdG9rZW5FeHBpcmF0aW9uVmFsdWUgPiBub3dXaXRoT2Zmc2V0O1xyXG5cclxuICAgIHRoaXMubG9nZ2VyU2VydmljZS5sb2dEZWJ1ZyhcclxuICAgICAgY29uZmlndXJhdGlvbixcclxuICAgICAgYEhhcyBpZFRva2VuIGV4cGlyZWQ6ICR7IXRva2VuTm90RXhwaXJlZH0gLS0+IGV4cGlyZXMgaW4gJHt0aGlzLm1pbGxpc1RvTWludXRlc0FuZFNlY29uZHMoXHJcbiAgICAgICAgdG9rZW5FeHBpcmF0aW9uVmFsdWUgLSBub3dXaXRoT2Zmc2V0XHJcbiAgICAgICl9ICwgJHtuZXcgRGF0ZSh0b2tlbkV4cGlyYXRpb25WYWx1ZSkudG9Mb2NhbGVUaW1lU3RyaW5nKCl9ID4gJHtuZXcgRGF0ZShub3dXaXRoT2Zmc2V0KS50b0xvY2FsZVRpbWVTdHJpbmcoKX1gXHJcbiAgICApO1xyXG5cclxuICAgIHJldHVybiB0b2tlbk5vdEV4cGlyZWQ7XHJcbiAgfVxyXG5cclxuICB2YWxpZGF0ZUFjY2Vzc1Rva2VuTm90RXhwaXJlZChhY2Nlc3NUb2tlbkV4cGlyZXNBdDogRGF0ZSwgY29uZmlndXJhdGlvbjogT3BlbklkQ29uZmlndXJhdGlvbiwgb2Zmc2V0U2Vjb25kcz86IG51bWJlcik6IGJvb2xlYW4ge1xyXG4gICAgLy8gdmFsdWUgaXMgb3B0aW9uYWwsIHNvIGlmIGl0IGRvZXMgbm90IGV4aXN0LCB0aGVuIGl0IGhhcyBub3QgZXhwaXJlZFxyXG4gICAgaWYgKCFhY2Nlc3NUb2tlbkV4cGlyZXNBdCkge1xyXG4gICAgICByZXR1cm4gdHJ1ZTtcclxuICAgIH1cclxuXHJcbiAgICBvZmZzZXRTZWNvbmRzID0gb2Zmc2V0U2Vjb25kcyB8fCAwO1xyXG4gICAgY29uc3QgYWNjZXNzVG9rZW5FeHBpcmF0aW9uVmFsdWUgPSBhY2Nlc3NUb2tlbkV4cGlyZXNBdC52YWx1ZU9mKCk7XHJcbiAgICBjb25zdCBub3dXaXRoT2Zmc2V0ID0gdGhpcy5jYWxjdWxhdGVOb3dXaXRoT2Zmc2V0KG9mZnNldFNlY29uZHMpO1xyXG4gICAgY29uc3QgdG9rZW5Ob3RFeHBpcmVkID0