diff options
Diffstat (limited to 'apps/dav/lib/DAV')
-rw-r--r-- | apps/dav/lib/DAV/CustomPropertiesBackend.php | 133 | ||||
-rw-r--r-- | apps/dav/lib/DAV/GroupPrincipalBackend.php | 38 | ||||
-rw-r--r-- | apps/dav/lib/DAV/PublicAuth.php | 4 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/Backend.php | 70 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/Plugin.php | 25 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/SharingMapper.php | 51 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/SharingService.php | 18 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/Xml/Invite.php | 29 | ||||
-rw-r--r-- | apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php | 13 | ||||
-rw-r--r-- | apps/dav/lib/DAV/ViewOnlyPlugin.php | 25 |
10 files changed, 220 insertions, 186 deletions
diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index ab62ae36c2c..f9a4f8ee986 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,12 +9,13 @@ namespace OCA\DAV\DAV; use Exception; +use OCA\DAV\CalDAV\Calendar; +use OCA\DAV\CalDAV\CalendarObject; +use OCA\DAV\CalDAV\DefaultCalendarValidator; use OCA\DAV\Connector\Sabre\Directory; -use OCA\DAV\Connector\Sabre\FilesPlugin; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IUser; -use Sabre\CalDAV\ICalendar; use Sabre\DAV\Exception as DavException; use Sabre\DAV\PropertyStorage\Backend\BackendInterface; use Sabre\DAV\PropFind; @@ -64,33 +66,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', ]; /** @@ -112,28 +97,11 @@ 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 XmlService $xmlService; /** @@ -142,15 +110,12 @@ 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 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, @@ -168,14 +133,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 @@ -237,6 +197,11 @@ class CustomPropertiesBackend implements BackendInterface { $this->cacheDirectory($path, $node); } + 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) { @@ -257,6 +222,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 * @@ -296,8 +271,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(); @@ -319,10 +294,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,16 +335,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 = []; @@ -411,7 +390,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( @@ -537,7 +516,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]; } @@ -552,7 +533,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 7ccc3f02594..77ba45182c9 100644 --- a/apps/dav/lib/DAV/GroupPrincipalBackend.php +++ b/apps/dav/lib/DAV/GroupPrincipalBackend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -20,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; } /** @@ -65,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); + } } } @@ -92,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); } @@ -201,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 2142bd04753..c2b4ada173a 100644 --- a/apps/dav/lib/DAV/PublicAuth.php +++ b/apps/dav/lib/DAV/PublicAuth.php @@ -53,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 cbae9ec1a37..d60f5cca7c6 100644 --- a/apps/dav/lib/DAV/Sharing/Backend.php +++ b/apps/dav/lib/DAV/Sharing/Backend.php @@ -27,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, @@ -58,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; } @@ -83,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); - } } } @@ -130,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); @@ -155,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') ]; @@ -203,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/Plugin.php b/apps/dav/lib/DAV/Sharing/Plugin.php index 98d6883e826..03e63813bab 100644 --- a/apps/dav/lib/DAV/Sharing/Plugin.php +++ b/apps/dav/lib/DAV/Sharing/Plugin.php @@ -12,6 +12,7 @@ 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; @@ -26,26 +27,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, + ) { } /** @@ -110,7 +103,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; } @@ -165,7 +158,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'); diff --git a/apps/dav/lib/DAV/Sharing/SharingMapper.php b/apps/dav/lib/DAV/Sharing/SharingMapper.php index 3bffddabd9c..e4722208189 100644 --- a/apps/dav/lib/DAV/Sharing/SharingMapper.php +++ b/apps/dav/lib/DAV/Sharing/SharingMapper.php @@ -11,24 +11,39 @@ 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']) @@ -95,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 a5a0f96ba59..11459e12d74 100644 --- a/apps/dav/lib/DAV/Sharing/SharingService.php +++ b/apps/dav/lib/DAV/Sharing/SharingService.php @@ -9,7 +9,9 @@ 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 { @@ -41,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 91eda03f45b..7a20dbe6df7 100644 --- a/apps/dav/lib/DAV/Sharing/Xml/Invite.php +++ b/apps/dav/lib/DAV/Sharing/Xml/Invite.php @@ -27,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 @@ -67,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 ca9602fb8c2..aefb39c5701 100644 --- a/apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php +++ b/apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php @@ -12,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/ViewOnlyPlugin.php b/apps/dav/lib/DAV/ViewOnlyPlugin.php index 80d0ddc8cf9..9b9615b8063 100644 --- a/apps/dav/lib/DAV/ViewOnlyPlugin.php +++ b/apps/dav/lib/DAV/ViewOnlyPlugin.php @@ -13,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; @@ -23,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; } /** @@ -45,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); } /** @@ -72,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 { @@ -81,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) { |