DecryptAll.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OC\Encryption;
  8. use OC\Encryption\Exceptions\DecryptionFailedException;
  9. use OC\Files\View;
  10. use OCP\Encryption\IEncryptionModule;
  11. use OCP\Encryption\IManager;
  12. use OCP\IUserManager;
  13. use Symfony\Component\Console\Helper\ProgressBar;
  14. use Symfony\Component\Console\Input\InputInterface;
  15. use Symfony\Component\Console\Output\OutputInterface;
  16. class DecryptAll {
  17. /** @var OutputInterface */
  18. protected $output;
  19. /** @var InputInterface */
  20. protected $input;
  21. /** @var array files which couldn't be decrypted */
  22. protected $failed;
  23. public function __construct(
  24. protected IManager $encryptionManager,
  25. protected IUserManager $userManager,
  26. protected View $rootView
  27. ) {
  28. $this->failed = [];
  29. }
  30. /**
  31. * start to decrypt all files
  32. *
  33. * @param InputInterface $input
  34. * @param OutputInterface $output
  35. * @param string $user which users data folder should be decrypted, default = all users
  36. * @return bool
  37. * @throws \Exception
  38. */
  39. public function decryptAll(InputInterface $input, OutputInterface $output, $user = '') {
  40. $this->input = $input;
  41. $this->output = $output;
  42. if ($user !== '' && $this->userManager->userExists($user) === false) {
  43. $this->output->writeln('User "' . $user . '" does not exist. Please check the username and try again');
  44. return false;
  45. }
  46. $this->output->writeln('prepare encryption modules...');
  47. if ($this->prepareEncryptionModules($user) === false) {
  48. return false;
  49. }
  50. $this->output->writeln(' done.');
  51. $this->decryptAllUsersFiles($user);
  52. if (empty($this->failed)) {
  53. $this->output->writeln('all files could be decrypted successfully!');
  54. } else {
  55. $this->output->writeln('Files for following users couldn\'t be decrypted, ');
  56. $this->output->writeln('maybe the user is not set up in a way that supports this operation: ');
  57. foreach ($this->failed as $uid => $paths) {
  58. $this->output->writeln(' ' . $uid);
  59. foreach ($paths as $path) {
  60. $this->output->writeln(' ' . $path);
  61. }
  62. }
  63. $this->output->writeln('');
  64. }
  65. return true;
  66. }
  67. /**
  68. * prepare encryption modules to perform the decrypt all function
  69. *
  70. * @param $user
  71. * @return bool
  72. */
  73. protected function prepareEncryptionModules($user) {
  74. // prepare all encryption modules for decrypt all
  75. $encryptionModules = $this->encryptionManager->getEncryptionModules();
  76. foreach ($encryptionModules as $moduleDesc) {
  77. /** @var IEncryptionModule $module */
  78. $module = call_user_func($moduleDesc['callback']);
  79. $this->output->writeln('');
  80. $this->output->writeln('Prepare "' . $module->getDisplayName() . '"');
  81. $this->output->writeln('');
  82. if ($module->prepareDecryptAll($this->input, $this->output, $user) === false) {
  83. $this->output->writeln('Module "' . $moduleDesc['displayName'] . '" does not support the functionality to decrypt all files again or the initialization of the module failed!');
  84. return false;
  85. }
  86. }
  87. return true;
  88. }
  89. /**
  90. * iterate over all user and encrypt their files
  91. *
  92. * @param string $user which users files should be decrypted, default = all users
  93. */
  94. protected function decryptAllUsersFiles($user = '') {
  95. $this->output->writeln("\n");
  96. $userList = [];
  97. if ($user === '') {
  98. $fetchUsersProgress = new ProgressBar($this->output);
  99. $fetchUsersProgress->setFormat(" %message% \n [%bar%]");
  100. $fetchUsersProgress->start();
  101. $fetchUsersProgress->setMessage("Fetch list of users...");
  102. $fetchUsersProgress->advance();
  103. foreach ($this->userManager->getBackends() as $backend) {
  104. $limit = 500;
  105. $offset = 0;
  106. do {
  107. $users = $backend->getUsers('', $limit, $offset);
  108. foreach ($users as $user) {
  109. $userList[] = $user;
  110. }
  111. $offset += $limit;
  112. $fetchUsersProgress->advance();
  113. } while (count($users) >= $limit);
  114. $fetchUsersProgress->setMessage("Fetch list of users... finished");
  115. $fetchUsersProgress->finish();
  116. }
  117. } else {
  118. $userList[] = $user;
  119. }
  120. $this->output->writeln("\n\n");
  121. $progress = new ProgressBar($this->output);
  122. $progress->setFormat(" %message% \n [%bar%]");
  123. $progress->start();
  124. $progress->setMessage("starting to decrypt files...");
  125. $progress->advance();
  126. $numberOfUsers = count($userList);
  127. $userNo = 1;
  128. foreach ($userList as $uid) {
  129. $userCount = "$uid ($userNo of $numberOfUsers)";
  130. $this->decryptUsersFiles($uid, $progress, $userCount);
  131. $userNo++;
  132. }
  133. $progress->setMessage("starting to decrypt files... finished");
  134. $progress->finish();
  135. $this->output->writeln("\n\n");
  136. }
  137. /**
  138. * encrypt files from the given user
  139. *
  140. * @param string $uid
  141. * @param ProgressBar $progress
  142. * @param string $userCount
  143. */
  144. protected function decryptUsersFiles($uid, ProgressBar $progress, $userCount) {
  145. $this->setupUserFS($uid);
  146. $directories = [];
  147. $directories[] = '/' . $uid . '/files';
  148. while ($root = array_pop($directories)) {
  149. $content = $this->rootView->getDirectoryContent($root);
  150. foreach ($content as $file) {
  151. // only decrypt files owned by the user
  152. if ($file->getStorage()->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {
  153. continue;
  154. }
  155. $path = $root . '/' . $file['name'];
  156. if ($this->rootView->is_dir($path)) {
  157. $directories[] = $path;
  158. continue;
  159. } else {
  160. try {
  161. $progress->setMessage("decrypt files for user $userCount: $path");
  162. $progress->advance();
  163. if ($file->isEncrypted() === false) {
  164. $progress->setMessage("decrypt files for user $userCount: $path (already decrypted)");
  165. $progress->advance();
  166. } else {
  167. if ($this->decryptFile($path) === false) {
  168. $progress->setMessage("decrypt files for user $userCount: $path (already decrypted)");
  169. $progress->advance();
  170. }
  171. }
  172. } catch (\Exception $e) {
  173. if (isset($this->failed[$uid])) {
  174. $this->failed[$uid][] = $path;
  175. } else {
  176. $this->failed[$uid] = [$path];
  177. }
  178. }
  179. }
  180. }
  181. }
  182. }
  183. /**
  184. * encrypt file
  185. *
  186. * @param string $path
  187. * @return bool
  188. */
  189. protected function decryptFile($path) {
  190. // skip already decrypted files
  191. $fileInfo = $this->rootView->getFileInfo($path);
  192. if ($fileInfo !== false && !$fileInfo->isEncrypted()) {
  193. return true;
  194. }
  195. $source = $path;
  196. $target = $path . '.decrypted.' . $this->getTimestamp();
  197. try {
  198. $this->rootView->copy($source, $target);
  199. $this->rootView->touch($target, $fileInfo->getMTime());
  200. $this->rootView->rename($target, $source);
  201. } catch (DecryptionFailedException $e) {
  202. if ($this->rootView->file_exists($target)) {
  203. $this->rootView->unlink($target);
  204. }
  205. return false;
  206. }
  207. return true;
  208. }
  209. /**
  210. * get current timestamp
  211. *
  212. * @return int
  213. */
  214. protected function getTimestamp() {
  215. return time();
  216. }
  217. /**
  218. * setup user file system
  219. *
  220. * @param string $uid
  221. */
  222. protected function setupUserFS($uid) {
  223. \OC_Util::tearDownFS();
  224. \OC_Util::setupFS($uid);
  225. }
  226. }