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

feat: disable policy feature when 403 or error when fetching and parsing bucket policy #902

Merged
merged 5 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
91 changes: 65 additions & 26 deletions web/src/core/adapters/s3Client/s3Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,27 +331,25 @@ export function createS3Client(

const { awsS3Client } = await getAwsS3Client();

const bucketPolicyAndAllowedPrefix = await (async () => {
const { GetBucketPolicyCommand } = await import("@aws-sdk/client-s3");

let sendResp: import("@aws-sdk/client-s3").GetBucketPolicyCommandOutput;
const resolveBucketPolicy = async () => {
const { GetBucketPolicyCommand, S3ServiceException } = await import(
"@aws-sdk/client-s3"
);

try {
sendResp = await awsS3Client.send(
new GetBucketPolicyCommand({
Bucket: bucketName
})
const sendResp = await awsS3Client.send(
new GetBucketPolicyCommand({ Bucket: bucketName })
);
} catch {
console.log("The error is ok, there is no bucket policy");
return undefined;
}

if (sendResp.Policy === undefined) {
return undefined;
}
if (!sendResp.Policy) {
console.info("Bucket policy is not defined, but it's okay.");
return {
isBucketPolicyAvailable: true,
bucketPolicy: undefined,
allowedPrefix: []
};
}

try {
// Validate and parse the policy
const parsedPolicy = s3BucketPolicySchema.parse(
JSON.parse(sendResp.Policy)
Expand All @@ -373,18 +371,55 @@ export function createS3Client(
resource.replace(`arn:aws:s3:::${bucketName}/`, "")
);

return { bucketPolicy: parsedPolicy, allowedPrefix };
} catch (e) {
console.warn("The best effort attempt failed to parse the policy", e);
return undefined;
}
})();
return {
isBucketPolicyAvailable: true,
bucketPolicy: parsedPolicy,
allowedPrefix
ddecrulle marked this conversation as resolved.
Show resolved Hide resolved
};
} catch (error: unknown) {
if (error instanceof S3ServiceException) {
switch (error.$metadata?.httpStatusCode) {
case 404:
console.info(
"Bucket policy does not exist (404), it's ok."
);
return {
isBucketPolicyAvailable: true,
bucketPolicy: undefined,
allowedPrefix: []
};
case 403:
console.info("Access denied to bucket policy (403).");
return {
isBucketPolicyAvailable: false,
bucketPolicy: undefined,
allowedPrefix: []
};
default:
console.error("An S3 error occurred:", error.message);
return {
isBucketPolicyAvailable: false,
bucketPolicy: undefined,
allowedPrefix: []
};
}
}

const { allowedPrefix, bucketPolicy } = bucketPolicyAndAllowedPrefix ?? {
allowedPrefix: [],
bucketPolicy: undefined
console.error(
"An unexpected error occurred when fetching bucket policy",
error
);
return {
isBucketPolicyAvailable: false,
bucketPolicy: undefined,
allowedPrefix: []
};
}
};

const { isBucketPolicyAvailable, allowedPrefix, bucketPolicy } =
await resolveBucketPolicy();

const Contents: import("@aws-sdk/client-s3")._Object[] = [];
const CommonPrefixes: import("@aws-sdk/client-s3").CommonPrefix[] = [];

Expand Down Expand Up @@ -439,7 +474,11 @@ export function createS3Client(
}
);

return { objects: [...directories, ...files], bucketPolicy };
return {
objects: [...directories, ...files],
bucketPolicy,
isBucketPolicyAvailable
};
},
setPathAccessPolicy: async ({ currentBucketPolicy, policy, path }) => {
const { getAwsS3Client } = await prApi;
Expand Down
8 changes: 5 additions & 3 deletions web/src/core/ports/S3Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ export type S3Client = {
/**
* In charge of creating bucket if doesn't exist.
*/
listObjects: (params: {
path: string;
}) => Promise<{ objects: S3Object[]; bucketPolicy: S3BucketPolicy | undefined }>;
listObjects: (params: { path: string }) => Promise<{
objects: S3Object[];
bucketPolicy: S3BucketPolicy | undefined;
isBucketPolicyAvailable: boolean;
}>;

setPathAccessPolicy: (params: {
path: string;
Expand Down
9 changes: 6 additions & 3 deletions web/src/core/usecases/fileExplorer/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const commandLogsEntries = createSelector(
export type CurrentWorkingDirectoryView = {
directoryPath: string;
items: CurrentWorkingDirectoryView.Item[];
isBucketPolicyFeatureEnabled: boolean;
};

export namespace CurrentWorkingDirectoryView {
Expand Down Expand Up @@ -93,12 +94,13 @@ const currentWorkingDirectoryView = createSelector(
createSelector(state, state => state.objects),
createSelector(state, state => state.ongoingOperations),
createSelector(state, state => state.s3FilesBeingUploaded),

createSelector(state, state => state.isBucketPolicyAvailable),
(
directoryPath,
objects,
ongoingOperations,
s3FilesBeingUploaded
s3FilesBeingUploaded,
isBucketPolicyAvailable
): CurrentWorkingDirectoryView | null => {
if (directoryPath === undefined) {
return null;
Expand Down Expand Up @@ -184,7 +186,8 @@ const currentWorkingDirectoryView = createSelector(

return {
directoryPath,
items
items,
isBucketPolicyFeatureEnabled: isBucketPolicyAvailable
};
}
);
Expand Down
7 changes: 6 additions & 1 deletion web/src/core/usecases/fileExplorer/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type State = {
resp: string | undefined;
}[];
bucketPolicy: S3BucketPolicy;
isBucketPolicyAvailable: boolean;
share:
| {
fileBasename: string;
Expand Down Expand Up @@ -57,6 +58,7 @@ export const { reducer, actions } = createUsecaseActions({
Version: "2012-10-17",
Statement: []
},
isBucketPolicyAvailable: true,
share: undefined
}),
reducers: {
Expand Down Expand Up @@ -127,17 +129,20 @@ export const { reducer, actions } = createUsecaseActions({
directoryPath: string;
objects: S3Object[];
bucketPolicy: S3BucketPolicy | undefined;
isBucketPolicyAvailable: boolean;
};
}
) => {
const { directoryPath, objects, bucketPolicy } = payload;
const { directoryPath, objects, bucketPolicy, isBucketPolicyAvailable } =
payload;

state.directoryPath = directoryPath;
state.objects = objects;
state.isNavigationOngoing = false;
if (bucketPolicy) {
state.bucketPolicy = bucketPolicy;
}
state.isBucketPolicyAvailable = isBucketPolicyAvailable;
// Properly restore state when navigating back to
// a directory with ongoing operations.
state.ongoingOperations
Expand Down
17 changes: 12 additions & 5 deletions web/src/core/usecases/fileExplorer/thunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,10 @@ const privateThunks = {
return r.s3Client;
});

const { objects, bucketPolicy } = await s3Client.listObjects({
path: directoryPath
});
const { objects, bucketPolicy, isBucketPolicyAvailable } =
await s3Client.listObjects({
path: directoryPath
});

if (ctx.completionStatus !== undefined) {
dispatch(actions.commandLogCancelled({ cmdId }));
Expand All @@ -173,7 +174,8 @@ const privateThunks = {
actions.navigationCompleted({
directoryPath,
objects,
bucketPolicy
bucketPolicy,
isBucketPolicyAvailable
})
);
}
Expand Down Expand Up @@ -279,7 +281,12 @@ export const thunks = {

const state = getState()[name];

const { directoryPath, objects } = state;
const { directoryPath, objects, isBucketPolicyAvailable } = state;

if (!isBucketPolicyAvailable) {
console.info("Bucket policy is not available");
return;
}

const object = objects.find(o => o.basename === basename && o.kind === kind);

Expand Down
9 changes: 8 additions & 1 deletion web/src/ui/pages/myFiles/Explorer/Explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export type ExplorerProps = {
commandLogsEntries: CommandBarProps.Entry[] | undefined;
evtAction: NonPostableEvt<"TRIGGER COPY PATH">;
items: Item[];
isBucketPolicyFeatureEnabled: boolean;
onNavigate: (params: { directoryPath: string }) => void;
changePolicy: (params: {
policy: Item["policy"];
Expand Down Expand Up @@ -108,7 +109,7 @@ export const Explorer = memo((props: ExplorerProps) => {
pathMinDepth,
onViewModeChange,
viewMode,

isBucketPolicyFeatureEnabled,
shareView,
onShareFileOpen,
onShareFileClose,
Expand Down Expand Up @@ -433,6 +434,9 @@ export const Explorer = memo((props: ExplorerProps) => {
onDeleteItem={itemsOnDeleteItem}
onShare={onShareDialogOpen}
evtAction={evtExplorerItemsAction}
isBucketPolicyFeatureEnabled={
isBucketPolicyFeatureEnabled
}
/>
);
case "list":
Expand All @@ -450,6 +454,9 @@ export const Explorer = memo((props: ExplorerProps) => {
onDeleteItems={itemsOnDeleteItems}
onShare={onShareDialogOpen}
evtAction={evtExplorerItemsAction}
isBucketPolicyFeatureEnabled={
isBucketPolicyFeatureEnabled
}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Story = StoryObj<typeof meta>;

export const FileSelected: Story = {
args: {
isBucketPolicyFeatureEnabled: true,
kind: "file",
basename: "example-file.txt",
size: 100000000,
Expand All @@ -31,6 +32,7 @@ export const FileSelected: Story = {
export const DirectoryUnselected: Story = {
args: {
kind: "directory",
isBucketPolicyFeatureEnabled: true,
basename: "example-directory",
size: undefined,
policy: "public",
Expand Down
19 changes: 11 additions & 8 deletions web/src/ui/pages/myFiles/Explorer/ExplorerItems/ExplorerItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type ExplorerItemProps = {

/** File size in bytes */
size: number | undefined;

isBucketPolicyFeatureEnabled: boolean;
policy: Item["policy"];
onPolicyChange: () => void;
onDoubleClick: () => void;
Expand All @@ -45,7 +45,8 @@ export const ExplorerItem = memo((props: ExplorerItemProps) => {
onDoubleClick,
onPolicyChange,
onClick,
isPolicyChanging
isPolicyChanging,
isBucketPolicyFeatureEnabled
} = props;

const prettySize = size ? fileSizePrettyPrint({ bytes: size }) : null;
Expand Down Expand Up @@ -96,12 +97,14 @@ export const ExplorerItem = memo((props: ExplorerItemProps) => {
})()}
hasShadow={true}
/>
<PolicySwitch
policy={policy}
className={classes.policyIcon}
changePolicy={onPolicyChange}
isPolicyChanging={isPolicyChanging}
/>
{isBucketPolicyFeatureEnabled && (
<PolicySwitch
policy={policy}
className={classes.policyIcon}
changePolicy={onPolicyChange}
isPolicyChanging={isPolicyChanging}
/>
)}
</div>

<div className={classes.textContainer}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const Default: Story = {
args: {
isNavigating: false,
items: itemsSample,
isBucketPolicyFeatureEnabled: true,
onNavigate: action("Navigate to directory"),
onOpenFile: action("Open file"),
onDeleteItem: action("Delete item"),
Expand All @@ -84,6 +85,7 @@ export const EmptyDirectory: Story = {
args: {
isNavigating: false,
items: [],
isBucketPolicyFeatureEnabled: true,
onNavigate: action("Navigate to directory"),
onOpenFile: action("Open file"),
onDeleteItem: action("Delete item"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type ExplorerItemsProps = {
isNavigating: boolean;

items: Item[];
isBucketPolicyFeatureEnabled: boolean;

onNavigate: (params: { basename: string }) => void;
onOpenFile: (params: { basename: string }) => void;
Expand All @@ -43,14 +44,15 @@ export const ExplorerItems = memo((props: ExplorerItemsProps) => {
className,
items,
isNavigating,
isBucketPolicyFeatureEnabled,
onNavigate,
onOpenFile,
onSelectedItemKindValueChange,
evtAction,
onPolicyChange,
onCopyPath,
onDeleteItem,
onShare
onShare,
evtAction
} = props;
const isEmpty = items.length === 0;

Expand Down Expand Up @@ -158,6 +160,9 @@ export const ExplorerItems = memo((props: ExplorerItemsProps) => {
isSelected={selectedItem.basename === basename}
size={size}
policy={policy}
isBucketPolicyFeatureEnabled={
isBucketPolicyFeatureEnabled
}
onPolicyChange={handlePolicyChange(item)}
onClick={handleItemClick(item)}
onDoubleClick={handleItemDoubleClick(item)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const Default: Story = {
args: {
isNavigating: false,
items: itemsSample,
isBucketPolicyFeatureEnabled: true,
onNavigate: action("Navigate to directory"),
onOpenFile: action("Open file"),
onDeleteItems: action("Delete items"),
Expand Down
Loading
Loading