Passed
Push — master ( 5cdc85...37718d )
by Morris
38:53 queued 21:57
created

OC_Mount_Config::prepareMountPointEntry()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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

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

232
			$option = self::setUserVars(/** @scrutinizer ignore-deprecated */ OCP\User::getUser(), $option);

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

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

Loading history...
233
		}
234
		if (class_exists($class)) {
235
			try {
236
				/** @var \OC\Files\Storage\Common $storage */
237
				$storage = new $class($options);
238
239
				try {
240
					$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

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