Skip to content
Snippets Groups Projects
Verified Commit fb8ffb05 authored by Daniel Göbel's avatar Daniel Göbel
Browse files

Respect file prefix in bucket permissions in bucket view

#71
parent dfa4d4fa
No related branches found
No related tags found
1 merge request!66Resolve "Fetch Objects directly from S3 Endpoint instead of S3 proxy"
This commit is part of merge request !66. Comments created here will be created in the context of that merge request.
...@@ -11,6 +11,7 @@ import { Tooltip } from "bootstrap"; ...@@ -11,6 +11,7 @@ import { Tooltip } from "bootstrap";
import { useBucketStore } from "@/stores/buckets"; import { useBucketStore } from "@/stores/buckets";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { useAuthStore } from "@/stores/users"; import { useAuthStore } from "@/stores/users";
import type { FolderTree } from "@/types/PseudoFolder";
const props = defineProps<{ const props = defineProps<{
active: boolean; active: boolean;
...@@ -27,6 +28,13 @@ const router = useRouter(); ...@@ -27,6 +28,13 @@ const router = useRouter();
const permission = computed<BucketPermissionOut | undefined>( const permission = computed<BucketPermissionOut | undefined>(
() => permissionRepository.ownPermissions[props.bucket.name], () => permissionRepository.ownPermissions[props.bucket.name],
); );
const subFolder = computed<FolderTree>(() => {
const subFolders: Record<string, FolderTree> = {};
if (permission.value?.file_prefix != null) {
subFolders[permission.value.file_prefix] = { subFolders: {}, files: [] };
}
return { subFolders: subFolders, files: [] };
});
const emit = defineEmits<{ const emit = defineEmits<{
(e: "delete-bucket", bucketName: string): void; (e: "delete-bucket", bucketName: string): void;
...@@ -50,7 +58,7 @@ onMounted(() => { ...@@ -50,7 +58,7 @@ onMounted(() => {
v-if="permission != undefined && props.active" v-if="permission != undefined && props.active"
:modalID="'view-permission-modal' + randomIDSuffix" :modalID="'view-permission-modal' + randomIDSuffix"
:bucket-name="props.bucket.name" :bucket-name="props.bucket.name"
:sub-folders="{ subFolders: {}, files: [] }" :sub-folders="subFolder"
:edit-user-permission="permission" :edit-user-permission="permission"
:readonly="true" :readonly="true"
:editable="false" :editable="false"
......
...@@ -160,7 +160,7 @@ function findSubFolders( ...@@ -160,7 +160,7 @@ function findSubFolders(
const subFolderString = const subFolderString =
(parentFolders.length > 0 ? parentFolders.join("/") + "/" : "") + (parentFolders.length > 0 ? parentFolders.join("/") + "/" : "") +
subFolder + subFolder +
"/"; (subFolder.endsWith("/") ? "" : "/");
arr.push( arr.push(
subFolderString, subFolderString,
...findSubFolders( ...findSubFolders(
......
...@@ -39,12 +39,12 @@ export const useS3ObjectStore = defineStore({ ...@@ -39,12 +39,12 @@ export const useS3ObjectStore = defineStore({
}, },
getters: { getters: {
getPresignedUrl(): (bucketName: string, key: string) => Promise<string> { getPresignedUrl(): (bucketName: string, key: string) => Promise<string> {
return async (bucketName, key) => { return (bucketName, key) => {
const command = new GetObjectCommand({ const command = new GetObjectCommand({
Bucket: bucketName, Bucket: bucketName,
Key: key, Key: key,
}); });
return await getSignedUrl(this.client, command, { return getSignedUrl(this.client, command, {
expiresIn: 30, expiresIn: 30,
}); });
}; };
...@@ -78,14 +78,15 @@ export const useS3ObjectStore = defineStore({ ...@@ -78,14 +78,15 @@ export const useS3ObjectStore = defineStore({
}, },
async fetchS3Objects( async fetchS3Objects(
bucketName: string, bucketName: string,
prefix?: string,
onFinally?: () => void, onFinally?: () => void,
): Promise<S3Object[]> { ): Promise<S3Object[]> {
if ((this.objectMapping[bucketName] ?? []).length > 0) { if (this.objectMapping[bucketName] != undefined) {
onFinally?.(); onFinally?.();
} }
const pag = paginateListObjectsV2( const pag = paginateListObjectsV2(
{ client: this.client }, { client: this.client },
{ Bucket: bucketName }, { Bucket: bucketName, Prefix: prefix },
); );
const objs: S3Object[] = []; const objs: S3Object[] = [];
try { try {
......
...@@ -8,7 +8,7 @@ import { useWorkflowExecutionStore } from "@/stores/workflowExecutions"; ...@@ -8,7 +8,7 @@ import { useWorkflowExecutionStore } from "@/stores/workflowExecutions";
import { useBucketStore } from "@/stores/buckets"; import { useBucketStore } from "@/stores/buckets";
import { useWorkflowStore } from "@/stores/workflows"; import { useWorkflowStore } from "@/stores/workflows";
import { useS3KeyStore } from "@/stores/s3keys"; import { useS3KeyStore } from "@/stores/s3keys";
import {useS3ObjectStore} from "@/stores/s3objects"; import { useS3ObjectStore } from "@/stores/s3objects";
type DecodedToken = { type DecodedToken = {
exp: number; exp: number;
......
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive, watch, computed } from "vue"; import { onMounted, reactive, watch, computed } from "vue";
import type { BucketPermissionOut } from "@/client/s3proxy";
import type { import type {
FolderTree, FolderTree,
S3PseudoFolder, S3PseudoFolder,
...@@ -34,14 +33,14 @@ const s3KeyRepository = useS3KeyStore(); ...@@ -34,14 +33,14 @@ const s3KeyRepository = useS3KeyStore();
const props = defineProps<{ const props = defineProps<{
bucketName: string; bucketName: string;
subFolders: string[] | string; subFolders: string[] | string;
permission?: BucketPermissionOut;
}>(); }>();
const randomIDSuffix = Math.random().toString(16).substring(2, 8); const randomIDSuffix = Math.random().toString(16).substring(2, 8);
let successToast: Toast | null = null; let successToast: Toast | null = null;
let refreshTimeout: NodeJS.Timeout | undefined = undefined;
// Reactive State // Reactive State
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
const deleteObjectsState = reactive<{ const deleteObjectsState = reactive<{
deletedItem: string; deletedItem: string;
potentialObjectToDelete: string; potentialObjectToDelete: string;
...@@ -57,7 +56,6 @@ const objectState = reactive<{ ...@@ -57,7 +56,6 @@ const objectState = reactive<{
filterString: string; filterString: string;
bucketNotFoundError: boolean; bucketNotFoundError: boolean;
bucketPermissionError: boolean; bucketPermissionError: boolean;
createdPermission: undefined | BucketPermissionOut;
editObjectKey: string; editObjectKey: string;
copyObject: S3Object; copyObject: S3Object;
viewDetailObject: S3Object; viewDetailObject: S3Object;
...@@ -66,7 +64,6 @@ const objectState = reactive<{ ...@@ -66,7 +64,6 @@ const objectState = reactive<{
filterString: "", filterString: "",
bucketNotFoundError: false, bucketNotFoundError: false,
bucketPermissionError: false, bucketPermissionError: false,
createdPermission: undefined,
editObjectKey: "", editObjectKey: "",
copyObject: { copyObject: {
Key: "", Key: "",
...@@ -213,9 +210,18 @@ const subFolderInUrl = computed<boolean>( ...@@ -213,9 +210,18 @@ const subFolderInUrl = computed<boolean>(
const errorLoadingObjects = computed<boolean>( const errorLoadingObjects = computed<boolean>(
() => objectState.bucketPermissionError || objectState.bucketNotFoundError, () => objectState.bucketPermissionError || objectState.bucketNotFoundError,
); );
const writableBucket = computed<boolean>(() => const writableBucket = computed<boolean>(() => {
bucketRepository.writableBucket(props.bucketName), // Allow only upload in bucket folder with respect to permission prefix
); let prefixWritable = true;
if (
bucketRepository.ownPermissions[props.bucketName]?.file_prefix != undefined
) {
prefixWritable =
bucketRepository.ownPermissions[props.bucketName]?.file_prefix ===
currentSubFolders.value.join("/") + "/";
}
return bucketRepository.writableBucket(props.bucketName) && prefixWritable;
});
const readableBucket = computed<boolean>(() => const readableBucket = computed<boolean>(() =>
bucketRepository.readableBucket(props.bucketName), bucketRepository.readableBucket(props.bucketName),
); );
...@@ -227,7 +233,9 @@ watch( ...@@ -227,7 +233,9 @@ watch(
(newBucketName, oldBucketName) => { (newBucketName, oldBucketName) => {
if (oldBucketName !== newBucketName) { if (oldBucketName !== newBucketName) {
// If bucket is changed, update the objects // If bucket is changed, update the objects
updateObjects(newBucketName); objectState.bucketPermissionError = false;
objectState.bucketNotFoundError = false;
fetchObjects();
objectState.filterString = ""; objectState.filterString = "";
} }
}, },
...@@ -251,9 +259,17 @@ watch( ...@@ -251,9 +259,17 @@ watch(
// Lifecycle Hooks // Lifecycle Hooks
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
onMounted(() => { onMounted(() => {
s3KeyRepository.fetchS3Keys(authStore.currentUID).then(() => { let counter = 0;
updateObjects(props.bucketName); const onFinally = () => {
}); counter++;
if (counter > 1) {
fetchObjects();
}
};
// wait till s3keys and ownPermissions are available before fetching objects
s3KeyRepository.fetchS3Keys(authStore.currentUID, onFinally);
bucketRepository.fetchOwnPermissions(onFinally);
document document
.querySelectorAll(".tooltip-container") .querySelectorAll(".tooltip-container")
.forEach( .forEach(
...@@ -303,25 +319,33 @@ function calculateFolderLastModified(folder: FolderTree): string { ...@@ -303,25 +319,33 @@ function calculateFolderLastModified(folder: FolderTree): string {
} }
/** /**
* Load the meta information about objects from a bucket * Fetch object from bucket with loading animation
* @param bucketName Name of a bucket
*/ */
async function updateObjects(bucketName: string) { function fetchObjects() {
objectState.loading = true; objectState.loading = true;
objectRepository.fetchS3Objects(bucketName, () => { const prefix: string | undefined =
objectState.loading = false; bucketRepository.ownPermissions[props.bucketName]?.file_prefix ?? undefined;
}); objectRepository
/* .fetchS3Objects(props.bucketName, prefix, () => {
objectState.loading = false;
} catch { })
objectState.bucketNotFoundError = true; .catch((error) => {
if (error.Code == "AccessDenied") {
if (error.status === 404) {
objectState.bucketNotFoundError = true;
} else if (error.status == 403) {
objectState.bucketPermissionError = true; objectState.bucketPermissionError = true;
} else {
objectState.bucketNotFoundError = true;
} }
*/ });
}
/**
* Fetch the meta information about objects from a bucket
*/
function refreshObjects() {
clearTimeout(refreshTimeout);
refreshTimeout = setTimeout(() => {
fetchObjects();
}, 500);
} }
function isS3Object( function isS3Object(
...@@ -465,7 +489,7 @@ function getObjectFileName(key: string): string { ...@@ -465,7 +489,7 @@ function getObjectFileName(key: string): string {
<!-- Inputs on top --> <!-- Inputs on top -->
<!-- Search bucket text input --> <!-- Search bucket text input -->
<div class="row"> <div class="row">
<div class="col-8"> <div class="col-5 me-auto">
<div class="input-group mt-2 rounded shadow-sm"> <div class="input-group mt-2 rounded shadow-sm">
<span class="input-group-text" id="objects-search-wrapping" <span class="input-group-text" id="objects-search-wrapping"
><font-awesome-icon icon="fa-solid fa-magnifying-glass" ><font-awesome-icon icon="fa-solid fa-magnifying-glass"
...@@ -483,6 +507,17 @@ function getObjectFileName(key: string): string { ...@@ -483,6 +507,17 @@ function getObjectFileName(key: string): string {
</div> </div>
<!-- Upload object button --> <!-- Upload object button -->
<div id="BucketViewButtons" class="col-auto"> <div id="BucketViewButtons" class="col-auto">
<button
type="button"
class="btn btn-light me-4 tooltip-container border shadow-sm"
:disabled="errorLoadingObjects"
data-bs-toggle="tooltip"
data-bs-title="Refresh Objects"
@click="refreshObjects"
>
<font-awesome-icon icon="fa-solid fa-arrow-rotate-right" />
<span class="visually-hidden">Refresh Objects</span>
</button>
<button <button
type="button" type="button"
class="btn btn-light me-2 tooltip-container border shadow-sm" class="btn btn-light me-2 tooltip-container border shadow-sm"
...@@ -503,7 +538,7 @@ function getObjectFileName(key: string): string { ...@@ -503,7 +538,7 @@ function getObjectFileName(key: string): string {
<!-- Add folder button --> <!-- Add folder button -->
<button <button
type="button" type="button"
class="btn btn-light m-2 tooltip-container border shadow-sm" class="btn btn-light me-4 tooltip-container border shadow-sm"
:disabled="errorLoadingObjects || !writableBucket" :disabled="errorLoadingObjects || !writableBucket"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-title="Create Folder" data-bs-title="Create Folder"
...@@ -523,7 +558,7 @@ function getObjectFileName(key: string): string { ...@@ -523,7 +558,7 @@ function getObjectFileName(key: string): string {
v-if="!authStore.foreignUser" v-if="!authStore.foreignUser"
:hidden="!bucketRepository.permissionFeatureAllowed(props.bucketName)" :hidden="!bucketRepository.permissionFeatureAllowed(props.bucketName)"
type="button" type="button"
class="btn btn-light m-2 tooltip-container border shadow-sm" class="btn btn-light me-2 tooltip-container border shadow-sm"
:disabled="errorLoadingObjects" :disabled="errorLoadingObjects"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-title="Create Bucket Permission" data-bs-title="Create Bucket Permission"
...@@ -547,7 +582,7 @@ function getObjectFileName(key: string): string { ...@@ -547,7 +582,7 @@ function getObjectFileName(key: string): string {
v-if="!authStore.foreignUser" v-if="!authStore.foreignUser"
:hidden="!bucketRepository.permissionFeatureAllowed(props.bucketName)" :hidden="!bucketRepository.permissionFeatureAllowed(props.bucketName)"
type="button" type="button"
class="btn btn-light m-2 tooltip-container border shadow-sm" class="btn btn-light tooltip-container border shadow-sm"
:disabled="errorLoadingObjects" :disabled="errorLoadingObjects"
data-bs-title="List Bucket Permission" data-bs-title="List Bucket Permission"
data-bs-toggle="modal" data-bs-toggle="modal"
...@@ -558,6 +593,7 @@ function getObjectFileName(key: string): string { ...@@ -558,6 +593,7 @@ function getObjectFileName(key: string): string {
</button> </button>
<permission-list-modal <permission-list-modal
v-if=" v-if="
objectState.loading == false &&
bucketRepository.ownPermissions[props.bucketName] == undefined && bucketRepository.ownPermissions[props.bucketName] == undefined &&
!authStore.foreignUser !authStore.foreignUser
" "
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment