From aa93ad2cb72053424a584ad6ed1b20797147e4f0 Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Sat, 3 Oct 2026 13:33:54 +0200 Subject: [PATCH] feat: Base's own password credential store factory to hook into Core's main version --- config/conf.xml | 4 +++- src/Factory/CredentialStoreFactory.php | 22 ++++++++++++++++++---- src/Service/SqlCredentialStore.php | 12 ++++++++++-- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/config/conf.xml b/config/conf.xml index e781dc21..0a3451c5 100644 --- a/config/conf.xml +++ b/config/conf.xml @@ -2727,8 +2727,10 @@ cookie policy. See session configuration options.">$_SERVER['SERVER_NAME'] ?? $_ sql + - + + Horde\Horde\Factory\CredentialStoreFactory diff --git a/src/Factory/CredentialStoreFactory.php b/src/Factory/CredentialStoreFactory.php index df2f7d70..c6479611 100644 --- a/src/Factory/CredentialStoreFactory.php +++ b/src/Factory/CredentialStoreFactory.php @@ -10,18 +10,32 @@ use Horde\Horde\Service\SqlCredentialStore; use Horde\Injector\Injector; use Horde\Secret\SecretManager; +use Horde\Horde\HordeConfig; +/** + * Horde Base's default CredentialStore factory. Do not hint against it directly. + * + * This is intended to be hooked into the Horde\Core\Service\Factory\CredentialStoreFactory via a config key. + * + * Downstream integrators and developers of new upstream credential store implementations (i.e. bindings to some vault technology) can override this factory via the config key. + * + * This is supposed to keep the mechanism open for extension and free of circular dependencies. + * + * @author Ralf Lang + * @package Horde\Horde\Factory + */ class CredentialStoreFactory { public function create(Injector $injector): CredentialStore { - $config = $injector->getInstance('Horde_Registry')->config(); - $driver = $config['password_credentials']['storage_driver'] ?? 'null'; + $config = $injector->get(HordeConfig::class); + $driver = $config->get('password_credentials.storage_driver'); //?? 'null'; + // TODO: Current implementation ignores custom storage driver settings within the 'password_credentials' config. return match ($driver) { 'sql' => new SqlCredentialStore( - db: $injector->getInstance(Adapter::class), - secret: $injector->getInstance(SecretManager::class), + db: $injector->get(Adapter::class), + secret: $injector->get(SecretManager::class), ), default => new NullCredentialStore(), }; diff --git a/src/Service/SqlCredentialStore.php b/src/Service/SqlCredentialStore.php index 33239884..9b651f6c 100644 --- a/src/Service/SqlCredentialStore.php +++ b/src/Service/SqlCredentialStore.php @@ -12,7 +12,11 @@ use Horde\Secret\SecretManager; use RuntimeException; -/** SQL-backed CredentialStore with libsodium encryption. */ +/** + * SQL-backed CredentialStore with libsodium encryption. + * + * Should be used via the child SqlCredentialStoreFactory hooked into Core's primary Horde\Core\Service\Factory\CredentialStoreFactory. + */ class SqlCredentialStore implements CredentialStore { public function __construct( @@ -133,7 +137,11 @@ private function hydrate(array $row): PasswordCredential $json = $this->secret->decrypt($encrypted); $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); - $purpose = ServicePurpose::deserialize($row['purpose_id']); + // purpose_id stores the bare identifier string (the lookup key used + // by find() and the unique index), not the full serialize() array. + // Reconstruct with the default grant strategy, matching how + // ServicePurpose::equals() compares on identifier alone. + $purpose = ServicePurpose::of($row['purpose_id']); return new HordePasswordCredential( $row['credential_id'],