diff --git a/apps/dav/lib/SystemTag/SystemTagPlugin.php b/apps/dav/lib/SystemTag/SystemTagPlugin.php index 8305944cb0bcd..428e71d91f432 100644 --- a/apps/dav/lib/SystemTag/SystemTagPlugin.php +++ b/apps/dav/lib/SystemTag/SystemTagPlugin.php @@ -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; @@ -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); } } @@ -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; @@ -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)); } diff --git a/apps/settings/lib/Settings/Admin/Server.php b/apps/settings/lib/Settings/Admin/Server.php index 0f253ddf6b1f3..8071d81207670 100644 --- a/apps/settings/lib/Settings/Admin/Server.php +++ b/apps/settings/lib/Settings/Admin/Server.php @@ -1,4 +1,5 @@ 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(), ], ''); diff --git a/apps/settings/tests/Settings/Admin/ServerTest.php b/apps/settings/tests/Settings/Admin/ServerTest.php index aa33ff9df7409..a49dcc98bf89b 100644 --- a/apps/settings/tests/Settings/Admin/ServerTest.php +++ b/apps/settings/tests/Settings/Admin/ServerTest.php @@ -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') diff --git a/apps/systemtags/src/components/SystemTagForm.vue b/apps/systemtags/src/components/SystemTagForm.vue index 86e2fc8e1084c..9cd2b454715de 100644 --- a/apps/systemtags/src/components/SystemTagForm.vue +++ b/apps/systemtags/src/components/SystemTagForm.vue @@ -9,9 +9,9 @@ aria-labelledby="system-tag-form-heading" @submit.prevent="handleSubmit" @reset="reset"> -

+

{{ t('systemtags', 'Create or edit tags') }} -

+
diff --git a/apps/systemtags/src/components/SystemTags.vue b/apps/systemtags/src/components/SystemTags.vue index 1f2b38bf10aaa..f65a68dfd90e4 100644 --- a/apps/systemtags/src/components/SystemTags.vue +++ b/apps/systemtags/src/components/SystemTags.vue @@ -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({ @@ -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 diff --git a/apps/systemtags/src/components/SystemTagsCreationControl.vue b/apps/systemtags/src/components/SystemTagsCreationControl.vue new file mode 100644 index 0000000000000..d0af07d4cd0d1 --- /dev/null +++ b/apps/systemtags/src/components/SystemTagsCreationControl.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/apps/systemtags/src/files_actions/bulkSystemTagsAction.ts b/apps/systemtags/src/files_actions/bulkSystemTagsAction.ts index b0d68ef65ca13..c764db98f2952 100644 --- a/apps/systemtags/src/files_actions/bulkSystemTagsAction.ts +++ b/apps/systemtags/src/files_actions/bulkSystemTagsAction.ts @@ -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' @@ -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 } diff --git a/apps/systemtags/src/services/api.ts b/apps/systemtags/src/services/api.ts index ef44fa82daed8..7d53b566d39c4 100644 --- a/apps/systemtags/src/services/api.ts +++ b/apps/systemtags/src/services/api.ts @@ -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 = ` @@ -203,3 +204,25 @@ export const setTagObjects = async function(tag: TagWithId, type: string, object }, }) } + +type OcsResponse = { + ocs: NonNullable, +} + +export const updateSystemTagsAdminRestriction = async (isAllowed: boolean): Promise => { + // 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 +} diff --git a/apps/systemtags/src/views/SystemTagsSection.vue b/apps/systemtags/src/views/SystemTagsSection.vue index 9745ab188afc2..8e2d6b53e2af9 100644 --- a/apps/systemtags/src/views/SystemTagsSection.vue +++ b/apps/systemtags/src/views/SystemTagsSection.vue @@ -6,10 +6,10 @@