Passed
Push — master ( fbf133...114af0 )
by Robin
14:48 queued 11s
created

MountConfig::readData()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 6
nop 1
dl 0
loc 15
rs 9.9
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 Arthur Schiwon <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Björn Schießle <[email protected]>
9
 * @author Christoph Wurst <[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
namespace OCA\Files_External;
41
42
use OCA\Files_External\Config\IConfigHandler;
43
use OCA\Files_External\Config\UserContext;
44
use OCA\Files_External\Lib\Backend\Backend;
45
use OCA\Files_External\Service\BackendService;
46
use OCA\Files_External\Service\GlobalStoragesService;
47
use OCA\Files_External\Service\UserGlobalStoragesService;
48
use OCA\Files_External\Service\UserStoragesService;
49
use OCP\Files\StorageNotAvailableException;
50
use OCP\IUser;
51
use phpseclib\Crypt\AES;
52
53
/**
54
 * Class to configure mount.json globally and for users
55
 */
56
class MountConfig {
57
	// TODO: make this class non-static and give it a proper namespace
58
59
	public const MOUNT_TYPE_GLOBAL = 'global';
60
	public const MOUNT_TYPE_GROUP = 'group';
61
	public const MOUNT_TYPE_USER = 'user';
62
	public const MOUNT_TYPE_PERSONAL = 'personal';
63
64
	// whether to skip backend test (for unit tests, as this static class is not mockable)
65
	public static $skipTest = false;
66
67
	/** @var UserGlobalStoragesService */
68
	private $userGlobalStorageService;
69
	/** @var UserStoragesService */
70
	private $userStorageService;
71
	/** @var GlobalStoragesService */
72
	private $globalStorageService;
73
74
	public function __construct(
75
		UserGlobalStoragesService $userGlobalStorageService,
76
		UserStoragesService $userStorageService,
77
		GlobalStoragesService $globalStorageService
78
	) {
79
		$this->userGlobalStorageService = $userGlobalStorageService;
80
		$this->userStorageService = $userStorageService;
81
		$this->globalStorageService = $globalStorageService;
82
	}
83
84
	/**
85
	 * @param mixed $input
86
	 * @param string|null $userId
87
	 * @return mixed
88
	 * @throws \OCP\AppFramework\QueryException
89
	 * @since 16.0.0
90
	 */
91
	public static function substitutePlaceholdersInConfig($input, string $userId = null) {
92
		/** @var BackendService $backendService */
93
		$backendService = \OC::$server->get(BackendService::class);
94
		/** @var IConfigHandler[] $handlers */
95
		$handlers = $backendService->getConfigHandlers();
96
		foreach ($handlers as $handler) {
97
			if ($handler instanceof UserContext && $userId !== null) {
98
				$handler->setUserId($userId);
99
			}
100
			$input = $handler->handle($input);
101
		}
102
		return $input;
103
	}
104
105
	/**
106
	 * Test connecting using the given backend configuration
107
	 *
108
	 * @param string $class backend class name
109
	 * @param array $options backend configuration options
110
	 * @param boolean $isPersonal
111
	 * @return int see self::STATUS_*
112
	 * @throws \Exception
113
	 */
114
	public static function getBackendStatus($class, $options, $isPersonal, $testOnly = true) {
115
		if (self::$skipTest) {
116
			return StorageNotAvailableException::STATUS_SUCCESS;
117
		}
118
		foreach ($options as $key => &$option) {
119
			if ($key === 'password') {
120
				// no replacements in passwords
121
				continue;
122
			}
123
			$option = self::substitutePlaceholdersInConfig($option);
124
		}
125
		if (class_exists($class)) {
126
			try {
127
				/** @var \OC\Files\Storage\Common $storage */
128
				$storage = new $class($options);
129
130
				try {
131
					$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

131
					/** @scrutinizer ignore-call */ 
132
     $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...
132
					$storage->setAvailability($result);
133
					if ($result) {
134
						return StorageNotAvailableException::STATUS_SUCCESS;
135
					}
136
				} catch (\Exception $e) {
137
					$storage->setAvailability(false);
138
					throw $e;
139
				}
140
			} catch (\Exception $exception) {
141
				\OC::$server->getLogger()->logException($exception, ['app' => 'files_external']);
142
				throw $exception;
143
			}
144
		}
145
		return StorageNotAvailableException::STATUS_ERROR;
146
	}
147
148
	/**
149
	 * Get backend dependency message
150
	 * TODO: move into AppFramework along with templates
151
	 *
152
	 * @param Backend[] $backends
153
	 * @return string
154
	 */
155
	public static function dependencyMessage($backends) {
156
		$l = \OC::$server->getL10N('files_external');
157
		$message = '';
158
		$dependencyGroups = [];
159
160
		foreach ($backends as $backend) {
161
			foreach ($backend->checkDependencies() as $dependency) {
162
				if ($message = $dependency->getMessage()) {
163
					$message .= '<p>' . $message . '</p>';
164
				} else {
165
					$dependencyGroups[$dependency->getDependency()][] = $backend;
166
				}
167
			}
168
		}
169
170
		foreach ($dependencyGroups as $module => $dependants) {
171
			$backends = implode(', ', array_map(function ($backend) {
172
				return '"' . $backend->getText() . '"';
173
			}, $dependants));
174
			$message .= '<p>' . MountConfig::getSingleDependencyMessage($l, $module, $backends) . '</p>';
175
		}
176
177
		return $message;
178
	}
179
180
	/**
181
	 * Returns a dependency missing message
182
	 *
183
	 * @param \OCP\IL10N $l
184
	 * @param string $module
185
	 * @param string $backend
186
	 * @return string
187
	 */
188
	private static function getSingleDependencyMessage(\OCP\IL10N $l, $module, $backend) {
189
		switch (strtolower($module)) {
190
			case 'curl':
191
				return $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]);
192
			case 'ftp':
193
				return $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]);
194
			default:
195
				return $l->t('"%1$s" is not installed. Mounting of %2$s is not possible. Please ask your system administrator to install it.', [$module, $backend]);
196
		}
197
	}
198
199
	/**
200
	 * Encrypt passwords in the given config options
201
	 *
202
	 * @param array $options mount options
203
	 * @return array updated options
204
	 */
205
	public static function encryptPasswords($options) {
206
		if (isset($options['password'])) {
207
			$options['password_encrypted'] = self::encryptPassword($options['password']);
208
			// do not unset the password, we want to keep the keys order
209
			// on load... because that's how the UI currently works
210
			$options['password'] = '';
211
		}
212
		return $options;
213
	}
214
215
	/**
216
	 * Decrypt passwords in the given config options
217
	 *
218
	 * @param array $options mount options
219
	 * @return array updated options
220
	 */
221
	public static function decryptPasswords($options) {
222
		// note: legacy options might still have the unencrypted password in the "password" field
223
		if (isset($options['password_encrypted'])) {
224
			$options['password'] = self::decryptPassword($options['password_encrypted']);
225
			unset($options['password_encrypted']);
226
		}
227
		return $options;
228
	}
229
230
	/**
231
	 * Encrypt a single password
232
	 *
233
	 * @param string $password plain text password
234
	 * @return string encrypted password
235
	 */
236
	private static function encryptPassword($password) {
237
		$cipher = self::getCipher();
238
		$iv = \OC::$server->getSecureRandom()->generate(16);
239
		$cipher->setIV($iv);
240
		return base64_encode($iv . $cipher->encrypt($password));
241
	}
242
243
	/**
244
	 * Decrypts a single password
245
	 *
246
	 * @param string $encryptedPassword encrypted password
247
	 * @return string plain text password
248
	 */
249
	private static function decryptPassword($encryptedPassword) {
250
		$cipher = self::getCipher();
251
		$binaryPassword = base64_decode($encryptedPassword);
252
		$iv = substr($binaryPassword, 0, 16);
253
		$cipher->setIV($iv);
254
		$binaryPassword = substr($binaryPassword, 16);
255
		return $cipher->decrypt($binaryPassword);
256
	}
257
258
	/**
259
	 * Returns the encryption cipher
260
	 *
261
	 * @return AES
262
	 */
263
	private static function getCipher() {
264
		$cipher = new AES(AES::MODE_CBC);
265
		$cipher->setKey(\OC::$server->getConfig()->getSystemValue('passwordsalt', null));
266
		return $cipher;
267
	}
268
269
	/**
270
	 * Computes a hash based on the given configuration.
271
	 * This is mostly used to find out whether configurations
272
	 * are the same.
273
	 *
274
	 * @param array $config
275
	 * @return string
276
	 */
277
	public static function makeConfigHash($config) {
278
		$data = json_encode(
279
			[
280
				'c' => $config['backend'],
281
				'a' => $config['authMechanism'],
282
				'm' => $config['mountpoint'],
283
				'o' => $config['options'],
284
				'p' => isset($config['priority']) ? $config['priority'] : -1,
285
				'mo' => isset($config['mountOptions']) ? $config['mountOptions'] : [],
286
			]
287
		);
288
		return hash('md5', $data);
289
	}
290
}
291