@scloud/cdk-patterns
Version:
Serverless CDK patterns for common infrastructure needs
458 lines • 68 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cognito = void 0;
const aws_cdk_lib_1 = require("aws-cdk-lib");
const aws_certificatemanager_1 = require("aws-cdk-lib/aws-certificatemanager");
const cognito = __importStar(require("aws-cdk-lib/aws-cognito"));
const aws_cognito_1 = require("aws-cdk-lib/aws-cognito");
const aws_route53_1 = require("aws-cdk-lib/aws-route53");
const aws_route53_targets_1 = require("aws-cdk-lib/aws-route53-targets");
const constructs_1 = require("constructs");
/**
* Authentication setup with Cognito.
*
* This construct offers a couple convenience static methods for typical use cases:
* - Cognito.withSSO()
* - Cognito.withSocialLogins()
*
* To customise this construct, you'll need to call these methods in the following oprder:
* - new Cognito()
* - addGoogleIdp() (optional)
* - addFacebookIdp() (optional)
* - addSamlIdp() (optional, can be called more than once)
* - createUserPoolClient()
* - addCustomDomain() / addDomainPrefix()
*
* Once set up, you can call signInUrl() to get a URL for the hosted UI sign-in page.
*
* NB: IF you want to use a custom domain, there's an unexpected error where the CDK deployment
* will fail unless there's an A record at the zone apex (at the time of writing) so you need to
* add a record at the apex before you attempt to create a custom domain.
*
* @returns Information about the created UserPool
*/
class Cognito extends constructs_1.Construct {
constructor(scope, id, props) {
super(scope, `${id}Cognito`);
this.samlIdps = [];
/** All callback URLs, including any alternative URL will be visible in this properly */
this.callbackUrls = [];
// Store the ID so we can it in methods:
this.id = id;
// Cognito user pool
this.userPool = new aws_cognito_1.UserPool(scope, `${id}UserPool`, {
userPoolName: id,
selfSignUpEnabled: true,
accountRecovery: aws_cognito_1.AccountRecovery.EMAIL_ONLY,
signInAliases: { username: false, email: true },
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
...props,
});
}
addGoogleIdp(googleClientId, googleClientSecret) {
if (this.googleIdp)
throw new Error(`Google identity provider has already been created for ${this.id}. You'll need to call addGoogleIdp before creating the client.`);
if (this.userPoolClient)
throw new Error(`User pool client has already been created for ${this.id}. You'll need to call addGoogleIdp before creating the client.`);
// Google identity provider
this.googleIdp = new aws_cognito_1.UserPoolIdentityProviderGoogle(this, `${this.id}GoogleIDP`, {
userPool: this.userPool,
clientId: googleClientId,
clientSecret: googleClientSecret,
scopes: ['profile', 'email', 'openid'],
attributeMapping: {
email: cognito.ProviderAttribute.GOOGLE_EMAIL,
givenName: cognito.ProviderAttribute.GOOGLE_GIVEN_NAME,
familyName: cognito.ProviderAttribute.GOOGLE_FAMILY_NAME,
fullname: cognito.ProviderAttribute.GOOGLE_NAME,
profilePicture: cognito.ProviderAttribute.GOOGLE_PICTURE,
},
// scopes: [
// 'https://www.googleapis.com/auth/userinfo.email',
// 'https://www.googleapis.com/auth/userinfo.profile'],
});
return this.googleIdp;
}
addFacebookIdp(facebookAppId, facebookAppSecret) {
if (this.googleIdp)
throw new Error(`Facebook identity provider has already been created for ${this.id}. You'll need to call addGoogleIdp before creating the client.`);
if (this.userPoolClient)
throw new Error(`User pool client has already been created for ${this.id}. You'll need to call addFacebookIdp before creating the client.`);
this.facebookIdp = new aws_cognito_1.UserPoolIdentityProviderFacebook(this, `${this.id}FacebookIDP`, {
userPool: this.userPool,
clientId: facebookAppId,
clientSecret: facebookAppSecret,
scopes: ['public_profile', 'email'],
attributeMapping: {
email: cognito.ProviderAttribute.FACEBOOK_EMAIL,
givenName: cognito.ProviderAttribute.FACEBOOK_FIRST_NAME,
familyName: cognito.ProviderAttribute.FACEBOOK_LAST_NAME,
fullname: cognito.ProviderAttribute.FACEBOOK_NAME,
},
});
return this.facebookIdp;
}
/**
* Add a SAML sso identity provider.
*
* You can call this method more than once to add multiple SAML providers.
*
* @param SamlProviderName Name in the Cognito hosted UI under "Sign in with your corporate ID"
* @param FederationMetadataUrl SAML XML URL (e.g. Azure)
* @param FederationMetadataXml SAML metadata XML (e.g. Google Workspace)
*/
addSamlIdp(SamlProviderName, FederationMetadataUrl, FederationMetadataXml) {
// https://docs.aws.amazon.com/cdk/api/latest/docs/aws-cdk-lib_aws-cognito.CfnUserPoolIdentityProvider.html
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolidentityprovider.html
if (this.userPoolClient)
throw new Error(`User pool client has already been created for ${this.id}. You'll need to call addSamlIdp before creating the client.`);
const providerDetails = {};
if (FederationMetadataUrl) {
providerDetails.MetadataURL = FederationMetadataUrl;
}
if (FederationMetadataXml) {
providerDetails.MetadataFile = FederationMetadataXml;
}
const samlIdp = new aws_cognito_1.CfnUserPoolIdentityProvider(this, `${this.id}SamlIDP${SamlProviderName}`, {
userPoolId: this.userPool.userPoolId,
providerName: SamlProviderName || this.id,
providerType: 'SAML',
attributeMapping: {
// https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html
given_name: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname',
family_name: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname',
email: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
},
providerDetails,
});
this.samlIdps.push(samlIdp);
return samlIdp;
}
/**
* Create a Cognito User Pool Client.
*
* If you want to add identity providers such as Google, Facebook or saml sso you'll need to call addGoogleIdp(), addFacebookIdp() and/or addSamlIdp() first.
*
* @param enableEmail Whether to enable email as a sign-up/sign-in method.
* @param callbackUrl Allowed callback URL on your app to receive an authentication code (?code=...)
* @param alternativeCallbackUrls Zero or more additonal authorized callback URL, for example if you wneed to allow localhost in a development environment.
* @returns cognito.UserPoolClient
*/
createUserPoolClient(props) {
if (this.userPoolClient)
throw new Error(`User pool client has already been created for ${this.id}`);
const identityProviders = [];
if (props.enableEmail)
identityProviders.push(aws_cognito_1.UserPoolClientIdentityProvider.COGNITO);
if (this.googleIdp)
identityProviders.push(aws_cognito_1.UserPoolClientIdentityProvider.GOOGLE);
if (this.facebookIdp)
identityProviders.push(aws_cognito_1.UserPoolClientIdentityProvider.FACEBOOK);
this.samlIdps.forEach((saml) => {
identityProviders.push(aws_cognito_1.UserPoolClientIdentityProvider.custom(saml.providerName));
});
this.callbackUrl = props.callbackUrl;
this.callbackUrls = [props.callbackUrl];
this.callbackUrls.push(...props.alternativeCallbackUrls);
this.logoutUrl = props.logoutUrl;
const userPoolClient = new aws_cognito_1.UserPoolClient(this, `${this.id}UserPoolClient`, {
userPool: this.userPool,
userPoolClientName: this.id,
generateSecret: props.generateSecret === false ? false : true,
preventUserExistenceErrors: true,
supportedIdentityProviders: identityProviders,
oAuth: {
callbackUrls: this.callbackUrls,
logoutUrls: this.logoutUrl ? [this.logoutUrl] : undefined,
flows: {
authorizationCodeGrant: true,
},
scopes: [
cognito.OAuthScope.EMAIL,
cognito.OAuthScope.OPENID,
cognito.OAuthScope.PROFILE,
],
},
});
// These dependencies seemed to be needed at the time of writing:
if (this.googleIdp)
userPoolClient.node.addDependency(this.googleIdp);
if (this.facebookIdp)
userPoolClient.node.addDependency(this.facebookIdp);
if (this.samlIdps) {
this.samlIdps.forEach((samlIdp) => userPoolClient.node.addDependency(samlIdp));
}
this.userPoolClient = userPoolClient;
return this.userPoolClient;
}
/**
* Add a custom domain name to the Cognito User Pool.
*
* NOTE: there's a Cognito quirk where it seems that an A record at the zone apex must exist
* (even for a delegated subdomain) otherwise you can't create a custom user pool domain.
* @see https://stackoverflow.com/questions/79833464/aws-cognito-custom-domain-fails-to-create-invalid-request-provided-awscogn/79833465#79833465
*
* AWS recommends auth.<domain> for custom domains, so this is the default if you don't pass a value for domainName.
*
* NB at the time of writing there's a hard limit of 4 custom Cognito domains per AWS account.
*
* You can add either a custom domain or a domain prefix, but not both.
*
* A domain prefix must be globally unique across all AWS accounts.
*
* @param zone The HostedZone in which to create an alias record for the user pool.
* @param domainName Leave this out to use the recommended `auth.<domain>`, or pass a fully qualified domain name.
*/
addCustomDomain(zone, domainName) {
if (this.domain)
throw new Error(`A domain has already been created for ${this.id}`);
// NB at the time of writing there's a hard limit of 4 custom Cognito domains.
const authDomainName = domainName || `auth.${zone.zoneName}`;
// Ideally find a way to look up an existing A record at the apex and conditionally create a placeholder
// see https://stackoverflow.com/questions/79833464/aws-cognito-custom-domain-fails-to-create-invalid-request-provided-awscogn/79833465#79833465
// const apexCheck = ARecord.fromARecordAttributes(this, 'ExistingApex', { targetDNS: authDomainName, zone });
// // 2️⃣ Create placeholder if no apex exists
// try {
// const exists = apexCheck.node.defaultChild; // will throw if not found
// if (exists) console.log(`Zone apex record found for ${zone.zoneName}. We can create a Cognito custom domain at ${authDomainName}`);
// } catch {
// console.log(`Zone apex record not found for ${zone.zoneName}. We need to create a placeholder record to enable a Cognito custom domain to be created successfully at ${authDomainName}`);
// new ARecord(this, 'ZoneApexPlaceholder', {
// zone,
// recordName: authDomainName,
// target: RecordTarget.fromIpAddresses('192.0.2.1'), // safe placeholder IP in TEST-NET-1
// comment: 'Placeholder zone apex record to enable a Cognito custom domain to be created',
// });
// }
// AWS-managed certificate (auto-renews)
const certificate = new aws_certificatemanager_1.DnsValidatedCertificate(this, `${this.id}UserPoolCertificate`, {
domainName: authDomainName,
hostedZone: zone,
region: 'us-east-1', // Cloudfront requires this
});
// NB a custom domain can only be set up after an A record has been created at the zone apex
this.domain = new cognito.UserPoolDomain(this, `${this.id}UserPoolDomain`, {
userPool: this.userPool,
customDomain: {
domainName: authDomainName,
certificate,
},
});
// https://stackoverflow.com/a/62075314/723506
new aws_route53_1.ARecord(this, `${this.id}CognitoCustomDomainARecord`, {
zone,
recordName: authDomainName,
target: aws_route53_1.RecordTarget.fromAlias(new aws_route53_targets_1.UserPoolDomainTarget(this.domain)),
});
}
/**
* Set a domain prefix for the URL of the Cognito User Pool.
*
* This will set the user pool URL to https://<domainPrefix>.auth.<region>.amazoncognito.com
*
* You don't have to set a custom domain prefix. If you don't, the prefix will be generated by AWS.
*
* If can set a custom domain prefix, or a custom domain, but not both.
*
* @param domainPrefix Leave this out to use the recommended `auth.<domain>`, or pass a fully qualified domain name.
*/
addDomainPrefix(domainPrefix) {
if (this.domain)
throw new Error(`A domain has already been created for ${this.id}`);
this.domain = new cognito.UserPoolDomain(this, `${this.id}UserPoolDomain`, {
userPool: this.userPool,
cognitoDomain: {
domainPrefix,
},
});
}
/**
* Constructs a URL for the hosted UI sign-in page.
*
* You'll need to call either addCustomDomain() or addDomainPrefix() first.
*
* @param callbackUrl Optional: defaults to the this.callbackUrl.
*/
signInUrl(callbackUrl) {
var _a;
if (!this.domain)
throw new Error(`You must call addCustomDomain() or addDomainPrefix() before calling signInUrl() for ${this.id}`);
if (!this.userPoolClient)
throw new Error(`You must call createUserPoolClient() before calling signInUrl() for ${this.id}`);
return (_a = this.domain) === null || _a === void 0 ? void 0 : _a.signInUrl(this.userPoolClient, { redirectUri: callbackUrl || this.callbackUrl });
}
/**
* @deprecated Use withSSOMetadataUrl() or withSSOMetadataXml() instead.
*
* Creates a Cognito instance configured for SAML sso (e.g. Azure or Google Workspace).
*
* You'll need to pass either a federationMetadataUrl or a federationMetadataXml.
*
* You'll want to pass either a domain prefix (creates https://<prefix>.auth.<region>.amazoncognito.com) or a
* zone (and optionally domainName) if you don't pass a domainName the user pool url will be https://auth.<zoneName>
*
* NB at the time of writing AWS has a hard limit of 4 custom Cognito domains so if you're running multiple user pools
* in a single AWS account you may need to use domain prefixes.
*/
static withSSO(scope, id, callbackUrl, samlProviderName, federationMetadataUrl, federationMetadataXml, zone, domainName, domainPrefix, logoutUrl, generateSecret, ...alternativeCallbackUrls) {
const sso = new Cognito(scope, id);
sso.addSamlIdp(samlProviderName, federationMetadataUrl, federationMetadataXml);
sso.createUserPoolClient({ callbackUrl, enableEmail: false, logoutUrl, alternativeCallbackUrls, generateSecret });
if (domainPrefix)
sso.addDomainPrefix(domainPrefix);
else if (zone)
sso.addCustomDomain(zone, domainName || `auth.${zone.zoneName}`);
return sso;
}
/**
* Creates a Cognito instance configured for email login.
*
* You'll want to pass either a domain prefix (creates https://<prefix>.auth.<region>.amazoncognito.com) or a
* zone (and optionally domainName) if you don't pass a domainName the user pool url will be https://auth.<zoneName>
*
* NB at the time of writing AWS has a hard limit of 4 custom Cognito domains so if you're running multiple user pools
* in a single AWS account you may need to use domain prefixes.
*/
static withEmailLogin(scope, id, callbackUrl, zone, domainName, domainPrefix, logoutUrl, generateSecret, ...alternativeCallbackUrls) {
const email = new Cognito(scope, id);
email.createUserPoolClient({ callbackUrl, enableEmail: true, logoutUrl, alternativeCallbackUrls, generateSecret });
if (domainPrefix)
email.addDomainPrefix(domainPrefix);
else if (zone)
email.addCustomDomain(zone, domainName || `auth.${zone.zoneName}`);
return email;
}
/**
* Creates a Cognito instance configured for Social logins (Google and Facebook) and optionally email.
*
* You'll want to pass either a domain prefix (creates https://<prefix>.auth.<region>.amazoncognito.com) or a
* zone (and optionally domainName) if you don't pass a domainName the user pool url will be https://auth.<zoneName>
*
* NB at the time of writing AWS has a hard limit of 4 custom Cognito domains so if you're running multiple user pools
* in a single AWS account you may need to use domain prefixes.
*/
static withSocialLogins(scope, id, callbackUrl, googleClientId, googleClientSecret, facebookAppId, facebookAppSecret, enableEmailLogin, zone, domainName, domainPrefix, logoutUrl, generateSecret, ...alternativeCallbackUrls) {
const social = new Cognito(scope, id);
if (googleClientId && googleClientSecret)
social.addGoogleIdp(googleClientId, googleClientSecret);
if (facebookAppId && facebookAppSecret)
social.addFacebookIdp(facebookAppId, facebookAppSecret);
social.createUserPoolClient({ callbackUrl, enableEmail: enableEmailLogin, logoutUrl, alternativeCallbackUrls, generateSecret });
if (domainPrefix)
social.addDomainPrefix(domainPrefix);
else if (zone)
social.addCustomDomain(zone, domainName || `auth.${zone.zoneName}`);
return social;
}
/**
* Creates a Cognito instance configured for SAML sso where you have a metadata URL (e.g. Azure).
*
* You'll need to pass a federationMetadataUrl (e.g. provided by Azure).
*
* If configuring an 'Enerprise Application' in Azure, the "Identifier (Entity ID)" will be:
*
* urn:amazon:cognito:sp:<user pool id> (e.g. <region>_XyZaBcD1E)
*
* The "Reply URL (Assertion Consumer Service URL)" will be:
*
* https://<Your user pool domain>/saml2/idpresponse
*
* With an Amazon Cognito domain prefix:
* https://<yourDomainPrefix>.auth.<region>.amazoncognito.com/saml2/idpresponse
* With a custom domain:
* https://<Your custom domain>/saml2/idpresponse
*
* see: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-saml-idp.html
*
* You'll want to pass either a domain prefix (creates https://<prefix>.auth.<region>.amazoncognito.com) or a
* zone (and optionally domainName) if you don't pass a domainName the user pool url will be https://auth.<zoneName>
*
* NB at the time of writing AWS has a hard limit of 4 custom Cognito domains so if you're running multiple user pools
* in a single AWS account you may need to use domain prefixes.
*/
static withSSOMetadataUrl(scope, id, callbackUrl, samlProviderName, federationMetadataUrl, zone, domainName, domainPrefix, logoutUrl, generateSecret, ...alternativeCallbackUrls) {
const sso = new Cognito(scope, id);
sso.addSamlIdp(samlProviderName, federationMetadataUrl, undefined);
sso.createUserPoolClient({ callbackUrl, enableEmail: false, logoutUrl, alternativeCallbackUrls, generateSecret });
if (domainPrefix)
sso.addDomainPrefix(domainPrefix);
else if (zone)
sso.addCustomDomain(zone, domainName || `auth.${zone.zoneName}`);
return sso;
}
/**
* Creates a Cognito instance configured for SAML sso where you have a metadata XML file (e.g. Google Workspace).
*
* You'll need to pass federationMetadataXml data as a string (e.g. downloaded from your Google Workspace).
*
* If configuring an 'App' in Google Workspace (under "Apps/Web and mobile apps" in the admin console) the "ACS URL" will be:
*
* https://<Your user pool domain>/saml2/idpresponse
*
* With an Amazon Cognito domain prefix:
* https://<yourDomainPrefix>.auth.<region>.amazoncognito.com/saml2/idpresponse
* With a custom domain:
* https://<Your custom domain>/saml2/idpresponse
*
* The "Enitiy ID" will be:
*
* urn:amazon:cognito:sp:<user pool id> (e.g. <region>_XyZaBcD1E)
*
* see: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-saml-idp.html
*
* In Google Workspace you'll need to set "Use access" to e.g. "ON for everyone" (oe select an organisational unit).
* NB it's usually best to test Google Worspace sso in incognito mode as if you're already signed in you may get a 403 error.
* This is possibly because your local cached credentials haven't yet updated with access to the App.
*
* You'll want to pass either a domain prefix (creates https://<prefix>.auth.<region>.amazoncognito.com) or a
* zone (and optionally domainName) if you don't pass a domainName the user pool url will be https://auth.<zoneName>
*
* NB at the time of writing AWS has a hard limit of 4 custom Cognito domains so if you're running multiple user pools
* in a single AWS account you may need to use domain prefixes.
*/
static withSSOMetadataXml(scope, id, callbackUrl, samlProviderName, federationMetadataXml, zone, domainName, domainPrefix, logoutUrl, generateSecret, ...alternativeCallbackUrls) {
const sso = new Cognito(scope, id);
sso.addSamlIdp(samlProviderName, undefined, federationMetadataXml);
sso.createUserPoolClient({ callbackUrl, enableEmail: false, logoutUrl, alternativeCallbackUrls, generateSecret });
if (domainPrefix)
sso.addDomainPrefix(domainPrefix);
else if (zone)
sso.addCustomDomain(zone, domainName || `auth.${zone.zoneName}`);
return sso;
}
}
exports.Cognito = Cognito;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29nbml0by5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9Db2duaXRvLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLDZDQUE0QztBQUM1QywrRUFBNkU7QUFDN0UsaUVBQW1EO0FBQ25ELHlEQU1pQztBQUNqQyx5REFFaUM7QUFDakMseUVBQXVFO0FBQ3ZFLDJDQUF1QztBQUV2Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXNCRztBQUNILE1BQWEsT0FBUSxTQUFRLHNCQUFTO0lBd0JwQyxZQUNFLEtBQWdCLEVBQ2hCLEVBQVUsRUFDVixLQUFzQztRQUV0QyxLQUFLLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxTQUFTLENBQUMsQ0FBQztRQWhCL0IsYUFBUSxHQUEwQyxFQUFFLENBQUM7UUFLckQsd0ZBQXdGO1FBQ3hGLGlCQUFZLEdBQWEsRUFBRSxDQUFDO1FBWTFCLHdDQUF3QztRQUN4QyxJQUFJLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQztRQUViLG9CQUFvQjtRQUNwQixJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksc0JBQVEsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLFVBQVUsRUFBRTtZQUNuRCxZQUFZLEVBQUUsRUFBRTtZQUNoQixpQkFBaUIsRUFBRSxJQUFJO1lBQ3ZCLGVBQWUsRUFBRSw2QkFBZSxDQUFDLFVBQVU7WUFDM0MsYUFBYSxFQUFFLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFO1lBQy9DLGFBQWEsRUFBRSwyQkFBYSxDQUFDLE9BQU87WUFDcEMsR0FBRyxLQUFLO1NBQ1QsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELFlBQVksQ0FDVixjQUFzQixFQUN0QixrQkFBMEI7UUFFMUIsSUFBSSxJQUFJLENBQUMsU0FBUztZQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMseURBQXlELElBQUksQ0FBQyxFQUFFLGdF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Z0IsRUFDaEIsRUFBVSxFQUNWLFdBQW1CLEVBQ25CLGdCQUF3QixFQUN4QixxQkFBMEMsRUFDMUMsSUFBa0IsRUFDbEIsVUFBbUIsRUFDbkIsWUFBcUIsRUFDckIsU0FBa0IsRUFDbEIsY0FBd0IsRUFDeEIsR0FBRyx1QkFBaUM7UUFFcEMsTUFBTSxHQUFHLEdBQUcsSUFBSSxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQ25DLEdBQUcsQ0FBQyxVQUFVLENBQUMsZ0JBQWdCLEVBQUUsU0FBUyxFQUFFLHFCQUFxQixDQUFDLENBQUM7UUFDbkUsR0FBRyxDQUFDLG9CQUFvQixDQUFDLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLHVCQUF1QixFQUFFLGNBQWMsRUFBRSxDQUFDLENBQUM7UUFDbEgsSUFBSSxZQUFZO1lBQUUsR0FBRyxDQUFDLGVBQWUsQ0FBQyxZQUFZLENBQUMsQ0FBQzthQUMvQyxJQUFJLElBQUk7WUFBRSxHQUFHLENBQUMsZUFBZSxDQUFDLElBQUksRUFBRSxVQUFVLElBQUksUUFBUSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUNoRixPQUFPLEdBQUcsQ0FBQztJQUNiLENBQUM7Q0FDRjtBQWxmRCwwQkFrZkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBSZW1vdmFsUG9saWN5IH0gZnJvbSAnYXdzLWNkay1saWInO1xuaW1wb3J0IHsgRG5zVmFsaWRhdGVkQ2VydGlmaWNhdGUgfSBmcm9tICdhd3MtY2RrLWxpYi9hd3MtY2VydGlmaWNhdGVtYW5hZ2VyJztcbmltcG9ydCAqIGFzIGNvZ25pdG8gZnJvbSAnYXdzLWNkay1saWIvYXdzLWNvZ25pdG8nO1xuaW1wb3J0IHtcbiAgQWNjb3VudFJlY292ZXJ5LCBDZm5Vc2VyUG9vbElkZW50aXR5UHJvdmlkZXIsIFVzZXJQb29sLCBVc2VyUG9vbENsaWVudCxcbiAgVXNlclBvb2xDbGllbnRJZGVudGl0eVByb3ZpZGVyLFxuICBVc2VyUG9vbERvbWFpbixcbiAgVXNlclBvb2xJZGVudGl0eVByb3ZpZGVyRmFjZWJvb2ssXG4gIFVzZXJQb29sSWRlbnRpdHlQcm92aWRlckdvb2dsZSxcbn0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWNvZ25pdG8nO1xuaW1wb3J0IHtcbiAgQVJlY29yZCwgSUhvc3RlZFpvbmUsIFJlY29yZFRhcmdldCxcbn0gZnJvbSAnYXdzLWNkay1saWIvYXdzLXJvdXRlNTMnO1xuaW1wb3J0IHsgVXNlclBvb2xEb21haW5UYXJnZXQgfSBmcm9tICdhd3MtY2RrLWxpYi9hd3Mtcm91dGU1My10YXJnZXRzJztcbmltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gJ2NvbnN0cnVjdHMnO1xuXG4vKipcbiAqIEF1dGhlbnRpY2F0aW9uIHNldHVwIHdpdGggQ29nbml0by5cbiAqXG4gKiBUaGlzIGNvbnN0cnVjdCBvZmZlcnMgYSBjb3VwbGUgY29udmVuaWVuY2Ugc3RhdGljIG1ldGhvZHMgZm9yIHR5cGljYWwgdXNlIGNhc2VzOlxuICogIC0gQ29nbml0by53aXRoU1NPKClcbiAqICAtIENvZ25pdG8ud2l0aFNvY2lhbExvZ2lucygpXG4gKlxuICogVG8gY3VzdG9taXNlIHRoaXMgY29uc3RydWN0LCB5b3UnbGwgbmVlZCB0byBjYWxsIHRoZXNlIG1ldGhvZHMgaW4gdGhlIGZvbGxvd2luZyBvcHJkZXI6XG4gKiAtIG5ldyBDb2duaXRvKClcbiAqIC0gYWRkR29vZ2xlSWRwKCkgKG9wdGlvbmFsKVxuICogLSBhZGRGYWNlYm9va0lkcCgpIChvcHRpb25hbClcbiAqIC0gYWRkU2FtbElkcCgpIChvcHRpb25hbCwgY2FuIGJlIGNhbGxlZCBtb3JlIHRoYW4gb25jZSlcbiAqIC0gY3JlYXRlVXNlclBvb2xDbGllbnQoKVxuICogLSBhZGRDdXN0b21Eb21haW4oKSAvIGFkZERvbWFpblByZWZpeCgpXG4gKlxuICogT25jZSBzZXQgdXAsIHlvdSBjYW4gY2FsbCBzaWduSW5VcmwoKSB0byBnZXQgYSBVUkwgZm9yIHRoZSBob3N0ZWQgVUkgc2lnbi1pbiBwYWdlLlxuICpcbiAqIE5COiBJRiB5b3Ugd2FudCB0byB1c2UgYSBjdXN0b20gZG9tYWluLCB0aGVyZSdzIGFuIHVuZXhwZWN0ZWQgZXJyb3Igd2hlcmUgdGhlIENESyBkZXBsb3ltZW50XG4gKiB3aWxsIGZhaWwgdW5sZXNzIHRoZXJlJ3MgYW4gQSByZWNvcmQgYXQgdGhlIHpvbmUgYXBleCAoYXQgdGhlIHRpbWUgb2Ygd3JpdGluZykgc28geW91IG5lZWQgdG9cbiAqIGFkZCBhIHJlY29yZCBhdCB0aGUgYXBleCBiZWZvcmUgeW91IGF0dGVtcHQgdG8gY3JlYXRlIGEgY3VzdG9tIGRvbWFpbi5cbiAqXG4gKiBAcmV0dXJucyBJbmZvcm1hdGlvbiBhYm91dCB0aGUgY3JlYXRlZCBVc2VyUG9vbFxuICovXG5leHBvcnQgY2xhc3MgQ29nbml0byBleHRlbmRzIENvbnN0cnVjdCB7XG4gIGlkOiBzdHJpbmc7XG5cbiAgdXNlclBvb2w6IFVzZXJQb29sO1xuXG4gIGRvbWFpbjogVXNlclBvb2xEb21haW4gfCB1bmRlZmluZWQ7XG5cbiAgdXNlclBvb2xDbGllbnQ6IFVzZXJQb29sQ2xpZW50O1xuXG4gIGdvb2dsZUlkcDogY29nbml0by5Vc2VyUG9vbElkZW50aXR5UHJvdmlkZXJHb29nbGUgfCB1bmRlZmluZWQ7XG5cbiAgZmFjZWJvb2tJZHA6IGNvZ25pdG8uVXNlclBvb2xJZGVudGl0eVByb3ZpZGVyRmFjZWJvb2sgfCB1bmRlZmluZWQ7XG5cbiAgc2FtbElkcHM6IGNvZ25pdG8uQ2ZuVXNlclBvb2xJZGVudGl0eVByb3ZpZGVyW10gPSBbXTtcblxuICAvKiogVHlwaWNhbGx5IHRoZXJlJ3Mgb25seSBvbmUgY2FsbGJhY2sgVVJMICovXG4gIGNhbGxiYWNrVXJsOiBzdHJpbmc7XG5cbiAgLyoqIEFsbCBjYWxsYmFjayBVUkxzLCBpbmNsdWRpbmcgYW55IGFsdGVybmF0aXZlIFVSTCB3aWxsIGJlIHZpc2libGUgaW4gdGhpcyBwcm9wZXJseSAqL1xuICBjYWxsYmFja1VybHM6IHN0cmluZ1tdID0gW107XG5cbiAgLyoqIE9wdGlvbmFsIGxvZ291dCBVUkwgKi9cbiAgbG9nb3V0VXJsOiBzdHJpbmcgfCB1bmRlZmluZWQ7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgc2NvcGU6IENvbnN0cnVjdCxcbiAgICBpZDogc3RyaW5nLFxuICAgIHByb3BzPzogUGFydGlhbDxjb2duaXRvLlVzZXJQb29sUHJvcHM+LFxuICApIHtcbiAgICBzdXBlcihzY29wZSwgYCR7aWR9Q29nbml0b2ApO1xuXG4gICAgLy8gU3RvcmUgdGhlIElEIHNvIHdlIGNhbiBpdCBpbiBtZXRob2RzOlxuICAgIHRoaXMuaWQgPSBpZDtcblxuICAgIC8vIENvZ25pdG8gdXNlciBwb29sXG4gICAgdGhpcy51c2VyUG9vbCA9IG5ldyBVc2VyUG9vbChzY29wZSwgYCR7aWR9VXNlclBvb2xgLCB7XG4gICAgICB1c2VyUG9vbE5hbWU6IGlkLFxuICAgICAgc2VsZlNpZ25VcEVuYWJsZWQ6IHRydWUsXG4gICAgICBhY2NvdW50UmVjb3Zlcnk6IEFjY291bnRSZWNvdmVyeS5FTUFJTF9PTkxZLFxuICAgICAgc2lnbkluQWxpYXNlczogeyB1c2VybmFtZTogZmFsc2UsIGVtYWlsOiB0cnVlIH0sXG4gICAgICByZW1vdmFsUG9saWN5OiBSZW1vdmFsUG9saWN5LkRFU1RST1ksXG4gICAgICAuLi5wcm9wcyxcbiAgICB9KTtcbiAgfVxuXG4gIGFkZEdvb2dsZUlkcChcbiAgICBnb29nbGVDbGllbnRJZDogc3RyaW5nLFxuICAgIGdvb2dsZUNsaWVudFNlY3JldDogc3RyaW5nLFxuICApOiBVc2VyUG9vbElkZW50aXR5UHJvdmlkZXJHb29nbGUge1xuICAgIGlmICh0aGlzLmdvb2dsZUlkcCkgdGhyb3cgbmV3IEVycm9yKGBHb29nbGUgaWRlbnRpdHkgcHJvdmlkZXIgaGFzIGFscmVhZHkgYmVlbiBjcmVhdGVkIGZvciAke3RoaXMuaWR9LiBZb3UnbGwgbmVlZCB0byBjYWxsIGFkZEdvb2dsZUlkcCBiZWZvcmUgY3JlYXRpbmcgdGhlIGNsaWVudC5gKTtcbiAgICBpZiAodGhpcy51c2VyUG9vbENsaWVudCkgdGhyb3cgbmV3IEVycm9yKGBVc2VyIHBvb2wgY2xpZW50IGhhcyBhbHJlYWR5IGJlZW4gY3JlYXRlZCBmb3IgJHt0aGlzLmlkfS4gWW91J2xsIG5lZWQgdG8gY2FsbCBhZGRHb29nbGVJZHAgYmVmb3JlIGNyZWF0aW5nIHRoZSBjbGllbnQuYCk7XG5cbiAgICAvLyBHb29nbGUgaWRlbnRpdHkgcHJvdmlkZXJcbiAgICB0aGlzLmdvb2dsZUlkcCA9IG5ldyBVc2VyUG9vbElkZW50aXR5UHJvdmlkZXJHb29nbGUodGhpcywgYCR7dGhpcy5pZH1Hb29nbGVJRFBgLCB7XG4gICAgICB1c2VyUG9vbDogdGhpcy51c2VyUG9vbCxcbiAgICAgIGNsaWVudElkOiBnb29nbGVDbGllbnRJZCxcbiAgICAgIGNsaWVudFNlY3JldDogZ29vZ2xlQ2xpZW50U2VjcmV0LFxuICAgICAgc2NvcGVzOiBbJ3Byb2ZpbGUnLCAnZW1haWwnLCAnb3BlbmlkJ10sXG4gICAgICBhdHRyaWJ1dGVNYXBwaW5nOiB7XG4gICAgICAgIGVtYWlsOiBjb2duaXRvLlByb3ZpZGVyQXR0cmlidXRlLkdPT0dMRV9FTUFJTCxcbiAgICAgICAgZ2l2ZW5OYW1lOiBjb2duaXRvLlByb3ZpZGVyQXR0cmlidXRlLkdPT0dMRV9HSVZFTl9OQU1FLFxuICAgICAgICBmYW1pbHlOYW1lOiBjb2duaXRvLlByb3ZpZGVyQXR0cmlidXRlLkdPT0dMRV9GQU1JTFlfTkFNRSxcbiAgICAgICAgZnVsbG5hbWU6IGNvZ25pdG8uUHJvdmlkZXJBdHRyaWJ1dGUuR09PR0xFX05BTUUsXG4gICAgICAgIHByb2ZpbGVQaWN0dXJlOiBjb2duaXRvLlByb3ZpZGVyQXR0cmlidXRlLkdPT0dMRV9QSUNUVVJFLFxuICAgICAgfSxcbiAgICAgIC8vIHNjb3BlczogW1xuICAgICAgLy8gICAnaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vYXV0aC91c2VyaW5mby5lbWFpbCcsXG4gICAgICAvLyAgICdodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9hdXRoL3VzZXJpbmZvLnByb2ZpbGUnXSxcbiAgICB9KTtcblxuICAgIHJldHVybiB0aGlzLmdvb2dsZUlkcDtcbiAgfVxuXG4gIGFkZEZhY2Vib29rSWRwKFxuICAgIGZhY2Vib29rQXBwSWQ6IHN0cmluZyxcbiAgICBmYWNlYm9va0FwcFNlY3JldDogc3RyaW5nLFxuICApOiBVc2VyUG9vbElkZW50aXR5UHJvdmlkZXJGYWNlYm9vayB7XG4gICAgaWYgKHRoaXMuZ29vZ2xlSWRwKSB0aHJvdyBuZXcgRXJyb3IoYEZhY2Vib29rIGlkZW50aXR5IHByb3ZpZGVyIGhhcyBhbHJlYWR5IGJlZW4gY3JlYXRlZCBmb3IgJHt0aGlzLmlkfS4gWW91J2xsIG5lZWQgdG8gY2FsbCBhZGRHb29nbGVJZHAgYmVmb3JlIGNyZWF0aW5nIHRoZSBjbGllbnQuYCk7XG4gICAgaWYgKHRoaXMudXNlclBvb2xDbGllbnQpIHRocm93IG5ldyBFcnJvcihgVXNlciBwb29sIGNsaWVudCBoYXMgYWxyZWFkeSBiZWVuIGNyZWF0ZWQgZm9yICR7dGhpcy5pZH0uIFlvdSdsbCBuZWVkIHRvIGNhbGwgYWRkRmFjZWJvb2tJZHAgYmVmb3JlIGNyZWF0aW5nIHRoZSBjbGllbnQuYCk7XG4gICAgdGhpcy5mYWNlYm9va0lkcCA9IG5ldyBVc2VyUG9vbElkZW50aXR5UHJvdmlkZXJGYWNlYm9vayh0aGlzLCBgJHt0aGlzLmlkfUZhY2Vib29rSURQYCwge1xuICAgICAgdXNlclBvb2w6IHRoaXMudXNlclBvb2wsXG4gICAgICBjbGllbnRJZDogZmFjZWJvb2tBcHBJZCxcbiAgICAgIGNsaWVudFNlY3JldDogZmFjZWJvb2tBcHBTZWNyZXQsXG4gICAgICBzY29wZXM6IFsncHVibGljX3Byb2ZpbGUnLCAnZW1haWwnXSxcbiAgICAgIGF0dHJpYnV0ZU1hcHBpbmc6IHtcbiAgICAgICAgZW1haWw6IGNvZ25pdG8uUHJvdmlkZXJBdHRyaWJ1dGUuRkFDRUJPT0tfRU1BSUwsXG4gICAgICAgIGdpdmVuTmFtZTogY29nbml0by5Qcm92aWRlckF0dHJpYnV0ZS5GQUNFQk9PS19GSVJTVF9OQU1FLFxuICAgICAgICBmYW1pbHlOYW1lOiBjb2duaXRvLlByb3ZpZGVyQXR0cmlidXRlLkZBQ0VCT09LX0xBU1RfTkFNRSxcbiAgICAgICAgZnVsbG5hbWU6IGNvZ25pdG8uUHJvdmlkZXJBdHRyaWJ1dGUuRkFDRUJPT0tfTkFNRSxcbiAgICAgIH0sXG4gICAgfSk7XG5cbiAgICByZXR1cm4gdGhpcy5mYWNlYm9va0lkcDtcbiAgfVxuXG4gIC8qKlxuICAgKiBBZGQgYSBTQU1MIHNzbyBpZGVudGl0eSBwcm92aWRlci5cbiAgICpcbiAgICogWW91IGNhbiBjYWxsIHRoaXMgbWV0aG9kIG1vcmUgdGhhbiBvbmNlIHRvIGFkZCBtdWx0aXBsZSBTQU1MIHByb3ZpZGVycy5cbiAgICpcbiAgICogQHBhcmFtIFNhbWxQcm92aWRlck5hbWUgTmFtZSBpbiB0aGUgQ29nbml0byBob3N0ZWQgVUkgdW5kZXIgXCJTaWduIGluIHdpdGggeW91ciBjb3Jwb3JhdGUgSURcIlxuICAgKiBAcGFyYW0gRmVkZXJhdGlvbk1ldGFkYXRhVXJsIFNBTUwgWE1MIFVSTCAoZS5nLiBBenVyZSlcbiAgICogQHBhcmFtIEZlZGVyYXRpb25NZXRhZGF0YVhtbCBTQU1MIG1ldGFkYXRhIFhNTCAoZS5nLiBHb29nbGUgV29ya3NwYWNlKVxuICAgKi9cbiAgYWRkU2FtbElkcChcbiAgICBTYW1sUHJvdmlkZXJOYW1lOiBzdHJpbmcsXG4gICAgRmVkZXJhdGlvbk1ldGFkYXRhVXJsPzogc3RyaW5nLFxuICAgIEZlZGVyYXRpb25NZXRhZGF0YVhtbD86IHN0cmluZyxcbiAgKTogQ2ZuVXNlclBvb2xJZGVudGl0eVByb3ZpZGVyIHtcbiAgICAvLyBodHRwczovL2RvY3MuYXdzLmFtYXpvbi5jb20vY2RrL2FwaS9sYXRlc3QvZG9jcy9hd3MtY2RrLWxpYl9hd3MtY29nbml0by5DZm5Vc2VyUG9vbElkZW50aXR5UHJvdmlkZXIuaHRtbFxuICAgIC8vIGh0dHBzOi8vZG9jcy5hd3MuYW1hem9uLmNvbS9BV1NDbG91ZEZvcm1hdGlvbi9sYXRlc3QvVXNlckd1aWRlL2F3cy1yZXNvdXJjZS1jb2duaXRvLXVzZXJwb29saWRlbnRpdHlwcm92aWRlci5odG1sXG5cbiAgICBpZiAodGhpcy51c2VyUG9vbENsaWVudCkgdGhyb3cgbmV3IEVycm9yKGBVc2VyIHBvb2wgY2xpZW50IGhhcyBhbHJlYWR5IGJlZW4gY3JlYXRlZCBmb3IgJHt0aGlzLmlkfS4gWW91J2xsIG5lZWQgdG8gY2FsbCBhZGRTYW1sSWRwIGJlZm9yZSBjcmVhdGluZyB0aGUgY2xpZW50LmApO1xuXG4gICAgY29uc3QgcHJvdmlkZXJEZXRhaWxzOiB7IFtrZXk6IHN0cmluZ106IHN0cmluZzsgfSA9IHt9O1xuICAgIGlmIChGZWRlcmF0aW9uTWV0YWRhdGFVcmwpIHtcbiAgICAgIHByb3ZpZGVyRGV0YWlscy5NZXRhZGF0YVVSTCA9IEZlZGVyYXRpb25NZXRhZGF0YVVybDtcbiAgICB9XG4gICAgaWYgKEZlZGVyYXRpb25NZXRhZGF0YVhtbCkge1xuICAgICAgcHJvdmlkZXJEZXRhaWxzLk1ldGFkYXRhRmlsZSA9IEZlZGVyYXRpb25NZXRhZGF0YVhtbDtcbiAgICB9XG5cbiAgICBjb25zdCBzYW1sSWRwID0gbmV3IENmblVzZXJQb29sSWRlbnRpdHlQcm92aWRlcih0aGlzLCBgJHt0aGlzLmlkfVNhbWxJRFAke1NhbWxQcm92aWRlck5hbWV9YCwge1xuICAgICAgdXNlclBvb2xJZDogdGhpcy51c2VyUG9vbC51c2VyUG9vbElkLFxuICAgICAgcHJvdmlkZXJOYW1lOiBTYW1sUHJvdmlkZXJOYW1lIHx8IHRoaXMuaWQsXG4gICAgICBwcm92aWRlclR5cGU6ICdTQU1MJyxcbiAgICAgIGF0dHJpYnV0ZU1hcHBpbmc6IHtcbiAgICAgICAgLy8gaHR0cHM6Ly9kb2NzLmF3cy5hbWF6b24uY29tL2NvZ25pdG8vbGF0ZXN0L2RldmVsb3Blcmd1aWRlL3VzZXItcG9vbC1zZXR0aW5ncy1hdHRyaWJ1dGVzLmh0bWxcbiAgICAgICAgZ2l2ZW5fbmFtZTogJ2h0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2dpdmVubmFtZScsXG4gICAgICAgIGZhbWlseV9uYW1lOiAnaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvc3VybmFtZScsXG4gICAgICAgIGVtYWlsOiAnaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvZW1haWxhZGRyZXNzJyxcbiAgICAgIH0sXG4gICAgICBwcm92aWRlckRldGFpbHMsXG4gICAgfSk7XG4gICAgdGhpcy5zYW1sSWRwcy5wdXNoKHNhbWxJZHApO1xuXG4gICAgcmV0dXJuIHNhbWxJZHA7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGEgQ29nbml0byBVc2VyIFBvb2wgQ2xpZW50LlxuICAgKlxuICAgKiBJZiB5b3Ugd2FudCB0byBhZGQgaWRlbnRpdHkgcHJvdmlkZXJzIHN1Y2ggYXMgR29vZ2xlLCBGYWNlYm9vayBvciBzYW1sIHNzbyB5b3UnbGwgbmVlZCB0byBjYWxsIGFkZEdvb2dsZUlkcCgpLCBhZGRGYWNlYm9va0lkcCgpIGFuZC9vciBhZGRTYW1sSWRwKCkgZmlyc3QuXG4gICAqXG4gICAqIEBwYXJhbSBlbmFibGVFbWFpbCBXaGV0aGVyIHRvIGVuYWJsZSBlbWFpbCBhcyBhIHNpZ24tdXAvc2lnbi1pbiBtZXRob2QuXG4gICAqIEBwYXJhbSBjYWxsYmFja1VybCBBbGxvd2VkIGNhbGxiYWNrIFVSTCBvbiB5b3VyIGFwcCB0byByZWNlaXZlIGFuIGF1dGhlbnRpY2F0aW9uIGNvZGUgKD9jb2RlPS4uLilcbiAgICogQHBhcmFtIGFsdGVybmF0aXZlQ2FsbGJhY2tVcmxzIFplcm8gb3IgbW9yZSBhZGRpdG9uYWwgYXV0aG9yaXplZCBjYWxsYmFjayBVUkwsIGZvciBleGFtcGxlIGlmIHlvdSB3bmVlZCB0byBhbGxvdyBsb2NhbGhvc3QgaW4gYSBkZXZlbG9wbWVudCBlbnZpcm9ubWVudC5cbiAgICogQHJldHVybnMgY29nbml0by5Vc2VyUG9vbENsaWVudFxuICAgKi9cbiAgY3JlYXRlVXNlclBvb2xDbGllbnQocHJvcHM6IHtcbiAgICBjYWxsYmFja1VybDogc3RyaW5nLFxuICAgIGVuYWJsZUVtYWlsPzogYm9vbGVhbixcbiAgICBsb2dvdXRVcmw/OiBzdHJpbmcsXG4gICAgYWx0ZXJuYXRpdmVDYWxsYmFja1VybHM6IHN0cmluZ1tdO1xuICAgIGdlbmVyYXRlU2VjcmV0PzogYm9vbGVhbixcbiAgfSk6IFVzZXJQb29sQ2xpZW50IHtcbiAgICBpZiAodGhpcy51c2VyUG9vbENsaWVudCkgdGhyb3cgbmV3IEVycm9yKGBVc2VyIHBvb2wgY2xpZW50IGhhcyBhbHJlYWR5IGJlZW4gY3JlYXRlZCBmb3IgJHt0aGlzLmlkfWApO1xuXG4gICAgY29uc3QgaWRlbnRpdHlQcm92aWRlcnM6IGNvZ25pdG8uVXNlclBvb2xDbGllbnRJZGVudGl0eVByb3ZpZGVyW10gPSBbXTtcbiAgICBpZiAocHJvcHMuZW5hYmxlRW1haWwpIGlkZW50aXR5UHJvdmlkZXJzLnB1c2goVXNlclBvb2xDbGllbnRJZGVudGl0eVByb3ZpZGVyLkNPR05JVE8pO1xuICAgIGlmICh0aGlzLmdvb2dsZUlkcCkgaWRlbnRpdHlQcm92aWRlcnMucHVzaChVc2VyUG9vbENsaWVudElkZW50aXR5UHJvdmlkZXIuR09PR0xFKTtcbiAgICBpZiAodGhpcy5mYWNlYm9va0lkcCkgaWRlbnRpdHlQcm92aWRlcnMucHVzaChVc2VyUG9vbENsaWVudElkZW50aXR5UHJvdmlkZXIuRkFDRUJPT0spO1xuICAgIHRoaXMuc2FtbElkcHMuZm9yRWFjaCgoc2FtbCkgPT4ge1xuICAgICAgaWRlbnRpdHlQcm92aWRlcnMucHVzaChVc2VyUG9vbENsaWVudElkZW50aXR5UHJvdmlkZXIuY3VzdG9tKHNhbWwucHJvdmlkZXJOYW1lKSk7XG4gICAgfSk7XG5cbiAgICB0aGlzLmNhbGxiYWNrVXJsID0gcHJvcHMuY2FsbGJhY2tVcmw7XG4gICAgdGhpcy5jYWxsYmFja1VybHMgPSBbcHJvcHMuY2FsbGJhY2tVcmxdO1xuICAgIHRoaXMuY2FsbGJhY2tVcmxzLnB1c2goLi4ucHJvcHMuYWx0ZXJuYXRpdmVDYWxsYmFja1VybHMpO1xuICAgIHRoaXMubG9nb3V0VXJsID0gcHJvcHMubG9nb3V0VXJsO1xuICAgIGNvbnN0IHVzZXJQb29sQ2xpZW50ID0gbmV3IFVzZXJQb29sQ2xpZW50KHRoaXMsIGAke3RoaXMuaWR9VXNlclBvb2xDbGllbnRgLCB7XG4gICAgICB1c2VyUG9vbDogdGhpcy51c2VyUG9vbCxcbiAgICAgIHVzZXJQb29sQ2xpZW50TmFtZTogdGhpcy5pZCxcbiAgICAgIGdlbmVyYXRlU2VjcmV0OiBwcm9wcy5nZW5lcmF0ZVNlY3JldCA9PT0gZmFsc2UgPyBmYWxzZSA6IHRydWUsXG4gICAgICBwcmV2ZW50VXNlckV4aXN0ZW5jZUVycm9yczogdHJ1ZSxcbiAgICAgIHN1cHBvcnRlZElkZW50aXR5UHJvdmlkZXJzOiBpZGVudGl0eVByb3ZpZGVycyxcbiAgICAgIG9BdXRoOiB7XG4gICAgICAgIGNhbGxiYWNrVXJsczogdGhpcy5jYWxsYmFja1VybHMsXG4gICAgICAgIGxvZ291dFVybHM6IHRoaXMubG9nb3V0VXJsID8gW3RoaXMubG9nb3V0VXJsXSA6IHVuZGVmaW5lZCxcbiAgICAgICAgZmxvd3M6IHtcbiAgICAgICAgICBhdXRob3JpemF0aW9uQ29kZUdyYW50OiB0cnVlLFxuICAgICAgICB9LFxuICAgICAgICBzY29wZXM6IFtcbiAgICAgICAgICBjb2duaXRvLk9BdXRoU2NvcGUuRU1BSUwsXG4gICAgICAgICAgY29nbml0by5PQXV0aFNjb3BlLk9QRU5JRCxcbiAgICAgICAgICBjb2duaXRvLk9BdXRoU2NvcGUuUFJPRklMRSxcbiAgICAgICAgXSxcbiAgICAgIH0sXG4gICAgfSk7XG5cbiAgICAvLyBUaGVzZSBkZXBlbmRlbmNpZXMgc2VlbWVkIHRvIGJlIG5lZWRlZCBhdCB0aGUgdGltZSBvZiB3cml0aW5nOlxuICAgIGlmICh0aGlzLmdvb2dsZUlkcCkgdXNlclBvb2xDbGllbnQubm9kZS5hZGREZXBlbmRlbmN5KHRoaXMuZ29vZ2xlSWRwKTtcbiAgICBpZiAodGhpcy5mYWNlYm9va0lkcCkgdXNlclBvb2xDbGllbnQubm9kZS5hZGREZXBlbmRlbmN5KHRoaXMuZmFjZWJvb2tJZHApO1xuICAgIGlmICh0aGlzLnNhbWxJZHBzKSB7XG4gICAgICB0aGlzLnNhbWxJZHBzLmZvckVhY2goKHNhbWxJZHApID0+IHVzZXJQb29sQ2xpZW50Lm5vZGUuYWRkRGVwZW5kZW5jeShzYW1sSWRwKSk7XG4gICAgfVxuXG4gICAgdGhpcy51c2VyUG9vbENsaWVudCA9IHVzZXJQb29sQ2xpZW50O1xuICAgIHJldHVybiB0aGlzLnVzZXJQb29sQ2xpZW50O1xuICB9XG5cbiAgLyoqXG4gICAqIEFkZCBhIGN1c3RvbSBkb21haW4gbmFtZSB0byB0aGUgQ29nbml0byBVc2VyIFBvb2wuXG4gICAqIFxuICAgKiBOT1RFOiB0aGVyZSdzIGEgQ29nbml0byBxdWlyayB3aGVyZSBpdCBzZWVtcyB