Skip to content

Commit f999897

Browse files
authored
Enable user pool group access to storage and modify owner access syntax (#1104)
1 parent bdbf6e8 commit f999897

File tree

14 files changed

+165
-100
lines changed

14 files changed

+165
-100
lines changed

.changeset/spicy-bulldogs-itch.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@aws-amplify/backend-storage': minor
3+
'@aws-amplify/backend-auth': minor
4+
---
5+
6+
Enable auth group access to storage and change syntax for specifying owner-based access

packages/backend-auth/API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export type AuthLoginWithFactoryProps = Omit<AuthProps['loginWith'], 'externalPr
4646
};
4747

4848
// @public (undocumented)
49-
export type BackendAuth = ResourceProvider<AuthResources> & ResourceAccessAcceptorFactory<AuthRoleName>;
49+
export type BackendAuth = ResourceProvider<AuthResources> & ResourceAccessAcceptorFactory<AuthRoleName | string>;
5050

5151
// @public
5252
export const defineAuth: (props: AmplifyAuthProps) => ConstructFactory<BackendAuth>;

packages/backend-auth/src/factory.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { UserPool, UserPoolOperation } from 'aws-cdk-lib/aws-cognito';
2323
import { AmplifyUserError } from '@aws-amplify/platform-core';
2424

2525
export type BackendAuth = ResourceProvider<AuthResources> &
26-
ResourceAccessAcceptorFactory<AuthRoleName>;
26+
ResourceAccessAcceptorFactory<AuthRoleName | string>;
2727

2828
export type AmplifyAuthProps = Expand<
2929
Omit<AuthProps, 'outputStorageStrategy' | 'loginWith'> & {
@@ -126,12 +126,24 @@ class AmplifyAuthGenerator implements ConstructContainerEntryGenerator {
126126

127127
const authConstructMixin: BackendAuth = {
128128
...authConstruct,
129+
/**
130+
* Returns a resourceAccessAcceptor for the given role
131+
* @param roleIdentifier Either the auth or unauth role name or the name of a UserPool group
132+
*/
129133
getResourceAccessAcceptor: (
130-
roleName: AuthRoleName
134+
roleIdentifier: AuthRoleName | string
131135
): ResourceAccessAcceptor => ({
132-
identifier: `${roleName}ResourceAccessAcceptor`,
136+
identifier: `${roleIdentifier}ResourceAccessAcceptor`,
133137
acceptResourceAccess: (policy: Policy) => {
134-
const role = authConstruct.resources[roleName];
138+
const role = roleNameIsAuthRoleName(roleIdentifier)
139+
? authConstruct.resources[roleIdentifier]
140+
: authConstruct.resources.groups?.[roleIdentifier]?.role;
141+
if (!role) {
142+
throw new AmplifyUserError('InvalidResourceAccessConfig', {
143+
message: `No auth IAM role found for "${roleIdentifier}".`,
144+
resolution: `If you are trying to configure UserPool group access, ensure that the group name is specified correctly.`,
145+
});
146+
}
135147
policy.attachToRole(role);
136148
},
137149
}),
@@ -140,6 +152,13 @@ class AmplifyAuthGenerator implements ConstructContainerEntryGenerator {
140152
};
141153
}
142154

155+
const roleNameIsAuthRoleName = (roleName: string): roleName is AuthRoleName => {
156+
return (
157+
roleName === 'authenticatedUserIamRole' ||
158+
roleName === 'unauthenticatedUserIamRole'
159+
);
160+
};
161+
143162
/**
144163
* Provide the settings that will be used for authentication.
145164
*/

packages/backend-storage/API.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,23 @@ export type AmplifyStorageTriggerEvent = 'onDelete' | 'onUpload';
3333
// @public
3434
export const defineStorage: (props: AmplifyStorageFactoryProps) => ConstructFactory<ResourceProvider<StorageResources>>;
3535

36+
// @public
37+
export type EntityId = 'identity';
38+
3639
// @public
3740
export type StorageAccessBuilder = {
3841
authenticated: StorageActionBuilder;
3942
guest: StorageActionBuilder;
40-
owner: StorageActionBuilder;
43+
group: (groupName: string) => StorageActionBuilder;
44+
entity: (entityId: EntityId) => StorageActionBuilder;
4145
resource: (other: ConstructFactory<ResourceProvider & ResourceAccessAcceptorFactory>) => StorageActionBuilder;
4246
};
4347

4448
// @public (undocumented)
4549
export type StorageAccessDefinition = {
4650
getResourceAccessAcceptor: (getInstanceProps: ConstructFactoryGetInstanceProps) => ResourceAccessAcceptor;
4751
actions: StorageAction[];
48-
ownerPlaceholderSubstitution: string;
52+
idSubstitution: string;
4953
};
5054

5155
// @public (undocumented)

packages/backend-storage/src/access_builder.test.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ import {
1010

1111
void describe('storageAccessBuilder', () => {
1212
const resourceAccessAcceptorMock = mock.fn();
13+
const groupAccessAcceptorMock = mock.fn();
1314

1415
const getResourceAccessAcceptorMock = mock.fn(
1516
// allows us to get proper typing on the mock args
1617
// eslint-disable-next-line @typescript-eslint/no-unused-vars
17-
(_: string) => resourceAccessAcceptorMock
18+
(roleName: string) =>
19+
roleName === 'testGroupName'
20+
? groupAccessAcceptorMock
21+
: resourceAccessAcceptorMock
1822
);
1923

2024
const getConstructFactoryMock = mock.fn(
@@ -50,7 +54,7 @@ void describe('storageAccessBuilder', () => {
5054
'write',
5155
'delete',
5256
]);
53-
assert.equal(accessDefinition.ownerPlaceholderSubstitution, '*');
57+
assert.equal(accessDefinition.idSubstitution, '*');
5458
assert.equal(
5559
accessDefinition.getResourceAccessAcceptor(stubGetInstanceProps),
5660
resourceAccessAcceptorMock
@@ -75,7 +79,7 @@ void describe('storageAccessBuilder', () => {
7579
'write',
7680
'delete',
7781
]);
78-
assert.equal(accessDefinition.ownerPlaceholderSubstitution, '*');
82+
assert.equal(accessDefinition.idSubstitution, '*');
7983
assert.equal(
8084
accessDefinition.getResourceAccessAcceptor(stubGetInstanceProps),
8185
resourceAccessAcceptorMock
@@ -89,19 +93,17 @@ void describe('storageAccessBuilder', () => {
8993
'unauthenticatedUserIamRole'
9094
);
9195
});
92-
void it('builds storage access definition for owner', () => {
93-
const accessDefinition = roleAccessBuilder.owner.to([
94-
'read',
95-
'write',
96-
'delete',
97-
]);
96+
void it('builds storage access definition for IdP identity', () => {
97+
const accessDefinition = roleAccessBuilder
98+
.entity('identity')
99+
.to(['read', 'write', 'delete']);
98100
assert.deepStrictEqual(accessDefinition.actions, [
99101
'read',
100102
'write',
101103
'delete',
102104
]);
103105
assert.equal(
104-
accessDefinition.ownerPlaceholderSubstitution,
106+
accessDefinition.idSubstitution,
105107
'${cognito-identity.amazonaws.com:sub}'
106108
);
107109
assert.equal(
@@ -133,10 +135,23 @@ void describe('storageAccessBuilder', () => {
133135
'write',
134136
'delete',
135137
]);
136-
assert.equal(accessDefinition.ownerPlaceholderSubstitution, '*');
138+
assert.equal(accessDefinition.idSubstitution, '*');
137139
assert.equal(
138140
accessDefinition.getResourceAccessAcceptor(stubGetInstanceProps),
139141
resourceAccessAcceptorMock
140142
);
141143
});
144+
145+
void it('builds storage access definition for user pool groups', () => {
146+
const accessDefinition = roleAccessBuilder
147+
.group('testGroupName')
148+
.to(['read', 'write']);
149+
150+
assert.deepStrictEqual(accessDefinition.actions, ['read', 'write']);
151+
assert.equal(accessDefinition.idSubstitution, '*');
152+
assert.equal(
153+
accessDefinition.getResourceAccessAcceptor(stubGetInstanceProps),
154+
groupAccessAcceptorMock
155+
);
156+
});
142157
});

packages/backend-storage/src/access_builder.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,37 @@ export const roleAccessBuilder: StorageAccessBuilder = {
1111
to: (actions) => ({
1212
getResourceAccessAcceptor: getAuthRoleResourceAccessAcceptor,
1313
actions,
14-
ownerPlaceholderSubstitution: '*',
14+
idSubstitution: '*',
1515
}),
1616
},
1717
guest: {
1818
to: (actions) => ({
1919
getResourceAccessAcceptor: getUnauthRoleResourceAccessAcceptor,
2020
actions,
21-
ownerPlaceholderSubstitution: '*',
21+
idSubstitution: '*',
2222
}),
2323
},
24-
owner: {
24+
group: (groupName) => ({
25+
to: (actions) => ({
26+
getResourceAccessAcceptor: (getInstanceProps) =>
27+
getUserRoleResourceAccessAcceptor(getInstanceProps, groupName),
28+
actions,
29+
idSubstitution: '*',
30+
}),
31+
}),
32+
entity: () => ({
2533
to: (actions) => ({
2634
getResourceAccessAcceptor: getAuthRoleResourceAccessAcceptor,
2735
actions,
28-
ownerPlaceholderSubstitution: '${cognito-identity.amazonaws.com:sub}',
36+
idSubstitution: '${cognito-identity.amazonaws.com:sub}',
2937
}),
30-
},
38+
}),
3139
resource: (other) => ({
3240
to: (actions) => ({
3341
getResourceAccessAcceptor: (getInstanceProps) =>
3442
other.getInstance(getInstanceProps).getResourceAccessAcceptor(),
3543
actions,
36-
ownerPlaceholderSubstitution: '*',
44+
idSubstitution: '*',
3745
}),
3846
}),
3947
};
@@ -56,19 +64,19 @@ const getUnauthRoleResourceAccessAcceptor = (
5664

5765
const getUserRoleResourceAccessAcceptor = (
5866
getInstanceProps: ConstructFactoryGetInstanceProps,
59-
roleName: AuthRoleName
67+
roleName: AuthRoleName | string
6068
) => {
6169
const resourceAccessAcceptor = getInstanceProps.constructContainer
6270
.getConstructFactory<
63-
ResourceProvider & ResourceAccessAcceptorFactory<AuthRoleName>
71+
ResourceProvider & ResourceAccessAcceptorFactory<AuthRoleName | string>
6472
>('AuthResources')
6573
?.getInstance(getInstanceProps)
6674
.getResourceAccessAcceptor(roleName);
6775
if (!resourceAccessAcceptor) {
6876
throw new Error(
69-
`Cannot specify ${
77+
`Cannot specify auth access for ${
7078
roleName as string
71-
} user policies without defining auth. See <defineAuth docs link> for more information.`
79+
} users without defining auth. See https://docs.amplify.aws/gen2/build-a-backend/auth/set-up-auth/ for more information.`
7280
);
7381
}
7482
return resourceAccessAcceptor;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const ownerPathPartToken = '{owner}';
1+
export const entityIdPathToken = '{entity_id}';

0 commit comments

Comments
 (0)