From 56b17099f5ac48a7a4b5ce712c491ba38c8ba772 Mon Sep 17 00:00:00 2001 From: Stephen Cuppett Date: Wed, 29 Apr 2026 11:58:31 -0400 Subject: [PATCH 1/3] fix(encryption): Refactor EncryptionWrapper with HomeMountPoint support Rewrite conditional flow to use early-return guards: skip IDisableEncryptionStorage, skip the root mount, respect encryptHomeStorage for HomeMountPoints. Uses IAppConfig for the encryptHomeStorage setting with a legacy string fallback for the upgrade window. Co-Authored-By: Claude Sonnet 4.6 (1M context) Signed-off-by: Stephen Cuppett --- lib/private/Encryption/EncryptionWrapper.php | 86 ++++++++++++++------ 1 file changed, 62 insertions(+), 24 deletions(-) diff --git a/lib/private/Encryption/EncryptionWrapper.php b/lib/private/Encryption/EncryptionWrapper.php index 68d2efd8b8c0b..355257218e806 100644 --- a/lib/private/Encryption/EncryptionWrapper.php +++ b/lib/private/Encryption/EncryptionWrapper.php @@ -8,14 +8,17 @@ namespace OC\Encryption; use OC\Files\Filesystem; +use OC\Files\Mount\HomeMountPoint; use OC\Files\Storage\Wrapper\Encryption; use OC\Files\View; use OC\Memcache\ArrayCache; use OCP\Encryption\IFile; use OCP\Encryption\Keys\IStorage as EncryptionKeysStorage; +use OCP\Exceptions\AppConfigTypeConflictException; use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage\IDisableEncryptionStorage; use OCP\Files\Storage\IStorage; +use OCP\IAppConfig; use OCP\IConfig; use OCP\IGroupManager; use OCP\IUserManager; @@ -57,32 +60,67 @@ public function wrapStorage(string $mountPoint, IStorage $storage, IMountPoint $ 'mount' => $mount ]; - if ($force || (!$storage->instanceOfStorage(IDisableEncryptionStorage::class) && $mountPoint !== '/')) { - $user = Server::get(IUserSession::class)->getUser(); - $mountManager = Filesystem::getMountManager(); - $uid = $user ? $user->getUID() : null; - $fileHelper = Server::get(IFile::class); - $keyStorage = Server::get(EncryptionKeysStorage::class); + // Only evaluate other conditions if not forced + if (!$force) { + // If a disabled storage medium, return basic storage + if ($storage->instanceOfStorage(IDisableEncryptionStorage::class)) { + return $storage; + } - $util = new Util( - new View(), - Server::get(IUserManager::class), - Server::get(IGroupManager::class), - Server::get(IConfig::class) - ); - return new Encryption( - $parameters, - $this->manager, - $util, - $this->logger, - $fileHelper, - $uid, - $keyStorage, - $mountManager, - $this->arrayCache + // Root mount point handling: skip encryption wrapper + if ($mountPoint === '/') { + return $storage; + } + + // Skip encryption for home mounts if encryptHomeStorage is disabled + if ($mount instanceof HomeMountPoint && !$this->shouldEncryptHomeStorage()) { + return $storage; + } + } + + // Apply encryption wrapper + $user = Server::get(IUserSession::class)->getUser(); + $mountManager = Filesystem::getMountManager(); + $uid = $user ? $user->getUID() : null; + $fileHelper = Server::get(IFile::class); + $keyStorage = Server::get(EncryptionKeysStorage::class); + + $util = new Util( + new View(), + Server::get(IUserManager::class), + Server::get(IGroupManager::class), + Server::get(IConfig::class) + ); + return new Encryption( + $parameters, + $this->manager, + $util, + $this->logger, + $fileHelper, + $uid, + $keyStorage, + $mountManager, + $this->arrayCache + ); + } + + private function shouldEncryptHomeStorage(): bool { + $appConfig = Server::get(IAppConfig::class); + try { + return $appConfig->getValueBool('encryption', 'encryptHomeStorage', true); + } catch (AppConfigTypeConflictException) { + // Stored as VALUE_STRING from a pre-upgrade installation. + // RetypeEncryptionConfigKeys repair step will fix the type on occ upgrade. + return $this->parseLegacyBoolString( + $appConfig->getValueString('encryption', 'encryptHomeStorage', '1') ); - } else { - return $storage; + } catch (\Throwable) { + // DB not ready (e.g. oc_appconfig does not yet exist during install). + return true; } } + + private function parseLegacyBoolString(string $value): bool { + return in_array(strtolower(trim($value)), ['1', 'true', 'yes', 'on'], true); + } } From c87968ef23275ddbf7abd52ab5d5d8c899d865ef Mon Sep 17 00:00:00 2001 From: Stephen Cuppett Date: Thu, 30 Apr 2026 20:58:12 -0400 Subject: [PATCH 2/3] fix(encryption): Inject IAppConfig for encryptHomeStorage value Signed-off-by: Stephen Cuppett --- lib/private/Encryption/EncryptionWrapper.php | 25 +++---------------- lib/private/Encryption/Manager.php | 6 +++-- lib/private/Server.php | 3 ++- .../lib/Encryption/EncryptionWrapperTest.php | 7 +++++- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/lib/private/Encryption/EncryptionWrapper.php b/lib/private/Encryption/EncryptionWrapper.php index 355257218e806..a926b9a9a7a98 100644 --- a/lib/private/Encryption/EncryptionWrapper.php +++ b/lib/private/Encryption/EncryptionWrapper.php @@ -14,7 +14,6 @@ use OC\Memcache\ArrayCache; use OCP\Encryption\IFile; use OCP\Encryption\Keys\IStorage as EncryptionKeysStorage; -use OCP\Exceptions\AppConfigTypeConflictException; use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage\IDisableEncryptionStorage; use OCP\Files\Storage\IStorage; @@ -40,6 +39,7 @@ class EncryptionWrapper { public function __construct( private ArrayCache $arrayCache, private Manager $manager, + private IAppConfig $appConfig, private LoggerInterface $logger, ) { } @@ -73,7 +73,8 @@ public function wrapStorage(string $mountPoint, IStorage $storage, IMountPoint $ } // Skip encryption for home mounts if encryptHomeStorage is disabled - if ($mount instanceof HomeMountPoint && !$this->shouldEncryptHomeStorage()) { + if ($mount instanceof HomeMountPoint && + !$this->appConfig->getValueBool('encryption', 'encryptHomeStorage', true)) { return $storage; } } @@ -103,24 +104,4 @@ public function wrapStorage(string $mountPoint, IStorage $storage, IMountPoint $ $this->arrayCache ); } - - private function shouldEncryptHomeStorage(): bool { - $appConfig = Server::get(IAppConfig::class); - try { - return $appConfig->getValueBool('encryption', 'encryptHomeStorage', true); - } catch (AppConfigTypeConflictException) { - // Stored as VALUE_STRING from a pre-upgrade installation. - // RetypeEncryptionConfigKeys repair step will fix the type on occ upgrade. - return $this->parseLegacyBoolString( - $appConfig->getValueString('encryption', 'encryptHomeStorage', '1') - ); - } catch (\Throwable) { - // DB not ready (e.g. oc_appconfig does not yet exist during install). - return true; - } - } - - private function parseLegacyBoolString(string $value): bool { - return in_array(strtolower(trim($value)), ['1', 'true', 'yes', 'on'], true); - } } diff --git a/lib/private/Encryption/Manager.php b/lib/private/Encryption/Manager.php index ac27f0911b8da..b35480e1c1661 100644 --- a/lib/private/Encryption/Manager.php +++ b/lib/private/Encryption/Manager.php @@ -18,6 +18,7 @@ use OCP\Encryption\IManager; use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage\IStorage; +use OCP\IAppConfig; use OCP\IConfig; use OCP\IL10N; use Psr\Log\LoggerInterface; @@ -32,6 +33,7 @@ public function __construct( protected View $rootView, protected Util $util, protected ArrayCache $arrayCache, + protected IAppConfig $appConfig, ) { $this->encryptionModules = []; } @@ -205,13 +207,13 @@ public function getDefaultEncryptionModuleId() { public function setupStorage() { // If encryption is disabled and there are no loaded modules it makes no sense to load the wrapper if (!empty($this->encryptionModules) || $this->isEnabled()) { - $encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->logger); + $encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->appConfig, $this->logger); Filesystem::addStorageWrapper('oc_encryption', [$encryptionWrapper, 'wrapStorage'], 2); } } public function forceWrapStorage(IMountPoint $mountPoint, IStorage $storage) { - $encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->logger); + $encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->appConfig, $this->logger); return $encryptionWrapper->wrapStorage($mountPoint->getMountPoint(), $storage, $mountPoint, true); } diff --git a/lib/private/Server.php b/lib/private/Server.php index 2a33c18eb05d9..164a32bf7d2df 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -384,7 +384,8 @@ public function __construct( $c->getL10N('core'), new View(), $util, - new ArrayCache() + new ArrayCache(), + $c->get(IAppConfig::class), ); }); $this->registerAlias(\OCP\Encryption\IManager::class, Encryption\Manager::class); diff --git a/tests/lib/Encryption/EncryptionWrapperTest.php b/tests/lib/Encryption/EncryptionWrapperTest.php index b80d1a08af64e..c3ef81cf2805d 100644 --- a/tests/lib/Encryption/EncryptionWrapperTest.php +++ b/tests/lib/Encryption/EncryptionWrapperTest.php @@ -15,6 +15,7 @@ use OCA\Files_Trashbin\Storage; use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage\IDisableEncryptionStorage; +use OCP\IAppConfig; use Psr\Log\LoggerInterface; use Test\TestCase; @@ -31,6 +32,9 @@ class EncryptionWrapperTest extends TestCase { /** @var \PHPUnit\Framework\MockObject\MockObject|ArrayCache */ private $arrayCache; + /** @var \PHPUnit\Framework\MockObject\MockObject|IAppConfig */ + private $appConfig; + #[\Override] protected function setUp(): void { parent::setUp(); @@ -38,8 +42,9 @@ protected function setUp(): void { $this->arrayCache = $this->createMock(ArrayCache::class); $this->manager = $this->createMock(Manager::class); $this->logger = $this->createMock(LoggerInterface::class); + $this->appConfig = $this->createMock(IAppConfig::class); - $this->instance = new EncryptionWrapper($this->arrayCache, $this->manager, $this->logger); + $this->instance = new EncryptionWrapper($this->arrayCache, $this->manager, $this->appConfig, $this->logger); } From af2453ea1fbb582986012cc1fa8100b3c06b98c1 Mon Sep 17 00:00:00 2001 From: Stephen Cuppett Date: Fri, 1 May 2026 07:17:30 -0400 Subject: [PATCH 3/3] fix(encryption): Resolve IAppConfig lazily to prevent early IDBConnection creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Injecting IAppConfig as a constructor parameter into Encryption\Manager (and through it into EncryptionWrapper) caused IDBConnection to be eagerly resolved during OC::init() on PHP <8.4 (no lazy ghost objects). This happened before maintenance:install's Sqlite::initialize() wrote dbname to config.php, so the connection latched onto the default database name ('owncloud') instead of the configured one ('nextcloud'). All migrations then ran against owncloud.db, and the subsequent enable_all.php process opened an empty nextcloud.db — crashing with "no such table: oc_appconfig". Remove IAppConfig from Manager's constructor and Server.php's factory closure. Resolve it lazily via Server::get(IAppConfig::class) inside EncryptionWrapper::wrapStorage(), which is only called after the filesystem is set up, never during bootstrap. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Stephen Cuppett --- lib/private/Encryption/EncryptionWrapper.php | 5 ++--- lib/private/Encryption/Manager.php | 6 ++---- lib/private/Server.php | 3 +-- tests/lib/Encryption/EncryptionWrapperTest.php | 7 +------ 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/lib/private/Encryption/EncryptionWrapper.php b/lib/private/Encryption/EncryptionWrapper.php index a926b9a9a7a98..9748f535910fa 100644 --- a/lib/private/Encryption/EncryptionWrapper.php +++ b/lib/private/Encryption/EncryptionWrapper.php @@ -39,7 +39,6 @@ class EncryptionWrapper { public function __construct( private ArrayCache $arrayCache, private Manager $manager, - private IAppConfig $appConfig, private LoggerInterface $logger, ) { } @@ -73,8 +72,8 @@ public function wrapStorage(string $mountPoint, IStorage $storage, IMountPoint $ } // Skip encryption for home mounts if encryptHomeStorage is disabled - if ($mount instanceof HomeMountPoint && - !$this->appConfig->getValueBool('encryption', 'encryptHomeStorage', true)) { + if ($mount instanceof HomeMountPoint + && !Server::get(IAppConfig::class)->getValueBool('encryption', 'encryptHomeStorage', true)) { return $storage; } } diff --git a/lib/private/Encryption/Manager.php b/lib/private/Encryption/Manager.php index b35480e1c1661..ac27f0911b8da 100644 --- a/lib/private/Encryption/Manager.php +++ b/lib/private/Encryption/Manager.php @@ -18,7 +18,6 @@ use OCP\Encryption\IManager; use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage\IStorage; -use OCP\IAppConfig; use OCP\IConfig; use OCP\IL10N; use Psr\Log\LoggerInterface; @@ -33,7 +32,6 @@ public function __construct( protected View $rootView, protected Util $util, protected ArrayCache $arrayCache, - protected IAppConfig $appConfig, ) { $this->encryptionModules = []; } @@ -207,13 +205,13 @@ public function getDefaultEncryptionModuleId() { public function setupStorage() { // If encryption is disabled and there are no loaded modules it makes no sense to load the wrapper if (!empty($this->encryptionModules) || $this->isEnabled()) { - $encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->appConfig, $this->logger); + $encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->logger); Filesystem::addStorageWrapper('oc_encryption', [$encryptionWrapper, 'wrapStorage'], 2); } } public function forceWrapStorage(IMountPoint $mountPoint, IStorage $storage) { - $encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->appConfig, $this->logger); + $encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->logger); return $encryptionWrapper->wrapStorage($mountPoint->getMountPoint(), $storage, $mountPoint, true); } diff --git a/lib/private/Server.php b/lib/private/Server.php index 164a32bf7d2df..2a33c18eb05d9 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -384,8 +384,7 @@ public function __construct( $c->getL10N('core'), new View(), $util, - new ArrayCache(), - $c->get(IAppConfig::class), + new ArrayCache() ); }); $this->registerAlias(\OCP\Encryption\IManager::class, Encryption\Manager::class); diff --git a/tests/lib/Encryption/EncryptionWrapperTest.php b/tests/lib/Encryption/EncryptionWrapperTest.php index c3ef81cf2805d..b80d1a08af64e 100644 --- a/tests/lib/Encryption/EncryptionWrapperTest.php +++ b/tests/lib/Encryption/EncryptionWrapperTest.php @@ -15,7 +15,6 @@ use OCA\Files_Trashbin\Storage; use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage\IDisableEncryptionStorage; -use OCP\IAppConfig; use Psr\Log\LoggerInterface; use Test\TestCase; @@ -32,9 +31,6 @@ class EncryptionWrapperTest extends TestCase { /** @var \PHPUnit\Framework\MockObject\MockObject|ArrayCache */ private $arrayCache; - /** @var \PHPUnit\Framework\MockObject\MockObject|IAppConfig */ - private $appConfig; - #[\Override] protected function setUp(): void { parent::setUp(); @@ -42,9 +38,8 @@ protected function setUp(): void { $this->arrayCache = $this->createMock(ArrayCache::class); $this->manager = $this->createMock(Manager::class); $this->logger = $this->createMock(LoggerInterface::class); - $this->appConfig = $this->createMock(IAppConfig::class); - $this->instance = new EncryptionWrapper($this->arrayCache, $this->manager, $this->appConfig, $this->logger); + $this->instance = new EncryptionWrapper($this->arrayCache, $this->manager, $this->logger); }