diff --git a/changelog/unreleased/enhancement-optional-roles b/changelog/unreleased/enhancement-optional-roles new file mode 100644 index 00000000000..8669509e0f3 --- /dev/null +++ b/changelog/unreleased/enhancement-optional-roles @@ -0,0 +1,7 @@ +Enhancement: Optional Contributor role and configurable resharing permissions + +We've added 2 new capabilities that modify the sharing Roles: +- `files_sharing.resharing_default` (default true): wether the resharing bit should be added to the normal roles (viewer, editor, etc) by default when re-sharing is enabled systemwise. If default is set false, users can still set resharing in the custom permissions role. +- `files_sharing.public.can_contribute` (default true): enables or disables the Contributor role for public links + +https://github.com/owncloud/web/pull/7965 diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue index 12d121074de..6f3fb8c142a 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue @@ -85,6 +85,7 @@ import { useCapabilityFilesSharingAllowCustomPermissions, useCapabilityFilesSharingCanDenyAccess, useCapabilityFilesSharingResharing, + useCapabilityFilesSharingResharingDefault, useCapabilityShareJailEnabled, useStore } from 'web-pkg/src/composables' @@ -124,6 +125,7 @@ export default defineComponent({ return { resource: inject('resource'), hasResharing: useCapabilityFilesSharingResharing(store), + resharingDefault: useCapabilityFilesSharingResharingDefault(store), hasShareJail: useCapabilityShareJailEnabled(store), hasRoleCustomPermissions: useCapabilityFilesSharingAllowCustomPermissions(store), hasRoleDenyAccess: useCapabilityFilesSharingCanDenyAccess(store), @@ -302,7 +304,9 @@ export default defineComponent({ const bitmask = this.selectedRole.hasCustomPermissions ? SharePermissions.permissionsToBitmask(this.customPermissions) : SharePermissions.permissionsToBitmask( - this.selectedRole.permissions(this.hasResharing || this.resourceIsSpace) + this.selectedRole.permissions( + (this.hasResharing && this.resharingDefault) || this.resourceIsSpace + ) ) let path = this.resource.path diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue index 0f15d9d536a..0bc9a85ee82 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue @@ -107,7 +107,10 @@ import { DateTime } from 'luxon' import EditDropdown from './EditDropdown.vue' import RoleDropdown from './RoleDropdown.vue' import { SharePermissions, ShareTypes } from 'web-client/src/helpers/share' -import { useCapabilityFilesSharingResharing } from 'web-pkg/src/composables' +import { + useCapabilityFilesSharingResharing, + useCapabilityFilesSharingResharingDefault +} from 'web-pkg/src/composables' import { extractDomSelector } from 'web-client/src/helpers/resource' import { defineComponent } from 'vue' import * as uuid from 'uuid' @@ -138,6 +141,7 @@ export default defineComponent({ setup() { return { hasResharing: useCapabilityFilesSharingResharing(), + resharingDefault: useCapabilityFilesSharingResharingDefault(), ...useGraphClient() } }, @@ -357,7 +361,9 @@ export default defineComponent({ const bitmask = role.hasCustomPermissions ? SharePermissions.permissionsToBitmask(permissions) : SharePermissions.permissionsToBitmask( - role.permissions(this.hasResharing || this.isAnySpaceShareType) + role.permissions( + (this.hasResharing && this.resharingDefault) || this.isAnySpaceShareType + ) ) const changeMethod = this.isAnySpaceShareType ? this.changeSpaceMember : this.changeShare changeMethod({ 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 94eef07c252..2a318402de7 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 @@ -42,7 +42,10 @@ > - + @@ -117,6 +120,7 @@ import { defineComponent, inject, PropType } from 'vue' import { useCapabilityFilesSharingAllowCustomPermissions, useCapabilityFilesSharingCanDenyAccess, + useCapabilityFilesSharingResharingDefault, useStore } from 'web-pkg/src/composables' import { Resource } from 'web-client' @@ -152,7 +156,8 @@ export default defineComponent({ resource: inject('resource'), incomingParentShare: inject('incomingParentShare'), hasRoleDenyAccess: useCapabilityFilesSharingCanDenyAccess(store), - hasRoleCustomPermissions: useCapabilityFilesSharingAllowCustomPermissions(store) + hasRoleCustomPermissions: useCapabilityFilesSharingAllowCustomPermissions(store), + resharingDefault: useCapabilityFilesSharingResharingDefault(store) } }, data() { @@ -214,7 +219,7 @@ export default defineComponent({ return this.resource.type === 'space' }, defaultCustomPermissions() { - return [...this.selectedRole.permissions(this.allowSharePermission)] + return [...this.selectedRole.permissions(this.allowSharePermission && this.resharingDefault)] } }, @@ -263,7 +268,7 @@ export default defineComponent({ return } this.selectedRole = role - this.customPermissions = role.permissions(this.allowSharePermission) + this.customPermissions = role.permissions(this.allowSharePermission && this.resharingDefault) this.publishChange() }, @@ -281,7 +286,7 @@ export default defineComponent({ this.selectedRole = PeopleShareRoles.getByBitmask( bitmask, this.resource.isFolder, - this.allowSharePermission + this.allowSharePermission && this.resharingDefault ) this.publishChange() }, diff --git a/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue b/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue index 46b5bedc63b..ef12e4e76d5 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue @@ -116,6 +116,7 @@ import { useCapabilityShareJailEnabled, useCapabilityFilesSharingResharing, useCapabilityFilesSharingPublicCanEdit, + useCapabilityFilesSharingPublicCanContribute, useCapabilityFilesSharingPublicAlias } from 'web-pkg/src/composables' import { shareViaLinkHelp, shareViaIndirectLinkHelp } from '../../../helpers/contextualHelpers' @@ -153,6 +154,7 @@ export default defineComponent({ hasShareJail: useCapabilityShareJailEnabled(), hasResharing: useCapabilityFilesSharingResharing(), hasPublicLinkEditing: useCapabilityFilesSharingPublicCanEdit(), + hasPublicLinkContribute: useCapabilityFilesSharingPublicCanContribute(), hasPublicLinkAliasSupport: useCapabilityFilesSharingPublicAlias(), indirectLinkListCollapsed, linkListCollapsed @@ -561,6 +563,7 @@ export default defineComponent({ this.incomingParentShare.permissions, this.resource.isFolder, this.hasPublicLinkEditing, + this.hasPublicLinkContribute, this.hasPublicLinkAliasSupport, !!link.password ) @@ -569,6 +572,7 @@ export default defineComponent({ return LinkShareRoles.list( this.resource.isFolder, this.hasPublicLinkEditing, + this.hasPublicLinkContribute, this.hasPublicLinkAliasSupport, !!link.password ) diff --git a/packages/web-app-files/src/store/actions.ts b/packages/web-app-files/src/store/actions.ts index 814a82e3251..8d9db0176fb 100644 --- a/packages/web-app-files/src/store/actions.ts +++ b/packages/web-app-files/src/store/actions.ts @@ -22,7 +22,10 @@ import { Language } from 'vue3-gettext' import { DavProperty } from 'web-client/src/webdav/constants' const allowSharePermissions = (getters) => { - return get(getters, `capabilities.files_sharing.resharing`, true) + return ( + get(getters, `capabilities.files_sharing.resharing`, true) && + get(getters, `capabilities.files_sharing.resharing_default`, true) + ) } export default { diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/DetailsAndEdit.spec.ts b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/DetailsAndEdit.spec.ts index 9ee426ad9fd..59ad472301d 100644 --- a/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/DetailsAndEdit.spec.ts +++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/DetailsAndEdit.spec.ts @@ -9,7 +9,7 @@ import { import { mockDeep } from 'jest-mock-extended' import { Resource } from 'web-client' -const availableRoleOptions = LinkShareRoles.list(false, true, true) +const availableRoleOptions = LinkShareRoles.list(false, true, true, true) const exampleLink = { name: 'Example link', diff --git a/packages/web-client/src/helpers/share/role.ts b/packages/web-client/src/helpers/share/role.ts index fc02c8d88d3..ee3dff33cdc 100644 --- a/packages/web-client/src/helpers/share/role.ts +++ b/packages/web-client/src/helpers/share/role.ts @@ -356,30 +356,26 @@ export abstract class PeopleShareRoles { } export abstract class LinkShareRoles { - static readonly all = [ - linkRoleViewerFile, - linkRoleViewerFolder, - linkRoleContributorFolder, - linkRoleEditorFolder, - linkRoleUploaderFolder - ] - static list( isFolder: boolean, canEditFile = false, + canContribute = false, hasAliasLinks = false, hasPassword = false ): ShareRole[] { return [ ...(hasAliasLinks && !hasPassword ? [linkRoleInternalFile, linkRoleInternalFolder] : []), - ...this.all, + linkRoleViewerFile, + linkRoleViewerFolder, + ...(canContribute ? [linkRoleContributorFolder] : []), + linkRoleEditorFolder, + linkRoleUploaderFolder, ...(canEditFile ? [linkRoleEditorFile] : []) ].filter((r) => r.folder === isFolder) } static getByBitmask(bitmask: number, isFolder: boolean): ShareRole { - return [...this.all, linkRoleEditorFile, linkRoleInternalFile, linkRoleInternalFolder] // Always return all roles - .find((r) => r.folder === isFolder && r.bitmask(false) === bitmask) + return this.list(isFolder, true, true, true, false).find((r) => r.bitmask(false) === bitmask) } /** @@ -387,6 +383,7 @@ export abstract class LinkShareRoles { * @param bitmask * @param isFolder * @param canEditFile + * @param canContribute * @param hasAliasLinks * @param hasPassword */ @@ -394,16 +391,15 @@ export abstract class LinkShareRoles { bitmask: number, isFolder: boolean, canEditFile = false, + canContribute = false, hasAliasLinks = false, hasPassword = false ): ShareRole[] { - return [ - ...(hasAliasLinks && !hasPassword ? [linkRoleInternalFile, linkRoleInternalFolder] : []), - ...this.all, - ...(canEditFile ? [linkRoleEditorFile] : []) - ].filter((r) => { - return r.folder === isFolder && bitmask === (bitmask | r.bitmask(false)) - }) + return this.list(isFolder, canEditFile, canContribute, hasAliasLinks, hasPassword).filter( + (r) => { + return bitmask === (bitmask | r.bitmask(false)) + } + ) } } diff --git a/packages/web-client/tests/unit/helpers/share/role.spec.ts b/packages/web-client/tests/unit/helpers/share/role.spec.ts index ffde11dec76..883a154f652 100644 --- a/packages/web-client/tests/unit/helpers/share/role.spec.ts +++ b/packages/web-client/tests/unit/helpers/share/role.spec.ts @@ -2,9 +2,12 @@ import { linkRoleContributorFolder, linkRoleEditorFolder, linkRoleUploaderFolder, + linkRoleInternalFile, + linkRoleInternalFolder, linkRoleViewerFile, linkRoleViewerFolder, LinkShareRoles, + linkRoleEditorFile, peopleRoleCustomFile, peopleRoleCustomFolder, peopleRoleEditorFile, @@ -219,18 +222,24 @@ describe('roles', () => { 'all folder related share roles', { folder: true, - result: LinkShareRoles.all.filter((r) => r.folder === true) + result: [ + linkRoleInternalFolder, + linkRoleViewerFolder, + linkRoleContributorFolder, + linkRoleEditorFolder, + linkRoleUploaderFolder + ] } ], [ 'all file related share roles', { folder: false, - result: LinkShareRoles.all.filter((r) => r.folder === false) + result: [linkRoleInternalFile, linkRoleViewerFile, linkRoleEditorFile] } ] ])('%s', (name: string, { folder, result }) => { - expect(LinkShareRoles.list(folder)).toEqual(result) + expect(LinkShareRoles.list(folder, true, true, true, false)).toEqual(result) }) }) describe('getByBitmask', () => { diff --git a/packages/web-pkg/src/composables/capability/useCapability.ts b/packages/web-pkg/src/composables/capability/useCapability.ts index 368bdc70013..94918e5253a 100644 --- a/packages/web-pkg/src/composables/capability/useCapability.ts +++ b/packages/web-pkg/src/composables/capability/useCapability.ts @@ -33,6 +33,10 @@ export const useCapabilityFilesSharingResharing = createCapabilityComposable( 'files_sharing.resharing', true ) +export const useCapabilityFilesSharingResharingDefault = createCapabilityComposable( + 'files_sharing.resharing_default', + true +) export const useCapabilitySpacesEnabled = createCapabilityComposable('spaces.enabled', false) export const useCapabilityProjectSpacesEnabled = createCapabilityComposable( @@ -69,6 +73,10 @@ export const useCapabilityFilesSharingPublicCanEdit = createCapabilityComposable 'files_sharing.public.can_edit', false ) +export const useCapabilityFilesSharingPublicCanContribute = createCapabilityComposable( + 'files_sharing.public.can_contribute', + true +) export const useCapabilityFilesSharingPublicAlias = createCapabilityComposable( 'files_sharing.public.alias', false