aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/lib/DAV/Sharing
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/lib/DAV/Sharing')
-rw-r--r--apps/dav/lib/DAV/Sharing/Backend.php70
-rw-r--r--apps/dav/lib/DAV/Sharing/Plugin.php59
-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
6 files changed, 149 insertions, 91 deletions
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..82b000bc8ce 100644
--- a/apps/dav/lib/DAV/Sharing/Plugin.php
+++ b/apps/dav/lib/DAV/Sharing/Plugin.php
@@ -12,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;
@@ -26,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,
+ ) {
}
/**
@@ -96,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']);
}
@@ -110,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;
}
@@ -165,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');
@@ -175,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.
@@ -186,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 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',
]);