Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions build/integration/files_features/encryption.feature
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,62 @@ Feature: encryption
Then the command output does not contain the text "server-side encrypted: yes"
And Downloading file "/non-encrypted.txt" with range "bytes=0-8"
And Downloaded content should be "BLABLABLA"

Scenario: copy a folder with per-user keys
# Setup encryption with per-user keys
Given using new dav path
And invoking occ with "app:enable encryption"
And the command was successful
And invoking occ with "encryption:disable-master-key" with input "y"
And the command was successful
And invoking occ with "encryption:enable"
And the command was successful
And user "user1" exists
And User "user1" created a folder "/source"
And User "user1" created a folder "/source/sub"
And User "user1" uploads file with content "BLABLABLA" to "/source/sub/encrypted.txt"
# The target folders only exist on the storage, not yet in the cache, while the files inside are written
When User "user1" copies file "/source" to "/copy"
Then the HTTP status code should be "201"
And As an "user1"
And Downloading file "/copy/sub/encrypted.txt"
And Downloaded content should be "BLABLABLA"
# Restore the initial encryption state
And invoking occ with "encryption:disable"
And the command was successful
And invoking occ with "encryption:enable-master-key" with input "y"
And the command was successful

Scenario: copy a folder into a shared folder with per-user keys
# Setup encryption with per-user keys
Given using new dav path
And invoking occ with "app:enable encryption"
And the command was successful
And invoking occ with "encryption:disable-master-key" with input "y"
And the command was successful
And invoking occ with "encryption:enable"
And the command was successful
And user "user1" exists
And user "user2" exists
# Log in once so that the key pair of the share recipient exists
And User "user2" uploads file with content "BLABLABLA" to "/init.txt"
And User "user1" created a folder "/shared"
And User "user1" created a folder "/source"
And User "user1" uploads file with content "BLABLABLA" to "/source/encrypted.txt"
And as "user1" creating a share with
| path | /shared |
| shareType | 0 |
| shareWith | user2 |
| permissions | 31 |
And the HTTP status code should be "200"
# The share key of the recipient has to be created from the closest known parent
When User "user1" copies file "/source" to "/shared/copy"
Then the HTTP status code should be "201"
And As an "user2"
And Downloading file "/shared/copy/encrypted.txt"
And Downloaded content should be "BLABLABLA"
# Restore the initial encryption state
And invoking occ with "encryption:disable"
And the command was successful
And invoking occ with "encryption:enable-master-key" with input "y"
And the command was successful
10 changes: 10 additions & 0 deletions build/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2960,6 +2960,16 @@
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
</DeprecatedMethod>
</file>
<file src="core/Command/Encryption/DecryptAll.php">
<DeprecatedMethod>
<code><![CDATA[getAppValue]]></code>
<code><![CDATA[setAppValue]]></code>
<code><![CDATA[setAppValue]]></code>
<code><![CDATA[setAppValue]]></code>
<code><![CDATA[setAppValue]]></code>
<code><![CDATA[setAppValue]]></code>
</DeprecatedMethod>
</file>
<file src="core/Command/Encryption/Disable.php">
<DeprecatedMethod>
<code><![CDATA[getAppValue]]></code>
Expand Down
14 changes: 6 additions & 8 deletions core/Command/Encryption/DecryptAll.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
namespace OC\Core\Command\Encryption;

use OCP\App\IAppManager;
use OCP\IAppConfig;
use OCP\IConfig;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
Expand All @@ -25,7 +24,6 @@ class DecryptAll extends Command {
public function __construct(
protected IAppManager $appManager,
protected IConfig $config,
protected IAppConfig $appConfig,
protected \OC\Encryption\DecryptAll $decryptAll,
protected QuestionHelper $questionHelper,
) {
Expand Down Expand Up @@ -89,11 +87,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return 1;
}

$originallyEnabled = $this->appConfig->getValueBool('core', 'encryption_enabled');
$originallyEnabled = $this->config->getAppValue('core', 'encryption_enabled', 'no') === 'yes';
try {
if ($originallyEnabled) {
$output->write('Disable server side encryption... ');
$this->appConfig->setValueBool('core', 'encryption_enabled', false);
$this->config->setAppValue('core', 'encryption_enabled', 'no');
$output->writeln('done.');
} else {
$output->writeln('Server side encryption not enabled. Nothing to do.');
Expand Down Expand Up @@ -121,26 +119,26 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->writeln(' aborted.');
if ($originallyEnabled) {
$output->writeln('Server side encryption remains enabled');
$this->appConfig->setValueBool('core', 'encryption_enabled', true);
$this->config->setAppValue('core', 'encryption_enabled', 'yes');
}
} elseif (($uid !== '') && $originallyEnabled) {
$output->writeln('Server side encryption remains enabled');
$this->appConfig->setValueBool('core', 'encryption_enabled', true);
$this->config->setAppValue('core', 'encryption_enabled', 'yes');
}
$this->resetMaintenanceAndTrashbin();
return 0;
}
if ($originallyEnabled) {
$output->write('Enable server side encryption... ');
$this->appConfig->setValueBool('core', 'encryption_enabled', true);
$this->config->setAppValue('core', 'encryption_enabled', 'yes');
$output->writeln('done.');
}
$output->writeln('aborted');
return 1;
} catch (\Exception $e) {
// enable server side encryption again if something went wrong
if ($originallyEnabled) {
$this->appConfig->setValueBool('core', 'encryption_enabled', true);
$this->config->setAppValue('core', 'encryption_enabled', 'yes');
}
$this->resetMaintenanceAndTrashbin();
throw $e;
Expand Down
28 changes: 26 additions & 2 deletions lib/private/Encryption/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
use OCA\Files_External\Service\GlobalStoragesService;
use OCP\App\IAppManager;
use OCP\Cache\CappedMemoryCache;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\Share\IManager;

Expand Down Expand Up @@ -73,11 +75,14 @@ public function getAccessList($path) {
// first get the shares for the parent and cache the result so that we don't
// need to check all parents for every file
$parent = dirname($ownerPath);
$parentNode = $userFolder->get($parent);
if (isset($this->cache[$parent])) {
$resultForParents = $this->cache[$parent];
} else {
$resultForParents = $this->shareManager->getAccessList($parentNode);
$resultForParents = ['users' => [], 'public' => false, 'remote' => false];
$parentNode = $this->getClosestExistingNode($userFolder, $parent);
if ($parentNode !== null) {
$resultForParents = $this->shareManager->getAccessList($parentNode) + $resultForParents;
}
$this->cache[$parent] = $resultForParents;
}
$userIds = array_merge($userIds, $resultForParents['users']);
Expand Down Expand Up @@ -109,4 +114,23 @@ public function getAccessList($path) {

return ['users' => $uniqueUserIds, 'public' => $public];
}

/**
* Get the node for $path, or for its closest ancestor that is known to the cache.
*
* @return ?Node null if not even the user folder itself could be resolved
*/
private function getClosestExistingNode(Folder $userFolder, string $path): ?Node {
while (true) {
try {
return $userFolder->get($path);
} catch (NotFoundException) {
$parent = dirname($path);
if ($parent === $path) {
return null;
}
$path = $parent;
}
}
}
}
48 changes: 20 additions & 28 deletions tests/Core/Command/Encryption/DecryptAllTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

use OC\Core\Command\Encryption\DecryptAll;
use OCP\App\IAppManager;
use OCP\IAppConfig;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Console\Helper\QuestionHelper;
Expand All @@ -20,7 +19,6 @@

class DecryptAllTest extends TestCase {
private MockObject&IConfig $config;
private MockObject&IAppConfig $appConfig;
private MockObject&IAppManager $appManager;
private MockObject&InputInterface $consoleInput;
private MockObject&OutputInterface $consoleOutput;
Expand All @@ -31,7 +29,6 @@ protected function setUp(): void {
parent::setUp();

$this->config = $this->createMock(IConfig::class);
$this->appConfig = $this->createMock(IAppConfig::class);
$this->appManager = $this->createMock(IAppManager::class);
$this->questionHelper = $this->createMock(QuestionHelper::class);
$this->decryptAll = $this->createMock(\OC\Encryption\DecryptAll::class);
Expand Down Expand Up @@ -74,7 +71,6 @@ public function testMaintenanceAndTrashbin(): void {
$instance = new DecryptAll(
$this->appManager,
$this->config,
$this->appConfig,
$this->decryptAll,
$this->questionHelper
);
Expand All @@ -95,15 +91,14 @@ public function testExecute($encryptionEnabled, $continue): void {
$instance = new DecryptAll(
$this->appManager,
$this->config,
$this->appConfig,
$this->decryptAll,
$this->questionHelper
);

$this->appConfig->expects($this->once())
->method('getValueBool')
->with('core', 'encryption_enabled')
->willReturn($encryptionEnabled);
$this->config->expects($this->once())
->method('getAppValue')
->with('core', 'encryption_enabled', 'no')
->willReturn($encryptionEnabled ? 'yes' : 'no');

$this->consoleInput->expects($this->any())
->method('getArgument')
Expand All @@ -112,19 +107,18 @@ public function testExecute($encryptionEnabled, $continue): void {

if ($encryptionEnabled) {
$calls = [
['core', 'encryption_enabled', false, false],
['core', 'encryption_enabled', true, false],
['core', 'encryption_enabled', 'no'],
['core', 'encryption_enabled', 'yes'],
];
$this->appConfig->expects($this->exactly(count($calls)))
->method('setValueBool')
->willReturnCallback(function () use (&$calls): bool {
$this->config->expects($this->exactly(count($calls)))
->method('setAppValue')
->willReturnCallback(function () use (&$calls): void {
$expected = array_shift($calls);
$this->assertEquals($expected, func_get_args());
return true;
});
} else {
$this->appConfig->expects($this->never())
->method('setValueBool');
$this->config->expects($this->never())
->method('setAppValue');
}
$this->questionHelper->expects($this->once())
->method('ask')
Expand Down Expand Up @@ -156,27 +150,25 @@ public function testExecuteFailure(): void {
$instance = new DecryptAll(
$this->appManager,
$this->config,
$this->appConfig,
$this->decryptAll,
$this->questionHelper
);

// make sure that we enable encryption again after a exception was thrown
$calls = [
['core', 'encryption_enabled', false, false],
['core', 'encryption_enabled', true, false],
['core', 'encryption_enabled', 'no'],
['core', 'encryption_enabled', 'yes'],
];
$this->appConfig->expects($this->exactly(2))
->method('setValuebool')
->willReturnCallback(function () use (&$calls): bool {
$this->config->expects($this->exactly(2))
->method('setAppValue')
->willReturnCallback(function () use (&$calls): void {
$expected = array_shift($calls);
$this->assertEquals($expected, func_get_args());
return true;
});
$this->appConfig->expects($this->once())
->method('getValueBool')
->with('core', 'encryption_enabled')
->willReturn(true);
$this->config->expects($this->once())
->method('getAppValue')
->with('core', 'encryption_enabled', 'no')
->willReturn('yes');

$this->consoleInput->expects($this->any())
->method('getArgument')
Expand Down
Loading
Loading