Completed
Push — stable8 ( 420dab...a42074 )
by
unknown
10:01
created

OC_Mount_Config   F

Complexity

Total Complexity 161

Size/Duplication

Total Lines 925
Duplicated Lines 17.84 %

Coupling/Cohesion

Components 1
Dependencies 21

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 165
loc 925
rs 1.263
c 1
b 0
f 0
wmc 161
lcom 1
cbo 21

28 Methods

Rating   Name   Duplication   Size   Complexity  
C addMountPoint() 0 80 18
C mergeMountPoints() 0 30 8
A registerBackend() 0 8 2
A setUp() 0 6 1
B getBackends() 0 25 6
B initMountPointsHook() 0 27 3
D getAbsoluteMountPoints() 68 136 35
A setUserVars() 0 3 1
B getPersonalBackends() 0 22 4
C getSystemMountPoints() 69 75 13
B getPersonalMountPoints() 0 24 4
B getBackendStatus() 0 18 5
C removeMountPoint() 0 34 7
A movePersonalMountPoint() 0 14 2
B readData() 5 23 6
B writeData() 6 20 5
D checkDependencies() 0 25 9
A addDependency() 0 11 3
D generateDependencyMessage() 0 29 9
A getSingleDependencyMessage() 0 10 3
A encryptPasswords() 9 9 2
A decryptPasswords() 8 8 2
A encryptPassword() 0 6 1
A decryptPassword() 0 8 1
A getCipher() 0 8 2
A makeConfigHash() 0 10 1
B addStorageIdToConfig() 0 16 5
A addStorageId() 0 20 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
 * ownCloud
4
 *
5
 * @author Michael Gapczynski
6
 * @copyright 2012 Michael Gapczynski [email protected]
7
 * @copyright 2014 Vincent Petry <[email protected]>
8
 * @copyright 2014 Robin McCorkell <[email protected]>
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
12
 * License as published by the Free Software Foundation; either
13
 * version 3 of the License, or any later version.
14
 *
15
 * This library is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public
21
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
/**
25
 * Class to configure mount.json globally and for users
26
 */
27
class OC_Mount_Config {
28
	// TODO: make this class non-static and give it a proper namespace
29
30
	const MOUNT_TYPE_GLOBAL = 'global';
31
	const MOUNT_TYPE_GROUP = 'group';
32
	const MOUNT_TYPE_USER = 'user';
33
	const MOUNT_TYPE_PERSONAL = 'personal';
34
35
	// whether to skip backend test (for unit tests, as this static class is not mockable)
36
	public static $skipTest = false;
37
38
	private static $backends = array();
39
40
	/**
41
	 * @param string $class
42
	 * @param array $definition
43
	 * @return bool
44
	 */
45
	public static function registerBackend($class, $definition) {
46
		if (!isset($definition['backend'])) {
47
			return false;
48
		}
49
50
		OC_Mount_Config::$backends[$class] = $definition;
51
		return true;
52
	}
53
54
	/**
55
	 * Setup backends
56
	 *
57
	 * @return array of previously registered backends
58
	 */
59
	public static function setUp($backends = array()) {
60
		$backup = self::$backends;
61
		self::$backends = $backends;
62
63
		return $backup;
64
	}
65
66
	/**
67
	 * Get details on each of the external storage backends, used for the mount config UI
68
	 * If a custom UI is needed, add the key 'custom' and a javascript file with that name will be loaded
69
	 * If the configuration parameter should be secret, add a '*' to the beginning of the value
70
	 * If the configuration parameter is a boolean, add a '!' to the beginning of the value
71
	 * If the configuration parameter is optional, add a '&' to the beginning of the value
72
	 * If the configuration parameter is hidden, add a '#' to the beginning of the value
73
	 *
74
	 * @return array
75
	 */
76
	public static function getBackends() {
77
		$sortFunc = function ($a, $b) {
78
			return strcasecmp($a['backend'], $b['backend']);
79
		};
80
81
		$backEnds = array();
82
83
		foreach (OC_Mount_Config::$backends as $class => $backend) {
84
			if (isset($backend['has_dependencies']) and $backend['has_dependencies'] === true) {
85
				if (!method_exists($class, 'checkDependencies')) {
86
					\OCP\Util::writeLog('files_external',
87
						"Backend class $class has dependencies but doesn't provide method checkDependencies()",
88
						\OCP\Util::DEBUG);
89
					continue;
90
				} elseif ($class::checkDependencies() !== true) {
91
					continue;
92
				}
93
			}
94
			$backEnds[$class] = $backend;
95
		}
96
97
		uasort($backEnds, $sortFunc);
98
99
		return $backEnds;
100
	}
101
102
	/**
103
	 * Hook that mounts the given user's visible mount points
104
	 *
105
	 * @param array $data
106
	 */
107
	public static function initMountPointsHook($data) {
108
		self::addStorageIdToConfig(null);
109
		if ($data['user']) {
110
			self::addStorageIdToConfig($data['user']);
111
			$user = \OC::$server->getUserManager()->get($data['user']);
112
			if (!$user) {
113
				\OC_Log::write(
114
					'files_external',
115
					'Cannot init external mount points for non-existant user "' . $data['user'] . '".',
116
					\OC_Log::WARN
117
				);
118
				return;
119
			}
120
			$userView = new \OC\Files\View('/' . $user->getUID() . '/files');
121
			$changePropagator = new \OC\Files\Cache\ChangePropagator($userView);
122
			$etagPropagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, \OC::$server->getConfig());
123
			$etagPropagator->propagateDirtyMountPoints();
124
			\OCP\Util::connectHook(
125
				\OC\Files\Filesystem::CLASSNAME,
126
				\OC\Files\Filesystem::signal_create_mount,
127
				$etagPropagator, 'updateHook');
0 ignored issues
show
Documentation introduced by
$etagPropagator is of type object<OCA\Files_External\EtagPropagator>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
128
			\OCP\Util::connectHook(
129
				\OC\Files\Filesystem::CLASSNAME,
130
				\OC\Files\Filesystem::signal_delete_mount,
131
				$etagPropagator, 'updateHook');
0 ignored issues
show
Documentation introduced by
$etagPropagator is of type object<OCA\Files_External\EtagPropagator>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
132
		}
133
	}
134
135
	/**
136
	 * Returns the mount points for the given user.
137
	 * The mount point is relative to the data directory.
138
	 *
139
	 * @param string $user user
140
	 * @return array of mount point string as key, mountpoint config as value
141
	 */
142
	public static function getAbsoluteMountPoints($user) {
143
		$mountPoints = array();
144
145
		$datadir = \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data");
146
		$mount_file = \OC_Config::getValue("mount_file", $datadir . "/mount.json");
147
148
		$backends = self::getBackends();
149
150
		//move config file to it's new position
151
		if (is_file(\OC::$SERVERROOT . '/config/mount.json')) {
152
			rename(\OC::$SERVERROOT . '/config/mount.json', $mount_file);
153
		}
154
155
		// Load system mount points
156
		$mountConfig = self::readData();
157
158
		// Global mount points (is this redundant?)
159
		if (isset($mountConfig[self::MOUNT_TYPE_GLOBAL])) {
160
			foreach ($mountConfig[self::MOUNT_TYPE_GLOBAL] as $mountPoint => $options) {
161
				$options['personal'] = false;
162
				$options['options'] = self::decryptPasswords($options['options']);
163
				if (!isset($options['priority'])) {
164
					$options['priority'] = $backends[$options['class']]['priority'];
165
				}
166
167
				// Override if priority greater
168 View Code Duplication
				if ((!isset($mountPoints[$mountPoint]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
169
					|| ($options['priority'] >= $mountPoints[$mountPoint]['priority'])
170
				) {
171
					$options['priority_type'] = self::MOUNT_TYPE_GLOBAL;
172
					$options['backend'] = $backends[$options['class']]['backend'];
173
					$mountPoints[$mountPoint] = $options;
174
				}
175
			}
176
		}
177
		// All user mount points
178
		if (isset($mountConfig[self::MOUNT_TYPE_USER]) && isset($mountConfig[self::MOUNT_TYPE_USER]['all'])) {
179
			$mounts = $mountConfig[self::MOUNT_TYPE_USER]['all'];
180
			foreach ($mounts as $mountPoint => $options) {
181
				$mountPoint = self::setUserVars($user, $mountPoint);
182
				foreach ($options as &$option) {
183
					$option = self::setUserVars($user, $option);
184
				}
185
				$options['personal'] = false;
186
				$options['options'] = self::decryptPasswords($options['options']);
187
				if (!isset($options['priority'])) {
188
					$options['priority'] = $backends[$options['class']]['priority'];
189
				}
190
191
				// Override if priority greater
192 View Code Duplication
				if ((!isset($mountPoints[$mountPoint]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
					|| ($options['priority'] >= $mountPoints[$mountPoint]['priority'])
194
				) {
195
					$options['priority_type'] = self::MOUNT_TYPE_GLOBAL;
196
					$options['backend'] = $backends[$options['class']]['backend'];
197
					$mountPoints[$mountPoint] = $options;
198
				}
199
			}
200
		}
201
		// Group mount points
202 View Code Duplication
		if (isset($mountConfig[self::MOUNT_TYPE_GROUP])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
			foreach ($mountConfig[self::MOUNT_TYPE_GROUP] as $group => $mounts) {
204
				if (\OC_Group::inGroup($user, $group)) {
205
					foreach ($mounts as $mountPoint => $options) {
206
						$mountPoint = self::setUserVars($user, $mountPoint);
207
						foreach ($options as &$option) {
208
							$option = self::setUserVars($user, $option);
209
						}
210
						$options['personal'] = false;
211
						$options['options'] = self::decryptPasswords($options['options']);
212
						if (!isset($options['priority'])) {
213
							$options['priority'] = $backends[$options['class']]['priority'];
214
						}
215
216
						// Override if priority greater or if priority type different
217
						if ((!isset($mountPoints[$mountPoint]))
218
							|| ($options['priority'] >= $mountPoints[$mountPoint]['priority'])
219
							|| ($mountPoints[$mountPoint]['priority_type'] !== self::MOUNT_TYPE_GROUP)
220
						) {
221
							$options['priority_type'] = self::MOUNT_TYPE_GROUP;
222
							$options['backend'] = $backends[$options['class']]['backend'];
223
							$mountPoints[$mountPoint] = $options;
224
						}
225
					}
226
				}
227
			}
228
		}
229
		// User mount points
230 View Code Duplication
		if (isset($mountConfig[self::MOUNT_TYPE_USER])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
231
			foreach ($mountConfig[self::MOUNT_TYPE_USER] as $mountUser => $mounts) {
232
				if (strtolower($mountUser) === strtolower($user)) {
233
					foreach ($mounts as $mountPoint => $options) {
234
						$mountPoint = self::setUserVars($user, $mountPoint);
235
						foreach ($options as &$option) {
236
							$option = self::setUserVars($user, $option);
237
						}
238
						$options['personal'] = false;
239
						$options['options'] = self::decryptPasswords($options['options']);
240
						if (!isset($options['priority'])) {
241
							$options['priority'] = $backends[$options['class']]['priority'];
242
						}
243
244
						// Override if priority greater or if priority type different
245
						if ((!isset($mountPoints[$mountPoint]))
246
							|| ($options['priority'] >= $mountPoints[$mountPoint]['priority'])
247
							|| ($mountPoints[$mountPoint]['priority_type'] !== self::MOUNT_TYPE_USER)
248
						) {
249
							$options['priority_type'] = self::MOUNT_TYPE_USER;
250
							$options['backend'] = $backends[$options['class']]['backend'];
251
							$mountPoints[$mountPoint] = $options;
252
						}
253
					}
254
				}
255
			}
256
		}
257
258
		$personalBackends = self::getPersonalBackends();
259
260
		// Load personal mount points
261
		$mountConfig = self::readData($user);
262
		if (isset($mountConfig[self::MOUNT_TYPE_USER][$user])) {
263
			foreach ($mountConfig[self::MOUNT_TYPE_USER][$user] as $mountPoint => $options) {
264
				if (isset($personalBackends[$options['class']])) {
265
					$options['personal'] = true;
266
					$options['options'] = self::decryptPasswords($options['options']);
267
268
					// Always override previous config
269
					$options['priority_type'] = self::MOUNT_TYPE_PERSONAL;
270
					$options['backend'] = $backends[$options['class']]['backend'];
271
					$mountPoints[$mountPoint] = $options;
272
				}
273
			}
274
		}
275
276
		return $mountPoints;
277
	}
278
279
	/**
280
	 * fill in the correct values for $user
281
	 *
282
	 * @param string $user
283
	 * @param string $input
284
	 * @return string
285
	 */
286
	private static function setUserVars($user, $input) {
287
		return str_replace('$user', $user, $input);
288
	}
289
290
291
	/**
292
	 * Get details on each of the external storage backends, used for the mount config UI
293
	 * Some backends are not available as a personal backend, f.e. Local and such that have
294
	 * been disabled by the admin.
295
	 *
296
	 * If a custom UI is needed, add the key 'custom' and a javascript file with that name will be loaded
297
	 * If the configuration parameter should be secret, add a '*' to the beginning of the value
298
	 * If the configuration parameter is a boolean, add a '!' to the beginning of the value
299
	 * If the configuration parameter is optional, add a '&' to the beginning of the value
300
	 * If the configuration parameter is hidden, add a '#' to the beginning of the value
301
	 *
302
	 * @return array
303
	 */
304
	public static function getPersonalBackends() {
305
306
		// Check whether the user has permissions to add personal storage backends
307
		// return an empty array if this is not the case
308
		if (OCP\Config::getAppValue('files_external', 'allow_user_mounting', 'yes') !== 'yes') {
309
			return array();
310
		}
311
312
		$backEnds = self::getBackends();
313
314
		// Remove local storage and other disabled storages
315
		unset($backEnds['\OC\Files\Storage\Local']);
316
317
		$allowedBackEnds = explode(',', OCP\Config::getAppValue('files_external', 'user_mounting_backends', ''));
318
		foreach ($backEnds as $backend => $null) {
319
			if (!in_array($backend, $allowedBackEnds)) {
320
				unset($backEnds[$backend]);
321
			}
322
		}
323
324
		return $backEnds;
325
	}
326
327
	/**
328
	 * Get the system mount points
329
	 * The returned array is not in the same format as getUserMountPoints()
330
	 *
331
	 * @return array
332
	 */
333
	public static function getSystemMountPoints() {
334
		$mountPoints = self::readData();
335
		$backends = self::getBackends();
336
		$system = array();
337 View Code Duplication
		if (isset($mountPoints[self::MOUNT_TYPE_GROUP])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
338
			foreach ($mountPoints[self::MOUNT_TYPE_GROUP] as $group => $mounts) {
339
				foreach ($mounts as $mountPoint => $mount) {
340
					// Update old classes to new namespace
341
					if (strpos($mount['class'], 'OC_Filestorage_') !== false) {
342
						$mount['class'] = '\OC\Files\Storage\\' . substr($mount['class'], 15);
343
					}
344
					$mount['options'] = self::decryptPasswords($mount['options']);
345
					if (!isset($mount['priority'])) {
346
						$mount['priority'] = $backends[$mount['class']]['priority'];
347
					}
348
					// Remove '/$user/files/' from mount point
349
					$mountPoint = substr($mountPoint, 13);
350
351
					$config = array(
352
						'class' => $mount['class'],
353
						'mountpoint' => $mountPoint,
354
						'backend' => $backends[$mount['class']]['backend'],
355
						'priority' => $mount['priority'],
356
						'options' => $mount['options'],
357
						'applicable' => array('groups' => array($group), 'users' => array()),
358
						'status' => self::getBackendStatus($mount['class'], $mount['options'], false)
359
					);
360
					$hash = self::makeConfigHash($config);
361
					// If an existing config exists (with same class, mountpoint and options)
362
					if (isset($system[$hash])) {
363
						// add the groups into that config
364
						$system[$hash]['applicable']['groups']
365
							= array_merge($system[$hash]['applicable']['groups'], array($group));
366
					} else {
367
						$system[$hash] = $config;
368
					}
369
				}
370
			}
371
		}
372 View Code Duplication
		if (isset($mountPoints[self::MOUNT_TYPE_USER])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
373
			foreach ($mountPoints[self::MOUNT_TYPE_USER] as $user => $mounts) {
374
				foreach ($mounts as $mountPoint => $mount) {
375
					// Update old classes to new namespace
376
					if (strpos($mount['class'], 'OC_Filestorage_') !== false) {
377
						$mount['class'] = '\OC\Files\Storage\\' . substr($mount['class'], 15);
378
					}
379
					$mount['options'] = self::decryptPasswords($mount['options']);
380
					if (!isset($mount['priority'])) {
381
						$mount['priority'] = $backends[$mount['class']]['priority'];
382
					}
383
					// Remove '/$user/files/' from mount point
384
					$mountPoint = substr($mountPoint, 13);
385
					$config = array(
386
						'class' => $mount['class'],
387
						'mountpoint' => $mountPoint,
388
						'backend' => $backends[$mount['class']]['backend'],
389
						'priority' => $mount['priority'],
390
						'options' => $mount['options'],
391
						'applicable' => array('groups' => array(), 'users' => array($user)),
392
						'status' => self::getBackendStatus($mount['class'], $mount['options'], false)
393
					);
394
					$hash = self::makeConfigHash($config);
395
					// If an existing config exists (with same class, mountpoint and options)
396
					if (isset($system[$hash])) {
397
						// add the users into that config
398
						$system[$hash]['applicable']['users']
399
							= array_merge($system[$hash]['applicable']['users'], array($user));
400
					} else {
401
						$system[$hash] = $config;
402
					}
403
				}
404
			}
405
		}
406
		return array_values($system);
407
	}
408
409
	/**
410
	 * Get the personal mount points of the current user
411
	 * The returned array is not in the same format as getUserMountPoints()
412
	 *
413
	 * @return array
414
	 */
415
	public static function getPersonalMountPoints() {
416
		$mountPoints = self::readData(OCP\User::getUser());
417
		$backEnds = self::getBackends();
418
		$uid = OCP\User::getUser();
419
		$personal = array();
420
		if (isset($mountPoints[self::MOUNT_TYPE_USER][$uid])) {
421
			foreach ($mountPoints[self::MOUNT_TYPE_USER][$uid] as $mountPoint => $mount) {
422
				// Update old classes to new namespace
423
				if (strpos($mount['class'], 'OC_Filestorage_') !== false) {
424
					$mount['class'] = '\OC\Files\Storage\\' . substr($mount['class'], 15);
425
				}
426
				$mount['options'] = self::decryptPasswords($mount['options']);
427
				$personal[] = array(
428
					'class' => $mount['class'],
429
					// Remove '/uid/files/' from mount point
430
					'mountpoint' => substr($mountPoint, strlen($uid) + 8),
431
					'backend' => $backEnds[$mount['class']]['backend'],
432
					'options' => $mount['options'],
433
					'status' => self::getBackendStatus($mount['class'], $mount['options'], true)
434
				);
435
			}
436
		}
437
		return $personal;
438
	}
439
440
	/**
441
	 * Test connecting using the given backend configuration
442
	 *
443
	 * @param string $class backend class name
444
	 * @param array $options backend configuration options
445
	 * @return bool true if the connection succeeded, false otherwise
446
	 */
447
	private static function getBackendStatus($class, $options, $isPersonal) {
448
		if (self::$skipTest) {
449
			return true;
450
		}
451
		foreach ($options as &$option) {
452
			$option = self::setUserVars(OCP\User::getUser(), $option);
453
		}
454
		if (class_exists($class)) {
455
			try {
456
				$storage = new $class($options);
457
				return $storage->test($isPersonal);
458
			} catch (Exception $exception) {
459
				\OCP\Util::logException('files_external', $exception);
460
				return false;
461
			}
462
		}
463
		return false;
464
	}
465
466
	/**
467
	 * Add a mount point to the filesystem
468
	 *
469
	 * @param string $mountPoint Mount point
470
	 * @param string $class Backend class
471
	 * @param array $classOptions Backend parameters for the class
472
	 * @param string $mountType MOUNT_TYPE_GROUP | MOUNT_TYPE_USER
473
	 * @param string $applicable User or group to apply mount to
474
	 * @param bool $isPersonal Personal or system mount point i.e. is this being called from the personal or admin page
475
	 * @param int|null $priority Mount point priority, null for default
476
	 * @return boolean
477
	 */
478
	public static function addMountPoint($mountPoint,
479
										 $class,
480
										 $classOptions,
481
										 $mountType,
482
										 $applicable,
483
										 $isPersonal = false,
484
										 $priority = null) {
485
		$backends = self::getBackends();
486
		$mountPoint = OC\Files\Filesystem::normalizePath($mountPoint);
487
		$relMountPoint = $mountPoint;
488
		if ($mountPoint === '' || $mountPoint === '/') {
489
			// can't mount at root folder
490
			return false;
491
		}
492
493
		if (isset($classOptions['objectstore'])) {
494
			// objectstore cannot be set by client side
495
			return false;
496
		}
497
498
		if (!isset($backends[$class])) {
499
			// invalid backend
500
			return false;
501
		}
502
		if ($isPersonal) {
503
			// Verify that the mount point applies for the current user
504
			// Prevent non-admin users from mounting local storage and other disabled backends
505
			$allowed_backends = self::getPersonalBackends();
506
			if ($applicable != OCP\User::getUser() || !isset($allowed_backends[$class])) {
507
				return false;
508
			}
509
			$mountPoint = '/' . $applicable . '/files/' . ltrim($mountPoint, '/');
510
		} else {
511
			$mountPoint = '/$user/files/' . ltrim($mountPoint, '/');
512
		}
513
514
		$mount = array($applicable => array(
515
			$mountPoint => array(
516
				'class' => $class,
517
				'options' => self::encryptPasswords($classOptions))
518
		)
519
		);
520
		if (!$isPersonal && !is_null($priority)) {
521
			$mount[$applicable][$mountPoint]['priority'] = $priority;
522
		}
523
524
		$mountPoints = self::readData($isPersonal ? OCP\User::getUser() : null);
525
		// who else loves multi-dimensional array ?
526
		$isNew = !isset($mountPoints[$mountType]) ||
527
			!isset($mountPoints[$mountType][$applicable]) ||
528
			!isset($mountPoints[$mountType][$applicable][$mountPoint]);
529
		$mountPoints = self::mergeMountPoints($mountPoints, $mount, $mountType);
530
531
		// Set default priority if none set
532
		if (!isset($mountPoints[$mountType][$applicable][$mountPoint]['priority'])) {
533
			if (isset($backends[$class]['priority'])) {
534
				$mountPoints[$mountType][$applicable][$mountPoint]['priority']
535
					= $backends[$class]['priority'];
536
			} else {
537
				$mountPoints[$mountType][$applicable][$mountPoint]['priority']
538
					= 100;
539
			}
540
		}
541
542
		self::writeData($isPersonal ? OCP\User::getUser() : null, $mountPoints);
543
544
		$result = self::getBackendStatus($class, $classOptions, $isPersonal);
545
		if ($result && $isNew) {
546
			\OC_Hook::emit(
547
				\OC\Files\Filesystem::CLASSNAME,
548
				\OC\Files\Filesystem::signal_create_mount,
549
				array(
550
					\OC\Files\Filesystem::signal_param_path => $relMountPoint,
551
					\OC\Files\Filesystem::signal_param_mount_type => $mountType,
552
					\OC\Files\Filesystem::signal_param_users => $applicable,
553
				)
554
			);
555
		}
556
		return $result;
557
	}
558
559
	/**
560
	 *
561
	 * @param string $mountPoint Mount point
562
	 * @param string $mountType MOUNT_TYPE_GROUP | MOUNT_TYPE_USER
563
	 * @param string $applicable User or group to remove mount from
564
	 * @param bool $isPersonal Personal or system mount point
565
	 * @return bool
566
	 */
567
	public static function removeMountPoint($mountPoint, $mountType, $applicable, $isPersonal = false) {
568
		// Verify that the mount point applies for the current user
569
		$relMountPoints = $mountPoint;
570
		if ($isPersonal) {
571
			if ($applicable != OCP\User::getUser()) {
572
				return false;
573
			}
574
			$mountPoint = '/' . $applicable . '/files/' . ltrim($mountPoint, '/');
575
		} else {
576
			$mountPoint = '/$user/files/' . ltrim($mountPoint, '/');
577
		}
578
		$mountPoint = \OC\Files\Filesystem::normalizePath($mountPoint);
579
		$mountPoints = self::readData($isPersonal ? OCP\User::getUser() : null);
580
		// Remove mount point
581
		unset($mountPoints[$mountType][$applicable][$mountPoint]);
582
		// Unset parent arrays if empty
583
		if (empty($mountPoints[$mountType][$applicable])) {
584
			unset($mountPoints[$mountType][$applicable]);
585
			if (empty($mountPoints[$mountType])) {
586
				unset($mountPoints[$mountType]);
587
			}
588
		}
589
		self::writeData($isPersonal ? OCP\User::getUser() : null, $mountPoints);
590
		\OC_Hook::emit(
591
			\OC\Files\Filesystem::CLASSNAME,
592
			\OC\Files\Filesystem::signal_delete_mount,
593
			array(
594
				\OC\Files\Filesystem::signal_param_path => $relMountPoints,
595
				\OC\Files\Filesystem::signal_param_mount_type => $mountType,
596
				\OC\Files\Filesystem::signal_param_users => $applicable,
597
			)
598
		);
599
		return true;
600
	}
601
602
	/**
603
	 *
604
	 * @param string $mountPoint Mount point
605
	 * @param string $target The new mount point
606
	 * @param string $mountType MOUNT_TYPE_GROUP | MOUNT_TYPE_USER
607
	 * @return bool
608
	 */
609
	public static function movePersonalMountPoint($mountPoint, $target, $mountType) {
610
		$mountPoint = rtrim($mountPoint, '/');
611
		$user = OCP\User::getUser();
612
		$mountPoints = self::readData($user);
613
		if (!isset($mountPoints[$mountType][$user][$mountPoint])) {
614
			return false;
615
		}
616
		$mountPoints[$mountType][$user][$target] = $mountPoints[$mountType][$user][$mountPoint];
617
		// Remove old mount point
618
		unset($mountPoints[$mountType][$user][$mountPoint]);
619
620
		self::writeData($user, $mountPoints);
621
		return true;
622
	}
623
624
	/**
625
	 * Read the mount points in the config file into an array
626
	 *
627
	 * @param string|null $user If not null, personal for $user, otherwise system
628
	 * @return array
629
	 */
630
	private static function readData($user = null) {
631
		$parser = new \OC\ArrayParser();
632
		if (isset($user)) {
633
			$phpFile = OC_User::getHome($user) . '/mount.php';
634
			$jsonFile = OC_User::getHome($user) . '/mount.json';
635 View Code Duplication
		} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
636
			$phpFile = OC::$SERVERROOT . '/config/mount.php';
637
			$datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data/');
638
			$jsonFile = \OC_Config::getValue('mount_file', $datadir . '/mount.json');
639
		}
640
		if (is_file($jsonFile)) {
641
			$mountPoints = json_decode(file_get_contents($jsonFile), true);
642
			if (is_array($mountPoints)) {
643
				return $mountPoints;
644
			}
645
		} elseif (is_file($phpFile)) {
646
			$mountPoints = $parser->parsePHP(file_get_contents($phpFile));
647
			if (is_array($mountPoints)) {
648
				return $mountPoints;
649
			}
650
		}
651
		return array();
652
	}
653
654
	/**
655
	 * Write the mount points to the config file
656
	 *
657
	 * @param string|null $user If not null, personal for $user, otherwise system
658
	 * @param array $data Mount points
659
	 */
660
	private static function writeData($user, $data) {
661 View Code Duplication
		if (isset($user)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
662
			$file = OC_User::getHome($user) . '/mount.json';
663
		} else {
664
			$datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data/');
665
			$file = \OC_Config::getValue('mount_file', $datadir . '/mount.json');
666
		}
667
668
		foreach ($data as &$applicables) {
669
			foreach ($applicables as &$mountPoints) {
670
				foreach ($mountPoints as &$options) {
671
					self::addStorageId($options);
672
				}
673
			}
674
		}
675
676
		$content = json_encode($data, JSON_PRETTY_PRINT);
677
		@file_put_contents($file, $content);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
678
		@chmod($file, 0640);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
679
	}
680
681
	/**
682
	 * check dependencies
683
	 */
684
	public static function checkDependencies() {
685
		$dependencies = array();
686
		foreach (OC_Mount_Config::$backends as $class => $backend) {
687
			if (isset($backend['has_dependencies']) and $backend['has_dependencies'] === true) {
688
				$result = $class::checkDependencies();
689
				if ($result !== true) {
690
					if (!is_array($result)) {
691
						$result = array($result);
692
					}
693
					foreach ($result as $key => $value) {
694
						if (is_numeric($key)) {
695
							OC_Mount_Config::addDependency($dependencies, $value, $backend['backend']);
696
						} else {
697
							OC_Mount_Config::addDependency($dependencies, $key, $backend['backend'], $value);
698
						}
699
					}
700
				}
701
			}
702
		}
703
704
		if (count($dependencies) > 0) {
705
			return OC_Mount_Config::generateDependencyMessage($dependencies);
706
		}
707
		return '';
708
	}
709
710
	private static function addDependency(&$dependencies, $module, $backend, $message = null) {
711
		if (!isset($dependencies[$module])) {
712
			$dependencies[$module] = array();
713
		}
714
715
		if ($message === null) {
716
			$dependencies[$module][] = $backend;
717
		} else {
718
			$dependencies[$module][] = array('backend' => $backend, 'message' => $message);
719
		}
720
	}
721
722
	private static function generateDependencyMessage($dependencies) {
723
		$l = new \OC_L10N('files_external');
724
		$dependencyMessage = '';
725
		foreach ($dependencies as $module => $backends) {
726
			$dependencyGroup = array();
727
			foreach ($backends as $backend) {
728
				if (is_array($backend)) {
729
					$dependencyMessage .= '<br />' . $l->t('<b>Note:</b> ') . $backend['message'];
730
				} else {
731
					$dependencyGroup[] = $backend;
732
				}
733
			}
734
735
			$dependencyGroupCount = count($dependencyGroup);
736
			if ($dependencyGroupCount > 0) {
737
				$backends = '';
738
				for ($i = 0; $i < $dependencyGroupCount; $i++) {
739
					if ($i > 0 && $i === $dependencyGroupCount - 1) {
740
						$backends .= ' ' . $l->t('and') . ' ';
741
					} elseif ($i > 0) {
742
						$backends .= ', ';
743
					}
744
					$backends .= '<i>' . $dependencyGroup[$i] . '</i>';
745
				}
746
				$dependencyMessage .= '<br />' . OC_Mount_Config::getSingleDependencyMessage($l, $module, $backends);
747
			}
748
		}
749
		return $dependencyMessage;
750
	}
751
752
	/**
753
	 * Returns a dependency missing message
754
	 *
755
	 * @param OC_L10N $l
756
	 * @param string $module
757
	 * @param string $backend
758
	 * @return string
759
	 */
760
	private static function getSingleDependencyMessage(OC_L10N $l, $module, $backend) {
761
		switch (strtolower($module)) {
762
			case 'curl':
763
				return $l->t('<b>Note:</b> 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);
0 ignored issues
show
Documentation introduced by
$backend is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
764
			case 'ftp':
765
				return $l->t('<b>Note:</b> 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);
0 ignored issues
show
Documentation introduced by
$backend is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
766
			default:
767
				return $l->t('<b>Note:</b> "%s" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it.', array($module, $backend));
768
		}
769
	}
770
771
	/**
772
	 * Encrypt passwords in the given config options
773
	 *
774
	 * @param array $options mount options
775
	 * @return array updated options
776
	 */
777 View Code Duplication
	private static function encryptPasswords($options) {
778
		if (isset($options['password'])) {
779
			$options['password_encrypted'] = self::encryptPassword($options['password']);
780
			// do not unset the password, we want to keep the keys order
781
			// on load... because that's how the UI currently works
782
			$options['password'] = '';
783
		}
784
		return $options;
785
	}
786
787
	/**
788
	 * Decrypt passwords in the given config options
789
	 *
790
	 * @param array $options mount options
791
	 * @return array updated options
792
	 */
793 View Code Duplication
	private static function decryptPasswords($options) {
794
		// note: legacy options might still have the unencrypted password in the "password" field
795
		if (isset($options['password_encrypted'])) {
796
			$options['password'] = self::decryptPassword($options['password_encrypted']);
797
			unset($options['password_encrypted']);
798
		}
799
		return $options;
800
	}
801
802
	/**
803
	 * Encrypt a single password
804
	 *
805
	 * @param string $password plain text password
806
	 * @return string encrypted password
807
	 */
808
	private static function encryptPassword($password) {
809
		$cipher = self::getCipher();
810
		$iv = \OCP\Util::generateRandomBytes(16);
811
		$cipher->setIV($iv);
812
		return base64_encode($iv . $cipher->encrypt($password));
813
	}
814
815
	/**
816
	 * Decrypts a single password
817
	 *
818
	 * @param string $encryptedPassword encrypted password
819
	 * @return string plain text password
820
	 */
821
	private static function decryptPassword($encryptedPassword) {
822
		$cipher = self::getCipher();
823
		$binaryPassword = base64_decode($encryptedPassword);
824
		$iv = substr($binaryPassword, 0, 16);
825
		$cipher->setIV($iv);
826
		$binaryPassword = substr($binaryPassword, 16);
827
		return $cipher->decrypt($binaryPassword);
828
	}
829
830
	/**
831
	 * Merges mount points
832
	 *
833
	 * @param array $data Existing mount points
834
	 * @param array $mountPoint New mount point
835
	 * @param string $mountType
836
	 * @return array
837
	 */
838
	private static function mergeMountPoints($data, $mountPoint, $mountType) {
839
		$applicable = key($mountPoint);
840
		$mountPath = key($mountPoint[$applicable]);
841
		if (isset($data[$mountType])) {
842
			if (isset($data[$mountType][$applicable])) {
843
				// Merge priorities
844
				if (isset($data[$mountType][$applicable][$mountPath])
845
					&& isset($data[$mountType][$applicable][$mountPath]['priority'])
846
					&& !isset($mountPoint[$applicable][$mountPath]['priority'])
847
				) {
848
					$mountPoint[$applicable][$mountPath]['priority']
849
						= $data[$mountType][$applicable][$mountPath]['priority'];
850
				}
851
				// Persistent objectstore
852
				if (isset($data[$mountType][$applicable][$mountPath])
853
					&& isset($data[$mountType][$applicable][$mountPath]['objectstore'])
854
				) {
855
					$mountPoint[$applicable][$mountPath]['objectstore']
856
						= $data[$mountType][$applicable][$mountPath]['objectstore'];
857
				}
858
				$data[$mountType][$applicable]
859
					= array_merge($data[$mountType][$applicable], $mountPoint[$applicable]);
860
			} else {
861
				$data[$mountType] = array_merge($data[$mountType], $mountPoint);
862
			}
863
		} else {
864
			$data[$mountType] = $mountPoint;
865
		}
866
		return $data;
867
	}
868
869
	/**
870
	 * Returns the encryption cipher
871
	 */
872
	private static function getCipher() {
873
		if (!class_exists('Crypt_AES', false)) {
874
			include('Crypt/AES.php');
875
		}
876
		$cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
877
		$cipher->setKey(\OC::$server->getConfig()->getSystemValue('passwordsalt', null));
878
		return $cipher;
879
	}
880
881
	/**
882
	 * Computes a hash based on the given configuration.
883
	 * This is mostly used to find out whether configurations
884
	 * are the same.
885
	 */
886
	private static function makeConfigHash($config) {
887
		$data = json_encode(
888
			array(
889
				'c' => $config['class'],
890
				'm' => $config['mountpoint'],
891
				'o' => $config['options']
892
			)
893
		);
894
		return hash('md5', $data);
895
	}
896
897
	/**
898
	 * Add storage id to the storage configurations that did not have any.
899
	 *
900
	 * @param string $user user for which to process storage configs
901
	 */
902
	private static function addStorageIdToConfig($user) {
903
		$config = self::readData($user);
904
905
		$needUpdate = false;
906
		foreach ($config as &$applicables) {
907
			foreach ($applicables as &$mountPoints) {
908
				foreach ($mountPoints as &$options) {
909
					$needUpdate |= !isset($options['storage_id']);
910
				}
911
			}
912
		}
913
914
		if ($needUpdate) {
915
			self::writeData($user, $config);
916
		}
917
	}
918
919
	/**
920
	 * Get storage id from the numeric storage id and set
921
	 * it into the given options argument. Only do this
922
	 * if there was no storage id set yet.
923
	 *
924
	 * This might also fail if a storage wasn't fully configured yet
925
	 * and couldn't be mounted, in which case this will simply return false.
926
	 *
927
	 * @param array $options storage options
928
	 *
929
	 * @return bool true if the storage id was added, false otherwise
930
	 */
931
	private static function addStorageId(&$options) {
932
		if (isset($options['storage_id'])) {
933
			return false;
934
		}
935
936
		$class = $options['class'];
937
		try {
938
			/** @var \OC\Files\Storage\Storage $storage */
939
			$storage = new $class($options['options']);
940
			// TODO: introduce StorageConfigException
941
		} catch (\Exception $e) {
942
			// storage might not be fully configured yet (ex: Dropbox)
943
			// note that storage instances aren't supposed to open any connections
944
			// in the constructor, so this exception is likely to be a config exception
945
			return false;
946
		}
947
948
		$options['storage_id'] = $storage->getCache()->getNumericStorageId();
949
		return true;
950
	}
951
}
952