Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storage Browser Default Auth #13866

Open
wants to merge 11 commits into
base: storage-browser/integrity
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/aws-amplify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@
"name": "[Storage] uploadData (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ uploadData }",
"limit": "22.07 kB"
"limit": "22.08 kB"
}
]
}
53 changes: 53 additions & 0 deletions packages/core/__tests__/parseAmplifyOutputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,59 @@ describe('parseAmplifyOutputs tests', () => {

expect(() => parseAmplifyOutputs(amplifyOutputs)).toThrow();
});
it('should parse storage bucket with paths', () => {
const amplifyOutputs: AmplifyOutputs = {
version: '1.2',
storage: {
aws_region: 'us-west-2',
bucket_name: 'storage-bucket-test',
buckets: [
{
name: 'default-bucket',
bucket_name: 'storage-bucket-test',
aws_region: 'us-west-2',
paths: {
'other/*': {
guest: ['get', 'list'],
authenticated: ['get', 'list', 'write'],
},
'admin/*': {
groupsauditor: ['get', 'list'],
groupsadmin: ['get', 'list', 'write', 'delete'],
},
},
},
],
},
};

const result = parseAmplifyOutputs(amplifyOutputs);

expect(result).toEqual({
Storage: {
S3: {
bucket: 'storage-bucket-test',
region: 'us-west-2',
buckets: {
'default-bucket': {
bucketName: 'storage-bucket-test',
region: 'us-west-2',
paths: {
'other/*': {
guest: ['get', 'list'],
authenticated: ['get', 'list', 'write'],
},
'admin/*': {
groupsauditor: ['get', 'list'],
groupsadmin: ['get', 'list', 'write', 'delete'],
},
},
},
},
},
},
});
});
});

describe('analytics tests', () => {
Expand Down
27 changes: 15 additions & 12 deletions packages/core/src/parseAmplifyOutputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,18 +342,21 @@ function createBucketInfoMap(
): Record<string, BucketInfo> {
const mappedBuckets: Record<string, BucketInfo> = {};

buckets.forEach(({ name, bucket_name: bucketName, aws_region: region }) => {
if (name in mappedBuckets) {
throw new Error(
`Duplicate friendly name found: ${name}. Name must be unique.`,
);
}

mappedBuckets[name] = {
bucketName,
region,
};
});
buckets.forEach(
({ name, bucket_name: bucketName, aws_region: region, paths }) => {
if (name in mappedBuckets) {
throw new Error(
`Duplicate friendly name found: ${name}. Name must be unique.`,
);
}

mappedBuckets[name] = {
bucketName,
region,
paths,
};
},
);

return mappedBuckets;
}
2 changes: 2 additions & 0 deletions packages/core/src/singleton/AmplifyOutputs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export interface AmplifyOutputsStorageBucketProperties {
bucket_name: string;
/** Region for the bucket */
aws_region: string;
/** Paths to object with access permissions */
paths?: Record<string, Record<string, string[]>>;
}
export interface AmplifyOutputsStorageProperties {
/** Default region for Storage */
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/singleton/Storage/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface BucketInfo {
bucketName: string;
/** Region of the bucket */
region: string;
/** Paths to object with access permissions */
paths?: Record<string, Record<string, string[]>>;
}
export interface S3ProviderConfig {
S3: {
Expand Down
Copy link
Member Author

@ashika112 ashika112 Oct 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adapter will most likely be moved to UI

Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { Amplify, fetchAuthSession } from '@aws-amplify/core';

import { generateLocationsFromPaths } from '../../../src/internals/amplifyAuthConfigAdapter/generateLocationsFromPaths';
import { createAmplifyAuthConfigAdapter } from '../../../src/internals';

jest.mock('@aws-amplify/core', () => ({
ConsoleLogger: jest.fn(),
Amplify: {
getConfig: jest.fn(),
Auth: {
getConfig: jest.fn(),
fetchAuthSession: jest.fn(),
},
},
fetchAuthSession: jest.fn(),
}));
jest.mock(
'../../../src/internals/amplifyAuthConfigAdapter/generateLocationsFromPaths',
);

const credentials = {
accessKeyId: 'accessKeyId',
sessionToken: 'sessionToken',
secretAccessKey: 'secretAccessKey',
};
const identityId = 'identityId';

const mockGetConfig = jest.mocked(Amplify.getConfig);
const mockFetchAuthSession = fetchAuthSession as jest.Mock;
const mockGenerateLocationsFromPaths = generateLocationsFromPaths as jest.Mock;

describe('createAmplifyAuthConfigAdapter', () => {
beforeEach(() => {
jest.clearAllMocks();
});

mockGetConfig.mockReturnValue({
Storage: {
S3: {
bucket: 'bucket1',
region: 'region1',
buckets: {
'bucket-1': {
bucketName: 'bucket-1',
region: 'region1',
paths: {},
},
},
},
},
});
mockFetchAuthSession.mockResolvedValue({
credentials,
identityId,
tokens: {
accessToken: { payload: {} },
},
});

it('should return an AuthConfigAdapter with listLocations function', async () => {
const adapter = createAmplifyAuthConfigAdapter();
expect(adapter).toHaveProperty('listLocations');
const { listLocations } = adapter;
await listLocations();
expect(mockFetchAuthSession).toHaveBeenCalled();
});

it('should return empty locations when buckets are not defined', async () => {
mockGetConfig.mockReturnValue({ Storage: { S3: { buckets: undefined } } });

const adapter = createAmplifyAuthConfigAdapter();
const result = await adapter.listLocations();

expect(result).toEqual({ locations: [] });
});

it('should generate locations correctly when buckets are defined', async () => {
const mockBuckets = {
bucket1: {
bucketName: 'bucket1',
region: 'region1',
paths: {
'/path1': {
entityidentity: ['read', 'write'],
groupsadmin: ['read'],
},
},
},
};

mockGetConfig.mockReturnValue({
Storage: { S3: { buckets: mockBuckets } },
});
mockGenerateLocationsFromPaths.mockReturnValue([
{
type: 'PREFIX',
permission: ['read', 'write'],
scope: {
bucketName: 'bucket1',
path: '/path1',
},
},
]);

const adapter = createAmplifyAuthConfigAdapter();
const result = await adapter.listLocations();

expect(result).toEqual({
locations: [
{
type: 'PREFIX',
permission: ['read', 'write'],
scope: {
bucketName: 'bucket1',
path: '/path1',
},
},
],
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { generateLocationsFromPaths } from '../../../src/internals/amplifyAuthConfigAdapter/generateLocationsFromPaths';
import { BucketInfo } from '../../../src/providers/s3/types/options';

describe('generateLocationsFromPaths', () => {
const mockBuckets: Record<string, BucketInfo> = {
bucket1: {
bucketName: 'bucket1',
region: 'region1',
paths: {
'path1/*': {
guest: ['get', 'list'],
authenticated: ['get', 'list', 'write'],
},
'path2/*': {
groupsauditor: ['get', 'list'],
groupsadmin: ['get', 'list', 'write', 'delete'],
},
// eslint-disable-next-line no-template-curly-in-string
'profile-pictures/${cognito-identity.amazonaws.com:sub}/*': {
entityidentity: ['get', 'list', 'write', 'delete'],
},
},
},
bucket2: {
bucketName: 'bucket2',
region: 'region1',
paths: {
'path3/*': {
guest: ['read'],
},
},
},
};

it('should generate locations correctly when tokens are true', () => {
const result = generateLocationsFromPaths({
buckets: mockBuckets,
tokens: true,
identityId: '12345',
userGroup: 'admin',
});

expect(result).toEqual([
{
type: 'PREFIX',
permission: ['get', 'list', 'write'],
scope: {
bucketName: 'bucket1',
path: 'path1/*',
},
},
{
type: 'PREFIX',
permission: ['get', 'list', 'write', 'delete'],
scope: {
bucketName: 'bucket1',
path: 'path2/*',
},
},
{
type: 'PREFIX',
permission: ['get', 'list', 'write', 'delete'],
scope: {
bucketName: 'bucket1',
path: 'profile-pictures/12345/*',
},
},
]);
});

it('should generate locations correctly when tokens are true & bad userGroup', () => {
const result = generateLocationsFromPaths({
buckets: mockBuckets,
tokens: true,
identityId: '12345',
userGroup: 'editor',
});

expect(result).toEqual([
{
type: 'PREFIX',
permission: ['get', 'list', 'write'],
scope: {
bucketName: 'bucket1',
path: 'path1/*',
},
},
{
type: 'PREFIX',
permission: ['get', 'list', 'write', 'delete'],
scope: {
bucketName: 'bucket1',
path: 'profile-pictures/12345/*',
},
},
]);
});

it('should return empty array when paths are not defined', () => {
const result = generateLocationsFromPaths({
buckets: {
bucket1: {
bucketName: 'bucket1',
region: 'region1',
paths: undefined,
},
},
tokens: true,
identityId: '12345',
userGroup: 'admin',
});

expect(result).toEqual([]);
});

it('should generate locations correctly when tokens are false', () => {
const result = generateLocationsFromPaths({
buckets: mockBuckets,
tokens: false,
});

expect(result).toEqual([
{
type: 'PREFIX',
permission: ['get', 'list'],
scope: {
bucketName: 'bucket1',
path: 'path1/*',
},
},
{
type: 'PREFIX',
permission: ['read'],
scope: {
bucketName: 'bucket2',
path: 'path3/*',
},
},
]);
});
});
Loading
Loading