Skip to content

Commit

Permalink
wip: Add admin toggle to disable systemtag
Browse files Browse the repository at this point in the history
Signed-off-by: nfebe <[email protected]>
  • Loading branch information
nfebe committed Jan 2, 2025
1 parent d8d3c7e commit e27715d
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 53 deletions.
7 changes: 2 additions & 5 deletions apps/dav/lib/SystemTag/SystemTagPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
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 @@ -190,8 +189,6 @@ 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 @@ -379,7 +376,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 @@ -397,7 +394,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
159 changes: 159 additions & 0 deletions apps/files_sharing/src/actions/sharingStatusAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/**
* @copyright Copyright (c) 2023 John Molakvoæ <[email protected]>
*
* @author John Molakvoæ <[email protected]>
*
* @license AGPL-3.0-or-later
*
* 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/>.
*
*/
import { Node, View, registerFileAction, FileAction, Permission } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { Type } from '@nextcloud/sharing'

import AccountGroupSvg from '@mdi/svg/svg/account-group.svg?raw'
import AccountPlusSvg from '@mdi/svg/svg/account-plus.svg?raw'
import LinkSvg from '@mdi/svg/svg/link.svg?raw'
import CircleSvg from '../../../../core/img/apps/circles.svg?raw'

import { action as sidebarAction } from '../../../files/src/actions/sidebarAction'
import { generateUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'

import './sharingStatusAction.scss'

Check failure on line 35 in apps/files_sharing/src/actions/sharingStatusAction.ts

View workflow job for this annotation

GitHub Actions / NPM lint

Unable to resolve path to module './sharingStatusAction.scss'

const isDarkMode = window?.matchMedia?.('(prefers-color-scheme: dark)')?.matches === true
|| document.querySelector('[data-themes*=dark]') !== null

const generateAvatarSvg = (userId: string, isGuest = false) => {
console.debug("USER ID, is Guest", userId, isGuest)

Check failure on line 41 in apps/files_sharing/src/actions/sharingStatusAction.ts

View workflow job for this annotation

GitHub Actions / NPM lint

Strings must use singlequote
const url = isDarkMode ? '/avatar/{userId}/32/dark' : '/avatar/{userId}/32'
const avatarUrl = generateUrl(isGuest ? url : url + '?guestFallback=true', { userId })
return `<svg width="32" height="32" viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg" class="sharing-status__avatar">
<image href="${avatarUrl}" height="32" width="32" />
</svg>`
}

const isExternal = (node: Node) => {
return node.attributes.remote_id !== undefined
}

export const action = new FileAction({
id: 'sharing-status',
displayName(nodes: Node[]) {
const node = nodes[0]
const shareTypes = Object.values(node?.attributes?.['share-types'] || {}).flat() as number[]
const ownerId = node?.attributes?.['owner-id']

if (shareTypes.length > 0
|| (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
return t('files_sharing', 'Shared')
}

return ''
},

title(nodes: Node[]) {
const node = nodes[0]
const ownerId = node?.attributes?.['owner-id']
const ownerDisplayName = node?.attributes?.['owner-display-name']

// Mixed share types
if (Array.isArray(node.attributes?.['share-types']) && node.attributes?.['share-types'].length > 1) {
return t('files_sharing', 'Shared multiple times with different people')
}

if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
return t('files_sharing', 'Shared by {ownerDisplayName}', { ownerDisplayName })
}

return t('files_sharing', 'Show sharing options')
},

iconSvgInline(nodes: Node[]) {
const node = nodes[0]
console.debug("Node check", node)

Check failure on line 88 in apps/files_sharing/src/actions/sharingStatusAction.ts

View workflow job for this annotation

GitHub Actions / NPM lint

Strings must use singlequote
const shareTypes = Object.values(node?.attributes?.['share-types'] || {}).flat() as number[]

// Mixed share types
if (Array.isArray(node.attributes?.['share-types']) && node.attributes?.['share-types'].length > 1) {
return AccountPlusSvg
}

// Link shares
if (shareTypes.includes(Type.SHARE_TYPE_LINK)
|| shareTypes.includes(Type.SHARE_TYPE_EMAIL)) {
return LinkSvg
}

// Group shares
if (shareTypes.includes(Type.SHARE_TYPE_GROUP)
|| shareTypes.includes(Type.SHARE_TYPE_REMOTE_GROUP)) {
return AccountGroupSvg
}

// Circle shares
if (shareTypes.includes(Type.SHARE_TYPE_CIRCLE)) {
return CircleSvg
}

const ownerId = node?.attributes?.['owner-id']
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
console.debug("IS EXTERNAL", isExternal(node))

Check failure on line 115 in apps/files_sharing/src/actions/sharingStatusAction.ts

View workflow job for this annotation

GitHub Actions / NPM lint

Strings must use singlequote
console.debug("EXTENAL NODE", node)

Check failure on line 116 in apps/files_sharing/src/actions/sharingStatusAction.ts

View workflow job for this annotation

GitHub Actions / NPM lint

Strings must use singlequote
return generateAvatarSvg(ownerId, true)
}

return AccountPlusSvg
},

enabled(nodes: Node[]) {
if (nodes.length !== 1) {
return false
}

const node = nodes[0]
const ownerId = node?.attributes?.['owner-id']
const isMixed = Array.isArray(node.attributes?.['share-types'])

// If the node is shared multiple times with
// different share types to the current user
if (isMixed) {
return true
}

// If the node is shared by someone else
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
return true
}

return (node.permissions & Permission.SHARE) !== 0
},

async exec(node: Node, view: View, dir: string) {
// You need read permissions to see the sidebar
if ((node.permissions & Permission.READ) !== 0) {
window.OCA?.Files?.Sidebar?.setActiveTab?.('sharing')
return sidebarAction.exec(node, view, dir)
}
return null
},

inline: () => true,

})

registerFileAction(action)
5 changes: 5 additions & 0 deletions apps/systemtags/src/components/SystemTags.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export default Vue.extend({

data() {
return {
allowSystemTagCreationForNonAdmins: false, // obtain default value
sortedTags: [] as TagWithId[],
selectedTags: [] as TagWithId[],
loadingTags: false,
Expand Down Expand Up @@ -202,6 +203,10 @@ export default Vue.extend({
}
this.loading = false
},
async onSystemTagCreationPermissionChange(checked) {
return checked

}
},
})
</script>
Expand Down
3 changes: 3 additions & 0 deletions apps/systemtags/src/views/SystemTagsSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<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.')">
<SystemTagsControl />
<NcLoadingIcon v-if="loadingTags"
:name="t('systemtags', 'Loading collaborative tags …')"
:size="32" />
Expand All @@ -29,6 +30,7 @@ import { translate as t } from '@nextcloud/l10n'
import { showError } from '@nextcloud/dialogs'

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

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

Expand All @@ -40,6 +42,7 @@ export default Vue.extend({
components: {
NcLoadingIcon,
NcSettingsSection,
SystemTagsControl,
SystemTagForm,
},

Expand Down
1 change: 0 additions & 1 deletion lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,6 @@
'OCP\\SystemTag\\MapperEvent' => $baseDir . '/lib/public/SystemTag/MapperEvent.php',
'OCP\\SystemTag\\SystemTagsEntityEvent' => $baseDir . '/lib/public/SystemTag/SystemTagsEntityEvent.php',
'OCP\\SystemTag\\TagAlreadyExistsException' => $baseDir . '/lib/public/SystemTag/TagAlreadyExistsException.php',
'OCP\\SystemTag\\TagCreationForbiddenException' => $baseDir . '/lib/public/SystemTag/TagCreationForbiddenException.php',
'OCP\\SystemTag\\TagNotFoundException' => $baseDir . '/lib/public/SystemTag/TagNotFoundException.php',
'OCP\\Talk\\Exceptions\\NoBackendException' => $baseDir . '/lib/public/Talk/Exceptions/NoBackendException.php',
'OCP\\Talk\\IBroker' => $baseDir . '/lib/public/Talk/IBroker.php',
Expand Down
1 change: 0 additions & 1 deletion lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\SystemTag\\MapperEvent' => __DIR__ . '/../../..' . '/lib/public/SystemTag/MapperEvent.php',
'OCP\\SystemTag\\SystemTagsEntityEvent' => __DIR__ . '/../../..' . '/lib/public/SystemTag/SystemTagsEntityEvent.php',
'OCP\\SystemTag\\TagAlreadyExistsException' => __DIR__ . '/../../..' . '/lib/public/SystemTag/TagAlreadyExistsException.php',
'OCP\\SystemTag\\TagCreationForbiddenException' => __DIR__ . '/../../..' . '/lib/public/SystemTag/TagCreationForbiddenException.php',
'OCP\\SystemTag\\TagNotFoundException' => __DIR__ . '/../../..' . '/lib/public/SystemTag/TagNotFoundException.php',
'OCP\\Talk\\Exceptions\\NoBackendException' => __DIR__ . '/../../..' . '/lib/public/Talk/Exceptions/NoBackendException.php',
'OCP\\Talk\\IBroker' => __DIR__ . '/../../..' . '/lib/public/Talk/IBroker.php',
Expand Down
12 changes: 3 additions & 9 deletions lib/private/SystemTag/ManagerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@
namespace OC\SystemTag;

use OCP\EventDispatcher\IEventDispatcher;
use OCP\IAppConfig;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IServerContainer;
use OCP\IUserSession;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ISystemTagManagerFactory;
use OCP\SystemTag\ISystemTagObjectMapper;
Expand All @@ -40,11 +36,9 @@ public function __construct(
*/
public function getManager(): ISystemTagManager {
return new SystemTagManager(
$this->serverContainer->get(IDBConnection::class),
$this->serverContainer->get(IGroupManager::class),
$this->serverContainer->getDatabaseConnection(),
$this->serverContainer->getGroupManager(),
$this->serverContainer->get(IEventDispatcher::class),
$this->serverContainer->get(IUserSession::class),
$this->serverContainer->get(IAppConfig::class),
);
}

Expand All @@ -56,7 +50,7 @@ public function getManager(): ISystemTagManager {
*/
public function getObjectMapper(): ISystemTagObjectMapper {
return new SystemTagObjectMapper(
$this->serverContainer->get(IDBConnection::class),
$this->serverContainer->getDatabaseConnection(),
$this->getManager(),
$this->serverContainer->get(IEventDispatcher::class),
);
Expand Down
25 changes: 0 additions & 25 deletions lib/private/SystemTag/SystemTagManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IAppConfig;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
use OCP\SystemTag\ISystemTag;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ManagerEvent;
use OCP\SystemTag\TagAlreadyExistsException;
use OCP\SystemTag\TagCreationForbiddenException;
use OCP\SystemTag\TagNotFoundException;

/**
Expand All @@ -39,8 +36,6 @@ public function __construct(
protected IDBConnection $connection,
protected IGroupManager $groupManager,
protected IEventDispatcher $dispatcher,
private IUserSession $userSession,
private IAppConfig $appConfig,
) {
$query = $this->connection->getQueryBuilder();
$this->selectTagQuery = $query->select('*')
Expand Down Expand Up @@ -150,10 +145,6 @@ public function getTag(string $tagName, bool $userVisible, bool $userAssignable)
}

public function createTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag {
$user = $this->userSession->getUser();
if (!$this->canUserCreateTag($user)) {
throw new TagCreationForbiddenException('Tag creation forbidden');
}
// Length of name column is 64
$truncatedTagName = substr($tagName, 0, 64);
$query = $this->connection->getQueryBuilder();
Expand Down Expand Up @@ -328,22 +319,6 @@ public function canUserAssignTag(ISystemTag $tag, ?IUser $user): bool {
return false;
}

public function canUserCreateTag(?IUser $user): bool {
if ($user === null) {
// If no user given, allows only calls from CLI
return \OC::$CLI;
}

if ($this->appConfig->getValueBool('systemtags', 'only_admins_can_create', false) === false) {
return true;
}

return $this->groupManager->isAdmin($user->getUID());
}

/**
* {@inheritdoc}
*/
public function canUserSeeTag(ISystemTag $tag, ?IUser $user): bool {
// If no user, then we only show public tags
if (!$user && $tag->getAccessLevel() === ISystemTag::ACCESS_LEVEL_PUBLIC) {
Expand Down
12 changes: 0 additions & 12 deletions lib/public/SystemTag/ISystemTagManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,8 @@ public function getTag(string $tagName, bool $userVisible, bool $userAssignable)
* @return ISystemTag system tag
*
* @throws TagAlreadyExistsException if tag already exists
* @throws TagCreationForbiddenException if user doesn’t have the right to create a new tag
*
* @since 9.0.0
* @since 31.0.0 Can throw TagCreationForbiddenExceptionif user doesn’t have the right to create a new tag
*/
public function createTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag;

Expand Down Expand Up @@ -119,16 +117,6 @@ public function deleteTags($tagIds);
*/
public function canUserAssignTag(ISystemTag $tag, ?IUser $user): bool;

/**
* Checks whether the given user is allowed to create new tags
*
* @param IUser|null $user user to check permission for
* @return bool true if the user is allowed to create a new tag, false otherwise
*
* @since 31.0.0
*/
public function canUserCreateTag(?IUser $user): bool;

/**
* Checks whether the given user is allowed to see the tag with the given id.
*
Expand Down

0 comments on commit e27715d

Please sign in to comment.