Passed
Push — master ( 7e1bee...0eb3c3 )
by Roeland
10:50 queued 12s
created

OC_Mount_Config   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 383
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 151
dl 0
loc 383
rs 8.5599
c 0
b 0
f 0
wmc 48

17 Methods

Rating   Name   Duplication   Size   Complexity  
A registerBackend() 0 7 1
A getSystemMountPoints() 0 9 2
A substitutePlaceholdersInConfig() 0 12 4
A getAbsoluteMountPoints() 0 33 5
A getPersonalMountPoints() 0 9 2
A prepareMountPointEntry() 0 23 3
A setUserVars() 0 3 1
A encryptPasswords() 0 8 2
A encryptPassword() 0 5 1
A decryptPasswords() 0 7 2
A getSingleDependencyMessage() 0 8 3
A getCipher() 0 4 1
A makeConfigHash() 0 12 3
A readData() 0 15 4
B getBackendStatus() 0 32 8
A decryptPassword() 0 7 1
A dependencyMessage() 0 23 5

How to fix   Complexity   

Complex Class

Complex classes like OC_Mount_Config often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OC_Mount_Config, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Andreas Fischer <[email protected]>
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Björn Schießle <[email protected]>
9
 * @author Daniel Kesselberg <[email protected]>
10
 * @author Frank Karlitschek <[email protected]>
11
 * @author Jesús Macias <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author Juan Pablo Villafáñez <[email protected]>
14
 * @author Julius Härtl <[email protected]>
15
 * @author Lukas Reschke <[email protected]>
16
 * @author Michael Gapczynski <[email protected]>
17
 * @author Morris Jobke <[email protected]>
18
 * @author Philipp Kapfer <[email protected]>
19
 * @author Robin Appelman <[email protected]>
20
 * @author Robin McCorkell <[email protected]>
21
 * @author Roeland Jago Douma <[email protected]>
22
 * @author Thomas Müller <[email protected]>
23
 * @author Vincent Petry <[email protected]>
24
 *
25
 * @license AGPL-3.0
26
 *
27
 * This code is free software: you can redistribute it and/or modify
28
 * it under the terms of the GNU Affero General Public License, version 3,
29
 * as published by the Free Software Foundation.
30
 *
31
 * This program is distributed in the hope that it will be useful,
32
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34
 * GNU Affero General Public License for more details.
35
 *
36
 * You should have received a copy of the GNU Affero General Public License, version 3,
37
 * along with this program. If not, see <http://www.gnu.org/licenses/>
38
 *
39
 */
40
41
use OCA\Files_External\AppInfo\Application;
42
use OCA\Files_External\Config\IConfigHandler;
43
use OCA\Files_External\Config\UserContext;
44
use OCA\Files_External\Config\UserPlaceholderHandler;
45
use OCA\Files_External\Lib\Auth\Builtin;
46
use OCA\Files_External\Lib\Backend\Backend;
47
use OCA\Files_External\Lib\Backend\LegacyBackend;
48
use OCA\Files_External\Lib\StorageConfig;
49
use OCA\Files_External\Service\BackendService;
50
use OCA\Files_External\Service\GlobalStoragesService;
51
use OCA\Files_External\Service\UserGlobalStoragesService;
52
use OCA\Files_External\Service\UserStoragesService;
53
use OCP\Files\StorageNotAvailableException;
54
use OCP\IUserManager;
55
use phpseclib\Crypt\AES;
56
57
/**
58
 * Class to configure mount.json globally and for users
59
 */
60
class OC_Mount_Config {
61
	// TODO: make this class non-static and give it a proper namespace
62
63
	const MOUNT_TYPE_GLOBAL = 'global';
64
	const MOUNT_TYPE_GROUP = 'group';
65
	const MOUNT_TYPE_USER = 'user';
66
	const MOUNT_TYPE_PERSONAL = 'personal';
67
68
	// whether to skip backend test (for unit tests, as this static class is not mockable)
69
	public static $skipTest = false;
70
71
	/** @var Application */
72
	public static $app;
73
74
	/**
75
	 * @param string $class
76
	 * @param array $definition
77
	 * @return bool
78
	 * @deprecated 8.2.0 use \OCA\Files_External\Service\BackendService::registerBackend()
79
	 */
80
	public static function registerBackend($class, $definition) {
81
		$backendService = self::$app->getContainer()->query(BackendService::class);
82
		$auth = self::$app->getContainer()->query(Builtin::class);
83
84
		$backendService->registerBackend(new LegacyBackend($class, $definition, $auth));
85
86
		return true;
87
	}
88
89
	/**
90
	 * Returns the mount points for the given user.
91
	 * The mount point is relative to the data directory.
92
	 *
93
	 * @param string $uid user
94
	 * @return array of mount point string as key, mountpoint config as value
95
	 *
96
	 * @deprecated 8.2.0 use UserGlobalStoragesService::getStorages() and UserStoragesService::getStorages()
97
	 */
98
	public static function getAbsoluteMountPoints($uid) {
99
		$mountPoints = array();
100
101
		$userGlobalStoragesService = self::$app->getContainer()->query(UserGlobalStoragesService::class);
102
		$userStoragesService = self::$app->getContainer()->query(UserStoragesService::class);
103
		$user = self::$app->getContainer()->query(IUserManager::class)->get($uid);
104
105
		$userGlobalStoragesService->setUser($user);
106
		$userStoragesService->setUser($user);
107
108
		foreach ($userGlobalStoragesService->getStorages() as $storage) {
109
			/** @var \OCA\Files_External\Lib\StorageConfig $storage */
110
			$mountPoint = '/'.$uid.'/files'.$storage->getMountPoint();
111
			$mountEntry = self::prepareMountPointEntry($storage, false);
112
			foreach ($mountEntry['options'] as &$option) {
113
				$option = self::substitutePlaceholdersInConfig($option, $uid);
114
			}
115
			$mountPoints[$mountPoint] = $mountEntry;
116
		}
117
118
		foreach ($userStoragesService->getStorages() as $storage) {
119
			$mountPoint = '/'.$uid.'/files'.$storage->getMountPoint();
120
			$mountEntry = self::prepareMountPointEntry($storage, true);
121
			foreach ($mountEntry['options'] as &$option) {
122
				$option = self::substitutePlaceholdersInConfig($option, $uid);
123
			}
124
			$mountPoints[$mountPoint] = $mountEntry;
125
		}
126
127
		$userGlobalStoragesService->resetUser();
128
		$userStoragesService->resetUser();
129
130
		return $mountPoints;
131
	}
132
133
	/**
134
	 * Get the system mount points
135
	 *
136
	 * @return array
137
	 *
138
	 * @deprecated 8.2.0 use GlobalStoragesService::getStorages()
139
	 */
140
	public static function getSystemMountPoints() {
141
		$mountPoints = [];
142
		$service = self::$app->getContainer()->query(GlobalStoragesService::class);
143
144
		foreach ($service->getStorages() as $storage) {
145
			$mountPoints[] = self::prepareMountPointEntry($storage, false);
146
		}
147
148
		return $mountPoints;
149
	}
150
151
	/**
152
	 * Get the personal mount points of the current user
153
	 *
154
	 * @return array
155
	 *
156
	 * @deprecated 8.2.0 use UserStoragesService::getStorages()
157
	 */
158
	public static function getPersonalMountPoints() {
159
		$mountPoints = [];
160
		$service = self::$app->getContainer()->query(UserStoragesService::class);
161
162
		foreach ($service->getStorages() as $storage) {
163
			$mountPoints[] = self::prepareMountPointEntry($storage, true);
164
		}
165
166
		return $mountPoints;
167
	}
168
169
	/**
170
	 * Convert a StorageConfig to the legacy mountPoints array format
171
	 * There's a lot of extra information in here, to satisfy all of the legacy functions
172
	 *
173
	 * @param StorageConfig $storage
174
	 * @param bool $isPersonal
175
	 * @return array
176
	 */
177
	private static function prepareMountPointEntry(StorageConfig $storage, $isPersonal) {
178
		$mountEntry = [];
179
180
		$mountEntry['mountpoint'] = substr($storage->getMountPoint(), 1); // remove leading slash
181
		$mountEntry['class'] = $storage->getBackend()->getIdentifier();
182
		$mountEntry['backend'] = $storage->getBackend()->getText();
183
		$mountEntry['authMechanism'] = $storage->getAuthMechanism()->getIdentifier();
184
		$mountEntry['personal'] = $isPersonal;
185
		$mountEntry['options'] = self::decryptPasswords($storage->getBackendOptions());
186
		$mountEntry['mountOptions'] = $storage->getMountOptions();
187
		$mountEntry['priority'] = $storage->getPriority();
188
		$mountEntry['applicable'] = [
189
			'groups' => $storage->getApplicableGroups(),
190
			'users' => $storage->getApplicableUsers(),
191
		];
192
		// if mountpoint is applicable to all users the old API expects ['all']
193
		if (empty($mountEntry['applicable']['groups']) && empty($mountEntry['applicable']['users'])) {
194
			$mountEntry['applicable']['users'] = ['all'];
195
		}
196
197
		$mountEntry['id'] = $storage->getId();
198
199
		return $mountEntry;
200
	}
201
202
	/**
203
	 * fill in the correct values for $user
204
	 *
205
	 * @param string $user user value
206
	 * @param string|array $input
207
	 * @return string
208
	 * @deprecated use self::substitutePlaceholdersInConfig($input)
209
	 */
210
	public static function setUserVars($user, $input) {
0 ignored issues
show
Unused Code introduced by
The parameter $user is not used and could be removed. ( Ignorable by Annotation )

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

210
	public static function setUserVars(/** @scrutinizer ignore-unused */ $user, $input) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
211
		$handler = self::$app->getContainer()->query(UserPlaceholderHandler::class);
212
		return $handler->handle($input);
213
	}
214
215
	/**
216
	 * @param mixed $input
217
	 * @param string|null $userId
218
	 * @return mixed
219
	 * @throws \OCP\AppFramework\QueryException
220
	 * @since 16.0.0
221
	 */
222
	public static function substitutePlaceholdersInConfig($input, string $userId = null) {
223
		/** @var BackendService $backendService */
224
		$backendService = self::$app->getContainer()->query(BackendService::class);
225
		/** @var IConfigHandler[] $handlers */
226
		$handlers = $backendService->getConfigHandlers();
227
		foreach ($handlers as $handler) {
228
			if ($handler instanceof UserContext && $userId !== null) {
229
				$handler->setUserId($userId);
230
			}
231
			$input = $handler->handle($input);
232
		}
233
		return $input;
234
	}
235
236
	/**
237
	 * Test connecting using the given backend configuration
238
	 *
239
	 * @param string $class backend class name
240
	 * @param array $options backend configuration options
241
	 * @param boolean $isPersonal
242
	 * @return int see self::STATUS_*
243
	 * @throws Exception
244
	 */
245
	public static function getBackendStatus($class, $options, $isPersonal, $testOnly = true) {
246
		if (self::$skipTest) {
247
			return StorageNotAvailableException::STATUS_SUCCESS;
248
		}
249
		foreach ($options as $key => &$option) {
250
			if($key === 'password') {
251
				// no replacements in passwords
252
				continue;
253
			}
254
			$option = self::substitutePlaceholdersInConfig($option);
255
		}
256
		if (class_exists($class)) {
257
			try {
258
				/** @var \OC\Files\Storage\Common $storage */
259
				$storage = new $class($options);
260
261
				try {
262
					$result = $storage->test($isPersonal, $testOnly);
0 ignored issues
show
Unused Code introduced by
The call to OC\Files\Storage\Common::test() has too many arguments starting with $isPersonal. ( Ignorable by Annotation )

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

262
					/** @scrutinizer ignore-call */ 
263
     $result = $storage->test($isPersonal, $testOnly);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
263
					$storage->setAvailability($result);
264
					if ($result) {
265
						return StorageNotAvailableException::STATUS_SUCCESS;
266
					}
267
				} catch (\Exception $e) {
268
					$storage->setAvailability(false);
269
					throw $e;
270
				}
271
			} catch (Exception $exception) {
272
				\OC::$server->getLogger()->logException($exception, ['app' => 'files_external']);
273
				throw $exception;
274
			}
275
		}
276
		return StorageNotAvailableException::STATUS_ERROR;
277
	}
278
279
	/**
280
	 * Read the mount points in the config file into an array
281
	 *
282
	 * @param string|null $user If not null, personal for $user, otherwise system
283
	 * @return array
284
	 */
285
	public static function readData($user = null) {
286
		if (isset($user)) {
287
			$jsonFile = \OC::$server->getUserManager()->get($user)->getHome() . '/mount.json';
288
		} else {
289
			$config = \OC::$server->getConfig();
290
			$datadir = $config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/');
291
			$jsonFile = $config->getSystemValue('mount_file', $datadir . '/mount.json');
292
		}
293
		if (is_file($jsonFile)) {
294
			$mountPoints = json_decode(file_get_contents($jsonFile), true);
295
			if (is_array($mountPoints)) {
296
				return $mountPoints;
297
			}
298
		}
299
		return array();
300
	}
301
302
	/**
303
	 * Get backend dependency message
304
	 * TODO: move into AppFramework along with templates
305
	 *
306
	 * @param Backend[] $backends
307
	 * @return string
308
	 */
309
	public static function dependencyMessage($backends) {
310
		$l = \OC::$server->getL10N('files_external');
311
		$message = '';
312
		$dependencyGroups = [];
313
314
		foreach ($backends as $backend) {
315
			foreach ($backend->checkDependencies() as $dependency) {
316
				if ($message = $dependency->getMessage()) {
317
					$message .= '<p>' . $message . '</p>';
318
				} else {
319
					$dependencyGroups[$dependency->getDependency()][] = $backend;
320
				}
321
			}
322
		}
323
324
		foreach ($dependencyGroups as $module => $dependants) {
325
			$backends = implode(', ', array_map(function($backend) {
326
				return '"' . $backend->getText() . '"';
327
			}, $dependants));
328
			$message .= '<p>' . OC_Mount_Config::getSingleDependencyMessage($l, $module, $backends) . '</p>';
329
		}
330
331
		return $message;
332
	}
333
334
	/**
335
	 * Returns a dependency missing message
336
	 *
337
	 * @param \OCP\IL10N $l
338
	 * @param string $module
339
	 * @param string $backend
340
	 * @return string
341
	 */
342
	private static function getSingleDependencyMessage(\OCP\IL10N $l, $module, $backend) {
343
		switch (strtolower($module)) {
344
			case 'curl':
345
				return (string)$l->t('The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it.', [$backend]);
346
			case 'ftp':
347
				return (string)$l->t('The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it.', [$backend]);
348
			default:
349
				return (string)$l->t('"%1$s" is not installed. Mounting of %2$s is not possible. Please ask your system administrator to install it.', [$module, $backend]);
350
		}
351
	}
352
353
	/**
354
	 * Encrypt passwords in the given config options
355
	 *
356
	 * @param array $options mount options
357
	 * @return array updated options
358
	 */
359
	public static function encryptPasswords($options) {
360
		if (isset($options['password'])) {
361
			$options['password_encrypted'] = self::encryptPassword($options['password']);
362
			// do not unset the password, we want to keep the keys order
363
			// on load... because that's how the UI currently works
364
			$options['password'] = '';
365
		}
366
		return $options;
367
	}
368
369
	/**
370
	 * Decrypt passwords in the given config options
371
	 *
372
	 * @param array $options mount options
373
	 * @return array updated options
374
	 */
375
	public static function decryptPasswords($options) {
376
		// note: legacy options might still have the unencrypted password in the "password" field
377
		if (isset($options['password_encrypted'])) {
378
			$options['password'] = self::decryptPassword($options['password_encrypted']);
379
			unset($options['password_encrypted']);
380
		}
381
		return $options;
382
	}
383
384
	/**
385
	 * Encrypt a single password
386
	 *
387
	 * @param string $password plain text password
388
	 * @return string encrypted password
389
	 */
390
	private static function encryptPassword($password) {
391
		$cipher = self::getCipher();
392
		$iv = \OC::$server->getSecureRandom()->generate(16);
393
		$cipher->setIV($iv);
394
		return base64_encode($iv . $cipher->encrypt($password));
395
	}
396
397
	/**
398
	 * Decrypts a single password
399
	 *
400
	 * @param string $encryptedPassword encrypted password
401
	 * @return string plain text password
402
	 */
403
	private static function decryptPassword($encryptedPassword) {
404
		$cipher = self::getCipher();
405
		$binaryPassword = base64_decode($encryptedPassword);
406
		$iv = substr($binaryPassword, 0, 16);
407
		$cipher->setIV($iv);
408
		$binaryPassword = substr($binaryPassword, 16);
409
		return $cipher->decrypt($binaryPassword);
410
	}
411
412
	/**
413
	 * Returns the encryption cipher
414
	 *
415
	 * @return AES
416
	 */
417
	private static function getCipher() {
418
		$cipher = new AES(AES::MODE_CBC);
419
		$cipher->setKey(\OC::$server->getConfig()->getSystemValue('passwordsalt', null));
420
		return $cipher;
421
	}
422
423
	/**
424
	 * Computes a hash based on the given configuration.
425
	 * This is mostly used to find out whether configurations
426
	 * are the same.
427
	 *
428
	 * @param array $config
429
	 * @return string
430
	 */
431
	public static function makeConfigHash($config) {
432
		$data = json_encode(
433
			array(
434
				'c' => $config['backend'],
435
				'a' => $config['authMechanism'],
436
				'm' => $config['mountpoint'],
437
				'o' => $config['options'],
438
				'p' => isset($config['priority']) ? $config['priority'] : -1,
439
				'mo' => isset($config['mountOptions']) ? $config['mountOptions'] : [],
440
			)
441
		);
442
		return hash('md5', $data);
443
	}
444
}
445