Skip to content

Commit

Permalink
chore: merge changes from storage-browser/main into storage-browser/i…
Browse files Browse the repository at this point in the history
…ntegrity (#13631)
  • Loading branch information
AllanZhengYP authored Jul 23, 2024
2 parents 06c093b + c5464ac commit e0efd35
Show file tree
Hide file tree
Showing 32 changed files with 810 additions and 77 deletions.
30 changes: 15 additions & 15 deletions packages/aws-amplify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@
"name": "[Analytics] record (Pinpoint)",
"path": "./dist/esm/analytics/index.mjs",
"import": "{ record }",
"limit": "17.09 kB"
"limit": "17.14 kB"
},
{
"name": "[Analytics] record (Kinesis)",
Expand All @@ -311,13 +311,13 @@
"name": "[Analytics] record (Personalize)",
"path": "./dist/esm/analytics/personalize/index.mjs",
"import": "{ record }",
"limit": "49.50 kB"
"limit": "49.53 kB"
},
{
"name": "[Analytics] identifyUser (Pinpoint)",
"path": "./dist/esm/analytics/index.mjs",
"import": "{ identifyUser }",
"limit": "15.59 kB"
"limit": "15.64 kB"
},
{
"name": "[Analytics] enable",
Expand Down Expand Up @@ -353,13 +353,13 @@
"name": "[Auth] resetPassword (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ resetPassword }",
"limit": "12.44 kB"
"limit": "12.48 kB"
},
{
"name": "[Auth] confirmResetPassword (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ confirmResetPassword }",
"limit": "12.39 kB"
"limit": "12.42 kB"
},
{
"name": "[Auth] signIn (Cognito)",
Expand All @@ -371,7 +371,7 @@
"name": "[Auth] resendSignUpCode (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ resendSignUpCode }",
"limit": "12.40 kB"
"limit": "12.44 kB"
},
{
"name": "[Auth] confirmSignUp (Cognito)",
Expand All @@ -383,31 +383,31 @@
"name": "[Auth] confirmSignIn (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ confirmSignIn }",
"limit": "28.27 kB"
"limit": "28.32 kB"
},
{
"name": "[Auth] updateMFAPreference (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ updateMFAPreference }",
"limit": "11.74 kB"
"limit": "11.78 kB"
},
{
"name": "[Auth] fetchMFAPreference (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ fetchMFAPreference }",
"limit": "11.78 kB"
"limit": "11.81 kB"
},
{
"name": "[Auth] verifyTOTPSetup (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ verifyTOTPSetup }",
"limit": "12.6 kB"
"limit": "12.7 kB"
},
{
"name": "[Auth] updatePassword (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ updatePassword }",
"limit": "12.63 kB"
"limit": "12.67 kB"
},
{
"name": "[Auth] setUpTOTP (Cognito)",
Expand All @@ -431,7 +431,7 @@
"name": "[Auth] confirmUserAttribute (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ confirmUserAttribute }",
"limit": "12.61 kB"
"limit": "12.64 kB"
},
{
"name": "[Auth] signInWithRedirect (Cognito)",
Expand All @@ -443,19 +443,19 @@
"name": "[Auth] fetchUserAttributes (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ fetchUserAttributes }",
"limit": "11.69 kB"
"limit": "11.72 kB"
},
{
"name": "[Auth] Basic Auth Flow (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ signIn, signOut, fetchAuthSession, confirmSignIn }",
"limit": "30.06 kB"
"limit": "30.11 kB"
},
{
"name": "[Auth] OAuth Auth Flow (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ signInWithRedirect, signOut, fetchAuthSession }",
"limit": "21.47 kB"
"limit": "21.52 kB"
},
{
"name": "[Storage] copy (S3)",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/Platform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ export enum StorageAction {
Remove = '5',
GetProperties = '6',
GetUrl = '7',
GetDataAccess = '8',
ListCallerAccessGrants = '9',
}

interface ActionMap {
Expand Down
6 changes: 3 additions & 3 deletions packages/interactions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,19 @@
"name": "Interactions (default to Lex v2)",
"path": "./dist/esm/index.mjs",
"import": "{ Interactions }",
"limit": "52.52 kB"
"limit": "52.55 kB"
},
{
"name": "Interactions (Lex v2)",
"path": "./dist/esm/lex-v2/index.mjs",
"import": "{ Interactions }",
"limit": "52.52 kB"
"limit": "52.55 kB"
},
{
"name": "Interactions (Lex v1)",
"path": "./dist/esm/lex-v1/index.mjs",
"import": "{ Interactions }",
"limit": "47.33 kB"
"limit": "47.37 kB"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import {
StorageValidationErrorCode,
validationErrorMap,
} from '../../../../../src/errors/types/validation';
import {
CallbackPathStorageInput,
DeprecatedStorageInput,
} from '../../../../../src/providers/s3/utils/resolveS3ConfigAndInput';
import { INVALID_STORAGE_INPUT } from '../../../../../src/errors/constants';

jest.mock('@aws-amplify/core', () => ({
ConsoleLogger: jest.fn(),
Expand Down Expand Up @@ -76,13 +81,11 @@ describe('resolveS3ConfigAndInput', () => {
}
});

it('should throw if identityId is not available', async () => {
it('should not throw if identityId is not available', async () => {
mockFetchAuthSession.mockResolvedValueOnce({
credentials,
});
await expect(resolveS3ConfigAndInput(Amplify, {})).rejects.toMatchObject(
validationErrorMap[StorageValidationErrorCode.NoIdentityId],
);
expect(async () => resolveS3ConfigAndInput(Amplify, {})).not.toThrow();
});

it('should resolve bucket from S3 config', async () => {
Expand Down Expand Up @@ -179,7 +182,7 @@ describe('resolveS3ConfigAndInput', () => {
it('should resolve prefix with given access level', async () => {
mockDefaultResolvePrefix.mockResolvedValueOnce('prefix');
const { keyPrefix } = await resolveS3ConfigAndInput(Amplify, {
accessLevel: 'someLevel' as any,
options: { accessLevel: 'someLevel' as any },
});
expect(mockDefaultResolvePrefix).toHaveBeenCalledWith({
accessLevel: 'someLevel',
Expand Down Expand Up @@ -214,4 +217,91 @@ describe('resolveS3ConfigAndInput', () => {
});
expect(keyPrefix).toEqual('prefix');
});

describe('with locationCredentialsProvider', () => {
const mockLocationCredentialsProvider = jest
.fn()
.mockReturnValue({ credentials });
it('should resolve credentials without Amplify singleton', async () => {
mockGetConfig.mockReturnValue({
Storage: {
S3: {
bucket,
region,
},
},
});
const { s3Config } = await resolveS3ConfigAndInput(Amplify, {
options: {
locationCredentialsProvider: mockLocationCredentialsProvider,
},
});

if (typeof s3Config.credentials === 'function') {
const result = await s3Config.credentials();
expect(mockLocationCredentialsProvider).toHaveBeenCalled();
expect(result).toEqual(credentials);
} else {
throw new Error('Expect credentials to be a function');
}
});

it('should not throw when path is pass as a string', async () => {
const { s3Config } = await resolveS3ConfigAndInput(Amplify, {
path: 'my-path',
options: {
locationCredentialsProvider: mockLocationCredentialsProvider,
},
});

if (typeof s3Config.credentials === 'function') {
const result = await s3Config.credentials();
expect(mockLocationCredentialsProvider).toHaveBeenCalled();
expect(result).toEqual(credentials);
} else {
throw new Error('Expect credentials to be a function');
}
});

describe('with deprecated or callback paths as inputs', () => {
const key = 'mock-value';
const prefix = 'mock-value';
const path = () => 'path';
const deprecatedInputs: DeprecatedStorageInput[] = [
{ prefix },
{ key },
{
source: { key },
destination: { key },
},
];
const callbackPathInputs: CallbackPathStorageInput[] = [
{ path },
{
destination: { path },
source: { path },
},
];

const testCases = [...deprecatedInputs, ...callbackPathInputs];

it.each(testCases)('should throw when input is %s', async input => {
const { s3Config } = await resolveS3ConfigAndInput(Amplify, {
...input,
options: {
locationCredentialsProvider: mockLocationCredentialsProvider,
},
});
if (typeof s3Config.credentials === 'function') {
await expect(s3Config.credentials()).rejects.toThrow(
expect.objectContaining({
name: INVALID_STORAGE_INPUT,
}),
);
} else {
throw new Error('Expect credentials to be a function');
}
});
});
});
});
116 changes: 116 additions & 0 deletions packages/storage/__tests__/storageBrowser/apis/getDataAccess.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { getDataAccess } from '../../../src/storageBrowser/apis/getDataAccess';
import { getDataAccess as getDataAccessClient } from '../../../src/providers/s3/utils/client/s3control';
import { GetDataAccessInput } from '../../../src/storageBrowser/apis/types';

jest.mock('../../../src/providers/s3/utils/client/s3control');

const MOCK_ACCOUNT_ID = 'accountId';
const MOCK_REGION = 'us-east-2';
const MOCK_ACCESS_ID = 'accessId';
const MOCK_SECRET_ACCESS_KEY = 'secretAccessKey';
const MOCK_SESSION_TOKEN = 'sessionToken';
const MOCK_EXPIRATION = '2013-09-17T18:07:53.000Z';
const MOCK_EXPIRATION_DATE = new Date(MOCK_EXPIRATION);
const MOCK_SCOPE = 's3://mybucket/files/*';
const MOCK_CREDENTIALS = {
credentials: {
accessKeyId: MOCK_ACCESS_ID,
secretAccessKey: MOCK_SECRET_ACCESS_KEY,
sessionToken: MOCK_SESSION_TOKEN,
expiration: MOCK_EXPIRATION_DATE,
},
};
const MOCK_ACCESS_CREDENTIALS = {
AccessKeyId: MOCK_ACCESS_ID,
SecretAccessKey: MOCK_SECRET_ACCESS_KEY,
SessionToken: MOCK_SESSION_TOKEN,
Expiration: MOCK_EXPIRATION_DATE,
};
const MOCK_CREDENTIAL_PROVIDER = async () => MOCK_CREDENTIALS;

const sharedGetDataAccessParams: GetDataAccessInput = {
accountId: MOCK_ACCOUNT_ID,
credentialsProvider: MOCK_CREDENTIAL_PROVIDER,
durationSeconds: 900,
permission: 'READWRITE',
region: MOCK_REGION,
scope: MOCK_SCOPE,
};

describe('getDataAccess', () => {
const getDataAccessClientMock = getDataAccessClient as jest.Mock;

beforeEach(() => {
jest.clearAllMocks();

getDataAccessClientMock.mockResolvedValue({
Credentials: MOCK_ACCESS_CREDENTIALS,
MatchedGrantTarget: MOCK_SCOPE,
});
});

it('should invoke the getDataAccess client correctly', async () => {
const result = await getDataAccess(sharedGetDataAccessParams);

expect(getDataAccessClientMock).toHaveBeenCalledWith(
expect.objectContaining({
credentials: MOCK_CREDENTIALS.credentials,
region: MOCK_REGION,
userAgentValue: expect.stringContaining('storage/8'),
}),
expect.objectContaining({
AccountId: MOCK_ACCOUNT_ID,
Target: MOCK_SCOPE,
Permission: 'READWRITE',
TargetType: undefined,
DurationSeconds: 900,
}),
);

expect(result.credentials).toEqual(MOCK_CREDENTIALS.credentials);
expect(result.scope).toEqual(MOCK_SCOPE);
});

it('should throw an error if the service does not return credentials', async () => {
expect.assertions(1);

getDataAccessClientMock.mockResolvedValue({
Credentials: undefined,
MatchedGrantTarget: MOCK_SCOPE,
});

expect(getDataAccess(sharedGetDataAccessParams)).rejects.toThrow(
'Service did not return credentials.',
);
});

it('should set the correct target type when accessing an object', async () => {
const MOCK_OBJECT_SCOPE = 's3://mybucket/files/file.md';

getDataAccessClientMock.mockResolvedValue({
Credentials: MOCK_ACCESS_CREDENTIALS,
MatchedGrantTarget: MOCK_OBJECT_SCOPE,
});

const result = await getDataAccess({
...sharedGetDataAccessParams,
scope: MOCK_OBJECT_SCOPE,
});

expect(getDataAccessClientMock).toHaveBeenCalledWith(
expect.any(Object),
expect.objectContaining({
AccountId: MOCK_ACCOUNT_ID,
Target: MOCK_OBJECT_SCOPE,
Permission: 'READWRITE',
TargetType: 'Object',
DurationSeconds: 900,
}),
);

expect(result.scope).toEqual(MOCK_OBJECT_SCOPE);
});
});
Loading

0 comments on commit e0efd35

Please sign in to comment.