@@ -35,368 +35,368 @@ |
||
| 35 | 35 | |
| 36 | 36 | class EncryptAll { |
| 37 | 37 | |
| 38 | - /** @var array<string, array{password: string, user: IUser}> $userCache store one time passwords for the users */ |
|
| 39 | - protected array $userCache = []; |
|
| 40 | - protected OutputInterface $output; |
|
| 41 | - protected InputInterface $input; |
|
| 42 | - |
|
| 43 | - public function __construct( |
|
| 44 | - protected readonly Setup $userSetup, |
|
| 45 | - protected readonly IUserManager $userManager, |
|
| 46 | - protected readonly View $rootView, |
|
| 47 | - protected readonly KeyManager $keyManager, |
|
| 48 | - protected readonly Util $util, |
|
| 49 | - protected readonly IConfig $config, |
|
| 50 | - protected readonly IMailer $mailer, |
|
| 51 | - protected readonly IL10N $l, |
|
| 52 | - protected readonly IFactory $l10nFactory, |
|
| 53 | - protected readonly QuestionHelper $questionHelper, |
|
| 54 | - protected readonly ISecureRandom $secureRandom, |
|
| 55 | - protected readonly LoggerInterface $logger, |
|
| 56 | - protected readonly SetupManager $setupManager, |
|
| 57 | - ) { |
|
| 58 | - } |
|
| 59 | - |
|
| 60 | - /** |
|
| 61 | - * start to encrypt all files |
|
| 62 | - */ |
|
| 63 | - public function encryptAll(InputInterface $input, OutputInterface $output): void { |
|
| 64 | - $this->input = $input; |
|
| 65 | - $this->output = $output; |
|
| 66 | - |
|
| 67 | - $headline = 'Encrypt all files with the ' . Encryption::DISPLAY_NAME; |
|
| 68 | - $this->output->writeln("\n"); |
|
| 69 | - $this->output->writeln($headline); |
|
| 70 | - $this->output->writeln(str_pad('', strlen($headline), '=')); |
|
| 71 | - $this->output->writeln("\n"); |
|
| 72 | - |
|
| 73 | - if ($this->util->isMasterKeyEnabled()) { |
|
| 74 | - $this->output->writeln('Use master key to encrypt all files.'); |
|
| 75 | - $this->keyManager->validateMasterKey(); |
|
| 76 | - } else { |
|
| 77 | - //create private/public keys for each user and store the private key password |
|
| 78 | - $this->output->writeln('Create key-pair for every user'); |
|
| 79 | - $this->output->writeln('------------------------------'); |
|
| 80 | - $this->output->writeln(''); |
|
| 81 | - $this->output->writeln('This module will encrypt all files in the users files folder initially.'); |
|
| 82 | - $this->output->writeln('Already existing versions and files in the trash bin will not be encrypted.'); |
|
| 83 | - $this->output->writeln(''); |
|
| 84 | - $this->createKeyPairs(); |
|
| 85 | - } |
|
| 86 | - |
|
| 87 | - |
|
| 88 | - // output generated encryption key passwords |
|
| 89 | - if ($this->util->isMasterKeyEnabled() === false) { |
|
| 90 | - //send-out or display password list and write it to a file |
|
| 91 | - $this->output->writeln("\n"); |
|
| 92 | - $this->output->writeln('Generated encryption key passwords'); |
|
| 93 | - $this->output->writeln('----------------------------------'); |
|
| 94 | - $this->output->writeln(''); |
|
| 95 | - $this->outputPasswords(); |
|
| 96 | - } |
|
| 97 | - |
|
| 98 | - //setup users file system and encrypt all files one by one (take should encrypt setting of storage into account) |
|
| 99 | - $this->output->writeln("\n"); |
|
| 100 | - $this->output->writeln('Start to encrypt users files'); |
|
| 101 | - $this->output->writeln('----------------------------'); |
|
| 102 | - $this->output->writeln(''); |
|
| 103 | - $this->encryptAllUsersFiles(); |
|
| 104 | - $this->output->writeln("\n"); |
|
| 105 | - } |
|
| 106 | - |
|
| 107 | - /** |
|
| 108 | - * create key-pair for every user |
|
| 109 | - */ |
|
| 110 | - protected function createKeyPairs(): void { |
|
| 111 | - $this->output->writeln("\n"); |
|
| 112 | - $progress = new ProgressBar($this->output); |
|
| 113 | - $progress->setFormat(" %message% \n [%bar%]"); |
|
| 114 | - $progress->start(); |
|
| 115 | - |
|
| 116 | - foreach ($this->userManager->getSeenUsers() as $user) { |
|
| 117 | - if ($this->keyManager->userHasKeys($user->getUID()) === false) { |
|
| 118 | - $progress->setMessage('Create key-pair for ' . $user->getUID()); |
|
| 119 | - $progress->advance(); |
|
| 120 | - $this->setupUserFileSystem($user); |
|
| 121 | - $password = $this->generateOneTimePassword($user); |
|
| 122 | - $this->userSetup->setupUser($user->getUID(), $password); |
|
| 123 | - } else { |
|
| 124 | - // users which already have a key-pair will be stored with a |
|
| 125 | - // empty password and filtered out later |
|
| 126 | - $this->userCache[$user->getUID()] = ['password' => '', 'user' => $user]; |
|
| 127 | - } |
|
| 128 | - } |
|
| 129 | - |
|
| 130 | - $progress->setMessage('Key-pair created for all users'); |
|
| 131 | - $progress->finish(); |
|
| 132 | - } |
|
| 133 | - |
|
| 134 | - /** |
|
| 135 | - * iterate over all user and encrypt their files |
|
| 136 | - */ |
|
| 137 | - protected function encryptAllUsersFiles(): void { |
|
| 138 | - $this->output->writeln("\n"); |
|
| 139 | - $progress = new ProgressBar($this->output); |
|
| 140 | - $progress->setFormat(" %message% \n [%bar%]"); |
|
| 141 | - $progress->start(); |
|
| 142 | - $numberOfUsers = count($this->userCache); |
|
| 143 | - $userNo = 1; |
|
| 144 | - if ($this->util->isMasterKeyEnabled()) { |
|
| 145 | - $this->encryptAllUserFilesWithMasterKey($progress); |
|
| 146 | - } else { |
|
| 147 | - foreach ($this->userCache as $uid => $cache) { |
|
| 148 | - ['user' => $user, 'password' => $password] = $cache; |
|
| 149 | - $userCount = "$uid ($userNo of $numberOfUsers)"; |
|
| 150 | - $this->encryptUsersFiles($user, $progress, $userCount); |
|
| 151 | - $userNo++; |
|
| 152 | - } |
|
| 153 | - } |
|
| 154 | - $progress->setMessage('all files encrypted'); |
|
| 155 | - $progress->finish(); |
|
| 156 | - } |
|
| 157 | - |
|
| 158 | - /** |
|
| 159 | - * encrypt all user files with the master key |
|
| 160 | - */ |
|
| 161 | - protected function encryptAllUserFilesWithMasterKey(ProgressBar $progress): void { |
|
| 162 | - $userNo = 1; |
|
| 163 | - foreach ($this->userManager->getSeenUsers() as $user) { |
|
| 164 | - $userCount = $user->getUID() . " ($userNo)"; |
|
| 165 | - $this->encryptUsersFiles($user, $progress, $userCount); |
|
| 166 | - $userNo++; |
|
| 167 | - } |
|
| 168 | - } |
|
| 169 | - |
|
| 170 | - /** |
|
| 171 | - * encrypt files from the given user |
|
| 172 | - */ |
|
| 173 | - protected function encryptUsersFiles(IUser $user, ProgressBar $progress, string $userCount): void { |
|
| 174 | - $this->setupUserFileSystem($user); |
|
| 175 | - $uid = $user->getUID(); |
|
| 176 | - $directories = []; |
|
| 177 | - $directories[] = '/' . $uid . '/files'; |
|
| 178 | - |
|
| 179 | - while ($root = array_pop($directories)) { |
|
| 180 | - $content = $this->rootView->getDirectoryContent($root); |
|
| 181 | - foreach ($content as $file) { |
|
| 182 | - $path = $root . '/' . $file->getName(); |
|
| 183 | - if ($file->isShared()) { |
|
| 184 | - $progress->setMessage("Skip shared file/folder $path"); |
|
| 185 | - $progress->advance(); |
|
| 186 | - continue; |
|
| 187 | - } elseif ($file->getType() === FileInfo::TYPE_FOLDER) { |
|
| 188 | - $directories[] = $path; |
|
| 189 | - continue; |
|
| 190 | - } else { |
|
| 191 | - $progress->setMessage("encrypt files for user $userCount: $path"); |
|
| 192 | - $progress->advance(); |
|
| 193 | - try { |
|
| 194 | - if ($this->encryptFile($file, $path) === false) { |
|
| 195 | - $progress->setMessage("encrypt files for user $userCount: $path (already encrypted)"); |
|
| 196 | - $progress->advance(); |
|
| 197 | - } |
|
| 198 | - } catch (\Exception $e) { |
|
| 199 | - $progress->setMessage("Failed to encrypt path $path: " . $e->getMessage()); |
|
| 200 | - $progress->advance(); |
|
| 201 | - $this->logger->error( |
|
| 202 | - 'Failed to encrypt path {path}', |
|
| 203 | - [ |
|
| 204 | - 'user' => $uid, |
|
| 205 | - 'path' => $path, |
|
| 206 | - 'exception' => $e, |
|
| 207 | - ] |
|
| 208 | - ); |
|
| 209 | - } |
|
| 210 | - } |
|
| 211 | - } |
|
| 212 | - } |
|
| 213 | - } |
|
| 214 | - |
|
| 215 | - protected function encryptFile(FileInfo $fileInfo, string $path): bool { |
|
| 216 | - // skip already encrypted files |
|
| 217 | - if ($fileInfo->isEncrypted()) { |
|
| 218 | - return true; |
|
| 219 | - } |
|
| 220 | - |
|
| 221 | - $source = $path; |
|
| 222 | - $target = $path . '.encrypted.' . time(); |
|
| 223 | - |
|
| 224 | - try { |
|
| 225 | - $copySuccess = $this->rootView->copy($source, $target); |
|
| 226 | - if ($copySuccess === false) { |
|
| 227 | - /* Copy failed, abort */ |
|
| 228 | - if ($this->rootView->file_exists($target)) { |
|
| 229 | - $this->rootView->unlink($target); |
|
| 230 | - } |
|
| 231 | - throw new \Exception('Copy failed for ' . $source); |
|
| 232 | - } |
|
| 233 | - $this->rootView->rename($target, $source); |
|
| 234 | - } catch (DecryptionFailedException $e) { |
|
| 235 | - if ($this->rootView->file_exists($target)) { |
|
| 236 | - $this->rootView->unlink($target); |
|
| 237 | - } |
|
| 238 | - return false; |
|
| 239 | - } |
|
| 240 | - |
|
| 241 | - return true; |
|
| 242 | - } |
|
| 243 | - |
|
| 244 | - /** |
|
| 245 | - * output one-time encryption passwords |
|
| 246 | - */ |
|
| 247 | - protected function outputPasswords(): void { |
|
| 248 | - $table = new Table($this->output); |
|
| 249 | - $table->setHeaders(['Username', 'Private key password']); |
|
| 250 | - |
|
| 251 | - //create rows |
|
| 252 | - $newPasswords = []; |
|
| 253 | - $unchangedPasswords = []; |
|
| 254 | - foreach ($this->userCache as $uid => $cache) { |
|
| 255 | - ['user' => $user, 'password' => $password] = $cache; |
|
| 256 | - if (empty($password)) { |
|
| 257 | - $unchangedPasswords[] = $uid; |
|
| 258 | - } else { |
|
| 259 | - $newPasswords[] = [$uid, $password]; |
|
| 260 | - } |
|
| 261 | - } |
|
| 262 | - |
|
| 263 | - if (empty($newPasswords)) { |
|
| 264 | - $this->output->writeln("\nAll users already had a key-pair, no further action needed.\n"); |
|
| 265 | - return; |
|
| 266 | - } |
|
| 267 | - |
|
| 268 | - $table->setRows($newPasswords); |
|
| 269 | - $table->render(); |
|
| 270 | - |
|
| 271 | - if (!empty($unchangedPasswords)) { |
|
| 272 | - $this->output->writeln("\nThe following users already had a key-pair which was reused without setting a new password:\n"); |
|
| 273 | - foreach ($unchangedPasswords as $uid) { |
|
| 274 | - $this->output->writeln(" $uid"); |
|
| 275 | - } |
|
| 276 | - } |
|
| 277 | - |
|
| 278 | - $this->writePasswordsToFile($newPasswords); |
|
| 279 | - |
|
| 280 | - $this->output->writeln(''); |
|
| 281 | - $question = new ConfirmationQuestion('Do you want to send the passwords directly to the users by mail? (y/n) ', true); |
|
| 282 | - if ($this->questionHelper->ask($this->input, $this->output, $question)) { |
|
| 283 | - $this->sendPasswordsByMail(); |
|
| 284 | - } |
|
| 285 | - } |
|
| 286 | - |
|
| 287 | - /** |
|
| 288 | - * write one-time encryption passwords to a csv file |
|
| 289 | - */ |
|
| 290 | - protected function writePasswordsToFile(array $passwords): void { |
|
| 291 | - $fp = $this->rootView->fopen('oneTimeEncryptionPasswords.csv', 'w'); |
|
| 292 | - foreach ($passwords as $pwd) { |
|
| 293 | - fputcsv($fp, $pwd); |
|
| 294 | - } |
|
| 295 | - fclose($fp); |
|
| 296 | - $this->output->writeln("\n"); |
|
| 297 | - $this->output->writeln('A list of all newly created passwords was written to data/oneTimeEncryptionPasswords.csv'); |
|
| 298 | - $this->output->writeln(''); |
|
| 299 | - $this->output->writeln('Each of these users need to login to the web interface, go to the'); |
|
| 300 | - $this->output->writeln('personal settings section "basic encryption module" and'); |
|
| 301 | - $this->output->writeln('update the private key password to match the login password again by'); |
|
| 302 | - $this->output->writeln('entering the one-time password into the "old log-in password" field'); |
|
| 303 | - $this->output->writeln('and their current login password'); |
|
| 304 | - } |
|
| 305 | - |
|
| 306 | - /** |
|
| 307 | - * setup user file system |
|
| 308 | - */ |
|
| 309 | - protected function setupUserFileSystem(IUser $user): void { |
|
| 310 | - $this->setupManager->tearDown(); |
|
| 311 | - $this->setupManager->setupForUser($user); |
|
| 312 | - } |
|
| 313 | - |
|
| 314 | - /** |
|
| 315 | - * generate one time password for the user and store it in a array |
|
| 316 | - * |
|
| 317 | - * @return string password |
|
| 318 | - */ |
|
| 319 | - protected function generateOneTimePassword(IUser $user): string { |
|
| 320 | - $password = $this->secureRandom->generate(16, ISecureRandom::CHAR_HUMAN_READABLE); |
|
| 321 | - $this->userCache[$user->getUID()] = ['password' => $password, 'user' => $user]; |
|
| 322 | - return $password; |
|
| 323 | - } |
|
| 324 | - |
|
| 325 | - /** |
|
| 326 | - * send encryption key passwords to the users by mail |
|
| 327 | - */ |
|
| 328 | - protected function sendPasswordsByMail(): void { |
|
| 329 | - $noMail = []; |
|
| 330 | - |
|
| 331 | - $this->output->writeln(''); |
|
| 332 | - $progress = new ProgressBar($this->output, count($this->userCache)); |
|
| 333 | - $progress->start(); |
|
| 334 | - |
|
| 335 | - foreach ($this->userCache as $uid => $cache) { |
|
| 336 | - ['user' => $user, 'password' => $password] = $cache; |
|
| 337 | - $progress->advance(); |
|
| 338 | - if (!empty($password)) { |
|
| 339 | - $recipient = $this->userManager->get($uid); |
|
| 340 | - if (!$recipient instanceof IUser) { |
|
| 341 | - continue; |
|
| 342 | - } |
|
| 343 | - |
|
| 344 | - $recipientDisplayName = $recipient->getDisplayName(); |
|
| 345 | - $to = $recipient->getEMailAddress(); |
|
| 346 | - |
|
| 347 | - if ($to === '' || $to === null) { |
|
| 348 | - $noMail[] = $uid; |
|
| 349 | - continue; |
|
| 350 | - } |
|
| 351 | - |
|
| 352 | - $l = $this->l10nFactory->get('encryption', $this->l10nFactory->getUserLanguage($recipient)); |
|
| 353 | - |
|
| 354 | - $template = $this->mailer->createEMailTemplate('encryption.encryptAllPassword', [ |
|
| 355 | - 'user' => $recipient->getUID(), |
|
| 356 | - 'password' => $password, |
|
| 357 | - ]); |
|
| 358 | - |
|
| 359 | - $template->setSubject($l->t('one-time password for server-side-encryption')); |
|
| 360 | - // 'Hey there,<br><br>The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br> |
|
| 361 | - // 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>' |
|
| 362 | - $template->addHeader(); |
|
| 363 | - $template->addHeading($l->t('Encryption password')); |
|
| 364 | - $template->addBodyText( |
|
| 365 | - $l->t('The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.', [htmlspecialchars($password)]), |
|
| 366 | - $l->t('The administration enabled server-side-encryption. Your files were encrypted using the password "%s".', $password) |
|
| 367 | - ); |
|
| 368 | - $template->addBodyText( |
|
| 369 | - $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 login password" field and your current login password.') |
|
| 370 | - ); |
|
| 371 | - $template->addFooter(); |
|
| 372 | - |
|
| 373 | - // send it out now |
|
| 374 | - try { |
|
| 375 | - $message = $this->mailer->createMessage(); |
|
| 376 | - $message->setTo([$to => $recipientDisplayName]); |
|
| 377 | - $message->useTemplate($template); |
|
| 378 | - $message->setAutoSubmitted(AutoSubmitted::VALUE_AUTO_GENERATED); |
|
| 379 | - $this->mailer->send($message); |
|
| 380 | - } catch (\Exception $e) { |
|
| 381 | - $noMail[] = $uid; |
|
| 382 | - } |
|
| 383 | - } |
|
| 384 | - } |
|
| 385 | - |
|
| 386 | - $progress->finish(); |
|
| 387 | - |
|
| 388 | - if (empty($noMail)) { |
|
| 389 | - $this->output->writeln("\n\nPassword successfully send to all users"); |
|
| 390 | - } else { |
|
| 391 | - $table = new Table($this->output); |
|
| 392 | - $table->setHeaders(['Username', 'Private key password']); |
|
| 393 | - $this->output->writeln("\n\nCould not send password to following users:\n"); |
|
| 394 | - $rows = []; |
|
| 395 | - foreach ($noMail as $uid) { |
|
| 396 | - $rows[] = [$uid, $this->userCache[$uid]['password']]; |
|
| 397 | - } |
|
| 398 | - $table->setRows($rows); |
|
| 399 | - $table->render(); |
|
| 400 | - } |
|
| 401 | - } |
|
| 38 | + /** @var array<string, array{password: string, user: IUser}> $userCache store one time passwords for the users */ |
|
| 39 | + protected array $userCache = []; |
|
| 40 | + protected OutputInterface $output; |
|
| 41 | + protected InputInterface $input; |
|
| 42 | + |
|
| 43 | + public function __construct( |
|
| 44 | + protected readonly Setup $userSetup, |
|
| 45 | + protected readonly IUserManager $userManager, |
|
| 46 | + protected readonly View $rootView, |
|
| 47 | + protected readonly KeyManager $keyManager, |
|
| 48 | + protected readonly Util $util, |
|
| 49 | + protected readonly IConfig $config, |
|
| 50 | + protected readonly IMailer $mailer, |
|
| 51 | + protected readonly IL10N $l, |
|
| 52 | + protected readonly IFactory $l10nFactory, |
|
| 53 | + protected readonly QuestionHelper $questionHelper, |
|
| 54 | + protected readonly ISecureRandom $secureRandom, |
|
| 55 | + protected readonly LoggerInterface $logger, |
|
| 56 | + protected readonly SetupManager $setupManager, |
|
| 57 | + ) { |
|
| 58 | + } |
|
| 59 | + |
|
| 60 | + /** |
|
| 61 | + * start to encrypt all files |
|
| 62 | + */ |
|
| 63 | + public function encryptAll(InputInterface $input, OutputInterface $output): void { |
|
| 64 | + $this->input = $input; |
|
| 65 | + $this->output = $output; |
|
| 66 | + |
|
| 67 | + $headline = 'Encrypt all files with the ' . Encryption::DISPLAY_NAME; |
|
| 68 | + $this->output->writeln("\n"); |
|
| 69 | + $this->output->writeln($headline); |
|
| 70 | + $this->output->writeln(str_pad('', strlen($headline), '=')); |
|
| 71 | + $this->output->writeln("\n"); |
|
| 72 | + |
|
| 73 | + if ($this->util->isMasterKeyEnabled()) { |
|
| 74 | + $this->output->writeln('Use master key to encrypt all files.'); |
|
| 75 | + $this->keyManager->validateMasterKey(); |
|
| 76 | + } else { |
|
| 77 | + //create private/public keys for each user and store the private key password |
|
| 78 | + $this->output->writeln('Create key-pair for every user'); |
|
| 79 | + $this->output->writeln('------------------------------'); |
|
| 80 | + $this->output->writeln(''); |
|
| 81 | + $this->output->writeln('This module will encrypt all files in the users files folder initially.'); |
|
| 82 | + $this->output->writeln('Already existing versions and files in the trash bin will not be encrypted.'); |
|
| 83 | + $this->output->writeln(''); |
|
| 84 | + $this->createKeyPairs(); |
|
| 85 | + } |
|
| 86 | + |
|
| 87 | + |
|
| 88 | + // output generated encryption key passwords |
|
| 89 | + if ($this->util->isMasterKeyEnabled() === false) { |
|
| 90 | + //send-out or display password list and write it to a file |
|
| 91 | + $this->output->writeln("\n"); |
|
| 92 | + $this->output->writeln('Generated encryption key passwords'); |
|
| 93 | + $this->output->writeln('----------------------------------'); |
|
| 94 | + $this->output->writeln(''); |
|
| 95 | + $this->outputPasswords(); |
|
| 96 | + } |
|
| 97 | + |
|
| 98 | + //setup users file system and encrypt all files one by one (take should encrypt setting of storage into account) |
|
| 99 | + $this->output->writeln("\n"); |
|
| 100 | + $this->output->writeln('Start to encrypt users files'); |
|
| 101 | + $this->output->writeln('----------------------------'); |
|
| 102 | + $this->output->writeln(''); |
|
| 103 | + $this->encryptAllUsersFiles(); |
|
| 104 | + $this->output->writeln("\n"); |
|
| 105 | + } |
|
| 106 | + |
|
| 107 | + /** |
|
| 108 | + * create key-pair for every user |
|
| 109 | + */ |
|
| 110 | + protected function createKeyPairs(): void { |
|
| 111 | + $this->output->writeln("\n"); |
|
| 112 | + $progress = new ProgressBar($this->output); |
|
| 113 | + $progress->setFormat(" %message% \n [%bar%]"); |
|
| 114 | + $progress->start(); |
|
| 115 | + |
|
| 116 | + foreach ($this->userManager->getSeenUsers() as $user) { |
|
| 117 | + if ($this->keyManager->userHasKeys($user->getUID()) === false) { |
|
| 118 | + $progress->setMessage('Create key-pair for ' . $user->getUID()); |
|
| 119 | + $progress->advance(); |
|
| 120 | + $this->setupUserFileSystem($user); |
|
| 121 | + $password = $this->generateOneTimePassword($user); |
|
| 122 | + $this->userSetup->setupUser($user->getUID(), $password); |
|
| 123 | + } else { |
|
| 124 | + // users which already have a key-pair will be stored with a |
|
| 125 | + // empty password and filtered out later |
|
| 126 | + $this->userCache[$user->getUID()] = ['password' => '', 'user' => $user]; |
|
| 127 | + } |
|
| 128 | + } |
|
| 129 | + |
|
| 130 | + $progress->setMessage('Key-pair created for all users'); |
|
| 131 | + $progress->finish(); |
|
| 132 | + } |
|
| 133 | + |
|
| 134 | + /** |
|
| 135 | + * iterate over all user and encrypt their files |
|
| 136 | + */ |
|
| 137 | + protected function encryptAllUsersFiles(): void { |
|
| 138 | + $this->output->writeln("\n"); |
|
| 139 | + $progress = new ProgressBar($this->output); |
|
| 140 | + $progress->setFormat(" %message% \n [%bar%]"); |
|
| 141 | + $progress->start(); |
|
| 142 | + $numberOfUsers = count($this->userCache); |
|
| 143 | + $userNo = 1; |
|
| 144 | + if ($this->util->isMasterKeyEnabled()) { |
|
| 145 | + $this->encryptAllUserFilesWithMasterKey($progress); |
|
| 146 | + } else { |
|
| 147 | + foreach ($this->userCache as $uid => $cache) { |
|
| 148 | + ['user' => $user, 'password' => $password] = $cache; |
|
| 149 | + $userCount = "$uid ($userNo of $numberOfUsers)"; |
|
| 150 | + $this->encryptUsersFiles($user, $progress, $userCount); |
|
| 151 | + $userNo++; |
|
| 152 | + } |
|
| 153 | + } |
|
| 154 | + $progress->setMessage('all files encrypted'); |
|
| 155 | + $progress->finish(); |
|
| 156 | + } |
|
| 157 | + |
|
| 158 | + /** |
|
| 159 | + * encrypt all user files with the master key |
|
| 160 | + */ |
|
| 161 | + protected function encryptAllUserFilesWithMasterKey(ProgressBar $progress): void { |
|
| 162 | + $userNo = 1; |
|
| 163 | + foreach ($this->userManager->getSeenUsers() as $user) { |
|
| 164 | + $userCount = $user->getUID() . " ($userNo)"; |
|
| 165 | + $this->encryptUsersFiles($user, $progress, $userCount); |
|
| 166 | + $userNo++; |
|
| 167 | + } |
|
| 168 | + } |
|
| 169 | + |
|
| 170 | + /** |
|
| 171 | + * encrypt files from the given user |
|
| 172 | + */ |
|
| 173 | + protected function encryptUsersFiles(IUser $user, ProgressBar $progress, string $userCount): void { |
|
| 174 | + $this->setupUserFileSystem($user); |
|
| 175 | + $uid = $user->getUID(); |
|
| 176 | + $directories = []; |
|
| 177 | + $directories[] = '/' . $uid . '/files'; |
|
| 178 | + |
|
| 179 | + while ($root = array_pop($directories)) { |
|
| 180 | + $content = $this->rootView->getDirectoryContent($root); |
|
| 181 | + foreach ($content as $file) { |
|
| 182 | + $path = $root . '/' . $file->getName(); |
|
| 183 | + if ($file->isShared()) { |
|
| 184 | + $progress->setMessage("Skip shared file/folder $path"); |
|
| 185 | + $progress->advance(); |
|
| 186 | + continue; |
|
| 187 | + } elseif ($file->getType() === FileInfo::TYPE_FOLDER) { |
|
| 188 | + $directories[] = $path; |
|
| 189 | + continue; |
|
| 190 | + } else { |
|
| 191 | + $progress->setMessage("encrypt files for user $userCount: $path"); |
|
| 192 | + $progress->advance(); |
|
| 193 | + try { |
|
| 194 | + if ($this->encryptFile($file, $path) === false) { |
|
| 195 | + $progress->setMessage("encrypt files for user $userCount: $path (already encrypted)"); |
|
| 196 | + $progress->advance(); |
|
| 197 | + } |
|
| 198 | + } catch (\Exception $e) { |
|
| 199 | + $progress->setMessage("Failed to encrypt path $path: " . $e->getMessage()); |
|
| 200 | + $progress->advance(); |
|
| 201 | + $this->logger->error( |
|
| 202 | + 'Failed to encrypt path {path}', |
|
| 203 | + [ |
|
| 204 | + 'user' => $uid, |
|
| 205 | + 'path' => $path, |
|
| 206 | + 'exception' => $e, |
|
| 207 | + ] |
|
| 208 | + ); |
|
| 209 | + } |
|
| 210 | + } |
|
| 211 | + } |
|
| 212 | + } |
|
| 213 | + } |
|
| 214 | + |
|
| 215 | + protected function encryptFile(FileInfo $fileInfo, string $path): bool { |
|
| 216 | + // skip already encrypted files |
|
| 217 | + if ($fileInfo->isEncrypted()) { |
|
| 218 | + return true; |
|
| 219 | + } |
|
| 220 | + |
|
| 221 | + $source = $path; |
|
| 222 | + $target = $path . '.encrypted.' . time(); |
|
| 223 | + |
|
| 224 | + try { |
|
| 225 | + $copySuccess = $this->rootView->copy($source, $target); |
|
| 226 | + if ($copySuccess === false) { |
|
| 227 | + /* Copy failed, abort */ |
|
| 228 | + if ($this->rootView->file_exists($target)) { |
|
| 229 | + $this->rootView->unlink($target); |
|
| 230 | + } |
|
| 231 | + throw new \Exception('Copy failed for ' . $source); |
|
| 232 | + } |
|
| 233 | + $this->rootView->rename($target, $source); |
|
| 234 | + } catch (DecryptionFailedException $e) { |
|
| 235 | + if ($this->rootView->file_exists($target)) { |
|
| 236 | + $this->rootView->unlink($target); |
|
| 237 | + } |
|
| 238 | + return false; |
|
| 239 | + } |
|
| 240 | + |
|
| 241 | + return true; |
|
| 242 | + } |
|
| 243 | + |
|
| 244 | + /** |
|
| 245 | + * output one-time encryption passwords |
|
| 246 | + */ |
|
| 247 | + protected function outputPasswords(): void { |
|
| 248 | + $table = new Table($this->output); |
|
| 249 | + $table->setHeaders(['Username', 'Private key password']); |
|
| 250 | + |
|
| 251 | + //create rows |
|
| 252 | + $newPasswords = []; |
|
| 253 | + $unchangedPasswords = []; |
|
| 254 | + foreach ($this->userCache as $uid => $cache) { |
|
| 255 | + ['user' => $user, 'password' => $password] = $cache; |
|
| 256 | + if (empty($password)) { |
|
| 257 | + $unchangedPasswords[] = $uid; |
|
| 258 | + } else { |
|
| 259 | + $newPasswords[] = [$uid, $password]; |
|
| 260 | + } |
|
| 261 | + } |
|
| 262 | + |
|
| 263 | + if (empty($newPasswords)) { |
|
| 264 | + $this->output->writeln("\nAll users already had a key-pair, no further action needed.\n"); |
|
| 265 | + return; |
|
| 266 | + } |
|
| 267 | + |
|
| 268 | + $table->setRows($newPasswords); |
|
| 269 | + $table->render(); |
|
| 270 | + |
|
| 271 | + if (!empty($unchangedPasswords)) { |
|
| 272 | + $this->output->writeln("\nThe following users already had a key-pair which was reused without setting a new password:\n"); |
|
| 273 | + foreach ($unchangedPasswords as $uid) { |
|
| 274 | + $this->output->writeln(" $uid"); |
|
| 275 | + } |
|
| 276 | + } |
|
| 277 | + |
|
| 278 | + $this->writePasswordsToFile($newPasswords); |
|
| 279 | + |
|
| 280 | + $this->output->writeln(''); |
|
| 281 | + $question = new ConfirmationQuestion('Do you want to send the passwords directly to the users by mail? (y/n) ', true); |
|
| 282 | + if ($this->questionHelper->ask($this->input, $this->output, $question)) { |
|
| 283 | + $this->sendPasswordsByMail(); |
|
| 284 | + } |
|
| 285 | + } |
|
| 286 | + |
|
| 287 | + /** |
|
| 288 | + * write one-time encryption passwords to a csv file |
|
| 289 | + */ |
|
| 290 | + protected function writePasswordsToFile(array $passwords): void { |
|
| 291 | + $fp = $this->rootView->fopen('oneTimeEncryptionPasswords.csv', 'w'); |
|
| 292 | + foreach ($passwords as $pwd) { |
|
| 293 | + fputcsv($fp, $pwd); |
|
| 294 | + } |
|
| 295 | + fclose($fp); |
|
| 296 | + $this->output->writeln("\n"); |
|
| 297 | + $this->output->writeln('A list of all newly created passwords was written to data/oneTimeEncryptionPasswords.csv'); |
|
| 298 | + $this->output->writeln(''); |
|
| 299 | + $this->output->writeln('Each of these users need to login to the web interface, go to the'); |
|
| 300 | + $this->output->writeln('personal settings section "basic encryption module" and'); |
|
| 301 | + $this->output->writeln('update the private key password to match the login password again by'); |
|
| 302 | + $this->output->writeln('entering the one-time password into the "old log-in password" field'); |
|
| 303 | + $this->output->writeln('and their current login password'); |
|
| 304 | + } |
|
| 305 | + |
|
| 306 | + /** |
|
| 307 | + * setup user file system |
|
| 308 | + */ |
|
| 309 | + protected function setupUserFileSystem(IUser $user): void { |
|
| 310 | + $this->setupManager->tearDown(); |
|
| 311 | + $this->setupManager->setupForUser($user); |
|
| 312 | + } |
|
| 313 | + |
|
| 314 | + /** |
|
| 315 | + * generate one time password for the user and store it in a array |
|
| 316 | + * |
|
| 317 | + * @return string password |
|
| 318 | + */ |
|
| 319 | + protected function generateOneTimePassword(IUser $user): string { |
|
| 320 | + $password = $this->secureRandom->generate(16, ISecureRandom::CHAR_HUMAN_READABLE); |
|
| 321 | + $this->userCache[$user->getUID()] = ['password' => $password, 'user' => $user]; |
|
| 322 | + return $password; |
|
| 323 | + } |
|
| 324 | + |
|
| 325 | + /** |
|
| 326 | + * send encryption key passwords to the users by mail |
|
| 327 | + */ |
|
| 328 | + protected function sendPasswordsByMail(): void { |
|
| 329 | + $noMail = []; |
|
| 330 | + |
|
| 331 | + $this->output->writeln(''); |
|
| 332 | + $progress = new ProgressBar($this->output, count($this->userCache)); |
|
| 333 | + $progress->start(); |
|
| 334 | + |
|
| 335 | + foreach ($this->userCache as $uid => $cache) { |
|
| 336 | + ['user' => $user, 'password' => $password] = $cache; |
|
| 337 | + $progress->advance(); |
|
| 338 | + if (!empty($password)) { |
|
| 339 | + $recipient = $this->userManager->get($uid); |
|
| 340 | + if (!$recipient instanceof IUser) { |
|
| 341 | + continue; |
|
| 342 | + } |
|
| 343 | + |
|
| 344 | + $recipientDisplayName = $recipient->getDisplayName(); |
|
| 345 | + $to = $recipient->getEMailAddress(); |
|
| 346 | + |
|
| 347 | + if ($to === '' || $to === null) { |
|
| 348 | + $noMail[] = $uid; |
|
| 349 | + continue; |
|
| 350 | + } |
|
| 351 | + |
|
| 352 | + $l = $this->l10nFactory->get('encryption', $this->l10nFactory->getUserLanguage($recipient)); |
|
| 353 | + |
|
| 354 | + $template = $this->mailer->createEMailTemplate('encryption.encryptAllPassword', [ |
|
| 355 | + 'user' => $recipient->getUID(), |
|
| 356 | + 'password' => $password, |
|
| 357 | + ]); |
|
| 358 | + |
|
| 359 | + $template->setSubject($l->t('one-time password for server-side-encryption')); |
|
| 360 | + // 'Hey there,<br><br>The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br> |
|
| 361 | + // 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>' |
|
| 362 | + $template->addHeader(); |
|
| 363 | + $template->addHeading($l->t('Encryption password')); |
|
| 364 | + $template->addBodyText( |
|
| 365 | + $l->t('The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.', [htmlspecialchars($password)]), |
|
| 366 | + $l->t('The administration enabled server-side-encryption. Your files were encrypted using the password "%s".', $password) |
|
| 367 | + ); |
|
| 368 | + $template->addBodyText( |
|
| 369 | + $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 login password" field and your current login password.') |
|
| 370 | + ); |
|
| 371 | + $template->addFooter(); |
|
| 372 | + |
|
| 373 | + // send it out now |
|
| 374 | + try { |
|
| 375 | + $message = $this->mailer->createMessage(); |
|
| 376 | + $message->setTo([$to => $recipientDisplayName]); |
|
| 377 | + $message->useTemplate($template); |
|
| 378 | + $message->setAutoSubmitted(AutoSubmitted::VALUE_AUTO_GENERATED); |
|
| 379 | + $this->mailer->send($message); |
|
| 380 | + } catch (\Exception $e) { |
|
| 381 | + $noMail[] = $uid; |
|
| 382 | + } |
|
| 383 | + } |
|
| 384 | + } |
|
| 385 | + |
|
| 386 | + $progress->finish(); |
|
| 387 | + |
|
| 388 | + if (empty($noMail)) { |
|
| 389 | + $this->output->writeln("\n\nPassword successfully send to all users"); |
|
| 390 | + } else { |
|
| 391 | + $table = new Table($this->output); |
|
| 392 | + $table->setHeaders(['Username', 'Private key password']); |
|
| 393 | + $this->output->writeln("\n\nCould not send password to following users:\n"); |
|
| 394 | + $rows = []; |
|
| 395 | + foreach ($noMail as $uid) { |
|
| 396 | + $rows[] = [$uid, $this->userCache[$uid]['password']]; |
|
| 397 | + } |
|
| 398 | + $table->setRows($rows); |
|
| 399 | + $table->render(); |
|
| 400 | + } |
|
| 401 | + } |
|
| 402 | 402 | } |
@@ -17,93 +17,93 @@ |
||
| 17 | 17 | use Symfony\Component\Console\Question\ConfirmationQuestion; |
| 18 | 18 | |
| 19 | 19 | class EncryptAll extends Command { |
| 20 | - protected bool $wasTrashbinEnabled = false; |
|
| 20 | + protected bool $wasTrashbinEnabled = false; |
|
| 21 | 21 | |
| 22 | - public function __construct( |
|
| 23 | - protected IManager $encryptionManager, |
|
| 24 | - protected IAppManager $appManager, |
|
| 25 | - protected IConfig $config, |
|
| 26 | - protected QuestionHelper $questionHelper, |
|
| 27 | - ) { |
|
| 28 | - parent::__construct(); |
|
| 29 | - } |
|
| 22 | + public function __construct( |
|
| 23 | + protected IManager $encryptionManager, |
|
| 24 | + protected IAppManager $appManager, |
|
| 25 | + protected IConfig $config, |
|
| 26 | + protected QuestionHelper $questionHelper, |
|
| 27 | + ) { |
|
| 28 | + parent::__construct(); |
|
| 29 | + } |
|
| 30 | 30 | |
| 31 | - /** |
|
| 32 | - * Set maintenance mode and disable the trashbin app |
|
| 33 | - */ |
|
| 34 | - protected function forceMaintenanceAndTrashbin(): void { |
|
| 35 | - $this->wasTrashbinEnabled = (bool)$this->appManager->isEnabledForUser('files_trashbin'); |
|
| 36 | - $this->config->setSystemValue('maintenance', true); |
|
| 37 | - $this->appManager->disableApp('files_trashbin'); |
|
| 38 | - } |
|
| 31 | + /** |
|
| 32 | + * Set maintenance mode and disable the trashbin app |
|
| 33 | + */ |
|
| 34 | + protected function forceMaintenanceAndTrashbin(): void { |
|
| 35 | + $this->wasTrashbinEnabled = (bool)$this->appManager->isEnabledForUser('files_trashbin'); |
|
| 36 | + $this->config->setSystemValue('maintenance', true); |
|
| 37 | + $this->appManager->disableApp('files_trashbin'); |
|
| 38 | + } |
|
| 39 | 39 | |
| 40 | - /** |
|
| 41 | - * Reset the maintenance mode and re-enable the trashbin app |
|
| 42 | - */ |
|
| 43 | - protected function resetMaintenanceAndTrashbin(): void { |
|
| 44 | - $this->config->setSystemValue('maintenance', false); |
|
| 45 | - if ($this->wasTrashbinEnabled) { |
|
| 46 | - $this->appManager->enableApp('files_trashbin'); |
|
| 47 | - } |
|
| 48 | - } |
|
| 40 | + /** |
|
| 41 | + * Reset the maintenance mode and re-enable the trashbin app |
|
| 42 | + */ |
|
| 43 | + protected function resetMaintenanceAndTrashbin(): void { |
|
| 44 | + $this->config->setSystemValue('maintenance', false); |
|
| 45 | + if ($this->wasTrashbinEnabled) { |
|
| 46 | + $this->appManager->enableApp('files_trashbin'); |
|
| 47 | + } |
|
| 48 | + } |
|
| 49 | 49 | |
| 50 | - protected function configure() { |
|
| 51 | - parent::configure(); |
|
| 50 | + protected function configure() { |
|
| 51 | + parent::configure(); |
|
| 52 | 52 | |
| 53 | - $this->setName('encryption:encrypt-all'); |
|
| 54 | - $this->setDescription('Encrypt all files for all users'); |
|
| 55 | - $this->setHelp( |
|
| 56 | - 'This will encrypt all files for all users. ' |
|
| 57 | - . 'Please make sure that no user access his files during this process!' |
|
| 58 | - ); |
|
| 59 | - } |
|
| 53 | + $this->setName('encryption:encrypt-all'); |
|
| 54 | + $this->setDescription('Encrypt all files for all users'); |
|
| 55 | + $this->setHelp( |
|
| 56 | + 'This will encrypt all files for all users. ' |
|
| 57 | + . 'Please make sure that no user access his files during this process!' |
|
| 58 | + ); |
|
| 59 | + } |
|
| 60 | 60 | |
| 61 | - /** |
|
| 62 | - * @throws \Exception |
|
| 63 | - */ |
|
| 64 | - protected function execute(InputInterface $input, OutputInterface $output): int { |
|
| 65 | - if (!$input->isInteractive() && !$input->getOption('no-interaction')) { |
|
| 66 | - $output->writeln('Invalid TTY.'); |
|
| 67 | - $output->writeln('If you are trying to execute the command in a Docker '); |
|
| 68 | - $output->writeln("container, do not forget to execute 'docker exec' with"); |
|
| 69 | - $output->writeln("the '-i' and '-t' options."); |
|
| 70 | - $output->writeln(''); |
|
| 71 | - return 1; |
|
| 72 | - } |
|
| 61 | + /** |
|
| 62 | + * @throws \Exception |
|
| 63 | + */ |
|
| 64 | + protected function execute(InputInterface $input, OutputInterface $output): int { |
|
| 65 | + if (!$input->isInteractive() && !$input->getOption('no-interaction')) { |
|
| 66 | + $output->writeln('Invalid TTY.'); |
|
| 67 | + $output->writeln('If you are trying to execute the command in a Docker '); |
|
| 68 | + $output->writeln("container, do not forget to execute 'docker exec' with"); |
|
| 69 | + $output->writeln("the '-i' and '-t' options."); |
|
| 70 | + $output->writeln(''); |
|
| 71 | + return 1; |
|
| 72 | + } |
|
| 73 | 73 | |
| 74 | - if ($this->encryptionManager->isEnabled() === false) { |
|
| 75 | - throw new \Exception('Server side encryption is not enabled'); |
|
| 76 | - } |
|
| 74 | + if ($this->encryptionManager->isEnabled() === false) { |
|
| 75 | + throw new \Exception('Server side encryption is not enabled'); |
|
| 76 | + } |
|
| 77 | 77 | |
| 78 | - if ($this->config->getSystemValueBool('maintenance')) { |
|
| 79 | - $output->writeln('<error>This command cannot be run with maintenance mode enabled.</error>'); |
|
| 80 | - return self::FAILURE; |
|
| 81 | - } |
|
| 78 | + if ($this->config->getSystemValueBool('maintenance')) { |
|
| 79 | + $output->writeln('<error>This command cannot be run with maintenance mode enabled.</error>'); |
|
| 80 | + return self::FAILURE; |
|
| 81 | + } |
|
| 82 | 82 | |
| 83 | - $output->writeln("\n"); |
|
| 84 | - $output->writeln('You are about to encrypt all files stored in your Nextcloud installation.'); |
|
| 85 | - $output->writeln('Depending on the number of available files, and their size, this may take quite some time.'); |
|
| 86 | - $output->writeln('Please ensure that no user accesses their files during this time!'); |
|
| 87 | - $output->writeln('Note: The encryption module you use determines which files get encrypted.'); |
|
| 88 | - $output->writeln(''); |
|
| 89 | - $question = new ConfirmationQuestion('Do you really want to continue? (y/n) ', true); |
|
| 90 | - if ($this->questionHelper->ask($input, $output, $question)) { |
|
| 91 | - //run encryption with the answer yes in interactive mode |
|
| 92 | - $this->forceMaintenanceAndTrashbin(); |
|
| 83 | + $output->writeln("\n"); |
|
| 84 | + $output->writeln('You are about to encrypt all files stored in your Nextcloud installation.'); |
|
| 85 | + $output->writeln('Depending on the number of available files, and their size, this may take quite some time.'); |
|
| 86 | + $output->writeln('Please ensure that no user accesses their files during this time!'); |
|
| 87 | + $output->writeln('Note: The encryption module you use determines which files get encrypted.'); |
|
| 88 | + $output->writeln(''); |
|
| 89 | + $question = new ConfirmationQuestion('Do you really want to continue? (y/n) ', true); |
|
| 90 | + if ($this->questionHelper->ask($input, $output, $question)) { |
|
| 91 | + //run encryption with the answer yes in interactive mode |
|
| 92 | + $this->forceMaintenanceAndTrashbin(); |
|
| 93 | 93 | |
| 94 | - try { |
|
| 95 | - $defaultModule = $this->encryptionManager->getEncryptionModule(); |
|
| 96 | - $defaultModule->encryptAll($input, $output); |
|
| 97 | - } catch (\Exception $ex) { |
|
| 98 | - $this->resetMaintenanceAndTrashbin(); |
|
| 99 | - throw $ex; |
|
| 100 | - } |
|
| 94 | + try { |
|
| 95 | + $defaultModule = $this->encryptionManager->getEncryptionModule(); |
|
| 96 | + $defaultModule->encryptAll($input, $output); |
|
| 97 | + } catch (\Exception $ex) { |
|
| 98 | + $this->resetMaintenanceAndTrashbin(); |
|
| 99 | + throw $ex; |
|
| 100 | + } |
|
| 101 | 101 | |
| 102 | - $this->resetMaintenanceAndTrashbin(); |
|
| 103 | - return self::SUCCESS; |
|
| 104 | - } |
|
| 105 | - //abort on no in interactive mode |
|
| 106 | - $output->writeln('aborted'); |
|
| 107 | - return self::FAILURE; |
|
| 108 | - } |
|
| 102 | + $this->resetMaintenanceAndTrashbin(); |
|
| 103 | + return self::SUCCESS; |
|
| 104 | + } |
|
| 105 | + //abort on no in interactive mode |
|
| 106 | + $output->writeln('aborted'); |
|
| 107 | + return self::FAILURE; |
|
| 108 | + } |
|
| 109 | 109 | } |