diff options
Diffstat (limited to 'apps/dav/lib/DAV')
-rw-r--r-- | apps/dav/lib/DAV/CustomPropertiesBackend.php | 239 | ||||
-rw-r--r-- | apps/dav/lib/DAV/GroupPrincipalBackend.php | 66 | ||||
-rw-r--r-- | apps/dav/lib/DAV/PublicAuth.php | 27 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/Backend.php | 99 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/IShareable.php | 24 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/Plugin.php | 84 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/SharingMapper.php | 72 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/SharingService.php | 36 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/Xml/Invite.php | 57 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php | 36 | ||||
-rw-r--r-- | apps/dav/lib/DAV/SystemPrincipalBackend.php | 23 | ||||
-rw-r--r-- | apps/dav/lib/DAV/ViewOnlyPlugin.php | 46 |
12 files changed, 361 insertions, 448 deletions
diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index 48872048ea8..e9b2137178d 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -1,38 +1,28 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @copyright Copyright (c) 2017, Georg Ehrke <oc.list@georgehrke.com> - * - * @author Georg Ehrke <oc.list@georgehrke.com> - * @author Robin Appelman <robin@icewind.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Richard Steinmetz <richard@steinmetz.cloud> - * - * @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: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\DAV\DAV; use Exception; +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\CalDAV\Calendar; +use OCA\DAV\CalDAV\CalendarHome; +use OCA\DAV\CalDAV\CalendarObject; +use OCA\DAV\CalDAV\DefaultCalendarValidator; +use OCA\DAV\CalDAV\Integration\ExternalCalendar; +use OCA\DAV\CalDAV\Outbox; +use OCA\DAV\CalDAV\Trashbin\TrashbinHome; use OCA\DAV\Connector\Sabre\Directory; -use OCA\DAV\Connector\Sabre\FilesPlugin; +use OCA\DAV\Db\PropertyMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IUser; -use Sabre\CalDAV\ICalendar; +use Sabre\CalDAV\Schedule\Inbox; use Sabre\DAV\Exception as DavException; use Sabre\DAV\PropertyStorage\Backend\BackendInterface; use Sabre\DAV\PropFind; @@ -83,33 +73,16 @@ class CustomPropertiesBackend implements BackendInterface { '{DAV:}getetag', '{DAV:}quota-used-bytes', '{DAV:}quota-available-bytes', - '{http://owncloud.org/ns}permissions', - '{http://owncloud.org/ns}downloadURL', - '{http://owncloud.org/ns}dDC', - '{http://owncloud.org/ns}size', - '{http://nextcloud.org/ns}is-encrypted', - - // Currently, returning null from any propfind handler would still trigger the backend, - // so we add all known Nextcloud custom properties in here to avoid that - - // text app - '{http://nextcloud.org/ns}rich-workspace', - '{http://nextcloud.org/ns}rich-workspace-file', - // groupfolders - '{http://nextcloud.org/ns}acl-enabled', - '{http://nextcloud.org/ns}acl-can-manage', - '{http://nextcloud.org/ns}acl-list', - '{http://nextcloud.org/ns}inherited-acl-list', - '{http://nextcloud.org/ns}group-folder-id', - // files_lock - '{http://nextcloud.org/ns}lock', - '{http://nextcloud.org/ns}lock-owner-type', - '{http://nextcloud.org/ns}lock-owner', - '{http://nextcloud.org/ns}lock-owner-displayname', - '{http://nextcloud.org/ns}lock-owner-editor', - '{http://nextcloud.org/ns}lock-time', - '{http://nextcloud.org/ns}lock-timeout', - '{http://nextcloud.org/ns}lock-token', + ]; + + /** + * Allowed properties for the oc/nc namespace, all other properties in the namespace are ignored + * + * @var string[] + */ + private const ALLOWED_NC_PROPERTIES = [ + '{http://owncloud.org/ns}calendar-enabled', + '{http://owncloud.org/ns}enabled', ]; /** @@ -131,28 +104,10 @@ class CustomPropertiesBackend implements BackendInterface { ]; /** - * @var Tree - */ - private $tree; - - /** - * @var IDBConnection - */ - private $connection; - - /** - * @var IUser - */ - private $user; - - /** * Properties cache - * - * @var array */ - private $userCache = []; - - private Server $server; + private array $userCache = []; + private array $publishedCache = []; private XmlService $xmlService; /** @@ -161,15 +116,13 @@ class CustomPropertiesBackend implements BackendInterface { * @param IUser $user owner of the tree and properties */ public function __construct( - Server $server, - Tree $tree, - IDBConnection $connection, - IUser $user, + private Server $server, + private Tree $tree, + private IDBConnection $connection, + private IUser $user, + private PropertyMapper $propertyMapper, + private DefaultCalendarValidator $defaultCalendarValidator, ) { - $this->server = $server; - $this->tree = $tree; - $this->connection = $connection; - $this->user = $user; $this->xmlService = new XmlService(); $this->xmlService->elementMap = array_merge( $this->xmlService->elementMap, @@ -187,14 +140,9 @@ class CustomPropertiesBackend implements BackendInterface { public function propFind($path, PropFind $propFind) { $requestedProps = $propFind->get404Properties(); - // these might appear - $requestedProps = array_diff( - $requestedProps, - self::IGNORED_PROPERTIES, - ); $requestedProps = array_filter( $requestedProps, - fn ($prop) => !str_starts_with($prop, FilesPlugin::FILE_METADATA_PREFIX), + $this->isPropertyAllowed(...), ); // substr of calendars/ => path is inside the CalDAV component @@ -256,6 +204,18 @@ class CustomPropertiesBackend implements BackendInterface { $this->cacheDirectory($path, $node); } + if ($node instanceof CalendarHome && $propFind->getDepth() !== 0) { + $backend = $node->getCalDAVBackend(); + if ($backend instanceof CalDavBackend) { + $this->cacheCalendars($node, $requestedProps); + } + } + + if ($node instanceof CalendarObject) { + // No custom properties supported on individual events + return; + } + // First fetch the published properties (set by another user), then get the ones set by // the current user. If both are set then the latter as priority. foreach ($this->getPublishedProperties($path, $requestedProps) as $propName => $propValue) { @@ -276,6 +236,16 @@ class CustomPropertiesBackend implements BackendInterface { } } + private function isPropertyAllowed(string $property): bool { + if (in_array($property, self::IGNORED_PROPERTIES)) { + return false; + } + if (str_starts_with($property, '{http://owncloud.org/ns}') || str_starts_with($property, '{http://nextcloud.org/ns}')) { + return in_array($property, self::ALLOWED_NC_PROPERTIES); + } + return true; + } + /** * Updates properties for a path * @@ -315,8 +285,8 @@ class CustomPropertiesBackend implements BackendInterface { */ public function move($source, $destination) { $statement = $this->connection->prepare( - 'UPDATE `*PREFIX*properties` SET `propertypath` = ?' . - ' WHERE `userid` = ? AND `propertypath` = ?' + 'UPDATE `*PREFIX*properties` SET `propertypath` = ?' + . ' WHERE `userid` = ? AND `propertypath` = ?' ); $statement->execute([$this->formatPath($destination), $this->user->getUID(), $this->formatPath($source)]); $statement->closeCursor(); @@ -338,10 +308,11 @@ class CustomPropertiesBackend implements BackendInterface { // $path is the principal here as this prop is only set on principals $node = $this->tree->getNodeForPath($href); - if (!($node instanceof ICalendar) || $node->getOwner() !== $path) { + if (!($node instanceof Calendar) || $node->getOwner() !== $path) { throw new DavException('No such calendar'); } + $this->defaultCalendarValidator->validateScheduleDefaultCalendar($node); break; } } @@ -359,6 +330,10 @@ class CustomPropertiesBackend implements BackendInterface { return []; } + if (isset($this->publishedCache[$path])) { + return $this->publishedCache[$path]; + } + $qb = $this->connection->getQueryBuilder(); $qb->select('*') ->from(self::TABLE_NAME) @@ -369,6 +344,7 @@ class CustomPropertiesBackend implements BackendInterface { $props[$row['propertyname']] = $this->decodeValueFromDatabase($row['propertyvalue'], $row['valuetype']); } $result->closeCursor(); + $this->publishedCache[$path] = $props; return $props; } @@ -378,16 +354,19 @@ class CustomPropertiesBackend implements BackendInterface { private function cacheDirectory(string $path, Directory $node): void { $prefix = ltrim($path . '/', '/'); $query = $this->connection->getQueryBuilder(); - $query->select('name', 'propertypath', 'propertyname', 'propertyvalue', 'valuetype') + $query->select('name', 'p.propertypath', 'p.propertyname', 'p.propertyvalue', 'p.valuetype') ->from('filecache', 'f') - ->leftJoin('f', 'properties', 'p', $query->expr()->andX( - $query->expr()->eq('propertypath', $query->func()->concat( - $query->createNamedParameter($prefix), - 'name' - )), - $query->expr()->eq('userid', $query->createNamedParameter($this->user->getUID())) - )) - ->where($query->expr()->eq('parent', $query->createNamedParameter($node->getInternalFileId(), IQueryBuilder::PARAM_INT))); + ->hintShardKey('storage', $node->getNode()->getMountPoint()->getNumericStorageId()) + ->leftJoin('f', 'properties', 'p', $query->expr()->eq('p.propertypath', $query->func()->concat( + $query->createNamedParameter($prefix), + 'f.name' + )), + ) + ->where($query->expr()->eq('parent', $query->createNamedParameter($node->getInternalFileId(), IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->orX( + $query->expr()->eq('p.userid', $query->createNamedParameter($this->user->getUID())), + $query->expr()->isNull('p.userid'), + )); $result = $query->executeQuery(); $propsByPath = []; @@ -404,6 +383,62 @@ class CustomPropertiesBackend implements BackendInterface { $this->userCache = array_merge($this->userCache, $propsByPath); } + private function cacheCalendars(CalendarHome $node, array $requestedProperties): void { + $calendars = $node->getChildren(); + + $users = []; + foreach ($calendars as $calendar) { + if ($calendar instanceof Calendar) { + $user = str_replace('principals/users/', '', $calendar->getPrincipalURI()); + if (!isset($users[$user])) { + $users[$user] = ['calendars/' . $user]; + } + $users[$user][] = 'calendars/' . $user . '/' . $calendar->getUri(); + } elseif ($calendar instanceof Inbox || $calendar instanceof Outbox || $calendar instanceof TrashbinHome || $calendar instanceof ExternalCalendar) { + if ($calendar->getOwner()) { + $user = str_replace('principals/users/', '', $calendar->getOwner()); + if (!isset($users[$user])) { + $users[$user] = ['calendars/' . $user]; + } + $users[$user][] = 'calendars/' . $user . '/' . $calendar->getName(); + } + } + } + + // user properties + $properties = $this->propertyMapper->findPropertiesByPathsAndUsers($users); + + $propsByPath = []; + foreach ($users as $paths) { + foreach ($paths as $path) { + $propsByPath[$path] = []; + } + } + + foreach ($properties as $property) { + $propsByPath[$property->getPropertypath()][$property->getPropertyname()] = $this->decodeValueFromDatabase($property->getPropertyvalue(), $property->getValuetype()); + } + $this->userCache = array_merge($this->userCache, $propsByPath); + + // published properties + $allowedProps = array_intersect(self::PUBLISHED_READ_ONLY_PROPERTIES, $requestedProperties); + if (empty($allowedProps)) { + return; + } + $paths = []; + foreach ($users as $nestedPaths) { + $paths = array_merge($paths, $nestedPaths); + } + $paths = array_unique($paths); + + $propsByPath = array_fill_keys(array_values($paths), []); + $properties = $this->propertyMapper->findPropertiesByPaths($paths, $allowedProps); + foreach ($properties as $property) { + $propsByPath[$property->getPropertypath()][$property->getPropertyname()] = $this->decodeValueFromDatabase($property->getPropertyvalue(), $property->getValuetype()); + } + $this->publishedCache = array_merge($this->publishedCache, $propsByPath); + } + /** * Returns a list of properties for the given path and current user * @@ -430,7 +465,7 @@ class CustomPropertiesBackend implements BackendInterface { // request only a subset $sql .= ' AND `propertyname` in (?)'; $whereValues[] = $requestedProperties; - $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY; + $whereTypes[] = IQueryBuilder::PARAM_STR_ARRAY; } $result = $this->connection->executeQuery( @@ -556,7 +591,9 @@ class CustomPropertiesBackend implements BackendInterface { $value = $value->getHref(); } else { $valueType = self::PROPERTY_TYPE_OBJECT; - $value = serialize($value); + // serialize produces null character + // these can not be properly stored in some databases and need to be replaced + $value = str_replace(chr(0), '\x00', serialize($value)); } return [$value, $valueType]; } @@ -571,7 +608,9 @@ class CustomPropertiesBackend implements BackendInterface { case self::PROPERTY_TYPE_HREF: return new Href($value); case self::PROPERTY_TYPE_OBJECT: - return unserialize($value); + // some databases can not handel null characters, these are custom encoded during serialization + // this custom encoding needs to be first reversed before unserializing + return unserialize(str_replace('\x00', chr(0), $value)); case self::PROPERTY_TYPE_STRING: default: return $value; diff --git a/apps/dav/lib/DAV/GroupPrincipalBackend.php b/apps/dav/lib/DAV/GroupPrincipalBackend.php index 8c126e6b71c..77ba45182c9 100644 --- a/apps/dav/lib/DAV/GroupPrincipalBackend.php +++ b/apps/dav/lib/DAV/GroupPrincipalBackend.php @@ -1,30 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @copyright Copyright (c) 2018, Georg Ehrke - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Georg Ehrke <oc.list@georgehrke.com> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Citharel <nextcloud@tcit.fr> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\DAV\DAV; @@ -42,32 +21,17 @@ use Sabre\DAVACL\PrincipalBackend\BackendInterface; class GroupPrincipalBackend implements BackendInterface { public const PRINCIPAL_PREFIX = 'principals/groups'; - /** @var IGroupManager */ - private $groupManager; - - /** @var IUserSession */ - private $userSession; - - /** @var IShareManager */ - private $shareManager; - /** @var IConfig */ - private $config; - /** - * @param IGroupManager $IGroupManager + * @param IGroupManager $groupManager * @param IUserSession $userSession * @param IShareManager $shareManager */ public function __construct( - IGroupManager $IGroupManager, - IUserSession $userSession, - IShareManager $shareManager, - IConfig $config + private IGroupManager $groupManager, + private IUserSession $userSession, + private IShareManager $shareManager, + private IConfig $config, ) { - $this->groupManager = $IGroupManager; - $this->userSession = $userSession; - $this->shareManager = $shareManager; - $this->config = $config; } /** @@ -87,8 +51,10 @@ class GroupPrincipalBackend implements BackendInterface { $principals = []; if ($prefixPath === self::PRINCIPAL_PREFIX) { - foreach ($this->groupManager->search('') as $user) { - $principals[] = $this->groupToPrincipal($user); + foreach ($this->groupManager->search('') as $group) { + if (!$group->hideFromCollaboration()) { + $principals[] = $this->groupToPrincipal($group); + } } } @@ -114,7 +80,7 @@ class GroupPrincipalBackend implements BackendInterface { $name = urldecode($elements[2]); $group = $this->groupManager->get($name); - if (!is_null($group)) { + if ($group !== null && !$group->hideFromCollaboration()) { return $this->groupToPrincipal($group); } @@ -223,6 +189,10 @@ class GroupPrincipalBackend implements BackendInterface { $groups = $this->groupManager->search($value, $searchLimit); $results[] = array_reduce($groups, function (array $carry, IGroup $group) use ($restrictGroups) { + if ($group->hideFromCollaboration()) { + return $carry; + } + $gid = $group->getGID(); // is sharing restricted to groups only? if ($restrictGroups !== false) { diff --git a/apps/dav/lib/DAV/PublicAuth.php b/apps/dav/lib/DAV/PublicAuth.php index 3ba8bb2f3c5..c2b4ada173a 100644 --- a/apps/dav/lib/DAV/PublicAuth.php +++ b/apps/dav/lib/DAV/PublicAuth.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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 OCA\DAV\DAV; @@ -68,9 +53,9 @@ class PublicAuth implements BackendInterface { */ public function check(RequestInterface $request, ResponseInterface $response) { if ($this->isRequestPublic($request)) { - return [true, "principals/system/public"]; + return [true, 'principals/system/public']; } - return [false, "No public access to this resource."]; + return [false, 'No public access to this resource.']; } /** diff --git a/apps/dav/lib/DAV/Sharing/Backend.php b/apps/dav/lib/DAV/Sharing/Backend.php index b467479bc1e..d60f5cca7c6 100644 --- a/apps/dav/lib/DAV/Sharing/Backend.php +++ b/apps/dav/lib/DAV/Sharing/Backend.php @@ -2,32 +2,9 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Citharel <nextcloud@tcit.fr> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Anna Larch <anna.larch@gmx.net> - * - * @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 OCA\DAV\DAV\Sharing; @@ -50,7 +27,8 @@ abstract class Backend { private ICache $shareCache; - public function __construct(private IUserManager $userManager, + public function __construct( + private IUserManager $userManager, private IGroupManager $groupManager, private Principal $principalBackend, private ICacheFactory $cacheFactory, @@ -81,13 +59,13 @@ abstract class Backend { } // Don't add share for owner - if($shareable->getOwner() !== null && strcasecmp($shareable->getOwner(), $principal) === 0) { + if ($shareable->getOwner() !== null && strcasecmp($shareable->getOwner(), $principal) === 0) { continue; } $principalparts[2] = urldecode($principalparts[2]); - if (($principalparts[1] === 'users' && !$this->userManager->userExists($principalparts[2])) || - ($principalparts[1] === 'groups' && !$this->groupManager->groupExists($principalparts[2]))) { + if (($principalparts[1] === 'users' && !$this->userManager->userExists($principalparts[2])) + || ($principalparts[1] === 'groups' && !$this->groupManager->groupExists($principalparts[2]))) { // User or group does not exist continue; } @@ -106,20 +84,12 @@ abstract class Backend { } // Don't add unshare for owner - if($shareable->getOwner() !== null && strcasecmp($shareable->getOwner(), $principal) === 0) { + if ($shareable->getOwner() !== null && strcasecmp($shareable->getOwner(), $principal) === 0) { continue; } // Delete any possible direct shares (since the frontend does not separate between them) $this->service->deleteShare($shareable->getResourceId(), $principal); - - // Check if a user has a groupshare that they're trying to free themselves from - // If so we need to add a self::ACCESS_UNSHARED row - if(!str_contains($principal, 'group') - && $this->service->hasGroupShare($oldShares) - ) { - $this->service->unshare($shareable->getResourceId(), $principal); - } } } @@ -153,15 +123,15 @@ abstract class Backend { $rows = $this->service->getShares($resourceId); $shares = []; - foreach($rows as $row) { + foreach ($rows as $row) { $p = $this->principalBackend->getPrincipalByPath($row['principaluri']); $shares[] = [ 'href' => "principal:{$row['principaluri']}", 'commonName' => isset($p['{DAV:}displayname']) ? (string)$p['{DAV:}displayname'] : '', 'status' => 1, - 'readOnly' => (int) $row['access'] === Backend::ACCESS_READ, + 'readOnly' => (int)$row['access'] === Backend::ACCESS_READ, '{http://owncloud.org/ns}principal' => (string)$row['principaluri'], - '{http://owncloud.org/ns}group-share' => isset($p['uri']) && str_starts_with($p['uri'], 'principals/groups') + '{http://owncloud.org/ns}group-share' => isset($p['uri']) && (str_starts_with($p['uri'], 'principals/groups') || str_starts_with($p['uri'], 'principals/circles')) ]; } $this->shareCache->set((string)$resourceId, $shares); @@ -178,14 +148,14 @@ abstract class Backend { $rows = $this->service->getSharesForIds($resourceIds); $sharesByResource = array_fill_keys($resourceIds, []); - foreach($rows as $row) { + foreach ($rows as $row) { $resourceId = (int)$row['resourceid']; $p = $this->principalBackend->getPrincipalByPath($row['principaluri']); $sharesByResource[$resourceId][] = [ 'href' => "principal:{$row['principaluri']}", 'commonName' => isset($p['{DAV:}displayname']) ? (string)$p['{DAV:}displayname'] : '', 'status' => 1, - 'readOnly' => (int) $row['access'] === self::ACCESS_READ, + 'readOnly' => (int)$row['access'] === self::ACCESS_READ, '{http://owncloud.org/ns}principal' => (string)$row['principaluri'], '{http://owncloud.org/ns}group-share' => isset($p['uri']) && str_starts_with($p['uri'], 'principals/groups') ]; @@ -226,4 +196,45 @@ abstract class Backend { } return $acl; } + + public function unshare(IShareable $shareable, string $principalUri): bool { + $this->shareCache->clear(); + + $principal = $this->principalBackend->findByUri($principalUri, ''); + if (empty($principal)) { + return false; + } + + if ($shareable->getOwner() === $principal) { + return false; + } + + // Delete any possible direct shares (since the frontend does not separate between them) + $this->service->deleteShare($shareable->getResourceId(), $principal); + + $needsUnshare = $this->hasAccessByGroupOrCirclesMembership( + $shareable->getResourceId(), + $principal + ); + + if ($needsUnshare) { + $this->service->unshare($shareable->getResourceId(), $principal); + } + + return true; + } + + private function hasAccessByGroupOrCirclesMembership(int $resourceId, string $principal) { + $memberships = array_merge( + $this->principalBackend->getGroupMembership($principal, true), + $this->principalBackend->getCircleMembership($principal) + ); + + $shares = array_column( + $this->service->getShares($resourceId), + 'principaluri' + ); + + return count(array_intersect($memberships, $shares)) > 0; + } } diff --git a/apps/dav/lib/DAV/Sharing/IShareable.php b/apps/dav/lib/DAV/Sharing/IShareable.php index 759981af078..d83079f6975 100644 --- a/apps/dav/lib/DAV/Sharing/IShareable.php +++ b/apps/dav/lib/DAV/Sharing/IShareable.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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 OCA\DAV\DAV\Sharing; diff --git a/apps/dav/lib/DAV/Sharing/Plugin.php b/apps/dav/lib/DAV/Sharing/Plugin.php index 78e086bc907..82b000bc8ce 100644 --- a/apps/dav/lib/DAV/Sharing/Plugin.php +++ b/apps/dav/lib/DAV/Sharing/Plugin.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Georg Ehrke <oc.list@georgehrke.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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 OCA\DAV\DAV\Sharing; @@ -29,9 +12,11 @@ use OCA\DAV\CalDAV\CalendarHome; use OCA\DAV\Connector\Sabre\Auth; use OCA\DAV\DAV\Sharing\Xml\Invite; use OCA\DAV\DAV\Sharing\Xml\ShareRequest; +use OCP\AppFramework\Http; use OCP\IConfig; use OCP\IRequest; use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ICollection; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\DAV\Server; @@ -43,26 +28,18 @@ class Plugin extends ServerPlugin { public const NS_OWNCLOUD = 'http://owncloud.org/ns'; public const NS_NEXTCLOUD = 'http://nextcloud.com/ns'; - /** @var Auth */ - private $auth; - - /** @var IRequest */ - private $request; - - /** @var IConfig */ - private $config; - /** * Plugin constructor. * - * @param Auth $authBackEnd + * @param Auth $auth * @param IRequest $request * @param IConfig $config */ - public function __construct(Auth $authBackEnd, IRequest $request, IConfig $config) { - $this->auth = $authBackEnd; - $this->request = $request; - $this->config = $config; + public function __construct( + private Auth $auth, + private IRequest $request, + private IConfig $config, + ) { } /** @@ -113,6 +90,7 @@ class Plugin extends ServerPlugin { $this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}invite'] = Invite::class; $this->server->on('method:POST', [$this, 'httpPost']); + $this->server->on('preloadCollection', $this->preloadCollection(...)); $this->server->on('propFind', [$this, 'propFind']); } @@ -127,7 +105,7 @@ class Plugin extends ServerPlugin { $path = $request->getPath(); // Only handling xml - $contentType = (string) $request->getHeader('Content-Type'); + $contentType = (string)$request->getHeader('Content-Type'); if (!str_contains($contentType, 'application/xml') && !str_contains($contentType, 'text/xml')) { return; } @@ -182,7 +160,7 @@ class Plugin extends ServerPlugin { $node->updateShares($message->set, $message->remove); - $response->setStatus(200); + $response->setStatus(Http::STATUS_OK); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $response->setHeader('X-Sabre-Status', 'everything-went-well'); @@ -192,6 +170,24 @@ class Plugin extends ServerPlugin { } } + private function preloadCollection(PropFind $propFind, ICollection $collection): void { + if (!$collection instanceof CalendarHome || $propFind->getDepth() !== 1) { + return; + } + + $backend = $collection->getCalDAVBackend(); + if (!$backend instanceof CalDavBackend) { + return; + } + + $calendars = $collection->getChildren(); + $calendars = array_filter($calendars, static fn (INode $node) => $node instanceof IShareable); + /** @var int[] $resourceIds */ + $resourceIds = array_map( + static fn (IShareable $node) => $node->getResourceId(), $calendars); + $backend->preloadShares($resourceIds); + } + /** * This event is triggered when properties are requested for a certain * node. @@ -203,20 +199,6 @@ class Plugin extends ServerPlugin { * @return void */ public function propFind(PropFind $propFind, INode $node) { - if ($node instanceof CalendarHome && $propFind->getDepth() === 1) { - $backend = $node->getCalDAVBackend(); - if ($backend instanceof CalDavBackend) { - $calendars = $node->getChildren(); - $calendars = array_filter($calendars, function (INode $node) { - return $node instanceof IShareable; - }); - /** @var int[] $resourceIds */ - $resourceIds = array_map(function (IShareable $node) { - return $node->getResourceId(); - }, $calendars); - $backend->preloadShares($resourceIds); - } - } if ($node instanceof IShareable) { $propFind->handle('{' . Plugin::NS_OWNCLOUD . '}invite', function () use ($node) { return new Invite( diff --git a/apps/dav/lib/DAV/Sharing/SharingMapper.php b/apps/dav/lib/DAV/Sharing/SharingMapper.php index c0c939c7a5e..e4722208189 100644 --- a/apps/dav/lib/DAV/Sharing/SharingMapper.php +++ b/apps/dav/lib/DAV/Sharing/SharingMapper.php @@ -1,49 +1,49 @@ <?php declare(strict_types=1); -/* - * @copyright 2024 Anna Larch <anna.larch@gmx.net> - * - * @author Anna Larch <anna.larch@gmx.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library 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 library. If not, see <http://www.gnu.org/licenses/>. +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\DAV\DAV\Sharing; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; class SharingMapper { - public function __construct(private IDBConnection $db) { + public function __construct( + private IDBConnection $db, + ) { } - public function getSharesForId(int $resourceId, string $resourceType): array { + protected function getSharesForIdByAccess(int $resourceId, string $resourceType, bool $sharesWithAccess): array { $query = $this->db->getQueryBuilder(); - $result = $query->select(['principaluri', 'access']) + $query->select(['principaluri', 'access']) ->from('dav_shares') ->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId, IQueryBuilder::PARAM_INT))) ->andWhere($query->expr()->eq('type', $query->createNamedParameter($resourceType, IQueryBuilder::PARAM_STR))) - ->andWhere($query->expr()->neq('access', $query->createNamedParameter(Backend::ACCESS_UNSHARED, IQueryBuilder::PARAM_INT))) - ->groupBy(['principaluri', 'access']) - ->executeQuery(); + ->groupBy(['principaluri', 'access']); + if ($sharesWithAccess) { + $query->andWhere($query->expr()->neq('access', $query->createNamedParameter(Backend::ACCESS_UNSHARED, IQueryBuilder::PARAM_INT))); + } else { + $query->andWhere($query->expr()->eq('access', $query->createNamedParameter(Backend::ACCESS_UNSHARED, IQueryBuilder::PARAM_INT))); + } + + $result = $query->executeQuery(); $rows = $result->fetchAll(); $result->closeCursor(); return $rows; } + public function getSharesForId(int $resourceId, string $resourceType): array { + return $this->getSharesForIdByAccess($resourceId, $resourceType, true); + } + + public function getUnsharesForId(int $resourceId, string $resourceType): array { + return $this->getSharesForIdByAccess($resourceId, $resourceType, false); + } + public function getSharesForIds(array $resourceIds, string $resourceType): array { $query = $this->db->getQueryBuilder(); $result = $query->select(['resourceid', 'principaluri', 'access']) @@ -110,4 +110,28 @@ class SharingMapper { ->andWhere($query->expr()->eq('type', $query->createNamedParameter($resourceType))) ->executeStatement(); } + + public function getSharesByPrincipals(array $principals, string $resourceType): array { + $query = $this->db->getQueryBuilder(); + $result = $query->select(['id', 'principaluri', 'type', 'access', 'resourceid']) + ->from('dav_shares') + ->where($query->expr()->in('principaluri', $query->createNamedParameter($principals, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY)) + ->andWhere($query->expr()->eq('type', $query->createNamedParameter($resourceType))) + ->orderBy('id') + ->executeQuery(); + + $rows = $result->fetchAll(); + $result->closeCursor(); + + return $rows; + } + + public function deleteUnsharesByPrincipal(string $principal, string $resourceType): void { + $query = $this->db->getQueryBuilder(); + $query->delete('dav_shares') + ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principal))) + ->andWhere($query->expr()->eq('type', $query->createNamedParameter($resourceType))) + ->andWhere($query->expr()->eq('access', $query->createNamedParameter(Backend::ACCESS_UNSHARED, IQueryBuilder::PARAM_INT))) + ->executeStatement(); + } } diff --git a/apps/dav/lib/DAV/Sharing/SharingService.php b/apps/dav/lib/DAV/Sharing/SharingService.php index 4b2a0beed1c..11459e12d74 100644 --- a/apps/dav/lib/DAV/Sharing/SharingService.php +++ b/apps/dav/lib/DAV/Sharing/SharingService.php @@ -2,28 +2,16 @@ declare(strict_types=1); /** - * @copyright 2024 Anna Larch <anna.larch@gmx.net> - * - * @author Anna Larch <anna.larch@gmx.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\DAV\DAV\Sharing; abstract class SharingService { protected string $resourceType = ''; - public function __construct(protected SharingMapper $mapper) { + public function __construct( + protected SharingMapper $mapper, + ) { } public function getResourceType(): string { @@ -55,17 +43,11 @@ abstract class SharingService { return $this->mapper->getSharesForId($resourceId, $this->getResourceType()); } - public function getSharesForIds(array $resourceIds): array { - return $this->mapper->getSharesForIds($resourceIds, $this->getResourceType()); + public function getUnshares(int $resourceId): array { + return $this->mapper->getUnsharesForId($resourceId, $this->getResourceType()); } - /** - * @param array $oldShares - * @return bool - */ - public function hasGroupShare(array $oldShares): bool { - return !empty(array_filter($oldShares, function (array $share) { - return $share['{http://owncloud.org/ns}group-share'] === true; - })); + public function getSharesForIds(array $resourceIds): array { + return $this->mapper->getSharesForIds($resourceIds, $this->getResourceType()); } } diff --git a/apps/dav/lib/DAV/Sharing/Xml/Invite.php b/apps/dav/lib/DAV/Sharing/Xml/Invite.php index 3c03dfa5d25..7a20dbe6df7 100644 --- a/apps/dav/lib/DAV/Sharing/Xml/Invite.php +++ b/apps/dav/lib/DAV/Sharing/Xml/Invite.php @@ -1,28 +1,10 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Robin Appelman <robin@icewind.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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-FileCopyrightText: fruux GmbH (https://fruux.com/) + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\DAV\DAV\Sharing\Xml; @@ -45,21 +27,6 @@ use Sabre\Xml\XmlSerializable; class Invite implements XmlSerializable { /** - * The list of users a calendar has been shared to. - * - * @var array - */ - protected $users; - - /** - * The organizer contains information about the person who shared the - * object. - * - * @var array|null - */ - protected $organizer; - - /** * Creates the property. * * Users is an array. Each element of the array has the following @@ -85,9 +52,17 @@ class Invite implements XmlSerializable { * * @param array $users */ - public function __construct(array $users, ?array $organizer = null) { - $this->users = $users; - $this->organizer = $organizer; + public function __construct( + /** + * The list of users a calendar has been shared to. + */ + protected array $users, + /** + * The organizer contains information about the person who shared the + * object. + */ + protected ?array $organizer = null, + ) { } /** diff --git a/apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php b/apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php index 6a97cd30d9f..aefb39c5701 100644 --- a/apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php +++ b/apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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 OCA\DAV\DAV\Sharing\Xml; @@ -27,24 +12,21 @@ use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; class ShareRequest implements XmlDeserializable { - public $set = []; - - public $remove = []; - /** * Constructor * * @param array $set * @param array $remove */ - public function __construct(array $set, array $remove) { - $this->set = $set; - $this->remove = $remove; + public function __construct( + public array $set, + public array $remove, + ) { } public static function xmlDeserialize(Reader $reader) { $elements = $reader->parseInnerTree([ - '{' . Plugin::NS_OWNCLOUD. '}set' => 'Sabre\\Xml\\Element\\KeyValue', + '{' . Plugin::NS_OWNCLOUD . '}set' => 'Sabre\\Xml\\Element\\KeyValue', '{' . Plugin::NS_OWNCLOUD . '}remove' => 'Sabre\\Xml\\Element\\KeyValue', ]); diff --git a/apps/dav/lib/DAV/SystemPrincipalBackend.php b/apps/dav/lib/DAV/SystemPrincipalBackend.php index d5739212e86..9760d68f05f 100644 --- a/apps/dav/lib/DAV/SystemPrincipalBackend.php +++ b/apps/dav/lib/DAV/SystemPrincipalBackend.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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 OCA\DAV\DAV; diff --git a/apps/dav/lib/DAV/ViewOnlyPlugin.php b/apps/dav/lib/DAV/ViewOnlyPlugin.php index 389dd96efb4..9b9615b8063 100644 --- a/apps/dav/lib/DAV/ViewOnlyPlugin.php +++ b/apps/dav/lib/DAV/ViewOnlyPlugin.php @@ -1,22 +1,9 @@ <?php + /** - * @author Piotr Mrowczynski piotr@owncloud.com - * - * @copyright Copyright (c) 2019, ownCloud GmbH - * @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: 2022-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2019 ownCloud GmbH + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\DAV\DAV; @@ -26,6 +13,7 @@ use OCA\DAV\Connector\Sabre\File as DavFile; use OCA\Files_Versions\Sabre\VersionFile; use OCP\Files\Folder; use OCP\Files\NotFoundException; +use OCP\Files\Storage\ISharedStorage; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; @@ -36,12 +24,10 @@ use Sabre\HTTP\RequestInterface; */ class ViewOnlyPlugin extends ServerPlugin { private ?Server $server = null; - private ?Folder $userFolder; public function __construct( - ?Folder $userFolder, + private ?Folder $userFolder, ) { - $this->userFolder = $userFolder; } /** @@ -58,6 +44,7 @@ class ViewOnlyPlugin extends ServerPlugin { //Sabre\DAV\CorePlugin::httpGet $this->server->on('method:GET', [$this, 'checkViewOnly'], 90); $this->server->on('method:COPY', [$this, 'checkViewOnly'], 90); + $this->server->on('method:MOVE', [$this, 'checkViewOnly'], 90); } /** @@ -85,7 +72,7 @@ class ViewOnlyPlugin extends ServerPlugin { $nodes = $this->userFolder->getById($node->getId()); $node = array_pop($nodes); if (!$node) { - throw new NotFoundException("Version file not accessible by current user"); + throw new NotFoundException('Version file not accessible by current user'); } } } else { @@ -94,21 +81,28 @@ class ViewOnlyPlugin extends ServerPlugin { $storage = $node->getStorage(); - if (!$storage->instanceOfStorage(\OCA\Files_Sharing\SharedStorage::class)) { + if (!$storage->instanceOfStorage(ISharedStorage::class)) { return true; } + // Extract extra permissions - /** @var \OCA\Files_Sharing\SharedStorage $storage */ + /** @var ISharedStorage $storage */ $share = $storage->getShare(); - $attributes = $share->getAttributes(); if ($attributes === null) { return true; } - // Check if read-only and on whether permission can download is both set and disabled. + // We have two options here, if download is disabled, but viewing is allowed, + // we still allow the GET request to return the file content. $canDownload = $attributes->getAttribute('permissions', 'download'); - if ($canDownload !== null && !$canDownload) { + if (!$share->canSeeContent()) { + throw new Forbidden('Access to this shared resource has been denied because its download permission is disabled.'); + } + + // If download is disabled, we disable the COPY and MOVE methods even if the + // shareapi_allow_view_without_download is set to true. + if ($request->getMethod() !== 'GET' && ($canDownload !== null && !$canDownload)) { throw new Forbidden('Access to this shared resource has been denied because its download permission is disabled.'); } } catch (NotFound $e) { |