Manager.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\Collaboration\Resources;
  8. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  9. use OCP\Collaboration\Resources\CollectionException;
  10. use OCP\Collaboration\Resources\ICollection;
  11. use OCP\Collaboration\Resources\IManager;
  12. use OCP\Collaboration\Resources\IProvider;
  13. use OCP\Collaboration\Resources\IProviderManager;
  14. use OCP\Collaboration\Resources\IResource;
  15. use OCP\Collaboration\Resources\ResourceException;
  16. use OCP\DB\QueryBuilder\IQueryBuilder;
  17. use OCP\IDBConnection;
  18. use OCP\IUser;
  19. use Psr\Log\LoggerInterface;
  20. class Manager implements IManager {
  21. public const TABLE_COLLECTIONS = 'collres_collections';
  22. public const TABLE_RESOURCES = 'collres_resources';
  23. public const TABLE_ACCESS_CACHE = 'collres_accesscache';
  24. /** @var string[] */
  25. protected array $providers = [];
  26. public function __construct(
  27. protected IDBConnection $connection,
  28. protected IProviderManager $providerManager,
  29. protected LoggerInterface $logger,
  30. ) {
  31. }
  32. /**
  33. * @throws CollectionException when the collection could not be found
  34. * @since 16.0.0
  35. */
  36. public function getCollection(int $id): ICollection {
  37. $query = $this->connection->getQueryBuilder();
  38. $query->select('*')
  39. ->from(self::TABLE_COLLECTIONS)
  40. ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
  41. $result = $query->execute();
  42. $row = $result->fetch();
  43. $result->closeCursor();
  44. if (!$row) {
  45. throw new CollectionException('Collection not found');
  46. }
  47. return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name']);
  48. }
  49. /**
  50. * @throws CollectionException when the collection could not be found
  51. * @since 16.0.0
  52. */
  53. public function getCollectionForUser(int $id, ?IUser $user): ICollection {
  54. $query = $this->connection->getQueryBuilder();
  55. $userId = $user instanceof IUser ? $user->getUID() : '';
  56. $query->select('*')
  57. ->from(self::TABLE_COLLECTIONS, 'c')
  58. ->leftJoin(
  59. 'c', self::TABLE_ACCESS_CACHE, 'a',
  60. $query->expr()->andX(
  61. $query->expr()->eq('c.id', 'a.collection_id'),
  62. $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
  63. )
  64. )
  65. ->where($query->expr()->eq('c.id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
  66. $result = $query->execute();
  67. $row = $result->fetch();
  68. $result->closeCursor();
  69. if (!$row) {
  70. throw new CollectionException('Collection not found');
  71. }
  72. $access = $row['access'] === null ? null : (bool) $row['access'];
  73. if ($user instanceof IUser) {
  74. return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name'], $user, $access);
  75. }
  76. return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name'], $user, $access);
  77. }
  78. /**
  79. * @return ICollection[]
  80. * @since 16.0.0
  81. */
  82. public function searchCollections(IUser $user, string $filter, int $limit = 50, int $start = 0): array {
  83. $query = $this->connection->getQueryBuilder();
  84. $userId = $user->getUID();
  85. $query->select('c.*', 'a.access')
  86. ->from(self::TABLE_COLLECTIONS, 'c')
  87. ->leftJoin(
  88. 'c', self::TABLE_ACCESS_CACHE, 'a',
  89. $query->expr()->andX(
  90. $query->expr()->eq('c.id', 'a.collection_id'),
  91. $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
  92. )
  93. )
  94. ->where($query->expr()->eq('a.access', $query->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
  95. ->orderBy('c.id')
  96. ->setMaxResults($limit)
  97. ->setFirstResult($start);
  98. if ($filter !== '') {
  99. $query->andWhere($query->expr()->iLike('c.name', $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($filter) . '%')));
  100. }
  101. $result = $query->execute();
  102. $collections = [];
  103. $foundResults = 0;
  104. while ($row = $result->fetch()) {
  105. $foundResults++;
  106. $access = $row['access'] === null ? null : (bool) $row['access'];
  107. $collection = new Collection($this, $this->connection, (int)$row['id'], (string)$row['name'], $user, $access);
  108. if ($collection->canAccess($user)) {
  109. $collections[] = $collection;
  110. }
  111. }
  112. $result->closeCursor();
  113. if (empty($collections) && $foundResults === $limit) {
  114. return $this->searchCollections($user, $filter, $limit, $start + $limit);
  115. }
  116. return $collections;
  117. }
  118. /**
  119. * @since 16.0.0
  120. */
  121. public function newCollection(string $name): ICollection {
  122. $query = $this->connection->getQueryBuilder();
  123. $query->insert(self::TABLE_COLLECTIONS)
  124. ->values([
  125. 'name' => $query->createNamedParameter($name),
  126. ]);
  127. $query->execute();
  128. return new Collection($this, $this->connection, $query->getLastInsertId(), $name);
  129. }
  130. /**
  131. * @since 16.0.0
  132. */
  133. public function createResource(string $type, string $id): IResource {
  134. return new Resource($this, $this->connection, $type, $id);
  135. }
  136. /**
  137. * @throws ResourceException
  138. * @since 16.0.0
  139. */
  140. public function getResourceForUser(string $type, string $id, ?IUser $user): IResource {
  141. $query = $this->connection->getQueryBuilder();
  142. $userId = $user instanceof IUser ? $user->getUID() : '';
  143. $query->select('r.*', 'a.access')
  144. ->from(self::TABLE_RESOURCES, 'r')
  145. ->leftJoin(
  146. 'r', self::TABLE_ACCESS_CACHE, 'a',
  147. $query->expr()->andX(
  148. $query->expr()->eq('r.resource_id', 'a.resource_id'),
  149. $query->expr()->eq('r.resource_type', 'a.resource_type'),
  150. $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
  151. )
  152. )
  153. ->where($query->expr()->eq('r.resource_type', $query->createNamedParameter($type, IQueryBuilder::PARAM_STR)))
  154. ->andWhere($query->expr()->eq('r.resource_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_STR)));
  155. $result = $query->execute();
  156. $row = $result->fetch();
  157. $result->closeCursor();
  158. if (!$row) {
  159. throw new ResourceException('Resource not found');
  160. }
  161. $access = $row['access'] === null ? null : (bool) $row['access'];
  162. if ($user instanceof IUser) {
  163. return new Resource($this, $this->connection, $type, $id, $user, $access);
  164. }
  165. return new Resource($this, $this->connection, $type, $id, null, $access);
  166. }
  167. /**
  168. * @return IResource[]
  169. * @since 16.0.0
  170. */
  171. public function getResourcesByCollectionForUser(ICollection $collection, ?IUser $user): array {
  172. $query = $this->connection->getQueryBuilder();
  173. $userId = $user instanceof IUser ? $user->getUID() : '';
  174. $query->select('r.*', 'a.access')
  175. ->from(self::TABLE_RESOURCES, 'r')
  176. ->leftJoin(
  177. 'r', self::TABLE_ACCESS_CACHE, 'a',
  178. $query->expr()->andX(
  179. $query->expr()->eq('r.resource_id', 'a.resource_id'),
  180. $query->expr()->eq('r.resource_type', 'a.resource_type'),
  181. $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
  182. )
  183. )
  184. ->where($query->expr()->eq('r.collection_id', $query->createNamedParameter($collection->getId(), IQueryBuilder::PARAM_INT)));
  185. $resources = [];
  186. $result = $query->execute();
  187. while ($row = $result->fetch()) {
  188. $access = $row['access'] === null ? null : (bool) $row['access'];
  189. $resources[] = new Resource($this, $this->connection, $row['resource_type'], $row['resource_id'], $user, $access);
  190. }
  191. $result->closeCursor();
  192. return $resources;
  193. }
  194. /**
  195. * Get the rich object data of a resource
  196. *
  197. * @since 16.0.0
  198. */
  199. public function getResourceRichObject(IResource $resource): array {
  200. foreach ($this->providerManager->getResourceProviders() as $provider) {
  201. if ($provider->getType() === $resource->getType()) {
  202. try {
  203. return $provider->getResourceRichObject($resource);
  204. } catch (ResourceException $e) {
  205. }
  206. }
  207. }
  208. return [];
  209. }
  210. /**
  211. * Can a user/guest access the collection
  212. *
  213. * @since 16.0.0
  214. */
  215. public function canAccessResource(IResource $resource, ?IUser $user): bool {
  216. $access = $this->checkAccessCacheForUserByResource($resource, $user);
  217. if (\is_bool($access)) {
  218. return $access;
  219. }
  220. $access = false;
  221. foreach ($this->providerManager->getResourceProviders() as $provider) {
  222. if ($provider->getType() === $resource->getType()) {
  223. try {
  224. if ($provider->canAccessResource($resource, $user)) {
  225. $access = true;
  226. break;
  227. }
  228. } catch (ResourceException $e) {
  229. }
  230. }
  231. }
  232. $this->cacheAccessForResource($resource, $user, $access);
  233. return $access;
  234. }
  235. /**
  236. * Can a user/guest access the collection
  237. *
  238. * @since 16.0.0
  239. */
  240. public function canAccessCollection(ICollection $collection, ?IUser $user): bool {
  241. $access = $this->checkAccessCacheForUserByCollection($collection, $user);
  242. if (\is_bool($access)) {
  243. return $access;
  244. }
  245. $access = null;
  246. // Access is granted when a user can access all resources
  247. foreach ($collection->getResources() as $resource) {
  248. if (!$resource->canAccess($user)) {
  249. $access = false;
  250. break;
  251. }
  252. $access = true;
  253. }
  254. $this->cacheAccessForCollection($collection, $user, $access);
  255. return $access;
  256. }
  257. protected function checkAccessCacheForUserByResource(IResource $resource, ?IUser $user): ?bool {
  258. $query = $this->connection->getQueryBuilder();
  259. $userId = $user instanceof IUser ? $user->getUID() : '';
  260. $query->select('access')
  261. ->from(self::TABLE_ACCESS_CACHE)
  262. ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId(), IQueryBuilder::PARAM_STR)))
  263. ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType(), IQueryBuilder::PARAM_STR)))
  264. ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
  265. ->setMaxResults(1);
  266. $hasAccess = null;
  267. $result = $query->execute();
  268. if ($row = $result->fetch()) {
  269. $hasAccess = (bool) $row['access'];
  270. }
  271. $result->closeCursor();
  272. return $hasAccess;
  273. }
  274. protected function checkAccessCacheForUserByCollection(ICollection $collection, ?IUser $user): ?bool {
  275. $query = $this->connection->getQueryBuilder();
  276. $userId = $user instanceof IUser ? $user->getUID() : '';
  277. $query->select('access')
  278. ->from(self::TABLE_ACCESS_CACHE)
  279. ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId(), IQueryBuilder::PARAM_INT)))
  280. ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
  281. ->setMaxResults(1);
  282. $hasAccess = null;
  283. $result = $query->execute();
  284. if ($row = $result->fetch()) {
  285. $hasAccess = (bool) $row['access'];
  286. }
  287. $result->closeCursor();
  288. return $hasAccess;
  289. }
  290. public function cacheAccessForResource(IResource $resource, ?IUser $user, bool $access): void {
  291. $query = $this->connection->getQueryBuilder();
  292. $userId = $user instanceof IUser ? $user->getUID() : '';
  293. $query->insert(self::TABLE_ACCESS_CACHE)
  294. ->values([
  295. 'user_id' => $query->createNamedParameter($userId),
  296. 'resource_id' => $query->createNamedParameter($resource->getId()),
  297. 'resource_type' => $query->createNamedParameter($resource->getType()),
  298. 'access' => $query->createNamedParameter($access, IQueryBuilder::PARAM_BOOL),
  299. ]);
  300. try {
  301. $query->execute();
  302. } catch (UniqueConstraintViolationException $e) {
  303. }
  304. }
  305. public function cacheAccessForCollection(ICollection $collection, ?IUser $user, bool $access): void {
  306. $query = $this->connection->getQueryBuilder();
  307. $userId = $user instanceof IUser ? $user->getUID() : '';
  308. $query->insert(self::TABLE_ACCESS_CACHE)
  309. ->values([
  310. 'user_id' => $query->createNamedParameter($userId),
  311. 'collection_id' => $query->createNamedParameter($collection->getId()),
  312. 'access' => $query->createNamedParameter($access, IQueryBuilder::PARAM_BOOL),
  313. ]);
  314. try {
  315. $query->execute();
  316. } catch (UniqueConstraintViolationException $e) {
  317. }
  318. }
  319. public function invalidateAccessCacheForUser(?IUser $user): void {
  320. $query = $this->connection->getQueryBuilder();
  321. $userId = $user instanceof IUser ? $user->getUID() : '';
  322. $query->delete(self::TABLE_ACCESS_CACHE)
  323. ->where($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
  324. $query->execute();
  325. }
  326. public function invalidateAccessCacheForResource(IResource $resource): void {
  327. $query = $this->connection->getQueryBuilder();
  328. $query->delete(self::TABLE_ACCESS_CACHE)
  329. ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())))
  330. ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType(), IQueryBuilder::PARAM_STR)));
  331. $query->execute();
  332. foreach ($resource->getCollections() as $collection) {
  333. $this->invalidateAccessCacheForCollection($collection);
  334. }
  335. }
  336. public function invalidateAccessCacheForAllCollections(): void {
  337. $query = $this->connection->getQueryBuilder();
  338. $query->delete(self::TABLE_ACCESS_CACHE)
  339. ->where($query->expr()->neq('collection_id', $query->createNamedParameter(0)));
  340. $query->execute();
  341. }
  342. public function invalidateAccessCacheForCollection(ICollection $collection): void {
  343. $query = $this->connection->getQueryBuilder();
  344. $query->delete(self::TABLE_ACCESS_CACHE)
  345. ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId())));
  346. $query->execute();
  347. }
  348. public function invalidateAccessCacheForProvider(IProvider $provider): void {
  349. $query = $this->connection->getQueryBuilder();
  350. $query->delete(self::TABLE_ACCESS_CACHE)
  351. ->where($query->expr()->eq('resource_type', $query->createNamedParameter($provider->getType(), IQueryBuilder::PARAM_STR)));
  352. $query->execute();
  353. }
  354. public function invalidateAccessCacheForResourceByUser(IResource $resource, ?IUser $user): void {
  355. $query = $this->connection->getQueryBuilder();
  356. $userId = $user instanceof IUser ? $user->getUID() : '';
  357. $query->delete(self::TABLE_ACCESS_CACHE)
  358. ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())))
  359. ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
  360. $query->execute();
  361. foreach ($resource->getCollections() as $collection) {
  362. $this->invalidateAccessCacheForCollectionByUser($collection, $user);
  363. }
  364. }
  365. protected function invalidateAccessCacheForCollectionByUser(ICollection $collection, ?IUser $user): void {
  366. $query = $this->connection->getQueryBuilder();
  367. $userId = $user instanceof IUser ? $user->getUID() : '';
  368. $query->delete(self::TABLE_ACCESS_CACHE)
  369. ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId())))
  370. ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
  371. $query->execute();
  372. }
  373. public function invalidateAccessCacheForProviderByUser(IProvider $provider, ?IUser $user): void {
  374. $query = $this->connection->getQueryBuilder();
  375. $userId = $user instanceof IUser ? $user->getUID() : '';
  376. $query->delete(self::TABLE_ACCESS_CACHE)
  377. ->where($query->expr()->eq('resource_type', $query->createNamedParameter($provider->getType(), IQueryBuilder::PARAM_STR)))
  378. ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
  379. $query->execute();
  380. }
  381. public function registerResourceProvider(string $provider): void {
  382. $this->logger->debug('\OC\Collaboration\Resources\Manager::registerResourceProvider is deprecated', ['provider' => $provider]);
  383. $this->providerManager->registerResourceProvider($provider);
  384. }
  385. /**
  386. * Get the resource type of the provider
  387. *
  388. * @since 16.0.0
  389. */
  390. public function getType(): string {
  391. return '';
  392. }
  393. }