UNPKG

angular-simple-oidc

Version:

Angular Library implementing Open Id Connect specification. Code Flow, Refresh Tokens, Session Management, Discovery Document.

350 lines 46 kB
import { Injectable } from '@angular/core'; import { TokenHelperService } from './token-helper.service'; import { TokenCryptoService } from './token-crypto.service'; import { InvalidStateError, AuthorizationCallbackError, AuthorizationCallbackMissingParameterError, IdentityTokenMalformedError, JWTKeysMissingError, SignatureAlgorithmNotSupportedError, JWTKeysInvalidError, InvalidSignatureError, InvalidNonceError, ClaimRequiredError, ClaimTypeInvalidError, DateClaimInvalidError, IssuedAtValidationFailedError, IssuerValidationFailedError, AudienceValidationFailedError, TokenExpiredError, AccessTokenHashValidationFailedError } from './token-validation-errors'; import { RequiredParemetersMissingError } from './errors'; /** * Implements Identity and Access tokens validations according to the * Open ID Connect specification. * https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation * Inspiration taken from https://github.com/damienbod/angular-auth-oidc-client */ export class TokenValidationService { constructor(tokenHelper, tokenCrypto) { this.tokenHelper = tokenHelper; this.tokenCrypto = tokenCrypto; } validateIdToken(thisClientId, idToken, decodedIdToken, nonce, discoveryDocument, jwtKeys, tokenValidationConfig) { // Apply all validation as defined on // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation this.validateIdTokenSignature(idToken, jwtKeys); this.validateIdTokenNonce(decodedIdToken, nonce); this.validateIdTokenRequiredFields(decodedIdToken); this.validateIdTokenIssuedAt(decodedIdToken, tokenValidationConfig); this.validateIdTokenIssuer(decodedIdToken, discoveryDocument.issuer); this.validateIdTokenAud(decodedIdToken, thisClientId); this.validateIdTokenExpiration(decodedIdToken); } /** * The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery) * MUST exactly match the value of the iss (issuer) Claim. */ validateIdTokenIssuer(idToken, discoveryDocumentIssuer) { if (idToken.iss !== discoveryDocumentIssuer) { throw new IssuerValidationFailedError(idToken.iss, discoveryDocumentIssuer, { idToken, discoveryDocumentIssuer }); } } /** * Access Token Validation * Hash the octets of the ASCII representation of the access_token with the hash algorithm specified in 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. * Take the left- most half of the hash and base64url- encode it. * 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 */ validateAccessToken(accessToken, idTokenAtHash) { // The at_hash is optional for the code flow if (!idTokenAtHash) { console.info(`No "at_hash" in Identity Token: Skipping access token validation.`); return; } const accessTokenHash = this.tokenCrypto.sha256b64First128Bits(accessToken); if (idTokenAtHash !== accessTokenHash) { throw new AccessTokenHashValidationFailedError({ accessToken, idTokenAtHash, calculatedHash: accessTokenHash }); } } /** * 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(idToken, thisClientId) { let aud = idToken.aud; if (!Array.isArray(aud)) { aud = [aud]; } const valid = aud.includes(thisClientId); if (!valid) { throw new AudienceValidationFailedError(aud.join(','), thisClientId, { idToken, thisClientId, aud }); } } /** * The Client MUST validate the signature of the ID Token according to JWS using the algorithm * specified in the alg Header Parameter of the JOSE Header. * The Client MUST use the keys provided by the Issuer. * The alg value SHOULD be RS256. * Validation of tokens using other signing algorithms is described in the * OpenID Connect Core 1.0 specification. */ validateIdTokenSignature(idToken, jwtKeys) { if (!jwtKeys || !jwtKeys.keys || !jwtKeys.keys.length) { throw new JWTKeysMissingError({ idToken, jwtKeys }); } const header = this.tokenHelper.getHeaderFromToken(idToken); if (header.alg !== 'RS256') { throw new SignatureAlgorithmNotSupportedError({ idToken, jwtKeys, header, }); } // Filter keys according to kty and use let keysToTry = jwtKeys.keys .filter(k => k.kty === 'RSA' && k.use === 'sig'); if (!keysToTry.length) { throw new JWTKeysInvalidError({ idToken, jwtKeys, header, keysToTry }); } // Token header may have a 'kid' claim (key id) // which determines which JWT key should be used for validation // If present, must be a case sensitive string. // https://tools.ietf.org/html/rfc7515#section-4.1.4 // https://tools.ietf.org/html/rfc7515#appendix-D let kid; if (header.kid && typeof header.kid === 'string' && header.kid.length) { if (keysToTry.some(k => k.kid === header.kid)) { // Threat the kid as a hint, prioritizing it's key // but still trying the other keys if the desired key fails. kid = header.kid; keysToTry = keysToTry.sort(k => k.kid === kid ? -1 : 1); } else { console.info(`Identity token's header contained 'kid' but no key with that kid was found on JWT Keys. Will still try to validate using other keys, if any. kid: ${header.kid}, ValidKeys kids: ${JSON.stringify(keysToTry.map(k => k.kid))}`); } } // Validate each key returning as soon as one suceeds for (const key of keysToTry) { if (this.tokenCrypto.verifySignature(key, idToken)) { if (kid && kid !== key.kid) { console.info(`Identity token's header contained 'kid' ${kid} but key signature was validated using key ${key.kid}`); } return; } } throw new InvalidSignatureError({ idToken, jwtKeys, header, keysToTry, kid }); } /** * The current time MUST be before the time represented by the exp Claim * (possibly allowing for some small leeway to account for clock skew) */ validateIdTokenExpiration(idToken, offsetSeconds) { this.validateTokenNumericClaim(idToken, 'exp'); const tokenExpirationDate = this.tokenHelper.convertTokenClaimToDate(idToken.exp); if (!tokenExpirationDate) { throw new DateClaimInvalidError('exp', { idToken, offsetSeconds, parsedDate: tokenExpirationDate }); } offsetSeconds = offsetSeconds || 0; const tokenExpirationMs = tokenExpirationDate.valueOf(); const maxDateMs = new Date().valueOf() - offsetSeconds * 1000; const tokenNotExpired = tokenExpirationMs > maxDateMs; if (!tokenNotExpired) { throw new TokenExpiredError(tokenExpirationDate, { idToken, offsetSeconds, tokenExpirationDate, tokenExpirationMs, maxDateMs, maxDate: new Date(maxDateMs) }); } } /** * 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. */ validateIdTokenIssuedAt(idToken, config = {}) { if (config.disableIdTokenIATValidation) { console.info('Issued At validation has been disabled by configuration'); return; } this.validateTokenNumericClaim(idToken, 'iat'); const idTokenIATDate = this.tokenHelper.convertTokenClaimToDate(idToken.iat); if (!idTokenIATDate) { throw new DateClaimInvalidError('iat', { idToken, config, parsedDate: idTokenIATDate }); } const maxOffsetInMs = (config.idTokenIATOffsetAllowed || 5) * 1000; const now = new Date().valueOf(); const valid = (now - idTokenIATDate.valueOf()) < maxOffsetInMs; if (!valid) { throw new IssuedAtValidationFailedError(maxOffsetInMs / 1000, { idToken, config, iatDiff: now - idTokenIATDate.valueOf(), maxOffsetInMs, }); } } /** * 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. */ validateIdTokenNonce(idToken, localNonce) { if (idToken.nonce !== localNonce) { throw new InvalidNonceError({ localNonce, idTokenNonce: idToken.nonce, idToken }); } } /** * 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. */ validateIdTokenRequiredFields(idToken) { const requiredClaims = ['iss', 'sub', 'aud', 'exp', 'iat']; for (const key of requiredClaims) { if (!idToken.hasOwnProperty(key)) { throw new ClaimRequiredError(key, { idToken, requiredClaims, missingClaim: key }); } } } /** * Validates that an expected token numeric field is a number on runtime. */ validateTokenNumericClaim(idToken, claim) { if (typeof idToken[claim] !== 'number') { if (!idToken[claim]) { throw new ClaimRequiredError(claim.toString(), { idToken, requiredClaim: claim }); } else { throw new ClaimTypeInvalidError(claim.toString(), 'number', typeof (idToken[claim]), { idToken, claim, claimType: typeof (idToken[claim]), claimExpectedType: 'number' }); } } } /** * Makes sure that the format of the identity token is correct. * It needs to be a non-empty string and contain three dots */ validateIdTokenFormat(idToken) { if (!idToken || !idToken.length) { throw new RequiredParemetersMissingError('idToken', null); } const expectedSliceAmount = 3; const slices = idToken.split('.'); if (slices.length !== expectedSliceAmount) { throw new IdentityTokenMalformedError({ idToken }); } } /** * Validates the local state against the * returned state from the IDP to make sure it matches */ validateAuthorizeCallbackState(localState, state) { if (state !== localState) { throw new InvalidStateError({ localState, returnedState: state, }); } } validateAuthorizeCallbackFormat(code, state, error, href) { if (typeof error === 'string') { throw new AuthorizationCallbackError(error, { url: href, error, }); } if (typeof code !== 'string') { throw new AuthorizationCallbackMissingParameterError('code', { url: href, }); } if (typeof state !== 'string') { throw new AuthorizationCallbackMissingParameterError('state', { url: href, }); } } } TokenValidationService.decorators = [ { type: Injectable } ]; TokenValidationService.ctorParameters = () => [ { type: TokenHelperService }, { type: TokenCryptoService } ]; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW4tdmFsaWRhdGlvbi5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvYW5ndWxhci1zaW1wbGUtb2lkYy9jb3JlL2xpYi90b2tlbi12YWxpZGF0aW9uLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUM1RCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUc1RCxPQUFPLEVBQ0gsaUJBQWlCLEVBQ2pCLDBCQUEwQixFQUMxQiwwQ0FBMEMsRUFDMUMsMkJBQTJCLEVBQzNCLG1CQUFtQixFQUNuQixtQ0FBbUMsRUFDbkMsbUJBQW1CLEVBQ25CLHFCQUFxQixFQUNyQixpQkFBaUIsRUFDakIsa0JBQWtCLEVBQ2xCLHFCQUFxQixFQUNyQixxQkFBcUIsRUFDckIsNkJBQTZCLEVBQzdCLDJCQUEyQixFQUMzQiw2QkFBNkIsRUFDN0IsaUJBQWlCLEVBQ2pCLG9DQUFvQyxFQUN2QyxNQUFNLDJCQUEyQixDQUFDO0FBQ25DLE9BQU8sRUFBRSw4QkFBOEIsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUUxRDs7Ozs7R0FLRztBQUVILE1BQU0sT0FBTyxzQkFBc0I7SUFDL0IsWUFDdUIsV0FBK0IsRUFDL0IsV0FBK0I7UUFEL0IsZ0JBQVcsR0FBWCxXQUFXLENBQW9CO1FBQy9CLGdCQUFXLEdBQVgsV0FBVyxDQUFvQjtJQUNsRCxDQUFDO0lBRUUsZUFBZSxDQUNsQixZQUFvQixFQUNwQixPQUFlLEVBQ2YsY0FBb0MsRUFDcEMsS0FBYSxFQUNiLGlCQUFvQyxFQUNwQyxPQUFnQixFQUNoQixxQkFBNkM7UUFFN0MscUNBQXFDO1FBQ3JDLDBFQUEwRTtRQUUxRSxJQUFJLENBQUMsd0JBQXdCLENBQUMsT0FBTyxFQUFF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LE1BQU0sRUFBRTtnQkFDekQsR0FBRyxFQUFFLElBQUk7YUFDWixDQUFDLENBQUM7U0FDTjtRQUVELElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxFQUFFO1lBQzNCLE1BQU0sSUFBSSwwQ0FBMEMsQ0FBQyxPQUFPLEVBQUU7Z0JBQzFELEdBQUcsRUFBRSxJQUFJO2FBQ1osQ0FBQyxDQUFDO1NBQ047SUFDTCxDQUFDOzs7WUF6WEosVUFBVTs7O1lBL0JGLGtCQUFrQjtZQUNsQixrQkFBa0IiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBUb2tlbkhlbHBlclNlcnZpY2UgfSBmcm9tICcuL3Rva2VuLWhlbHBlci5zZXJ2aWNlJztcbmltcG9ydCB7IFRva2VuQ3J5cHRvU2VydmljZSB9IGZyb20gJy4vdG9rZW4tY3J5cHRvLnNlcnZpY2UnO1xuaW1wb3J0IHsgRGVjb2RlZElkZW50aXR5VG9rZW4sIFRva2VuVmFsaWRhdGlvbkNvbmZpZyB9IGZyb20gJy4vbW9kZWxzJztcbmltcG9ydCB7IEpXVEtleXMsIERpc2NvdmVyeURvY3VtZW50IH0gZnJvbSAnLi9tb2RlbHMnO1xuaW1wb3J0IHtcbiAgICBJbnZhbGlkU3RhdGVFcnJvcixcbiAgICBBdXRob3JpemF0aW9uQ2FsbGJhY2tFcnJvcixcbiAgICBBdXRob3JpemF0aW9uQ2FsbGJhY2tNaXNzaW5nUGFyYW1ldGVyRXJyb3IsXG4gICAgSWRlbnRpdHlUb2tlbk1hbGZvcm1lZEVycm9yLFxuICAgIEpXVEtleXNNaXNzaW5nRXJyb3IsXG4gICAgU2lnbmF0dXJlQWxnb3JpdGhtTm90U3VwcG9ydGVkRXJyb3IsXG4gICAgSldUS2V5c0ludmFsaWRFcnJvcixcbiAgICBJbnZhbGlkU2lnbmF0dXJlRXJyb3IsXG4gICAgSW52YWxpZE5vbmNlRXJyb3IsXG4gICAgQ2xhaW1SZXF1aXJlZEVycm9yLFxuICAgIENsYWltVHlwZUludmFsaWRFcnJvcixcbiAgICBEYXRlQ2xhaW1JbnZhbGlkRXJyb3IsXG4gICAgSXNzdWVkQXRWYWxpZGF0aW9uRmFpbGVkRXJyb3IsXG4gICAgSXNzdWVyVmFsaWRhdGlvbkZhaWxlZEVycm9yLFxuICAgIEF1ZGllbmNlVmFsaWRhdGlvbkZhaWxlZEVycm9yLFxuICAgIFRva2VuRXhwaXJlZEVycm9yLFxuICAgIEFjY2Vzc1Rva2VuSGFzaFZhbGlkYXRpb25GYWlsZWRFcnJvclxufSBmcm9tICcuL3Rva2VuLXZhbGlkYXRpb24tZXJyb3JzJztcbmltcG9ydCB7IFJlcXVpcmVkUGFyZW1ldGVyc01pc3NpbmdFcnJvciB9IGZyb20gJy4vZXJyb3JzJztcblxuLyoqXG4gKiBJbXBsZW1lbnRzIElkZW50aXR5IGFuZCBBY2Nlc3MgdG9rZW5zIHZhbGlkYXRpb25zIGFjY29yZGluZyB0byB0aGVcbiAqIE9wZW4gSUQgQ29ubmVjdCBzcGVjaWZpY2F0aW9uLlxuICogaHR0cHM6Ly9vcGVuaWQubmV0L3NwZWNzL29wZW5pZC1jb25uZWN0LWNvcmUtMV8wLmh0bWwjSURUb2tlblZhbGlkYXRpb25cbiAqIEluc3BpcmF0aW9uIHRha2VuIGZyb20gaHR0cHM6Ly9naXRodWIuY29tL2RhbWllbmJvZC9hbmd1bGFyLWF1dGgtb2lkYy1jbGllbnRcbiAqL1xuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIFRva2VuVmFsaWRhdGlvblNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKFxuICAgICAgICBwcm90ZWN0ZWQgcmVhZG9ubHkgdG9rZW5IZWxwZXI6IFRva2VuSGVscGVyU2VydmljZSxcbiAgICAgICAgcHJvdGVjdGVkIHJlYWRvbmx5IHRva2VuQ3J5cHRvOiBUb2tlbkNyeXB0b1NlcnZpY2UsXG4gICAgKSB7IH1cblxuICAgIHB1YmxpYyB2YWxpZGF0ZUlkVG9rZW4oXG4gICAgICAgIHRoaXNDbGllbnRJZDogc3RyaW5nLFxuICAgICAgICBpZFRva2VuOiBzdHJpbmcsXG4gICAgICAgIGRlY29kZWRJZFRva2VuOiBEZWNvZGVkSWRlbnRpdHlUb2tlbixcbiAgICAgICAgbm9uY2U6IHN0cmluZyxcbiAgICAgICAgZGlzY292ZXJ5RG9jdW1lbnQ6IERpc2NvdmVyeURvY3VtZW50LFxuICAgICAgICBqd3RLZXlzOiBKV1RLZXlzLFxuICAgICAgICB0b2tlblZhbGlkYXRpb25Db25maWc/OiBUb2tlblZhbGlkYXRpb25Db25maWcpIHtcblxuICAgICAgICAvLyBBcHBseSBhbGwgdmFsaWRhdGlvbiBhcyBkZWZpbmVkIG9uXG4gICAgICAgIC8vIGh0dHBzOi8vb3BlbmlkLm5ldC9zcGVjcy9vcGVuaWQtY29ubmVjdC1jb3JlLTFfMC5odG1sI0lEVG9rZW5WYWxpZGF0aW9uXG5cbiAgICAgICAgdGhpcy52YWxpZGF0ZUlkVG9rZW5TaWduYXR1cmUoaWRUb2tlbiwgand0S2V5cyk7XG4gICAgICAgIHRoaXMudmFsaWRhdGVJZFRva2VuTm9uY2UoZGVjb2RlZElkVG9rZW4sIG5vbmNlKTtcbiAgICAgICAgdGhpcy52YWxpZGF0ZUlkVG9rZW5SZXF1aXJlZEZpZWxkcyhkZWNvZGVkSWRUb2tlbik7XG4gICAgICAgIHRoaXMudmFsaWRhdGVJZFRva2VuSXNzdWVkQXQoZGVjb2RlZElkVG9rZW4sIHRva2VuVmFsaWRhdGlvbkNvbmZpZyk7XG4gICAgICAgIHRoaXMudmFsaWRhdGVJZFRva2VuSXNzdWVyKGRlY29kZWRJZFRva2VuLCBkaXNjb3ZlcnlEb2N1bWVudC5pc3N1ZXIpO1xuICAgICAgICB0aGlzLnZhbGlkYXRlSWRUb2tlbkF1ZChkZWNvZGVkSWRUb2tlbiwgdGhpc0NsaWVudElkKTtcbiAgICAgICAgdGhpcy52YWxpZGF0ZUlkVG9rZW5FeHBpcmF0aW9uKGRlY29kZWRJZFRva2VuKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAqIFRoZSBJc3N1ZXIgSWRlbnRpZmllciBmb3IgdGhlIE9wZW5JRCBQcm92aWRlciAod2hpY2ggaXMgdHlwaWNhbGx5IG9idGFpbmVkIGR1cmluZyBEaXNjb3ZlcnkpXG4gICAgKiBNVVNUIGV4YWN0bHkgbWF0Y2ggdGhlIHZhbHVlIG9mIHRoZSBpc3MgKGlzc3VlcikgQ2xhaW0uXG4gICAgKi9cbiAgICBwdWJsaWMgdmFsaWRhdGVJZFRva2VuSXNzdWVyKGlkVG9rZW46IERlY29kZWRJZGVudGl0eVRva2VuLCBkaXNjb3ZlcnlEb2N1bWVudElzc3Vlcjogc3RyaW5nKSB7XG4gICAgICAgIGlmIChpZFRva2VuLmlzcyAhPT0gZGlzY292ZXJ5RG9jdW1lbnRJc3N1ZXIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBJc3N1ZXJWYWxpZGF0aW9uRmFpbGVkRXJyb3IoaWRUb2tlbi5pc3MsIGRpc2NvdmVyeURvY3VtZW50SXNzdWVyLCB7XG4gICAgICAgICAgICAgICAgaWRUb2tlbixcbiAgICAgICAgICAgICAgICBkaXNjb3ZlcnlEb2N1bWVudElzc3VlclxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBY2Nlc3MgVG9rZW4gVmFsaWRhdGlvblxuICAgICAqIEhhc2ggdGhlIG9jdGV0cyBvZiB0aGUgQVNDSUkgcmVwcmVzZW50YXRpb24gb2YgdGhlIGFjY2Vzc190b2tlbiB3aXRoIHRoZSBoYXNoIGFsZ29yaXRobSBzcGVjaWZpZWQgaW4gSldBXG4gICAgICogZm9yIHRoZSBhbGcgSGVhZGVyIFBhcmFtZXRlciBvZiB0aGUgSUQgVG9rZW4ncyBKT1NFIEhlYWRlci5cbiAgICAgKiBGb3IgaW5zdGFuY2UsIGlmIHRoZSBhbGcgaXMgUlMyNTYsIHRoZSBoYXNoIGFsZ29yaXRobSB1c2VkIGlzIFNIQS0yNTYuXG4gICAgICogVGFrZSB0aGUgbGVmdC0gbW9zdCBoYWxmIG9mIHRoZSBoYXNoIGFuZCBiYXNlNjR1cmwtIGVuY29kZSBpdC5cbiAgICAgKiBUaGUgdmFsdWUgb2YgYXRfaGFzaCBpbiB0aGUgSUQgVG9rZW4gTVVTVCBtYXRjaCB0aGUgdmFsdWUgcHJvZHVjZWQgaW4gdGhlIHByZXZpb3VzIHN0ZXBcbiAgICAgKiBpZiBhdF9oYXNoIGlzIHByZXNlbnQgaW4gdGhlIElEIFRva2VuXG4gICAgKi9cbiAgICBwdWJsaWMgdmFsaWRhdGVBY2Nlc3NUb2tlbihhY2Nlc3NUb2tlbjogc3RyaW5nLCBpZFRva2VuQXRIYXNoOiBzdHJpbmcpIHtcbiAgICAgICAgLy8gVGhlIGF0X2hhc2ggaXMgb3B0aW9uYWwgZm9yIHRoZSBjb2RlIGZsb3dcbiAgICAgICAgaWYgKCFpZFRva2VuQXRIYXNoKSB7XG4gICAgICAgICAgICBjb25zb2xlLmluZm8oYE5vIFwiYXRfaGFzaFwiIGluIElkZW50aXR5IFRva2VuOiBTa2lwcGluZyBhY2Nlc3MgdG9rZW4gdmFsaWRhdGlvbi5gKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IGFjY2Vzc1Rva2VuSGFzaCA9IHRoaXMudG9rZW5DcnlwdG8uc2hhMjU2YjY0Rmlyc3QxMjhCaXRzKGFjY2Vzc1Rva2VuKTtcbiAgICAgICAgaWYgKGlkVG9rZW5BdEhhc2ggIT09IGFjY2Vzc1Rva2VuSGFzaCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEFjY2Vzc1Rva2VuSGFzaFZhbGlkYXRpb25GYWlsZWRFcnJvcih7XG4gICAgICAgICAgICAgICAgYWNjZXNzVG9rZW4sXG4gICAgICAgICAgICAgICAgaWRUb2tlbkF0SGFzaCxcbiAgICAgICAgICAgICAgICBjYWxjdWxhdGVkSGFzaDogYWNjZXNzVG9rZW5IYXNoXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICogVGhlIENsaWVudCBNVVNUIHZhbGlkYXRlIHRoYXQgdGhlIGF1ZCAoYXVkaWVuY2UpIENsYWltIGNvbnRhaW5zXG4gICAgKiBpdHMgY2xpZW50X2lkIHZhbHVlIHJlZ2lzdGVyZWQgYXQgdGhlIElzc3VlciBpZGVudGlmaWVkIGJ5IHRoZSBpc3MgKGlzc3VlcikgQ2xhaW0gYXMgYW4gYXVkaWVuY2UuXG4gICAgKiBUaGUgSUQgVG9rZW4gTVVTVCBiZSByZWplY3RlZCBpZiB0aGUgSUQgVG9rZW4gZG9lcyBub3QgbGlzdCB0aGUgQ2xpZW50IGFzIGEgdmFsaWQgYXVkaWVuY2UsXG4gICAgKiBvciBpZiBpdCBjb250YWlucyBhZGRpdGlvbmFsIGF1ZGllbmNlcyBub3QgdHJ1c3RlZCBieSB0aGUgQ2xpZW50XG4gICAgKi9cbiAgICBwdWJsaWMgdmFsaWRhdGVJZFRva2VuQXVkKGlkVG9rZW46IERlY29kZWRJZGVudGl0eVRva2VuLCB0aGlzQ2xpZW50SWQ6IHN0cmluZykge1xuICAgICAgICBsZXQgYXVkID0gaWRUb2tlbi5hdWQ7XG4gICAgICAgIGlmICghQXJyYXkuaXNBcnJheShhdWQpKSB7XG4gICAgICAgICAgICBhdWQgPSBbYXVkXTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IHZhbGlkID0gYXVkLmluY2x1ZGVzKHRoaXNDbGllbnRJZCk7XG4gICAgICAgIGlmICghdmFsaWQpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBBdWRpZW5jZVZhbGlkYXRpb25GYWlsZWRFcnJvcihhdWQuam9pbignLCcpLCB0aGlzQ2xpZW50SWQsIHtcbiAgICAgICAgICAgICAgICBpZFRva2VuLFxuICAgICAgICAgICAgICAgIHRoaXNDbGllbnRJZCxcbiAgICAgICAgICAgICAgICBhdWRcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVGhlIENsaWVudCBNVVNUIHZhbGlkYXRlIHRoZSBzaWduYXR1cmUgb2YgdGhlIElEIFRva2VuIGFjY29yZGluZyB0byBKV1MgdXNpbmcgdGhlIGFsZ29yaXRobVxuICAgICAqIHNwZWNpZmllZCBpbiB0aGUgYWxnIEhlYWRlciBQYXJhbWV0ZXIgb2YgdGhlIEpPU0UgSGVhZGVyLlxuICAgICAqIFRoZSBDbGllbnQgTVVTVCB1c2UgdGhlIGtleXMgcHJvdmlkZWQgYnkgdGhlIElzc3Vlci5cbiAgICAgKiBUaGUgYWxnIHZhbHVlIFNIT1VMRCBiZSBSUzI1Ni5cbiAgICAgKiBWYWxpZGF0aW9uIG9mIHRva2VucyB1c2luZyBvdGhlciBzaWduaW5nIGFsZ29yaXRobXMgaXMgZGVzY3JpYmVkIGluIHRoZVxuICAgICAqIE9wZW5JRCBDb25uZWN0IENvcmUgMS4wIHNwZWNpZmljYXRpb24uXG4gICAgICovXG4gICAgcHVibGljIHZhbGlkYXRlSWRUb2tlblNpZ25hdHVyZShpZFRva2VuOiBzdHJpbmcsIGp3dEtleXM6IEpXVEtleXMpIHtcblxuICAgICAgICBpZiAoIWp3dEtleXMgfHwgIWp3dEtleXMua2V5cyB8fCAhand0S2V5cy5rZXlzLmxlbmd0aCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEpXVEtleXNNaXNzaW5nRXJyb3Ioe1xuICAgICAgICAgICAgICAgIGlkVG9rZW4sXG4gICAgICAgICAgICAgICAgand0S2V5c1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBoZWFkZXIgPSB0aGlzLnRva2VuSGVscGVyLmdldEhlYWRlckZyb21Ub2tlbihpZFRva2VuKTtcblxuICAgICAgICBpZiAoaGVhZGVyLmFsZyAhPT0gJ1JTMjU2Jykge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFNpZ25hdHVyZUFsZ29yaXRobU5vdFN1cHBvcnRlZEVycm9yKHtcbiAgICAgICAgICAgICAgICBpZFRva2VuLFxuICAgICAgICAgICAgICAgIGp3dEtleXMsXG4gICAgICAgICAgICAgICAgaGVhZGVyLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBGaWx0ZXIga2V5cyBhY2NvcmRpbmcgdG8ga3R5IGFuZCB1c2VcbiAgICAgICAgbGV0IGtleXNUb1RyeSA9IGp3dEtleXMua2V5c1xuICAgICAgICAgICAgLmZpbHRlcihrID0+IGsua3R5ID09PSAnUlNBJyAmJiBrLnVzZSA9PT0gJ3NpZycpO1xuXG4gICAgICAgIGlmICgha2V5c1RvVHJ5Lmxlbmd0aCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEpXVEtleXNJbnZhbGlkRXJyb3Ioe1xuICAgICAgICAgICAgICAgIGlkVG9rZW4sXG4gICAgICAgICAgICAgICAgand0S2V5cyxcbiAgICAgICAgICAgICAgICBoZWFkZXIsXG4gICAgICAgICAgICAgICAga2V5c1RvVHJ5XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFRva2VuIGhlYWRlciBtYXkgaGF2ZSBhICdraWQnIGNsYWltIChrZXkgaWQpXG4gICAgICAgIC8vIHdoaWNoIGRldGVybWluZXMgd2hpY2ggSldUIGtleSBzaG91bGQgYmUgdXNlZCBmb3IgdmFsaWRhdGlvblxuICAgICAgICAvLyBJZiBwcmVzZW50LCBtdXN0IGJlIGEgY2FzZSBzZW5zaXRpdmUgc3RyaW5nLlxuICAgICAgICAvLyBodHRwczovL3Rvb2xzLmlldGYub3JnL2h0bWwvcmZjNzUxNSNzZWN0aW9uLTQuMS40XG4gICAgICAgIC8vIGh0dHBzOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9yZmM3NTE1I2FwcGVuZGl4LURcblxuICAgICAgICBsZXQga2lkOiBzdHJpbmc7XG4gICAgICAgIGlmIChoZWFkZXIua2lkICYmIHR5cGVvZiBoZWFkZXIua2lkID09PSAnc3RyaW5nJyAmJiBoZWFkZXIua2lkLmxlbmd0aCkge1xuICAgICAgICAgICAgaWYgKGtleXNUb1RyeS5zb21lKGsgPT4gay5raWQgPT09IGhlYWRlci5raWQpKSB7XG4gICAgICAgICAgICAgICAgLy8gVGhyZWF0IHRoZSBraWQgYXMgYSBoaW50LCBwcmlvcml0aXppbmcgaXQncyBrZXlcbiAgICAgICAgICAgICAgICAvLyBidXQgc3RpbGwgdHJ5aW5nIHRoZSBvdGhlciBrZXlzIGlmIHRoZSBkZXNpcmVkIGtleSBmYWlscy5cbiAgICAgICAgICAgICAgICBraWQgPSBoZWFkZXIua2lkO1xuICAgICAgICAgICAgICAgIGtleXNUb1RyeSA9IGtleXNUb1RyeS5zb3J0KGsgPT4gay5raWQgPT09IGtpZCA/IC0xIDogMSk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUuaW5mbyhgSWRlbnRpdHkgdG9rZW4ncyBoZWFkZXIgY29udGFpbmVkICdraWQnXG4gICAgICAgICAgICAgICAgYnV0IG5vIGtleSB3aXRoIHRoYXQga2lkIHdhcyBmb3VuZCBvbiBKV1QgS2V5cy5cbiAgICAgICAgICAgICAgICBXaWxsIHN0aWxsIHRyeSB0byB2YWxpZGF0ZSB1c2luZyBvdGhlciBrZXlzLCBpZiBhbnkuXG4gICAgICAgICAgICAgICAga2lkOiAke2hlYWRlci5raWR9LFxuICAgICAgICAgICAgICAgIFZhbGlkS2V5cyBraWRzOiAke0pTT04uc3RyaW5naWZ5KGtleXNUb1RyeS5tYXAoayA9PiBrLmtpZCkpfWApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gVmFsaWRhdGUgZWFjaCBrZXkgcmV0dXJuaW5nIGFzIHNvb24gYXMgb25lIHN1Y2VlZHNcbiAgICAgICAgZm9yIChjb25zdCBrZXkgb2Yga2V5c1RvVHJ5KSB7XG4gICAgICAgICAgICBpZiAodGhpcy50b2tlbkNyeXB0by52ZXJpZnlTaWduYXR1cmUoa2V5LCBpZFRva2VuKSkge1xuICAgICAgICAgICAgICAgIGlmIChraWQgJiYga2lkICE9PSBrZXkua2lkKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUuaW5mbyhgSWRlbnRpdHkgdG9rZW4ncyBoZWFkZXIgY29udGFpbmVkICdraWQnICR7a2lkfVxuICAgICAgICAgICAgICAgICAgICBidXQga2V5IHNpZ25hdHVyZSB3YXMgdmFsaWRhdGVkIHVzaW5nIGtleSAke2tleS5raWR9YCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHRocm93IG5ldyBJbnZhbGlkU2lnbmF0dXJlRXJyb3Ioe1xuICAgICAgICAgICAgaWRUb2tlbixcbiAgICAgICAgICAgIGp3dEtleXMsXG4gICAgICAgICAgICBoZWFkZXIsXG4gICAgICAgICAgICBrZXlzVG9UcnksXG4gICAgICAgICAgICBraWRcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVGhlIGN1cnJlbnQgdGltZSBNVVNUIGJlIGJlZm9yZSB0aGUgdGltZSByZXByZXNlbnRlZCBieSB0aGUgZXhwIENsYWltXG4gICAgICogKHBvc3NpYmx5IGFsbG93aW5nIGZvciBzb21lIHNtYWxsIGxlZXdheSB0byBhY2NvdW50IGZvciBjbG9jayBza2V3KVxuICAgICAqL1xuICAgIHB1YmxpYyB2YWxpZGF0ZUlkVG9rZW5FeHBpcmF0aW9uKGlkVG9rZW46IERlY29kZWRJZGVudGl0eVRva2VuLCBvZmZzZXRTZWNvbmRzPzogbnVtYmVyKSB7XG5cbiAgICAgICAgdGhpcy52YWxpZGF0ZVRva2VuTnVtZXJpY0NsYWltKGlkVG9rZW4sICdleHAnKTtcblxuICAgICAgICBjb25zdCB0b2tlbkV4cGlyYXRpb25EYXRlID0gdGhpcy50b2tlbkhlbHBlci5jb252ZXJ0VG9rZW5DbGFpbVRvRGF0ZShpZFRva2VuLmV4cCk7XG4gICAgICAgIGlmICghdG9rZW5FeHBpcmF0aW9uRGF0ZSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IERhdGVDbGFpbUludmFsaWRFcnJvcignZXhwJywge1xuICAgICAgICAgICAgICAgIGlkVG9rZW4sXG4gICAgICAgICAgICAgICAgb2Zmc2V0U2Vjb25kcyxcbiAgICAgICAgICAgICAgICBwYXJzZWREYXRlOiB0b2tlbkV4cGlyYXRpb25EYXRlXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuXG4gICAgICAgIG9mZnNldFNlY29uZHMgPSBvZmZzZXRTZWNvbmRzIHx8IDA7XG4gICAgICAgIGNvbnN0IHRva2VuRXhwaXJhdGlvbk1zID0gdG9rZW5FeHBpcmF0aW9uRGF0ZS52YWx1ZU9mKCk7XG4gICAgICAgIGNvbnN0IG1heERhdGVNcyA9IG5ldyBEYXRlKCkudmFsdWVPZigpIC0gb2Zmc2V0U2Vjb25kcyAqIDEwMDA7XG4gICAgICAgIGNvbnN0IHRva2VuTm90RXhwaXJlZCA9IHRva2VuRXhwaXJhdGlvbk1zID4gbWF4RGF0ZU1zO1xuICAgICAgICBpZiAoIXRva2VuTm90RXhwaXJlZCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFRva2VuRXhwaXJlZEVycm9yKHRva2VuRXhwaXJhdGlvbkRhdGUsIHtcbiAgICAgICAgICAgICAgICBpZFRva2VuLFxuICAgICAgICAgICAgICAgIG9mZnNldFNlY29uZHMsXG4gICAgICAgICAgICAgICAgdG9rZW5FeHBpcmF0aW9uRGF0ZSxcbiAgICAgICAgICAgICAgICB0b2tlbkV4cGlyYXRpb25NcyxcbiAgICAgICAgICAgICAgICBtYXhEYXRlTXMsXG4gICAgICAgICAgICAgICAgbWF4RGF0ZTogbmV3IERhdGUobWF4RGF0ZU1zKVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAqIFRoZSBpYXQgQ2xhaW0gY2FuIGJlIHVzZWQgdG8gcmVqZWN0IHRva2VucyB0aGF0IHdlcmUgaXNzdWVkIHRvbyBmYXIgYXdheSBmcm9tIHRoZSBjdXJyZW50IHRpbWUsXG4gICAgKiBsaW1pdGluZyB0aGUgYW1vdW50IG9mIHRpbWUgdGhhdCBub25jZXMgbmVlZCB0byBiZSBzdG9yZWQgdG8gcHJldmVudCBhdHRhY2tzLlxuICAgICogVGhlIGFjY2VwdGFibGUgcmFuZ2UgaXMgQ2xpZW50IHNwZWNpZmljLlxuICAgICovXG4gICAgcHVibGljIHZhbGlkYXRlSWRUb2tlbklzc3VlZEF0KGlkVG9rZW46IERlY29kZWRJZGVudGl0eVRva2VuLCBjb25maWc6IFRva2VuVmFsaWRhdGlvbkNvbmZpZyA9IHt9KSB7XG5cbiAgICAgICAgaWYgKGNvbmZpZy5kaXNhYmxlSWRUb2tlbklBVFZhbGlkYXRpb24pIHtcbiAgICAgICAgICAgIGNvbnNvbGUuaW5mbygnSXNzdWVkIEF0IHZhbGlkYXRpb24gaGFzIGJlZW4gZGlzYWJsZWQgYnkgY29uZmlndXJhdGlvbicpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy52YWxpZGF0ZVRva2VuTnVtZXJpY0NsYWltKGlkVG9rZW4sICdpYXQnKTtcblxuICAgICAgICBjb25zdCBpZFRva2VuSUFURGF0ZSA9IHRoaXMudG9rZW5IZWxwZXIuY29udmVydFRva2VuQ2xhaW1Ub0RhdGUoaWRUb2tlbi5pYXQpO1xuICAgICAgICBpZiAoIWlkVG9rZW5JQVREYXRlKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRGF0ZUNsYWltSW52YWxpZEVycm9yKCdpYXQnLCB7XG4gICAgICAgICAgICAgICAgaWRUb2tlbixcbiAgICAgICAgICAgICAgICBjb25maWcsXG4gICAgICAgICAgICAgICAgcGFyc2VkRGF0ZTogaWRUb2tlbklBVERhdGVcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgbWF4T2Zmc2V0SW5NcyA9IChjb25maWcuaWRUb2tlbklBVE9mZnNldEFsbG93ZWQgfHwgNSkgKiAxMDAwO1xuICAgICAgICBjb25zdCBub3cgPSBuZXcgRGF0ZSgpLnZhbHVlT2YoKTtcbiAgICAgICAgY29uc3QgdmFsaWQgPSAobm93IC0gaWRUb2tlbklBVERhdGUudmFsdWVPZigpKSA8IG1heE9mZnNldEluTXM7XG4gICAgICAgIGlmICghdmFsaWQpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBJc3N1ZWRBdFZhbGlkYXRpb25GYWlsZWRFcnJvcihtYXhPZmZzZXRJbk1zIC8gMTAwMCwge1xuICAgICAgICAgICAgICAgIGlkVG9rZW4sXG4gICAgICAgICAgICAgICAgY29uZmlnLFxuICAgICAgICAgICAgICAgIGlhdERpZmY6IG5vdyAtIGlkVG9rZW5JQVREYXRlLnZhbHVlT2YoKSxcbiAgICAgICAgICAgICAgICBtYXhPZmZzZXRJbk1zLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAqIFRoZSB2YWx1ZSBvZiB0aGUgbm9uY2UgQ2xhaW0gTVVTVCBiZSBjaGVja2VkIHRvIHZlcmlmeSB0aGF0IGl0IGlzIHRoZSBzYW1lIHZhbHVlIGFzIHRoZSBvbmVcbiAgICAqIHRoYXQgd2FzIHNlbnQgaW4gdGhlIEF1dGhlbnRpY2F0aW9uIFJlcXVlc3QuXG4gICAgKiBUaGUgQ2xpZW50IFNIT1VMRCBjaGVjayB0aGUgbm9uY2UgdmFsdWUgZm9yIHJlcGxheSBhdHRhY2tzLlxuICAgICogVGhlIHByZWNpc2UgbWV0aG9kIGZvciBkZXRlY3RpbmcgcmVwbGF5IGF0dGFja3MgaXMgQ2xpZW50IHNwZWNpZmljLlxuICAgICovXG4gICAgcHVibGljIHZhbGlkYXRlSWRUb2tlbk5vbmNlKGlkVG9rZW46IERlY29kZWRJZGVudGl0eVRva2VuLCBsb2NhbE5vbmNlOiBzdHJpbmcpIHtcbiAgICAgICAgaWYgKGlkVG9rZW4ubm9uY2UgIT09IGxvY2FsTm9uY2UpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBJbnZhbGlkTm9uY2VFcnJvcih7XG4gICAgICAgICAgICAgICAgbG9jYWxOb25jZSxcbiAgICAgICAgICAgICAgICBpZFRva2VuTm9uY2U6IGlkVG9rZW4ubm9uY2UsXG4gICAgICAgICAgICAgICAgaWRUb2tlblxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAqIGlzc1xuICAgICogUkVRVUlSRUQuIElzc3VlciBJZGVudGlmaWVyIGZvciB0aGUgSXNzdWVyIG9mIHRoZSByZXNwb25zZS5cbiAgICAqIFRoZSBpc3MgdmFsdWUgaXMgYSBjYXNlLXNlbnNpdGl2ZSBVUkwgdXNpbmcgdGhlIGh0dHBzIHNjaGVtZSB0aGF0IGNvbnRhaW5zIHNjaGVtZSwgaG9zdCxcbiAgICAqIGFuZCBvcHRpb25hbGx5LCBwb3J0IG51bWJlciBhbmQgcGF0aCBjb21wb25lbnRzIGFuZCBubyBxdWVyeSBvciBmcmFnbWVudCBjb21wb25lbnRzLlxuICAgICpcbiAgICAqIHN1YlxuICAgICogUkVRVUlSRUQuIFN1YmplY3QgSWRlbnRpZmllci5Mb2NhbGx5IHVuaXF1ZSBhbmQgbmV2ZXIgcmVhc3NpZ25lZCBpZGVudGlmaWVyIHdpdGhpbiB0aGUgSXNzdWVyIGZvciB0aGUgRW5kLSBVc2VyLFxuICAgICogd2hpY2ggaXMgaW50ZW5kZWQgdG8gYmUgY29uc3VtZWQgYnkgdGhlIENsaWVudCwgZS5nLiwgMjQ0MDAzMjAgb3IgQUl0T2F3bXd0V3djVDBrNTFCYXlld052dXRySlVxc3ZsNnFzN0E0LlxuICAgICogSXQgTVVTVCBOT1QgZXhjZWVkIDI1NSBBU0NJSSBjaGFyYWN0ZXJzIGluIGxlbmd0aC5UaGUgc3ViIHZhbHVlIGlzIGEgY2FzZS1zZW5zaXRpdmUgc3RyaW5nLlxuICAgICpcbiAgICAqIGF1ZFxuICAgICogUkVRVUlSRUQuIEF1ZGllbmNlKHMpIHRoYXQgdGhpcyBJRCBUb2tlbiBpcyBpbnRlbmRlZCBmb3IuXG4gICAgKiBJdCBNVVNUIGNvbnRhaW4gdGhlIE9BdXRoIDIuMCBjbGllbnRfaWQgb2YgdGhlIFJlbHlpbmcgUGFydHkgYXMgYW4gYXVkaWVuY2UgdmFsdWUuXG4gICAgKiBJdCBNQVkgYWxzbyBjb250YWluIGlkZW50aWZpZXJzIGZvciBvdGhlciBhdWRpZW5jZXMuSW4gdGhlIGdlbmVyYWwgY2FzZSwgdGhlIGF1ZCB2YWx1ZSBpcyBhbiBhcnJheSBvZiBjYXNlLXNlbnNpdGl2ZSBzdHJpbmdzLlxuICAgICogSW4gdGhlIGNvbW1vbiBzcGVjaWFsIGNhc2Ugd2hlbiB0aGVyZSBpcyBvbmUgYXVkaWVuY2UsIHRoZSBhdWQgdmFsdWUgTUFZIGJlIGEgc2luZ2xlIGNhc2Utc2Vuc2l0aXZlIHN0cmluZy5cbiAgICAqXG4gICAgKiBleHBcbiAgICAqIFJFUVVJUkVELiBFeHBpcmF0aW9uIHRpbWUgb24gb3IgYWZ0ZXIgd2hpY2ggdGhlIElEIFRva2VuIE1VU1QgTk9UIGJlIGFjY2VwdGVkIGZvciBwcm9jZXNzaW5nLlxuICAgICogVGhlIHByb2Nlc3Npbmcgb2YgdGhpcyBwYXJhbWV0ZXIgcmVxdWlyZXMgdGhhdCB0aGUgY3VycmVudCBkYXRlLyB0aW1lIE1VU1QgYmUgYmVmb3JlXG4gICAgKiB0aGUgZXhwaXJhdGlvbiBkYXRlLyB0aW1lIGxpc3RlZCBpbiB0aGUgdmFsdWUuXG4gICAgKiBJbXBsZW1lbnRlcnMgTUFZIHByb3ZpZGUgZm9yIHNvbWUgc21hbGwgbGVld2F5LCB1c3VhbGx5IG5vIG1vcmUgdGhhbiBhIGZldyBtaW51dGVzLCB0byBhY2NvdW50IGZvciBjbG9jayBza2V3LlxuICAgICogSXRzIHZhbHVlIGlzIGEgSlNPTiBbUkZDNzE1OV0gbnVtYmVyIHJlcHJlc2VudGluZyB0aGUgbnVtYmVyIG9mIHNlY29uZHMgZnJvbSAxOTcwLSAwMSAtIDAxVDAwOiAwMDowMFpcbiAgICAqIGFzIG1lYXN1cmVkIGluIFVUQyB1bnRpbCB0aGUgZGF0ZS8gdGltZS5cbiAgICAqIFNlZSBSRkMgMzMzOSBbUkZDMzMzOV0gZm9yIGRldGFpbHMgcmVnYXJkaW5nIGRhdGUvIHRpbWVzIGluIGdlbmVyYWwgYW5kIFVUQyBpbiBwYXJ0aWN1bGFyLlxuICAgICpcbiAgICAqIGlhdFxuICAgICogUkVRVUlSRUQuIFRpbWUgYXQgd2hpY2ggdGhlIEpXVCB3YXMgaXNzdWVkLiBJdHMgdmFsdWUgaXMgYSBKU09OIG51bWJlciByZXByZXNlbnRpbmcgdGhlIG51bWJlciBvZiBzZWNvbmRzXG4gICAgKiBmcm9tIDE5NzAtIDAxIC0gMDFUMDA6IDAwOjAwWiBhcyBtZWFzdXJlZFxuICAgICogaW4gVVRDIHVudGlsIHRoZSBkYXRlLyB0aW1lLlxuICAgICovXG4gICAgcHVibGljIHZhbGlkYXRlSWRUb2tlblJlcXVpcmVkRmllbGRzKGlkVG9rZW46IERlY29kZWRJZGVudGl0eVRva2VuKSB7XG4gICAgICAgIGNvbnN0IHJlcXVpcmVkQ2xhaW1zID0gWydpc3MnLCAnc3ViJywgJ2F1ZCcsICdleHAnLCAnaWF0J107XG4gICAgICAgIGZvciAoY29uc3Qga2V5IG9mIHJlcXVpcmVkQ2xhaW1zKSB7XG4gICAgICAgICAgICBpZiAoIWlkVG9rZW4uaGFzT3duUHJvcGVydHkoa2V5KSkge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBDbGFpbVJlcXVpcmVkRXJyb3Ioa2V5LCB7XG4gICAgICAgICAgICAgICAgICAgIGlkVG9rZW4sXG4gICAgICAgICAgICAgICAgICAgIHJlcXVpcmVkQ2xhaW1zLFxuICAgICAgICAgICAgICAgICAgICBtaXNzaW5nQ2xhaW06IGtleVxuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVmFsaWRhdGVzIHRoYXQgYW4gZXhwZWN0ZWQgdG9rZW4gbnVtZXJpYyBmaWVsZCBpcyBhIG51bWJlciBvbiBydW50aW1lLlxuICAgICAqL1xuICAgIHB1YmxpYyB2YWxpZGF0ZVRva2VuTnVtZXJpY0NsYWltPFQgZXh0ZW5kcyBEZWNvZGVkSWRlbnRpdHlUb2tlbj4oaWRUb2tlbjogVCwgY2xhaW06IGtleW9mIFQpIHtcbiAgICAgICAgaWYgKHR5cGVvZiBpZFRva2VuW2NsYWltXSAhPT0gJ251bWJlcicpIHtcbiAgICAgICAgICAgIGlmICghaWRUb2tlbltjbGFpbV0pIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgQ2xhaW1SZXF1aXJlZEVycm9yKGNsYWltLnRvU3RyaW5nKCksIHtcbiAgICAgICAgICAgICAgICAgICAgaWRUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgcmVxdWlyZWRDbGFpbTogY2xhaW1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IENsYWltVHlwZUludmFsaWRFcnJvcihjbGFpbS50b1N0cmluZygpLCAnbnVtYmVyJywgdHlwZW9mIChpZFRva2VuW2NsYWltXSksIHtcbiAgICAgICAgICAgICAgICAgICAgaWRUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgY2xhaW0sXG4gICAgICAgICAgICAgICAgICAgIGNsYWltVHlwZTogdHlwZW9mIChpZFRva2VuW2NsYWltXSksXG4gICAgICAgICAgICAgICAgICAgIGNsYWltRXhwZWN0ZWRUeXBlOiAnbnVtYmVyJ1xuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogTWFrZXMgc3VyZSB0aGF0IHRoZSBmb3JtYXQgb2YgdGhlIGlkZW50aXR5IHRva2VuIGlzIGNvcnJlY3QuXG4gICAgICogSXQgbmVlZHMgdG8gYmUgYSBub24tZW1wdHkgc3RyaW5nIGFuZCBjb250YWluIHRocmVlIGRvdHNcbiAgICAgKi9cbiAgICBwdWJsaWMgdmFsaWRhdGVJZFRva2VuRm9ybWF0KGlkVG9rZW46IHN0cmluZykge1xuICAgICAgICBpZiAoIWlkVG9rZW4gfHwgIWlkVG9rZW4ubGVuZ3RoKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgUmVxdWlyZWRQYXJlbWV0ZXJzTWlzc2luZ0Vycm9yKCdpZFRva2VuJywgbnVsbCk7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBleHBlY3RlZFNsaWNlQW1vdW50ID0gMztcbiAgICAgICAgY29uc3Qgc2xpY2VzID0gaWRUb2tlbi5zcGxpdCgnLicpO1xuXG4gICAgICAgIGlmIChzbGljZXMubGVuZ3RoICE9PSBleHBlY3RlZFNsaWNlQW1vdW50KSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgSWRlbnRpdHlUb2tlbk1hbGZvcm1lZEVycm9yKHtcbiAgICAgICAgICAgICAgICBpZFRva2VuXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFZhbGlkYXRlcyB0aGUgbG9jYWwgc3RhdGUgYWdhaW5zdCB0aGVcbiAgICAgKiByZXR1cm5lZCBzdGF0ZSBmcm9tIHRoZSBJRFAgdG8gbWFrZSBzdXJlIGl0IG1hdGNoZXNcbiAgICAgKi9cbiAgICBwdWJsaWMgdmFsaWRhdGVBdXRob3JpemVDYWxsYmFja1N0YXRlKGxvY2FsU3RhdGU6IHN0cmluZywgc3RhdGU6IHN0cmluZykge1xuICAgICAgICBpZiAoc3RhdGUgIT09IGxvY2FsU3RhdGUpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBJbnZhbGlkU3RhdGVFcnJvcih7XG4gICAgICAgICAgICAgICAgbG9jYWxTdGF0ZSxcbiAgICAgICAgICAgICAgICByZXR1cm5lZFN0YXRlOiBzdGF0ZSxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHVibGljIHZhbGlkYXRlQXV0aG9yaXplQ2FsbGJhY2tGb3JtYXQoXG4gICAgICAgIGNvZGU6IHN0cmluZyxcbiAgICAgICAgc3RhdGU6IHN0cmluZyxcbiAgICAgICAgZXJyb3I6IHN0cmluZyxcbiAgICAgICAgaHJlZjogc3RyaW5nKSB7XG5cbiAgICAgICAgaWYgKHR5cGVvZiBlcnJvciA9PT0gJ3N0cmluZycpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBBdXRob3JpemF0aW9uQ2FsbGJhY2tFcnJvcihlcnJvciwge1xuICAgICAgICAgICAgICAgIHVybDogaHJlZixcbiAgICAgICAgICAgICAgICBlcnJvcixcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHR5cGVvZiBjb2RlICE9PSAnc3RyaW5nJykge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEF1dGhvcml6YXRpb25DYWxsYmFja01pc3NpbmdQYXJhbWV0ZXJFcnJvcignY29kZScsIHtcbiAgICAgICAgICAgICAgICB1cmw6IGhyZWYsXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh0eXBlb2Ygc3RhdGUgIT09ICdzdHJpbmcnKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgQXV0aG9yaXphdGlvbkNhbGxiYWNrTWlzc2luZ1BhcmFtZXRlckVycm9yKCdzdGF0ZScsIHtcbiAgICAgICAgICAgICAgICB1cmw6IGhyZWYsXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH1cbn1cbiJdfQ==