Skip to content

Commit

Permalink
Merge pull request #49514 from nextcloud/feat/restrict-tag-creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Altahrim authored Jan 22, 2025
2 parents 49cfd30 + 00fbbf9 commit c10b8e2
Show file tree
Hide file tree
Showing 28 changed files with 324 additions and 44 deletions.
7 changes: 5 additions & 2 deletions apps/dav/lib/SystemTag/SystemTagPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ISystemTagObjectMapper;
use OCP\SystemTag\TagAlreadyExistsException;
use OCP\SystemTag\TagCreationForbiddenException;
use OCP\Util;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Conflict;
Expand Down Expand Up @@ -189,6 +190,8 @@ private function createTag($data, $contentType = 'application/json') {
return $tag;
} catch (TagAlreadyExistsException $e) {
throw new Conflict('Tag already exists', 0, $e);
} catch (TagCreationForbiddenException $e) {
throw new Forbidden('You don’t have right to create tags', 0, $e);
}
}

Expand Down Expand Up @@ -376,7 +379,7 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
if (!$node instanceof SystemTagNode && !$node instanceof SystemTagObjectType) {
return;
}

$propPatch->handle([self::OBJECTIDS_PROPERTYNAME], function ($props) use ($node) {
if (!$node instanceof SystemTagObjectType) {
return false;
Expand All @@ -394,7 +397,7 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
if (count($objectTypes) !== 1 || $objectTypes[0] !== $node->getName()) {
throw new BadRequest('Invalid object-ids property. All object types must be of the same type: ' . $node->getName());
}

$this->tagMapper->setObjectIdsForTag($node->getSystemTag()->getId(), $node->getName(), array_keys($objects));
}

Expand Down
4 changes: 4 additions & 0 deletions apps/settings/lib/Settings/Admin/Server.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down Expand Up @@ -53,6 +54,9 @@ public function getForm() {
$this->initialStateService->provideInitialState('profileEnabledGlobally', $this->profileManager->isProfileEnabled());
$this->initialStateService->provideInitialState('profileEnabledByDefault', $this->isProfileEnabledByDefault($this->config));

// Basic settings
$this->initialStateService->provideInitialState('restrictSystemTagsCreationToAdmin', $this->appConfig->getValueString('systemtags', 'restrict_creation_to_admin', 'true'));

return new TemplateResponse('settings', 'settings/admin/server', [
'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
], '');
Expand Down
3 changes: 1 addition & 2 deletions apps/settings/tests/Settings/Admin/ServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ public function testGetForm(): void {
$this->appConfig
->expects($this->any())
->method('getValueString')
->with('core', 'backgroundjobs_mode', 'ajax')
->willReturn('ajax');
->willReturnCallback(fn ($a, $b, $default) => $default);
$this->profileManager
->expects($this->exactly(2))
->method('isProfileEnabled')
Expand Down
4 changes: 2 additions & 2 deletions apps/systemtags/src/components/SystemTagForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
aria-labelledby="system-tag-form-heading"
@submit.prevent="handleSubmit"
@reset="reset">
<h3 id="system-tag-form-heading">
<h4 id="system-tag-form-heading">
{{ t('systemtags', 'Create or edit tags') }}
</h3>
</h4>

<div class="system-tag-form__group">
<label for="system-tags-input">{{ t('systemtags', 'Search for a tag to edit') }}</label>
Expand Down
6 changes: 6 additions & 0 deletions apps/systemtags/src/components/SystemTags.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import {
setTagForFile,
} from '../services/files.js'

import { loadState } from '@nextcloud/initial-state'

import type { Tag, TagWithId } from '../types.js'

export default Vue.extend({
Expand Down Expand Up @@ -188,6 +190,10 @@ export default Vue.extend({
this.sortedTags.unshift(createdTag)
this.selectedTags.push(createdTag)
} catch (error) {
if(loadState('settings', 'restrictSystemTagsCreationToAdmin', '0') === '1') {
showError(t('systemtags', 'System admin disabled tag creation. You can only use existing ones.'))
return
}
showError(t('systemtags', 'Failed to create tag'))
}
this.loading = false
Expand Down
78 changes: 78 additions & 0 deletions apps/systemtags/src/components/SystemTagsCreationControl.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!--
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<template>
<div id="system-tags-creation-control">
<h4 class="inlineblock">
{{ t('settings', 'System tag creation') }}
</h4>

<p class="settings-hint">
{{ t('settings', 'If enabled, regular accounts will be restricted from creating new tags but will still be able to assign and remove them from their files.') }}
</p>

<NcCheckboxRadioSwitch type="switch"
:checked.sync="systemTagsCreationRestrictedToAdmin"
@update:checked="updateSystemTagsDefault">
{{ t('settings', 'Restrict tag creation to admins only') }}
</NcCheckboxRadioSwitch>
</div>
</template>

<script lang="ts">
import { loadState } from '@nextcloud/initial-state'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import logger from '../logger.ts'
import { updateSystemTagsAdminRestriction } from '../services/api.js'

import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'

export default {
name: 'SystemTagsCreationControl',

components: {
NcCheckboxRadioSwitch,
},

data() {
return {
// By default, system tags creation is not restricted to admins
systemTagsCreationRestrictedToAdmin: loadState('settings', 'restrictSystemTagsCreationToAdmin', '0') === '1',
}
},
methods: {
t,
async updateSystemTagsDefault(isRestricted: boolean) {
try {
const responseData = await updateSystemTagsAdminRestriction(isRestricted)
console.debug('updateSystemTagsDefault', responseData)
this.handleResponse({
isRestricted,
status: responseData.ocs?.meta?.status,
})
} catch (e) {
this.handleResponse({
errorMessage: t('settings', 'Unable to update setting'),
error: e,
})
}
},

handleResponse({ isRestricted, status, errorMessage, error }) {
if (status === 'ok') {
this.systemTagsCreationRestrictedToAdmin = isRestricted
showSuccess(t('settings', `System tag creation is now ${isRestricted ? 'restricted to administrators' : 'allowed for everybody'}`))
return
}

if (errorMessage) {
showError(errorMessage)
logger.error(errorMessage, error)
}
},
},
}
</script>
7 changes: 7 additions & 0 deletions apps/systemtags/src/files_actions/bulkSystemTagsAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { FileAction } from '@nextcloud/files'
import { isPublicShare } from '@nextcloud/sharing/public'
import { spawnDialog } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import { getCurrentUser } from '@nextcloud/auth'
import { loadState } from '@nextcloud/initial-state'

import TagMultipleSvg from '@mdi/svg/svg/tag-multiple.svg?raw'

Expand All @@ -34,6 +36,11 @@ export const action = new FileAction({

// If the app is disabled, the action is not available anyway
enabled(nodes) {
// By default, everyone can create system tags
if (loadState('settings', 'restrictSystemTagsCreationToAdmin', '0') === '1' && getCurrentUser()?.isAdmin !== true) {
return false
}

if (isPublicShare()) {
return false
}
Expand Down
25 changes: 24 additions & 1 deletion apps/systemtags/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import type { FileStat, ResponseDataDetailed, WebDAVClientError } from 'webdav'
import type { ServerTag, Tag, TagWithId } from '../types.js'

import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { generateUrl, generateOcsUrl } from '@nextcloud/router'
import { t } from '@nextcloud/l10n'

import { davClient } from './davClient.js'
import { formatTag, parseIdFromLocation, parseTags } from '../utils'
import logger from '../logger.ts'
import { emit } from '@nextcloud/event-bus'
import { confirmPassword } from '@nextcloud/password-confirmation'

export const fetchTagsPayload = `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
Expand Down Expand Up @@ -203,3 +204,25 @@ export const setTagObjects = async function(tag: TagWithId, type: string, object
},
})
}

type OcsResponse = {
ocs: NonNullable<unknown>,
}

export const updateSystemTagsAdminRestriction = async (isAllowed: boolean): Promise<OcsResponse> => {
// Convert to string for compatibility
const isAllowedString = isAllowed ? '1' : '0'

const url = generateOcsUrl('/apps/provisioning_api/api/v1/config/apps/{appId}/{key}', {
appId: 'systemtags',
key: 'restrict_creation_to_admin',
})

await confirmPassword()

const res = await axios.post(url, {
value: isAllowedString,
})

return res.data
}
4 changes: 3 additions & 1 deletion apps/systemtags/src/views/SystemTagsSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
<template>
<NcSettingsSection :name="t('systemtags', 'Collaborative tags')"
:description="t('systemtags', 'Collaborative tags are available for all users. Restricted tags are visible to users but cannot be assigned by them. Invisible tags are for internal use, since users cannot see or assign them.')">
<SystemTagsCreationControl />
<NcLoadingIcon v-if="loadingTags"
:name="t('systemtags', 'Loading collaborative tags …')"
:size="32" />

<SystemTagForm v-else
:tags="tags"
@tag:created="handleCreate"
Expand All @@ -29,6 +29,7 @@ import { translate as t } from '@nextcloud/l10n'
import { showError } from '@nextcloud/dialogs'

import SystemTagForm from '../components/SystemTagForm.vue'
import SystemTagsCreationControl from '../components/SystemTagsCreationControl.vue'

import { fetchTags } from '../services/api.js'

Expand All @@ -41,6 +42,7 @@ export default Vue.extend({
NcLoadingIcon,
NcSettingsSection,
SystemTagForm,
SystemTagsCreationControl,
},

data() {
Expand Down
6 changes: 6 additions & 0 deletions dist/5531-5531.js.license
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,18 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/event-bus
- version: 3.3.1
- license: GPL-3.0-or-later
- @nextcloud/initial-state
- version: 2.2.0
- license: GPL-3.0-or-later
- @nextcloud/l10n
- version: 3.1.0
- license: GPL-3.0-or-later
- @nextcloud/logger
- version: 3.0.2
- license: GPL-3.0-or-later
- @nextcloud/password-confirmation
- version: 5.3.1
- license: MIT
- @nextcloud/router
- version: 3.0.1
- license: GPL-3.0-or-later
Expand Down
4 changes: 2 additions & 2 deletions dist/core-common.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/core-common.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/files-sidebar.js

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions dist/files-sidebar.js.license
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ SPDX-FileCopyrightText: jden <[email protected]>
SPDX-FileCopyrightText: inherits developers
SPDX-FileCopyrightText: escape-html developers
SPDX-FileCopyrightText: defunctzombie
SPDX-FileCopyrightText: debounce developers
SPDX-FileCopyrightText: atomiks
SPDX-FileCopyrightText: Varun A P
SPDX-FileCopyrightText: Tobias Koppers @sokra
Expand Down Expand Up @@ -114,6 +115,9 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/logger
- version: 3.0.2
- license: GPL-3.0-or-later
- @nextcloud/password-confirmation
- version: 5.3.1
- license: MIT
- @nextcloud/paths
- version: 2.2.1
- license: GPL-3.0-or-later
Expand Down Expand Up @@ -183,6 +187,9 @@ This file is generated from multiple sources. Included packages:
- css-loader
- version: 7.1.2
- license: MIT
- debounce
- version: 2.2.0
- license: MIT
- define-data-property
- version: 1.1.4
- license: MIT
Expand Down
2 changes: 1 addition & 1 deletion dist/files-sidebar.js.map

Large diffs are not rendered by default.

Loading

0 comments on commit c10b8e2

Please sign in to comment.