EncryptionTest.php 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  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\Files\Storage\Wrapper;
  8. use Exception;
  9. use OC;
  10. use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
  11. use OC\Encryption\File;
  12. use OC\Encryption\Update;
  13. use OC\Encryption\Util;
  14. use OC\Files\Cache\Cache;
  15. use OC\Files\Cache\CacheEntry;
  16. use OC\Files\Mount\MountPoint;
  17. use OC\Files\Storage\Temporary;
  18. use OC\Files\Storage\Wrapper\Encryption;
  19. use OC\Files\View;
  20. use OC\Memcache\ArrayCache;
  21. use OC\User\Manager;
  22. use OCP\Encryption\IEncryptionModule;
  23. use OCP\Encryption\IFile;
  24. use OCP\Encryption\Keys\IStorage;
  25. use OCP\EventDispatcher\IEventDispatcher;
  26. use OCP\Files\Cache\ICache;
  27. use OCP\Files\Mount\IMountPoint;
  28. use OCP\ICacheFactory;
  29. use OCP\IConfig;
  30. use PHPUnit\Framework\MockObject\MockObject;
  31. use Psr\Log\LoggerInterface;
  32. use Test\Files\Storage\Storage;
  33. class EncryptionTest extends Storage {
  34. /**
  35. * block size will always be 8192 for a PHP stream
  36. * @see https://bugs.php.net/bug.php?id=21641
  37. */
  38. protected int $headerSize = 8192;
  39. private Temporary $sourceStorage;
  40. /** @var Encryption&MockObject */
  41. protected $instance;
  42. private \OC\Encryption\Keys\Storage&MockObject $keyStore;
  43. private Util&MockObject $util;
  44. private \OC\Encryption\Manager&MockObject $encryptionManager;
  45. private IEncryptionModule&MockObject $encryptionModule;
  46. private Update&MockObject $update;
  47. private Cache&MockObject $cache;
  48. private LoggerInterface&MockObject $logger;
  49. private File&MockObject $file;
  50. private MountPoint&MockObject $mount;
  51. private \OC\Files\Mount\Manager&MockObject $mountManager;
  52. private \OC\Group\Manager&MockObject $groupManager;
  53. private IConfig&MockObject $config;
  54. private ArrayCache&MockObject $arrayCache;
  55. /** dummy unencrypted size */
  56. private int $dummySize = -1;
  57. protected function setUp(): void {
  58. parent::setUp();
  59. $mockModule = $this->buildMockModule();
  60. $this->encryptionManager = $this->getMockBuilder('\OC\Encryption\Manager')
  61. ->disableOriginalConstructor()
  62. ->setMethods(['getEncryptionModule', 'isEnabled'])
  63. ->getMock();
  64. $this->encryptionManager->expects($this->any())
  65. ->method('getEncryptionModule')
  66. ->willReturn($mockModule);
  67. $this->arrayCache = $this->createMock(ArrayCache::class);
  68. $this->config = $this->getMockBuilder(IConfig::class)
  69. ->disableOriginalConstructor()
  70. ->getMock();
  71. $this->groupManager = $this->getMockBuilder('\OC\Group\Manager')
  72. ->disableOriginalConstructor()
  73. ->getMock();
  74. $this->util = $this->getMockBuilder('\OC\Encryption\Util')
  75. ->setMethods(['getUidAndFilename', 'isFile', 'isExcluded', 'stripPartialFileExtension'])
  76. ->setConstructorArgs([new View(), new Manager(
  77. $this->config,
  78. $this->createMock(ICacheFactory::class),
  79. $this->createMock(IEventDispatcher::class),
  80. $this->createMock(LoggerInterface::class),
  81. ), $this->groupManager, $this->config, $this->arrayCache])
  82. ->getMock();
  83. $this->util->expects($this->any())
  84. ->method('getUidAndFilename')
  85. ->willReturnCallback(function ($path) {
  86. return ['user1', $path];
  87. });
  88. $this->util->expects($this->any())
  89. ->method('stripPartialFileExtension')
  90. ->willReturnCallback(function ($path) {
  91. return $path;
  92. });
  93. $this->file = $this->getMockBuilder('\OC\Encryption\File')
  94. ->disableOriginalConstructor()
  95. ->setMethods(['getAccessList'])
  96. ->getMock();
  97. $this->file->expects($this->any())->method('getAccessList')->willReturn([]);
  98. $this->logger = $this->createMock(LoggerInterface::class);
  99. $this->sourceStorage = new Temporary([]);
  100. $this->keyStore = $this->getMockBuilder('\OC\Encryption\Keys\Storage')
  101. ->disableOriginalConstructor()->getMock();
  102. $this->update = $this->getMockBuilder('\OC\Encryption\Update')
  103. ->disableOriginalConstructor()->getMock();
  104. $this->mount = $this->getMockBuilder('\OC\Files\Mount\MountPoint')
  105. ->disableOriginalConstructor()
  106. ->setMethods(['getOption'])
  107. ->getMock();
  108. $this->mount->expects($this->any())->method('getOption')->willReturnCallback(function ($option, $default) {
  109. if ($option === 'encrypt' && $default === true) {
  110. global $mockedMountPointEncryptionEnabled;
  111. if ($mockedMountPointEncryptionEnabled !== null) {
  112. return $mockedMountPointEncryptionEnabled;
  113. }
  114. }
  115. return true;
  116. });
  117. $this->cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
  118. ->disableOriginalConstructor()->getMock();
  119. $this->cache->expects($this->any())
  120. ->method('get')
  121. ->willReturnCallback(function ($path) {
  122. return ['encrypted' => false, 'path' => $path];
  123. });
  124. $this->mountManager = $this->createMock(\OC\Files\Mount\Manager::class);
  125. $this->mountManager->method('findByStorageId')
  126. ->willReturn([]);
  127. $this->instance = $this->getMockBuilder(Encryption::class)
  128. ->setConstructorArgs(
  129. [
  130. [
  131. 'storage' => $this->sourceStorage,
  132. 'root' => 'foo',
  133. 'mountPoint' => '/',
  134. 'mount' => $this->mount
  135. ],
  136. $this->encryptionManager,
  137. $this->util,
  138. $this->logger,
  139. $this->file,
  140. null,
  141. $this->keyStore,
  142. $this->update,
  143. $this->mountManager,
  144. $this->arrayCache
  145. ]
  146. )
  147. ->setMethods(['getMetaData', 'getCache', 'getEncryptionModule'])
  148. ->getMock();
  149. $this->instance->expects($this->any())
  150. ->method('getMetaData')
  151. ->willReturnCallback(function ($path) {
  152. return ['encrypted' => true, 'size' => $this->dummySize, 'path' => $path];
  153. });
  154. $this->instance->expects($this->any())
  155. ->method('getCache')
  156. ->willReturn($this->cache);
  157. $this->instance->expects($this->any())
  158. ->method('getEncryptionModule')
  159. ->willReturn($mockModule);
  160. }
  161. protected function buildMockModule(): IEncryptionModule&MockObject {
  162. $this->encryptionModule = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule')
  163. ->disableOriginalConstructor()
  164. ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll', 'prepareDecryptAll', 'isReadyForUser', 'needDetailedAccessList'])
  165. ->getMock();
  166. $this->encryptionModule->expects($this->any())->method('getId')->willReturn('UNIT_TEST_MODULE');
  167. $this->encryptionModule->expects($this->any())->method('getDisplayName')->willReturn('Unit test module');
  168. $this->encryptionModule->expects($this->any())->method('begin')->willReturn([]);
  169. $this->encryptionModule->expects($this->any())->method('end')->willReturn('');
  170. $this->encryptionModule->expects($this->any())->method('encrypt')->willReturnArgument(0);
  171. $this->encryptionModule->expects($this->any())->method('decrypt')->willReturnArgument(0);
  172. $this->encryptionModule->expects($this->any())->method('update')->willReturn(true);
  173. $this->encryptionModule->expects($this->any())->method('shouldEncrypt')->willReturn(true);
  174. $this->encryptionModule->expects($this->any())->method('getUnencryptedBlockSize')->willReturn(8192);
  175. $this->encryptionModule->expects($this->any())->method('isReadable')->willReturn(true);
  176. $this->encryptionModule->expects($this->any())->method('needDetailedAccessList')->willReturn(false);
  177. return $this->encryptionModule;
  178. }
  179. /**
  180. * @dataProvider dataTestGetMetaData
  181. *
  182. * @param string $path
  183. * @param array $metaData
  184. * @param bool $encrypted
  185. * @param bool $unencryptedSizeSet
  186. * @param int $storedUnencryptedSize
  187. * @param array $expected
  188. */
  189. public function testGetMetaData($path, $metaData, $encrypted, $unencryptedSizeSet, $storedUnencryptedSize, $expected): void {
  190. $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
  191. ->disableOriginalConstructor()->getMock();
  192. $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
  193. ->disableOriginalConstructor()->getMock();
  194. $cache->expects($this->any())
  195. ->method('get')
  196. ->willReturnCallback(
  197. function ($path) use ($encrypted) {
  198. return new CacheEntry(['encrypted' => $encrypted, 'path' => $path, 'size' => 0, 'fileid' => 1]);
  199. }
  200. );
  201. $this->instance = $this->getMockBuilder(Encryption::class)
  202. ->setConstructorArgs(
  203. [
  204. [
  205. 'storage' => $sourceStorage,
  206. 'root' => 'foo',
  207. 'mountPoint' => '/',
  208. 'mount' => $this->mount
  209. ],
  210. $this->encryptionManager,
  211. $this->util,
  212. $this->logger,
  213. $this->file,
  214. null,
  215. $this->keyStore,
  216. $this->update,
  217. $this->mountManager,
  218. $this->arrayCache,
  219. ]
  220. )
  221. ->setMethods(['getCache', 'verifyUnencryptedSize'])
  222. ->getMock();
  223. if ($unencryptedSizeSet) {
  224. $this->invokePrivate($this->instance, 'unencryptedSize', [[$path => $storedUnencryptedSize]]);
  225. }
  226. $fileEntry = $this->getMockBuilder('\OC\Files\Cache\Cache')
  227. ->disableOriginalConstructor()->getMock();
  228. $sourceStorage->expects($this->once())->method('getMetaData')->with($path)
  229. ->willReturn($metaData);
  230. $sourceStorage->expects($this->any())
  231. ->method('getCache')
  232. ->with($path)
  233. ->willReturn($fileEntry);
  234. if ($metaData !== null) {
  235. $fileEntry->expects($this->any())
  236. ->method('get')
  237. ->with($metaData['fileid']);
  238. }
  239. $this->instance->expects($this->any())->method('getCache')->willReturn($cache);
  240. if ($expected !== null) {
  241. $this->instance->expects($this->any())->method('verifyUnencryptedSize')
  242. ->with($path, 0)->willReturn($expected['size']);
  243. }
  244. $result = $this->instance->getMetaData($path);
  245. if (isset($expected['encrypted'])) {
  246. $this->assertSame($expected['encrypted'], (bool)$result['encrypted']);
  247. if (isset($expected['encryptedVersion'])) {
  248. $this->assertSame($expected['encryptedVersion'], $result['encryptedVersion']);
  249. }
  250. }
  251. if ($expected !== null) {
  252. $this->assertSame($expected['size'], $result['size']);
  253. } else {
  254. $this->assertSame(null, $result);
  255. }
  256. }
  257. public function dataTestGetMetaData() {
  258. return [
  259. ['/test.txt', ['size' => 42, 'encrypted' => 2, 'encryptedVersion' => 2, 'fileid' => 1], true, true, 12, ['size' => 12, 'encrypted' => true, 'encryptedVersion' => 2]],
  260. ['/test.txt', null, true, true, 12, null],
  261. ['/test.txt', ['size' => 42, 'encrypted' => 0, 'fileid' => 1], false, false, 12, ['size' => 42, 'encrypted' => false]],
  262. ['/test.txt', ['size' => 42, 'encrypted' => false, 'fileid' => 1], true, false, 12, ['size' => 12, 'encrypted' => true]]
  263. ];
  264. }
  265. public function testFilesize(): void {
  266. $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
  267. ->disableOriginalConstructor()->getMock();
  268. $cache->expects($this->any())
  269. ->method('get')
  270. ->willReturn(new CacheEntry(['encrypted' => true, 'path' => '/test.txt', 'size' => 0, 'fileid' => 1]));
  271. $this->instance = $this->getMockBuilder(Encryption::class)
  272. ->setConstructorArgs(
  273. [
  274. [
  275. 'storage' => $this->sourceStorage,
  276. 'root' => 'foo',
  277. 'mountPoint' => '/',
  278. 'mount' => $this->mount
  279. ],
  280. $this->encryptionManager,
  281. $this->util,
  282. $this->logger,
  283. $this->file,
  284. null,
  285. $this->keyStore,
  286. $this->update,
  287. $this->mountManager,
  288. $this->arrayCache,
  289. ]
  290. )
  291. ->setMethods(['getCache', 'verifyUnencryptedSize'])
  292. ->getMock();
  293. $this->instance->expects($this->any())->method('getCache')->willReturn($cache);
  294. $this->instance->expects($this->any())->method('verifyUnencryptedSize')
  295. ->willReturn(42);
  296. $this->assertSame(42,
  297. $this->instance->filesize('/test.txt')
  298. );
  299. }
  300. /**
  301. * @dataProvider dataTestVerifyUnencryptedSize
  302. *
  303. * @param int $encryptedSize
  304. * @param int $unencryptedSize
  305. * @param bool $failure
  306. * @param int $expected
  307. */
  308. public function testVerifyUnencryptedSize($encryptedSize, $unencryptedSize, $failure, $expected): void {
  309. $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
  310. ->disableOriginalConstructor()->getMock();
  311. $this->instance = $this->getMockBuilder(Encryption::class)
  312. ->setConstructorArgs(
  313. [
  314. [
  315. 'storage' => $sourceStorage,
  316. 'root' => 'foo',
  317. 'mountPoint' => '/',
  318. 'mount' => $this->mount
  319. ],
  320. $this->encryptionManager,
  321. $this->util,
  322. $this->logger,
  323. $this->file,
  324. null,
  325. $this->keyStore,
  326. $this->update,
  327. $this->mountManager,
  328. $this->arrayCache,
  329. ]
  330. )
  331. ->setMethods(['fixUnencryptedSize'])
  332. ->getMock();
  333. $sourceStorage->expects($this->once())->method('filesize')->willReturn($encryptedSize);
  334. $this->instance->expects($this->any())->method('fixUnencryptedSize')
  335. ->with('/test.txt', $encryptedSize, $unencryptedSize)
  336. ->willReturnCallback(
  337. function () use ($failure, $expected) {
  338. if ($failure) {
  339. throw new Exception();
  340. } else {
  341. return $expected;
  342. }
  343. }
  344. );
  345. $this->assertSame(
  346. $expected,
  347. $this->invokePrivate($this->instance, 'verifyUnencryptedSize', ['/test.txt', $unencryptedSize])
  348. );
  349. }
  350. public function dataTestVerifyUnencryptedSize() {
  351. return [
  352. [120, 80, false, 80],
  353. [120, 120, false, 80],
  354. [120, -1, false, 80],
  355. [120, -1, true, -1]
  356. ];
  357. }
  358. /**
  359. * @dataProvider dataTestCopyAndRename
  360. *
  361. * @param string $source
  362. * @param string $target
  363. * @param $encryptionEnabled
  364. * @param boolean $renameKeysReturn
  365. */
  366. public function testRename($source,
  367. $target,
  368. $encryptionEnabled,
  369. $renameKeysReturn): void {
  370. if ($encryptionEnabled) {
  371. $this->keyStore
  372. ->expects($this->once())
  373. ->method('renameKeys')
  374. ->willReturn($renameKeysReturn);
  375. } else {
  376. $this->keyStore
  377. ->expects($this->never())->method('renameKeys');
  378. }
  379. $this->util->expects($this->any())
  380. ->method('isFile')->willReturn(true);
  381. $this->encryptionManager->expects($this->once())
  382. ->method('isEnabled')->willReturn($encryptionEnabled);
  383. $this->instance->mkdir($source);
  384. $this->instance->mkdir(dirname($target));
  385. $this->instance->rename($source, $target);
  386. }
  387. public function testCopyEncryption(): void {
  388. $this->instance->file_put_contents('source.txt', 'bar');
  389. $this->instance->copy('source.txt', 'target.txt');
  390. $this->assertSame('bar', $this->instance->file_get_contents('target.txt'));
  391. $targetMeta = $this->instance->getMetaData('target.txt');
  392. $sourceMeta = $this->instance->getMetaData('source.txt');
  393. $this->assertSame($sourceMeta['encrypted'], $targetMeta['encrypted']);
  394. $this->assertSame($sourceMeta['size'], $targetMeta['size']);
  395. }
  396. /**
  397. * data provider for testCopyTesting() and dataTestCopyAndRename()
  398. *
  399. * @return array
  400. */
  401. public function dataTestCopyAndRename() {
  402. return [
  403. ['source', 'target', true, false, false],
  404. ['source', 'target', true, true, false],
  405. ['source', '/subFolder/target', true, false, false],
  406. ['source', '/subFolder/target', true, true, true],
  407. ['source', '/subFolder/target', false, true, false],
  408. ];
  409. }
  410. public function testIsLocal(): void {
  411. $this->encryptionManager->expects($this->once())
  412. ->method('isEnabled')->willReturn(true);
  413. $this->assertFalse($this->instance->isLocal());
  414. }
  415. /**
  416. * @dataProvider dataTestRmdir
  417. *
  418. * @param string $path
  419. * @param boolean $rmdirResult
  420. * @param boolean $isExcluded
  421. * @param boolean $encryptionEnabled
  422. */
  423. public function testRmdir($path, $rmdirResult, $isExcluded, $encryptionEnabled): void {
  424. $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
  425. ->disableOriginalConstructor()->getMock();
  426. $util = $this->getMockBuilder('\OC\Encryption\Util')->disableOriginalConstructor()->getMock();
  427. $sourceStorage->expects($this->once())->method('rmdir')->willReturn($rmdirResult);
  428. $util->expects($this->any())->method('isExcluded')->willReturn($isExcluded);
  429. $this->encryptionManager->expects($this->any())->method('isEnabled')->willReturn($encryptionEnabled);
  430. $encryptionStorage = new Encryption(
  431. [
  432. 'storage' => $sourceStorage,
  433. 'root' => 'foo',
  434. 'mountPoint' => '/mountPoint',
  435. 'mount' => $this->mount
  436. ],
  437. $this->encryptionManager,
  438. $util,
  439. $this->logger,
  440. $this->file,
  441. null,
  442. $this->keyStore,
  443. $this->update,
  444. $this->mountManager,
  445. $this->arrayCache,
  446. );
  447. if ($rmdirResult === true && $isExcluded === false && $encryptionEnabled === true) {
  448. $this->keyStore->expects($this->once())->method('deleteAllFileKeys')->with('/mountPoint' . $path);
  449. } else {
  450. $this->keyStore->expects($this->never())->method('deleteAllFileKeys');
  451. }
  452. $encryptionStorage->rmdir($path);
  453. }
  454. public function dataTestRmdir() {
  455. return [
  456. ['/file.txt', true, true, true],
  457. ['/file.txt', false, true, true],
  458. ['/file.txt', true, false, true],
  459. ['/file.txt', false, false, true],
  460. ['/file.txt', true, true, false],
  461. ['/file.txt', false, true, false],
  462. ['/file.txt', true, false, false],
  463. ['/file.txt', false, false, false],
  464. ];
  465. }
  466. /**
  467. * @dataProvider dataTestCopyKeys
  468. *
  469. * @param boolean $excluded
  470. * @param boolean $expected
  471. */
  472. public function testCopyKeys($excluded, $expected): void {
  473. $this->util->expects($this->once())
  474. ->method('isExcluded')
  475. ->willReturn($excluded);
  476. if ($excluded) {
  477. $this->keyStore->expects($this->never())->method('copyKeys');
  478. } else {
  479. $this->keyStore->expects($this->once())->method('copyKeys')->willReturn(true);
  480. }
  481. $this->assertSame($expected,
  482. self::invokePrivate($this->instance, 'copyKeys', ['/source', '/target'])
  483. );
  484. }
  485. public function dataTestCopyKeys() {
  486. return [
  487. [true, false],
  488. [false, true],
  489. ];
  490. }
  491. /**
  492. * @dataProvider dataTestGetHeader
  493. *
  494. * @param string $path
  495. * @param bool $strippedPathExists
  496. * @param string $strippedPath
  497. */
  498. public function testGetHeader($path, $strippedPathExists, $strippedPath): void {
  499. $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
  500. ->disableOriginalConstructor()->getMock();
  501. $util = $this->getMockBuilder('\OC\Encryption\Util')
  502. ->setConstructorArgs(
  503. [
  504. new View(),
  505. new Manager(
  506. $this->config,
  507. $this->createMock(ICacheFactory::class),
  508. $this->createMock(IEventDispatcher::class),
  509. $this->createMock(LoggerInterface::class),
  510. ),
  511. $this->groupManager,
  512. $this->config,
  513. $this->arrayCache
  514. ]
  515. )->getMock();
  516. $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
  517. ->disableOriginalConstructor()->getMock();
  518. $cache->expects($this->any())
  519. ->method('get')
  520. ->willReturnCallback(function ($path) {
  521. return ['encrypted' => true, 'path' => $path];
  522. });
  523. $instance = $this->getMockBuilder(Encryption::class)
  524. ->setConstructorArgs(
  525. [
  526. [
  527. 'storage' => $sourceStorage,
  528. 'root' => 'foo',
  529. 'mountPoint' => '/',
  530. 'mount' => $this->mount
  531. ],
  532. $this->encryptionManager,
  533. $util,
  534. $this->logger,
  535. $this->file,
  536. null,
  537. $this->keyStore,
  538. $this->update,
  539. $this->mountManager,
  540. $this->arrayCache,
  541. ]
  542. )
  543. ->setMethods(['getCache', 'readFirstBlock'])
  544. ->getMock();
  545. $instance->method('getCache')->willReturn($cache);
  546. $util->method('parseRawHeader')
  547. ->willReturn([Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']);
  548. if ($strippedPathExists) {
  549. $instance->method('readFirstBlock')
  550. ->with($strippedPath)->willReturn('');
  551. } else {
  552. $instance->method('readFirstBlock')
  553. ->with($path)->willReturn('');
  554. }
  555. $util->expects($this->once())->method('stripPartialFileExtension')
  556. ->with($path)->willReturn($strippedPath);
  557. $sourceStorage->expects($this->once())
  558. ->method('is_file')
  559. ->with($strippedPath)
  560. ->willReturn($strippedPathExists);
  561. $this->invokePrivate($instance, 'getHeader', [$path]);
  562. }
  563. public function dataTestGetHeader() {
  564. return [
  565. ['/foo/bar.txt', false, '/foo/bar.txt'],
  566. ['/foo/bar.txt.part', false, '/foo/bar.txt'],
  567. ['/foo/bar.txt.ocTransferId7437493.part', false, '/foo/bar.txt'],
  568. ['/foo/bar.txt.part', true, '/foo/bar.txt'],
  569. ['/foo/bar.txt.ocTransferId7437493.part', true, '/foo/bar.txt'],
  570. ];
  571. }
  572. /**
  573. * test if getHeader adds the default module correctly to the header for
  574. * legacy files
  575. *
  576. * @dataProvider dataTestGetHeaderAddLegacyModule
  577. */
  578. public function testGetHeaderAddLegacyModule($header, $isEncrypted, $strippedPathExists, $expected): void {
  579. $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
  580. ->disableOriginalConstructor()->getMock();
  581. $sourceStorage->expects($this->once())
  582. ->method('is_file')
  583. ->with('test.txt')
  584. ->willReturn($strippedPathExists);
  585. $util = $this->getMockBuilder('\OC\Encryption\Util')
  586. ->onlyMethods(['stripPartialFileExtension', 'parseRawHeader'])
  587. ->setConstructorArgs([new View(), new Manager(
  588. $this->config,
  589. $this->createMock(ICacheFactory::class),
  590. $this->createMock(IEventDispatcher::class),
  591. $this->createMock(LoggerInterface::class),
  592. ), $this->groupManager, $this->config, $this->arrayCache])
  593. ->getMock();
  594. $util->expects($this->any())
  595. ->method('stripPartialFileExtension')
  596. ->willReturnCallback(function ($path) {
  597. return $path;
  598. });
  599. $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
  600. ->disableOriginalConstructor()->getMock();
  601. $cache->expects($this->any())
  602. ->method('get')
  603. ->willReturnCallback(function ($path) use ($isEncrypted) {
  604. return ['encrypted' => $isEncrypted, 'path' => $path];
  605. });
  606. $instance = $this->getMockBuilder(Encryption::class)
  607. ->setConstructorArgs(
  608. [
  609. [
  610. 'storage' => $sourceStorage,
  611. 'root' => 'foo',
  612. 'mountPoint' => '/',
  613. 'mount' => $this->mount
  614. ],
  615. $this->encryptionManager,
  616. $util,
  617. $this->logger,
  618. $this->file,
  619. null,
  620. $this->keyStore,
  621. $this->update,
  622. $this->mountManager,
  623. $this->arrayCache,
  624. ]
  625. )
  626. ->setMethods(['readFirstBlock', 'getCache'])
  627. ->getMock();
  628. $instance->method('readFirstBlock')->willReturn('');
  629. $util->method(('parseRawHeader'))->willReturn($header);
  630. $instance->method('getCache')->willReturn($cache);
  631. $result = $this->invokePrivate($instance, 'getHeader', ['test.txt']);
  632. $this->assertSameSize($expected, $result);
  633. foreach ($result as $key => $value) {
  634. $this->assertArrayHasKey($key, $expected);
  635. $this->assertSame($expected[$key], $value);
  636. }
  637. }
  638. public function dataTestGetHeaderAddLegacyModule() {
  639. return [
  640. [['cipher' => 'AES-128'], true, true, ['cipher' => 'AES-128', Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']],
  641. [[], true, false, []],
  642. [[], true, true, [Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']],
  643. [[], false, true, []],
  644. ];
  645. }
  646. public function dataCopyBetweenStorage() {
  647. return [
  648. [true, true, true],
  649. [true, false, false],
  650. [false, true, false],
  651. [false, false, false],
  652. ];
  653. }
  654. public function testCopyBetweenStorageMinimumEncryptedVersion(): void {
  655. $storage2 = $this->createMock(\OC\Files\Storage\Storage::class);
  656. $sourceInternalPath = $targetInternalPath = 'file.txt';
  657. $preserveMtime = $isRename = false;
  658. $storage2->expects($this->any())
  659. ->method('fopen')
  660. ->willReturnCallback(function ($path, $mode) {
  661. $temp = OC::$server->getTempManager();
  662. return fopen($temp->getTemporaryFile(), $mode);
  663. });
  664. $storage2->method('getId')
  665. ->willReturn('stroage2');
  666. $cache = $this->createMock(ICache::class);
  667. $cache->expects($this->once())
  668. ->method('get')
  669. ->with($sourceInternalPath)
  670. ->willReturn(['encryptedVersion' => 0]);
  671. $storage2->expects($this->once())
  672. ->method('getCache')
  673. ->willReturn($cache);
  674. $this->encryptionManager->expects($this->any())
  675. ->method('isEnabled')
  676. ->willReturn(true);
  677. global $mockedMountPointEncryptionEnabled;
  678. $mockedMountPointEncryptionEnabled = true;
  679. $expectedCachePut = [
  680. 'encrypted' => true,
  681. ];
  682. $expectedCachePut['encryptedVersion'] = 1;
  683. $this->cache->expects($this->once())
  684. ->method('put')
  685. ->with($sourceInternalPath, $expectedCachePut);
  686. $this->invokePrivate($this->instance, 'copyBetweenStorage', [$storage2, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename]);
  687. $this->assertFalse(false);
  688. }
  689. /**
  690. * @dataProvider dataCopyBetweenStorage
  691. *
  692. * @param bool $encryptionEnabled
  693. * @param bool $mountPointEncryptionEnabled
  694. * @param bool $expectedEncrypted
  695. */
  696. public function testCopyBetweenStorage($encryptionEnabled, $mountPointEncryptionEnabled, $expectedEncrypted): void {
  697. $storage2 = $this->createMock(\OC\Files\Storage\Storage::class);
  698. $sourceInternalPath = $targetInternalPath = 'file.txt';
  699. $preserveMtime = $isRename = false;
  700. $storage2->expects($this->any())
  701. ->method('fopen')
  702. ->willReturnCallback(function ($path, $mode) {
  703. $temp = OC::$server->getTempManager();
  704. return fopen($temp->getTemporaryFile(), $mode);
  705. });
  706. $storage2->method('getId')
  707. ->willReturn('stroage2');
  708. if ($expectedEncrypted) {
  709. $cache = $this->createMock(ICache::class);
  710. $cache->expects($this->once())
  711. ->method('get')
  712. ->with($sourceInternalPath)
  713. ->willReturn(['encryptedVersion' => 12345]);
  714. $storage2->expects($this->once())
  715. ->method('getCache')
  716. ->willReturn($cache);
  717. }
  718. $this->encryptionManager->expects($this->any())
  719. ->method('isEnabled')
  720. ->willReturn($encryptionEnabled);
  721. // FIXME can not overwrite the return after definition
  722. // $this->mount->expects($this->at(0))
  723. // ->method('getOption')
  724. // ->with('encrypt', true)
  725. // ->willReturn($mountPointEncryptionEnabled);
  726. global $mockedMountPointEncryptionEnabled;
  727. $mockedMountPointEncryptionEnabled = $mountPointEncryptionEnabled;
  728. $expectedCachePut = [
  729. 'encrypted' => $expectedEncrypted,
  730. ];
  731. if ($expectedEncrypted === true) {
  732. $expectedCachePut['encryptedVersion'] = 1;
  733. }
  734. $this->arrayCache->expects($this->never())->method('set');
  735. $this->cache->expects($this->once())
  736. ->method('put')
  737. ->with($sourceInternalPath, $expectedCachePut);
  738. $this->invokePrivate($this->instance, 'copyBetweenStorage', [$storage2, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename]);
  739. $this->assertFalse(false);
  740. }
  741. /**
  742. * @dataProvider dataTestCopyBetweenStorageVersions
  743. *
  744. * @param string $sourceInternalPath
  745. * @param string $targetInternalPath
  746. * @param bool $copyResult
  747. * @param bool $encrypted
  748. */
  749. public function testCopyBetweenStorageVersions($sourceInternalPath, $targetInternalPath, $copyResult, $encrypted): void {
  750. $sourceStorage = $this->createMock(\OC\Files\Storage\Storage::class);
  751. $targetStorage = $this->createMock(\OC\Files\Storage\Storage::class);
  752. $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
  753. ->disableOriginalConstructor()->getMock();
  754. $mountPoint = '/mountPoint';
  755. /** @var Encryption |MockObject $instance */
  756. $instance = $this->getMockBuilder(Encryption::class)
  757. ->setConstructorArgs(
  758. [
  759. [
  760. 'storage' => $targetStorage,
  761. 'root' => 'foo',
  762. 'mountPoint' => $mountPoint,
  763. 'mount' => $this->mount
  764. ],
  765. $this->encryptionManager,
  766. $this->util,
  767. $this->logger,
  768. $this->file,
  769. null,
  770. $this->keyStore,
  771. $this->update,
  772. $this->mountManager,
  773. $this->arrayCache
  774. ]
  775. )
  776. ->setMethods(['updateUnencryptedSize', 'getCache'])
  777. ->getMock();
  778. $targetStorage->expects($this->once())->method('copyFromStorage')
  779. ->with($sourceStorage, $sourceInternalPath, $targetInternalPath)
  780. ->willReturn($copyResult);
  781. $instance->expects($this->any())->method('getCache')
  782. ->willReturn($cache);
  783. $this->arrayCache->expects($this->once())->method('set')
  784. ->with('encryption_copy_version_' . $sourceInternalPath, true);
  785. if ($copyResult) {
  786. $cache->expects($this->once())->method('get')
  787. ->with($sourceInternalPath)
  788. ->willReturn(new CacheEntry(['encrypted' => $encrypted, 'size' => 42]));
  789. if ($encrypted) {
  790. $instance->expects($this->once())->method('updateUnencryptedSize')
  791. ->with($mountPoint . $targetInternalPath, 42);
  792. } else {
  793. $instance->expects($this->never())->method('updateUnencryptedSize');
  794. }
  795. } else {
  796. $instance->expects($this->never())->method('updateUnencryptedSize');
  797. }
  798. $result = $this->invokePrivate(
  799. $instance,
  800. 'copyBetweenStorage',
  801. [
  802. $sourceStorage,
  803. $sourceInternalPath,
  804. $targetInternalPath,
  805. false,
  806. false
  807. ]
  808. );
  809. $this->assertSame($copyResult, $result);
  810. }
  811. public function dataTestCopyBetweenStorageVersions() {
  812. return [
  813. ['/files/foo.txt', '/files_versions/foo.txt.768743', true, true],
  814. ['/files/foo.txt', '/files_versions/foo.txt.768743', true, false],
  815. ['/files/foo.txt', '/files_versions/foo.txt.768743', false, true],
  816. ['/files/foo.txt', '/files_versions/foo.txt.768743', false, false],
  817. ['/files_versions/foo.txt.6487634', '/files/foo.txt', true, true],
  818. ['/files_versions/foo.txt.6487634', '/files/foo.txt', true, false],
  819. ['/files_versions/foo.txt.6487634', '/files/foo.txt', false, true],
  820. ['/files_versions/foo.txt.6487634', '/files/foo.txt', false, false],
  821. ];
  822. }
  823. /**
  824. * @dataProvider dataTestIsVersion
  825. * @param string $path
  826. * @param bool $expected
  827. */
  828. public function testIsVersion($path, $expected): void {
  829. $this->assertSame($expected,
  830. $this->invokePrivate($this->instance, 'isVersion', [$path])
  831. );
  832. }
  833. public function dataTestIsVersion() {
  834. return [
  835. ['files_versions/foo', true],
  836. ['/files_versions/foo', true],
  837. ['//files_versions/foo', true],
  838. ['files/versions/foo', false],
  839. ['files/files_versions/foo', false],
  840. ['files_versions_test/foo', false],
  841. ];
  842. }
  843. /**
  844. * @dataProvider dataTestShouldEncrypt
  845. *
  846. * @param bool $encryptMountPoint
  847. * @param mixed $encryptionModule
  848. * @param bool $encryptionModuleShouldEncrypt
  849. * @param bool $expected
  850. */
  851. public function testShouldEncrypt(
  852. $encryptMountPoint,
  853. $encryptionModule,
  854. $encryptionModuleShouldEncrypt,
  855. $expected,
  856. ): void {
  857. $encryptionManager = $this->createMock(\OC\Encryption\Manager::class);
  858. $util = $this->createMock(Util::class);
  859. $fileHelper = $this->createMock(IFile::class);
  860. $keyStorage = $this->createMock(IStorage::class);
  861. $update = $this->createMock(Update::class);
  862. $mountManager = $this->createMock(\OC\Files\Mount\Manager::class);
  863. $mount = $this->createMock(IMountPoint::class);
  864. $arrayCache = $this->createMock(ArrayCache::class);
  865. $path = '/welcome.txt';
  866. $fullPath = 'admin/files/welcome.txt';
  867. $defaultEncryptionModule = $this->createMock(IEncryptionModule::class);
  868. $wrapper = $this->getMockBuilder(Encryption::class)
  869. ->setConstructorArgs(
  870. [
  871. ['mountPoint' => '', 'mount' => $mount, 'storage' => ''],
  872. $encryptionManager,
  873. $util,
  874. $this->logger,
  875. $fileHelper,
  876. null,
  877. $keyStorage,
  878. $update,
  879. $mountManager,
  880. $arrayCache
  881. ]
  882. )
  883. ->setMethods(['getFullPath', 'getEncryptionModule'])
  884. ->getMock();
  885. if ($encryptionModule === true) {
  886. /** @var IEncryptionModule|MockObject $encryptionModule */
  887. $encryptionModule = $this->createMock(IEncryptionModule::class);
  888. }
  889. $wrapper->method('getFullPath')->with($path)->willReturn($fullPath);
  890. $wrapper->expects($encryptMountPoint ? $this->once() : $this->never())
  891. ->method('getEncryptionModule')
  892. ->with($fullPath)
  893. ->willReturnCallback(
  894. function () use ($encryptionModule) {
  895. if ($encryptionModule === false) {
  896. throw new ModuleDoesNotExistsException();
  897. }
  898. return $encryptionModule;
  899. }
  900. );
  901. $mount->expects($this->once())->method('getOption')->with('encrypt', true)
  902. ->willReturn($encryptMountPoint);
  903. if ($encryptionModule !== null && $encryptionModule !== false) {
  904. $encryptionModule
  905. ->method('shouldEncrypt')
  906. ->with($fullPath)
  907. ->willReturn($encryptionModuleShouldEncrypt);
  908. }
  909. if ($encryptionModule === null) {
  910. $encryptionManager->expects($this->once())
  911. ->method('getEncryptionModule')
  912. ->willReturn($defaultEncryptionModule);
  913. }
  914. $defaultEncryptionModule->method('shouldEncrypt')->willReturn(true);
  915. $result = $this->invokePrivate($wrapper, 'shouldEncrypt', [$path]);
  916. $this->assertSame($expected, $result);
  917. }
  918. public function dataTestShouldEncrypt() {
  919. return [
  920. [false, false, false, false],
  921. [true, false, false, false],
  922. [true, true, false, false],
  923. [true, true, true, true],
  924. [true, null, false, true],
  925. ];
  926. }
  927. }