diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 137cd1d214e10..e8766a08b56cd 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -528,6 +528,7 @@ The following third-party identity providers are currently supported in the CDK - [Google Login](https://developers.google.com/identity/sign-in/web/sign-in) - [Sign In With Apple](https://developer.apple.com/sign-in-with-apple/get-started/) - [OpenID Connect](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-oidc-idp.html) +- [SAML](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-saml-idp.html) The following code configures a user pool to federate with the third party provider, 'Login with Amazon'. The identity provider needs to be configured with a set of credentials that the Cognito backend can use to federate with the diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts index fd7ad04af70fe..b99422478a85d 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts @@ -4,3 +4,4 @@ export * from './amazon'; export * from './facebook'; export * from './google'; export * from './oidc'; +export * from './saml'; diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/saml.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/saml.ts new file mode 100644 index 0000000000000..3c8846b904693 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/saml.ts @@ -0,0 +1,132 @@ +import { Names, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnUserPoolIdentityProvider } from '../cognito.generated'; +import { UserPoolIdentityProviderProps } from './base'; +import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base'; + +/** + * Properties to initialize UserPoolIdentityProviderSaml. + */ +export interface UserPoolIdentityProviderSamlProps extends UserPoolIdentityProviderProps { + /** + * The name of the provider. Must be between 3 and 32 characters. + * + * @default - the unique ID of the construct + */ + readonly name?: string; + + /** + * Identifiers + * + * Identifiers can be used to redirect users to the correct IdP in multitenant apps. + * + * @default - no identifiers used + */ + readonly identifiers?: string[] + + /** + * The SAML metadata. + */ + readonly metadata: UserPoolIdentityProviderSamlMetadata; + + /** + * Whether to enable the "Sign-out flow" feature. + * + * @default - false + */ + readonly idpSignout?: boolean; +} + +/** + * Metadata types that can be used for a SAML user pool identity provider. + */ +export enum UserPoolIdentityProviderSamlMetadataType { + /** Metadata provided via a URL. */ + URL = 'url', + + /** Metadata provided via the contents of a file. */ + FILE = 'file', +} + +/** + * Metadata for a SAML user pool identity provider. + */ +export class UserPoolIdentityProviderSamlMetadata { + + /** + * Specify SAML metadata via a URL. + */ + public static url(url: string): UserPoolIdentityProviderSamlMetadata { + return new UserPoolIdentityProviderSamlMetadata(url, UserPoolIdentityProviderSamlMetadataType.URL); + } + + /** + * Specify SAML metadata via the contents of a file. + */ + public static file(fileContent: string): UserPoolIdentityProviderSamlMetadata { + return new UserPoolIdentityProviderSamlMetadata(fileContent, UserPoolIdentityProviderSamlMetadataType.FILE); + } + + /** + * Construct the metadata for a SAML identity provider. + * + * @param metadataContent A URL hosting SAML metadata, or the content of a file containing SAML metadata. + * @param metadataType The type of metadata, either a URL or file content. + */ + private constructor(public readonly metadataContent: string, public readonly metadataType: UserPoolIdentityProviderSamlMetadataType) { + } +} + +/** + * Represents a identity provider that integrates with SAML. + * @resource AWS::Cognito::UserPoolIdentityProvider + */ +export class UserPoolIdentityProviderSaml extends UserPoolIdentityProviderBase { + public readonly providerName: string; + + constructor(scope: Construct, id: string, props: UserPoolIdentityProviderSamlProps) { + super(scope, id, props); + + this.validateName(props.name); + + const { metadataType, metadataContent } = props.metadata; + + const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { + userPoolId: props.userPool.userPoolId, + providerName: this.getProviderName(props.name), + providerType: 'SAML', + providerDetails: { + IDPSignout: props.idpSignout ?? false, + MetadataURL: metadataType === UserPoolIdentityProviderSamlMetadataType.URL ? metadataContent : undefined, + MetadataFile: metadataType === UserPoolIdentityProviderSamlMetadataType.FILE ? metadataContent : undefined, + }, + idpIdentifiers: props.identifiers, + attributeMapping: super.configureAttributeMapping(), + }); + + this.providerName = super.getResourceNameAttribute(resource.ref); + } + + private getProviderName(name?: string): string { + if (name) { + this.validateName(name); + return name; + } + + const uniqueName = Names.uniqueResourceName(this, { + maxLength: 32, + }); + + if (uniqueName.length < 3) { + return `${uniqueName}saml`; + } + + return uniqueName; + } + + private validateName(name?: string) { + if (name && !Token.isUnresolved(name) && (name.length < 3 || name.length > 32)) { + throw new Error(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`); + } + } +} diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 633a3b5195fa2..d7e78bfc5cb27 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -83,6 +83,7 @@ "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", + "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", @@ -126,7 +127,8 @@ "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAmazonProps", "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderGoogleProps", "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAppleProps", - "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderOidcProps" + "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderOidcProps", + "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderSamlProps" ] }, "stability": "stable", diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.saml.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.saml.ts new file mode 100644 index 0000000000000..fd1de9dd1fc77 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.saml.ts @@ -0,0 +1,40 @@ +import { App, CfnOutput, RemovalPolicy, Stack } from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import { Construct } from 'constructs'; +import { UserPool, UserPoolIdentityProviderSaml, UserPoolIdentityProviderSamlMetadata } from '../lib'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + const userpool = new UserPool(this, 'pool', { + removalPolicy: RemovalPolicy.DESTROY, + }); + + new UserPoolIdentityProviderSaml(this, 'cdk', { + userPool: userpool, + name: 'cdk', + metadata: UserPoolIdentityProviderSamlMetadata.url('https://fujifish.github.io/samling/public/metadata.xml'), + }); + + const client = userpool.addClient('client'); + + const domain = userpool.addDomain('domain', { + cognitoDomain: { + domainPrefix: 'cdk-test-pool', + }, + }); + + new CfnOutput(this, 'SignInLink', { + value: domain.signInUrl(client, { + redirectUri: 'https://example.com', + }), + }); + } +} + +const app = new App(); +const testCase = new TestStack(app, 'integ-user-pool-identity-provider-saml-stack'); + +new IntegTest(app, 'integ-user-pool-identity-provider-saml-test', { + testCases: [testCase], +}); diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integ-user-pool-identity-provider-saml-stack.assets.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integ-user-pool-identity-provider-saml-stack.assets.json new file mode 100644 index 0000000000000..8e65975b437d9 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integ-user-pool-identity-provider-saml-stack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "6f6f07786415216f13b738979cec5ad81dbab3283fae83b99324965935cc1d60": { + "source": { + "path": "integ-user-pool-identity-provider-saml-stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "6f6f07786415216f13b738979cec5ad81dbab3283fae83b99324965935cc1d60.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integ-user-pool-identity-provider-saml-stack.template.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integ-user-pool-identity-provider-saml-stack.template.json new file mode 100644 index 0000000000000..56cb33a5c739f --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integ-user-pool-identity-provider-saml-stack.template.json @@ -0,0 +1,145 @@ +{ + "Resources": { + "pool056F3F7E": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsVerificationMessage": "The verification code to your new account is {####}", + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "poolclient2623294C": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "pool056F3F7E" + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + { + "Ref": "cdk52888317" + }, + "COGNITO" + ] + } + }, + "pooldomain430FA744": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "cdk-test-pool", + "UserPoolId": { + "Ref": "pool056F3F7E" + } + } + }, + "cdk52888317": { + "Type": "AWS::Cognito::UserPoolIdentityProvider", + "Properties": { + "ProviderName": "cdk", + "ProviderType": "SAML", + "UserPoolId": { + "Ref": "pool056F3F7E" + }, + "ProviderDetails": { + "IDPSignout": false, + "MetadataURL": "https://fujifish.github.io/samling/public/metadata.xml" + } + } + } + }, + "Outputs": { + "SignInLink": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "pooldomain430FA744" + }, + ".auth.", + { + "Ref": "AWS::Region" + }, + ".amazoncognito.com/login?client_id=", + { + "Ref": "poolclient2623294C" + }, + "&response_type=code&redirect_uri=https://example.com" + ] + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integ.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integ.json new file mode 100644 index 0000000000000..eb9199222282a --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "integ-user-pool-identity-provider-saml-test/DefaultTest": { + "stacks": [ + "integ-user-pool-identity-provider-saml-stack" + ], + "assertionStack": "integ-user-pool-identity-provider-saml-test/DefaultTest/DeployAssert", + "assertionStackName": "integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.assets.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.assets.json new file mode 100644 index 0000000000000..39aac6c49fa80 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.template.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..dc88fad953da5 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/manifest.json @@ -0,0 +1,135 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "integ-user-pool-identity-provider-saml-stack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-user-pool-identity-provider-saml-stack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-user-pool-identity-provider-saml-stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integ-user-pool-identity-provider-saml-stack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/6f6f07786415216f13b738979cec5ad81dbab3283fae83b99324965935cc1d60.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-user-pool-identity-provider-saml-stack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integ-user-pool-identity-provider-saml-stack.assets" + ], + "metadata": { + "/integ-user-pool-identity-provider-saml-stack/pool/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "pool056F3F7E" + } + ], + "/integ-user-pool-identity-provider-saml-stack/pool/client/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "poolclient2623294C" + } + ], + "/integ-user-pool-identity-provider-saml-stack/pool/domain/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "pooldomain430FA744" + } + ], + "/integ-user-pool-identity-provider-saml-stack/cdk/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "cdk52888317" + } + ], + "/integ-user-pool-identity-provider-saml-stack/SignInLink": [ + { + "type": "aws:cdk:logicalId", + "data": "SignInLink" + } + ], + "/integ-user-pool-identity-provider-saml-stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-user-pool-identity-provider-saml-stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-user-pool-identity-provider-saml-stack" + }, + "integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.assets" + ], + "metadata": { + "/integ-user-pool-identity-provider-saml-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-user-pool-identity-provider-saml-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-user-pool-identity-provider-saml-test/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/tree.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/tree.json new file mode 100644 index 0000000000000..65caa4df4b09b --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.saml.integ.snapshot/tree.json @@ -0,0 +1,228 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "integ-user-pool-identity-provider-saml-stack": { + "id": "integ-user-pool-identity-provider-saml-stack", + "path": "integ-user-pool-identity-provider-saml-stack", + "children": { + "pool": { + "id": "pool", + "path": "integ-user-pool-identity-provider-saml-stack/pool", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-user-pool-identity-provider-saml-stack/pool/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cognito::UserPool", + "aws:cdk:cloudformation:props": { + "accountRecoverySetting": { + "recoveryMechanisms": [ + { + "name": "verified_phone_number", + "priority": 1 + }, + { + "name": "verified_email", + "priority": 2 + } + ] + }, + "adminCreateUserConfig": { + "allowAdminCreateUserOnly": true + }, + "emailVerificationMessage": "The verification code to your new account is {####}", + "emailVerificationSubject": "Verify your new account", + "smsVerificationMessage": "The verification code to your new account is {####}", + "verificationMessageTemplate": { + "defaultEmailOption": "CONFIRM_WITH_CODE", + "emailMessage": "The verification code to your new account is {####}", + "emailSubject": "Verify your new account", + "smsMessage": "The verification code to your new account is {####}" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.CfnUserPool", + "version": "0.0.0" + } + }, + "client": { + "id": "client", + "path": "integ-user-pool-identity-provider-saml-stack/pool/client", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-user-pool-identity-provider-saml-stack/pool/client/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cognito::UserPoolClient", + "aws:cdk:cloudformation:props": { + "userPoolId": { + "Ref": "pool056F3F7E" + }, + "allowedOAuthFlows": [ + "implicit", + "code" + ], + "allowedOAuthFlowsUserPoolClient": true, + "allowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "callbackUrLs": [ + "https://example.com" + ], + "supportedIdentityProviders": [ + { + "Ref": "cdk52888317" + }, + "COGNITO" + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.CfnUserPoolClient", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.UserPoolClient", + "version": "0.0.0" + } + }, + "domain": { + "id": "domain", + "path": "integ-user-pool-identity-provider-saml-stack/pool/domain", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-user-pool-identity-provider-saml-stack/pool/domain/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cognito::UserPoolDomain", + "aws:cdk:cloudformation:props": { + "domain": "cdk-test-pool", + "userPoolId": { + "Ref": "pool056F3F7E" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.CfnUserPoolDomain", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.UserPoolDomain", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.UserPool", + "version": "0.0.0" + } + }, + "cdk": { + "id": "cdk", + "path": "integ-user-pool-identity-provider-saml-stack/cdk", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-user-pool-identity-provider-saml-stack/cdk/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cognito::UserPoolIdentityProvider", + "aws:cdk:cloudformation:props": { + "providerName": "cdk", + "providerType": "SAML", + "userPoolId": { + "Ref": "pool056F3F7E" + }, + "providerDetails": { + "IDPSignout": false, + "MetadataURL": "https://fujifish.github.io/samling/public/metadata.xml" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.CfnUserPoolIdentityProvider", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.UserPoolIdentityProviderSaml", + "version": "0.0.0" + } + }, + "SignInLink": { + "id": "SignInLink", + "path": "integ-user-pool-identity-provider-saml-stack/SignInLink", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "integ-user-pool-identity-provider-saml-test": { + "id": "integ-user-pool-identity-provider-saml-test", + "path": "integ-user-pool-identity-provider-saml-test", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "integ-user-pool-identity-provider-saml-test/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "integ-user-pool-identity-provider-saml-test/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "integ-user-pool-identity-provider-saml-test/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/saml.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/saml.test.ts new file mode 100644 index 0000000000000..464bd66396ca3 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/saml.test.ts @@ -0,0 +1,184 @@ +import { Template, Match } from '@aws-cdk/assertions'; +import { Stack } from '@aws-cdk/core'; +import { ProviderAttribute, UserPool, UserPoolIdentityProviderSaml, UserPoolIdentityProviderSamlMetadata } from '../../lib'; + +describe('UserPoolIdentityProvider', () => { + describe('saml', () => { + test('metadata URL', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderSaml(stack, 'userpoolidp', { + userPool: pool, + metadata: UserPoolIdentityProviderSamlMetadata.url('https://my-metadata-url.com'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'SAML', + ProviderDetails: { + MetadataURL: 'https://my-metadata-url.com', + IDPSignout: false, + }, + }); + }); + + test('metadata file', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderSaml(stack, 'userpoolidp', { + userPool: pool, + metadata: UserPoolIdentityProviderSamlMetadata.file('my-file-contents'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'SAML', + ProviderDetails: { + MetadataFile: 'my-file-contents', + IDPSignout: false, + }, + }); + }); + + test('idpSignout', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderSaml(stack, 'userpoolidp', { + userPool: pool, + metadata: UserPoolIdentityProviderSamlMetadata.file('my-file-contents'), + idpSignout: true, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'SAML', + ProviderDetails: { + MetadataFile: 'my-file-contents', + IDPSignout: true, + }, + }); + }); + + test('registered with user pool', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + const provider = new UserPoolIdentityProviderSaml(stack, 'userpoolidp', { + userPool: pool, + metadata: UserPoolIdentityProviderSamlMetadata.file('my-file-contents'), + }); + + // THEN + expect(pool.identityProviders).toContain(provider); + }); + + test('attribute mapping', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderSaml(stack, 'userpoolidp', { + userPool: pool, + metadata: UserPoolIdentityProviderSamlMetadata.file('my-file-contents'), + attributeMapping: { + familyName: ProviderAttribute.other('family_name'), + givenName: ProviderAttribute.other('given_name'), + custom: { + customAttr1: ProviderAttribute.other('email'), + customAttr2: ProviderAttribute.other('sub'), + }, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + AttributeMapping: { + family_name: 'family_name', + given_name: 'given_name', + customAttr1: 'email', + customAttr2: 'sub', + }, + }); + }); + + test('with provider name', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderSaml(stack, 'userpoolidp', { + userPool: pool, + name: 'my-provider', + metadata: UserPoolIdentityProviderSamlMetadata.file('my-file-contents'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'my-provider', + }); + }); + + test('throws with invalid provider name', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // THEN + expect(() => new UserPoolIdentityProviderSaml(stack, 'userpoolidp', { + userPool: pool, + name: 'xy', + metadata: UserPoolIdentityProviderSamlMetadata.file('my-file-contents'), + })).toThrow(/Expected provider name to be between 3 and 32 characters/); + }); + + test('generates a valid name when unique id is too short', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderSaml(stack, 'xy', { + userPool: pool, + metadata: UserPoolIdentityProviderSamlMetadata.file('my-file-contents'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'xysaml', + }); + }); + + test('generates a valid name when unique id is too long', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderSaml(stack, `${'saml'.repeat(10)}xyz`, { + userPool: pool, + metadata: UserPoolIdentityProviderSamlMetadata.file('my-file-contents'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: Match.stringLikeRegexp('^\\w{3,32}$'), + }); + }); + }); +});