diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/cdk.out new file mode 100644 index 0000000000000..bd5311dc372de --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.5"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/integ.json new file mode 100644 index 0000000000000..f1a91df50309a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "36.0.5", + "testCases": { + "integ-user-pool-group/DefaultTest": { + "stacks": [ + "user-pool-group-stack" + ], + "assertionStack": "integ-user-pool-group/DefaultTest/DeployAssert", + "assertionStackName": "integuserpoolgroupDefaultTestDeployAssertED51AC37" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/integuserpoolgroupDefaultTestDeployAssertED51AC37.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/integuserpoolgroupDefaultTestDeployAssertED51AC37.assets.json new file mode 100644 index 0000000000000..28deb49760e98 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/integuserpoolgroupDefaultTestDeployAssertED51AC37.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.5", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "integuserpoolgroupDefaultTestDeployAssertED51AC37.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-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/integuserpoolgroupDefaultTestDeployAssertED51AC37.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/integuserpoolgroupDefaultTestDeployAssertED51AC37.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/integuserpoolgroupDefaultTestDeployAssertED51AC37.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-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/manifest.json new file mode 100644 index 0000000000000..37099577d2bd1 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/manifest.json @@ -0,0 +1,131 @@ +{ + "version": "36.0.5", + "artifacts": { + "user-pool-group-stack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "user-pool-group-stack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "user-pool-group-stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "user-pool-group-stack.template.json", + "terminationProtection": false, + "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}/e4818d062171ff6331b3b3006fb0ab0841f2234a861d18a4d86936c3122bc981.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "user-pool-group-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": [ + "user-pool-group-stack.assets" + ], + "metadata": { + "/user-pool-group-stack/UserPool/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "UserPool6BA7E5F2" + } + ], + "/user-pool-group-stack/UserPool/another-group/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "UserPoolanothergroup728681C4" + } + ], + "/user-pool-group-stack/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Role1ABCC5F0" + } + ], + "/user-pool-group-stack/UserPoolGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "UserPoolGroup3C2AB4D4" + } + ], + "/user-pool-group-stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/user-pool-group-stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "user-pool-group-stack" + }, + "integuserpoolgroupDefaultTestDeployAssertED51AC37.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integuserpoolgroupDefaultTestDeployAssertED51AC37.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integuserpoolgroupDefaultTestDeployAssertED51AC37": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integuserpoolgroupDefaultTestDeployAssertED51AC37.template.json", + "terminationProtection": false, + "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": [ + "integuserpoolgroupDefaultTestDeployAssertED51AC37.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": [ + "integuserpoolgroupDefaultTestDeployAssertED51AC37.assets" + ], + "metadata": { + "/integ-user-pool-group/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-user-pool-group/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-user-pool-group/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/tree.json new file mode 100644 index 0000000000000..3d47d348fb70d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/tree.json @@ -0,0 +1,253 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "user-pool-group-stack": { + "id": "user-pool-group-stack", + "path": "user-pool-group-stack", + "children": { + "UserPool": { + "id": "UserPool", + "path": "user-pool-group-stack/UserPool", + "children": { + "Resource": { + "id": "Resource", + "path": "user-pool-group-stack/UserPool/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": "constructs.Construct", + "version": "10.3.0" + } + }, + "another-group": { + "id": "another-group", + "path": "user-pool-group-stack/UserPool/another-group", + "children": { + "Resource": { + "id": "Resource", + "path": "user-pool-group-stack/UserPool/another-group/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cognito::UserPoolGroup", + "aws:cdk:cloudformation:props": { + "precedence": 1, + "userPoolId": { + "Ref": "UserPool6BA7E5F2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Role": { + "id": "Role", + "path": "user-pool-group-stack/Role", + "children": { + "ImportRole": { + "id": "ImportRole", + "path": "user-pool-group-stack/Role/ImportRole", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Resource": { + "id": "Resource", + "path": "user-pool-group-stack/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "cognito-idp.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "UserPoolGroup": { + "id": "UserPoolGroup", + "path": "user-pool-group-stack/UserPoolGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "user-pool-group-stack/UserPoolGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cognito::UserPoolGroup", + "aws:cdk:cloudformation:props": { + "description": "My user pool group", + "groupName": "test-group", + "precedence": 0, + "roleArn": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + }, + "userPoolId": { + "Ref": "UserPool6BA7E5F2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "user-pool-group-stack/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "user-pool-group-stack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "integ-user-pool-group": { + "id": "integ-user-pool-group", + "path": "integ-user-pool-group", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "integ-user-pool-group/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "integ-user-pool-group/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "integ-user-pool-group/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-user-pool-group/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-user-pool-group/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/user-pool-group-stack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/user-pool-group-stack.assets.json new file mode 100644 index 0000000000000..d03b9656a7162 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/user-pool-group-stack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.5", + "files": { + "e4818d062171ff6331b3b3006fb0ab0841f2234a861d18a4d86936c3122bc981": { + "source": { + "path": "user-pool-group-stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "e4818d062171ff6331b3b3006fb0ab0841f2234a861d18a4d86936c3122bc981.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-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/user-pool-group-stack.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/user-pool-group-stack.template.json new file mode 100644 index 0000000000000..f7cfaf0f6f794 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.js.snapshot/user-pool-group-stack.template.json @@ -0,0 +1,112 @@ +{ + "Resources": { + "UserPool6BA7E5F2": { + "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" + }, + "UserPoolanothergroup728681C4": { + "Type": "AWS::Cognito::UserPoolGroup", + "Properties": { + "Precedence": 1, + "UserPoolId": { + "Ref": "UserPool6BA7E5F2" + } + } + }, + "Role1ABCC5F0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "cognito-idp.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "UserPoolGroup3C2AB4D4": { + "Type": "AWS::Cognito::UserPoolGroup", + "Properties": { + "Description": "My user pool group", + "GroupName": "test-group", + "Precedence": 0, + "RoleArn": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + }, + "UserPoolId": { + "Ref": "UserPool6BA7E5F2" + } + } + } + }, + "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-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.ts new file mode 100644 index 0000000000000..bd8cc3fac6ab9 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cognito/test/integ.user-pool-group.ts @@ -0,0 +1,37 @@ +import { App, RemovalPolicy, Stack } from 'aws-cdk-lib'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import { Construct } from 'constructs'; +import { UserPool, UserPoolGroup } from 'aws-cdk-lib/aws-cognito'; +import { Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + const userPool = new UserPool(this, 'UserPool', { + removalPolicy: RemovalPolicy.DESTROY, + }); + + const role = new Role(this, 'Role', { + assumedBy: new ServicePrincipal('cognito-idp.amazonaws.com'), + }); + + new UserPoolGroup(this, 'UserPoolGroup', { + userPool: userPool, + groupName: 'test-group', + description: 'My user pool group', + precedence: 0, + role, + }); + + userPool.addGroup('another-group', { + precedence: 1, + }); + } +} + +const app = new App(); +const stack = new TestStack(app, 'user-pool-group-stack'); + +new IntegTest(app, 'integ-user-pool-group', { + testCases: [stack], +}); diff --git a/packages/aws-cdk-lib/aws-cognito/README.md b/packages/aws-cdk-lib/aws-cognito/README.md index 35df1769e41ac..6c0d6cb295fdc 100644 --- a/packages/aws-cdk-lib/aws-cognito/README.md +++ b/packages/aws-cdk-lib/aws-cognito/README.md @@ -1003,7 +1003,6 @@ const userpool = new cognito.UserPool(this, 'UserPool', { By default deletion protection is disabled. - ### `email_verified` Attribute Mapping If you use a third-party identity provider, you can specify the `email_verified` attribute in attributeMapping. @@ -1019,4 +1018,30 @@ new cognito.UserPoolIdentityProviderGoogle(this, 'google', { emailVerified: cognito.ProviderAttribute.GOOGLE_EMAIL_VERIFIED, // you can mapping the `email_verified` attribute. }, }); -``` \ No newline at end of file +``` + +### User Pool Group + +Support for groups in Amazon Cognito user pools enables you to create and manage groups, add users to groups, and remove users from groups. +Use groups to create collections of users to manage their permissions or to represent different types of users. + +You can assign an AWS Identity and Access Management (IAM) role to a group to define the permissions for members of a group. + +For more information, see [Adding groups to a user pool](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-user-groups.html). + +```ts +declare const userPool: cognito.UserPool; +declare const role: iam.Role; + +new cognito.UserPoolGroup(this, 'UserPoolGroup', { + userPool, + groupName: 'my-group-name', + precedence: 1, + role, // assign IAM Role +}); + +// You can also add a group by using addGroup method. +userPool.addGroup('AnotherUserPoolGroup', { + groupName: 'another-group-name' +}); +``` diff --git a/packages/aws-cdk-lib/aws-cognito/lib/index.ts b/packages/aws-cdk-lib/aws-cognito/lib/index.ts index 7d5ce97fc2c76..9591ac8156d51 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/index.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/index.ts @@ -5,6 +5,7 @@ export * from './user-pool-attr'; export * from './user-pool-client'; export * from './user-pool-domain'; export * from './user-pool-email'; +export * from './user-pool-group'; export * from './user-pool-idp'; export * from './user-pool-idps'; export * from './user-pool-resource-server'; diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-group.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-group.ts new file mode 100644 index 0000000000000..52b55a12e83e5 --- /dev/null +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-group.ts @@ -0,0 +1,120 @@ +import { Construct } from 'constructs'; +import { CfnUserPoolGroup } from './cognito.generated'; +import { IUserPool } from './user-pool'; +import { IRole } from '../../aws-iam'; +import { IResource, Resource, Token } from '../../core'; + +/** + * Represents a user pool group. + */ +export interface IUserPoolGroup extends IResource { + /** + * The user group name + * @attribute + */ + readonly groupName: string; +} + +/** + * Options to create a UserPoolGroup + */ +export interface UserPoolGroupOptions { + /** + * A string containing the description of the group. + * + * @default - no description + */ + readonly description?: string; + + /** + * The name of the group. Must be unique. + * + * @default - auto generate a name + */ + readonly groupName?: string; + + /** + * A non-negative integer value that specifies the precedence of this group relative to the other groups + * that a user can belong to in the user pool. Zero is the highest precedence value. + * + * Groups with lower Precedence values take precedence over groups with higher or null Precedence values. + * If a user belongs to two or more groups, it is the group with the lowest precedence value + * whose role ARN is given in the user's tokens for the cognito:roles and cognito:preferred_role claims. + * + * Two groups can have the same Precedence value. If this happens, neither group takes precedence over the other. + * If two groups with the same Precedence have the same role ARN, that role is used in the cognito:preferred_role + * claim in tokens for users in each group. + * If the two groups have different role ARNs, the cognito:preferred_role claim isn't set in users' tokens. + * + * @default - null + */ + readonly precedence?: number; + + /** + * The role for the group. + * + * @default - no description + */ + readonly role?: IRole; +} + +/** + * Props for UserPoolGroup construct + */ +export interface UserPoolGroupProps extends UserPoolGroupOptions { + /** + * The user pool to which this group is associated. + */ + readonly userPool: IUserPool; +} + +/** + * Define a user pool group + */ +export class UserPoolGroup extends Resource implements IUserPoolGroup { + /** + * Import a UserPoolGroup given its group name + */ + public static fromGroupName(scope: Construct, id: string, groupName: string): IUserPoolGroup { + class Import extends Resource implements IUserPoolGroup { + public readonly groupName = groupName; + } + return new Import(scope, id); + } + + public readonly groupName: string; + + constructor(scope: Construct, id: string, props: UserPoolGroupProps) { + super(scope, id); + + if (props.description !== undefined && + !Token.isUnresolved(props.description) && + (props.description.length > 2048)) { + throw new Error(`\`description\` must be between 0 and 2048 characters. Received: ${props.description.length} characters`); + } + + if (props.precedence !== undefined && + !Token.isUnresolved(props.precedence) && + (props.precedence < 0 || props.precedence > 2 ** 31 - 1)) { + throw new Error(`\`precedence\` must be between 0 and 2^31-1. Received: ${props.precedence}`); + } + + if ( + props.groupName !== undefined && + !Token.isUnresolved(props.groupName) && + !/^[\p{L}\p{M}\p{S}\p{N}\p{P}]{1,128}$/u.test(props.groupName) + ) { + throw new Error('\`groupName\` must be between 1 and 128 characters and can include letters, numbers, and symbols.'); + } + + const resource = new CfnUserPoolGroup(this, 'Resource', { + userPoolId: props.userPool.userPoolId, + description: props.description, + groupName: props.groupName, + precedence: props.precedence, + roleArn: props.role?.roleArn, + }); + + this.groupName = resource.ref; + } +} diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool.ts index ede93a84af1f0..f4bc5b8b29a11 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool.ts @@ -6,6 +6,7 @@ import { ICustomAttribute, StandardAttribute, StandardAttributes } from './user- import { UserPoolClient, UserPoolClientOptions } from './user-pool-client'; import { UserPoolDomain, UserPoolDomainOptions } from './user-pool-domain'; import { UserPoolEmail } from './user-pool-email'; +import { UserPoolGroup, UserPoolGroupOptions } from './user-pool-group'; import { IUserPoolIdentityProvider } from './user-pool-idp'; import { UserPoolResourceServer, UserPoolResourceServerOptions } from './user-pool-resource-server'; import { Grant, IGrantable, IRole, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from '../../aws-iam'; @@ -797,6 +798,12 @@ export interface IUserPool extends IResource { */ addResourceServer(id: string, options: UserPoolResourceServerOptions): UserPoolResourceServer; + /** + * Add a new group to this user pool. + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-user-groups.html + */ + addGroup(id: string, options: UserPoolGroupOptions): UserPoolGroup; + /** * Register an identity provider with this user pool. */ @@ -836,6 +843,13 @@ abstract class UserPoolBase extends Resource implements IUserPool { }); } + public addGroup(id: string, options: UserPoolGroupOptions): UserPoolGroup { + return new UserPoolGroup(this, id, { + userPool: this, + ...options, + }); + } + public registerIdentityProvider(provider: IUserPoolIdentityProvider) { this.identityProviders.push(provider); } diff --git a/packages/aws-cdk-lib/aws-cognito/test/user-pool-group.test.ts b/packages/aws-cdk-lib/aws-cognito/test/user-pool-group.test.ts new file mode 100644 index 0000000000000..52bedca6e1c52 --- /dev/null +++ b/packages/aws-cdk-lib/aws-cognito/test/user-pool-group.test.ts @@ -0,0 +1,70 @@ +import { Template } from '../../assertions'; +import { Role, ServicePrincipal } from '../../aws-iam'; +import { Stack } from '../../core'; +import { UserPool, UserPoolGroup } from '../lib'; + +describe('User Pool Group', () => { + let stack: Stack; + let userPool: UserPool; + beforeEach(() => { + stack = new Stack(); + userPool = new UserPool(stack, 'Pool'); + }); + + test('create User Pool Group', () => { + // GIVEN + const role = new Role(stack, 'Role', { + assumedBy: new ServicePrincipal('service.amazonaws.com'), + }); + + // WHEN + new UserPoolGroup(stack, 'UserPoolGroup', { + userPool, + description: 'test description', + groupName: 'test-group-name', + precedence: 1, + role, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolGroup', { + UserPoolId: stack.resolve(userPool.userPoolId), + Description: 'test description', + GroupName: 'test-group-name', + Precedence: 1, + RoleArn: stack.resolve(role.roleArn), + }); + }); + + test('create User Pool Group using addGroup method', () => { + // WHEN + userPool.addGroup('Group', {}); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolGroup', { + UserPoolId: stack.resolve(userPool.userPoolId), + }); + }); + + test('throws when description length is invalid', () => { + expect(() => new UserPoolGroup(stack, 'UserPoolGroup', { + userPool, + description: 'a'.repeat(2049), + })).toThrow('`description` must be between 0 and 2048 characters. Received: 2049 characters'); + }); + + test.each([-1, 2 ** 31])('throws when precedence is invalid, precedence: %s', (precedence) => { + expect(() => new UserPoolGroup(stack, 'UserPoolGroup', { + userPool, + precedence, + })).toThrow(`\`precedence\` must be between 0 and 2^31-1. Received: ${precedence}`); + }); + + test.each(['', 'a'.repeat(149), 'include space name'])('throws when groupName is invalid, groupName: %s', (groupName) => { + expect(() => new UserPoolGroup(stack, 'UserPoolGroup', { + userPool, + groupName, + })).toThrow('\`groupName\` must be between 1 and 128 characters and can include letters, numbers, and symbols.'); + }); + +}); diff --git a/packages/aws-cdk-lib/awslint.json b/packages/aws-cdk-lib/awslint.json index 53028a08f7c84..d85aaef4834d9 100644 --- a/packages/aws-cdk-lib/awslint.json +++ b/packages/aws-cdk-lib/awslint.json @@ -89,6 +89,7 @@ "resource-interface-extends-resource:aws-cdk-lib.aws_codedeploy.ILambdaDeploymentConfig", "resource-interface-extends-resource:aws-cdk-lib.aws_codedeploy.IServerDeploymentConfig", "props-physical-name:aws-cdk-lib.aws_cognito.UserPoolDomainProps", + "props-physical-name:aws-cdk-lib.aws_cognito.UserPoolGroupProps", "props-physical-name:aws-cdk-lib.aws_cognito.UserPoolIdentityProviderAmazonProps", "props-physical-name:aws-cdk-lib.aws_cognito.UserPoolIdentityProviderAppleProps", "props-physical-name:aws-cdk-lib.aws_cognito.UserPoolIdentityProviderFacebookProps",