Cache end to end encrypted paths
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
This commit is contained in:
Родитель
ace8a27210
Коммит
b8b9c93312
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче