Passed
Push — master ( 72e6ca...592cc0 )
by John
12:30 queued 11s
created

DBConfigService::modifyMountsOnUserDelete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 1
dl 0
loc 2
rs 10
c 2
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Joas Schilling <[email protected]>
6
 * @author Lukas Reschke <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author Robin McCorkell <[email protected]>
9
 *
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
26
namespace OCA\Files_External\Service;
27
28
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
29
use OCP\DB\QueryBuilder\IQueryBuilder;
30
use OCP\IDBConnection;
31
use OCP\Security\ICrypto;
32
33
/**
34
 * Stores the mount config in the database
35
 */
36
class DBConfigService {
37
	const MOUNT_TYPE_ADMIN = 1;
38
	const MOUNT_TYPE_PERSONAl = 2;
39
40
	const APPLICABLE_TYPE_GLOBAL = 1;
41
	const APPLICABLE_TYPE_GROUP = 2;
42
	const APPLICABLE_TYPE_USER = 3;
43
44
	/**
45
	 * @var IDBConnection
46
	 */
47
	private $connection;
48
49
	/**
50
	 * @var ICrypto
51
	 */
52
	private $crypto;
53
54
	/**
55
	 * DBConfigService constructor.
56
	 *
57
	 * @param IDBConnection $connection
58
	 * @param ICrypto $crypto
59
	 */
60
	public function __construct(IDBConnection $connection, ICrypto $crypto) {
61
		$this->connection = $connection;
62
		$this->crypto = $crypto;
63
	}
64
65
	/**
66
	 * @param int $mountId
67
	 * @return array
68
	 */
69
	public function getMountById($mountId) {
70
		$builder = $this->connection->getQueryBuilder();
71
		$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
72
			->from('external_mounts', 'm')
73
			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
74
		$mounts = $this->getMountsFromQuery($query);
75
		if (count($mounts) > 0) {
76
			return $mounts[0];
77
		} else {
78
			return null;
79
		}
80
	}
81
82
	/**
83
	 * Get all configured mounts
84
	 *
85
	 * @return array
86
	 */
87
	public function getAllMounts() {
88
		$builder = $this->connection->getQueryBuilder();
89
		$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
90
			->from('external_mounts');
91
		return $this->getMountsFromQuery($query);
92
	}
93
94
	public function getMountsForUser($userId, $groupIds) {
95
		$builder = $this->connection->getQueryBuilder();
96
		$query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
97
			->from('external_mounts', 'm')
98
			->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
99
			->where($builder->expr()->orX(
100
				$builder->expr()->andX( // global mounts
101
					$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GLOBAL, IQueryBuilder::PARAM_INT)),
102
					$builder->expr()->isNull('a.value')
103
				),
104
				$builder->expr()->andX( // mounts for user
105
					$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_USER, IQueryBuilder::PARAM_INT)),
106
					$builder->expr()->eq('a.value', $builder->createNamedParameter($userId))
107
				),
108
				$builder->expr()->andX( // mounts for group
109
					$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
110
					$builder->expr()->in('a.value', $builder->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY))
111
				)
112
			));
113
114
		return $this->getMountsFromQuery($query);
115
	}
116
117
	public function modifyMountsOnUserDelete(string $uid): void {
118
		$this->modifyMountsOnDelete($uid, self::APPLICABLE_TYPE_USER);
119
	}
120
121
	public function modifyMountsOnGroupDelete(string $gid): void {
122
		$this->modifyMountsOnDelete($gid, self::APPLICABLE_TYPE_GROUP);
123
	}
124
125
	protected function modifyMountsOnDelete(string $applicableId, int $applicableType): void {
126
		$builder = $this->connection->getQueryBuilder();
127
		$query = $builder->select(['a.mount_id', $builder->func()->count('a.mount_id', 'count')])
128
			->from('external_applicable', 'a')
129
			->rightJoin('a', 'external_applicable', 'b', $builder->expr()->eq('a.mount_id', 'b.mount_id'))
130
			->where($builder->expr()->andX(
131
				$builder->expr()->eq('a.type', $builder->createNamedParameter($applicableType, IQueryBuilder::PARAM_INT)),
132
				$builder->expr()->eq('a.value', $builder->createNamedParameter($applicableId))
133
			)
134
			)
135
			->groupBy(['a.mount_id']);
136
		$stmt = $query->execute();
137
		$result = $stmt->fetchAll();
138
		$stmt->closeCursor();
139
140
		foreach ($result as $row) {
141
			if((int)$row['count'] > 1) {
142
				$this->removeApplicable($row['mount_id'], $applicableType, $applicableId);
143
			} else {
144
				$this->removeMount($row['mount_id']);
145
			}
146
		}
147
	}
148
149
	/**
150
	 * Get admin defined mounts
151
	 *
152
	 * @return array
153
	 * @suppress SqlInjectionChecker
154
	 */
155
	public function getAdminMounts() {
156
		$builder = $this->connection->getQueryBuilder();
157
		$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
158
			->from('external_mounts')
159
			->where($builder->expr()->eq('type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
160
		return $this->getMountsFromQuery($query);
161
	}
162
163
	protected function getForQuery(IQueryBuilder $builder, $type, $value) {
164
		$query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
165
			->from('external_mounts', 'm')
166
			->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
167
			->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
168
169
		if (is_null($value)) {
170
			$query = $query->andWhere($builder->expr()->isNull('a.value'));
171
		} else {
172
			$query = $query->andWhere($builder->expr()->eq('a.value', $builder->createNamedParameter($value)));
173
		}
174
175
		return $query;
176
	}
177
178
	/**
179
	 * Get mounts by applicable
180
	 *
181
	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
182
	 * @param string|null $value user_id, group_id or null for global mounts
183
	 * @return array
184
	 */
185
	public function getMountsFor($type, $value) {
186
		$builder = $this->connection->getQueryBuilder();
187
		$query = $this->getForQuery($builder, $type, $value);
188
189
		return $this->getMountsFromQuery($query);
190
	}
191
192
	/**
193
	 * Get admin defined mounts by applicable
194
	 *
195
	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
196
	 * @param string|null $value user_id, group_id or null for global mounts
197
	 * @return array
198
	 * @suppress SqlInjectionChecker
199
	 */
200
	public function getAdminMountsFor($type, $value) {
201
		$builder = $this->connection->getQueryBuilder();
202
		$query = $this->getForQuery($builder, $type, $value);
203
		$query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
204
205
		return $this->getMountsFromQuery($query);
206
	}
207
208
	/**
209
	 * Get admin defined mounts for multiple applicable
210
	 *
211
	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
212
	 * @param string[] $values user_ids or group_ids
213
	 * @return array
214
	 * @suppress SqlInjectionChecker
215
	 */
216
	public function getAdminMountsForMultiple($type, array $values) {
217
		$builder = $this->connection->getQueryBuilder();
218
		$params = array_map(function ($value) use ($builder) {
219
			return $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR);
220
		}, $values);
221
222
		$query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
223
			->from('external_mounts', 'm')
224
			->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
225
			->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
226
			->andWhere($builder->expr()->in('a.value', $params));
227
		$query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
228
229
		return $this->getMountsFromQuery($query);
230
	}
231
232
	/**
233
	 * Get user defined mounts by applicable
234
	 *
235
	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
236
	 * @param string|null $value user_id, group_id or null for global mounts
237
	 * @return array
238
	 * @suppress SqlInjectionChecker
239
	 */
240
	public function getUserMountsFor($type, $value) {
241
		$builder = $this->connection->getQueryBuilder();
242
		$query = $this->getForQuery($builder, $type, $value);
243
		$query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_PERSONAl, IQueryBuilder::PARAM_INT)));
244
245
		return $this->getMountsFromQuery($query);
246
	}
247
248
	/**
249
	 * Add a mount to the database
250
	 *
251
	 * @param string $mountPoint
252
	 * @param string $storageBackend
253
	 * @param string $authBackend
254
	 * @param int $priority
255
	 * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAL
256
	 * @return int the id of the new mount
257
	 */
258
	public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) {
259
		if (!$priority) {
260
			$priority = 100;
261
		}
262
		$builder = $this->connection->getQueryBuilder();
263
		$query = $builder->insert('external_mounts')
264
			->values([
265
				'mount_point' => $builder->createNamedParameter($mountPoint, IQueryBuilder::PARAM_STR),
266
				'storage_backend' => $builder->createNamedParameter($storageBackend, IQueryBuilder::PARAM_STR),
267
				'auth_backend' => $builder->createNamedParameter($authBackend, IQueryBuilder::PARAM_STR),
268
				'priority' => $builder->createNamedParameter($priority, IQueryBuilder::PARAM_INT),
269
				'type' => $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)
270
			]);
271
		$query->execute();
272
		return (int)$this->connection->lastInsertId('*PREFIX*external_mounts');
273
	}
274
275
	/**
276
	 * Remove a mount from the database
277
	 *
278
	 * @param int $mountId
279
	 */
280
	public function removeMount($mountId) {
281
		$builder = $this->connection->getQueryBuilder();
282
		$query = $builder->delete('external_mounts')
283
			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
284
		$query->execute();
285
286
		$query = $builder->delete('external_applicable')
287
			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
288
		$query->execute();
289
290
		$query = $builder->delete('external_config')
291
			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
292
		$query->execute();
293
294
		$query = $builder->delete('external_options')
295
			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
296
		$query->execute();
297
	}
298
299
	/**
300
	 * @param int $mountId
301
	 * @param string $newMountPoint
302
	 */
303
	public function setMountPoint($mountId, $newMountPoint) {
304
		$builder = $this->connection->getQueryBuilder();
305
306
		$query = $builder->update('external_mounts')
307
			->set('mount_point', $builder->createNamedParameter($newMountPoint))
308
			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
309
310
		$query->execute();
311
	}
312
313
	/**
314
	 * @param int $mountId
315
	 * @param string $newAuthBackend
316
	 */
317
	public function setAuthBackend($mountId, $newAuthBackend) {
318
		$builder = $this->connection->getQueryBuilder();
319
320
		$query = $builder->update('external_mounts')
321
			->set('auth_backend', $builder->createNamedParameter($newAuthBackend))
322
			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
323
324
		$query->execute();
325
	}
326
327
	/**
328
	 * @param int $mountId
329
	 * @param string $key
330
	 * @param string $value
331
	 */
332
	public function setConfig($mountId, $key, $value) {
333
		if ($key === 'password') {
334
			$value = $this->encryptValue($value);
335
		}
336
337
		try {
338
			$builder = $this->connection->getQueryBuilder();
339
			$builder->insert('external_config')
340
				->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
341
				->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
342
				->setValue('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
343
				->execute();
344
		} catch(UniqueConstraintViolationException $e) {
345
			$builder = $this->connection->getQueryBuilder();
346
			$query = $builder->update('external_config')
347
				->set('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
348
				->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
349
				->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
350
			$query->execute();
351
		}
352
	}
353
354
	/**
355
	 * @param int $mountId
356
	 * @param string $key
357
	 * @param string $value
358
	 */
359
	public function setOption($mountId, $key, $value) {
360
		try {
361
			$builder = $this->connection->getQueryBuilder();
362
			$builder->insert('external_options')
363
				->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
364
				->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
365
				->setValue('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
366
				->execute();
367
		} catch(UniqueConstraintViolationException $e) {
368
			$builder = $this->connection->getQueryBuilder();
369
			$query = $builder->update('external_options')
370
				->set('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
371
				->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
372
				->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
373
			$query->execute();
374
		}
375
	}
376
377
	public function addApplicable($mountId, $type, $value) {
378
		try {
379
			$builder = $this->connection->getQueryBuilder();
380
			$builder->insert('external_applicable')
381
				->setValue('mount_id', $builder->createNamedParameter($mountId))
382
				->setValue('type', $builder->createNamedParameter($type))
383
				->setValue('value', $builder->createNamedParameter($value))
384
				->execute();
385
		} catch(UniqueConstraintViolationException $e) {
386
			// applicable exists already
387
		}
388
	}
389
390
	public function removeApplicable($mountId, $type, $value) {
391
		$builder = $this->connection->getQueryBuilder();
392
		$query = $builder->delete('external_applicable')
393
			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
394
			->andWhere($builder->expr()->eq('type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
395
396
		if (is_null($value)) {
397
			$query = $query->andWhere($builder->expr()->isNull('value'));
398
		} else {
399
			$query = $query->andWhere($builder->expr()->eq('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR)));
400
		}
401
402
		$query->execute();
403
	}
404
405
	private function getMountsFromQuery(IQueryBuilder $query) {
406
		$result = $query->execute();
407
		$mounts = $result->fetchAll();
408
		$uniqueMounts = [];
409
		foreach ($mounts as $mount) {
410
			$id = $mount['mount_id'];
411
			if (!isset($uniqueMounts[$id])) {
412
				$uniqueMounts[$id] = $mount;
413
			}
414
		}
415
		$uniqueMounts = array_values($uniqueMounts);
416
417
		$mountIds = array_map(function ($mount) {
418
			return $mount['mount_id'];
419
		}, $uniqueMounts);
420
		$mountIds = array_values(array_unique($mountIds));
421
422
		$applicable = $this->getApplicableForMounts($mountIds);
423
		$config = $this->getConfigForMounts($mountIds);
424
		$options = $this->getOptionsForMounts($mountIds);
425
426
		return array_map(function ($mount, $applicable, $config, $options) {
427
			$mount['type'] = (int)$mount['type'];
428
			$mount['priority'] = (int)$mount['priority'];
429
			$mount['applicable'] = $applicable;
430
			$mount['config'] = $config;
431
			$mount['options'] = $options;
432
			return $mount;
433
		}, $uniqueMounts, $applicable, $config, $options);
434
	}
435
436
	/**
437
	 * Get mount options from a table grouped by mount id
438
	 *
439
	 * @param string $table
440
	 * @param string[] $fields
441
	 * @param int[] $mountIds
442
	 * @return array [$mountId => [['field1' => $value1, ...], ...], ...]
443
	 */
444
	private function selectForMounts($table, array $fields, array $mountIds) {
445
		if (count($mountIds) === 0) {
446
			return [];
447
		}
448
		$builder = $this->connection->getQueryBuilder();
449
		$fields[] = 'mount_id';
450
		$placeHolders = array_map(function ($id) use ($builder) {
451
			return $builder->createPositionalParameter($id, IQueryBuilder::PARAM_INT);
452
		}, $mountIds);
453
		$query = $builder->select($fields)
454
			->from($table)
455
			->where($builder->expr()->in('mount_id', $placeHolders));
456
		$rows = $query->execute()->fetchAll();
457
458
		$result = [];
459
		foreach ($mountIds as $mountId) {
460
			$result[$mountId] = [];
461
		}
462
		foreach ($rows as $row) {
463
			if (isset($row['type'])) {
464
				$row['type'] = (int)$row['type'];
465
			}
466
			$result[$row['mount_id']][] = $row;
467
		}
468
		return $result;
469
	}
470
471
	/**
472
	 * @param int[] $mountIds
473
	 * @return array [$id => [['type' => $type, 'value' => $value], ...], ...]
474
	 */
475
	public function getApplicableForMounts($mountIds) {
476
		return $this->selectForMounts('external_applicable', ['type', 'value'], $mountIds);
477
	}
478
479
	/**
480
	 * @param int[] $mountIds
481
	 * @return array [$id => ['key1' => $value1, ...], ...]
482
	 */
483
	public function getConfigForMounts($mountIds) {
484
		$mountConfigs = $this->selectForMounts('external_config', ['key', 'value'], $mountIds);
485
		return array_map([$this, 'createKeyValueMap'], $mountConfigs);
486
	}
487
488
	/**
489
	 * @param int[] $mountIds
490
	 * @return array [$id => ['key1' => $value1, ...], ...]
491
	 */
492
	public function getOptionsForMounts($mountIds) {
493
		$mountOptions = $this->selectForMounts('external_options', ['key', 'value'], $mountIds);
494
		$optionsMap = array_map([$this, 'createKeyValueMap'], $mountOptions);
495
		return array_map(function (array $options) {
496
			return array_map(function ($option) {
497
				return json_decode($option);
498
			}, $options);
499
		}, $optionsMap);
500
	}
501
502
	/**
503
	 * @param array $keyValuePairs [['key'=>$key, 'value=>$value], ...]
504
	 * @return array ['key1' => $value1, ...]
505
	 */
506
	private function createKeyValueMap(array $keyValuePairs) {
507
		$decryptedPairts = array_map(function ($pair) {
508
			if ($pair['key'] === 'password') {
509
				$pair['value'] = $this->decryptValue($pair['value']);
510
			}
511
			return $pair;
512
		}, $keyValuePairs);
513
		$keys = array_map(function ($pair) {
514
			return $pair['key'];
515
		}, $decryptedPairts);
516
		$values = array_map(function ($pair) {
517
			return $pair['value'];
518
		}, $decryptedPairts);
519
520
		return array_combine($keys, $values);
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_combine($keys, $values) could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
521
	}
522
523
	private function encryptValue($value) {
524
		return $this->crypto->encrypt($value);
525
	}
526
527
	private function decryptValue($value) {
528
		try {
529
			return $this->crypto->decrypt($value);
530
		} catch (\Exception $e) {
531
			return $value;
532
		}
533
	}
534
}
535