diff options
Diffstat (limited to 'lib/private/SystemTag/SystemTagManager.php')
-rw-r--r-- | lib/private/SystemTag/SystemTagManager.php | 152 |
1 files changed, 87 insertions, 65 deletions
diff --git a/lib/private/SystemTag/SystemTagManager.php b/lib/private/SystemTag/SystemTagManager.php index 67e1a7d921f..4b421fa033a 100644 --- a/lib/private/SystemTag/SystemTagManager.php +++ b/lib/private/SystemTag/SystemTagManager.php @@ -1,45 +1,28 @@ <?php declare(strict_types=1); - /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\SystemTag; 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; +use OCP\SystemTag\TagUpdateForbiddenException; /** * Manager class for system tags @@ -57,6 +40,8 @@ class SystemTagManager implements ISystemTagManager { protected IDBConnection $connection, protected IGroupManager $groupManager, protected IEventDispatcher $dispatcher, + private IUserSession $userSession, + private IAppConfig $appConfig, ) { $query = $this->connection->getQueryBuilder(); $this->selectTagQuery = $query->select('*') @@ -66,9 +51,6 @@ class SystemTagManager implements ISystemTagManager { ->andWhere($query->expr()->eq('editable', $query->createParameter('editable'))); } - /** - * {@inheritdoc} - */ public function getTagsByIds($tagIds, ?IUser $user = null): array { if (!\is_array($tagIds)) { $tagIds = [$tagIds]; @@ -113,9 +95,6 @@ class SystemTagManager implements ISystemTagManager { return $tags; } - /** - * {@inheritdoc} - */ public function getAllTags($visibilityFilter = null, $nameSearchPattern = null): array { $tags = []; @@ -129,9 +108,9 @@ class SystemTagManager implements ISystemTagManager { if (!empty($nameSearchPattern)) { $query->andWhere( - $query->expr()->like( + $query->expr()->iLike( 'name', - $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($nameSearchPattern). '%') + $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($nameSearchPattern) . '%') ) ); } @@ -141,7 +120,7 @@ class SystemTagManager implements ISystemTagManager { ->addOrderBy('visibility', 'ASC') ->addOrderBy('editable', 'ASC'); - $result = $query->execute(); + $result = $query->executeQuery(); while ($row = $result->fetch()) { $tags[$row['id']] = $this->createSystemTagFromRow($row); } @@ -151,9 +130,6 @@ class SystemTagManager implements ISystemTagManager { return $tags; } - /** - * {@inheritdoc} - */ public function getTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag { // Length of name column is 64 $truncatedTagName = substr($tagName, 0, 64); @@ -167,17 +143,27 @@ class SystemTagManager implements ISystemTagManager { $result->closeCursor(); if (!$row) { throw new TagNotFoundException( - 'Tag ("' . $truncatedTagName . '", '. $userVisible . ', ' . $userAssignable . ') does not exist' + 'Tag ("' . $truncatedTagName . '", ' . $userVisible . ', ' . $userAssignable . ') does not exist' ); } return $this->createSystemTagFromRow($row); } - /** - * {@inheritdoc} - */ public function createTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag { + $user = $this->userSession->getUser(); + if (!$this->canUserCreateTag($user)) { + throw new TagCreationForbiddenException(); + } + + // Check if tag already exists (case-insensitive) + $existingTags = $this->getAllTags(null, $tagName); + foreach ($existingTags as $existingTag) { + if (mb_strtolower($existingTag->getName()) === mb_strtolower($tagName)) { + throw new TagAlreadyExistsException('Tag ' . $tagName . ' already exists'); + } + } + // Length of name column is 64 $truncatedTagName = substr($tagName, 0, 64); $query = $this->connection->getQueryBuilder(); @@ -186,13 +172,14 @@ class SystemTagManager implements ISystemTagManager { 'name' => $query->createNamedParameter($truncatedTagName), 'visibility' => $query->createNamedParameter($userVisible ? 1 : 0), 'editable' => $query->createNamedParameter($userAssignable ? 1 : 0), + 'etag' => $query->createNamedParameter(md5((string)time())), ]); try { $query->execute(); } catch (UniqueConstraintViolationException $e) { throw new TagAlreadyExistsException( - 'Tag ("' . $truncatedTagName . '", '. $userVisible . ', ' . $userAssignable . ') already exists', + 'Tag ("' . $truncatedTagName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists', 0, $e ); @@ -214,14 +201,12 @@ class SystemTagManager implements ISystemTagManager { return $tag; } - /** - * {@inheritdoc} - */ public function updateTag( string $tagId, string $newName, bool $userVisible, bool $userAssignable, + ?string $color, ): void { try { $tags = $this->getTagsByIds($tagId); @@ -231,26 +216,45 @@ class SystemTagManager implements ISystemTagManager { ); } + $user = $this->userSession->getUser(); + if (!$this->canUserUpdateTag($user)) { + throw new TagUpdateForbiddenException(); + } + $beforeUpdate = array_shift($tags); // Length of name column is 64 + $newName = trim($newName); $truncatedNewName = substr($newName, 0, 64); $afterUpdate = new SystemTag( $tagId, $truncatedNewName, $userVisible, - $userAssignable + $userAssignable, + $beforeUpdate->getETag(), + $color ); + // Check if tag already exists (case-insensitive) + $existingTags = $this->getAllTags(null, $truncatedNewName); + foreach ($existingTags as $existingTag) { + if (mb_strtolower($existingTag->getName()) === mb_strtolower($truncatedNewName) + && $existingTag->getId() !== $tagId) { + throw new TagAlreadyExistsException('Tag ' . $truncatedNewName . ' already exists'); + } + } + $query = $this->connection->getQueryBuilder(); $query->update(self::TAG_TABLE) ->set('name', $query->createParameter('name')) ->set('visibility', $query->createParameter('visibility')) ->set('editable', $query->createParameter('editable')) + ->set('color', $query->createParameter('color')) ->where($query->expr()->eq('id', $query->createParameter('tagid'))) ->setParameter('name', $truncatedNewName) ->setParameter('visibility', $userVisible ? 1 : 0) ->setParameter('editable', $userAssignable ? 1 : 0) - ->setParameter('tagid', $tagId); + ->setParameter('tagid', $tagId) + ->setParameter('color', $color); try { if ($query->execute() === 0) { @@ -260,7 +264,7 @@ class SystemTagManager implements ISystemTagManager { } } catch (UniqueConstraintViolationException $e) { throw new TagAlreadyExistsException( - 'Tag ("' . $newName . '", '. $userVisible . ', ' . $userAssignable . ') already exists', + 'Tag ("' . $newName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists', 0, $e ); @@ -271,9 +275,6 @@ class SystemTagManager implements ISystemTagManager { )); } - /** - * {@inheritdoc} - */ public function deleteTags($tagIds): void { if (!\is_array($tagIds)) { $tagIds = [$tagIds]; @@ -323,10 +324,11 @@ class SystemTagManager implements ISystemTagManager { } } - /** - * {@inheritdoc} - */ - public function canUserAssignTag(ISystemTag $tag, IUser $user): bool { + public function canUserAssignTag(ISystemTag $tag, ?IUser $user): bool { + if ($user === null) { + return false; + } + // early check to avoid unneeded group lookups if ($tag->isUserAssignable() && $tag->isUserVisible()) { return true; @@ -351,14 +353,39 @@ class SystemTagManager implements ISystemTagManager { return false; } - /** - * {@inheritdoc} - */ - public function canUserSeeTag(ISystemTag $tag, IUser $user): bool { + 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', 'restrict_creation_to_admin', false) === false) { + return true; + } + + return $this->groupManager->isAdmin($user->getUID()); + } + + public function canUserUpdateTag(?IUser $user): bool { + // We currently have no different permissions for updating tags than for creating them + return $this->canUserCreateTag($user); + } + + 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) { + return true; + } + if ($tag->isUserVisible()) { return true; } + // if not returned yet, and user is not logged in, then the tag is not visible + if ($user === null) { + return false; + } + if ($this->groupManager->isAdmin($user->getUID())) { return true; } @@ -367,12 +394,9 @@ class SystemTagManager implements ISystemTagManager { } private function createSystemTagFromRow($row): SystemTag { - return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable']); + return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable'], $row['etag'], $row['color']); } - /** - * {@inheritdoc} - */ public function setTagGroups(ISystemTag $tag, array $groupIds): void { // delete relationships first $this->connection->beginTransaction(); @@ -404,9 +428,6 @@ class SystemTagManager implements ISystemTagManager { } } - /** - * {@inheritdoc} - */ public function getTagGroups(ISystemTag $tag): array { $groupIds = []; $query = $this->connection->getQueryBuilder(); @@ -424,4 +445,5 @@ class SystemTagManager implements ISystemTagManager { return $groupIds; } + } |