You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Manager.php 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com>
  5. *
  6. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  7. * @author Joas Schilling <coding@schilljs.com>
  8. * @author Julius Härtl <jus@bitgrid.net>
  9. *
  10. * @license GNU AGPL version 3 or any later version
  11. *
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU Affero General Public License as
  14. * published by the Free Software Foundation, either version 3 of the
  15. * License, or (at your option) any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU Affero General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU Affero General Public License
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. *
  25. */
  26. namespace OC\Collaboration\Resources;
  27. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  28. use OCP\Collaboration\Resources\CollectionException;
  29. use OCP\Collaboration\Resources\ICollection;
  30. use OCP\Collaboration\Resources\IManager;
  31. use OCP\Collaboration\Resources\IProvider;
  32. use OCP\Collaboration\Resources\IProviderManager;
  33. use OCP\Collaboration\Resources\IResource;
  34. use OCP\Collaboration\Resources\ResourceException;
  35. use OCP\DB\QueryBuilder\IQueryBuilder;
  36. use OCP\IDBConnection;
  37. use OCP\IUser;
  38. use Psr\Log\LoggerInterface;
  39. class Manager implements IManager {
  40. public const TABLE_COLLECTIONS = 'collres_collections';
  41. public const TABLE_RESOURCES = 'collres_resources';
  42. public const TABLE_ACCESS_CACHE = 'collres_accesscache';
  43. /** @var IDBConnection */
  44. protected $connection;
  45. /** @var IProviderManager */
  46. protected $providerManager;
  47. /** @var LoggerInterface */
  48. protected $logger;
  49. /** @var string[] */
  50. protected $providers = [];
  51. public function __construct(IDBConnection $connection, IProviderManager $providerManager, LoggerInterface $logger) {
  52. $this->connection = $connection;
  53. $this->providerManager = $providerManager;
  54. $this->logger = $logger;
  55. }
  56. /**
  57. * @param int $id
  58. * @return ICollection
  59. * @throws CollectionException when the collection could not be found
  60. * @since 16.0.0
  61. */
  62. public function getCollection(int $id): ICollection {
  63. $query = $this->connection->getQueryBuilder();
  64. $query->select('*')
  65. ->from(self::TABLE_COLLECTIONS)
  66. ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
  67. $result = $query->execute();
  68. $row = $result->fetch();
  69. $result->closeCursor();
  70. if (!$row) {
  71. throw new CollectionException('Collection not found');
  72. }
  73. return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name']);
  74. }
  75. /**
  76. * @param int $id
  77. * @param IUser|null $user
  78. * @return ICollection
  79. * @throws CollectionException when the collection could not be found
  80. * @since 16.0.0
  81. */
  82. public function getCollectionForUser(int $id, ?IUser $user): ICollection {
  83. $query = $this->connection->getQueryBuilder();
  84. $userId = $user instanceof IUser ? $user->getUID() : '';
  85. $query->select('*')
  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('c.id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
  95. $result = $query->execute();
  96. $row = $result->fetch();
  97. $result->closeCursor();
  98. if (!$row) {
  99. throw new CollectionException('Collection not found');
  100. }
  101. $access = $row['access'] === null ? null : (bool) $row['access'];
  102. if ($user instanceof IUser) {
  103. return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name'], $user, $access);
  104. }
  105. return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name'], $user, $access);
  106. }
  107. /**
  108. * @param IUser $user
  109. * @param string $filter
  110. * @param int $limit
  111. * @param int $start
  112. * @return ICollection[]
  113. * @since 16.0.0
  114. */
  115. public function searchCollections(IUser $user, string $filter, int $limit = 50, int $start = 0): array {
  116. $query = $this->connection->getQueryBuilder();
  117. $userId = $user->getUID();
  118. $query->select('c.*', 'a.access')
  119. ->from(self::TABLE_COLLECTIONS, 'c')
  120. ->leftJoin(
  121. 'c', self::TABLE_ACCESS_CACHE, 'a',
  122. $query->expr()->andX(
  123. $query->expr()->eq('c.id', 'a.collection_id'),
  124. $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
  125. )
  126. )
  127. ->where($query->expr()->eq('a.access', $query->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
  128. ->orderBy('c.id')
  129. ->setMaxResults($limit)
  130. ->setFirstResult($start);
  131. if ($filter !== '') {
  132. $query->where($query->expr()->iLike('c.name', $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($filter) . '%')));
  133. }
  134. $result = $query->execute();
  135. $collections = [];
  136. $foundResults = 0;
  137. while ($row = $result->fetch()) {
  138. $foundResults++;
  139. $access = $row['access'] === null ? null : (bool) $row['access'];
  140. $collection = new Collection($this, $this->connection, (int)$row['id'], (string)$row['name'], $user, $access);
  141. if ($collection->canAccess($user)) {
  142. $collections[] = $collection;
  143. }
  144. }
  145. $result->closeCursor();
  146. if (empty($collections) && $foundResults === $limit) {
  147. return $this->searchCollections($user, $filter, $limit, $start + $limit);
  148. }
  149. return $collections;
  150. }
  151. /**
  152. * @param string $name
  153. * @return ICollection
  154. * @since 16.0.0
  155. */
  156. public function newCollection(string $name): ICollection {
  157. $query = $this->connection->getQueryBuilder();
  158. $query->insert(self::TABLE_COLLECTIONS)
  159. ->values([
  160. 'name' => $query->createNamedParameter($name),
  161. ]);
  162. $query->execute();
  163. return new Collection($this, $this->connection, $query->getLastInsertId(), $name);
  164. }
  165. /**
  166. * @param string $type
  167. * @param string $id
  168. * @return IResource
  169. * @since 16.0.0
  170. */
  171. public function createResource(string $type, string $id): IResource {
  172. return new Resource($this, $this->connection, $type, $id);
  173. }
  174. /**
  175. * @param string $type
  176. * @param string $id
  177. * @param IUser|null $user
  178. * @return IResource
  179. * @throws ResourceException
  180. * @since 16.0.0
  181. */
  182. public function getResourceForUser(string $type, string $id, ?IUser $user): IResource {
  183. $query = $this->connection->getQueryBuilder();
  184. $userId = $user instanceof IUser ? $user->getUID() : '';
  185. $query->select('r.*', 'a.access')
  186. ->from(self::TABLE_RESOURCES, 'r')
  187. ->leftJoin(
  188. 'r', self::TABLE_ACCESS_CACHE, 'a',
  189. $query->expr()->andX(
  190. $query->expr()->eq('r.resource_id', 'a.resource_id'),
  191. $query->expr()->eq('r.resource_type', 'a.resource_type'),
  192. $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
  193. )
  194. )
  195. ->where($query->expr()->eq('r.resource_type', $query->createNamedParameter($type, IQueryBuilder::PARAM_STR)))
  196. ->andWhere($query->expr()->eq('r.resource_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_STR)));
  197. $result = $query->execute();
  198. $row = $result->fetch();
  199. $result->closeCursor();
  200. if (!$row) {
  201. throw new ResourceException('Resource not found');
  202. }
  203. $access = $row['access'] === null ? null : (bool) $row['access'];
  204. if ($user instanceof IUser) {
  205. return new Resource($this, $this->connection, $type, $id, $user, $access);
  206. }
  207. return new Resource($this, $this->connection, $type, $id, null, $access);
  208. }
  209. /**
  210. * @param ICollection $collection
  211. * @param IUser|null $user
  212. * @return IResource[]
  213. * @since 16.0.0
  214. */
  215. public function getResourcesByCollectionForUser(ICollection $collection, ?IUser $user): array {
  216. $query = $this->connection->getQueryBuilder();
  217. $userId = $user instanceof IUser ? $user->getUID() : '';
  218. $query->select('r.*', 'a.access')
  219. ->from(self::TABLE_RESOURCES, 'r')
  220. ->leftJoin(
  221. 'r', self::TABLE_ACCESS_CACHE, 'a',
  222. $query->expr()->andX(
  223. $query->expr()->eq('r.resource_id', 'a.resource_id'),
  224. $query->expr()->eq('r.resource_type', 'a.resource_type'),
  225. $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
  226. )
  227. )
  228. ->where($query->expr()->eq('r.collection_id', $query->createNamedParameter($collection->getId(), IQueryBuilder::PARAM_INT)));
  229. $resources = [];
  230. $result = $query->execute();
  231. while ($row = $result->fetch()) {
  232. $access = $row['access'] === null ? null : (bool) $row['access'];
  233. $resources[] = new Resource($this, $this->connection, $row['resource_type'], $row['resource_id'], $user, $access);
  234. }
  235. $result->closeCursor();
  236. return $resources;
  237. }
  238. /**
  239. * Get the rich object data of a resource
  240. *
  241. * @param IResource $resource
  242. * @return array
  243. * @since 16.0.0
  244. */
  245. public function getResourceRichObject(IResource $resource): array {
  246. foreach ($this->providerManager->getResourceProviders() as $provider) {
  247. if ($provider->getType() === $resource->getType()) {
  248. try {
  249. return $provider->getResourceRichObject($resource);
  250. } catch (ResourceException $e) {
  251. }
  252. }
  253. }
  254. return [];
  255. }
  256. /**
  257. * Can a user/guest access the collection
  258. *
  259. * @param IResource $resource
  260. * @param IUser|null $user
  261. * @return bool
  262. * @since 16.0.0
  263. */
  264. public function canAccessResource(IResource $resource, ?IUser $user): bool {
  265. $access = $this->checkAccessCacheForUserByResource($resource, $user);
  266. if (\is_bool($access)) {
  267. return $access;
  268. }
  269. $access = false;
  270. foreach ($this->providerManager->getResourceProviders() as $provider) {
  271. if ($provider->getType() === $resource->getType()) {
  272. try {
  273. if ($provider->canAccessResource($resource, $user)) {
  274. $access = true;
  275. break;
  276. }
  277. } catch (ResourceException $e) {
  278. }
  279. }
  280. }
  281. $this->cacheAccessForResource($resource, $user, $access);
  282. return $access;
  283. }
  284. /**
  285. * Can a user/guest access the collection
  286. *
  287. * @param ICollection $collection
  288. * @param IUser|null $user
  289. * @return bool
  290. * @since 16.0.0
  291. */
  292. public function canAccessCollection(ICollection $collection, ?IUser $user): bool {
  293. $access = $this->checkAccessCacheForUserByCollection($collection, $user);
  294. if (\is_bool($access)) {
  295. return $access;
  296. }
  297. $access = null;
  298. // Access is granted when a user can access all resources
  299. foreach ($collection->getResources() as $resource) {
  300. if (!$resource->canAccess($user)) {
  301. $access = false;
  302. break;
  303. }
  304. $access = true;
  305. }
  306. $this->cacheAccessForCollection($collection, $user, $access);
  307. return $access;
  308. }
  309. protected function checkAccessCacheForUserByResource(IResource $resource, ?IUser $user): ?bool {
  310. $query = $this->connection->getQueryBuilder();
  311. $userId = $user instanceof IUser ? $user->getUID() : '';
  312. $query->select('access')
  313. ->from(self::TABLE_ACCESS_CACHE)
  314. ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId(), IQueryBuilder::PARAM_STR)))
  315. ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType(), IQueryBuilder::PARAM_STR)))
  316. ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
  317. ->setMaxResults(1);
  318. $hasAccess = null;
  319. $result = $query->execute();
  320. if ($row = $result->fetch()) {
  321. $hasAccess = (bool) $row['access'];
  322. }
  323. $result->closeCursor();
  324. return $hasAccess;
  325. }
  326. protected function checkAccessCacheForUserByCollection(ICollection $collection, ?IUser $user): ?bool {
  327. $query = $this->connection->getQueryBuilder();
  328. $userId = $user instanceof IUser ? $user->getUID() : '';
  329. $query->select('access')
  330. ->from(self::TABLE_ACCESS_CACHE)
  331. ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId(), IQueryBuilder::PARAM_INT)))
  332. ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
  333. ->setMaxResults(1);
  334. $hasAccess = null;
  335. $result = $query->execute();
  336. if ($row = $result->fetch()) {
  337. $hasAccess = (bool) $row['access'];
  338. }
  339. $result->closeCursor();
  340. return $hasAccess;
  341. }
  342. public function cacheAccessForResource(IResource $resource, ?IUser $user, bool $access): void {
  343. $query = $this->connection->getQueryBuilder();
  344. $userId = $user instanceof IUser ? $user->getUID() : '';
  345. $query->insert(self::TABLE_ACCESS_CACHE)
  346. ->values([
  347. 'user_id' => $query->createNamedParameter($userId),
  348. 'resource_id' => $query->createNamedParameter($resource->getId()),
  349. 'resource_type' => $query->createNamedParameter($resource->getType()),
  350. 'access' => $query->createNamedParameter($access, IQueryBuilder::PARAM_BOOL),
  351. ]);
  352. try {
  353. $query->execute();
  354. } catch (UniqueConstraintViolationException $e) {
  355. }
  356. }
  357. public function cacheAccessForCollection(ICollection $collection, ?IUser $user, bool $access): void {
  358. $query = $this->connection->getQueryBuilder();
  359. $userId = $user instanceof IUser ? $user->getUID() : '';
  360. $query->insert(self::TABLE_ACCESS_CACHE)
  361. ->values([
  362. 'user_id' => $query->createNamedParameter($userId),
  363. 'collection_id' => $query->createNamedParameter($collection->getId()),
  364. 'access' => $query->createNamedParameter($access, IQueryBuilder::PARAM_BOOL),
  365. ]);
  366. try {
  367. $query->execute();
  368. } catch (UniqueConstraintViolationException $e) {
  369. }
  370. }
  371. public function invalidateAccessCacheForUser(?IUser $user): void {
  372. $query = $this->connection->getQueryBuilder();
  373. $userId = $user instanceof IUser ? $user->getUID() : '';
  374. $query->delete(self::TABLE_ACCESS_CACHE)
  375. ->where($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
  376. $query->execute();
  377. }
  378. public function invalidateAccessCacheForResource(IResource $resource): void {
  379. $query = $this->connection->getQueryBuilder();
  380. $query->delete(self::TABLE_ACCESS_CACHE)
  381. ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())))
  382. ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType(), IQueryBuilder::PARAM_STR)));
  383. $query->execute();
  384. foreach ($resource->getCollections() as $collection) {
  385. $this->invalidateAccessCacheForCollection($collection);
  386. }
  387. }
  388. public function invalidateAccessCacheForAllCollections(): void {
  389. $query = $this->connection->getQueryBuilder();
  390. $query->delete(self::TABLE_ACCESS_CACHE)
  391. ->where($query->expr()->neq('collection_id', $query->createNamedParameter(0)));
  392. $query->execute();
  393. }
  394. public function invalidateAccessCacheForCollection(ICollection $collection): void {
  395. $query = $this->connection->getQueryBuilder();
  396. $query->delete(self::TABLE_ACCESS_CACHE)
  397. ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId())));
  398. $query->execute();
  399. }
  400. public function invalidateAccessCacheForProvider(IProvider $provider): void {
  401. $query = $this->connection->getQueryBuilder();
  402. $query->delete(self::TABLE_ACCESS_CACHE)
  403. ->where($query->expr()->eq('resource_type', $query->createNamedParameter($provider->getType(), IQueryBuilder::PARAM_STR)));
  404. $query->execute();
  405. }
  406. public function invalidateAccessCacheForResourceByUser(IResource $resource, ?IUser $user): void {
  407. $query = $this->connection->getQueryBuilder();
  408. $userId = $user instanceof IUser ? $user->getUID() : '';
  409. $query->delete(self::TABLE_ACCESS_CACHE)
  410. ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())))
  411. ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
  412. $query->execute();
  413. foreach ($resource->getCollections() as $collection) {
  414. $this->invalidateAccessCacheForCollectionByUser($collection, $user);
  415. }
  416. }
  417. protected function invalidateAccessCacheForCollectionByUser(ICollection $collection, ?IUser $user): void {
  418. $query = $this->connection->getQueryBuilder();
  419. $userId = $user instanceof IUser ? $user->getUID() : '';
  420. $query->delete(self::TABLE_ACCESS_CACHE)
  421. ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId())))
  422. ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
  423. $query->execute();
  424. }
  425. public function invalidateAccessCacheForProviderByUser(IProvider $provider, ?IUser $user): void {
  426. $query = $this->connection->getQueryBuilder();
  427. $userId = $user instanceof IUser ? $user->getUID() : '';
  428. $query->delete(self::TABLE_ACCESS_CACHE)
  429. ->where($query->expr()->eq('resource_type', $query->createNamedParameter($provider->getType(), IQueryBuilder::PARAM_STR)))
  430. ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
  431. $query->execute();
  432. }
  433. /**
  434. * @param string $provider
  435. */
  436. public function registerResourceProvider(string $provider): void {
  437. $this->logger->debug('\OC\Collaboration\Resources\Manager::registerResourceProvider is deprecated', ['provider' => $provider]);
  438. $this->providerManager->registerResourceProvider($provider);
  439. }
  440. /**
  441. * Get the resource type of the provider
  442. *
  443. * @return string
  444. * @since 16.0.0
  445. */
  446. public function getType(): string {
  447. return '';
  448. }
  449. }