aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/lib/DAV
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/lib/DAV')
-rw-r--r--apps/dav/lib/DAV/CustomPropertiesBackend.php133
-rw-r--r--apps/dav/lib/DAV/GroupPrincipalBackend.php38
-rw-r--r--apps/dav/lib/DAV/PublicAuth.php4
-rw-r--r--apps/dav/lib/DAV/Sharing/Backend.php70
-rw-r--r--apps/dav/lib/DAV/Sharing/Plugin.php25
-rw-r--r--apps/dav/lib/DAV/Sharing/SharingMapper.php51
-rw-r--r--apps/dav/lib/DAV/Sharing/SharingService.php18
-rw-r--r--apps/dav/lib/DAV/Sharing/Xml/Invite.php29
-rw-r--r--apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php13
-rw-r--r--apps/dav/lib/DAV/ViewOnlyPlugin.php25
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) {