From 6ab312ee884c172fcca7fe9d8c0086eb9601378d Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Tue, 21 Jan 2025 10:36:59 +0100 Subject: [PATCH] Add cache to client repository --- config-templates/module_oidc.php | 56 ++++++++-------- src/ModuleConfig.php | 16 +++++ .../AbstractDatabaseRepository.php | 7 ++ src/Repositories/ClientRepository.php | 65 ++++++++++++++++++- src/Repositories/UserRepository.php | 5 -- 5 files changed, 116 insertions(+), 33 deletions(-) diff --git a/config-templates/module_oidc.php b/config-templates/module_oidc.php index c4f8d03b..9344ab8b 100644 --- a/config-templates/module_oidc.php +++ b/config-templates/module_oidc.php @@ -258,42 +258,46 @@ // also give proper adapter arguments for its instantiation below. // @see https://symfony.com/doc/current/components/cache.html#available-cache-adapters ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => null, - //ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\FilesystemAdapter::class, - //ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\MemcachedAdapter::class, +// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\FilesystemAdapter::class, +// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\MemcachedAdapter::class, - // Federation cache adapter arguments used for adapter instantiation. Refer to documentation for particular + // Protocol cache adapter arguments used for adapter instantiation. Refer to documentation for particular // adapter on which arguments are needed to create its instance, in the order of constructor arguments. // See examples below. ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [ // Adapter arguments here... ], // Example for FileSystemAdapter: - //ModuleConfig::OPTION_FEDERATION_CACHE_ADAPTER_ARGUMENTS => [ - // 'openidFederation', // Namespace, subdirectory of main cache directory - // 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime) - // '/path/to/main/cache/directory' // Must be writable. Can be set to null to use system temporary directory. - //], - // Example for MemcachedAdapter: - //ModuleConfig::OPTION_FEDERATION_CACHE_ADAPTER_ARGUMENTS => [ - // // First argument is a connection instance, so we can use the helper method to create it. In this example a - // // single server is used. Refer to documentation on how to use multiple servers, and / or to provide other - // // options. - // \Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection( - // 'memcached://localhost' - // // the DSN can include config options (pass them as a query string): - // // 'memcached://localhost:11222?retry_timeout=10' - // // 'memcached://localhost:11222?socket_recv_size=1&socket_send_size=2' - // ), - // 'openidFederation', // Namespace, key prefix. - // 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime) - //], +// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [ +// 'openidFederation', // Namespace, subdirectory of main cache directory +// 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime) +// '/path/to/main/cache/directory' // Must be writable. Can be set to null to use system temporary directory. +// ], +// Example for MemcachedAdapter: +// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [ +// // First argument is a connection instance, so we can use the helper method to create it. In this example a +// // single server is used. Refer to documentation on how to use multiple servers, and / or to provide other +// // options. +// \Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection( +// 'memcached://localhost' +// // the DSN can include config options (pass them as a query string): +// // 'memcached://localhost:11222?retry_timeout=10' +// // 'memcached://localhost:11222?socket_recv_size=1&socket_send_size=2' +// ), +// 'openidProtocol', // Namespace, key prefix. +// 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime) +// ], + /** + * Protocol cache duration for particular entities. This is only relevant if protocol cache adapter is set up. + * For duration format info, check https://www.php.net/manual/en/dateinterval.construct.php. + */ // Cache duration for user entities (authenticated users data). If not set, cache duration will be the same as - // session duration. This is used to avoid fetching user data from database on every authentication event. - // This is only relevant if protocol cache adapter is set up. For duration format info, check - // https://www.php.net/manual/en/dateinterval.construct.php. + // session duration. // ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => 'PT1H', // 1 hour - ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => null, // fallback to session duration + ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => null, // Fallback to session duration + // Cache duration for client entities, with given default. + ModuleConfig::OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION => 'PT10M', // 10 minutes /** * Cron related options. diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 6c905eb3..dcb2de51 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -80,6 +80,7 @@ class ModuleConfig final public const OPTION_PROTOCOL_CACHE_ADAPTER = 'protocol_cache_adapter'; final public const OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS = 'protocol_cache_adapter_arguments'; final public const OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION = 'protocol_user_entity_cache_duration'; + final public const OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION = 'protocol_client_entity_cache_duration'; final public const OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS = 'federation_participation_limit_by_trust_marks'; @@ -464,6 +465,21 @@ public function getProtocolUserEntityCacheDuration(): DateInterval ); } + /** + * Get cache duration for client entities (user data), with given default + * + * @throws \Exception + */ + public function getProtocolClientEntityCacheDuration(): DateInterval + { + return new DateInterval( + $this->config()->getOptionalString( + self::OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION, + null, + ) ?? 'PT10M', + ); + } + /***************************************************************************************************************** * OpenID Federation related config. diff --git a/src/Repositories/AbstractDatabaseRepository.php b/src/Repositories/AbstractDatabaseRepository.php index 61d8b416..9434eafb 100644 --- a/src/Repositories/AbstractDatabaseRepository.php +++ b/src/Repositories/AbstractDatabaseRepository.php @@ -32,5 +32,12 @@ public function __construct( ) { } + public function getCacheKey(string $identifier): string + { + return is_string($tableName = $this->getTableName()) ? + $tableName . '_' . $identifier : + $identifier; + } + abstract public function getTableName(): ?string; } diff --git a/src/Repositories/ClientRepository.php b/src/Repositories/ClientRepository.php index 97a62766..6e056c7a 100644 --- a/src/Repositories/ClientRepository.php +++ b/src/Repositories/ClientRepository.php @@ -92,6 +92,13 @@ public function validateClient($clientIdentifier, $clientSecret, $grantType): bo */ public function findById(string $clientIdentifier, ?string $owner = null): ?ClientEntityInterface { + /** @var ?array $cachedState */ + $cachedState = $this->protocolCache?->get(null, $this->getCacheKey($clientIdentifier)); + + if (is_array($cachedState)) { + return $this->clientEntityFactory->fromState($cachedState); + } + /** * @var string $query * @var array $params @@ -116,11 +123,26 @@ public function findById(string $clientIdentifier, ?string $owner = null): ?Clie return null; } - return $this->clientEntityFactory->fromState($row); + $clientEntity = $this->clientEntityFactory->fromState($row); + + $this->protocolCache?->set( + $clientEntity->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($clientEntity->getIdentifier()), + ); + + return $clientEntity; } public function findByEntityIdentifier(string $entityIdentifier, ?string $owner = null): ?ClientEntityInterface { + /** @var ?array $cachedState */ + $cachedState = $this->protocolCache?->get(null, $this->getCacheKey($entityIdentifier)); + + if (is_array($cachedState)) { + return $this->clientEntityFactory->fromState($cachedState); + } + /** * @var string $query * @var array $params @@ -153,7 +175,15 @@ public function findByEntityIdentifier(string $entityIdentifier, ?string $owner return null; } - return $this->clientEntityFactory->fromState($row); + $clientEntity = $this->clientEntityFactory->fromState($row); + + $this->protocolCache?->set( + $clientEntity->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($entityIdentifier), + ); + + return $clientEntity; } private function addOwnerWhereClause(string $query, array $params, ?string $owner = null): array @@ -302,6 +332,19 @@ public function add(ClientEntityInterface $client): void $stmt, $client->getState(), ); + + $this->protocolCache?->set( + $client->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($client->getIdentifier()), + ); + if (($entityIdentifier = $client->getEntityIdentifier()) !== null) { + $this->protocolCache?->set( + $client->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($entityIdentifier), + ); + } } public function delete(ClientEntityInterface $client, ?string $owner = null): void @@ -318,6 +361,11 @@ public function delete(ClientEntityInterface $client, ?string $owner = null): vo $owner, ); $this->database->write($sqlQuery, $params); + + $this->protocolCache?->delete($this->getCacheKey($client->getIdentifier())); + if (($entityIdentifier = $client->getEntityIdentifier()) !== null) { + $this->protocolCache?->delete($this->getCacheKey($entityIdentifier)); + } } public function update(ClientEntityInterface $client, ?string $owner = null): void @@ -366,6 +414,19 @@ public function update(ClientEntityInterface $client, ?string $owner = null): vo $sqlQuery, $params, ); + + $this->protocolCache?->set( + $client->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($client->getIdentifier()), + ); + if (($entityIdentifier = $client->getEntityIdentifier()) !== null) { + $this->protocolCache?->set( + $client->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($entityIdentifier), + ); + } } private function count(string $query, ?string $owner): int diff --git a/src/Repositories/UserRepository.php b/src/Repositories/UserRepository.php index 420e02f5..b98aa88b 100644 --- a/src/Repositories/UserRepository.php +++ b/src/Repositories/UserRepository.php @@ -48,11 +48,6 @@ public function getTableName(): string return $this->database->applyPrefix(self::TABLE_NAME); } - public function getCacheKey(string $identifier): string - { - return $this->getTableName() . '_' . $identifier; - } - /** * @param string $identifier *