UNPKG

angular-auth-oidc-client

Version:
438 lines 72.7 kB
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