DecryptAllTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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 Test\Encryption;
  8. use OC\Encryption\DecryptAll;
  9. use OC\Encryption\Exceptions\DecryptionFailedException;
  10. use OC\Encryption\Manager;
  11. use OC\Files\FileInfo;
  12. use OC\Files\View;
  13. use OCP\Files\Storage\IStorage;
  14. use OCP\IUserManager;
  15. use OCP\UserInterface;
  16. use Symfony\Component\Console\Formatter\OutputFormatterInterface;
  17. use Symfony\Component\Console\Helper\ProgressBar;
  18. use Symfony\Component\Console\Input\InputInterface;
  19. use Symfony\Component\Console\Output\OutputInterface;
  20. use Test\TestCase;
  21. /**
  22. * Class DecryptAllTest
  23. *
  24. * @group DB
  25. *
  26. * @package Test\Encryption
  27. */
  28. class DecryptAllTest extends TestCase {
  29. /** @var \PHPUnit\Framework\MockObject\MockObject | IUserManager */
  30. protected $userManager;
  31. /** @var \PHPUnit\Framework\MockObject\MockObject | Manager */
  32. protected $encryptionManager;
  33. /** @var \PHPUnit\Framework\MockObject\MockObject | View */
  34. protected $view;
  35. /** @var \PHPUnit\Framework\MockObject\MockObject | \Symfony\Component\Console\Input\InputInterface */
  36. protected $inputInterface;
  37. /** @var \PHPUnit\Framework\MockObject\MockObject | \Symfony\Component\Console\Output\OutputInterface */
  38. protected $outputInterface;
  39. /** @var \PHPUnit\Framework\MockObject\MockObject | \OCP\UserInterface */
  40. protected $userInterface;
  41. /** @var DecryptAll */
  42. protected $instance;
  43. protected function setUp(): void {
  44. parent::setUp();
  45. $this->userManager = $this->getMockBuilder(IUserManager::class)
  46. ->disableOriginalConstructor()->getMock();
  47. $this->encryptionManager = $this->getMockBuilder('OC\Encryption\Manager')
  48. ->disableOriginalConstructor()->getMock();
  49. $this->view = $this->getMockBuilder(View::class)
  50. ->disableOriginalConstructor()->getMock();
  51. $this->inputInterface = $this->getMockBuilder(InputInterface::class)
  52. ->disableOriginalConstructor()->getMock();
  53. $this->outputInterface = $this->getMockBuilder(OutputInterface::class)
  54. ->disableOriginalConstructor()->getMock();
  55. $this->outputInterface->expects($this->any())->method('isDecorated')
  56. ->willReturn(false);
  57. $this->userInterface = $this->getMockBuilder(UserInterface::class)
  58. ->disableOriginalConstructor()->getMock();
  59. /* We need format method to return a string */
  60. $outputFormatter = $this->createMock(OutputFormatterInterface::class);
  61. $outputFormatter->method('format')->willReturn('foo');
  62. $outputFormatter->method('isDecorated')->willReturn(false);
  63. $this->outputInterface->expects($this->any())->method('getFormatter')
  64. ->willReturn($outputFormatter);
  65. $this->instance = new DecryptAll($this->encryptionManager, $this->userManager, $this->view);
  66. $this->invokePrivate($this->instance, 'input', [$this->inputInterface]);
  67. $this->invokePrivate($this->instance, 'output', [$this->outputInterface]);
  68. }
  69. public function dataDecryptAll() {
  70. return [
  71. [true, 'user1', true],
  72. [false, 'user1', true],
  73. [true, '0', true],
  74. [false, '0', true],
  75. [true, '', false],
  76. ];
  77. }
  78. /**
  79. * @dataProvider dataDecryptAll
  80. * @param bool $prepareResult
  81. * @param string $user
  82. * @param bool $userExistsChecked
  83. */
  84. public function testDecryptAll($prepareResult, $user, $userExistsChecked): void {
  85. if ($userExistsChecked) {
  86. $this->userManager->expects($this->once())->method('userExists')->willReturn(true);
  87. } else {
  88. $this->userManager->expects($this->never())->method('userExists');
  89. }
  90. /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject | $instance */
  91. $instance = $this->getMockBuilder('OC\Encryption\DecryptAll')
  92. ->setConstructorArgs(
  93. [
  94. $this->encryptionManager,
  95. $this->userManager,
  96. $this->view
  97. ]
  98. )
  99. ->setMethods(['prepareEncryptionModules', 'decryptAllUsersFiles'])
  100. ->getMock();
  101. $instance->expects($this->once())
  102. ->method('prepareEncryptionModules')
  103. ->with($user)
  104. ->willReturn($prepareResult);
  105. if ($prepareResult) {
  106. $instance->expects($this->once())
  107. ->method('decryptAllUsersFiles')
  108. ->with($user);
  109. } else {
  110. $instance->expects($this->never())->method('decryptAllUsersFiles');
  111. }
  112. $instance->decryptAll($this->inputInterface, $this->outputInterface, $user);
  113. }
  114. /**
  115. * test decrypt all call with a user who doesn't exists
  116. */
  117. public function testDecryptAllWrongUser(): void {
  118. $this->userManager->expects($this->once())->method('userExists')->willReturn(false);
  119. $this->outputInterface->expects($this->once())->method('writeln')
  120. ->with('User "user1" does not exist. Please check the username and try again');
  121. $this->assertFalse(
  122. $this->instance->decryptAll($this->inputInterface, $this->outputInterface, 'user1')
  123. );
  124. }
  125. public function dataTrueFalse() {
  126. return [
  127. [true],
  128. [false],
  129. ];
  130. }
  131. /**
  132. * @dataProvider dataTrueFalse
  133. * @param bool $success
  134. */
  135. public function testPrepareEncryptionModules($success): void {
  136. $user = 'user1';
  137. $dummyEncryptionModule = $this->getMockBuilder('OCP\Encryption\IEncryptionModule')
  138. ->disableOriginalConstructor()->getMock();
  139. $dummyEncryptionModule->expects($this->once())
  140. ->method('prepareDecryptAll')
  141. ->with($this->inputInterface, $this->outputInterface, $user)
  142. ->willReturn($success);
  143. $callback = function () use ($dummyEncryptionModule) {
  144. return $dummyEncryptionModule;
  145. };
  146. $moduleDescription = [
  147. 'id' => 'id',
  148. 'displayName' => 'displayName',
  149. 'callback' => $callback
  150. ];
  151. $this->encryptionManager->expects($this->once())
  152. ->method('getEncryptionModules')
  153. ->willReturn([$moduleDescription]);
  154. $this->assertSame($success,
  155. $this->invokePrivate($this->instance, 'prepareEncryptionModules', [$user])
  156. );
  157. }
  158. /**
  159. * @dataProvider dataTestDecryptAllUsersFiles
  160. */
  161. public function testDecryptAllUsersFiles($user): void {
  162. /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject | $instance */
  163. $instance = $this->getMockBuilder('OC\Encryption\DecryptAll')
  164. ->setConstructorArgs(
  165. [
  166. $this->encryptionManager,
  167. $this->userManager,
  168. $this->view
  169. ]
  170. )
  171. ->setMethods(['decryptUsersFiles'])
  172. ->getMock();
  173. $this->invokePrivate($instance, 'input', [$this->inputInterface]);
  174. $this->invokePrivate($instance, 'output', [$this->outputInterface]);
  175. if (empty($user)) {
  176. $this->userManager->expects($this->once())
  177. ->method('getBackends')
  178. ->willReturn([$this->userInterface]);
  179. $this->userInterface->expects($this->any())
  180. ->method('getUsers')
  181. ->willReturn(['user1', 'user2']);
  182. $instance->expects($this->exactly(2))
  183. ->method('decryptUsersFiles')
  184. ->withConsecutive(
  185. ['user1'],
  186. ['user2'],
  187. );
  188. } else {
  189. $instance->expects($this->once())
  190. ->method('decryptUsersFiles')
  191. ->with($user);
  192. }
  193. $this->invokePrivate($instance, 'decryptAllUsersFiles', [$user]);
  194. }
  195. public function dataTestDecryptAllUsersFiles() {
  196. return [
  197. ['user1'],
  198. ['']
  199. ];
  200. }
  201. public function testDecryptUsersFiles(): void {
  202. /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject $instance */
  203. $instance = $this->getMockBuilder('OC\Encryption\DecryptAll')
  204. ->setConstructorArgs(
  205. [
  206. $this->encryptionManager,
  207. $this->userManager,
  208. $this->view
  209. ]
  210. )
  211. ->setMethods(['decryptFile'])
  212. ->getMock();
  213. $storage = $this->getMockBuilder(IStorage::class)
  214. ->disableOriginalConstructor()->getMock();
  215. $sharedStorage = $this->getMockBuilder(IStorage::class)
  216. ->disableOriginalConstructor()->getMock();
  217. $sharedStorage->expects($this->once())->method('instanceOfStorage')
  218. ->with('OCA\Files_Sharing\SharedStorage')->willReturn(true);
  219. $this->view->expects($this->exactly(2))
  220. ->method('getDirectoryContent')
  221. ->withConsecutive(
  222. ['/user1/files'],
  223. ['/user1/files/foo']
  224. )
  225. ->willReturnOnConsecutiveCalls(
  226. [
  227. new FileInfo('path', $storage, 'intPath', ['name' => 'foo', 'type' => 'dir'], null),
  228. new FileInfo('path', $storage, 'intPath', ['name' => 'bar', 'type' => 'file', 'encrypted' => true], null),
  229. new FileInfo('path', $sharedStorage, 'intPath', ['name' => 'shared', 'type' => 'file', 'encrypted' => true], null),
  230. ],
  231. [
  232. new FileInfo('path', $storage, 'intPath', ['name' => 'subfile', 'type' => 'file', 'encrypted' => true], null)
  233. ]
  234. );
  235. $this->view->expects($this->any())->method('is_dir')
  236. ->willReturnCallback(
  237. function ($path) {
  238. if ($path === '/user1/files/foo') {
  239. return true;
  240. }
  241. return false;
  242. }
  243. );
  244. $instance->expects($this->exactly(2))
  245. ->method('decryptFile')
  246. ->withConsecutive(
  247. ['/user1/files/bar'],
  248. ['/user1/files/foo/subfile'],
  249. );
  250. /* We need format method to return a string */
  251. $outputFormatter = $this->createMock(OutputFormatterInterface::class);
  252. $outputFormatter->method('isDecorated')->willReturn(false);
  253. $outputFormatter->method('format')->willReturn('foo');
  254. $output = $this->createMock(OutputInterface::class);
  255. $output->expects($this->any())
  256. ->method('getFormatter')
  257. ->willReturn($outputFormatter);
  258. $progressBar = new ProgressBar($output);
  259. $this->invokePrivate($instance, 'decryptUsersFiles', ['user1', $progressBar, '']);
  260. }
  261. /**
  262. * @dataProvider dataTrueFalse
  263. */
  264. public function testDecryptFile($isEncrypted): void {
  265. $path = 'test.txt';
  266. /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject $instance */
  267. $instance = $this->getMockBuilder('OC\Encryption\DecryptAll')
  268. ->setConstructorArgs(
  269. [
  270. $this->encryptionManager,
  271. $this->userManager,
  272. $this->view
  273. ]
  274. )
  275. ->setMethods(['getTimestamp'])
  276. ->getMock();
  277. $fileInfo = $this->createMock(FileInfo::class);
  278. $fileInfo->expects($this->any())->method('isEncrypted')
  279. ->willReturn($isEncrypted);
  280. $this->view->expects($this->any())->method('getFileInfo')
  281. ->willReturn($fileInfo);
  282. if ($isEncrypted) {
  283. $instance->expects($this->any())->method('getTimestamp')->willReturn(42);
  284. $this->view->expects($this->once())
  285. ->method('copy')
  286. ->with($path, $path . '.decrypted.42');
  287. $this->view->expects($this->once())
  288. ->method('rename')
  289. ->with($path . '.decrypted.42', $path);
  290. } else {
  291. $instance->expects($this->never())->method('getTimestamp');
  292. $this->view->expects($this->never())->method('copy');
  293. $this->view->expects($this->never())->method('rename');
  294. }
  295. $this->assertTrue(
  296. $this->invokePrivate($instance, 'decryptFile', [$path])
  297. );
  298. }
  299. public function testDecryptFileFailure(): void {
  300. $path = 'test.txt';
  301. /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject $instance */
  302. $instance = $this->getMockBuilder('OC\Encryption\DecryptAll')
  303. ->setConstructorArgs(
  304. [
  305. $this->encryptionManager,
  306. $this->userManager,
  307. $this->view
  308. ]
  309. )
  310. ->setMethods(['getTimestamp'])
  311. ->getMock();
  312. $fileInfo = $this->createMock(FileInfo::class);
  313. $fileInfo->expects($this->any())->method('isEncrypted')
  314. ->willReturn(true);
  315. $this->view->expects($this->any())->method('getFileInfo')
  316. ->willReturn($fileInfo);
  317. $instance->expects($this->any())->method('getTimestamp')->willReturn(42);
  318. $this->view->expects($this->once())
  319. ->method('copy')
  320. ->with($path, $path . '.decrypted.42')
  321. ->willReturnCallback(function () {
  322. throw new DecryptionFailedException();
  323. });
  324. $this->view->expects($this->never())->method('rename');
  325. $this->view->expects($this->once())
  326. ->method('file_exists')
  327. ->with($path . '.decrypted.42')
  328. ->willReturn(true);
  329. $this->view->expects($this->once())
  330. ->method('unlink')
  331. ->with($path . '.decrypted.42');
  332. $this->assertFalse(
  333. $this->invokePrivate($instance, 'decryptFile', [$path])
  334. );
  335. }
  336. }