Completed
Push — master ( e4992c...6d0a35 )
by
unknown
10:42
created

Util::getUserWithAccessToMountPoint()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 19

Duplication

Lines 1
Ratio 5.26 %

Importance

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