Passed
Push — master ( 8a7963...0b88b5 )
by Joas
17:38 queued 13s
created

EncryptAll::createMailBody()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 1
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bjoern Schiessle <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Kenneth Newwood <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 *
13
 * @license AGPL-3.0
14
 *
15
 * This code is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License, version 3,
17
 * as published by the Free Software Foundation.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License, version 3,
25
 * along with this program. If not, see <http://www.gnu.org/licenses/>
26
 *
27
 */
28
namespace OCA\Encryption\Crypto;
29
30
use OC\Encryption\Exceptions\DecryptionFailedException;
31
use OC\Files\View;
32
use OCA\Encryption\KeyManager;
33
use OCA\Encryption\Users\Setup;
34
use OCA\Encryption\Util;
35
use OCP\IConfig;
36
use OCP\IL10N;
37
use OCP\IUser;
38
use OCP\IUserManager;
39
use OCP\L10N\IFactory;
40
use OCP\Mail\Headers\AutoSubmitted;
41
use OCP\Mail\IMailer;
42
use OCP\Security\ISecureRandom;
43
use Symfony\Component\Console\Helper\ProgressBar;
44
use Symfony\Component\Console\Helper\QuestionHelper;
45
use Symfony\Component\Console\Helper\Table;
46
use Symfony\Component\Console\Input\InputInterface;
47
use Symfony\Component\Console\Output\OutputInterface;
48
use Symfony\Component\Console\Question\ConfirmationQuestion;
49
50
class EncryptAll {
51
52
	/** @var Setup */
53
	protected $userSetup;
54
55
	/** @var IUserManager */
56
	protected $userManager;
57
58
	/** @var View */
59
	protected $rootView;
60
61
	/** @var KeyManager */
62
	protected $keyManager;
63
64
	/** @var Util */
65
	protected $util;
66
67
	/** @var array  */
68
	protected $userPasswords;
69
70
	/** @var  IConfig */
71
	protected $config;
72
73
	/** @var IMailer */
74
	protected $mailer;
75
76
	/** @var  IL10N */
77
	protected $l;
78
79
	/** @var  IFactory */
80
	protected $l10nFactory;
81
82
	/** @var  QuestionHelper */
83
	protected $questionHelper;
84
85
	/** @var  OutputInterface */
86
	protected $output;
87
88
	/** @var  InputInterface */
89
	protected $input;
90
91
	/** @var ISecureRandom */
92
	protected $secureRandom;
93
94
	public function __construct(
95
		Setup $userSetup,
96
		IUserManager $userManager,
97
		View $rootView,
98
		KeyManager $keyManager,
99
		Util $util,
100
		IConfig $config,
101
		IMailer $mailer,
102
		IL10N $l,
103
		IFactory $l10nFactory,
104
		QuestionHelper $questionHelper,
105
		ISecureRandom $secureRandom
106
	) {
107
		$this->userSetup = $userSetup;
108
		$this->userManager = $userManager;
109
		$this->rootView = $rootView;
110
		$this->keyManager = $keyManager;
111
		$this->util = $util;
112
		$this->config = $config;
113
		$this->mailer = $mailer;
114
		$this->l = $l;
115
		$this->l10nFactory = $l10nFactory;
116
		$this->questionHelper = $questionHelper;
117
		$this->secureRandom = $secureRandom;
118
		// store one time passwords for the users
119
		$this->userPasswords = [];
120
	}
121
122
	/**
123
	 * start to encrypt all files
124
	 *
125
	 * @param InputInterface $input
126
	 * @param OutputInterface $output
127
	 */
128
	public function encryptAll(InputInterface $input, OutputInterface $output) {
129
		$this->input = $input;
130
		$this->output = $output;
131
132
		$headline = 'Encrypt all files with the ' . Encryption::DISPLAY_NAME;
133
		$this->output->writeln("\n");
134
		$this->output->writeln($headline);
135
		$this->output->writeln(str_pad('', strlen($headline), '='));
136
		$this->output->writeln("\n");
137
138
		if ($this->util->isMasterKeyEnabled()) {
139
			$this->output->writeln('Use master key to encrypt all files.');
140
			$this->keyManager->validateMasterKey();
141
		} else {
142
			//create private/public keys for each user and store the private key password
143
			$this->output->writeln('Create key-pair for every user');
144
			$this->output->writeln('------------------------------');
145
			$this->output->writeln('');
146
			$this->output->writeln('This module will encrypt all files in the users files folder initially.');
147
			$this->output->writeln('Already existing versions and files in the trash bin will not be encrypted.');
148
			$this->output->writeln('');
149
			$this->createKeyPairs();
150
		}
151
152
153
		// output generated encryption key passwords
154
		if ($this->util->isMasterKeyEnabled() === false) {
155
			//send-out or display password list and write it to a file
156
			$this->output->writeln("\n");
157
			$this->output->writeln('Generated encryption key passwords');
158
			$this->output->writeln('----------------------------------');
159
			$this->output->writeln('');
160
			$this->outputPasswords();
161
		}
162
163
		//setup users file system and encrypt all files one by one (take should encrypt setting of storage into account)
164
		$this->output->writeln("\n");
165
		$this->output->writeln('Start to encrypt users files');
166
		$this->output->writeln('----------------------------');
167
		$this->output->writeln('');
168
		$this->encryptAllUsersFiles();
169
		$this->output->writeln("\n");
170
	}
171
172
	/**
173
	 * create key-pair for every user
174
	 */
175
	protected function createKeyPairs() {
176
		$this->output->writeln("\n");
177
		$progress = new ProgressBar($this->output);
178
		$progress->setFormat(" %message% \n [%bar%]");
179
		$progress->start();
180
181
		foreach ($this->userManager->getBackends() as $backend) {
182
			$limit = 500;
183
			$offset = 0;
184
			do {
185
				$users = $backend->getUsers('', $limit, $offset);
186
				foreach ($users as $user) {
187
					if ($this->keyManager->userHasKeys($user) === false) {
188
						$progress->setMessage('Create key-pair for ' . $user);
189
						$progress->advance();
190
						$this->setupUserFS($user);
191
						$password = $this->generateOneTimePassword($user);
192
						$this->userSetup->setupUser($user, $password);
193
					} else {
194
						// users which already have a key-pair will be stored with a
195
						// empty password and filtered out later
196
						$this->userPasswords[$user] = '';
197
					}
198
				}
199
				$offset += $limit;
200
			} while (count($users) >= $limit);
201
		}
202
203
		$progress->setMessage('Key-pair created for all users');
204
		$progress->finish();
205
	}
206
207
	/**
208
	 * iterate over all user and encrypt their files
209
	 */
210
	protected function encryptAllUsersFiles() {
211
		$this->output->writeln("\n");
212
		$progress = new ProgressBar($this->output);
213
		$progress->setFormat(" %message% \n [%bar%]");
214
		$progress->start();
215
		$numberOfUsers = count($this->userPasswords);
216
		$userNo = 1;
217
		if ($this->util->isMasterKeyEnabled()) {
218
			$this->encryptAllUserFilesWithMasterKey($progress);
219
		} else {
220
			foreach ($this->userPasswords as $uid => $password) {
221
				$userCount = "$uid ($userNo of $numberOfUsers)";
222
				$this->encryptUsersFiles($uid, $progress, $userCount);
223
				$userNo++;
224
			}
225
		}
226
		$progress->setMessage("all files encrypted");
227
		$progress->finish();
228
	}
229
230
	/**
231
	 * encrypt all user files with the master key
232
	 *
233
	 * @param ProgressBar $progress
234
	 */
235
	protected function encryptAllUserFilesWithMasterKey(ProgressBar $progress) {
236
		$userNo = 1;
237
		foreach ($this->userManager->getBackends() as $backend) {
238
			$limit = 500;
239
			$offset = 0;
240
			do {
241
				$users = $backend->getUsers('', $limit, $offset);
242
				foreach ($users as $user) {
243
					$userCount = "$user ($userNo)";
244
					$this->encryptUsersFiles($user, $progress, $userCount);
245
					$userNo++;
246
				}
247
				$offset += $limit;
248
			} while (count($users) >= $limit);
249
		}
250
	}
251
252
	/**
253
	 * encrypt files from the given user
254
	 *
255
	 * @param string $uid
256
	 * @param ProgressBar $progress
257
	 * @param string $userCount
258
	 */
259
	protected function encryptUsersFiles($uid, ProgressBar $progress, $userCount) {
260
		$this->setupUserFS($uid);
261
		$directories = [];
262
		$directories[] = '/' . $uid . '/files';
263
264
		while ($root = array_pop($directories)) {
265
			$content = $this->rootView->getDirectoryContent($root);
266
			foreach ($content as $file) {
267
				$path = $root . '/' . $file['name'];
268
				if ($this->rootView->is_dir($path)) {
269
					$directories[] = $path;
270
					continue;
271
				} else {
272
					$progress->setMessage("encrypt files for user $userCount: $path");
273
					$progress->advance();
274
					if ($this->encryptFile($path) === false) {
275
						$progress->setMessage("encrypt files for user $userCount: $path (already encrypted)");
276
						$progress->advance();
277
					}
278
				}
279
			}
280
		}
281
	}
282
283
	/**
284
	 * encrypt file
285
	 *
286
	 * @param string $path
287
	 * @return bool
288
	 */
289
	protected function encryptFile($path) {
290
291
		// skip already encrypted files
292
		$fileInfo = $this->rootView->getFileInfo($path);
293
		if ($fileInfo !== false && $fileInfo->isEncrypted()) {
294
			return true;
295
		}
296
297
		$source = $path;
298
		$target = $path . '.encrypted.' . time();
299
300
		try {
301
			$this->rootView->copy($source, $target);
302
			$this->rootView->rename($target, $source);
303
		} catch (DecryptionFailedException $e) {
304
			if ($this->rootView->file_exists($target)) {
305
				$this->rootView->unlink($target);
306
			}
307
			return false;
308
		}
309
310
		return true;
311
	}
312
313
	/**
314
	 * output one-time encryption passwords
315
	 */
316
	protected function outputPasswords() {
317
		$table = new Table($this->output);
318
		$table->setHeaders(['Username', 'Private key password']);
319
320
		//create rows
321
		$newPasswords = [];
322
		$unchangedPasswords = [];
323
		foreach ($this->userPasswords as $uid => $password) {
324
			if (empty($password)) {
325
				$unchangedPasswords[] = $uid;
326
			} else {
327
				$newPasswords[] = [$uid, $password];
328
			}
329
		}
330
331
		if (empty($newPasswords)) {
332
			$this->output->writeln("\nAll users already had a key-pair, no further action needed.\n");
333
			return;
334
		}
335
336
		$table->setRows($newPasswords);
337
		$table->render();
338
339
		if (!empty($unchangedPasswords)) {
340
			$this->output->writeln("\nThe following users already had a key-pair which was reused without setting a new password:\n");
341
			foreach ($unchangedPasswords as $uid) {
342
				$this->output->writeln("    $uid");
343
			}
344
		}
345
346
		$this->writePasswordsToFile($newPasswords);
347
348
		$this->output->writeln('');
349
		$question = new ConfirmationQuestion('Do you want to send the passwords directly to the users by mail? (y/n) ', false);
350
		if ($this->questionHelper->ask($this->input, $this->output, $question)) {
351
			$this->sendPasswordsByMail();
352
		}
353
	}
354
355
	/**
356
	 * write one-time encryption passwords to a csv file
357
	 *
358
	 * @param array $passwords
359
	 */
360
	protected function writePasswordsToFile(array $passwords) {
361
		$fp = $this->rootView->fopen('oneTimeEncryptionPasswords.csv', 'w');
362
		foreach ($passwords as $pwd) {
363
			fputcsv($fp, $pwd);
364
		}
365
		fclose($fp);
366
		$this->output->writeln("\n");
367
		$this->output->writeln('A list of all newly created passwords was written to data/oneTimeEncryptionPasswords.csv');
368
		$this->output->writeln('');
369
		$this->output->writeln('Each of these users need to login to the web interface, go to the');
370
		$this->output->writeln('personal settings section "basic encryption module" and');
371
		$this->output->writeln('update the private key password to match the login password again by');
372
		$this->output->writeln('entering the one-time password into the "old log-in password" field');
373
		$this->output->writeln('and their current login password');
374
	}
375
376
	/**
377
	 * setup user file system
378
	 *
379
	 * @param string $uid
380
	 */
381
	protected function setupUserFS($uid) {
382
		\OC_Util::tearDownFS();
383
		\OC_Util::setupFS($uid);
384
	}
385
386
	/**
387
	 * generate one time password for the user and store it in a array
388
	 *
389
	 * @param string $uid
390
	 * @return string password
391
	 */
392
	protected function generateOneTimePassword($uid) {
393
		$password = $this->secureRandom->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
394
		$this->userPasswords[$uid] = $password;
395
		return $password;
396
	}
397
398
	/**
399
	 * send encryption key passwords to the users by mail
400
	 */
401
	protected function sendPasswordsByMail() {
402
		$noMail = [];
403
404
		$this->output->writeln('');
405
		$progress = new ProgressBar($this->output, count($this->userPasswords));
406
		$progress->start();
407
408
		foreach ($this->userPasswords as $uid => $password) {
409
			$progress->advance();
410
			if (!empty($password)) {
411
				$recipient = $this->userManager->get($uid);
412
				if (!$recipient instanceof IUser) {
413
					continue;
414
				}
415
416
				$recipientDisplayName = $recipient->getDisplayName();
417
				$to = $recipient->getEMailAddress();
418
419
				if ($to === '' || $to === null) {
420
					$noMail[] = $uid;
421
					continue;
422
				}
423
424
				$l = $this->l10nFactory->get('encryption', $this->l10nFactory->getUserLanguage($recipient));
425
426
				$template = $this->mailer->createEMailTemplate('encryption.encryptAllPassword', [
427
					'user' => $recipient->getUID(),
428
					'password' => $password,
429
				]);
430
431
				$template->setSubject($l->t('one-time password for server-side-encryption'));
432
				// 'Hey there,<br><br>The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>
433
				// Please login to the web interface, go to the section "Basic encryption module" of your personal settings and update your encryption password by entering this password into the "Old log-in password" field and your current login-password.<br><br>'
434
				$template->addHeader();
435
				$template->addHeading($l->t('Encryption password'));
436
				$template->addBodyText(
437
					$l->t('The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.', [htmlspecialchars($password)]),
438
					$l->t('The administration enabled server-side-encryption. Your files were encrypted using the password "%s".', $password)
439
				);
440
				$template->addBodyText(
441
					$l->t('Please login to the web interface, go to the "Security" section of your personal settings and update your encryption password by entering this password into the "Old log-in password" field and your current login-password.')
442
				);
443
				$template->addFooter();
444
445
				// send it out now
446
				try {
447
					$message = $this->mailer->createMessage();
448
					$message->setTo([$to => $recipientDisplayName]);
449
					$message->useTemplate($template);
450
					$message->setAutoSubmitted(AutoSubmitted::VALUE_AUTO_GENERATED);
451
					$this->mailer->send($message);
452
				} catch (\Exception $e) {
453
					$noMail[] = $uid;
454
				}
455
			}
456
		}
457
458
		$progress->finish();
459
460
		if (empty($noMail)) {
461
			$this->output->writeln("\n\nPassword successfully send to all users");
462
		} else {
463
			$table = new Table($this->output);
464
			$table->setHeaders(['Username', 'Private key password']);
465
			$this->output->writeln("\n\nCould not send password to following users:\n");
466
			$rows = [];
467
			foreach ($noMail as $uid) {
468
				$rows[] = [$uid, $this->userPasswords[$uid]];
469
			}
470
			$table->setRows($rows);
471
			$table->render();
472
		}
473
	}
474
}
475