ChangeKeyStorageRootTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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 Tests\Core\Command\Encryption;
  8. use OC\Core\Command\Encryption\ChangeKeyStorageRoot;
  9. use OC\Encryption\Util;
  10. use OC\Files\View;
  11. use OCP\IConfig;
  12. use OCP\IUserManager;
  13. use OCP\UserInterface;
  14. use Symfony\Component\Console\Formatter\OutputFormatterInterface;
  15. use Symfony\Component\Console\Helper\QuestionHelper;
  16. use Symfony\Component\Console\Input\InputInterface;
  17. use Symfony\Component\Console\Output\OutputInterface;
  18. use Test\TestCase;
  19. class ChangeKeyStorageRootTest extends TestCase {
  20. /** @var ChangeKeyStorageRoot */
  21. protected $changeKeyStorageRoot;
  22. /** @var View | \PHPUnit\Framework\MockObject\MockObject */
  23. protected $view;
  24. /** @var IUserManager | \PHPUnit\Framework\MockObject\MockObject */
  25. protected $userManager;
  26. /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */
  27. protected $config;
  28. /** @var Util | \PHPUnit\Framework\MockObject\MockObject */
  29. protected $util;
  30. /** @var QuestionHelper | \PHPUnit\Framework\MockObject\MockObject */
  31. protected $questionHelper;
  32. /** @var InputInterface | \PHPUnit\Framework\MockObject\MockObject */
  33. protected $inputInterface;
  34. /** @var OutputInterface | \PHPUnit\Framework\MockObject\MockObject */
  35. protected $outputInterface;
  36. /** @var \OCP\UserInterface | \PHPUnit\Framework\MockObject\MockObject */
  37. protected $userInterface;
  38. protected function setUp(): void {
  39. parent::setUp();
  40. $this->view = $this->getMockBuilder(View::class)->getMock();
  41. $this->userManager = $this->getMockBuilder(IUserManager::class)->getMock();
  42. $this->config = $this->getMockBuilder(IConfig::class)->getMock();
  43. $this->util = $this->getMockBuilder('OC\Encryption\Util')->disableOriginalConstructor()->getMock();
  44. $this->questionHelper = $this->getMockBuilder(QuestionHelper::class)->getMock();
  45. $this->inputInterface = $this->getMockBuilder(InputInterface::class)->getMock();
  46. $this->outputInterface = $this->getMockBuilder(OutputInterface::class)->getMock();
  47. $this->userInterface = $this->getMockBuilder(UserInterface::class)->getMock();
  48. /* We need format method to return a string */
  49. $outputFormatter = $this->createMock(OutputFormatterInterface::class);
  50. $outputFormatter->method('isDecorated')->willReturn(false);
  51. $outputFormatter->method('format')->willReturnArgument(0);
  52. $this->outputInterface->expects($this->any())->method('getFormatter')
  53. ->willReturn($outputFormatter);
  54. $this->changeKeyStorageRoot = new ChangeKeyStorageRoot(
  55. $this->view,
  56. $this->userManager,
  57. $this->config,
  58. $this->util,
  59. $this->questionHelper
  60. );
  61. }
  62. /**
  63. * @dataProvider dataTestExecute
  64. */
  65. public function testExecute($newRoot, $answer, $successMoveKey): void {
  66. $changeKeyStorageRoot = $this->getMockBuilder('OC\Core\Command\Encryption\ChangeKeyStorageRoot')
  67. ->setConstructorArgs(
  68. [
  69. $this->view,
  70. $this->userManager,
  71. $this->config,
  72. $this->util,
  73. $this->questionHelper
  74. ]
  75. )->setMethods(['moveAllKeys'])->getMock();
  76. $this->util->expects($this->once())->method('getKeyStorageRoot')
  77. ->willReturn('');
  78. $this->inputInterface->expects($this->once())->method('getArgument')
  79. ->with('newRoot')->willReturn($newRoot);
  80. if ($answer === true || $newRoot !== null) {
  81. $changeKeyStorageRoot->expects($this->once())->method('moveAllKeys')
  82. ->willReturn($successMoveKey);
  83. } else {
  84. $changeKeyStorageRoot->expects($this->never())->method('moveAllKeys');
  85. }
  86. if ($successMoveKey === true) {
  87. $this->util->expects($this->once())->method('setKeyStorageRoot');
  88. } else {
  89. $this->util->expects($this->never())->method('setKeyStorageRoot');
  90. }
  91. if ($newRoot === null) {
  92. $this->questionHelper->expects($this->once())->method('ask')->willReturn($answer);
  93. } else {
  94. $this->questionHelper->expects($this->never())->method('ask');
  95. }
  96. $this->invokePrivate(
  97. $changeKeyStorageRoot,
  98. 'execute',
  99. [$this->inputInterface, $this->outputInterface]
  100. );
  101. }
  102. public function dataTestExecute() {
  103. return [
  104. [null, true, true],
  105. [null, true, false],
  106. [null, false, null],
  107. ['/newRoot', null, true],
  108. ['/newRoot', null, false]
  109. ];
  110. }
  111. public function testMoveAllKeys(): void {
  112. /** @var \OC\Core\Command\Encryption\ChangeKeyStorageRoot $changeKeyStorageRoot */
  113. $changeKeyStorageRoot = $this->getMockBuilder('OC\Core\Command\Encryption\ChangeKeyStorageRoot')
  114. ->setConstructorArgs(
  115. [
  116. $this->view,
  117. $this->userManager,
  118. $this->config,
  119. $this->util,
  120. $this->questionHelper
  121. ]
  122. )->setMethods(['prepareNewRoot', 'moveSystemKeys', 'moveUserKeys'])->getMock();
  123. $changeKeyStorageRoot->expects($this->once())->method('prepareNewRoot')->with('newRoot');
  124. $changeKeyStorageRoot->expects($this->once())->method('moveSystemKeys')->with('oldRoot', 'newRoot');
  125. $changeKeyStorageRoot->expects($this->once())->method('moveUserKeys')->with('oldRoot', 'newRoot', $this->outputInterface);
  126. $this->invokePrivate($changeKeyStorageRoot, 'moveAllKeys', ['oldRoot', 'newRoot', $this->outputInterface]);
  127. }
  128. public function testPrepareNewRoot(): void {
  129. $this->view->expects($this->once())->method('is_dir')->with('newRoot')
  130. ->willReturn(true);
  131. $this->view->expects($this->once())->method('file_put_contents')
  132. ->with('newRoot/' . \OC\Encryption\Keys\Storage::KEY_STORAGE_MARKER,
  133. 'Nextcloud will detect this folder as key storage root only if this file exists')->willReturn(true);
  134. $this->invokePrivate($this->changeKeyStorageRoot, 'prepareNewRoot', ['newRoot']);
  135. }
  136. /**
  137. * @dataProvider dataTestPrepareNewRootException
  138. *
  139. * @param bool $dirExists
  140. * @param bool $couldCreateFile
  141. */
  142. public function testPrepareNewRootException($dirExists, $couldCreateFile): void {
  143. $this->expectException(\Exception::class);
  144. $this->view->expects($this->once())->method('is_dir')->with('newRoot')
  145. ->willReturn($dirExists);
  146. $this->view->expects($this->any())->method('file_put_contents')->willReturn($couldCreateFile);
  147. $this->invokePrivate($this->changeKeyStorageRoot, 'prepareNewRoot', ['newRoot']);
  148. }
  149. public function dataTestPrepareNewRootException() {
  150. return [
  151. [true, false],
  152. [true, null],
  153. [false, true]
  154. ];
  155. }
  156. /**
  157. * @dataProvider dataTestMoveSystemKeys
  158. *
  159. * @param bool $dirExists
  160. * @param bool $targetExists
  161. * @param bool $executeRename
  162. */
  163. public function testMoveSystemKeys($dirExists, $targetExists, $executeRename): void {
  164. $changeKeyStorageRoot = $this->getMockBuilder('OC\Core\Command\Encryption\ChangeKeyStorageRoot')
  165. ->setConstructorArgs(
  166. [
  167. $this->view,
  168. $this->userManager,
  169. $this->config,
  170. $this->util,
  171. $this->questionHelper
  172. ]
  173. )->setMethods(['targetExists'])->getMock();
  174. $this->view->expects($this->once())->method('is_dir')
  175. ->with('oldRoot/files_encryption')->willReturn($dirExists);
  176. $changeKeyStorageRoot->expects($this->any())->method('targetExists')
  177. ->with('newRoot/files_encryption')->willReturn($targetExists);
  178. if ($executeRename) {
  179. $this->view->expects($this->once())->method('rename')
  180. ->with('oldRoot/files_encryption', 'newRoot/files_encryption');
  181. } else {
  182. $this->view->expects($this->never())->method('rename');
  183. }
  184. $this->invokePrivate($changeKeyStorageRoot, 'moveSystemKeys', ['oldRoot', 'newRoot']);
  185. }
  186. public function dataTestMoveSystemKeys() {
  187. return [
  188. [true, false, true],
  189. [false, true, false],
  190. [true, true, false],
  191. [false, false, false]
  192. ];
  193. }
  194. public function testMoveUserKeys(): void {
  195. $changeKeyStorageRoot = $this->getMockBuilder('OC\Core\Command\Encryption\ChangeKeyStorageRoot')
  196. ->setConstructorArgs(
  197. [
  198. $this->view,
  199. $this->userManager,
  200. $this->config,
  201. $this->util,
  202. $this->questionHelper
  203. ]
  204. )->setMethods(['setupUserFS', 'moveUserEncryptionFolder'])->getMock();
  205. $this->userManager->expects($this->once())->method('getBackends')
  206. ->willReturn([$this->userInterface]);
  207. $this->userInterface->expects($this->once())->method('getUsers')
  208. ->willReturn(['user1', 'user2']);
  209. $changeKeyStorageRoot->expects($this->exactly(2))->method('setupUserFS');
  210. $changeKeyStorageRoot->expects($this->exactly(2))->method('moveUserEncryptionFolder');
  211. $this->invokePrivate($changeKeyStorageRoot, 'moveUserKeys', ['oldRoot', 'newRoot', $this->outputInterface]);
  212. }
  213. /**
  214. * @dataProvider dataTestMoveUserEncryptionFolder
  215. *
  216. * @param bool $userExists
  217. * @param bool $isDir
  218. * @param bool $targetExists
  219. * @param bool $shouldRename
  220. */
  221. public function testMoveUserEncryptionFolder($userExists, $isDir, $targetExists, $shouldRename): void {
  222. $changeKeyStorageRoot = $this->getMockBuilder('OC\Core\Command\Encryption\ChangeKeyStorageRoot')
  223. ->setConstructorArgs(
  224. [
  225. $this->view,
  226. $this->userManager,
  227. $this->config,
  228. $this->util,
  229. $this->questionHelper
  230. ]
  231. )->setMethods(['targetExists', 'prepareParentFolder'])->getMock();
  232. $this->userManager->expects($this->once())->method('userExists')
  233. ->willReturn($userExists);
  234. $this->view->expects($this->any())->method('is_dir')
  235. ->willReturn($isDir);
  236. $changeKeyStorageRoot->expects($this->any())->method('targetExists')
  237. ->willReturn($targetExists);
  238. if ($shouldRename) {
  239. $changeKeyStorageRoot->expects($this->once())->method('prepareParentFolder')
  240. ->with('newRoot/user1');
  241. $this->view->expects($this->once())->method('rename')
  242. ->with('oldRoot/user1/files_encryption', 'newRoot/user1/files_encryption');
  243. } else {
  244. $changeKeyStorageRoot->expects($this->never())->method('prepareParentFolder');
  245. $this->view->expects($this->never())->method('rename');
  246. }
  247. $this->invokePrivate($changeKeyStorageRoot, 'moveUserEncryptionFolder', ['user1', 'oldRoot', 'newRoot']);
  248. }
  249. public function dataTestMoveUserEncryptionFolder() {
  250. return [
  251. [true, true, false, true],
  252. [true, false, true, false],
  253. [false, true, true, false],
  254. [false, false, true, false],
  255. [false, true, false, false],
  256. [false, true, true, false],
  257. [false, false, false, false]
  258. ];
  259. }
  260. /**
  261. * @dataProvider dataTestPrepareParentFolder
  262. */
  263. public function testPrepareParentFolder($path, $pathExists): void {
  264. $this->view->expects($this->any())->method('file_exists')
  265. ->willReturnCallback(
  266. function ($fileExistsPath) use ($path, $pathExists) {
  267. if ($path === $fileExistsPath) {
  268. return $pathExists;
  269. }
  270. return false;
  271. }
  272. );
  273. if ($pathExists === false) {
  274. $subDirs = explode('/', ltrim($path, '/'));
  275. $this->view->expects($this->exactly(count($subDirs)))->method('mkdir');
  276. } else {
  277. $this->view->expects($this->never())->method('mkdir');
  278. }
  279. $this->invokePrivate(
  280. $this->changeKeyStorageRoot,
  281. 'prepareParentFolder',
  282. [$path]
  283. );
  284. }
  285. public function dataTestPrepareParentFolder() {
  286. return [
  287. ['/user/folder/sub_folder/keystorage', true],
  288. ['/user/folder/sub_folder/keystorage', false]
  289. ];
  290. }
  291. public function testTargetExists(): void {
  292. $this->view->expects($this->once())->method('file_exists')->with('path')
  293. ->willReturn(false);
  294. $this->assertFalse(
  295. $this->invokePrivate($this->changeKeyStorageRoot, 'targetExists', ['path'])
  296. );
  297. }
  298. public function testTargetExistsException(): void {
  299. $this->expectException(\Exception::class);
  300. $this->view->expects($this->once())->method('file_exists')->with('path')
  301. ->willReturn(true);
  302. $this->invokePrivate($this->changeKeyStorageRoot, 'targetExists', ['path']);
  303. }
  304. }