DecryptAll.php 7.6 KB

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