Skip to content

Commit c10b8e2

Browse files
authored
Merge pull request #49514 from nextcloud/feat/restrict-tag-creation
2 parents 49cfd30 + 00fbbf9 commit c10b8e2

28 files changed

+324
-44
lines changed

apps/dav/lib/SystemTag/SystemTagPlugin.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use OCP\SystemTag\ISystemTagManager;
1919
use OCP\SystemTag\ISystemTagObjectMapper;
2020
use OCP\SystemTag\TagAlreadyExistsException;
21+
use OCP\SystemTag\TagCreationForbiddenException;
2122
use OCP\Util;
2223
use Sabre\DAV\Exception\BadRequest;
2324
use Sabre\DAV\Exception\Conflict;
@@ -189,6 +190,8 @@ private function createTag($data, $contentType = 'application/json') {
189190
return $tag;
190191
} catch (TagAlreadyExistsException $e) {
191192
throw new Conflict('Tag already exists', 0, $e);
193+
} catch (TagCreationForbiddenException $e) {
194+
throw new Forbidden('You don’t have right to create tags', 0, $e);
192195
}
193196
}
194197

@@ -376,7 +379,7 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
376379
if (!$node instanceof SystemTagNode && !$node instanceof SystemTagObjectType) {
377380
return;
378381
}
379-
382+
380383
$propPatch->handle([self::OBJECTIDS_PROPERTYNAME], function ($props) use ($node) {
381384
if (!$node instanceof SystemTagObjectType) {
382385
return false;
@@ -394,7 +397,7 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
394397
if (count($objectTypes) !== 1 || $objectTypes[0] !== $node->getName()) {
395398
throw new BadRequest('Invalid object-ids property. All object types must be of the same type: ' . $node->getName());
396399
}
397-
400+
398401
$this->tagMapper->setObjectIdsForTag($node->getSystemTag()->getId(), $node->getName(), array_keys($objects));
399402
}
400403

apps/settings/lib/Settings/Admin/Server.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
/**
34
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
45
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -53,6 +54,9 @@ public function getForm() {
5354
$this->initialStateService->provideInitialState('profileEnabledGlobally', $this->profileManager->isProfileEnabled());
5455
$this->initialStateService->provideInitialState('profileEnabledByDefault', $this->isProfileEnabledByDefault($this->config));
5556

57+
// Basic settings
58+
$this->initialStateService->provideInitialState('restrictSystemTagsCreationToAdmin', $this->appConfig->getValueString('systemtags', 'restrict_creation_to_admin', 'true'));
59+
5660
return new TemplateResponse('settings', 'settings/admin/server', [
5761
'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
5862
], '');

apps/settings/tests/Settings/Admin/ServerTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ public function testGetForm(): void {
8484
$this->appConfig
8585
->expects($this->any())
8686
->method('getValueString')
87-
->with('core', 'backgroundjobs_mode', 'ajax')
88-
->willReturn('ajax');
87+
->willReturnCallback(fn ($a, $b, $default) => $default);
8988
$this->profileManager
9089
->expects($this->exactly(2))
9190
->method('isProfileEnabled')

apps/systemtags/src/components/SystemTagForm.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
aria-labelledby="system-tag-form-heading"
1010
@submit.prevent="handleSubmit"
1111
@reset="reset">
12-
<h3 id="system-tag-form-heading">
12+
<h4 id="system-tag-form-heading">
1313
{{ t('systemtags', 'Create or edit tags') }}
14-
</h3>
14+
</h4>
1515

1616
<div class="system-tag-form__group">
1717
<label for="system-tags-input">{{ t('systemtags', 'Search for a tag to edit') }}</label>

apps/systemtags/src/components/SystemTags.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ import {
5151
setTagForFile,
5252
} from '../services/files.js'
5353
54+
import { loadState } from '@nextcloud/initial-state'
55+
5456
import type { Tag, TagWithId } from '../types.js'
5557
5658
export default Vue.extend({
@@ -188,6 +190,10 @@ export default Vue.extend({
188190
this.sortedTags.unshift(createdTag)
189191
this.selectedTags.push(createdTag)
190192
} catch (error) {
193+
if(loadState('settings', 'restrictSystemTagsCreationToAdmin', '0') === '1') {
194+
showError(t('systemtags', 'System admin disabled tag creation. You can only use existing ones.'))
195+
return
196+
}
191197
showError(t('systemtags', 'Failed to create tag'))
192198
}
193199
this.loading = false
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<template>
7+
<div id="system-tags-creation-control">
8+
<h4 class="inlineblock">
9+
{{ t('settings', 'System tag creation') }}
10+
</h4>
11+
12+
<p class="settings-hint">
13+
{{ 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.') }}
14+
</p>
15+
16+
<NcCheckboxRadioSwitch type="switch"
17+
:checked.sync="systemTagsCreationRestrictedToAdmin"
18+
@update:checked="updateSystemTagsDefault">
19+
{{ t('settings', 'Restrict tag creation to admins only') }}
20+
</NcCheckboxRadioSwitch>
21+
</div>
22+
</template>
23+
24+
<script lang="ts">
25+
import { loadState } from '@nextcloud/initial-state'
26+
import { showError, showSuccess } from '@nextcloud/dialogs'
27+
import { t } from '@nextcloud/l10n'
28+
import logger from '../logger.ts'
29+
import { updateSystemTagsAdminRestriction } from '../services/api.js'
30+
31+
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
32+
33+
export default {
34+
name: 'SystemTagsCreationControl',
35+
36+
components: {
37+
NcCheckboxRadioSwitch,
38+
},
39+
40+
data() {
41+
return {
42+
// By default, system tags creation is not restricted to admins
43+
systemTagsCreationRestrictedToAdmin: loadState('settings', 'restrictSystemTagsCreationToAdmin', '0') === '1',
44+
}
45+
},
46+
methods: {
47+
t,
48+
async updateSystemTagsDefault(isRestricted: boolean) {
49+
try {
50+
const responseData = await updateSystemTagsAdminRestriction(isRestricted)
51+
console.debug('updateSystemTagsDefault', responseData)
52+
this.handleResponse({
53+
isRestricted,
54+
status: responseData.ocs?.meta?.status,
55+
})
56+
} catch (e) {
57+
this.handleResponse({
58+
errorMessage: t('settings', 'Unable to update setting'),
59+
error: e,
60+
})
61+
}
62+
},
63+
64+
handleResponse({ isRestricted, status, errorMessage, error }) {
65+
if (status === 'ok') {
66+
this.systemTagsCreationRestrictedToAdmin = isRestricted
67+
showSuccess(t('settings', `System tag creation is now ${isRestricted ? 'restricted to administrators' : 'allowed for everybody'}`))
68+
return
69+
}
70+
71+
if (errorMessage) {
72+
showError(errorMessage)
73+
logger.error(errorMessage, error)
74+
}
75+
},
76+
},
77+
}
78+
</script>

apps/systemtags/src/files_actions/bulkSystemTagsAction.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { FileAction } from '@nextcloud/files'
99
import { isPublicShare } from '@nextcloud/sharing/public'
1010
import { spawnDialog } from '@nextcloud/dialogs'
1111
import { t } from '@nextcloud/l10n'
12+
import { getCurrentUser } from '@nextcloud/auth'
13+
import { loadState } from '@nextcloud/initial-state'
1214

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

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

3537
// If the app is disabled, the action is not available anyway
3638
enabled(nodes) {
39+
// By default, everyone can create system tags
40+
if (loadState('settings', 'restrictSystemTagsCreationToAdmin', '0') === '1' && getCurrentUser()?.isAdmin !== true) {
41+
return false
42+
}
43+
3744
if (isPublicShare()) {
3845
return false
3946
}

apps/systemtags/src/services/api.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import type { FileStat, ResponseDataDetailed, WebDAVClientError } from 'webdav'
77
import type { ServerTag, Tag, TagWithId } from '../types.js'
88

99
import axios from '@nextcloud/axios'
10-
import { generateUrl } from '@nextcloud/router'
10+
import { generateUrl, generateOcsUrl } from '@nextcloud/router'
1111
import { t } from '@nextcloud/l10n'
1212

1313
import { davClient } from './davClient.js'
1414
import { formatTag, parseIdFromLocation, parseTags } from '../utils'
1515
import logger from '../logger.ts'
1616
import { emit } from '@nextcloud/event-bus'
17+
import { confirmPassword } from '@nextcloud/password-confirmation'
1718

1819
export const fetchTagsPayload = `<?xml version="1.0"?>
1920
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
@@ -203,3 +204,25 @@ export const setTagObjects = async function(tag: TagWithId, type: string, object
203204
},
204205
})
205206
}
207+
208+
type OcsResponse = {
209+
ocs: NonNullable<unknown>,
210+
}
211+
212+
export const updateSystemTagsAdminRestriction = async (isAllowed: boolean): Promise<OcsResponse> => {
213+
// Convert to string for compatibility
214+
const isAllowedString = isAllowed ? '1' : '0'
215+
216+
const url = generateOcsUrl('/apps/provisioning_api/api/v1/config/apps/{appId}/{key}', {
217+
appId: 'systemtags',
218+
key: 'restrict_creation_to_admin',
219+
})
220+
221+
await confirmPassword()
222+
223+
const res = await axios.post(url, {
224+
value: isAllowedString,
225+
})
226+
227+
return res.data
228+
}

apps/systemtags/src/views/SystemTagsSection.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
<template>
77
<NcSettingsSection :name="t('systemtags', 'Collaborative tags')"
88
: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.')">
9+
<SystemTagsCreationControl />
910
<NcLoadingIcon v-if="loadingTags"
1011
:name="t('systemtags', 'Loading collaborative tags …')"
1112
:size="32" />
12-
1313
<SystemTagForm v-else
1414
:tags="tags"
1515
@tag:created="handleCreate"
@@ -29,6 +29,7 @@ import { translate as t } from '@nextcloud/l10n'
2929
import { showError } from '@nextcloud/dialogs'
3030
3131
import SystemTagForm from '../components/SystemTagForm.vue'
32+
import SystemTagsCreationControl from '../components/SystemTagsCreationControl.vue'
3233
3334
import { fetchTags } from '../services/api.js'
3435
@@ -41,6 +42,7 @@ export default Vue.extend({
4142
NcLoadingIcon,
4243
NcSettingsSection,
4344
SystemTagForm,
45+
SystemTagsCreationControl,
4446
},
4547
4648
data() {

dist/5531-5531.js.license

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,18 @@ This file is generated from multiple sources. Included packages:
7979
- @nextcloud/event-bus
8080
- version: 3.3.1
8181
- license: GPL-3.0-or-later
82+
- @nextcloud/initial-state
83+
- version: 2.2.0
84+
- license: GPL-3.0-or-later
8285
- @nextcloud/l10n
8386
- version: 3.1.0
8487
- license: GPL-3.0-or-later
8588
- @nextcloud/logger
8689
- version: 3.0.2
8790
- license: GPL-3.0-or-later
91+
- @nextcloud/password-confirmation
92+
- version: 5.3.1
93+
- license: MIT
8894
- @nextcloud/router
8995
- version: 3.0.1
9096
- license: GPL-3.0-or-later

0 commit comments

Comments
 (0)