DBConfigService.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\Files_External\Service;
  8. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  9. use OCP\DB\QueryBuilder\IQueryBuilder;
  10. use OCP\IDBConnection;
  11. use OCP\Security\ICrypto;
  12. /**
  13. * Stores the mount config in the database
  14. */
  15. class DBConfigService {
  16. public const MOUNT_TYPE_ADMIN = 1;
  17. public const MOUNT_TYPE_PERSONAL = 2;
  18. /** @deprecated use MOUNT_TYPE_PERSONAL (full uppercase) instead */
  19. public const MOUNT_TYPE_PERSONAl = 2;
  20. public const APPLICABLE_TYPE_GLOBAL = 1;
  21. public const APPLICABLE_TYPE_GROUP = 2;
  22. public const APPLICABLE_TYPE_USER = 3;
  23. public function __construct(
  24. private IDBConnection $connection,
  25. private ICrypto $crypto,
  26. ) {
  27. }
  28. public function getMountById(int $mountId): ?array {
  29. $builder = $this->connection->getQueryBuilder();
  30. $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
  31. ->from('external_mounts', 'm')
  32. ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
  33. $mounts = $this->getMountsFromQuery($query);
  34. if (count($mounts) > 0) {
  35. return $mounts[0];
  36. } else {
  37. return null;
  38. }
  39. }
  40. /**
  41. * Get all configured mounts
  42. *
  43. * @return array
  44. */
  45. public function getAllMounts() {
  46. $builder = $this->connection->getQueryBuilder();
  47. $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
  48. ->from('external_mounts');
  49. return $this->getMountsFromQuery($query);
  50. }
  51. public function getMountsForUser($userId, $groupIds) {
  52. $builder = $this->connection->getQueryBuilder();
  53. $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
  54. ->from('external_mounts', 'm')
  55. ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
  56. ->where($builder->expr()->orX(
  57. $builder->expr()->andX( // global mounts
  58. $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GLOBAL, IQueryBuilder::PARAM_INT)),
  59. $builder->expr()->isNull('a.value')
  60. ),
  61. $builder->expr()->andX( // mounts for user
  62. $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_USER, IQueryBuilder::PARAM_INT)),
  63. $builder->expr()->eq('a.value', $builder->createNamedParameter($userId))
  64. ),
  65. $builder->expr()->andX( // mounts for group
  66. $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
  67. $builder->expr()->in('a.value', $builder->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY))
  68. )
  69. ));
  70. return $this->getMountsFromQuery($query);
  71. }
  72. public function modifyMountsOnUserDelete(string $uid): void {
  73. $this->modifyMountsOnDelete($uid, self::APPLICABLE_TYPE_USER);
  74. }
  75. public function modifyMountsOnGroupDelete(string $gid): void {
  76. $this->modifyMountsOnDelete($gid, self::APPLICABLE_TYPE_GROUP);
  77. }
  78. protected function modifyMountsOnDelete(string $applicableId, int $applicableType): void {
  79. $builder = $this->connection->getQueryBuilder();
  80. $query = $builder->select(['a.mount_id', $builder->func()->count('a.mount_id', 'count')])
  81. ->from('external_applicable', 'a')
  82. ->leftJoin('a', 'external_applicable', 'b', $builder->expr()->eq('a.mount_id', 'b.mount_id'))
  83. ->where($builder->expr()->andX(
  84. $builder->expr()->eq('b.type', $builder->createNamedParameter($applicableType, IQueryBuilder::PARAM_INT)),
  85. $builder->expr()->eq('b.value', $builder->createNamedParameter($applicableId))
  86. )
  87. )
  88. ->groupBy(['a.mount_id']);
  89. $stmt = $query->executeQuery();
  90. $result = $stmt->fetchAll();
  91. $stmt->closeCursor();
  92. foreach ($result as $row) {
  93. if ((int)$row['count'] > 1) {
  94. $this->removeApplicable($row['mount_id'], $applicableType, $applicableId);
  95. } else {
  96. $this->removeMount($row['mount_id']);
  97. }
  98. }
  99. }
  100. /**
  101. * Get admin defined mounts
  102. *
  103. * @return array
  104. */
  105. public function getAdminMounts() {
  106. $builder = $this->connection->getQueryBuilder();
  107. $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
  108. ->from('external_mounts')
  109. ->where($builder->expr()->eq('type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
  110. return $this->getMountsFromQuery($query);
  111. }
  112. protected function getForQuery(IQueryBuilder $builder, $type, $value) {
  113. $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
  114. ->from('external_mounts', 'm')
  115. ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
  116. ->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
  117. if (is_null($value)) {
  118. $query = $query->andWhere($builder->expr()->isNull('a.value'));
  119. } else {
  120. $query = $query->andWhere($builder->expr()->eq('a.value', $builder->createNamedParameter($value)));
  121. }
  122. return $query;
  123. }
  124. /**
  125. * Get mounts by applicable
  126. *
  127. * @param int $type any of the self::APPLICABLE_TYPE_ constants
  128. * @param string|null $value user_id, group_id or null for global mounts
  129. * @return array
  130. */
  131. public function getMountsFor($type, $value) {
  132. $builder = $this->connection->getQueryBuilder();
  133. $query = $this->getForQuery($builder, $type, $value);
  134. return $this->getMountsFromQuery($query);
  135. }
  136. /**
  137. * Get admin defined mounts by applicable
  138. *
  139. * @param int $type any of the self::APPLICABLE_TYPE_ constants
  140. * @param string|null $value user_id, group_id or null for global mounts
  141. * @return array
  142. */
  143. public function getAdminMountsFor($type, $value) {
  144. $builder = $this->connection->getQueryBuilder();
  145. $query = $this->getForQuery($builder, $type, $value);
  146. $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
  147. return $this->getMountsFromQuery($query);
  148. }
  149. /**
  150. * Get admin defined mounts for multiple applicable
  151. *
  152. * @param int $type any of the self::APPLICABLE_TYPE_ constants
  153. * @param string[] $values user_ids or group_ids
  154. * @return array
  155. */
  156. public function getAdminMountsForMultiple($type, array $values) {
  157. $builder = $this->connection->getQueryBuilder();
  158. $params = array_map(function ($value) use ($builder) {
  159. return $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR);
  160. }, $values);
  161. $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
  162. ->from('external_mounts', 'm')
  163. ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
  164. ->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
  165. ->andWhere($builder->expr()->in('a.value', $params));
  166. $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
  167. return $this->getMountsFromQuery($query);
  168. }
  169. /**
  170. * Get user defined mounts by applicable
  171. *
  172. * @param int $type any of the self::APPLICABLE_TYPE_ constants
  173. * @param string|null $value user_id, group_id or null for global mounts
  174. * @return array
  175. */
  176. public function getUserMountsFor($type, $value) {
  177. $builder = $this->connection->getQueryBuilder();
  178. $query = $this->getForQuery($builder, $type, $value);
  179. $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_PERSONAL, IQueryBuilder::PARAM_INT)));
  180. return $this->getMountsFromQuery($query);
  181. }
  182. /**
  183. * Add a mount to the database
  184. *
  185. * @param string $mountPoint
  186. * @param string $storageBackend
  187. * @param string $authBackend
  188. * @param int $priority
  189. * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAL
  190. * @return int the id of the new mount
  191. */
  192. public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) {
  193. if (!$priority) {
  194. $priority = 100;
  195. }
  196. $builder = $this->connection->getQueryBuilder();
  197. $query = $builder->insert('external_mounts')
  198. ->values([
  199. 'mount_point' => $builder->createNamedParameter($mountPoint, IQueryBuilder::PARAM_STR),
  200. 'storage_backend' => $builder->createNamedParameter($storageBackend, IQueryBuilder::PARAM_STR),
  201. 'auth_backend' => $builder->createNamedParameter($authBackend, IQueryBuilder::PARAM_STR),
  202. 'priority' => $builder->createNamedParameter($priority, IQueryBuilder::PARAM_INT),
  203. 'type' => $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)
  204. ]);
  205. $query->executeStatement();
  206. return $query->getLastInsertId();
  207. }
  208. /**
  209. * Remove a mount from the database
  210. *
  211. * @param int $mountId
  212. */
  213. public function removeMount($mountId) {
  214. $builder = $this->connection->getQueryBuilder();
  215. $query = $builder->delete('external_mounts')
  216. ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
  217. $query->executeStatement();
  218. $builder = $this->connection->getQueryBuilder();
  219. $query = $builder->delete('external_applicable')
  220. ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
  221. $query->executeStatement();
  222. $builder = $this->connection->getQueryBuilder();
  223. $query = $builder->delete('external_config')
  224. ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
  225. $query->executeStatement();
  226. $builder = $this->connection->getQueryBuilder();
  227. $query = $builder->delete('external_options')
  228. ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
  229. $query->executeStatement();
  230. }
  231. /**
  232. * @param int $mountId
  233. * @param string $newMountPoint
  234. */
  235. public function setMountPoint($mountId, $newMountPoint) {
  236. $builder = $this->connection->getQueryBuilder();
  237. $query = $builder->update('external_mounts')
  238. ->set('mount_point', $builder->createNamedParameter($newMountPoint))
  239. ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
  240. $query->executeStatement();
  241. }
  242. /**
  243. * @param int $mountId
  244. * @param string $newAuthBackend
  245. */
  246. public function setAuthBackend($mountId, $newAuthBackend) {
  247. $builder = $this->connection->getQueryBuilder();
  248. $query = $builder->update('external_mounts')
  249. ->set('auth_backend', $builder->createNamedParameter($newAuthBackend))
  250. ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
  251. $query->executeStatement();
  252. }
  253. /**
  254. * @param int $mountId
  255. * @param string $key
  256. * @param string $value
  257. */
  258. public function setConfig($mountId, $key, $value) {
  259. if ($key === 'password') {
  260. $value = $this->encryptValue($value);
  261. }
  262. try {
  263. $builder = $this->connection->getQueryBuilder();
  264. $builder->insert('external_config')
  265. ->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
  266. ->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
  267. ->setValue('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
  268. ->execute();
  269. } catch (UniqueConstraintViolationException $e) {
  270. $builder = $this->connection->getQueryBuilder();
  271. $query = $builder->update('external_config')
  272. ->set('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
  273. ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
  274. ->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
  275. $query->executeStatement();
  276. }
  277. }
  278. /**
  279. * @param int $mountId
  280. * @param string $key
  281. * @param string $value
  282. */
  283. public function setOption($mountId, $key, $value) {
  284. try {
  285. $builder = $this->connection->getQueryBuilder();
  286. $builder->insert('external_options')
  287. ->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
  288. ->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
  289. ->setValue('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
  290. ->execute();
  291. } catch (UniqueConstraintViolationException $e) {
  292. $builder = $this->connection->getQueryBuilder();
  293. $query = $builder->update('external_options')
  294. ->set('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
  295. ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
  296. ->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
  297. $query->executeStatement();
  298. }
  299. }
  300. public function addApplicable($mountId, $type, $value) {
  301. try {
  302. $builder = $this->connection->getQueryBuilder();
  303. $builder->insert('external_applicable')
  304. ->setValue('mount_id', $builder->createNamedParameter($mountId))
  305. ->setValue('type', $builder->createNamedParameter($type))
  306. ->setValue('value', $builder->createNamedParameter($value))
  307. ->execute();
  308. } catch (UniqueConstraintViolationException $e) {
  309. // applicable exists already
  310. }
  311. }
  312. public function removeApplicable($mountId, $type, $value) {
  313. $builder = $this->connection->getQueryBuilder();
  314. $query = $builder->delete('external_applicable')
  315. ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
  316. ->andWhere($builder->expr()->eq('type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
  317. if (is_null($value)) {
  318. $query = $query->andWhere($builder->expr()->isNull('value'));
  319. } else {
  320. $query = $query->andWhere($builder->expr()->eq('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR)));
  321. }
  322. $query->executeStatement();
  323. }
  324. private function getMountsFromQuery(IQueryBuilder $query) {
  325. $result = $query->executeQuery();
  326. $mounts = $result->fetchAll();
  327. $uniqueMounts = [];
  328. foreach ($mounts as $mount) {
  329. $id = $mount['mount_id'];
  330. if (!isset($uniqueMounts[$id])) {
  331. $uniqueMounts[$id] = $mount;
  332. }
  333. }
  334. $uniqueMounts = array_values($uniqueMounts);
  335. $mountIds = array_map(function ($mount) {
  336. return $mount['mount_id'];
  337. }, $uniqueMounts);
  338. $mountIds = array_values(array_unique($mountIds));
  339. $applicable = $this->getApplicableForMounts($mountIds);
  340. $config = $this->getConfigForMounts($mountIds);
  341. $options = $this->getOptionsForMounts($mountIds);
  342. return array_map(function ($mount, $applicable, $config, $options) {
  343. $mount['type'] = (int)$mount['type'];
  344. $mount['priority'] = (int)$mount['priority'];
  345. $mount['applicable'] = $applicable;
  346. $mount['config'] = $config;
  347. $mount['options'] = $options;
  348. return $mount;
  349. }, $uniqueMounts, $applicable, $config, $options);
  350. }
  351. /**
  352. * Get mount options from a table grouped by mount id
  353. *
  354. * @param string $table
  355. * @param string[] $fields
  356. * @param int[] $mountIds
  357. * @return array [$mountId => [['field1' => $value1, ...], ...], ...]
  358. */
  359. private function selectForMounts($table, array $fields, array $mountIds) {
  360. if (count($mountIds) === 0) {
  361. return [];
  362. }
  363. $builder = $this->connection->getQueryBuilder();
  364. $fields[] = 'mount_id';
  365. $placeHolders = array_map(function ($id) use ($builder) {
  366. return $builder->createPositionalParameter($id, IQueryBuilder::PARAM_INT);
  367. }, $mountIds);
  368. $query = $builder->select($fields)
  369. ->from($table)
  370. ->where($builder->expr()->in('mount_id', $placeHolders));
  371. $result = $query->executeQuery();
  372. $rows = $result->fetchAll();
  373. $result->closeCursor();
  374. $result = [];
  375. foreach ($mountIds as $mountId) {
  376. $result[$mountId] = [];
  377. }
  378. foreach ($rows as $row) {
  379. if (isset($row['type'])) {
  380. $row['type'] = (int)$row['type'];
  381. }
  382. $result[$row['mount_id']][] = $row;
  383. }
  384. return $result;
  385. }
  386. /**
  387. * @param int[] $mountIds
  388. * @return array [$id => [['type' => $type, 'value' => $value], ...], ...]
  389. */
  390. public function getApplicableForMounts($mountIds) {
  391. return $this->selectForMounts('external_applicable', ['type', 'value'], $mountIds);
  392. }
  393. /**
  394. * @param int[] $mountIds
  395. * @return array [$id => ['key1' => $value1, ...], ...]
  396. */
  397. public function getConfigForMounts($mountIds) {
  398. $mountConfigs = $this->selectForMounts('external_config', ['key', 'value'], $mountIds);
  399. return array_map([$this, 'createKeyValueMap'], $mountConfigs);
  400. }
  401. /**
  402. * @param int[] $mountIds
  403. * @return array [$id => ['key1' => $value1, ...], ...]
  404. */
  405. public function getOptionsForMounts($mountIds) {
  406. $mountOptions = $this->selectForMounts('external_options', ['key', 'value'], $mountIds);
  407. $optionsMap = array_map([$this, 'createKeyValueMap'], $mountOptions);
  408. return array_map(function (array $options) {
  409. return array_map(function ($option) {
  410. return json_decode($option);
  411. }, $options);
  412. }, $optionsMap);
  413. }
  414. /**
  415. * @param array $keyValuePairs [['key'=>$key, 'value=>$value], ...]
  416. * @return array ['key1' => $value1, ...]
  417. */
  418. private function createKeyValueMap(array $keyValuePairs) {
  419. $decryptedPairts = array_map(function ($pair) {
  420. if ($pair['key'] === 'password') {
  421. $pair['value'] = $this->decryptValue($pair['value']);
  422. }
  423. return $pair;
  424. }, $keyValuePairs);
  425. $keys = array_map(function ($pair) {
  426. return $pair['key'];
  427. }, $decryptedPairts);
  428. $values = array_map(function ($pair) {
  429. return $pair['value'];
  430. }, $decryptedPairts);
  431. return array_combine($keys, $values);
  432. }
  433. private function encryptValue($value) {
  434. return $this->crypto->encrypt($value);
  435. }
  436. private function decryptValue($value) {
  437. try {
  438. return $this->crypto->decrypt($value);
  439. } catch (\Exception $e) {
  440. return $value;
  441. }
  442. }
  443. }