Completed
Push — master ( 430fd0...c13468 )
by Thomas
11:39 queued 01:10
created

DecryptAll::decryptAll()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 33
Code Lines 21

Duplication

Lines 5
Ratio 15.15 %

Importance

Changes 0
Metric Value
cc 7
eloc 21
nc 6
nop 3
dl 5
loc 33
rs 6.7272
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Christian Jürges <[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) 2017, 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
27
namespace OC\Encryption;
28
29
use OC\Encryption\Exceptions\DecryptionFailedException;
30
use OC\Files\View;
31
use \OCP\Encryption\IEncryptionModule;
32
use OCP\ILogger;
33
use OCP\IUserManager;
34
use Symfony\Component\Console\Helper\ProgressBar;
35
use Symfony\Component\Console\Input\InputInterface;
36
use Symfony\Component\Console\Output\OutputInterface;
37
38
class DecryptAll {
39
40
	/** @var  OutputInterface */
41
	protected $output;
42
43
	/** @var  InputInterface */
44
	protected $input;
45
46
	/** @var  Manager */
47
	protected $encryptionManager;
48
49
	/** @var IUserManager */
50
	protected $userManager;
51
52
	/** @var View */
53
	protected $rootView;
54
55
	/** @var  array files which couldn't be decrypted */
56
	protected $failed;
57
58
	/** @var ILogger */
59
	protected $logger;
60
61
	/**
62
	 * @param Manager $encryptionManager
63
	 * @param IUserManager $userManager
64
	 * @param View $rootView
65
	 */
66
	public function __construct(
67
		Manager $encryptionManager,
68
		IUserManager $userManager,
69
		View $rootView,
70
		ILogger $logger
71
	) {
72
		$this->encryptionManager = $encryptionManager;
73
		$this->userManager = $userManager;
74
		$this->rootView = $rootView;
75
		$this->failed = [];
76
		$this->logger = $logger;
77
	}
78
79
	/**
80
	 * start to decrypt all files
81
	 *
82
	 * @param InputInterface $input
83
	 * @param OutputInterface $output
84
	 * @param string $user which users data folder should be decrypted, default = all users
85
	 * @return bool
86
	 * @throws \Exception
87
	 */
88
	public function decryptAll(InputInterface $input, OutputInterface $output, $user = '') {
89
90
		$this->input = $input;
91
		$this->output = $output;
92
93
		if ($user !== '' && $this->userManager->userExists($user) === false) {
94
			$this->output->writeln('User "' . $user . '" does not exist. Please check the username and try again');
95
			return false;
96
		}
97
98
		$this->output->writeln('prepare encryption modules...');
99 View Code Duplication
		if (\OC::$server->getAppConfig()->getValue('encryption', 'useMasterKey', '0') !== '0') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
100
			if ($this->prepareEncryptionModules($user) === false) {
101
				return false;
102
			}
103
		}
104
		$this->output->writeln(' done.');
105
106
		$this->decryptAllUsersFiles($user);
107
108
		if (empty($this->failed)) {
109
			$this->output->writeln('all files could be decrypted successfully!');
110
		} else {
111
			$this->output->writeln('Files for following users couldn\'t be decrypted, ');
112
			$this->output->writeln('maybe the user is not set up in a way that supports this operation: ');
113
			foreach ($this->failed as $uid => $paths) {
114
				$this->output->writeln('    ' . $uid);
115
			}
116
			$this->output->writeln('');
117
		}
118
119
		return true;
120
	}
121
122
	/**
123
	 * prepare encryption modules to perform the decrypt all function
124
	 *
125
	 * @param $user
126
	 * @return bool
127
	 */
128
	protected function prepareEncryptionModules($user) {
129
		// prepare all encryption modules for decrypt all
130
		$encryptionModules = $this->encryptionManager->getEncryptionModules();
131
		foreach ($encryptionModules as $moduleDesc) {
132
			/** @var IEncryptionModule $module */
133
			$module = call_user_func($moduleDesc['callback']);
134
			$this->output->writeln('');
135
			$this->output->writeln('Prepare "' . $module->getDisplayName() . '"');
136
			$this->output->writeln('');
137
			if ($module->prepareDecryptAll($this->input, $this->output, $user) === false) {
138
				$this->output->writeln('Module "' . $moduleDesc['displayName'] . '" does not support the functionality to decrypt all files again or the initialization of the module failed!');
139
				return false;
140
			}
141
		}
142
143
		return true;
144
	}
145
146
	/**
147
	 * iterate over all user and encrypt their files
148
	 *
149
	 * @param string $user which users files should be decrypted, default = all users
150
	 */
151
	protected function decryptAllUsersFiles($user = '') {
152
153
		$this->output->writeln("\n");
154
155
		$userList = [];
156
		if ($user === '') {
157
158
			$fetchUsersProgress = new ProgressBar($this->output);
159
			$fetchUsersProgress->setFormat(" %message% \n [%bar%]");
160
			$fetchUsersProgress->start();
161
			$fetchUsersProgress->setMessage("Fetch list of users...");
162
			$fetchUsersProgress->advance();
163
164 View Code Duplication
			foreach ($this->userManager->getBackends() as $backend) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
				$limit = 500;
166
				$offset = 0;
167
				do {
168
					$users = $backend->getUsers('', $limit, $offset);
169
					foreach ($users as $user) {
170
						$userList[] = $user;
171
					}
172
					$offset += $limit;
173
					$fetchUsersProgress->advance();
174
				} while (count($users) >= $limit);
175
				$fetchUsersProgress->setMessage("Fetch list of users... finished");
176
				$fetchUsersProgress->finish();
177
			}
178
		} else {
179
			$userList[] = $user;
180
		}
181
182
		$this->output->writeln("\n\n");
183
184
		$progress = new ProgressBar($this->output);
185
		$progress->setFormat(" %message% \n [%bar%]");
186
		$progress->start();
187
		$progress->setMessage("starting to decrypt files...");
188
		$progress->advance();
189
190
		$numberOfUsers = count($userList);
191
		$userNo = 1;
192
		foreach ($userList as $uid) {
193
			$userCount = "$uid ($userNo of $numberOfUsers)";
194 View Code Duplication
			if (\OC::$server->getAppConfig()->getValue('encryption', 'userSpecificKey', '0') !== '0') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
195
				if ($this->prepareEncryptionModules($uid) === false) {
196
					return false;
197
				}
198
			}
199
			$this->decryptUsersFiles($uid, $progress, $userCount);
200
			$userNo++;
201
		}
202
203
		$progress->setMessage("starting to decrypt files... finished");
204
		$progress->finish();
205
206
		$this->output->writeln("\n\n");
207
208
	}
209
210
	/**
211
	 * encrypt files from the given user
212
	 *
213
	 * @param string $uid
214
	 * @param ProgressBar $progress
215
	 * @param string $userCount
216
	 */
217
	protected function decryptUsersFiles($uid, ProgressBar $progress, $userCount) {
218
219
		$this->setupUserFS($uid);
220
		$directories = [];
221
		$directories[] = '/' . $uid . '/files';
222
223
		while ($root = array_pop($directories)) {
224
			$content = $this->rootView->getDirectoryContent($root);
225
			foreach ($content as $file) {
226
				// only decrypt files owned by the user
227
				if($file->getStorage()->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {
228
						continue;
229
				}
230
				$path = $root . '/' . $file['name'];
231
				if ($this->rootView->is_dir($path)) {
232
					$directories[] = $path;
233
					continue;
234
				} else {
235
					try {
236
						$progress->setMessage("decrypt files for user $userCount: $path");
237
						$progress->advance();
238
						if ($file->isEncrypted() === false) {
239
							$progress->setMessage("decrypt files for user $userCount: $path (already decrypted)");
240
							$progress->advance();
241
						} else {
242
							if ($this->decryptFile($path) === false) {
243
								$progress->setMessage("decrypt files for user $userCount: $path (already decrypted)");
244
								$progress->advance();
245
							}
246
						}
247
					} catch (\Exception $e) {
248
						$this->logger->logException($e, [
249
							'message' => "Exception trying to decrypt file <$path> for user <$uid>",
250
							'app' => __CLASS__
251
						]);
252
						if (isset($this->failed[$uid])) {
253
							$this->failed[$uid][] = $path;
254
						} else {
255
							$this->failed[$uid] = [$path];
256
						}
257
					}
258
				}
259
			}
260
		}
261
	}
262
263
	/**
264
	 * encrypt file
265
	 *
266
	 * @param string $path
267
	 * @return bool
268
	 */
269
	protected function decryptFile($path) {
270
271
		$source = $path;
272
		$target = $path . '.decrypted.' . $this->getTimestamp();
273
274
		try {
275
			\OC\Files\Storage\Wrapper\Encryption::setDisableWriteEncryption(true);
276
			$this->rootView->copy($source, $target);
277
			\OC\Files\Storage\Wrapper\Encryption::setDisableWriteEncryption(false);
278
			$this->rootView->rename($target, $source);
279
		} catch (DecryptionFailedException $e) {
280
			if ($this->rootView->file_exists($target)) {
281
				$this->rootView->unlink($target);
282
			}
283
			return false;
284
		}
285
286
		return true;
287
	}
288
289
	/**
290
	 * get current timestamp
291
	 *
292
	 * @return int
293
	 */
294
	protected function getTimestamp() {
295
		return time();
296
	}
297
298
299
	/**
300
	 * setup user file system
301
	 *
302
	 * @param string $uid
303
	 */
304
	protected function setupUserFS($uid) {
305
		\OC_Util::tearDownFS();
306
		\OC_Util::setupFS($uid);
307
	}
308
309
}
310