Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3501fc2
Implement s3 profile modal (WIP)
garronej Apr 11, 2026
73e8804
Move S3 profile modal into explorer dialogs
garronej Apr 29, 2026
f8658f4
Refactor S3ProfileDialog types for active view management
garronej Apr 29, 2026
b717593
Add isEditionOfAnExistingConfig prop to S3ProfileForm for edit mode h…
garronej Apr 29, 2026
f3c76a1
Add comment to clarify isEditionOfAnExistingConfig prop usage in S3Pr…
garronej Apr 29, 2026
77f9a28
Workable draft of the profile edit side modal
garronej Apr 29, 2026
ad7e656
Minimal S3ProfileDetail component
garronej Apr 29, 2026
ad95592
Making sure the tokens are updated when changing profile
garronej Apr 29, 2026
48c2054
Implement read of public/private policy in the core
garronej Apr 30, 2026
27d20bc
Do not sign url for public files
garronej Apr 30, 2026
09f7fd5
Implement infra for changing s3Uri public/private policy
garronej Apr 30, 2026
ec323a1
Log aws CLI commands for toggleS3UriPublicPrivatePolicy
garronej Apr 30, 2026
5c8303d
Remove S3SelectionActionBar buttons for unimplemented actions.
garronej Apr 30, 2026
5ced378
Add an overlay icon to show which object and prefixes are public.
garronej May 1, 2026
0f9bbb5
Prepare a usecase for handling sharing properly. Some refactoring
garronej May 2, 2026
97e8652
Implement the component that will populate the dialog for sharing obj…
garronej May 3, 2026
6ceed3f
Connect the headless share dialog with the core and integrate it to t…
garronej May 3, 2026
2f374ca
Fix evt not indexed
garronej May 4, 2026
5bb68cd
Simplification of the polycy management WIP
garronej May 4, 2026
2833e28
Make S3SelectionActionBar headless
garronej May 4, 2026
c74de72
propagate changes
garronej May 4, 2026
49e4139
Add public and private actions to S3SelectionActionBar
garronej May 4, 2026
0c4d4a9
Propagate policy changes to S3ExplorerMainView
garronej May 4, 2026
cf1a4c3
Propagate policy management changes to the Page
garronej May 4, 2026
f5c68a9
Implement putBucketPolicies
garronej May 4, 2026
4cd4500
Implement new bucket policy management pure util.
garronej May 5, 2026
e0d96d4
Add modal to confirm action of making directory public
garronej May 5, 2026
acaf165
Add icon
garronej May 5, 2026
a067aa9
Use s3Uri in the confirm public modal
garronej May 5, 2026
3dd37ca
Internationalize MakePrefixPublicDialog
garronej May 5, 2026
d62e7e2
Stack the bucket policies rules into two unique statements
garronej May 5, 2026
0564492
Refactor makePrefixPublic to include AWS CLI command formatting
garronej May 6, 2026
32f9681
Better positioning of the command bar on the S3Explorer page
garronej May 6, 2026
a265533
Replace overlay public icon by a tag
garronej May 6, 2026
4a5573d
Rename misleading props name
garronej May 6, 2026
ffe1328
Update function so that it gives the prefix from where is inherited t…
garronej May 6, 2026
a1b4b17
Show public segment in the s3UriBar
garronej May 7, 2026
a945c18
Fix uri bar public indicator
garronej May 12, 2026
ecae8d3
First implementation of S3ProfileForm
garronej May 12, 2026
3707907
Improve the s3 profile form UX
garronej May 12, 2026
9defd9c
Add URL style examples to S3ProfileForm for improved user guidance
garronej May 12, 2026
f0a354a
Hide command bar when no commands logged yet
garronej May 12, 2026
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
120 changes: 118 additions & 2 deletions web/src/core/adapters/s3Client/s3Client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { S3Client } from "core/ports/S3Client";
import type { BucketPolicies } from "core/tools/bucketPolicies";
import {
getNewlyRequestedOrCachedTokenFactory,
createSessionStorageTokenPersistence
} from "core/tools/getNewlyRequestedOrCachedToken";
import { assert, is, type Equals } from "tsafe/assert";
import { assert, is, typeGuard, type Equals } from "tsafe";
import type { Oidc } from "core/ports/Oidc";
import { getS3UriKey, parseS3Uri } from "core/tools/S3Uri";
import { exclude, id } from "tsafe";
Expand Down Expand Up @@ -219,6 +220,30 @@ export function createS3Client(
})();

const s3Client: S3Client = {
getUnsignedObjectHttpUrl: ({ s3Uri }) => {
const url = new URL(params.url);
const pathname = url.pathname.endsWith("/")
? url.pathname.slice(0, -1)
: url.pathname;
const encodedKey = getS3UriKey(s3Uri)
.split("/")
.map(encodeURIComponent)
.join("/");

if (params.pathStyleAccess) {
url.pathname = `${pathname}/${encodeURIComponent(
s3Uri.bucket
)}/${encodedKey}`;
} else {
url.hostname = `${s3Uri.bucket}.${url.hostname}`;
url.pathname = `${pathname}/${encodedKey}`;
}

url.search = "";
url.hash = "";

return url.href;
},
getToken: async ({ doForceRenew }) => {
const { getNewlyRequestedOrCachedToken, clearCachedToken } = await prApi;

Expand Down Expand Up @@ -433,7 +458,7 @@ export function createS3Client(
})
);
},
generateSignedDownloadUrl: async ({ s3Uri, validityDurationSecond }) => {
getSignedObjectHttpUrl: async ({ s3Uri, validityDurationSecond }) => {
const { getAwsS3Client } = await prApi;

const { awsS3Client } = await getAwsS3Client();
Expand Down Expand Up @@ -535,6 +560,97 @@ export function createS3Client(
}
}

return { isSuccess: true };
},
getBucketPolicies: async ({ bucket }) => {
const { getAwsS3Client } = await prApi;

const { awsS3Client } = await getAwsS3Client();

const { GetBucketPolicyCommand, S3ServiceException } = await import(
"@aws-sdk/client-s3"
);

let policy: string | undefined;

try {
({ Policy: policy } = await awsS3Client.send(
new GetBucketPolicyCommand({
Bucket: bucket
})
));
} catch (error) {
if (error instanceof S3ServiceException) {
const httpStatusCode = error.$metadata?.httpStatusCode;

if (httpStatusCode === 404) {
console.log(
[
`Onyxia: The 404 here is fine`,
`the bucket just doesn't have bucket policies yet.`
].join(" ")
);
return {
Version: "2012-10-17",
Statement: []
};
}

if (
httpStatusCode === 403 ||
httpStatusCode === 405 ||
httpStatusCode === 501 ||
error.name === "NoSuchBucketPolicy" ||
error.name === "NotImplemented" ||
error.name === "NotSupported"
) {
return undefined;
}
}

throw error;
}

if (policy === undefined) {
return undefined;
}

const bucketPolicies: unknown = JSON.parse(policy);

assert(
typeGuard<BucketPolicies>(
bucketPolicies,
typeof bucketPolicies === "object" &&
bucketPolicies !== null &&
!Array.isArray(bucketPolicies)
)
);

return bucketPolicies;
},
putBucketPolicies: async ({ bucket, bucketPolicies }) => {
const { getAwsS3Client } = await prApi;

const { awsS3Client } = await getAwsS3Client();

const { PutBucketPolicyCommand } = await import("@aws-sdk/client-s3");

try {
await awsS3Client.send(
new PutBucketPolicyCommand({
Bucket: bucket,
Policy: JSON.stringify(bucketPolicies)
})
);
} catch (error) {
assert(is<Error>(error));

return {
isSuccess: false,
errorMessage: error.message
};
}

return { isSuccess: true };
}
};
Expand Down
16 changes: 15 additions & 1 deletion web/src/core/ports/S3Client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { S3Uri } from "core/tools/S3Uri";
import type { NonPostableEvt } from "evt";
import type { BucketPolicies } from "core/tools/bucketPolicies";

export type S3Client = {
getToken: (params: { doForceRenew: boolean }) => Promise<
Expand Down Expand Up @@ -31,11 +32,15 @@ export type S3Client = {

deleteObject: (params: { s3Uri: S3Uri.NonTerminatedByDelimiter }) => Promise<void>;

generateSignedDownloadUrl: (params: {
getSignedObjectHttpUrl: (params: {
s3Uri: S3Uri.NonTerminatedByDelimiter;
validityDurationSecond: number;
}) => Promise<string>;

getUnsignedObjectHttpUrl: (params: {
s3Uri: S3Uri.NonTerminatedByDelimiter;
}) => string;

getObjectContent: (params: {
s3Uri: S3Uri.NonTerminatedByDelimiter;
range: `bytes=0-${number}` | undefined;
Expand All @@ -57,6 +62,15 @@ export type S3Client = {
errorMessage: string;
}
>;

getBucketPolicies: (params: {
bucket: string;
}) => Promise<BucketPolicies | undefined>;

putBucketPolicies: (params: {
bucket: string;
bucketPolicies: BucketPolicies;
}) => Promise<{ isSuccess: true } | { isSuccess: false; errorMessage: string }>;
};

export namespace S3Client {
Expand Down
Loading