From 92a70d385ab4b87bfdbc9e0c9b201700d747202f Mon Sep 17 00:00:00 2001 From: Pascal Wengerter Date: Tue, 28 Jun 2022 14:35:31 +0200 Subject: [PATCH] Migrate deny-acl UI code from CERNbox --- .../enhancement-deny-subfolder-share | 5 ++++ .../Shares/Collaborators/RoleDropdown.vue | 17 +++++++++--- .../src/helpers/resource/resource.ts | 1 + .../web-app-files/src/helpers/resources.ts | 12 +++++++++ .../src/helpers/share/permission.ts | 1 + .../web-app-files/src/helpers/share/role.ts | 26 ++++++++++++++----- packages/web-pkg/src/constants/dav.ts | 1 + 7 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 changelog/unreleased/enhancement-deny-subfolder-share diff --git a/changelog/unreleased/enhancement-deny-subfolder-share b/changelog/unreleased/enhancement-deny-subfolder-share new file mode 100644 index 00000000000..3b03a10d83f --- /dev/null +++ b/changelog/unreleased/enhancement-deny-subfolder-share @@ -0,0 +1,5 @@ +Enhancement: Deny subfolders inside share + +Subfolders within non-link shares can now be denied for certain share receivers if the backend is capabable of negative ACLs. + +https://github.com/owncloud/web/pull/7190 diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/RoleDropdown.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/RoleDropdown.vue index ba5be30c0f0..98f404da9aa 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/RoleDropdown.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/RoleDropdown.vue @@ -144,6 +144,8 @@ export default { inviteLabel() { if (this.selectedRole.hasCustomPermissions) { return this.$gettext('Invite with custom permissions') + } else if (this.selectedRole.permissions().includes(SharePermissions.deny)) { + return this.$gettext('Deny access') } else { return this.$gettextInterpolate(this.$gettext('Invite as %{ name }'), { name: this.selectedRole.inlineLabel || '' @@ -168,7 +170,7 @@ export default { }, availableRoles() { if (this.resourceIsSpace) { - return SpacePeopleShareRoles.list() + return SpacePeopleShareRoles.list(this.resource.canDeny()) } if (this.resource.isReceivedShare() && this.resourceIsSharable && this.share) { @@ -180,7 +182,11 @@ export default { ) } - return PeopleShareRoles.list(this.resource.isFolder, this.allowCustomSharing !== false) + return PeopleShareRoles.list( + this.resource.isFolder, + this.allowCustomSharing !== false, + this.resource.canDeny() + ) }, availablePermissions() { if (this.resource.isReceivedShare() && this.resourceIsSharable && this.share) { @@ -211,9 +217,12 @@ export default { if (this.existingRole) { this.selectedRole = this.existingRole } else if (this.resourceIsSpace) { - this.selectedRole = SpacePeopleShareRoles.list()[0] + this.selectedRole = SpacePeopleShareRoles.list(this.resource.canDeny())[0] } else { - this.selectedRole = PeopleShareRoles.list(this.resource.isFolder)[0] + this.selectedRole = PeopleShareRoles.list( + this.resource.isFolder, + this.resource.canDeny() + )[0] } if (this.selectedRole.hasCustomPermissions) { diff --git a/packages/web-app-files/src/helpers/resource/resource.ts b/packages/web-app-files/src/helpers/resource/resource.ts index b4d35dcbc57..c1d1efe35b8 100644 --- a/packages/web-app-files/src/helpers/resource/resource.ts +++ b/packages/web-app-files/src/helpers/resource/resource.ts @@ -34,6 +34,7 @@ export interface Resource { canRename?(): boolean canBeDeleted?(): boolean canBeRestored?(): boolean + canDeny?(): boolean isReceivedShare?(): boolean isMounted?(): boolean diff --git a/packages/web-app-files/src/helpers/resources.ts b/packages/web-app-files/src/helpers/resources.ts index 03b3204ed7e..c2c2d100cd9 100644 --- a/packages/web-app-files/src/helpers/resources.ts +++ b/packages/web-app-files/src/helpers/resources.ts @@ -12,6 +12,7 @@ import { ShareTypes, SpacePeopleShareRoles, spaceRoleEditor, + spaceRoleDeny, spaceRoleManager, spaceRoleViewer } from './share' @@ -109,6 +110,9 @@ export function buildResource(resource): Resource { isReceivedShare: function () { return this.permissions.indexOf(DavPermission.Shared) >= 0 }, + canDeny: function () { + return this.permissions.indexOf(DavPermission.Deny) >= 0 + }, getDomSelector: () => extractDomSelector(id) } } @@ -238,6 +242,9 @@ export function buildSpace(space) { isReceivedShare: function () { return false }, + canDeny: function () { + return false // FIXME + }, getDomSelector: () => extractDomSelector(space.id) } } @@ -402,6 +409,7 @@ export function buildSharedResource( resource.canShare = () => true resource.canRename = () => true resource.canBeDeleted = () => true + resource.canDeny = () => SharePermissions.deny.enabled(share.permissions) } resource.extension = extractExtensionFromFile(resource) @@ -441,6 +449,10 @@ export function buildSpaceShare(s, storageId): Share { permissions = spaceRoleViewer.bitmask(true) role = spaceRoleViewer break + case spaceRoleDeny.name: + permissions = spaceRoleDeny.bitmask(true) + role = spaceRoleDeny + break } return { diff --git a/packages/web-app-files/src/helpers/share/permission.ts b/packages/web-app-files/src/helpers/share/permission.ts index 63dacc7e8ff..51277e2c7c6 100644 --- a/packages/web-app-files/src/helpers/share/permission.ts +++ b/packages/web-app-files/src/helpers/share/permission.ts @@ -37,6 +37,7 @@ export abstract class SharePermissions { static readonly create = new SharePermission('create', 4, $gettext('Create')) static readonly delete = new SharePermission('delete', 8, $gettext('Delete')) static readonly share = new SharePermission('share', 16, $gettext('Share')) + static readonly deny = new SharePermission('deny', 64, $gettext('Deny')) static permissionsToBitmask(permissions: SharePermission[]): number { return (permissions || []).reduce((b: number, p: SharePermission) => b | p.bit, 0) diff --git a/packages/web-app-files/src/helpers/share/role.ts b/packages/web-app-files/src/helpers/share/role.ts index 1f000d55144..93106c714ff 100644 --- a/packages/web-app-files/src/helpers/share/role.ts +++ b/packages/web-app-files/src/helpers/share/role.ts @@ -172,6 +172,13 @@ export const peopleRoleCustomFolder = new CustomShareRole( SharePermissions.share ] ) +export const peopleRoleDenyFolder = new PeopleShareRole( + 'deny', + true, + $gettext('Deny'), + $gettext('deny'), + [SharePermissions.deny] +) export const linkRoleViewerFile = new LinkShareRole( 'viewer', false, @@ -214,6 +221,9 @@ export const linkRoleUploaderFolder = new LinkShareRole( $gettext('uploader'), [SharePermissions.create] ) +export const spaceRoleDeny = new SpaceShareRole('deny', false, $gettext('Deny'), $gettext('deny'), [ + SharePermissions.deny +]) export const spaceRoleViewer = new SpaceShareRole( 'viewer', false, @@ -245,12 +255,13 @@ export const spaceRoleManager = new SpaceShareRole( export abstract class SpacePeopleShareRoles { static readonly all = [spaceRoleViewer, spaceRoleEditor, spaceRoleManager] - static list(): ShareRole[] { - return this.all + static list(canDeny: boolean = false): ShareRole[] { + return [...this.all, ...(canDeny ? [spaceRoleDeny] : [])] } static getByBitmask(bitmask: number): ShareRole { - return this.all.find((r) => r.bitmask(true) === bitmask) + return [...this.all, spaceRoleDeny] // Retrieve all possible options always, even if deny is not enabled + .find((r) => r.bitmask(true) === bitmask) } } @@ -264,8 +275,8 @@ export abstract class PeopleShareRoles { static readonly allWithCustom = [...this.all, peopleRoleCustomFile, peopleRoleCustomFolder] - static list(isFolder: boolean, hasCustom = true): ShareRole[] { - return (hasCustom ? this.allWithCustom : this.all).filter((r) => r.folder === isFolder) + static list(isFolder: boolean, hasCustom: boolean = true, canDeny: boolean = false): ShareRole[] { + return [...(hasCustom ? this.allWithCustom : this.all), ...(canDeny ? [peopleRoleDenyFolder] : [])].filter((r) => r.folder === isFolder) } static custom(isFolder: boolean): ShareRole { @@ -273,7 +284,7 @@ export abstract class PeopleShareRoles { } static getByBitmask(bitmask: number, isFolder: boolean, allowSharing: boolean): ShareRole { - const role = this.allWithCustom + const role = [...this.allWithCustom, peopleRoleDenyFolder] // Retrieve all possible options always, even if deny is not enabled .filter((r) => !r.hasCustomPermissions) .find((r) => r.folder === isFolder && r.bitmask(allowSharing) === bitmask) return role || this.custom(isFolder) @@ -339,7 +350,8 @@ const shareRoleDescriptions = { [peopleRoleEditorFolder.bitmask(false)]: $gettext('Upload, edit, delete, download and preview'), [peopleRoleEditorFolder.bitmask(true)]: $gettext( 'Upload, edit, delete, download, preview and share' - ) + ), + [peopleRoleDenyFolder.bitmask(false)]: $gettext('Deny access') } /** diff --git a/packages/web-pkg/src/constants/dav.ts b/packages/web-pkg/src/constants/dav.ts index 197382fdfd5..f5bb8e4fde2 100644 --- a/packages/web-pkg/src/constants/dav.ts +++ b/packages/web-pkg/src/constants/dav.ts @@ -8,6 +8,7 @@ export abstract class DavPermission { static readonly Updateable: string = 'NV' static readonly FileUpdateable: string = 'W' static readonly FolderCreateable: string = 'CK' + static readonly Deny: string = 'Z' } export abstract class DavProperty {