diff --git a/cypress/e2e/encryption.cy.ts b/cypress/e2e/encryption.cy.ts new file mode 100644 index 000000000..478279603 --- /dev/null +++ b/cypress/e2e/encryption.cy.ts @@ -0,0 +1,169 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { + addUserToGroup, + createGroup, + createGroupFolder, + deleteGroupFolder, + disableEncryption, + disableEncryptionModule, + disableGroupfoldersEncryption, + disableHomeStorageEncryption, + enableEncryption, + enableEncryptionModule, + enableGroupfoldersEncryption, + enableHomeStorageEncryption, + enterFolder, + fileOrFolderExists, + PERMISSION_DELETE, + PERMISSION_READ, + PERMISSION_WRITE, +} from './groupfoldersUtils' + +import { + assertFileContent, + moveFile, +} from './files/filesUtils' + +import { randHash } from '../utils' + +import type { User } from '@nextcloud/cypress' + +describe('Groupfolders encryption behavior', () => { + let user1: User + let groupFolderId: string + let groupName: string + let groupFolderName: string + + before(() => { + enableEncryptionModule() + enableEncryption() + }) + + beforeEach(() => { + if (groupFolderId) { + deleteGroupFolder(groupFolderId) + } + groupName = `test_group_${randHash()}` + groupFolderName = `test_group_folder_${randHash()}` + + cy.createRandomUser() + .then(_user => { + user1 = _user + }) + createGroup(groupName) + .then(() => { + addUserToGroup(groupName, user1.userId) + createGroupFolder(groupFolderName, groupName, [PERMISSION_READ, PERMISSION_WRITE, PERMISSION_DELETE]) + }) + }) + + after(() => { + // Restore default values + disableGroupfoldersEncryption() + enableHomeStorageEncryption() + disableEncryption() + disableEncryptionModule() + }) + + it('Move file from encrypted storage to encrypted groupfolder', () => { + enableHomeStorageEncryption() + enableGroupfoldersEncryption() + + cy.uploadContent(user1, new Blob(['Content of the file']), 'text/plain', '/file1.txt') + + cy.login(user1) + cy.visit('/apps/files') + + moveFile('file1.txt', groupFolderName) + + enterFolder(groupFolderName) + fileOrFolderExists('file1.txt') + assertFileContent('file1.txt', 'Content of the file') + }) + + it('Move file from encrypted storage to non encrypted groupfolder', () => { + enableHomeStorageEncryption() + disableGroupfoldersEncryption() + + cy.uploadContent(user1, new Blob(['Content of the file']), 'text/plain', '/file1.txt') + + cy.login(user1) + cy.visit('/apps/files') + + moveFile('file1.txt', groupFolderName) + + enterFolder(groupFolderName) + fileOrFolderExists('file1.txt') + assertFileContent('file1.txt', 'Content of the file') + }) + + it('Move file from non encrypted storage to encrypted groupfolder', () => { + disableHomeStorageEncryption() + enableGroupfoldersEncryption() + + cy.uploadContent(user1, new Blob(['Content of the file']), 'text/plain', '/file1.txt') + + cy.login(user1) + cy.visit('/apps/files') + + moveFile('file1.txt', groupFolderName) + + enterFolder(groupFolderName) + fileOrFolderExists('file1.txt') + assertFileContent('file1.txt', 'Content of the file') + }) + + it('Move file from encrypted groupfolder to encrypted storage', () => { + enableHomeStorageEncryption() + enableGroupfoldersEncryption() + + cy.uploadContent(user1, new Blob(['Content of the file']), 'text/plain', `/${groupFolderName}/file1.txt`) + + cy.login(user1) + cy.visit('/apps/files') + + enterFolder(groupFolderName) + moveFile('file1.txt', '/') + + cy.visit('/apps/files') + fileOrFolderExists('file1.txt') + assertFileContent('file1.txt', 'Content of the file') + }) + + it('Move file from encrypted groupfolder to non encrypted storage', () => { + disableHomeStorageEncryption() + enableGroupfoldersEncryption() + + cy.uploadContent(user1, new Blob(['Content of the file']), 'text/plain', `/${groupFolderName}/file1.txt`) + + cy.login(user1) + cy.visit('/apps/files') + + enterFolder(groupFolderName) + moveFile('file1.txt', '/') + + cy.visit('/apps/files') + fileOrFolderExists('file1.txt') + assertFileContent('file1.txt', 'Content of the file') + }) + + it('Move file from non encrypted groupfolder to encrypted storage', () => { + enableHomeStorageEncryption() + disableGroupfoldersEncryption() + + cy.uploadContent(user1, new Blob(['Content of the file']), 'text/plain', `/${groupFolderName}/file1.txt`) + + cy.login(user1) + cy.visit('/apps/files') + + enterFolder(groupFolderName) + moveFile('file1.txt', '/') + + cy.visit('/apps/files') + fileOrFolderExists('file1.txt') + assertFileContent('file1.txt', 'Content of the file') + }) +}) diff --git a/cypress/e2e/files/filesUtils.ts b/cypress/e2e/files/filesUtils.ts index fae7d80ac..2ea28a8c5 100644 --- a/cypress/e2e/files/filesUtils.ts +++ b/cypress/e2e/files/filesUtils.ts @@ -94,3 +94,11 @@ export const clickOnBreadcumbs = (label: string) => { cy.get('[data-cy-files-content-breadcrumbs]').contains(label).click() cy.wait('@propfind') } + +export const assertFileContent = (fileName: string, expectedContent: string) => { + cy.intercept({ method: 'GET', times: 1, url: 'remote.php/**' }).as('downloadFile') + getRowForFile(fileName).should('be.visible') + triggerActionForFile(fileName, 'download') + cy.wait('@downloadFile') + .then(({ response }) => expect(response?.body).to.equal(expectedContent)) +} diff --git a/cypress/e2e/groupfoldersUtils.ts b/cypress/e2e/groupfoldersUtils.ts index c37f84e2e..577d4463e 100644 --- a/cypress/e2e/groupfoldersUtils.ts +++ b/cypress/e2e/groupfoldersUtils.ts @@ -57,6 +57,39 @@ export function deleteGroupFolder(groupFolderId: string) { return cy.runOccCommand(`groupfolders:delete ${groupFolderId}`) } +export function enableEncryptionModule() { + return cy.runOccCommand('app:enable encryption') +} + +export function disableEncryptionModule() { + return cy.runOccCommand('app:disable encryption') +} + +export function enableEncryption() { + return cy.runOccCommand('config:app:set --value=yes core encryption_enabled') +} + +export function disableEncryption() { + return cy.runOccCommand('config:app:delete core encryption_enabled') +} + +export function enableHomeStorageEncryption() { + // Default value is enabled + return cy.runOccCommand('config:app:delete encryption encryptHomeStorage') +} + +export function disableHomeStorageEncryption() { + return cy.runOccCommand('config:app:set --value=0 encryption encryptHomeStorage') +} + +export function enableGroupfoldersEncryption() { + return cy.runOccCommand('config:app:set --value=true groupfolders enable_encryption') +} + +export function disableGroupfoldersEncryption() { + return cy.runOccCommand('config:app:delete groupfolders enable_encryption') +} + export function fileOrFolderExists(name: string) { // Make sure file list is loaded first cy.get('[data-cy-files-list-tfoot],[data-cy-files-content-empty]').should('be.visible') diff --git a/lib/Mount/GroupFolderEncryptionJail.php b/lib/Mount/GroupFolderEncryptionJail.php new file mode 100644 index 000000000..7f9ce9e1a --- /dev/null +++ b/lib/Mount/GroupFolderEncryptionJail.php @@ -0,0 +1,29 @@ +getWrapperStorage(); + } + // By default the Jail reuses the inner cache, but when encryption is + // enabled the storage needs to be passed to the cache so it takes into + // account the outer Encryption wrapper. + $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage); + return new CacheJail($sourceCache, $this->rootPath); + } +} diff --git a/lib/Mount/MountProvider.php b/lib/Mount/MountProvider.php index 74a1cd4ec..16a406140 100644 --- a/lib/Mount/MountProvider.php +++ b/lib/Mount/MountProvider.php @@ -217,11 +217,11 @@ public function getMount( $cacheEntry['permissions'] &= $aclRootPermissions; } - $baseStorage = new Jail([ - 'storage' => $storage, - 'root' => $rootPath - ]); if ($this->enableEncryption) { + $baseStorage = new GroupFolderEncryptionJail([ + 'storage' => $storage, + 'root' => $rootPath + ]); $quotaStorage = new GroupFolderStorage([ 'storage' => $baseStorage, 'quota' => $quota, @@ -231,6 +231,10 @@ public function getMount( 'mountOwner' => $user, ]); } else { + $baseStorage = new Jail([ + 'storage' => $storage, + 'root' => $rootPath + ]); $quotaStorage = new GroupFolderNoEncryptionStorage([ 'storage' => $baseStorage, 'quota' => $quota, diff --git a/tests/stub.phpstub b/tests/stub.phpstub index e0b300368..7f68ac3eb 100644 --- a/tests/stub.phpstub +++ b/tests/stub.phpstub @@ -750,9 +750,15 @@ namespace OC\Files\Cache { namespace OC\Files\Cache\Wrapper { use OC\Files\Cache\Cache; + use OCP\Files\Cache\ICache; + class CacheWrapper extends Cache { public function getCache(): Cache {} } + + class CacheJail extends CacheWrapper { + public function __construct(?ICache $cache, string $root, ?CacheDependencies $dependencies = null) {} + } } namespace OC\Files {