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"
...@@ -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.
Finish editing this message first!
Please register or to comment