Completed
Push — master ( c38f87...350aa8 )
by Lukas
27s
created

Util   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 369
Duplicated Lines 0 %

Coupling/Cohesion

Components 4
Dependencies 15

Importance

Changes 0
Metric Value
dl 0
loc 369
rs 7.3333
c 0
b 0
f 0
wmc 49
lcom 4
cbo 15

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 18 1
A getEncryptionModuleId() 0 18 4
A createHeader() 0 18 4
A getAllFiles() 0 20 4
A isFile() 0 7 3
A getHeaderSize() 0 3 1
A getBlockSize() 0 3 1
A getUidAndFilename() 0 18 3
A stripPartialFileExtension() 0 20 3
B getUserWithAccessToMountPoint() 0 23 5
B isSystemWideMountPoint() 0 13 5
A isMountPointApplicableToUser() 0 15 4
C isExcluded() 0 31 7
A recoveryEnabled() 0 5 2
A setKeyStorageRoot() 0 3 1
A getKeyStorageRoot() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Util 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 Util, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Björn Schießle <[email protected]>
6
 * @author Jan-Christoph Borchardt <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Thomas Müller <[email protected]>
9
 *
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
26
namespace OC\Encryption;
27
28
use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException;
29
use OC\Encryption\Exceptions\EncryptionHeaderToLargeException;
30
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
31
use OC\Files\Filesystem;
32
use OC\Files\View;
33
use OCP\Encryption\IEncryptionModule;
34
use OCP\Files\Storage;
35
use OCP\IConfig;
36
37
class Util {
38
39
	const HEADER_START = 'HBEGIN';
40
	const HEADER_END = 'HEND';
41
	const HEADER_PADDING_CHAR = '-';
42
43
	const HEADER_ENCRYPTION_MODULE_KEY = 'oc_encryption_module';
44
45
	/**
46
	 * block size will always be 8192 for a PHP stream
47
	 * @see https://bugs.php.net/bug.php?id=21641
48
	 * @var integer
49
	 */
50
	protected $headerSize = 8192;
51
52
	/**
53
	 * block size will always be 8192 for a PHP stream
54
	 * @see https://bugs.php.net/bug.php?id=21641
55
	 * @var integer
56
	 */
57
	protected $blockSize = 8192;
58
59
	/** @var View */
60
	protected $rootView;
61
62
	/** @var array */
63
	protected $ocHeaderKeys;
64
65
	/** @var \OC\User\Manager */
66
	protected $userManager;
67
68
	/** @var IConfig */
69
	protected $config;
70
71
	/** @var array paths excluded from encryption */
72
	protected $excludedPaths;
73
74
	/** @var \OC\Group\Manager $manager */
75
	protected $groupManager;
76
77
	/**
78
	 *
79
	 * @param View $rootView
80
	 * @param \OC\User\Manager $userManager
81
	 * @param \OC\Group\Manager $groupManager
82
	 * @param IConfig $config
83
	 */
84
	public function __construct(
85
		View $rootView,
86
		\OC\User\Manager $userManager,
87
		\OC\Group\Manager $groupManager,
88
		IConfig $config) {
89
90
		$this->ocHeaderKeys = [
91
			self::HEADER_ENCRYPTION_MODULE_KEY
92
		];
93
94
		$this->rootView = $rootView;
95
		$this->userManager = $userManager;
96
		$this->groupManager = $groupManager;
97
		$this->config = $config;
98
99
		$this->excludedPaths[] = 'files_encryption';
100
		$this->excludedPaths[] = 'appdata_' . $config->getSystemValue('instanceid', null);
101
	}
102
103
	/**
104
	 * read encryption module ID from header
105
	 *
106
	 * @param array $header
107
	 * @return string
108
	 * @throws ModuleDoesNotExistsException
109
	 */
110
	public function getEncryptionModuleId(array $header = null) {
111
		$id = '';
112
		$encryptionModuleKey = self::HEADER_ENCRYPTION_MODULE_KEY;
113
114
		if (isset($header[$encryptionModuleKey])) {
115
			$id = $header[$encryptionModuleKey];
116
		} elseif (isset($header['cipher'])) {
117
			if (class_exists('\OCA\Encryption\Crypto\Encryption')) {
118
				// fall back to default encryption if the user migrated from
119
				// ownCloud <= 8.0 with the old encryption
120
				$id = \OCA\Encryption\Crypto\Encryption::ID;
121
			} else {
122
				throw new ModuleDoesNotExistsException('Default encryption module missing');
123
			}
124
		}
125
126
		return $id;
127
	}
128
129
	/**
130
	 * create header for encrypted file
131
	 *
132
	 * @param array $headerData
133
	 * @param IEncryptionModule $encryptionModule
134
	 * @return string
135
	 * @throws EncryptionHeaderToLargeException if header has to many arguments
136
	 * @throws EncryptionHeaderKeyExistsException if header key is already in use
137
	 */
138
	public function createHeader(array $headerData, IEncryptionModule $encryptionModule) {
139
		$header = self::HEADER_START . ':' . self::HEADER_ENCRYPTION_MODULE_KEY . ':' . $encryptionModule->getId() . ':';
140
		foreach ($headerData as $key => $value) {
141
			if (in_array($key, $this->ocHeaderKeys)) {
142
				throw new EncryptionHeaderKeyExistsException($key);
143
			}
144
			$header .= $key . ':' . $value . ':';
145
		}
146
		$header .= self::HEADER_END;
147
148
		if (strlen($header) > $this->getHeaderSize()) {
149
			throw new EncryptionHeaderToLargeException();
150
		}
151
152
		$paddedHeader = str_pad($header, $this->headerSize, self::HEADER_PADDING_CHAR, STR_PAD_RIGHT);
153
154
		return $paddedHeader;
155
	}
156
157
	/**
158
	 * go recursively through a dir and collect all files and sub files.
159
	 *
160
	 * @param string $dir relative to the users files folder
161
	 * @return array with list of files relative to the users files folder
162
	 */
163
	public function getAllFiles($dir) {
164
		$result = array();
165
		$dirList = array($dir);
166
167
		while ($dirList) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dirList of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
168
			$dir = array_pop($dirList);
169
			$content = $this->rootView->getDirectoryContent($dir);
170
171
			foreach ($content as $c) {
172
				if ($c->getType() === 'dir') {
173
					$dirList[] = $c->getPath();
174
				} else {
175
					$result[] =  $c->getPath();
176
				}
177
			}
178
179
		}
180
181
		return $result;
182
	}
183
184
	/**
185
	 * check if it is a file uploaded by the user stored in data/user/files
186
	 * or a metadata file
187
	 *
188
	 * @param string $path relative to the data/ folder
189
	 * @return boolean
190
	 */
191
	public function isFile($path) {
192
		$parts = explode('/', Filesystem::normalizePath($path), 4);
193
		if (isset($parts[2]) && $parts[2] === 'files') {
194
			return true;
195
		}
196
		return false;
197
	}
198
199
	/**
200
	 * return size of encryption header
201
	 *
202
	 * @return integer
203
	 */
204
	public function getHeaderSize() {
205
		return $this->headerSize;
206
	}
207
208
	/**
209
	 * return size of block read by a PHP stream
210
	 *
211
	 * @return integer
212
	 */
213
	public function getBlockSize() {
214
		return $this->blockSize;
215
	}
216
217
	/**
218
	 * get the owner and the path for the file relative to the owners files folder
219
	 *
220
	 * @param string $path
221
	 * @return array
222
	 * @throws \BadMethodCallException
223
	 */
224
	public function getUidAndFilename($path) {
225
226
		$parts = explode('/', $path);
227
		$uid = '';
228
		if (count($parts) > 2) {
229
			$uid = $parts[1];
230
		}
231
		if (!$this->userManager->userExists($uid)) {
232
			throw new \BadMethodCallException(
233
				'path needs to be relative to the system wide data folder and point to a user specific file'
234
			);
235
		}
236
237
		$ownerPath = implode('/', array_slice($parts, 2));
238
239
		return array($uid, Filesystem::normalizePath($ownerPath));
240
241
	}
242
243
	/**
244
	 * Remove .path extension from a file path
245
	 * @param string $path Path that may identify a .part file
246
	 * @return string File path without .part extension
247
	 * @note this is needed for reusing keys
248
	 */
249
	public function stripPartialFileExtension($path) {
250
		$extension = pathinfo($path, PATHINFO_EXTENSION);
251
252
		if ( $extension === 'part') {
253
254
			$newLength = strlen($path) - 5; // 5 = strlen(".part")
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
255
			$fPath = substr($path, 0, $newLength);
256
257
			// if path also contains a transaction id, we remove it too
258
			$extension = pathinfo($fPath, PATHINFO_EXTENSION);
259
			if(substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId")
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
260
				$newLength = strlen($fPath) - strlen($extension) -1;
261
				$fPath = substr($fPath, 0, $newLength);
262
			}
263
			return $fPath;
264
265
		} else {
266
			return $path;
267
		}
268
	}
269
270
	public function getUserWithAccessToMountPoint($users, $groups) {
271
		$result = array();
272
		if (in_array('all', $users)) {
273
			$result = \OCP\User::getUsers();
0 ignored issues
show
Deprecated Code introduced by
The method OCP\User::getUsers() has been deprecated with message: 8.1.0 use method search() of \OCP\IUserManager - \OC::$server->getUserManager()

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

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

Loading history...
274
		} else {
275
			$result = array_merge($result, $users);
276
277
			$groupManager = \OC::$server->getGroupManager();
278
			foreach ($groups as $group) {
279
				$groupObject = $groupManager->get($group);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $groupObject is correct as $groupManager->get($group) (which targets OC\Group\Manager::get()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
280
				if ($groupObject) {
281
					$foundUsers = $groupObject->searchUsers('', -1, 0);
282
					$userIds = [];
283
					foreach ($foundUsers as $user) {
284
						$userIds[] = $user->getUID();
285
					}
286
					$result = array_merge($result, $userIds);
287
				}
288
			}
289
		}
290
291
		return $result;
292
	}
293
294
	/**
295
	 * check if the file is stored on a system wide mount point
296
	 * @param string $path relative to /data/user with leading '/'
297
	 * @param string $uid
298
	 * @return boolean
299
	 */
300
	public function isSystemWideMountPoint($path, $uid) {
301
		if (\OCP\App::isEnabled("files_external")) {
302
			$mounts = \OC_Mount_Config::getSystemMountPoints();
0 ignored issues
show
Deprecated Code introduced by
The method OC_Mount_Config::getSystemMountPoints() has been deprecated with message: 8.2.0 use GlobalStoragesService::getStorages()

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

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

Loading history...
303
			foreach ($mounts as $mount) {
304
				if (strpos($path, '/files/' . $mount['mountpoint']) === 0) {
305
					if ($this->isMountPointApplicableToUser($mount, $uid)) {
306
						return true;
307
					}
308
				}
309
			}
310
		}
311
		return false;
312
	}
313
314
	/**
315
	 * check if mount point is applicable to user
316
	 *
317
	 * @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups']
318
	 * @param string $uid
319
	 * @return boolean
320
	 */
321
	private function isMountPointApplicableToUser($mount, $uid) {
322
		$acceptedUids = array('all', $uid);
323
		// check if mount point is applicable for the user
324
		$intersection = array_intersect($acceptedUids, $mount['applicable']['users']);
325
		if (!empty($intersection)) {
326
			return true;
327
		}
328
		// check if mount point is applicable for group where the user is a member
329
		foreach ($mount['applicable']['groups'] as $gid) {
330
			if ($this->groupManager->isInGroup($uid, $gid)) {
331
				return true;
332
			}
333
		}
334
		return false;
335
	}
336
337
	/**
338
	 * check if it is a path which is excluded by ownCloud from encryption
339
	 *
340
	 * @param string $path
341
	 * @return boolean
342
	 */
343
	public function isExcluded($path) {
344
		$normalizedPath = Filesystem::normalizePath($path);
345
		$root = explode('/', $normalizedPath, 4);
346
		if (count($root) > 1) {
347
348
			// detect alternative key storage root
349
			$rootDir = $this->getKeyStorageRoot();
350
			if ($rootDir !== '' &&
351
				0 === strpos(
352
					Filesystem::normalizePath($path),
353
					Filesystem::normalizePath($rootDir)
354
				)
355
			) {
356
				return true;
357
			}
358
359
360
			//detect system wide folders
361
			if (in_array($root[1], $this->excludedPaths)) {
362
				return true;
363
			}
364
365
			// detect user specific folders
366
			if ($this->userManager->userExists($root[1])
367
				&& in_array($root[2], $this->excludedPaths)) {
368
369
				return true;
370
			}
371
		}
372
		return false;
373
	}
374
375
	/**
376
	 * check if recovery key is enabled for user
377
	 *
378
	 * @param string $uid
379
	 * @return boolean
380
	 */
381
	public function recoveryEnabled($uid) {
382
		$enabled = $this->config->getUserValue($uid, 'encryption', 'recovery_enabled', '0');
383
384
		return ($enabled === '1') ? true : false;
385
	}
386
387
	/**
388
	 * set new key storage root
389
	 *
390
	 * @param string $root new key store root relative to the data folder
391
	 */
392
	public function setKeyStorageRoot($root) {
393
		$this->config->setAppValue('core', 'encryption_key_storage_root', $root);
394
	}
395
396
	/**
397
	 * get key storage root
398
	 *
399
	 * @return string key storage root
400
	 */
401
	public function getKeyStorageRoot() {
402
		return $this->config->getAppValue('core', 'encryption_key_storage_root', '');
403
	}
404
405
}
406