Passed
Push — master ( fd284f...fa2878 )
by Morris
13:09 queued 12s
created

StoragesService::getRustyStorageIdFromConfig()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 18
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Jesús Macias <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Robin McCorkell <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 * @author Stefan Weil <[email protected]>
15
 * @author Vincent Petry <[email protected]>
16
 *
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OCA\Files_External\Service;
34
35
use OC\Files\Filesystem;
36
use OCA\Files_External\Lib\Auth\AuthMechanism;
37
use OCA\Files_External\Lib\Auth\InvalidAuth;
38
use OCA\Files_External\Lib\Backend\Backend;
39
use OCA\Files_External\Lib\Backend\InvalidBackend;
40
use OCA\Files_External\Lib\DefinitionParameter;
41
use OCA\Files_External\Lib\StorageConfig;
42
use OCA\Files_External\NotFoundException;
43
use OCP\Files\Config\IUserMountCache;
44
use OCP\Files\StorageNotAvailableException;
45
use OCP\ILogger;
46
47
/**
48
 * Service class to manage external storages
49
 */
50
abstract class StoragesService {
51
52
	/** @var BackendService */
53
	protected $backendService;
54
55
	/**
56
	 * @var DBConfigService
57
	 */
58
	protected $dbConfig;
59
60
	/**
61
	 * @var IUserMountCache
62
	 */
63
	protected $userMountCache;
64
65
	/**
66
	 * @param BackendService $backendService
67
	 * @param DBConfigService $dbConfigService
68
	 * @param IUserMountCache $userMountCache
69
	 */
70
	public function __construct(BackendService $backendService, DBConfigService $dbConfigService, IUserMountCache $userMountCache) {
71
		$this->backendService = $backendService;
72
		$this->dbConfig = $dbConfigService;
73
		$this->userMountCache = $userMountCache;
74
	}
75
76
	protected function readDBConfig() {
77
		return $this->dbConfig->getAdminMounts();
78
	}
79
80
	protected function getStorageConfigFromDBMount(array $mount) {
81
		$applicableUsers = array_filter($mount['applicable'], function ($applicable) {
82
			return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_USER;
83
		});
84
		$applicableUsers = array_map(function ($applicable) {
85
			return $applicable['value'];
86
		}, $applicableUsers);
87
88
		$applicableGroups = array_filter($mount['applicable'], function ($applicable) {
89
			return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_GROUP;
90
		});
91
		$applicableGroups = array_map(function ($applicable) {
92
			return $applicable['value'];
93
		}, $applicableGroups);
94
95
		try {
96
			$config = $this->createStorage(
97
				$mount['mount_point'],
98
				$mount['storage_backend'],
99
				$mount['auth_backend'],
100
				$mount['config'],
101
				$mount['options'],
102
				array_values($applicableUsers),
103
				array_values($applicableGroups),
104
				$mount['priority']
105
			);
106
			$config->setType($mount['type']);
107
			$config->setId((int)$mount['mount_id']);
108
			return $config;
109
		} catch (\UnexpectedValueException $e) {
110
			// don't die if a storage backend doesn't exist
111
			\OC::$server->getLogger()->logException($e, [
112
				'message' => 'Could not load storage.',
113
				'level' => ILogger::ERROR,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

113
				'level' => /** @scrutinizer ignore-deprecated */ ILogger::ERROR,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
114
				'app' => 'files_external',
115
			]);
116
			return null;
117
		} catch (\InvalidArgumentException $e) {
118
			\OC::$server->getLogger()->logException($e, [
119
				'message' => 'Could not load storage.',
120
				'level' => ILogger::ERROR,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

120
				'level' => /** @scrutinizer ignore-deprecated */ ILogger::ERROR,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
121
				'app' => 'files_external',
122
			]);
123
			return null;
124
		}
125
	}
126
127
	/**
128
	 * Read the external storages config
129
	 *
130
	 * @return array map of storage id to storage config
131
	 */
132
	protected function readConfig() {
133
		$mounts = $this->readDBConfig();
134
		$configs = array_map([$this, 'getStorageConfigFromDBMount'], $mounts);
135
		$configs = array_filter($configs, function ($config) {
136
			return $config instanceof StorageConfig;
137
		});
138
139
		$keys = array_map(function (StorageConfig $config) {
140
			return $config->getId();
141
		}, $configs);
142
143
		return array_combine($keys, $configs);
144
	}
145
146
	/**
147
	 * Get a storage with status
148
	 *
149
	 * @param int $id storage id
150
	 *
151
	 * @return StorageConfig
152
	 * @throws NotFoundException if the storage with the given id was not found
153
	 */
154
	public function getStorage($id) {
155
		$mount = $this->dbConfig->getMountById($id);
156
157
		if (!is_array($mount)) {
0 ignored issues
show
introduced by
The condition is_array($mount) is always true.
Loading history...
158
			throw new NotFoundException('Storage with ID "' . $id . '" not found');
159
		}
160
161
		$config = $this->getStorageConfigFromDBMount($mount);
162
		if ($this->isApplicable($config)) {
163
			return $config;
164
		} else {
165
			throw new NotFoundException('Storage with ID "' . $id . '" not found');
166
		}
167
	}
168
169
	/**
170
	 * Check whether this storage service should provide access to a storage
171
	 *
172
	 * @param StorageConfig $config
173
	 * @return bool
174
	 */
175
	abstract protected function isApplicable(StorageConfig $config);
176
177
	/**
178
	 * Gets all storages, valid or not
179
	 *
180
	 * @return StorageConfig[] array of storage configs
181
	 */
182
	public function getAllStorages() {
183
		return $this->readConfig();
184
	}
185
186
	/**
187
	 * Gets all valid storages
188
	 *
189
	 * @return StorageConfig[]
190
	 */
191
	public function getStorages() {
192
		return array_filter($this->getAllStorages(), [$this, 'validateStorage']);
193
	}
194
195
	/**
196
	 * Validate storage
197
	 * FIXME: De-duplicate with StoragesController::validate()
198
	 *
199
	 * @param StorageConfig $storage
200
	 * @return bool
201
	 */
202
	protected function validateStorage(StorageConfig $storage) {
203
		/** @var Backend */
204
		$backend = $storage->getBackend();
205
		/** @var AuthMechanism */
206
		$authMechanism = $storage->getAuthMechanism();
207
208
		if (!$backend->isVisibleFor($this->getVisibilityType())) {
0 ignored issues
show
Bug introduced by
$this->getVisibilityType() of type string is incompatible with the type integer expected by parameter $visibility of OCA\Files_External\Lib\B...Backend::isVisibleFor(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

208
		if (!$backend->isVisibleFor(/** @scrutinizer ignore-type */ $this->getVisibilityType())) {
Loading history...
209
			// not permitted to use backend
210
			return false;
211
		}
212
		if (!$authMechanism->isVisibleFor($this->getVisibilityType())) {
0 ignored issues
show
Bug introduced by
$this->getVisibilityType() of type string is incompatible with the type integer expected by parameter $visibility of OCA\Files_External\Lib\A...chanism::isVisibleFor(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

212
		if (!$authMechanism->isVisibleFor(/** @scrutinizer ignore-type */ $this->getVisibilityType())) {
Loading history...
213
			// not permitted to use auth mechanism
214
			return false;
215
		}
216
217
		return true;
218
	}
219
220
	/**
221
	 * Get the visibility type for this controller, used in validation
222
	 *
223
	 * @return string BackendService::VISIBILITY_* constants
224
	 */
225
	abstract public function getVisibilityType();
226
227
	/**
228
	 * @return integer
229
	 */
230
	protected function getType() {
231
		return DBConfigService::MOUNT_TYPE_ADMIN;
232
	}
233
234
	/**
235
	 * Add new storage to the configuration
236
	 *
237
	 * @param StorageConfig $newStorage storage attributes
238
	 *
239
	 * @return StorageConfig storage config, with added id
240
	 */
241
	public function addStorage(StorageConfig $newStorage) {
242
		$allStorages = $this->readConfig();
243
244
		$configId = $this->dbConfig->addMount(
245
			$newStorage->getMountPoint(),
246
			$newStorage->getBackend()->getIdentifier(),
247
			$newStorage->getAuthMechanism()->getIdentifier(),
248
			$newStorage->getPriority(),
249
			$this->getType()
250
		);
251
252
		$newStorage->setId($configId);
253
254
		foreach ($newStorage->getApplicableUsers() as $user) {
255
			$this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_USER, $user);
256
		}
257
		foreach ($newStorage->getApplicableGroups() as $group) {
258
			$this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
259
		}
260
		foreach ($newStorage->getBackendOptions() as $key => $value) {
261
			$this->dbConfig->setConfig($configId, $key, $value);
262
		}
263
		foreach ($newStorage->getMountOptions() as $key => $value) {
264
			$this->dbConfig->setOption($configId, $key, $value);
265
		}
266
267
		if (count($newStorage->getApplicableUsers()) === 0 && count($newStorage->getApplicableGroups()) === 0) {
268
			$this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
269
		}
270
271
		// add new storage
272
		$allStorages[$configId] = $newStorage;
273
274
		$this->triggerHooks($newStorage, Filesystem::signal_create_mount);
275
276
		$newStorage->setStatus(StorageNotAvailableException::STATUS_SUCCESS);
277
		return $newStorage;
278
	}
279
280
	/**
281
	 * Create a storage from its parameters
282
	 *
283
	 * @param string $mountPoint storage mount point
284
	 * @param string $backendIdentifier backend identifier
285
	 * @param string $authMechanismIdentifier authentication mechanism identifier
286
	 * @param array $backendOptions backend-specific options
287
	 * @param array|null $mountOptions mount-specific options
288
	 * @param array|null $applicableUsers users for which to mount the storage
289
	 * @param array|null $applicableGroups groups for which to mount the storage
290
	 * @param int|null $priority priority
291
	 *
292
	 * @return StorageConfig
293
	 */
294
	public function createStorage(
295
		$mountPoint,
296
		$backendIdentifier,
297
		$authMechanismIdentifier,
298
		$backendOptions,
299
		$mountOptions = null,
300
		$applicableUsers = null,
301
		$applicableGroups = null,
302
		$priority = null
303
	) {
304
		$backend = $this->backendService->getBackend($backendIdentifier);
305
		if (!$backend) {
306
			$backend = new InvalidBackend($backendIdentifier);
307
		}
308
		$authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier);
309
		if (!$authMechanism) {
310
			$authMechanism = new InvalidAuth($authMechanismIdentifier);
311
		}
312
		$newStorage = new StorageConfig();
313
		$newStorage->setMountPoint($mountPoint);
314
		$newStorage->setBackend($backend);
315
		$newStorage->setAuthMechanism($authMechanism);
316
		$newStorage->setBackendOptions($backendOptions);
317
		if (isset($mountOptions)) {
318
			$newStorage->setMountOptions($mountOptions);
319
		}
320
		if (isset($applicableUsers)) {
321
			$newStorage->setApplicableUsers($applicableUsers);
322
		}
323
		if (isset($applicableGroups)) {
324
			$newStorage->setApplicableGroups($applicableGroups);
325
		}
326
		if (isset($priority)) {
327
			$newStorage->setPriority($priority);
328
		}
329
330
		return $newStorage;
331
	}
332
333
	/**
334
	 * Triggers the given hook signal for all the applicables given
335
	 *
336
	 * @param string $signal signal
337
	 * @param string $mountPoint hook mount pount param
338
	 * @param string $mountType hook mount type param
339
	 * @param array $applicableArray array of applicable users/groups for which to trigger the hook
340
	 */
341
	protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray) {
342
		foreach ($applicableArray as $applicable) {
343
			\OCP\Util::emitHook(
344
				Filesystem::CLASSNAME,
345
				$signal,
346
				[
347
					Filesystem::signal_param_path => $mountPoint,
348
					Filesystem::signal_param_mount_type => $mountType,
349
					Filesystem::signal_param_users => $applicable,
350
				]
351
			);
352
		}
353
	}
354
355
	/**
356
	 * Triggers $signal for all applicable users of the given
357
	 * storage
358
	 *
359
	 * @param StorageConfig $storage storage data
360
	 * @param string $signal signal to trigger
361
	 */
362
	abstract protected function triggerHooks(StorageConfig $storage, $signal);
363
364
	/**
365
	 * Triggers signal_create_mount or signal_delete_mount to
366
	 * accommodate for additions/deletions in applicableUsers
367
	 * and applicableGroups fields.
368
	 *
369
	 * @param StorageConfig $oldStorage old storage data
370
	 * @param StorageConfig $newStorage new storage data
371
	 */
372
	abstract protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage);
373
374
	/**
375
	 * Update storage to the configuration
376
	 *
377
	 * @param StorageConfig $updatedStorage storage attributes
378
	 *
379
	 * @return StorageConfig storage config
380
	 * @throws NotFoundException if the given storage does not exist in the config
381
	 */
382
	public function updateStorage(StorageConfig $updatedStorage) {
383
		$id = $updatedStorage->getId();
384
385
		$existingMount = $this->dbConfig->getMountById($id);
386
387
		if (!is_array($existingMount)) {
0 ignored issues
show
introduced by
The condition is_array($existingMount) is always true.
Loading history...
388
			throw new NotFoundException('Storage with ID "' . $id . '" not found while updating storage');
389
		}
390
391
		$oldStorage = $this->getStorageConfigFromDBMount($existingMount);
392
393
		if ($oldStorage->getBackend() instanceof InvalidBackend) {
394
			throw new NotFoundException('Storage with id "' . $id . '" cannot be edited due to missing backend');
395
		}
396
397
		$removedUsers = array_diff($oldStorage->getApplicableUsers(), $updatedStorage->getApplicableUsers());
398
		$removedGroups = array_diff($oldStorage->getApplicableGroups(), $updatedStorage->getApplicableGroups());
399
		$addedUsers = array_diff($updatedStorage->getApplicableUsers(), $oldStorage->getApplicableUsers());
400
		$addedGroups = array_diff($updatedStorage->getApplicableGroups(), $oldStorage->getApplicableGroups());
401
402
		$oldUserCount = count($oldStorage->getApplicableUsers());
403
		$oldGroupCount = count($oldStorage->getApplicableGroups());
404
		$newUserCount = count($updatedStorage->getApplicableUsers());
405
		$newGroupCount = count($updatedStorage->getApplicableGroups());
406
		$wasGlobal = ($oldUserCount + $oldGroupCount) === 0;
407
		$isGlobal = ($newUserCount + $newGroupCount) === 0;
408
409
		foreach ($removedUsers as $user) {
410
			$this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
411
		}
412
		foreach ($removedGroups as $group) {
413
			$this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
414
		}
415
		foreach ($addedUsers as $user) {
416
			$this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
417
		}
418
		foreach ($addedGroups as $group) {
419
			$this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
420
		}
421
422
		if ($wasGlobal && !$isGlobal) {
423
			$this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
424
		} elseif (!$wasGlobal && $isGlobal) {
425
			$this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
426
		}
427
428
		$changedConfig = array_diff_assoc($updatedStorage->getBackendOptions(), $oldStorage->getBackendOptions());
429
		$changedOptions = array_diff_assoc($updatedStorage->getMountOptions(), $oldStorage->getMountOptions());
430
431
		foreach ($changedConfig as $key => $value) {
432
			if ($value !== DefinitionParameter::UNMODIFIED_PLACEHOLDER) {
433
				$this->dbConfig->setConfig($id, $key, $value);
434
			}
435
		}
436
		foreach ($changedOptions as $key => $value) {
437
			$this->dbConfig->setOption($id, $key, $value);
438
		}
439
440
		if ($updatedStorage->getMountPoint() !== $oldStorage->getMountPoint()) {
441
			$this->dbConfig->setMountPoint($id, $updatedStorage->getMountPoint());
442
		}
443
444
		if ($updatedStorage->getAuthMechanism()->getIdentifier() !== $oldStorage->getAuthMechanism()->getIdentifier()) {
445
			$this->dbConfig->setAuthBackend($id, $updatedStorage->getAuthMechanism()->getIdentifier());
446
		}
447
448
		$this->triggerChangeHooks($oldStorage, $updatedStorage);
449
450
		if (($wasGlobal && !$isGlobal) || count($removedGroups) > 0) { // to expensive to properly handle these on the fly
451
			$this->userMountCache->remoteStorageMounts($this->getStorageId($updatedStorage));
452
		} else {
453
			$storageId = $this->getStorageId($updatedStorage);
454
			foreach ($removedUsers as $userId) {
455
				$this->userMountCache->removeUserStorageMount($storageId, $userId);
456
			}
457
		}
458
459
		return $this->getStorage($id);
460
	}
461
462
	/**
463
	 * Delete the storage with the given id.
464
	 *
465
	 * @param int $id storage id
466
	 *
467
	 * @throws NotFoundException if no storage was found with the given id
468
	 */
469
	public function removeStorage($id) {
470
		$existingMount = $this->dbConfig->getMountById($id);
471
472
		if (!is_array($existingMount)) {
0 ignored issues
show
introduced by
The condition is_array($existingMount) is always true.
Loading history...
473
			throw new NotFoundException('Storage with ID "' . $id . '" not found');
474
		}
475
476
		$this->dbConfig->removeMount($id);
477
478
		$deletedStorage = $this->getStorageConfigFromDBMount($existingMount);
479
		$this->triggerHooks($deletedStorage, Filesystem::signal_delete_mount);
480
481
		// delete oc_storages entries and oc_filecache
482
		\OC\Files\Cache\Storage::cleanByMountId($id);
483
	}
484
485
	/**
486
	 * Construct the storage implementation
487
	 *
488
	 * @param StorageConfig $storageConfig
489
	 * @return int
490
	 */
491
	private function getStorageId(StorageConfig $storageConfig) {
492
		try {
493
			$class = $storageConfig->getBackend()->getStorageClass();
494
			/** @var \OC\Files\Storage\Storage $storage */
495
			$storage = new $class($storageConfig->getBackendOptions());
496
497
			// auth mechanism should fire first
498
			$storage = $storageConfig->getBackend()->wrapStorage($storage);
499
			$storage = $storageConfig->getAuthMechanism()->wrapStorage($storage);
500
501
			return $storage->getStorageCache()->getNumericId();
502
		} catch (\Exception $e) {
503
			return -1;
504
		}
505
	}
506
}
507