Skip to content

Commit af3df78

Browse files
committed
Controls for Internal Only sharing
Toggle for folder/file 'internal only' setting 'internal' annotation added
1 parent eb3751e commit af3df78

12 files changed

Lines changed: 449 additions & 132 deletions

File tree

frontend/src/assets/main.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,21 @@ div:focus-visible {
271271
}
272272
}
273273

274+
.p-tag-info {
275+
background-color: rgb(224, 242, 254);
276+
outline-style: solid;
277+
outline-color: $bcbox-primary;
278+
outline-width: thin;
279+
280+
.p-tag-value {
281+
color: $bcbox-primary;
282+
}
283+
284+
.p-tag-icon {
285+
color: $bcbox-primary
286+
}
287+
}
288+
274289
/* datatable */
275290
.p-datatable,
276291
.p-treetable {
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<script setup lang="ts">
2+
import { computed, onMounted, ref, watch } from 'vue';
3+
4+
import { InputSwitch, useConfirm, useToast } from '@/lib/primevue';
5+
import { usePermissionStore } from '@/store';
6+
import { Permissions } from '@/utils/constants';
7+
8+
import type { Ref } from 'vue';
9+
10+
// Props
11+
type Props = {
12+
bucketId: string;
13+
bucketName: string;
14+
bucketPublic: boolean;
15+
userId: string;
16+
};
17+
18+
const props = withDefaults(defineProps<Props>(), {});
19+
20+
// Store
21+
const permissionStore = usePermissionStore();
22+
23+
// State
24+
const isInternal: Ref<boolean> = ref(false);
25+
const fetchBucketInternal = async (): Promise<boolean> => {
26+
const bucketIdpPermissions = await permissionStore.fetchBucketIdpPermissions({
27+
bucketId: props.bucketId,
28+
permCode: 'READ',
29+
idp: 'idir'
30+
});
31+
const hasPerms = (bucketIdpPermissions?.length ?? 0) > 0;
32+
33+
return hasPerms || props.bucketPublic;
34+
};
35+
36+
// check bucket for the IDP permission
37+
const isBucketInternal: Ref<boolean> = ref(false);
38+
const isParentBucketInternal: Ref<boolean> = ref(false);
39+
const fetchParentBucketInternal = async (): Promise<boolean> => {
40+
const bucketIdpPermissions = await permissionStore.fetchBucketIdpPermissions({
41+
bucketId: props.bucketId,
42+
permCode: 'READ',
43+
idp: 'idir'
44+
});
45+
return (bucketIdpPermissions?.length ?? 0) > 0;
46+
};
47+
48+
const isToggleEnabled = computed(() => {
49+
return (
50+
!isBucketInternal.value &&
51+
!props.bucketPublic &&
52+
usePermissionStore().isUserElevatedRights() &&
53+
permissionStore.isBucketActionAllowed(props.bucketId, props.userId, Permissions.MANAGE)
54+
);
55+
});
56+
57+
// Actions
58+
const toast = useToast();
59+
const confirm = useConfirm();
60+
61+
const toggleIdp = async (value: boolean) => {
62+
if (value) {
63+
confirm.require({
64+
message: "Setting this file to 'Internal only' will allow all IDIR users " + 'to view and download it.',
65+
header: 'Set file to Internal only?',
66+
acceptLabel: 'Set to Internal only',
67+
rejectLabel: 'Cancel',
68+
accept: () => {
69+
permissionStore
70+
.addBucketIdpPermission(props.bucketId, 'idir', 'READ')
71+
.then(() => {
72+
isInternal.value = true;
73+
toast.success('File set to Internal only', `"${props.bucketName}" is now Internal only`);
74+
})
75+
.catch((e) => toast.error('Setting file to Internal only failed', e.response?.data.detail, { life: 0 }));
76+
},
77+
reject: () => (isInternal.value = false),
78+
onHide: () => (isInternal.value = false)
79+
});
80+
} else
81+
confirm.require({
82+
message:
83+
"Setting this file to private will remove 'Internal only' access. " +
84+
'Only users with permissions will be able to view or download the file.',
85+
header: 'Set file to private?',
86+
acceptLabel: 'Set to private',
87+
rejectLabel: 'Cancel',
88+
accept: () => {
89+
permissionStore
90+
.deleteBucketIdpPermission(props.bucketId, 'idir', 'READ')
91+
.then(() => {
92+
isInternal.value = false;
93+
toast.success('File set to private', `"${props.bucketName}" is no longer 'Internal only'`);
94+
})
95+
.catch((e) => toast.error('Setting file to private failed', e.response?.data.detail, { life: 0 }));
96+
},
97+
reject: () => (isInternal.value = true),
98+
onHide: () => (isInternal.value = true)
99+
});
100+
};
101+
102+
onMounted(async () => {
103+
isInternal.value = await fetchBucketInternal();
104+
isParentBucketInternal.value = await fetchParentBucketInternal();
105+
});
106+
107+
watch(props, () => {
108+
if (props.bucketPublic === true) isInternal.value = true;
109+
else {
110+
const perms = permissionStore.getBucketIdpPermissions.filter(
111+
(p: any) => p.permCode === Permissions.READ && p.bucketId === props.bucketId && p.idp === 'idir'
112+
);
113+
if (perms.length > 0) isInternal.value = true;
114+
else {
115+
isInternal.value = false;
116+
}
117+
}
118+
});
119+
</script>
120+
121+
<template>
122+
<span
123+
v-tooltip="
124+
isToggleEnabled
125+
? ''
126+
: props.bucketPublic
127+
? 'Enabled by Public Sharing'
128+
: 'Change the folder\'s \'Internal only\' setting to update this file'
129+
"
130+
>
131+
<InputSwitch
132+
:model-value="isInternal"
133+
aria-label="Toggle to make file Internl only"
134+
:disabled="!isToggleEnabled"
135+
@update:model-value="toggleIdp($event)"
136+
/>
137+
</span>
138+
</template>

frontend/src/components/bucket/BucketPermission.vue

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { storeToRefs } from 'pinia';
33
import { computed, onBeforeMount, ref } from 'vue';
44
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
55
6-
import BucketPublicToggle from '@/components/bucket/BucketPublicToggle.vue';
6+
import { BucketPublicToggle, BucketIdpToggle } from '@/components/bucket';
77
import BucketPermissionAddUser from '@/components/bucket/BucketPermissionAddUser.vue';
88
import { BulkPermission } from '@/components/common';
99
import { useAlert } from '@/composables/useAlert';
@@ -82,16 +82,15 @@ onBeforeMount(async () => {
8282
<template>
8383
<TabView>
8484
<TabPanel header="Manage permissions">
85+
<h3>Sharing Access</h3>
8586
<!-- public toggle -->
8687
<div class="flex pb-3">
8788
<div class="flex-grow-1">
8889
<div class="pb-1">
89-
<h3>Set to public</h3>
90+
<h4>Set to public</h4>
9091
<p>
91-
Making a folder
92-
<strong>public</strong>
93-
means that all files within it, including those in any subfolders, can be accessed by anyone without
94-
requiring authentication.
92+
Enabling this shares all files and subfolders. Anyone with the link can view the content without signing
93+
in.
9594
</p>
9695
</div>
9796
</div>
@@ -104,6 +103,21 @@ onBeforeMount(async () => {
104103
:user-id="getUserId"
105104
/>
106105
</div>
106+
107+
<div class="flex flex-row pb-3">
108+
<div class="flex-grow-1">
109+
<h4 class="pb-1">Internl only</h4>
110+
<p>Allow all IDIR users to view this flder and its content</p>
111+
</div>
112+
<BucketIdpToggle
113+
v-if="bucket && getUserId"
114+
:bucket-id="bucket.bucketId"
115+
:bucket-name="bucket.bucketName"
116+
:bucket-public="bucket.public"
117+
:user-id="getUserId"
118+
/>
119+
</div>
120+
107121
<h3>User Permissions</h3>
108122
<!-- user search -->
109123
<div v-if="!showSearchUsers">

frontend/src/components/bucket/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ export { default as BucketConfigForm } from './BucketConfigForm.vue';
33
export { default as BucketList } from './BucketList.vue';
44
export { default as BucketPermission } from './BucketPermission.vue';
55
export { default as BucketPermissionAddUser } from './BucketPermissionAddUser.vue';
6+
export { default as BucketPublicToggle } from './BucketPublicToggle.vue';
7+
export { default as BucketIdpToggle } from './BucketIdpToggle.vue';
68
export { default as BucketSidebar } from './BucketSidebar.vue';
79
export { default as BucketTable } from './BucketTable.vue';
810
export { default as BucketTableBucketName } from './BucketTableBucketName.vue';

frontend/src/components/object/ObjectFileDetails.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ const object: Ref<COMSObject | undefined> = ref(undefined);
6565
const bucketId: Ref<string> = ref('');
6666
const permissionsVisible: Ref<boolean> = ref(false);
6767
68+
const isInternal = computed(() => permissionStore.getInternal(object.value));
69+
6870
// version stuff
6971
const bucketVersioningEnabled = computed(() => getIsVersioningEnabled.value(props.objectId));
7072
const currentVersionId: Ref<string | undefined> = ref(props.versionId);
@@ -170,6 +172,17 @@ onMounted(async () => {
170172
rounded
171173
icon="pi pi-info-circle"
172174
/>
175+
<Tag
176+
v-else-if="isInternal"
177+
v-tooltip="
178+
'This folder and its contents can be read by anyone internal to government. ' +
179+
'Change the settings in &quot;Folder permissions.&quot;'
180+
"
181+
value="Internal"
182+
severity="info"
183+
rounded
184+
icon="pi pi-info-circle"
185+
/>
173186
</span>
174187
</div>
175188

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<script setup lang="ts">
2+
import { computed, onMounted } from 'vue';
3+
4+
import { InputSwitch, useConfirm, useToast } from '@/lib/primevue';
5+
import { useBucketStore, usePermissionStore } from '@/store';
6+
import { Permissions } from '@/utils/constants';
7+
8+
import type { Ref } from 'vue';
9+
10+
// Props
11+
type Props = {
12+
bucketId: string;
13+
objectId: string;
14+
objectName: string;
15+
objectPublic: boolean;
16+
userId: string;
17+
};
18+
19+
const props = withDefaults(defineProps<Props>(), {});
20+
21+
// ---- State:
22+
const permissionStore = usePermissionStore();
23+
const bucketStore = useBucketStore();
24+
25+
// has object IDP permission
26+
const isObjectInternal = computed(() => permissionStore.getObjectInternal(props.objectId));
27+
// has bucket IDP permission
28+
const isBucketInternal = computed(() => permissionStore.getBucketInternal(props.bucketId));
29+
// is public
30+
const isPublic: Ref<boolean> = computed(() => {
31+
const bucketIsPublic = bucketStore.getBucket(props.bucketId)?.public ?? false;
32+
return bucketIsPublic || props.objectPublic;
33+
});
34+
35+
// value for toggle
36+
const switchVal: Ref<boolean> = computed(() => {
37+
return isObjectInternal.value || isBucketInternal.value || isPublic.value;
38+
});
39+
40+
const isToggleEnabled = computed(() => {
41+
return (
42+
!isBucketInternal.value &&
43+
!isPublic.value &&
44+
usePermissionStore().isUserElevatedRights() &&
45+
permissionStore.isObjectActionAllowed(props.objectId, props.userId, Permissions.MANAGE, props.bucketId as string)
46+
);
47+
});
48+
49+
// Actions
50+
const toast = useToast();
51+
const confirm = useConfirm();
52+
53+
const toggleIdp = async (value: boolean) => {
54+
if (value) {
55+
confirm.require({
56+
message: "Setting this file to 'Internal only' will allow all IDIR users " + 'to view and download it.',
57+
header: 'Set file to Internal only?',
58+
acceptLabel: 'Set to Internal only',
59+
rejectLabel: 'Cancel',
60+
accept: () => {
61+
permissionStore
62+
.addObjectIdpPermission(props.objectId, 'idir', 'READ')
63+
.then(() => {
64+
switchVal.value = true;
65+
toast.success('File set to Internal only', `"${props.objectName}" is now Internal only`);
66+
})
67+
.catch((e) => toast.error('Setting file to Internal only failed', e.response?.data.detail, { life: 0 }));
68+
},
69+
reject: () => (switchVal.value = false),
70+
onHide: () => (switchVal.value = false)
71+
});
72+
} else
73+
confirm.require({
74+
message:
75+
"Setting this file to private will remove 'Internal only' access. " +
76+
'Only users with permissions will be able to view or download the file.',
77+
header: 'Set file to private?',
78+
acceptLabel: 'Set to private',
79+
rejectLabel: 'Cancel',
80+
accept: () => {
81+
permissionStore
82+
.deleteObjectIdpPermission(props.objectId, 'idir', 'READ')
83+
.then(() => {
84+
switchVal.value = false;
85+
toast.success('File set to private', `"${props.objectName}" is no longer 'Internal only'`);
86+
})
87+
.catch((e) => toast.error('Setting file to private failed', e.response?.data.detail, { life: 0 }));
88+
},
89+
reject: () => (switchVal.value = true),
90+
onHide: () => (switchVal.value = true)
91+
});
92+
};
93+
94+
onMounted(async () => {
95+
await permissionStore.fetchObjectIdpPermissions({
96+
objectId: props.objectId,
97+
permCode: 'READ',
98+
idp: 'idir'
99+
});
100+
await permissionStore.fetchBucketIdpPermissions({
101+
bucketId: props.bucketId,
102+
permCode: 'READ',
103+
idp: 'idir'
104+
});
105+
});
106+
</script>
107+
108+
<template>
109+
<span
110+
v-tooltip="
111+
isToggleEnabled
112+
? ''
113+
: isPublic
114+
? 'Enabled by Public Sharing'
115+
: 'Change the folder\'s \'Internal only\' setting to update this file'
116+
"
117+
>
118+
<InputSwitch
119+
:model-value="switchVal"
120+
aria-label="Toggle to make file Internl only"
121+
:disabled="!isToggleEnabled"
122+
@update:model-value="toggleIdp($event)"
123+
/>
124+
</span>
125+
</template>

0 commit comments

Comments
 (0)