Skip to content

Commit

Permalink
chore(storage): update s3 control model (#13705)
Browse files Browse the repository at this point in the history
* chore(storage): update s3 control model

* fix: move permission validation to custom client

* chore: formatting code
  • Loading branch information
AllanZhengYP authored Aug 13, 2024
1 parent 7faa74e commit 0d31f3b
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ const listCallerAccessGrantsHappyCaseSingleGrant: ApiFunctionalTestCase<
<ListCallerAccessGrantsResult>
<NextToken>${MOCK_NEXT_TOKEN}</NextToken>
<CallerAccessGrantsList>
<AccessGrantsInstance>
<AccessGrant>
<ApplicationArn>${MOCK_APP_ARN}</ApplicationArn>
<GrantScope>${MOCK_GRANT_SCOPE}</GrantScope>
<Permission>${MOCK_PERMISSION}</Permission>
</AccessGrantsInstance>
</AccessGrant>
</CallerAccessGrantsList>
</ListCallerAccessGrantsResult>
`,
Expand Down Expand Up @@ -103,16 +103,16 @@ const listCallerAccessGrantsHappyCaseMultipleGrants: ApiFunctionalTestCase<
<ListCallerAccessGrantsResult>
<NextToken>${MOCK_NEXT_TOKEN}</NextToken>
<CallerAccessGrantsList>
<AccessGrantsInstance>
<AccessGrant>
<ApplicationArn>${MOCK_APP_ARN}</ApplicationArn>
<GrantScope>${MOCK_GRANT_SCOPE}</GrantScope>
<Permission>${MOCK_PERMISSION}</Permission>
</AccessGrantsInstance>
<AccessGrantsInstance>
</AccessGrant>
<AccessGrant>
<ApplicationArn>${MOCK_APP_ARN}</ApplicationArn>
<GrantScope>${MOCK_GRANT_SCOPE}</GrantScope>
<Permission>${MOCK_PERMISSION}</Permission>
</AccessGrantsInstance>
</AccessGrant>
</CallerAccessGrantsList>
</ListCallerAccessGrantsResult>
`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
parseXmlError,
s3TransferHandler,
} from '../utils';
import { createStringEnumDeserializer } from '../utils/deserializeHelpers';

import type {
ListCallerAccessGrantsCommandInput,
Expand Down Expand Up @@ -71,10 +72,7 @@ const listCallerAccessGrantsDeserializer = async (
CallerAccessGrantsList: [
'CallerAccessGrantsList',
value =>
emptyArrayGuard(
value.AccessGrantsInstance,
deserializeAccessGrantsList,
),
emptyArrayGuard(value.AccessGrant, deserializeAccessGrantsList),
],
NextToken: 'NextToken',
});
Expand All @@ -93,7 +91,13 @@ const deserializeCallerAccessGrant = (output: any) =>
map(output, {
ApplicationArn: 'ApplicationArn',
GrantScope: 'GrantScope',
Permission: 'Permission',
Permission: [
'Permission',
createStringEnumDeserializer(
['READ', 'READWRITE', 'WRITE'] as const,
'Permission',
),
],
});

export const listCallerAccessGrants = composeServiceApi(
Expand Down
157 changes: 80 additions & 77 deletions packages/storage/src/providers/s3/utils/client/s3control/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,80 +26,6 @@ declare const S3PrefixType: {
readonly Object: 'Object';
};

/**
* @public
*/
export type Permission = (typeof Permission)[keyof typeof Permission];

/**
* @public
*/
export type Privilege = (typeof Privilege)[keyof typeof Privilege];

/**
* @public
*/
export type S3PrefixType = (typeof S3PrefixType)[keyof typeof S3PrefixType];

/**
* @public
*
* The input for {@link ListCallerAccessGrantsCommand}.
*/
export type ListCallerAccessGrantsCommandInput = ListCallerAccessGrantsRequest;

/**
* @public
*
* The output of {@link ListCallerAccessGrantsCommand}.
*/
export interface ListCallerAccessGrantsCommandOutput
extends ListCallerAccessGrantsResult,
__MetadataBearer {}

/**
* @public
*/
export interface ListCallerAccessGrantsRequest {
AccountId?: string;
GrantScope?: string;
NextToken?: string;
MaxResults?: number;
}

/**
* @public
*/
export interface ListCallerAccessGrantsEntry {
Permission?: Permission | string;
GrantScope?: string;
ApplicationArn?: string;
}

/**
* @public
*/
export interface ListCallerAccessGrantsResult {
NextToken?: string;
CallerAccessGrantsList?: ListCallerAccessGrantsEntry[];
}

/**
* @public
*
* The input for {@link GetDataAccessCommand}.
*/
export type GetDataAccessCommandInput = GetDataAccessRequest;

/**
* @public
*
* The output of {@link GetDataAccessCommand}.
*/
export interface GetDataAccessCommandOutput
extends GetDataAccessResult,
__MetadataBearer {}

/**
* <p>The Amazon Web Services Security Token Service temporary credential that S3 Access Grants vends to grantees and client applications. </p>
* @public
Expand All @@ -110,26 +36,50 @@ export interface Credentials {
* @public
*/
AccessKeyId?: string;

/**
* <p>The secret access key of the Amazon Web Services STS temporary credential that S3 Access Grants vends to grantees and client applications. </p>
* @public
*/
SecretAccessKey?: string;

/**
* <p>The Amazon Web Services STS temporary credential that S3 Access Grants vends to grantees and client applications. </p>
* @public
* @public
*
* The input for {@link ListCallerAccessGrantsCommand}.
* @public
*
* The input for {@link ListCallerAccessGrantsCommand}.
*/
SessionToken?: string;

/**
* <p>The expiration date and time of the temporary credential that S3 Access Grants vends to grantees and client applications. </p>
* @public
* @public
*
* The output of {@link ListCallerAccessGrantsCommand}.
* @public
*
* The output of {@link ListCallerAccessGrantsCommand}.
*/
Expiration?: Date;
}

/**
* @public
*
* The input for {@link GetDataAccessCommand}.
*/
export type GetDataAccessCommandInput = GetDataAccessRequest;
/**
* @public
*
* The output of {@link GetDataAccessCommand}.
*/
export interface GetDataAccessCommandOutput
extends GetDataAccessResult,
__MetadataBearer {}

/**
* @public
*/
Expand Down Expand Up @@ -211,3 +161,56 @@ export interface GetDataAccessResult {
*/
MatchedGrantTarget?: string;
}

/**
* @public
*
* The input for {@link ListCallerAccessGrantsCommand}.
*/
export type ListCallerAccessGrantsCommandInput = ListCallerAccessGrantsRequest;
/**
* @public
*
* The output of {@link ListCallerAccessGrantsCommand}.
*/
export interface ListCallerAccessGrantsCommandOutput
extends ListCallerAccessGrantsResult,
__MetadataBearer {}
/**
* @public
*/
export interface ListCallerAccessGrantsEntry {
Permission?: Permission;
GrantScope?: string;
ApplicationArn?: string;
}
/**
* @public
*/
export interface ListCallerAccessGrantsRequest {
AccountId?: string;
GrantScope?: string;
NextToken?: string;
MaxResults?: number;
}
/**
* @public
*/
export interface ListCallerAccessGrantsResult {
NextToken?: string;
CallerAccessGrantsList?: ListCallerAccessGrantsEntry[];
}
/**
* @public
*/
export type Permission = (typeof Permission)[keyof typeof Permission];
/**
* @public
*/
export type Privilege = (typeof Privilege)[keyof typeof Privilege];
/**
* @public
*/
export type S3PrefixType = (typeof S3PrefixType)[keyof typeof S3PrefixType];

export {};
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,47 @@ export const deserializeTimestamp = (value: string): Date | undefined => {
return value ? new Date(value) : undefined;
};

/**
* Create a function deserializing a string to an enum value. If the string is not a valid enum value, it throws a
* StorageError.
*
* @example
* ```typescript
* const deserializeStringEnum = createStringEnumDeserializer(['a', 'b', 'c'] as const, 'FieldName');
* const deserializedArray = ['a', 'b', 'c'].map(deserializeStringEnum);
* // deserializedArray = ['a', 'b', 'c']
*
* const invalidValue = deserializeStringEnum('d');
* // Throws InvalidFieldName: Invalid FieldName: d
* ```
*
* @internal
*/
export const createStringEnumDeserializer = <T extends readonly string[]>(
enumValues: T,
fieldName: string,
) => {
const deserializeStringEnum = (
value: any,
): T extends (infer E)[] ? E : never => {
const parsedEnumValue = value
? (enumValues.find(enumValue => enumValue === value) as any)
: undefined;
if (!parsedEnumValue) {
throw new StorageError({
name: `Invalid${fieldName}`,
message: `Invalid ${fieldName}: ${value}`,
recoverySuggestion:
'This is likely to be a bug. Please reach out to library authors.',
});
}

return parsedEnumValue;
};

return deserializeStringEnum;
};

/**
* Function that makes sure the deserializer receives non-empty array.
*
Expand Down
17 changes: 2 additions & 15 deletions packages/storage/src/storageBrowser/apis/listCallerAccessGrants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { CredentialsProviderOptions } from '@aws-amplify/core/internals/aws-clie

import { logger } from '../../utils';
import { listCallerAccessGrants as listCallerAccessGrantsClient } from '../../providers/s3/utils/client/s3control';
import { LocationAccess, LocationType, Permission } from '../types';
import { LocationAccess, LocationType } from '../types';
import { StorageError } from '../../errors/StorageError';
import { getStorageUserAgentValue } from '../../providers/s3/utils/userAgent';

Expand Down Expand Up @@ -53,13 +53,11 @@ export const listCallerAccessGrants = async (

const accessGrants: LocationAccess[] =
CallerAccessGrantsList?.map(grant => {
// These values are correct from service mostly, but we add assertions to make TSC happy.
assertPermission(grant.Permission);
assertGrantScope(grant.GrantScope);

return {
scope: grant.GrantScope,
permission: grant.Permission,
permission: grant.Permission!,
type: parseGrantType(grant.GrantScope!),
};
}) ?? [];
Expand All @@ -86,17 +84,6 @@ const parseGrantType = (grantScope: string): LocationType => {
}
};

function assertPermission(
permissionValue: string | undefined,
): asserts permissionValue is Permission {
if (!['READ', 'READWRITE', 'WRITE'].includes(permissionValue ?? '')) {
throw new StorageError({
name: 'InvalidPermission',
message: `Invalid permission: ${permissionValue}`,
});
}
}

function assertGrantScope(value: unknown): asserts value is string {
if (typeof value !== 'string' || !value.startsWith('s3://')) {
throw new StorageError({
Expand Down

0 comments on commit 0d31f3b

Please sign in to comment.