Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLouis Chemineau <louis@chmn.me>2022-02-02 18:10:52 +0300
committerLouis Chemineau <louis@chmn.me>2022-02-15 13:54:17 +0300
commitda435b1e67930e85fc30fd1b94c6214caa086f4f (patch)
tree53eab82dc4e89e8da2e94185588e45a1f21687f7 /apps/files_sharing/src/components
parentacba237ec56a8987e246d1046bd8f5b763db1336 (diff)
Support CRUD share permissions
Signed-off-by: Louis Chemineau <louis@chmn.me>
Diffstat (limited to 'apps/files_sharing/src/components')
-rw-r--r--apps/files_sharing/src/components/SharePermissionsEditor.vue291
-rw-r--r--apps/files_sharing/src/components/SharingEntryLink.vue119
2 files changed, 303 insertions, 107 deletions
diff --git a/apps/files_sharing/src/components/SharePermissionsEditor.vue b/apps/files_sharing/src/components/SharePermissionsEditor.vue
new file mode 100644
index 00000000000..41c8c0b5cce
--- /dev/null
+++ b/apps/files_sharing/src/components/SharePermissionsEditor.vue
@@ -0,0 +1,291 @@
+<!--
+ - @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me>
+ -
+ - @author Louis Chmn <louis@chmn.me>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <span>
+ <!-- file -->
+ <ActionCheckbox v-if="!isFolder"
+ :checked="shareHasPermissions(atomicPermissions.UPDATE)"
+ :disabled="saving"
+ @update:checked="toggleSharePermissions(atomicPermissions.UPDATE)">
+ {{ t('files_sharing', 'Allow editing') }}
+ </ActionCheckbox>
+
+ <!-- folder -->
+ <template v-if="isFolder && fileHasCreatePermission && config.isPublicUploadEnabled">
+ <template v-if="!showCustomPermissionsForm">
+ <ActionRadio :checked="sharePermissionEqual(bundledPermissions.READ_ONLY)"
+ :value="bundledPermissions.READ_ONLY"
+ :name="randomFormName"
+ :disabled="saving"
+ @change="setSharePermissions(bundledPermissions.READ_ONLY)">
+ {{ t('files_sharing', 'Read only') }}
+ </ActionRadio>
+
+ <ActionRadio :checked="sharePermissionEqual(bundledPermissions.UPLOAD_AND_UPDATE)"
+ :value="bundledPermissions.UPLOAD_AND_UPDATE"
+ :disabled="saving"
+ :name="randomFormName"
+ @change="setSharePermissions(bundledPermissions.UPLOAD_AND_UPDATE)">
+ {{ t('files_sharing', 'Allow upload and editing') }}
+ </ActionRadio>
+ <ActionRadio :checked="sharePermissionEqual(bundledPermissions.FILE_DROP)"
+ :value="bundledPermissions.FILE_DROP"
+ :disabled="saving"
+ :name="randomFormName"
+ class="sharing-entry__action--public-upload"
+ @change="setSharePermissions(bundledPermissions.FILE_DROP)">
+ {{ t('files_sharing', 'File drop (upload only)') }}
+ </ActionRadio>
+
+ <!-- custom permissions button -->
+ <ActionButton :title="t('files_sharing', 'Custom permissions')"
+ @click="showCustomPermissionsForm = true">
+ <template #icon>
+ <Tune />
+ </template>
+ {{ sharePermissionsIsBundle ? "" : sharePermissionsSummary }}
+ </ActionButton>
+ </template>
+
+ <!-- custom permissions -->
+ <span v-else :class="{error: !sharePermissionsSetIsValid}">
+ <ActionCheckbox :checked="shareHasPermissions(atomicPermissions.READ)"
+ :disabled="saving || !canToggleSharePermissions(atomicPermissions.READ)"
+ @update:checked="toggleSharePermissions(atomicPermissions.READ)">
+ {{ t('files_sharing', 'Read') }}
+ </ActionCheckbox>
+ <ActionCheckbox :checked="shareHasPermissions(atomicPermissions.CREATE)"
+ :disabled="saving || !canToggleSharePermissions(atomicPermissions.CREATE)"
+ @update:checked="toggleSharePermissions(atomicPermissions.CREATE)">
+ {{ t('files_sharing', 'Upload') }}
+ </ActionCheckbox>
+ <ActionCheckbox :checked="shareHasPermissions(atomicPermissions.UPDATE)"
+ :disabled="saving || !canToggleSharePermissions(atomicPermissions.UPDATE)"
+ @update:checked="toggleSharePermissions(atomicPermissions.UPDATE)">
+ {{ t('files_sharing', 'Edit') }}
+ </ActionCheckbox>
+ <ActionCheckbox :checked="shareHasPermissions(atomicPermissions.DELETE)"
+ :disabled="saving || !canToggleSharePermissions(atomicPermissions.DELETE)"
+ @update:checked="toggleSharePermissions(atomicPermissions.DELETE)">
+ {{ t('files_sharing', 'Delete') }}
+ </ActionCheckbox>
+
+ <ActionButton @click="showCustomPermissionsForm = false">
+ <template #icon>
+ <ChevronLeft />
+ </template>
+ {{ t('files_sharing', 'Bundled permissions') }}
+ </ActionButton>
+ </span>
+ </template>
+ </span>
+</template>
+
+<script>
+import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
+import ActionRadio from '@nextcloud/vue/dist/Components/ActionRadio'
+import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox'
+
+import SharesMixin from '../mixins/SharesMixin'
+import {
+ ATOMIC_PERMISSIONS,
+ BUNDLED_PERMISSIONS,
+ hasPermissions,
+ permissionsSetIsValid,
+ togglePermissions,
+ canTogglePermissions,
+} from '../lib/SharePermissionsToolBox'
+
+import Tune from 'vue-material-design-icons/Tune'
+import ChevronLeft from 'vue-material-design-icons/ChevronLeft'
+
+export default {
+ name: 'SharePermissionsEditor',
+
+ components: {
+ ActionButton,
+ ActionCheckbox,
+ ActionRadio,
+ Tune,
+ ChevronLeft,
+ },
+
+ mixins: [SharesMixin],
+
+ data() {
+ return {
+ randomFormName: Math.random().toString(27).substring(2),
+
+ showCustomPermissionsForm: false,
+
+ atomicPermissions: ATOMIC_PERMISSIONS,
+ bundledPermissions: BUNDLED_PERMISSIONS,
+ }
+ },
+
+ computed: {
+ /**
+ * Return the summary of custom checked permissions.
+ *
+ * @return {string}
+ */
+ sharePermissionsSummary() {
+ return Object.values(this.atomicPermissions)
+ .filter(permission => this.shareHasPermissions(permission))
+ .map(permission => {
+ switch (permission) {
+ case this.atomicPermissions.CREATE:
+ return this.t('files_sharing', 'Upload')
+ case this.atomicPermissions.READ:
+ return this.t('files_sharing', 'Read')
+ case this.atomicPermissions.UPDATE:
+ return this.t('files_sharing', 'Edit')
+ case this.atomicPermissions.DELETE:
+ return this.t('files_sharing', 'Delete')
+ default:
+ return ''
+ }
+ })
+ .join(', ')
+ },
+
+ /**
+ * Return whether the share's permission is a bundle.
+ *
+ * @return {boolean}
+ */
+ sharePermissionsIsBundle() {
+ return Object.values(BUNDLED_PERMISSIONS)
+ .map(bundle => this.sharePermissionEqual(bundle))
+ .filter(isBundle => isBundle)
+ .length > 0
+ },
+
+ /**
+ * Return whether the share's permission is valid.
+ *
+ * @return {boolean}
+ */
+ sharePermissionsSetIsValid() {
+ return permissionsSetIsValid(this.share.permissions)
+ },
+
+ /**
+ * Is the current share a folder ?
+ * TODO: move to a proper FileInfo model?
+ *
+ * @return {boolean}
+ */
+ isFolder() {
+ return this.fileInfo.type === 'dir'
+ },
+
+ /**
+ * Does the current file/folder have create permissions.
+ * TODO: move to a proper FileInfo model?
+ *
+ * @return {boolean}
+ */
+ fileHasCreatePermission() {
+ return !!(this.fileInfo.permissions & ATOMIC_PERMISSIONS.CREATE)
+ },
+ },
+
+ mounted() {
+ // Show the Custom Permissions view on open if the permissions set is not a bundle.
+ this.showCustomPermissionsForm = !this.sharePermissionsIsBundle
+ },
+
+ methods: {
+ /**
+ * Return whether the share has the exact given permissions.
+ *
+ * @param {number} permissions - the permissions to check.
+ *
+ * @return {boolean}
+ */
+ sharePermissionEqual(permissions) {
+ // We use the share's permission without PERMISSION_SHARE as it is not relevant here.
+ return (this.share.permissions & ~ATOMIC_PERMISSIONS.SHARE) === permissions
+ },
+
+ /**
+ * Return whether the share has the given permissions.
+ *
+ * @param {number} permissions - the permissions to check.
+ *
+ * @return {boolean}
+ */
+ shareHasPermissions(permissions) {
+ return hasPermissions(this.share.permissions, permissions)
+ },
+
+ /**
+ * Set the share permissions to the given permissions.
+ *
+ * @param {number} permissions - the permissions to set.
+ *
+ * @return {void}
+ */
+ setSharePermissions(permissions) {
+ this.share.permissions = permissions
+ this.queueUpdate('permissions')
+ },
+
+ /**
+ * Return whether some given permissions can be toggled.
+ *
+ * @param {number} permissionsToToggle - the permissions to toggle.
+ *
+ * @return {boolean}
+ */
+ canToggleSharePermissions(permissionsToToggle) {
+ return canTogglePermissions(this.share.permissions, permissionsToToggle)
+ },
+
+ /**
+ * Toggle a given permission.
+ *
+ * @param {number} permissions - the permissions to toggle.
+ *
+ * @return {void}
+ */
+ toggleSharePermissions(permissions) {
+ this.share.permissions = togglePermissions(this.share.permissions, permissions)
+
+ if (!this.permissionsSetIsValid(this.share.permissions)) {
+ return
+ }
+
+ this.queueUpdate('permissions')
+ },
+ },
+}
+</script>
+<style lang="scss" scoped>
+.error {
+ ::v-deep .action-checkbox__label:before {
+ border: 1px solid var(--color-error);
+ }
+}
+</style>
diff --git a/apps/files_sharing/src/components/SharingEntryLink.vue b/apps/files_sharing/src/components/SharingEntryLink.vue
index 9fefa9b6f90..52215d37ec8 100644
--- a/apps/files_sharing/src/components/SharingEntryLink.vue
+++ b/apps/files_sharing/src/components/SharingEntryLink.vue
@@ -150,39 +150,12 @@
@submit="onLabelSubmit">
{{ t('files_sharing', 'Share label') }}
</ActionInput>
- <!-- folder -->
- <template v-if="isFolder && fileHasCreatePermission && config.isPublicUploadEnabled">
- <ActionRadio :checked="sharePermissions === publicUploadRValue"
- :value="publicUploadRValue"
- :name="randomId"
- :disabled="saving"
- @change="togglePermissions">
- {{ t('files_sharing', 'Read only') }}
- </ActionRadio>
- <ActionRadio :checked="sharePermissions === publicUploadRWValue"
- :value="publicUploadRWValue"
- :disabled="saving"
- :name="randomId"
- @change="togglePermissions">
- {{ t('files_sharing', 'Allow upload and editing') }}
- </ActionRadio>
- <ActionRadio :checked="sharePermissions === publicUploadWValue"
- :value="publicUploadWValue"
- :disabled="saving"
- :name="randomId"
- class="sharing-entry__action--public-upload"
- @change="togglePermissions">
- {{ t('files_sharing', 'File drop (upload only)') }}
- </ActionRadio>
- </template>
-
- <!-- file -->
- <ActionCheckbox v-if="!isFolder"
- :checked.sync="canUpdate"
- :disabled="saving"
- @change="queueUpdate('permissions')">
- {{ t('files_sharing', 'Allow editing') }}
- </ActionCheckbox>
+
+ <SharePermissionsEditor :can-reshare="canReshare"
+ :share.sync="share"
+ :file-info="fileInfo" />
+
+ <ActionSeparator />
<ActionCheckbox :checked.sync="share.hideDownload"
:disabled="saving"
@@ -282,6 +255,8 @@
@submit="onNoteSubmit" />
</template>
+ <ActionSeparator />
+
<!-- external actions -->
<ExternalShareAction v-for="action in externalLinkActions"
:id="action.id"
@@ -336,14 +311,15 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox'
import ActionInput from '@nextcloud/vue/dist/Components/ActionInput'
import ActionLink from '@nextcloud/vue/dist/Components/ActionLink'
-import ActionRadio from '@nextcloud/vue/dist/Components/ActionRadio'
import ActionText from '@nextcloud/vue/dist/Components/ActionText'
+import ActionSeparator from '@nextcloud/vue/dist/Components/ActionSeparator'
import ActionTextEditable from '@nextcloud/vue/dist/Components/ActionTextEditable'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import ExternalShareAction from './ExternalShareAction'
+import SharePermissionsEditor from './SharePermissionsEditor'
import GeneratePassword from '../utils/GeneratePassword'
import Share from '../models/Share'
import SharesMixin from '../mixins/SharesMixin'
@@ -355,13 +331,14 @@ export default {
Actions,
ActionButton,
ActionCheckbox,
- ActionRadio,
ActionInput,
ActionLink,
ActionText,
ActionTextEditable,
+ ActionSeparator,
Avatar,
ExternalShareAction,
+ SharePermissionsEditor,
},
directives: {
@@ -385,10 +362,6 @@ export default {
// Are we waiting for password/expiration date
pending: false,
- publicUploadRWValue: OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ | OC.PERMISSION_DELETE,
- publicUploadRValue: OC.PERMISSION_READ,
- publicUploadWValue: OC.PERMISSION_CREATE,
-
ExternalLegacyLinkActions: OCA.Sharing.ExternalLinkActions.state,
ExternalShareActions: OCA.Sharing.ExternalShareActions.state,
}
@@ -396,27 +369,6 @@ export default {
computed: {
/**
- * Return the current share permissions
- * We always ignore the SHARE permission as this is used for the
- * federated sharing.
- *
- * @return {number}
- */
- sharePermissions() {
- return this.share.permissions & ~OC.PERMISSION_SHARE
- },
- /**
- * Generate a unique random id for this SharingEntryLink only
- * This allows ActionRadios to have the same name prop
- * but not to impact others SharingEntryLink
- *
- * @return {string}
- */
- randomId() {
- return Math.random().toString(27).substr(2)
- },
-
- /**
* Link share label
*
* @return {string}
@@ -580,22 +532,6 @@ export default {
return this.config.isDefaultExpireDateEnforced && this.share && !this.share.id
},
- /**
- * Can the recipient edit the file ?
- *
- * @return {boolean}
- */
- canUpdate: {
- get() {
- return this.share.hasUpdatePermission
- },
- set(enabled) {
- this.share.permissions = enabled
- ? OC.PERMISSION_READ | OC.PERMISSION_UPDATE
- : OC.PERMISSION_READ
- },
- },
-
// if newPassword exists, but is empty, it means
// the user deleted the original password
hasUnsavedPassword() {
@@ -603,26 +539,6 @@ export default {
},
/**
- * Is the current share a folder ?
- * TODO: move to a proper FileInfo model?
- *
- * @return {boolean}
- */
- isFolder() {
- return this.fileInfo.type === 'dir'
- },
-
- /**
- * Does the current file/folder have create permissions
- * TODO: move to a proper FileInfo model?
- *
- * @return {boolean}
- */
- fileHasCreatePermission() {
- return !!(this.fileInfo.permissions & OC.PERMISSION_CREATE)
- },
-
- /**
* Return the public share link
*
* @return {string}
@@ -810,17 +726,6 @@ export default {
},
/**
- * On permissions change
- *
- * @param {Event} event js event
- */
- togglePermissions(event) {
- const permissions = parseInt(event.target.value, 10)
- this.share.permissions = permissions
- this.queueUpdate('permissions')
- },
-
- /**
* Label changed, let's save it to a different key
*
* @param {string} label the share label