Completed
Push — stable8.2 ( 3b3780...cb9d0b )
by Lukas
29s
created

Util::getBlockSize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1
Metric Value
dl 0
loc 3
ccs 1
cts 1
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Jan-Christoph Borchardt <[email protected]>
5
 * @author Joas Schilling <[email protected]>
6
 * @author Thomas Müller <[email protected]>
7
 *
8
 * @copyright Copyright (c) 2015, ownCloud, Inc.
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program 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 License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
namespace OC\Encryption;
26
27
use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException;
28
use OC\Encryption\Exceptions\EncryptionHeaderToLargeException;
29
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
30
use OC\Files\Filesystem;
31
use OC\Files\View;
32
use OCP\Encryption\IEncryptionModule;
33
use OCP\Files\Storage;
34
use OCP\IConfig;
35
36
class Util {
37
38
	const HEADER_START = 'HBEGIN';
39
	const HEADER_END = 'HEND';
40
	const HEADER_PADDING_CHAR = '-';
41
42
	const HEADER_ENCRYPTION_MODULE_KEY = 'oc_encryption_module';
43
44
	/**
45
	 * block size will always be 8192 for a PHP stream
46
	 * @see https://bugs.php.net/bug.php?id=21641
47
	 * @var integer
48
	 */
49
	protected $headerSize = 8192;
50
51
	/**
52
	 * block size will always be 8192 for a PHP stream
53
	 * @see https://bugs.php.net/bug.php?id=21641
54
	 * @var integer
55
	 */
56
	protected $blockSize = 8192;
57
58
	/** @var View */
59
	protected $rootView;
60
61
	/** @var array */
62
	protected $ocHeaderKeys;
63
64
	/** @var \OC\User\Manager */
65
	protected $userManager;
66
67
	/** @var IConfig */
68
	protected $config;
69
70
	/** @var array paths excluded from encryption */
71
	protected $excludedPaths;
72
73
	/** @var \OC\Group\Manager $manager */
74
	protected $groupManager;
75
76
	/**
77
	 *
78
	 * @param View $rootView
79
	 * @param \OC\User\Manager $userManager
80
	 * @param \OC\Group\Manager $groupManager
81
	 * @param IConfig $config
82
	 */
83
	public function __construct(
84
		View $rootView,
85 189
		\OC\User\Manager $userManager,
86
		\OC\Group\Manager $groupManager,
87
		IConfig $config) {
88
89
		$this->ocHeaderKeys = [
90
			self::HEADER_ENCRYPTION_MODULE_KEY
91 189
		];
92
93 189
		$this->rootView = $rootView;
94
		$this->userManager = $userManager;
95 189
		$this->groupManager = $groupManager;
96 189
		$this->config = $config;
97 189
98 189
		$this->excludedPaths[] = 'files_encryption';
99
	}
100 189
101 189
	/**
102
	 * read encryption module ID from header
103
	 *
104
	 * @param array $header
105
	 * @return string
106
	 * @throws ModuleDoesNotExistsException
107
	 */
108
	public function getEncryptionModuleId(array $header = null) {
109
		$id = '';
110 74
		$encryptionModuleKey = self::HEADER_ENCRYPTION_MODULE_KEY;
111 74
112 74
		if (isset($header[$encryptionModuleKey])) {
113
			$id = $header[$encryptionModuleKey];
114 74
		} elseif (isset($header['cipher'])) {
115 6
			if (class_exists('\OCA\Encryption\Crypto\Encryption')) {
116 74
				// fall back to default encryption if the user migrated from
117
				// ownCloud <= 8.0 with the old encryption
118
				$id = \OCA\Encryption\Crypto\Encryption::ID;
119
			} else {
120
				throw new ModuleDoesNotExistsException('Default encryption module missing');
121
			}
122
		}
123
124
		return $id;
125
	}
126 74
127
	/**
128
	 * create header for encrypted file
129
	 *
130
	 * @param array $headerData
131
	 * @param IEncryptionModule $encryptionModule
132
	 * @return string
133
	 * @throws EncryptionHeaderToLargeException if header has to many arguments
134
	 * @throws EncryptionHeaderKeyExistsException if header key is already in use
135
	 */
136
	public function createHeader(array $headerData, IEncryptionModule $encryptionModule) {
137
		$header = self::HEADER_START . ':' . self::HEADER_ENCRYPTION_MODULE_KEY . ':' . $encryptionModule->getId() . ':';
138 17
		foreach ($headerData as $key => $value) {
139 17
			if (in_array($key, $this->ocHeaderKeys)) {
140 17
				throw new EncryptionHeaderKeyExistsException($key);
141 7
			}
142 1
			$header .= $key . ':' . $value . ':';
143
		}
144 7
		$header .= self::HEADER_END;
145 17
146 16
		if (strlen($header) > $this->getHeaderSize()) {
147
			throw new EncryptionHeaderToLargeException();
148 16
		}
149
150
		$paddedHeader = str_pad($header, $this->headerSize, self::HEADER_PADDING_CHAR, STR_PAD_RIGHT);
151
152 16
		return $paddedHeader;
153
	}
154 16
155
	/**
156
	 * go recursively through a dir and collect all files and sub files.
157
	 *
158
	 * @param string $dir relative to the users files folder
159
	 * @return array with list of files relative to the users files folder
160
	 */
161
	public function getAllFiles($dir) {
162
		$result = array();
163
		$dirList = array($dir);
164
165
		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...
166
			$dir = array_pop($dirList);
167
			$content = $this->rootView->getDirectoryContent($dir);
168
169
			foreach ($content as $c) {
170
				if ($c->getType() === 'dir') {
171
					$dirList[] = $c->getPath();
172
				} else {
173
					$result[] =  $c->getPath();
174
				}
175
			}
176
177
		}
178
179
		return $result;
180
	}
181
182
	/**
183
	 * check if it is a file uploaded by the user stored in data/user/files
184
	 * or a metadata file
185
	 *
186
	 * @param string $path relative to the data/ folder
187
	 * @return boolean
188
	 */
189
	public function isFile($path) {
190
		$parts = explode('/', Filesystem::normalizePath($path), 4);
191 12
		if (isset($parts[2]) && $parts[2] === 'files') {
192 12
			return true;
193 12
		}
194 7
		return false;
195
	}
196 5
197
	/**
198
	 * return size of encryption header
199
	 *
200
	 * @return integer
201
	 */
202
	public function getHeaderSize() {
203
		return $this->headerSize;
204 72
	}
205 72
206
	/**
207
	 * return size of block read by a PHP stream
208
	 *
209
	 * @return integer
210
	 */
211
	public function getBlockSize() {
212
		return $this->blockSize;
213 14
	}
214 14
215
	/**
216
	 * get the owner and the path for the file relative to the owners files folder
217
	 *
218
	 * @param string $path
219
	 * @return array
220
	 * @throws \BadMethodCallException
221
	 */
222
	public function getUidAndFilename($path) {
223
224 5
		$parts = explode('/', $path);
225
		$uid = '';
226 5
		if (count($parts) > 2) {
227 5
			$uid = $parts[1];
228 5
		}
229 5
		if (!$this->userManager->userExists($uid)) {
230 5
			throw new \BadMethodCallException(
231 5
				'path needs to be relative to the system wide data folder and point to a user specific file'
232
			);
233
		}
234
235
		$ownerPath = implode('/', array_slice($parts, 2));
236
237 5
		return array($uid, Filesystem::normalizePath($ownerPath));
238
239 5
	}
240
241
	/**
242
	 * Remove .path extension from a file path
243
	 * @param string $path Path that may identify a .part file
244
	 * @return string File path without .part extension
245
	 * @note this is needed for reusing keys
246
	 */
247
	public function stripPartialFileExtension($path) {
248
		$extension = pathinfo($path, PATHINFO_EXTENSION);
249 75
250 75
		if ( $extension === 'part') {
251
252 75
			$newLength = strlen($path) - 5; // 5 = strlen(".part")
253
			$fPath = substr($path, 0, $newLength);
254 8
255 8
			// if path also contains a transaction id, we remove it too
256
			$extension = pathinfo($fPath, PATHINFO_EXTENSION);
257
			if(substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId")
258 8
				$newLength = strlen($fPath) - strlen($extension) -1;
259 8
				$fPath = substr($fPath, 0, $newLength);
260 7
			}
261 7
			return $fPath;
262 7
263 8
		} else {
264
			return $path;
265
		}
266 72
	}
267
268
	public function getUserWithAccessToMountPoint($users, $groups) {
269
		$result = array();
270
		if (in_array('all', $users)) {
271
			$result = \OCP\User::getUsers();
272
		} else {
273
			$result = array_merge($result, $users);
274
			foreach ($groups as $group) {
275
				$result = array_merge($result, \OC_Group::usersInGroup($group));
276
			}
277
		}
278
279
		return $result;
280
	}
281
282
	/**
283
	 * check if the file is stored on a system wide mount point
284
	 * @param string $path relative to /data/user with leading '/'
285
	 * @param string $uid
286
	 * @return boolean
287
	 */
288
	public function isSystemWideMountPoint($path, $uid) {
289
		if (\OCP\App::isEnabled("files_external")) {
290 5
			$mounts = \OC_Mount_Config::getSystemMountPoints();
291 5
			foreach ($mounts as $mount) {
292
				if (strpos($path, '/files/' . $mount['mountpoint']) === 0) {
293
					if ($this->isMountPointApplicableToUser($mount, $uid)) {
294
						return true;
295
					}
296
				}
297
			}
298
		}
299
		return false;
300
	}
301 5
302
	/**
303
	 * check if mount point is applicable to user
304
	 *
305
	 * @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups']
306
	 * @param string $uid
307
	 * @return boolean
308
	 */
309
	private function isMountPointApplicableToUser($mount, $uid) {
310
		$acceptedUids = array('all', $uid);
311
		// check if mount point is applicable for the user
312
		$intersection = array_intersect($acceptedUids, $mount['applicable']['users']);
313
		if (!empty($intersection)) {
314
			return true;
315
		}
316
		// check if mount point is applicable for group where the user is a member
317
		foreach ($mount['applicable']['groups'] as $gid) {
318
			if ($this->groupManager->isInGroup($uid, $gid)) {
319
				return true;
320
			}
321
		}
322
		return false;
323
	}
324
325
	/**
326
	 * check if it is a path which is excluded by ownCloud from encryption
327
	 *
328
	 * @param string $path
329
	 * @return boolean
330
	 */
331
	public function isExcluded($path) {
332
		$normalizedPath = Filesystem::normalizePath($path);
333 13
		$root = explode('/', $normalizedPath, 4);
334 13
		if (count($root) > 1) {
335 13
336 13
			// detect alternative key storage root
337
			$rootDir = $this->getKeyStorageRoot();
338
			if ($rootDir !== '' &&
339 13
				0 === strpos(
340 13
					Filesystem::normalizePath($path),
341 3
					Filesystem::normalizePath($rootDir)
342 3
				)
343 3
			) {
344 3
				return true;
345 13
			}
346 3
347
348
			//detect system wide folders
349
			if (in_array($root[1], $this->excludedPaths)) {
350
				return true;
351 10
			}
352 2
353
			// detect user specific folders
354
			if ($this->userManager->userExists($root[1])
355
				&& in_array($root[2], $this->excludedPaths)) {
356 8
357 8
				return true;
358
			}
359 6
		}
360
		return false;
361 7
	}
362 7
363
	/**
364
	 * check if recovery key is enabled for user
365
	 *
366
	 * @param string $uid
367
	 * @return boolean
368
	 */
369
	public function recoveryEnabled($uid) {
370
		$enabled = $this->config->getUserValue($uid, 'encryption', 'recovery_enabled', '0');
371
372
		return ($enabled === '1') ? true : false;
373
	}
374
375
	/**
376
	 * set new key storage root
377
	 *
378
	 * @param string $root new key store root relative to the data folder
379
	 */
380
	public function setKeyStorageRoot($root) {
381
		$this->config->setAppValue('core', 'encryption_key_storage_root', $root);
382
	}
383
384
	/**
385
	 * get key storage root
386
	 *
387
	 * @return string key storage root
388
	 */
389
	public function getKeyStorageRoot() {
390
		return $this->config->getAppValue('core', 'encryption_key_storage_root', '');
391 15
	}
392 15
393
}
394