Cache end to end encrypted paths

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
This commit is contained in:
Carl Schwan 2022-08-16 10:36:32 +02:00
Родитель ace8a27210
Коммит b8b9c93312
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: C3AA6B3A5EFA7AC5
7 изменённых файлов: 145 добавлений и 38 удалений

Просмотреть файл

@ -26,6 +26,7 @@ use OCP\AppFramework\Http;
use OCA\DAV\Connector\Sabre\Directory;
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\DAV\Connector\Sabre\File;
use OCA\EndToEndEncryption\E2EEnabledPathCache;
use OCP\Files\FileInfo;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
@ -41,6 +42,7 @@ abstract class APlugin extends ServerPlugin {
protected ?Server $server = null;
protected IRootFolder $rootFolder;
protected IUserSession $userSession;
protected E2EEnabledPathCache $pathCache;
/**
* Should plugin be applied to the current node?
@ -50,14 +52,15 @@ abstract class APlugin extends ServerPlugin {
/**
* APlugin constructor.
*
* @param IRootFolder $rootFolder
* @param IUserSession $userSession
*/
public function __construct(IRootFolder $rootFolder,
IUserSession $userSession) {
public function __construct(
IRootFolder $rootFolder,
IUserSession $userSession,
E2EEnabledPathCache $pathCache
) {
$this->rootFolder = $rootFolder;
$this->userSession = $userSession;
$this->pathCache = $pathCache;
}
/**
@ -117,6 +120,18 @@ abstract class APlugin extends ServerPlugin {
}
}
/**
* Checks if the path is an E2E folder or inside an E2E folder
*/
protected function isE2EEnabledPath(string $path): bool {
try {
$node = $this->getFileNode($path);
} catch (NotFound $e) {
return false;
}
return $this->pathCache->isE2EEnabledPath($node, $path);
}
/**
* Check if we process a file or directory. This plugin should ignore calendars
* and contacts
@ -132,27 +147,4 @@ abstract class APlugin extends ServerPlugin {
return $this->applyPlugin[$url];
}
/**
* Checks if the path is an E2E folder or inside an E2E folder
*/
protected function isE2EEnabledPath(string $path):bool {
try {
$node = $this->getFileNode($path);
} catch (NotFound $e) {
return false;
}
while ($node->isEncrypted() === false || $node->getType() === FileInfo::TYPE_FILE) {
$node = $node->getParent();
// Nitpick: This doesn't check if root is E2E,
// but that's not supported at the moment anyway
if ($node->getPath() === '/') {
// top-level folder reached
return false;
}
}
return true;
}
}

Просмотреть файл

@ -39,6 +39,7 @@ use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\INode;
use Sabre\DAV\Server;
use Sabre\HTTP\RequestInterface;
use OCA\EndToEndEncryption\E2EEnabledPathCache;
class LockPlugin extends APlugin {
private LockManager $lockManager;
@ -47,8 +48,9 @@ class LockPlugin extends APlugin {
public function __construct(IRootFolder $rootFolder,
IUserSession $userSession,
LockManager $lockManager,
UserAgentManager $userAgentManager) {
parent::__construct($rootFolder, $userSession);
UserAgentManager $userAgentManager,
E2EEnabledPathCache $pathCache) {
parent::__construct($rootFolder, $userSession, $pathCache);
$this->lockManager = $lockManager;
$this->userAgentManager = $userAgentManager;
}

Просмотреть файл

@ -27,6 +27,7 @@ namespace OCA\EndToEndEncryption\Connector\Sabre;
use OCA\DAV\Connector\Sabre\Directory;
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\EndToEndEncryption\UserAgentManager;
use OCA\EndToEndEncryption\E2EEnabledPathCache;
use OCP\Files\IRootFolder;
use OCP\IRequest;
use OCP\IUserSession;
@ -43,8 +44,9 @@ class PropFindPlugin extends APlugin {
public function __construct(IRootFolder $rootFolder,
IUserSession $userSession,
UserAgentManager $userAgentManager,
IRequest $request) {
parent::__construct($rootFolder, $userSession);
IRequest $request,
E2EEnabledPathCache $pathCache) {
parent::__construct($rootFolder, $userSession, $pathCache);
$this->userAgentManager = $userAgentManager;
$this->request = $request;
}

Просмотреть файл

@ -31,6 +31,7 @@ use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use OCA\EndToEndEncryption\E2EEnabledPathCache;
/**
* Class WritePlugin

102
lib/E2EEnabledPathCache.php Normal file
Просмотреть файл

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: 2020 Georg Ehrke <georg-nextcloud@ehrke.email>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\EndToEndEncryption;
use Sabre\DAV\INode;
use OCP\Files\Node;
use OCP\Files\IHomeStorage;
use OCP\Files\Cache\ICache;
use OC\Files\Storage\Wrapper\Wrapper;
use OCA\Files_Sharing\SharedStorage;
class E2EEnabledPathCache {
/**
* @psalm-type FileId=int
*
* @psalm-type EncryptedState=array{0: FileId, 1: bool}
*
* @psalm-type Path=string
*
* @psalm-type StorageId=string|int
*/
/** @var array<StorageId, array<Path, EncryptedState>> */
protected $cacheEntries;
/**
* Checks if the path is an E2E folder or inside an E2E folder
*
* @param INode&Node $node
*/
public function isE2EEnabledPath($node, string $path): bool {
$storage = $node->getStorage();
$cache = $storage->getCache();
$encryptedStates = $this->getEncryptedStates($cache, $path, $storage, !$storage->instanceOfStorage(IHomeStorage::class) || $storage->instanceOfStorage(SharedStorage::class));
foreach ($encryptedStates as [$fileid, $encryptedState]) {
if ($encryptedState) {
return true;
}
}
return false;
}
/**
* Get the file ids of the given path and its parents
*
* @return array<Path, EncryptedState>
*/
protected function getEncryptedStates(ICache $cache, string $path, $storage, bool $isExternalStorage): array {
/** @psalm-suppress InvalidArgument */
if ($storage->instanceOfStorage(\OCA\GroupFolders\Mount\GroupFolderStorage::class)) {
// Special implementation for groupfolder since all groupfolders share the same storage
// id so add the group folder id in the cache key too.
$groupFolderStorage = $storage;
if ($this->storage instanceof Wrapper) {
$groupFolderStorage = $getInstanceOfStorage(\OCA\GroupFolders\Mount\GroupFolderStorage::class);
}
if ($groupFolderStorage === null) {
throw new \LogicException('Should not happen: Storage is instance of GroupFolderStorage but no group folder storage found while unwrapping.');
}
/**
* @psalm-suppress UndefinedDocblockClass
* @psalm-suppress UndefinedInterfaceMethod
*/
$cacheId = $cache->getNumericStorageId() . '/' . $groupFolderStorage->getFolderId();
} else {
$cacheId = $cache->getNumericStorageId();
}
if (isset($this->cacheEntries[$cacheId][$path])) {
return $this->cacheEntries[$cacheId][$path];
}
$parentIds = [];
if ($path !== $this->dirname($path)) {
$cacheEntries = [];
$cacheEntry = $cache->get($path);
if ($cacheEntry !== false) {
$cacheEntries[] = [$cacheEntry->getId(), $cacheEntry->isEncrypted()];
if ($cacheEntry->isEncrypted()) {
// no need to go further down in the tree
$this->cacheEntries[$cacheId][$path] = $parentEntries;
return $cacheEntry;
}
}
$cacheEntries = array_merge($this->getEncryptedStates($cache, $this->dirname($path), $storage, $isExternalStorage), $cacheEntries);
} elseif (!$isExternalStorage) {
return [];
}
$this->cacheEntries[$cacheId][$path] = $cacheEntries;
return $cacheEntries;
}
protected function dirname(string $path): string {
$dir = dirname($path);
return $dir === '.' ? '' : $dir;
}
}

Просмотреть файл

@ -31,6 +31,7 @@ use OCA\DAV\Upload\FutureFile;
use OCA\EndToEndEncryption\Connector\Sabre\LockPlugin;
use OCA\EndToEndEncryption\LockManager;
use OCA\EndToEndEncryption\UserAgentManager;
use OCA\EndToEndEncryption\E2EEnabledPathCache;
use OCP\Files\FileInfo;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
@ -56,8 +57,10 @@ class LockPluginTest extends TestCase {
/** @var UserAgentManager|\PHPUnit\Framework\MockObject\MockObject */
private $userAgentManager;
/** @var LockPlugin */
private $plugin;
/** @var E2EEnabledPathCache|\PHPUnit\Framework\MockObject\MockObject */
private $pathCache;
private LockPlugin $plugin;
protected function setUp(): void {
parent::setUp();
@ -66,9 +69,10 @@ class LockPluginTest extends TestCase {
$this->userSession = $this->createMock(IUserSession::class);
$this->lockManager = $this->createMock(LockManager::class);
$this->userAgentManager = $this->createMock(UserAgentManager::class);
$this->pathCache = $this->createMock(E2EEnabledPathCache::class);
$this->plugin = new LockPlugin($this->rootFolder, $this->userSession,
$this->lockManager, $this->userAgentManager);
$this->lockManager, $this->userAgentManager, $this->pathCache);
}
public function testInitialize(): void {

Просмотреть файл

@ -25,6 +25,7 @@ namespace OCA\EndToEndEncryption\Tests\Unit\Connector\Sabre;
use OCA\DAV\Connector\Sabre\File;
use OCA\EndToEndEncryption\Connector\Sabre\RedirectRequestPlugin;
use OCA\EndToEndEncryption\E2EEnabledPathCache;
use OCP\Files\IRootFolder;
use OCP\IUserSession;
use Sabre\DAV\Server;
@ -39,16 +40,19 @@ class RedirectRequestPluginTest extends TestCase {
/** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */
private $userSession;
/** @var RedirectRequestPlugin */
private $plugin;
/** @var E2EEnabledPathCache|\PHPUnit\Framework\MockObject\MockObject */
private $pathCache;
private RedirectRequestPlugin $plugin;
protected function setUp(): void {
parent::setUp();
$this->rootFolder = $this->createMock(IRootFolder::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->pathCache = $this->createMock(E2EEnabledPathCache::class);
$this->plugin = new RedirectRequestPlugin($this->rootFolder, $this->userSession);
$this->plugin = new RedirectRequestPlugin($this->rootFolder, $this->userSession, $this->pathCache);
}
public function testInitialize(): void {