diff --git a/apps/encryption/tests/EncryptedStorageTest.php b/apps/encryption/tests/EncryptedStorageTest.php new file mode 100644 index 0000000000000..3219707d48b6c --- /dev/null +++ b/apps/encryption/tests/EncryptedStorageTest.php @@ -0,0 +1,85 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\encryption\tests; + +use OC\Files\Storage\Temporary; +use OC\Files\Storage\Wrapper\Encryption; +use OC\Files\View; +use OCP\Files\Mount\IMountManager; +use OCP\Files\Storage\IDisableEncryptionStorage; +use Test\TestCase; +use Test\Traits\EncryptionTrait; +use Test\Traits\MountProviderTrait; +use Test\Traits\UserTrait; + +class TemporaryNoEncrypted extends Temporary implements IDisableEncryptionStorage { + +} + +/** + * @group DB + */ +class EncryptedStorageTest extends TestCase { + use MountProviderTrait; + use EncryptionTrait; + use UserTrait; + + public function testMoveFromEncrypted() { + $this->createUser("test1", "test2"); + $this->setupForUser("test1", 'test2'); + + $unwrapped = new Temporary(); + + $this->registerMount("test1", new TemporaryNoEncrypted(), "/test1/files/unenc"); + $this->registerMount("test1", $unwrapped, "/test1/files/enc"); + + $this->loginWithEncryption("test1"); + + $view = new View("/test1/files"); + + /** @var IMountManager $mountManager */ + $mountManager = \OC::$server->get(IMountManager::class); + + $encryptedMount = $mountManager->find("/test1/files/enc"); + $unencryptedMount = $mountManager->find("/test1/files/unenc"); + $encryptedStorage = $encryptedMount->getStorage(); + $unencryptedStorage = $unencryptedMount->getStorage(); + $encryptedCache = $encryptedStorage->getCache(); + $unencryptedCache = $unencryptedStorage->getCache(); + + $this->assertTrue($encryptedStorage->instanceOfStorage(Encryption::class)); + $this->assertFalse($unencryptedStorage->instanceOfStorage(Encryption::class)); + + $encryptedStorage->file_put_contents("foo.txt", "bar"); + $this->assertEquals("bar", $encryptedStorage->file_get_contents("foo.txt")); + $this->assertStringStartsWith("HBEGIN:oc_encryption_module:", $unwrapped->file_get_contents("foo.txt")); + + $this->assertTrue($encryptedCache->get("foo.txt")->isEncrypted()); + + $view->rename("enc/foo.txt", "unenc/foo.txt"); + + $this->assertEquals("bar", $unencryptedStorage->file_get_contents("foo.txt")); + $this->assertFalse($unencryptedCache->get("foo.txt")->isEncrypted()); + } +} diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index ed5c40e86d25c..51269799cccdc 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -43,6 +43,7 @@ use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchQuery; +use OC\Files\Storage\Wrapper\Encryption; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Cache\CacheEntryInsertedEvent; @@ -639,6 +640,10 @@ protected function getMoveInfo($path) { return [$this->getNumericStorageId(), $path]; } + protected function hasEncryptionWrapper(): bool { + return $this->storage->instanceOfStorage(Encryption::class); + } + /** * Move a file or folder in the cache * @@ -690,6 +695,11 @@ public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT))) ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%'))); + // when moving from an encrypted storage to a non-encrypted storage remove the `encrypted` mark + if ($sourceCache->hasEncryptionWrapper() && !$this->hasEncryptionWrapper()) { + $query->set('encrypted', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)); + } + try { $query->execute(); } catch (\OC\DatabaseException $e) { @@ -706,6 +716,12 @@ public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { ->set('name', $query->createNamedParameter(basename($targetPath))) ->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT)) ->whereFileId($sourceId); + + // when moving from an encrypted storage to a non-encrypted storage remove the `encrypted` mark + if ($sourceCache->hasEncryptionWrapper() && !$this->hasEncryptionWrapper()) { + $query->set('encrypted', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)); + } + $query->execute(); $this->connection->commit(); @@ -1060,6 +1076,12 @@ public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, str throw new \RuntimeException("Invalid source cache entry on copyFromCache"); } $data = $this->cacheEntryToArray($sourceEntry); + + // when moving from an encrypted storage to a non-encrypted storage remove the `encrypted` mark + if ($sourceCache instanceof Cache && $sourceCache->hasEncryptionWrapper() && !$this->hasEncryptionWrapper()) { + $data['encrypted'] = 0; + } + $fileId = $this->put($targetPath, $data); if ($fileId <= 0) { throw new \RuntimeException("Failed to copy to " . $targetPath . " from cache with source data " . json_encode($data) . " "); diff --git a/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/lib/private/Files/Cache/Wrapper/CacheWrapper.php index e5300dc75f514..9a41b8ab65dae 100644 --- a/lib/private/Files/Cache/Wrapper/CacheWrapper.php +++ b/lib/private/Files/Cache/Wrapper/CacheWrapper.php @@ -56,6 +56,15 @@ protected function getCache() { return $this->cache; } + protected function hasEncryptionWrapper(): bool { + $cache = $this->getCache(); + if ($cache instanceof Cache) { + return $cache->hasEncryptionWrapper(); + } else { + return false; + } + } + /** * Make it easy for wrappers to modify every returned cache entry *