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
4 changes: 3 additions & 1 deletion config/conf.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2727,8 +2727,10 @@ cookie policy. See session configuration options.">$_SERVER['SERVER_NAME'] ?? $_

<configswitch name="storage_driver" desc="Where to store password credentials?">sql
<case name="null" desc="Disabled (no password credential storage)">
<configstring name="storage_factory" desc="Credential store factory class. Leave empty to keep password credential storage disabled."></configstring>
</case>
<case name="sql" desc="SQL Database (encrypted with libsodium)">
<case name="sql" desc="SQL Database encrypted with libsodium">
<configstring name="storage_factory" desc="Credential store factory class. Use Horde\Horde\Factory\CredentialStoreFactory unless you want to provide your own implementation.">Horde\Horde\Factory\CredentialStoreFactory</configstring>
<configsection name="storage_params">
<configsql switchname="driverconfig" />
</configsection>
Expand Down
22 changes: 18 additions & 4 deletions src/Factory/CredentialStoreFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ralf.lang@ralf-lang.de>
* @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(),
};
Expand Down
12 changes: 10 additions & 2 deletions src/Service/SqlCredentialStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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'],
Expand Down