diff --git a/packages/@aws-cdk/aws-iam/README.md b/packages/@aws-cdk/aws-iam/README.md index 8cf5978aaa7c6..b54a0bff34fc4 100644 --- a/packages/@aws-cdk/aws-iam/README.md +++ b/packages/@aws-cdk/aws-iam/README.md @@ -433,6 +433,18 @@ const user = User.fromUserAttributes(stack, 'MyImportedUserByAttributes', { }); ``` +To add a user to a group (both for a new and imported user/group): + +```ts +const user = new User(this, 'MyUser'); // or User.fromUserName(stack, 'User', 'johnsmith'); +const group = new Group(this, 'MyGroup'); // or Group.fromGroupArn(stack, 'Group', 'arn:aws:iam::account-id:group/group-name'); + +user.addToGroup(group); +// or +group.addUser(user); +``` + + ## Features * Policy name uniqueness is enforced. If two policies by the same name are attached to the same diff --git a/packages/@aws-cdk/aws-iam/lib/user.ts b/packages/@aws-cdk/aws-iam/lib/user.ts index 5c8f6418a9bb8..4874e84f791df 100644 --- a/packages/@aws-cdk/aws-iam/lib/user.ts +++ b/packages/@aws-cdk/aws-iam/lib/user.ts @@ -1,7 +1,7 @@ import { Arn, Aws, Lazy, Resource, SecretValue, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IGroup } from './group'; -import { CfnUser } from './iam.generated'; +import { CfnUser, CfnUserToGroupAddition } from './iam.generated'; import { IIdentity } from './identity-base'; import { IManagedPolicy } from './managed-policy'; import { Policy } from './policy'; @@ -181,6 +181,7 @@ export class User extends Resource implements IIdentity, IUser { public readonly policyFragment: PrincipalPolicyFragment = new ArnPrincipal(attrs.userArn).policyFragment; private readonly attachedPolicies = new AttachedPolicies(); private defaultPolicy?: Policy; + private groupId = 0; public addToPolicy(statement: PolicyStatement): boolean { return this.addToPrincipalPolicy(statement).statementAdded; @@ -195,8 +196,12 @@ export class User extends Resource implements IIdentity, IUser { return { statementAdded: true, policyDependable: this.defaultPolicy }; } - public addToGroup(_group: IGroup): void { - throw new Error('Cannot add imported User to Group'); + public addToGroup(group: IGroup): void { + new CfnUserToGroupAddition(Stack.of(group), `${this.userName}Group${this.groupId}`, { + groupName: group.groupName, + users: [this.userName], + }); + this.groupId += 1; } public attachInlinePolicy(policy: Policy): void { @@ -229,7 +234,7 @@ export class User extends Resource implements IIdentity, IUser { public readonly userArn: string; /** - * Returns the permissions boundary attached to this user + * Returns the permissions boundary attached to this user */ public readonly permissionsBoundary?: IManagedPolicy; diff --git a/packages/@aws-cdk/aws-iam/test/user.test.ts b/packages/@aws-cdk/aws-iam/test/user.test.ts index af30005bec513..5cc42ae015619 100644 --- a/packages/@aws-cdk/aws-iam/test/user.test.ts +++ b/packages/@aws-cdk/aws-iam/test/user.test.ts @@ -1,6 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { App, SecretValue, Stack } from '@aws-cdk/core'; -import { ManagedPolicy, Policy, PolicyStatement, User } from '../lib'; +import { Group, ManagedPolicy, Policy, PolicyStatement, User } from '../lib'; describe('IAM user', () => { test('default user', () => { @@ -177,4 +177,35 @@ describe('IAM user', () => { }, }); }); + + test('addToGroup for imported user', () => { + // GIVEN + const stack = new Stack(); + const user = User.fromUserName(stack, 'ImportedUser', 'john'); + const group = new Group(stack, 'Group'); + const otherGroup = new Group(stack, 'OtherGroup'); + + // WHEN + user.addToGroup(group); + otherGroup.addUser(user); + + // THEN + expect(stack).toHaveResource('AWS::IAM::UserToGroupAddition', { + GroupName: { + Ref: 'GroupC77FDACD', + }, + Users: [ + 'john', + ], + }); + + expect(stack).toHaveResource('AWS::IAM::UserToGroupAddition', { + GroupName: { + Ref: 'OtherGroup85E5C653', + }, + Users: [ + 'john', + ], + }); + }); });