aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Collaboration/Resources
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Collaboration/Resources')
-rw-r--r--lib/private/Collaboration/Resources/Collection.php180
-rw-r--r--lib/private/Collaboration/Resources/Listener.php53
-rw-r--r--lib/private/Collaboration/Resources/Manager.php467
-rw-r--r--lib/private/Collaboration/Resources/ProviderManager.php50
-rw-r--r--lib/private/Collaboration/Resources/Resource.php113
5 files changed, 863 insertions, 0 deletions
diff --git a/lib/private/Collaboration/Resources/Collection.php b/lib/private/Collaboration/Resources/Collection.php
new file mode 100644
index 00000000000..2481a3e9a09
--- /dev/null
+++ b/lib/private/Collaboration/Resources/Collection.php
@@ -0,0 +1,180 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Collaboration\Resources;
+
+use Doctrine\DBAL\Exception\ConstraintViolationException;
+use OCP\Collaboration\Resources\ICollection;
+use OCP\Collaboration\Resources\IManager;
+use OCP\Collaboration\Resources\IResource;
+use OCP\Collaboration\Resources\ResourceException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\IUser;
+
+class Collection implements ICollection {
+ /** @var IResource[] */
+ protected array $resources = [];
+
+ public function __construct(
+ /** @var Manager $manager */
+ protected IManager $manager,
+ protected IDBConnection $connection,
+ protected int $id,
+ protected string $name,
+ protected ?IUser $userForAccess = null,
+ protected ?bool $access = null,
+ ) {
+ }
+
+ /**
+ * @since 16.0.0
+ */
+ public function getId(): int {
+ return $this->id;
+ }
+
+ /**
+ * @since 16.0.0
+ */
+ public function getName(): string {
+ return $this->name;
+ }
+
+ /**
+ * @since 16.0.0
+ */
+ public function setName(string $name): void {
+ $query = $this->connection->getQueryBuilder();
+ $query->update(Manager::TABLE_COLLECTIONS)
+ ->set('name', $query->createNamedParameter($name))
+ ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
+ $query->executeStatement();
+
+ $this->name = $name;
+ }
+
+ /**
+ * @return IResource[]
+ * @since 16.0.0
+ */
+ public function getResources(): array {
+ if (empty($this->resources)) {
+ $this->resources = $this->manager->getResourcesByCollectionForUser($this, $this->userForAccess);
+ }
+
+ return $this->resources;
+ }
+
+ /**
+ * Adds a resource to a collection
+ *
+ * @throws ResourceException when the resource is already part of the collection
+ * @since 16.0.0
+ */
+ public function addResource(IResource $resource): void {
+ array_map(function (IResource $r) use ($resource) {
+ if ($this->isSameResource($r, $resource)) {
+ throw new ResourceException('Already part of the collection');
+ }
+ }, $this->getResources());
+
+ $this->resources[] = $resource;
+
+ $query = $this->connection->getQueryBuilder();
+ $query->insert(Manager::TABLE_RESOURCES)
+ ->values([
+ 'collection_id' => $query->createNamedParameter($this->id, IQueryBuilder::PARAM_INT),
+ 'resource_type' => $query->createNamedParameter($resource->getType()),
+ 'resource_id' => $query->createNamedParameter($resource->getId()),
+ ]);
+
+ try {
+ $query->execute();
+ } catch (ConstraintViolationException $e) {
+ throw new ResourceException('Already part of the collection');
+ }
+
+ $this->manager->invalidateAccessCacheForCollection($this);
+ }
+
+ /**
+ * Removes a resource from a collection
+ *
+ * @since 16.0.0
+ */
+ public function removeResource(IResource $resource): void {
+ $this->resources = array_filter($this->getResources(), function (IResource $r) use ($resource) {
+ return !$this->isSameResource($r, $resource);
+ });
+
+ $query = $this->connection->getQueryBuilder();
+ $query->delete(Manager::TABLE_RESOURCES)
+ ->where($query->expr()->eq('collection_id', $query->createNamedParameter($this->id, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType())))
+ ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())));
+ $query->executeStatement();
+
+ if (empty($this->resources)) {
+ $this->removeCollection();
+ } else {
+ $this->manager->invalidateAccessCacheForCollection($this);
+ }
+ }
+
+ /**
+ * Can a user/guest access the collection
+ *
+ * @since 16.0.0
+ */
+ public function canAccess(?IUser $user): bool {
+ if ($user instanceof IUser) {
+ return $this->canUserAccess($user);
+ }
+ return $this->canGuestAccess();
+ }
+
+ protected function canUserAccess(IUser $user): bool {
+ if (\is_bool($this->access) && $this->userForAccess instanceof IUser && $user->getUID() === $this->userForAccess->getUID()) {
+ return $this->access;
+ }
+
+ $access = $this->manager->canAccessCollection($this, $user);
+ if ($this->userForAccess instanceof IUser && $user->getUID() === $this->userForAccess->getUID()) {
+ $this->access = $access;
+ }
+ return $access;
+ }
+
+ protected function canGuestAccess(): bool {
+ if (\is_bool($this->access) && !$this->userForAccess instanceof IUser) {
+ return $this->access;
+ }
+
+ $access = $this->manager->canAccessCollection($this, null);
+ if (!$this->userForAccess instanceof IUser) {
+ $this->access = $access;
+ }
+ return $access;
+ }
+
+ protected function isSameResource(IResource $resource1, IResource $resource2): bool {
+ return $resource1->getType() === $resource2->getType()
+ && $resource1->getId() === $resource2->getId();
+ }
+
+ protected function removeCollection(): void {
+ $query = $this->connection->getQueryBuilder();
+ $query->delete(Manager::TABLE_COLLECTIONS)
+ ->where($query->expr()->eq('id', $query->createNamedParameter($this->id, IQueryBuilder::PARAM_INT)));
+ $query->executeStatement();
+
+ $this->manager->invalidateAccessCacheForCollection($this);
+ $this->id = 0;
+ }
+}
diff --git a/lib/private/Collaboration/Resources/Listener.php b/lib/private/Collaboration/Resources/Listener.php
new file mode 100644
index 00000000000..dfdde24d78e
--- /dev/null
+++ b/lib/private/Collaboration/Resources/Listener.php
@@ -0,0 +1,53 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Collaboration\Resources;
+
+use OCP\Collaboration\Resources\IManager;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Group\Events\BeforeGroupDeletedEvent;
+use OCP\Group\Events\UserAddedEvent;
+use OCP\Group\Events\UserRemovedEvent;
+use OCP\User\Events\UserDeletedEvent;
+
+class Listener {
+ public static function register(IEventDispatcher $eventDispatcher): void {
+ $eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
+ $user = $event->getUser();
+ /** @var IManager $resourceManager */
+ $resourceManager = \OCP\Server::get(IManager::class);
+
+ $resourceManager->invalidateAccessCacheForUser($user);
+ });
+ $eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
+ $user = $event->getUser();
+ /** @var IManager $resourceManager */
+ $resourceManager = \OCP\Server::get(IManager::class);
+
+ $resourceManager->invalidateAccessCacheForUser($user);
+ });
+
+ $eventDispatcher->addListener(UserDeletedEvent::class, function (UserDeletedEvent $event) {
+ $user = $event->getUser();
+ /** @var IManager $resourceManager */
+ $resourceManager = \OCP\Server::get(IManager::class);
+
+ $resourceManager->invalidateAccessCacheForUser($user);
+ });
+
+ $eventDispatcher->addListener(BeforeGroupDeletedEvent::class, function (BeforeGroupDeletedEvent $event) {
+ $group = $event->getGroup();
+ /** @var IManager $resourceManager */
+ $resourceManager = \OCP\Server::get(IManager::class);
+
+ foreach ($group->getUsers() as $user) {
+ $resourceManager->invalidateAccessCacheForUser($user);
+ }
+ });
+ }
+}
diff --git a/lib/private/Collaboration/Resources/Manager.php b/lib/private/Collaboration/Resources/Manager.php
new file mode 100644
index 00000000000..8d1e4b13287
--- /dev/null
+++ b/lib/private/Collaboration/Resources/Manager.php
@@ -0,0 +1,467 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Collaboration\Resources;
+
+use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
+use OCP\Collaboration\Resources\CollectionException;
+use OCP\Collaboration\Resources\ICollection;
+use OCP\Collaboration\Resources\IManager;
+use OCP\Collaboration\Resources\IProvider;
+use OCP\Collaboration\Resources\IProviderManager;
+use OCP\Collaboration\Resources\IResource;
+use OCP\Collaboration\Resources\ResourceException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\IUser;
+use Psr\Log\LoggerInterface;
+
+class Manager implements IManager {
+ public const TABLE_COLLECTIONS = 'collres_collections';
+ public const TABLE_RESOURCES = 'collres_resources';
+ public const TABLE_ACCESS_CACHE = 'collres_accesscache';
+
+ /** @var string[] */
+ protected array $providers = [];
+
+ public function __construct(
+ protected IDBConnection $connection,
+ protected IProviderManager $providerManager,
+ protected LoggerInterface $logger,
+ ) {
+ }
+
+ /**
+ * @throws CollectionException when the collection could not be found
+ * @since 16.0.0
+ */
+ public function getCollection(int $id): ICollection {
+ $query = $this->connection->getQueryBuilder();
+ $query->select('*')
+ ->from(self::TABLE_COLLECTIONS)
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
+ $result = $query->execute();
+ $row = $result->fetch();
+ $result->closeCursor();
+
+ if (!$row) {
+ throw new CollectionException('Collection not found');
+ }
+
+ return new Collection($this, $this->connection, (int)$row['id'], (string)$row['name']);
+ }
+
+ /**
+ * @throws CollectionException when the collection could not be found
+ * @since 16.0.0
+ */
+ public function getCollectionForUser(int $id, ?IUser $user): ICollection {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->select('*')
+ ->from(self::TABLE_COLLECTIONS, 'c')
+ ->leftJoin(
+ 'c', self::TABLE_ACCESS_CACHE, 'a',
+ $query->expr()->andX(
+ $query->expr()->eq('c.id', 'a.collection_id'),
+ $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
+ )
+ )
+ ->where($query->expr()->eq('c.id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
+ $result = $query->execute();
+ $row = $result->fetch();
+ $result->closeCursor();
+
+ if (!$row) {
+ throw new CollectionException('Collection not found');
+ }
+
+ $access = $row['access'] === null ? null : (bool)$row['access'];
+ if ($user instanceof IUser) {
+ return new Collection($this, $this->connection, (int)$row['id'], (string)$row['name'], $user, $access);
+ }
+
+ return new Collection($this, $this->connection, (int)$row['id'], (string)$row['name'], $user, $access);
+ }
+
+ /**
+ * @return ICollection[]
+ * @since 16.0.0
+ */
+ public function searchCollections(IUser $user, string $filter, int $limit = 50, int $start = 0): array {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user->getUID();
+
+ $query->select('c.*', 'a.access')
+ ->from(self::TABLE_COLLECTIONS, 'c')
+ ->leftJoin(
+ 'c', self::TABLE_ACCESS_CACHE, 'a',
+ $query->expr()->andX(
+ $query->expr()->eq('c.id', 'a.collection_id'),
+ $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
+ )
+ )
+ ->where($query->expr()->eq('a.access', $query->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
+ ->orderBy('c.id')
+ ->setMaxResults($limit)
+ ->setFirstResult($start);
+
+ if ($filter !== '') {
+ $query->andWhere($query->expr()->iLike('c.name', $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($filter) . '%')));
+ }
+
+ $result = $query->execute();
+ $collections = [];
+
+ $foundResults = 0;
+ while ($row = $result->fetch()) {
+ $foundResults++;
+ $access = $row['access'] === null ? null : (bool)$row['access'];
+ $collection = new Collection($this, $this->connection, (int)$row['id'], (string)$row['name'], $user, $access);
+ if ($collection->canAccess($user)) {
+ $collections[] = $collection;
+ }
+ }
+ $result->closeCursor();
+
+ if (empty($collections) && $foundResults === $limit) {
+ return $this->searchCollections($user, $filter, $limit, $start + $limit);
+ }
+
+ return $collections;
+ }
+
+ /**
+ * @since 16.0.0
+ */
+ public function newCollection(string $name): ICollection {
+ $query = $this->connection->getQueryBuilder();
+ $query->insert(self::TABLE_COLLECTIONS)
+ ->values([
+ 'name' => $query->createNamedParameter($name),
+ ]);
+ $query->execute();
+
+ return new Collection($this, $this->connection, $query->getLastInsertId(), $name);
+ }
+
+ /**
+ * @since 16.0.0
+ */
+ public function createResource(string $type, string $id): IResource {
+ return new Resource($this, $this->connection, $type, $id);
+ }
+
+ /**
+ * @throws ResourceException
+ * @since 16.0.0
+ */
+ public function getResourceForUser(string $type, string $id, ?IUser $user): IResource {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->select('r.*', 'a.access')
+ ->from(self::TABLE_RESOURCES, 'r')
+ ->leftJoin(
+ 'r', self::TABLE_ACCESS_CACHE, 'a',
+ $query->expr()->andX(
+ $query->expr()->eq('r.resource_id', 'a.resource_id'),
+ $query->expr()->eq('r.resource_type', 'a.resource_type'),
+ $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
+ )
+ )
+ ->where($query->expr()->eq('r.resource_type', $query->createNamedParameter($type, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('r.resource_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_STR)));
+ $result = $query->execute();
+ $row = $result->fetch();
+ $result->closeCursor();
+
+ if (!$row) {
+ throw new ResourceException('Resource not found');
+ }
+
+ $access = $row['access'] === null ? null : (bool)$row['access'];
+ if ($user instanceof IUser) {
+ return new Resource($this, $this->connection, $type, $id, $user, $access);
+ }
+
+ return new Resource($this, $this->connection, $type, $id, null, $access);
+ }
+
+ /**
+ * @return IResource[]
+ * @since 16.0.0
+ */
+ public function getResourcesByCollectionForUser(ICollection $collection, ?IUser $user): array {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->select('r.*', 'a.access')
+ ->from(self::TABLE_RESOURCES, 'r')
+ ->leftJoin(
+ 'r', self::TABLE_ACCESS_CACHE, 'a',
+ $query->expr()->andX(
+ $query->expr()->eq('r.resource_id', 'a.resource_id'),
+ $query->expr()->eq('r.resource_type', 'a.resource_type'),
+ $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
+ )
+ )
+ ->where($query->expr()->eq('r.collection_id', $query->createNamedParameter($collection->getId(), IQueryBuilder::PARAM_INT)));
+
+ $resources = [];
+ $result = $query->execute();
+ while ($row = $result->fetch()) {
+ $access = $row['access'] === null ? null : (bool)$row['access'];
+ $resources[] = new Resource($this, $this->connection, $row['resource_type'], $row['resource_id'], $user, $access);
+ }
+ $result->closeCursor();
+
+ return $resources;
+ }
+
+ /**
+ * Get the rich object data of a resource
+ *
+ * @since 16.0.0
+ */
+ public function getResourceRichObject(IResource $resource): array {
+ foreach ($this->providerManager->getResourceProviders() as $provider) {
+ if ($provider->getType() === $resource->getType()) {
+ try {
+ return $provider->getResourceRichObject($resource);
+ } catch (ResourceException $e) {
+ }
+ }
+ }
+
+ return [];
+ }
+
+ /**
+ * Can a user/guest access the collection
+ *
+ * @since 16.0.0
+ */
+ public function canAccessResource(IResource $resource, ?IUser $user): bool {
+ $access = $this->checkAccessCacheForUserByResource($resource, $user);
+ if (\is_bool($access)) {
+ return $access;
+ }
+
+ $access = false;
+ foreach ($this->providerManager->getResourceProviders() as $provider) {
+ if ($provider->getType() === $resource->getType()) {
+ try {
+ if ($provider->canAccessResource($resource, $user)) {
+ $access = true;
+ break;
+ }
+ } catch (ResourceException $e) {
+ }
+ }
+ }
+
+ $this->cacheAccessForResource($resource, $user, $access);
+ return $access;
+ }
+
+ /**
+ * Can a user/guest access the collection
+ *
+ * @since 16.0.0
+ */
+ public function canAccessCollection(ICollection $collection, ?IUser $user): bool {
+ $access = $this->checkAccessCacheForUserByCollection($collection, $user);
+ if (\is_bool($access)) {
+ return $access;
+ }
+
+ $access = null;
+ // Access is granted when a user can access all resources
+ foreach ($collection->getResources() as $resource) {
+ if (!$resource->canAccess($user)) {
+ $access = false;
+ break;
+ }
+
+ $access = true;
+ }
+
+ $this->cacheAccessForCollection($collection, $user, $access);
+ return $access;
+ }
+
+ protected function checkAccessCacheForUserByResource(IResource $resource, ?IUser $user): ?bool {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->select('access')
+ ->from(self::TABLE_ACCESS_CACHE)
+ ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId(), IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType(), IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
+ ->setMaxResults(1);
+
+ $hasAccess = null;
+ $result = $query->execute();
+ if ($row = $result->fetch()) {
+ $hasAccess = (bool)$row['access'];
+ }
+ $result->closeCursor();
+
+ return $hasAccess;
+ }
+
+ protected function checkAccessCacheForUserByCollection(ICollection $collection, ?IUser $user): ?bool {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->select('access')
+ ->from(self::TABLE_ACCESS_CACHE)
+ ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId(), IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
+ ->setMaxResults(1);
+
+ $hasAccess = null;
+ $result = $query->execute();
+ if ($row = $result->fetch()) {
+ $hasAccess = (bool)$row['access'];
+ }
+ $result->closeCursor();
+
+ return $hasAccess;
+ }
+
+ public function cacheAccessForResource(IResource $resource, ?IUser $user, bool $access): void {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->insert(self::TABLE_ACCESS_CACHE)
+ ->values([
+ 'user_id' => $query->createNamedParameter($userId),
+ 'resource_id' => $query->createNamedParameter($resource->getId()),
+ 'resource_type' => $query->createNamedParameter($resource->getType()),
+ 'access' => $query->createNamedParameter($access, IQueryBuilder::PARAM_BOOL),
+ ]);
+ try {
+ $query->execute();
+ } catch (UniqueConstraintViolationException $e) {
+ }
+ }
+
+ public function cacheAccessForCollection(ICollection $collection, ?IUser $user, bool $access): void {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->insert(self::TABLE_ACCESS_CACHE)
+ ->values([
+ 'user_id' => $query->createNamedParameter($userId),
+ 'collection_id' => $query->createNamedParameter($collection->getId()),
+ 'access' => $query->createNamedParameter($access, IQueryBuilder::PARAM_BOOL),
+ ]);
+ try {
+ $query->execute();
+ } catch (UniqueConstraintViolationException $e) {
+ }
+ }
+
+ public function invalidateAccessCacheForUser(?IUser $user): void {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->delete(self::TABLE_ACCESS_CACHE)
+ ->where($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
+ $query->execute();
+ }
+
+ public function invalidateAccessCacheForResource(IResource $resource): void {
+ $query = $this->connection->getQueryBuilder();
+
+ $query->delete(self::TABLE_ACCESS_CACHE)
+ ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())))
+ ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType(), IQueryBuilder::PARAM_STR)));
+ $query->execute();
+
+ foreach ($resource->getCollections() as $collection) {
+ $this->invalidateAccessCacheForCollection($collection);
+ }
+ }
+
+ public function invalidateAccessCacheForAllCollections(): void {
+ $query = $this->connection->getQueryBuilder();
+
+ $query->delete(self::TABLE_ACCESS_CACHE)
+ ->where($query->expr()->neq('collection_id', $query->createNamedParameter(0)));
+ $query->execute();
+ }
+
+ public function invalidateAccessCacheForCollection(ICollection $collection): void {
+ $query = $this->connection->getQueryBuilder();
+
+ $query->delete(self::TABLE_ACCESS_CACHE)
+ ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId())));
+ $query->execute();
+ }
+
+ public function invalidateAccessCacheForProvider(IProvider $provider): void {
+ $query = $this->connection->getQueryBuilder();
+
+ $query->delete(self::TABLE_ACCESS_CACHE)
+ ->where($query->expr()->eq('resource_type', $query->createNamedParameter($provider->getType(), IQueryBuilder::PARAM_STR)));
+ $query->execute();
+ }
+
+ public function invalidateAccessCacheForResourceByUser(IResource $resource, ?IUser $user): void {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->delete(self::TABLE_ACCESS_CACHE)
+ ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())))
+ ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
+ $query->execute();
+
+ foreach ($resource->getCollections() as $collection) {
+ $this->invalidateAccessCacheForCollectionByUser($collection, $user);
+ }
+ }
+
+ protected function invalidateAccessCacheForCollectionByUser(ICollection $collection, ?IUser $user): void {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->delete(self::TABLE_ACCESS_CACHE)
+ ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId())))
+ ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
+ $query->execute();
+ }
+
+ public function invalidateAccessCacheForProviderByUser(IProvider $provider, ?IUser $user): void {
+ $query = $this->connection->getQueryBuilder();
+ $userId = $user instanceof IUser ? $user->getUID() : '';
+
+ $query->delete(self::TABLE_ACCESS_CACHE)
+ ->where($query->expr()->eq('resource_type', $query->createNamedParameter($provider->getType(), IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
+ $query->execute();
+ }
+
+ public function registerResourceProvider(string $provider): void {
+ $this->logger->debug('\OC\Collaboration\Resources\Manager::registerResourceProvider is deprecated', ['provider' => $provider]);
+ $this->providerManager->registerResourceProvider($provider);
+ }
+
+ /**
+ * Get the resource type of the provider
+ *
+ * @since 16.0.0
+ */
+ public function getType(): string {
+ return '';
+ }
+}
diff --git a/lib/private/Collaboration/Resources/ProviderManager.php b/lib/private/Collaboration/Resources/ProviderManager.php
new file mode 100644
index 00000000000..0ce4ae7155a
--- /dev/null
+++ b/lib/private/Collaboration/Resources/ProviderManager.php
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Collaboration\Resources;
+
+use OCP\AppFramework\QueryException;
+use OCP\Collaboration\Resources\IProvider;
+use OCP\Collaboration\Resources\IProviderManager;
+use OCP\IServerContainer;
+use Psr\Log\LoggerInterface;
+
+class ProviderManager implements IProviderManager {
+ /** @var string[] */
+ protected array $providers = [];
+
+ /** @var IProvider[] */
+ protected array $providerInstances = [];
+
+ public function __construct(
+ protected IServerContainer $serverContainer,
+ protected LoggerInterface $logger,
+ ) {
+ }
+
+ public function getResourceProviders(): array {
+ if ($this->providers !== []) {
+ foreach ($this->providers as $provider) {
+ try {
+ $this->providerInstances[] = $this->serverContainer->query($provider);
+ } catch (QueryException $e) {
+ $this->logger->error("Could not query resource provider $provider: " . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ }
+ }
+ $this->providers = [];
+ }
+
+ return $this->providerInstances;
+ }
+
+ public function registerResourceProvider(string $provider): void {
+ $this->providers[] = $provider;
+ }
+}
diff --git a/lib/private/Collaboration/Resources/Resource.php b/lib/private/Collaboration/Resources/Resource.php
new file mode 100644
index 00000000000..19da3da7e7d
--- /dev/null
+++ b/lib/private/Collaboration/Resources/Resource.php
@@ -0,0 +1,113 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Collaboration\Resources;
+
+use OCP\Collaboration\Resources\ICollection;
+use OCP\Collaboration\Resources\IManager;
+use OCP\Collaboration\Resources\IResource;
+use OCP\IDBConnection;
+use OCP\IUser;
+
+class Resource implements IResource {
+ protected ?array $data = null;
+
+ public function __construct(
+ protected IManager $manager,
+ protected IDBConnection $connection,
+ protected string $type,
+ protected string $id,
+ protected ?IUser $userForAccess = null,
+ protected ?bool $access = null,
+ ) {
+ }
+
+ /**
+ * @since 16.0.0
+ */
+ public function getType(): string {
+ return $this->type;
+ }
+
+ /**
+ * @since 16.0.0
+ */
+ public function getId(): string {
+ return $this->id;
+ }
+
+ /**
+ * @since 16.0.0
+ */
+ public function getRichObject(): array {
+ if ($this->data === null) {
+ $this->data = $this->manager->getResourceRichObject($this);
+ }
+
+ return $this->data;
+ }
+
+ /**
+ * Can a user/guest access the resource
+ *
+ * @since 16.0.0
+ */
+ public function canAccess(?IUser $user): bool {
+ if ($user instanceof IUser) {
+ return $this->canUserAccess($user);
+ }
+ return $this->canGuestAccess();
+ }
+
+ protected function canUserAccess(IUser $user): bool {
+ if (\is_bool($this->access) && $this->userForAccess instanceof IUser && $user->getUID() === $this->userForAccess->getUID()) {
+ return $this->access;
+ }
+
+ $access = $this->manager->canAccessResource($this, $user);
+ if ($this->userForAccess instanceof IUser && $user->getUID() === $this->userForAccess->getUID()) {
+ $this->access = $access;
+ }
+ return $access;
+ }
+
+ protected function canGuestAccess(): bool {
+ if (\is_bool($this->access) && !$this->userForAccess instanceof IUser) {
+ return $this->access;
+ }
+
+ $access = $this->manager->canAccessResource($this, null);
+ if (!$this->userForAccess instanceof IUser) {
+ $this->access = $access;
+ }
+ return $access;
+ }
+
+ /**
+ * @return ICollection[]
+ * @since 16.0.0
+ */
+ public function getCollections(): array {
+ $collections = [];
+
+ $query = $this->connection->getQueryBuilder();
+
+ $query->select('collection_id')
+ ->from('collres_resources')
+ ->where($query->expr()->eq('resource_type', $query->createNamedParameter($this->getType())))
+ ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($this->getId())));
+
+ $result = $query->execute();
+ while ($row = $result->fetch()) {
+ $collections[] = $this->manager->getCollection((int)$row['collection_id']);
+ }
+ $result->closeCursor();
+
+ return $collections;
+ }
+}