Completed
Push — master ( 17691e...d906ba )
by Sujith
16:09 queued 04:29
created

Storage   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 361
Duplicated Lines 14.96 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 54
loc 361
rs 8.4864
c 0
b 0
f 0
wmc 48
lcom 1
cbo 8

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 3
A getUserKey() 0 4 1
A getFileKey() 0 15 3
A getSystemUserKey() 0 4 1
A setUserKey() 0 4 1
A setFileKey() 0 4 1
A setSystemUserKey() 0 4 1
A deleteFileKey() 0 4 2
A deleteAllFileKeys() 0 4 2
A deleteSystemUserKey() 0 4 2
A deleteUserKey() 0 17 3
A deleteAltUserStorageKeys() 0 16 3
A constructUserKeyPath() 0 12 2
A getKey() 0 15 3
A setKey() 0 12 3
A getFileKeyDir() 14 14 2
A renameKeys() 14 14 2
A copyKeys() 13 13 2
A getPathToKeys() 13 13 2
A keySetPreparation() 0 13 4
B setupUserMounts() 0 10 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author Thomas Müller <[email protected]>
6
 * @author Vincent Petry <[email protected]>
7
 *
8
 * @copyright Copyright (c) 2017, ownCloud GmbH
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\Keys;
26
27
use OC\Encryption\Util;
28
use OC\Files\Filesystem;
29
use OC\Files\View;
30
use OCP\Encryption\Keys\IStorage;
31
use OCP\IUserSession;
32
use OC\User\NoUserException;
33
34
class Storage implements IStorage {
35
36
	// hidden file which indicate that the folder is a valid key storage
37
	const KEY_STORAGE_MARKER = '.oc_key_storage';
38
39
	/** @var View */
40
	private $view;
41
42
	/** @var Util */
43
	private $util;
44
45
	// base dir where all the file related keys are stored
46
	/** @var string */
47
	private $keys_base_dir;
48
49
	// root of the key storage default is empty which means that we use the data folder
50
	/** @var string */
51
	private $root_dir;
52
53
	/** @var string */
54
	private $encryption_base_dir;
55
56
	/** @var array */
57
	private $keyCache = [];
58
59
	/** @var string */
60
	private $currentUser = null;
61
62
	/**
63
	 * @param View $view view
64
	 * @param Util $util encryption util class
65
	 * @param IUserSession $session user session
66
	 */
67
	public function __construct(View $view, Util $util, IUserSession $session) {
68
		$this->view = $view;
69
		$this->util = $util;
70
71
		$this->encryption_base_dir = '/files_encryption';
72
		$this->keys_base_dir = $this->encryption_base_dir .'/keys';
73
		$this->root_dir = $this->util->getKeyStorageRoot();
74
75
		if (!is_null($session) && !is_null($session->getUser())) {
76
			$this->currentUser = $session->getUser()->getUID();
77
		}
78
	}
79
80
	/**
81
	 * @inheritdoc
82
	 */
83
	public function getUserKey($uid, $keyId, $encryptionModuleId) {
84
		$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid);
85
		return $this->getKey($path);
86
	}
87
88
	/**
89
	 * @inheritdoc
90
	 */
91
	public function getFileKey($path, $keyId, $encryptionModuleId) {
92
		$realFile = $this->util->stripPartialFileExtension($path);
93
		$keyDir = $this->getFileKeyDir($encryptionModuleId, $realFile);
94
		$key = $this->getKey($keyDir . $keyId);
95
96
		if ($key === '' && $realFile !== $path) {
97
			// Check if the part file has keys and use them, if no normal keys
98
			// exist. This is required to fix copyBetweenStorage() when we
99
			// rename a .part file over storage borders.
100
			$keyDir = $this->getFileKeyDir($encryptionModuleId, $path);
101
			$key = $this->getKey($keyDir . $keyId);
102
		}
103
104
		return $key;
105
	}
106
107
	/**
108
	 * @inheritdoc
109
	 */
110
	public function getSystemUserKey($keyId, $encryptionModuleId) {
111
		$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null);
112
		return $this->getKey($path);
113
	}
114
115
	/**
116
	 * @inheritdoc
117
	 */
118
	public function setUserKey($uid, $keyId, $key, $encryptionModuleId) {
119
		$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid);
120
		return $this->setKey($path, $key);
121
	}
122
123
	/**
124
	 * @inheritdoc
125
	 */
126
	public function setFileKey($path, $keyId, $key, $encryptionModuleId) {
127
		$keyDir = $this->getFileKeyDir($encryptionModuleId, $path);
128
		return $this->setKey($keyDir . $keyId, $key);
129
	}
130
131
	/**
132
	 * @inheritdoc
133
	 */
134
	public function setSystemUserKey($keyId, $key, $encryptionModuleId) {
135
		$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null);
136
		return $this->setKey($path, $key);
137
	}
138
139
	/**
140
	 * @inheritdoc
141
	 */
142
	public function deleteUserKey($uid, $keyId, $encryptionModuleId) {
143
		try {
144
			$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid);
145
			return !$this->view->file_exists($path) || $this->view->unlink($path);
146
		} catch (NoUserException $e) {
147
			// this exception can come from initMountPoints() from setupUserMounts()
148
			// for a deleted user.
149
			//
150
			// It means, that:
151
			// - we are not running in alternative storage mode because we don't call
152
			// initMountPoints() in that mode
153
			// - the keys were in the user's home but since the user was deleted, the
154
			// user's home is gone and so are the keys
155
			//
156
			// So there is nothing to do, just ignore.
157
		}
158
	}
159
160
	/**
161
	 * @inheritdoc
162
	 */
163
	public function deleteFileKey($path, $keyId, $encryptionModuleId) {
164
		$keyDir = $this->getFileKeyDir($encryptionModuleId, $path);
165
		return !$this->view->file_exists($keyDir . $keyId) || $this->view->unlink($keyDir . $keyId);
166
	}
167
168
	/**
169
	 * @inheritdoc
170
	 */
171
	public function deleteAllFileKeys($path) {
172
		$keyDir = $this->getFileKeyDir('', $path);
173
		return !$this->view->file_exists($keyDir) || $this->view->deleteAll($keyDir);
174
	}
175
176
	/**
177
	 * @inheritdoc
178
	 */
179
	public function deleteSystemUserKey($keyId, $encryptionModuleId) {
180
		$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null);
181
		return !$this->view->file_exists($path) || $this->view->unlink($path);
182
	}
183
184
	/**
185
	 * @inheritdoc
186
	 */
187
188
	public function deleteAltUserStorageKeys($uid) {
189
		if (\OC::$server->getEncryptionManager()->isEnabled()) {
190
			/**
191
			 * If the key storage is not the default
192
			 * location, then we need to remove the keys
193
			 * in the alternate key location
194
			 */
195
			$keyStorageRoot = $this->util->getKeyStorageRoot();
196
			if ($keyStorageRoot !== '') {
197
				$this->view->rmdir($keyStorageRoot . '/' . $uid);
198
				return true;
199
			}
200
201
			return false;
202
		}
203
	}
204
205
	/**
206
	 * construct path to users key
207
	 *
208
	 * @param string $encryptionModuleId
209
	 * @param string $keyId
210
	 * @param string $uid
211
	 * @return string
212
	 */
213
	protected function constructUserKeyPath($encryptionModuleId, $keyId, $uid) {
214
215
		if ($uid === null) {
216
			$path = $this->root_dir . '/' . $this->encryption_base_dir . '/' . $encryptionModuleId . '/' . $keyId;
217
		} else {
218
			$this->setupUserMounts($uid);
219
			$path = $this->root_dir . '/' . $uid . $this->encryption_base_dir . '/'
220
				. $encryptionModuleId . '/' . $uid . '.' . $keyId;
221
		}
222
223
		return \OC\Files\Filesystem::normalizePath($path);
224
	}
225
226
	/**
227
	 * read key from hard disk
228
	 *
229
	 * @param string $path to key
230
	 * @return string
231
	 */
232
	private function getKey($path) {
233
234
		$key = '';
235
236
		if ($this->view->file_exists($path)) {
237
			if (isset($this->keyCache[$path])) {
238
				$key =  $this->keyCache[$path];
239
			} else {
240
				$key = $this->view->file_get_contents($path);
241
				$this->keyCache[$path] = $key;
242
			}
243
		}
244
245
		return $key;
246
	}
247
248
	/**
249
	 * write key to disk
250
	 *
251
	 *
252
	 * @param string $path path to key directory
253
	 * @param string $key key
254
	 * @return bool
255
	 */
256
	private function setKey($path, $key) {
257
		$this->keySetPreparation(dirname($path));
258
259
		$result = $this->view->file_put_contents($path, $key);
260
261
		if (is_int($result) && $result > 0) {
262
			$this->keyCache[$path] = $key;
263
			return true;
264
		}
265
266
		return false;
267
	}
268
269
	/**
270
	 * get path to key folder for a given file
271
	 *
272
	 * @param string $encryptionModuleId
273
	 * @param string $path path to the file, relative to data/
274
	 * @return string
275
	 */
276 View Code Duplication
	private function getFileKeyDir($encryptionModuleId, $path) {
277
278
		list($owner, $filename) = $this->util->getUidAndFilename($path);
279
280
		// in case of system wide mount points the keys are stored directly in the data directory
281
		if ($this->util->isSystemWideMountPoint($filename, $owner)) {
282
			$keyPath = $this->root_dir . '/' . $this->keys_base_dir . $filename . '/';
283
		} else {
284
			$this->setupUserMounts($owner);
285
			$keyPath = $this->root_dir . '/' . $owner . $this->keys_base_dir . $filename . '/';
286
		}
287
288
		return Filesystem::normalizePath($keyPath . $encryptionModuleId . '/', false);
289
	}
290
291
	/**
292
	 * move keys if a file was renamed
293
	 *
294
	 * @param string $source
295
	 * @param string $target
296
	 * @return boolean
297
	 */
298 View Code Duplication
	public function renameKeys($source, $target) {
299
300
		$sourcePath = $this->getPathToKeys($source);
301
		$targetPath = $this->getPathToKeys($target);
302
303
		if ($this->view->file_exists($sourcePath)) {
304
			$this->keySetPreparation(dirname($targetPath));
305
			$this->view->rename($sourcePath, $targetPath);
306
307
			return true;
308
		}
309
310
		return false;
311
	}
312
313
314
	/**
315
	 * copy keys if a file was renamed
316
	 *
317
	 * @param string $source
318
	 * @param string $target
319
	 * @return boolean
320
	 */
321 View Code Duplication
	public function copyKeys($source, $target) {
322
323
		$sourcePath = $this->getPathToKeys($source);
324
		$targetPath = $this->getPathToKeys($target);
325
326
		if ($this->view->file_exists($sourcePath)) {
327
			$this->keySetPreparation(dirname($targetPath));
328
			$this->view->copy($sourcePath, $targetPath);
329
			return true;
330
		}
331
332
		return false;
333
	}
334
335
	/**
336
	 * get system wide path and detect mount points
337
	 *
338
	 * @param string $path
339
	 * @return string
340
	 */
341 View Code Duplication
	protected function getPathToKeys($path) {
342
		list($owner, $relativePath) = $this->util->getUidAndFilename($path);
343
		$systemWideMountPoint = $this->util->isSystemWideMountPoint($relativePath, $owner);
344
345
		if ($systemWideMountPoint) {
346
			$systemPath = $this->root_dir . '/' . $this->keys_base_dir . $relativePath . '/';
347
		} else {
348
			$this->setupUserMounts($owner);
349
			$systemPath = $this->root_dir . '/' . $owner . $this->keys_base_dir . $relativePath . '/';
350
		}
351
352
		return  Filesystem::normalizePath($systemPath, false);
353
	}
354
355
	/**
356
	 * Make preparations to filesystem for saving a key file
357
	 *
358
	 * @param string $path relative to the views root
359
	 */
360
	protected function keySetPreparation($path) {
361
		// If the file resides within a subdirectory, create it
362
		if (!$this->view->file_exists($path)) {
363
			$sub_dirs = explode('/', ltrim($path, '/'));
364
			$dir = '';
365
			foreach ($sub_dirs as $sub_dir) {
366
				$dir .= '/' . $sub_dir;
367
				if (!$this->view->is_dir($dir)) {
368
					$this->view->mkdir($dir);
369
				}
370
			}
371
		}
372
	}
373
374
	/**
375
	 * Setup the mounts of the given user if different than
376
	 * the current user.
377
	 *
378
	 * This is needed because in many cases the keys are stored
379
	 * within the user's home storage.
380
	 *
381
	 * @param string $uid user id
382
	 */
383
	protected function setupUserMounts($uid) {
384
		if ($this->root_dir !== '') {
385
			// this means that the keys are stored outside of the user's homes,
386
			// so we don't need to mount anything
387
			return;
388
		}
389
		if (!is_null($uid) && $uid !== '' && $uid !== $this->currentUser) {
390
			\OC\Files\Filesystem::initMountPoints($uid);
391
		}
392
	}
393
394
}
395