ChangeKeyStorageRoot.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <?php
  2. /**
  3. * @author Björn Schießle <bjoern@schiessle.org>
  4. *
  5. * @copyright Copyright (c) 2016, ownCloud, Inc.
  6. * @license AGPL-3.0
  7. *
  8. * This code is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License, version 3,
  10. * as published by the Free Software Foundation.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License, version 3,
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>
  19. *
  20. */
  21. namespace OC\Core\Command\Encryption;
  22. use OC\Encryption\Keys\Storage;
  23. use OC\Encryption\Util;
  24. use OC\Files\Filesystem;
  25. use OC\Files\View;
  26. use OCP\IConfig;
  27. use OCP\IUserManager;
  28. use Symfony\Component\Console\Command\Command;
  29. use Symfony\Component\Console\Helper\ProgressBar;
  30. use Symfony\Component\Console\Helper\QuestionHelper;
  31. use Symfony\Component\Console\Input\InputArgument;
  32. use Symfony\Component\Console\Input\InputInterface;
  33. use Symfony\Component\Console\Output\OutputInterface;
  34. use Symfony\Component\Console\Question\ConfirmationQuestion;
  35. class ChangeKeyStorageRoot extends Command {
  36. /** @var View */
  37. protected $rootView;
  38. /** @var IUserManager */
  39. protected $userManager;
  40. /** @var IConfig */
  41. protected $config;
  42. /** @var Util */
  43. protected $util;
  44. /** @var QuestionHelper */
  45. protected $questionHelper;
  46. /**
  47. * @param View $view
  48. * @param IUserManager $userManager
  49. * @param IConfig $config
  50. * @param Util $util
  51. * @param QuestionHelper $questionHelper
  52. */
  53. public function __construct(View $view, IUserManager $userManager, IConfig $config, Util $util, QuestionHelper $questionHelper) {
  54. parent::__construct();
  55. $this->rootView = $view;
  56. $this->userManager = $userManager;
  57. $this->config = $config;
  58. $this->util = $util;
  59. $this->questionHelper = $questionHelper;
  60. }
  61. protected function configure() {
  62. parent::configure();
  63. $this
  64. ->setName('encryption:change-key-storage-root')
  65. ->setDescription('Change key storage root')
  66. ->addArgument(
  67. 'newRoot',
  68. InputArgument::OPTIONAL,
  69. 'new root of the key storage relative to the data folder'
  70. );
  71. }
  72. protected function execute(InputInterface $input, OutputInterface $output) {
  73. $oldRoot = $this->util->getKeyStorageRoot();
  74. $newRoot = $input->getArgument('newRoot');
  75. if ($newRoot === null) {
  76. $question = new ConfirmationQuestion('No storage root given, do you want to reset the key storage root to the default location? (y/n) ', false);
  77. if (!$this->questionHelper->ask($input, $output, $question)) {
  78. return;
  79. }
  80. $newRoot = '';
  81. }
  82. $oldRootDescription = $oldRoot !== '' ? $oldRoot : 'default storage location';
  83. $newRootDescription = $newRoot !== '' ? $newRoot : 'default storage location';
  84. $output->writeln("Change key storage root from <info>$oldRootDescription</info> to <info>$newRootDescription</info>");
  85. $success = $this->moveAllKeys($oldRoot, $newRoot, $output);
  86. if ($success) {
  87. $this->util->setKeyStorageRoot($newRoot);
  88. $output->writeln('');
  89. $output->writeln("Key storage root successfully changed to <info>$newRootDescription</info>");
  90. }
  91. }
  92. /**
  93. * move keys to new key storage root
  94. *
  95. * @param string $oldRoot
  96. * @param string $newRoot
  97. * @param OutputInterface $output
  98. * @return bool
  99. * @throws \Exception
  100. */
  101. protected function moveAllKeys($oldRoot, $newRoot, OutputInterface $output) {
  102. $output->writeln("Start to move keys:");
  103. if ($this->rootView->is_dir(($oldRoot)) === false) {
  104. $output->writeln("No old keys found: Nothing needs to be moved");
  105. return false;
  106. }
  107. $this->prepareNewRoot($newRoot);
  108. $this->moveSystemKeys($oldRoot, $newRoot);
  109. $this->moveUserKeys($oldRoot, $newRoot, $output);
  110. return true;
  111. }
  112. /**
  113. * prepare new key storage
  114. *
  115. * @param string $newRoot
  116. * @throws \Exception
  117. */
  118. protected function prepareNewRoot($newRoot) {
  119. if ($this->rootView->is_dir($newRoot) === false) {
  120. throw new \Exception("New root folder doesn't exist. Please create the folder or check the permissions and try again.");
  121. }
  122. $result = $this->rootView->file_put_contents(
  123. $newRoot . '/' . Storage::KEY_STORAGE_MARKER,
  124. 'ownCloud will detect this folder as key storage root only if this file exists'
  125. );
  126. if ($result === false) {
  127. throw new \Exception("Can't write to new root folder. Please check the permissions and try again");
  128. }
  129. }
  130. /**
  131. * move system key folder
  132. *
  133. * @param string $oldRoot
  134. * @param string $newRoot
  135. */
  136. protected function moveSystemKeys($oldRoot, $newRoot) {
  137. if (
  138. $this->rootView->is_dir($oldRoot . '/files_encryption') &&
  139. $this->targetExists($newRoot . '/files_encryption') === false
  140. ) {
  141. $this->rootView->rename($oldRoot . '/files_encryption', $newRoot . '/files_encryption');
  142. }
  143. }
  144. /**
  145. * setup file system for the given user
  146. *
  147. * @param string $uid
  148. */
  149. protected function setupUserFS($uid) {
  150. \OC_Util::tearDownFS();
  151. \OC_Util::setupFS($uid);
  152. }
  153. /**
  154. * iterate over each user and move the keys to the new storage
  155. *
  156. * @param string $oldRoot
  157. * @param string $newRoot
  158. * @param OutputInterface $output
  159. */
  160. protected function moveUserKeys($oldRoot, $newRoot, OutputInterface $output) {
  161. $progress = new ProgressBar($output);
  162. $progress->start();
  163. foreach($this->userManager->getBackends() as $backend) {
  164. $limit = 500;
  165. $offset = 0;
  166. do {
  167. $users = $backend->getUsers('', $limit, $offset);
  168. foreach ($users as $user) {
  169. $progress->advance();
  170. $this->setupUserFS($user);
  171. $this->moveUserEncryptionFolder($user, $oldRoot, $newRoot);
  172. }
  173. $offset += $limit;
  174. } while(count($users) >= $limit);
  175. }
  176. $progress->finish();
  177. }
  178. /**
  179. * move user encryption folder to new root folder
  180. *
  181. * @param string $user
  182. * @param string $oldRoot
  183. * @param string $newRoot
  184. * @throws \Exception
  185. */
  186. protected function moveUserEncryptionFolder($user, $oldRoot, $newRoot) {
  187. if ($this->userManager->userExists($user)) {
  188. $source = $oldRoot . '/' . $user . '/files_encryption';
  189. $target = $newRoot . '/' . $user . '/files_encryption';
  190. if (
  191. $this->rootView->is_dir($source) &&
  192. $this->targetExists($target) === false
  193. ) {
  194. $this->prepareParentFolder($newRoot . '/' . $user);
  195. $this->rootView->rename($source, $target);
  196. }
  197. }
  198. }
  199. /**
  200. * Make preparations to filesystem for saving a key file
  201. *
  202. * @param string $path relative to data/
  203. */
  204. protected function prepareParentFolder($path) {
  205. $path = Filesystem::normalizePath($path);
  206. // If the file resides within a subdirectory, create it
  207. if ($this->rootView->file_exists($path) === false) {
  208. $sub_dirs = explode('/', ltrim($path, '/'));
  209. $dir = '';
  210. foreach ($sub_dirs as $sub_dir) {
  211. $dir .= '/' . $sub_dir;
  212. if ($this->rootView->file_exists($dir) === false) {
  213. $this->rootView->mkdir($dir);
  214. }
  215. }
  216. }
  217. }
  218. /**
  219. * check if target already exists
  220. *
  221. * @param $path
  222. * @return bool
  223. * @throws \Exception
  224. */
  225. protected function targetExists($path) {
  226. if ($this->rootView->file_exists($path)) {
  227. throw new \Exception("new folder '$path' already exists");
  228. }
  229. return false;
  230. }
  231. }