Completed
Pull Request — master (#26700)
by Philipp
08:19
created

apps/files_external/lib/config.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * @author Andreas Fischer <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Björn Schießle <[email protected]>
6
 * @author Frank Karlitschek <[email protected]>
7
 * @author Jesús Macias <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Juan Pablo Villafáñez <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Michael Gapczynski <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Philipp Kapfer <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Robin McCorkell <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 * @author Vincent Petry <[email protected]>
18
 *
19
 * @copyright Copyright (c) 2016, ownCloud GmbH.
20
 * @license AGPL-3.0
21
 *
22
 * This code is free software: you can redistribute it and/or modify
23
 * it under the terms of the GNU Affero General Public License, version 3,
24
 * as published by the Free Software Foundation.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU Affero General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU Affero General Public License, version 3,
32
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
33
 *
34
 */
35
36
use phpseclib\Crypt\AES;
37
use \OCA\Files_External\AppInfo\Application;
38
use \OCA\Files_External\Lib\Backend\LegacyBackend;
39
use \OCA\Files_External\Lib\StorageConfig;
40
use \OCA\Files_External\Lib\Backend\Backend;
41
use \OCP\Files\StorageNotAvailableException;
42
43
/**
44
 * Class to configure mount.json globally and for users
45
 */
46
class OC_Mount_Config {
47
	// TODO: make this class non-static and give it a proper namespace
48
49
	const MOUNT_TYPE_GLOBAL = 'global';
50
	const MOUNT_TYPE_GROUP = 'group';
51
	const MOUNT_TYPE_USER = 'user';
52
	const MOUNT_TYPE_PERSONAL = 'personal';
53
54
	// whether to skip backend test (for unit tests, as this static class is not mockable)
55
	public static $skipTest = false;
56
57
	/** @var Application */
58
	public static $app;
59
60
	/**
61
	 * @param string $class
62
	 * @param array $definition
63
	 * @return bool
64
	 * @deprecated 8.2.0 use \OCA\Files_External\Service\BackendService::registerBackend()
65
	 */
66
	public static function registerBackend($class, $definition) {
67
		$backendService = self::$app->getContainer()->query('OCA\Files_External\Service\BackendService');
68
		$auth = self::$app->getContainer()->query('OCA\Files_External\Lib\Auth\Builtin');
69
70
		$backendService->registerBackend(new LegacyBackend($class, $definition, $auth));
71
72
		return true;
73
	}
74
75
	/**
76
	 * Returns the mount points for the given user.
77
	 * The mount point is relative to the data directory.
78
	 *
79
	 * @param string $uid user
80
	 * @return array of mount point string as key, mountpoint config as value
81
	 *
82
	 * @deprecated 8.2.0 use UserGlobalStoragesService::getStorages() and UserStoragesService::getStorages()
83
	 */
84
	public static function getAbsoluteMountPoints($uid) {
85
		$mountPoints = [];
86
87
		$userGlobalStoragesService = self::$app->getContainer()->query('OCA\Files_External\Service\UserGlobalStoragesService');
88
		$userStoragesService = self::$app->getContainer()->query('OCA\Files_External\Service\UserStoragesService');
89
		$user = self::$app->getContainer()->query('OCP\IUserManager')->get($uid);
90
91
		$userGlobalStoragesService->setUser($user);
92
		$userStoragesService->setUser($user);
93
94 View Code Duplication
		foreach ($userGlobalStoragesService->getStorages() as $storage) {
95
			/** @var \OCA\Files_External\Lib\StorageConfig $storage */
96
			$mountPoint = '/'.$uid.'/files'.$storage->getMountPoint();
97
			$mountEntry = self::prepareMountPointEntry($storage, false);
98
			foreach ($mountEntry['options'] as &$option) {
99
				$option = self::setUserVars($uid, $option);
100
			}
101
			$mountPoints[$mountPoint] = $mountEntry;
102
		}
103
104 View Code Duplication
		foreach ($userStoragesService->getStorages() as $storage) {
105
			$mountPoint = '/'.$uid.'/files'.$storage->getMountPoint();
106
			$mountEntry = self::prepareMountPointEntry($storage, true);
107
			foreach ($mountEntry['options'] as &$option) {
108
				$option = self::setUserVars($uid, $option);
109
			}
110
			$mountPoints[$mountPoint] = $mountEntry;
111
		}
112
113
		$userGlobalStoragesService->resetUser();
114
		$userStoragesService->resetUser();
115
116
		return $mountPoints;
117
	}
118
119
	/**
120
	 * Get the system mount points
121
	 *
122
	 * @return array
123
	 *
124
	 * @deprecated 8.2.0 use GlobalStoragesService::getStorages()
125
	 */
126 View Code Duplication
	public static function getSystemMountPoints() {
127
		$mountPoints = [];
128
		$service = self::$app->getContainer()->query('OCA\Files_External\Service\GlobalStoragesService');
129
130
		foreach ($service->getStorages() as $storage) {
131
			$mountPoints[] = self::prepareMountPointEntry($storage, false);
132
		}
133
134
		return $mountPoints;
135
	}
136
137
	/**
138
	 * Get the personal mount points of the current user
139
	 *
140
	 * @return array
141
	 *
142
	 * @deprecated 8.2.0 use UserStoragesService::getStorages()
143
	 */
144 View Code Duplication
	public static function getPersonalMountPoints() {
145
		$mountPoints = [];
146
		$service = self::$app->getContainer()->query('OCA\Files_External\Service\UserStoragesService');
147
148
		foreach ($service->getStorages() as $storage) {
149
			$mountPoints[] = self::prepareMountPointEntry($storage, true);
150
		}
151
152
		return $mountPoints;
153
	}
154
155
	/**
156
	 * Convert a StorageConfig to the legacy mountPoints array format
157
	 * There's a lot of extra information in here, to satisfy all of the legacy functions
158
	 *
159
	 * @param StorageConfig $storage
160
	 * @param bool $isPersonal
161
	 * @return array
162
	 */
163
	private static function prepareMountPointEntry(StorageConfig $storage, $isPersonal) {
164
		$mountEntry = [];
165
166
		$mountEntry['mountpoint'] = substr($storage->getMountPoint(), 1); // remove leading slash
167
		$mountEntry['class'] = $storage->getBackend()->getIdentifier();
168
		$mountEntry['backend'] = $storage->getBackend()->getText();
169
		$mountEntry['authMechanism'] = $storage->getAuthMechanism()->getIdentifier();
170
		$mountEntry['personal'] = $isPersonal;
171
		$mountEntry['options'] = self::decryptPasswords($storage->getBackendOptions());
172
		$mountEntry['mountOptions'] = $storage->getMountOptions();
173
		$mountEntry['priority'] = $storage->getPriority();
174
		$mountEntry['applicable'] = [
175
			'groups' => $storage->getApplicableGroups(),
176
			'users' => $storage->getApplicableUsers(),
177
		];
178
		// if mountpoint is applicable to all users the old API expects ['all']
179
		if (empty($mountEntry['applicable']['groups']) && empty($mountEntry['applicable']['users'])) {
180
			$mountEntry['applicable']['users'] = ['all'];
181
		}
182
183
		$mountEntry['id'] = $storage->getId();
184
185
		return $mountEntry;
186
	}
187
188
	/**
189
	 * fill in the correct values for $user
190
	 *
191
	 * @param string $user user value
192
	 * @param string|array $input
193
	 * @return string
194
	 */
195
	public static function setUserVars($user, $input) {
196
		if (is_array($input)) {
197
			foreach ($input as $key => $value) {
198
				if (is_string($value)) {
199
					$input[$key] = str_replace('$user', $user, $value);
200
				}
201
			}
202
		} else {
203
			if (is_string($input)) {
204
				$input = str_replace('$user', $user, $input);
205
			}
206
		}
207
		return $input;
208
	}
209
210
	/**
211
	 * Test connecting using the given backend configuration
212
	 *
213
	 * @param string $class backend class name
214
	 * @param array $options backend configuration options
215
	 * @param boolean $isPersonal
216
	 * @return int see self::STATUS_*
217
	 * @throws Exception
218
	 */
219
	public static function getBackendStatus($class, $options, $isPersonal, $testOnly = true) {
220
		if (self::$skipTest) {
221
			return StorageNotAvailableException::STATUS_SUCCESS;
222
		}
223
		foreach ($options as $key => $option) {
224
			$options[$key] = self::setUserVars(OCP\User::getUser(), $option);
225
		}
226
		if (class_exists($class)) {
227
			try {
228
				/** @var \OC\Files\Storage\Common $storage */
229
				$storage = new $class($options);
230
231
				try {
232
					$result = $storage->test($isPersonal, $testOnly);
233
					$storage->setAvailability($result);
234
					if ($result) {
235
						return StorageNotAvailableException::STATUS_SUCCESS;
236
					}
237
				} catch (\Exception $e) {
238
					$storage->setAvailability(false);
239
					throw $e;
240
				}
241
			} catch (Exception $exception) {
242
				\OCP\Util::logException('files_external', $exception);
243
				throw $exception;
244
			}
245
		}
246
		return StorageNotAvailableException::STATUS_ERROR;
247
	}
248
249
	/**
250
	 * Read the mount points in the config file into an array
251
	 *
252
	 * @param string|null $user If not null, personal for $user, otherwise system
253
	 * @return array
254
	 */
255
	public static function readData($user = null) {
256
		if (isset($user)) {
257
			$jsonFile = \OC::$server->getUserManager()->get($user)->getHome() . '/mount.json';
258 View Code Duplication
		} else {
0 ignored issues
show
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...
259
			$config = \OC::$server->getConfig();
260
			$datadir = $config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/');
261
			$jsonFile = $config->getSystemValue('mount_file', $datadir . '/mount.json');
262
		}
263
		if (is_file($jsonFile)) {
264
			$mountPoints = json_decode(file_get_contents($jsonFile), true);
265
			if (is_array($mountPoints)) {
266
				return $mountPoints;
267
			}
268
		}
269
		return [];
270
	}
271
272
	/**
273
	 * Get backend dependency message
274
	 * TODO: move into AppFramework along with templates
275
	 *
276
	 * @param Backend[] $backends
277
	 * @return string
278
	 */
279
	public static function dependencyMessage($backends) {
280
		$l = \OC::$server->getL10N('files_external');
281
		$message = '';
282
		$dependencyGroups = [];
283
284
		foreach ($backends as $backend) {
285
			foreach ($backend->checkDependencies() as $dependency) {
286
				if ($message = $dependency->getMessage()) {
287
					$message .= '<br />' . $l->t('<b>Note:</b> ') . $message;
288
				} else {
289
					$dependencyGroups[$dependency->getDependency()][] = $backend;
290
				}
291
			}
292
		}
293
294
		foreach ($dependencyGroups as $module => $dependants) {
295
			$backends = implode(', ', array_map(function($backend) {
296
				return '<i>' . $backend->getText() . '</i>';
297
			}, $dependants));
298
			$message .= '<br />' . OC_Mount_Config::getSingleDependencyMessage($l, $module, $backends);
299
		}
300
301
		return $message;
302
	}
303
304
	/**
305
	 * Returns a dependency missing message
306
	 *
307
	 * @param \OCP\IL10N $l
308
	 * @param string $module
309
	 * @param string $backend
310
	 * @return string
311
	 */
312
	private static function getSingleDependencyMessage(\OCP\IL10N $l, $module, $backend) {
313
		switch (strtolower($module)) {
314
			case 'curl':
315
				return (string)$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);
316
			default:
317
				return (string)$l->t('<b>Note:</b> "%s" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it.', [$module, $backend]);
318
		}
319
	}
320
321
	/**
322
	 * Encrypt passwords in the given config options
323
	 *
324
	 * @param array $options mount options
325
	 * @return array updated options
326
	 */
327 View Code Duplication
	public static function encryptPasswords($options) {
328
		if (isset($options['password'])) {
329
			$options['password_encrypted'] = self::encryptPassword($options['password']);
330
			// do not unset the password, we want to keep the keys order
331
			// on load... because that's how the UI currently works
332
			$options['password'] = '';
333
		}
334
		return $options;
335
	}
336
337
	/**
338
	 * Decrypt passwords in the given config options
339
	 *
340
	 * @param array $options mount options
341
	 * @return array updated options
342
	 */
343 View Code Duplication
	public static function decryptPasswords($options) {
344
		// note: legacy options might still have the unencrypted password in the "password" field
345
		if (isset($options['password_encrypted'])) {
346
			$options['password'] = self::decryptPassword($options['password_encrypted']);
347
			unset($options['password_encrypted']);
348
		}
349
		return $options;
350
	}
351
352
	/**
353
	 * Encrypt a single password
354
	 *
355
	 * @param string $password plain text password
356
	 * @return string encrypted password
357
	 */
358
	private static function encryptPassword($password) {
359
		$cipher = self::getCipher();
360
		$iv = \OCP\Util::generateRandomBytes(16);
361
		$cipher->setIV($iv);
362
		return base64_encode($iv . $cipher->encrypt($password));
363
	}
364
365
	/**
366
	 * Decrypts a single password
367
	 *
368
	 * @param string $encryptedPassword encrypted password
369
	 * @return string plain text password
370
	 */
371
	private static function decryptPassword($encryptedPassword) {
372
		$cipher = self::getCipher();
373
		$binaryPassword = base64_decode($encryptedPassword);
374
		$iv = substr($binaryPassword, 0, 16);
375
		$cipher->setIV($iv);
376
		$binaryPassword = substr($binaryPassword, 16);
377
		return $cipher->decrypt($binaryPassword);
378
	}
379
380
	/**
381
	 * Returns the encryption cipher
382
	 *
383
	 * @return AES
384
	 */
385
	private static function getCipher() {
386
		$cipher = new AES(AES::MODE_CBC);
387
		$cipher->setKey(\OC::$server->getConfig()->getSystemValue('passwordsalt', null));
388
		return $cipher;
389
	}
390
391
	/**
392
	 * Computes a hash based on the given configuration.
393
	 * This is mostly used to find out whether configurations
394
	 * are the same.
395
	 *
396
	 * @param array $config
397
	 * @return string
398
	 */
399
	public static function makeConfigHash($config) {
400
		$data = json_encode(
401
			[
402
				'c' => $config['backend'],
403
				'a' => $config['authMechanism'],
404
				'm' => $config['mountpoint'],
405
				'o' => $config['options'],
406
				'p' => isset($config['priority']) ? $config['priority'] : -1,
407
				'mo' => isset($config['mountOptions']) ? $config['mountOptions'] : [],
408
			]
409
		);
410
		return hash('md5', $data);
411
	}
412
}
413