Skip to content

Commit

Permalink
Add caching capabilities to UserRepository
Browse files Browse the repository at this point in the history
  • Loading branch information
cicnavi committed Nov 4, 2024
1 parent fe49beb commit 55e0b34
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 7 deletions.
7 changes: 7 additions & 0 deletions config-templates/module_oidc.php
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,13 @@
// 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime)
//],

// 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.
// ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => 'PT1H', // 1 hour
ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => null, // fallback to session duration

/**
* Cron related options.
*/
Expand Down
6 changes: 5 additions & 1 deletion routing/services/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,8 @@ services:
SimpleSAML\OpenID\Federation:
factory: [ '@SimpleSAML\Module\oidc\Factories\FederationFactory', 'build' ]
SimpleSAML\OpenID\Jwks:
factory: [ '@SimpleSAML\Module\oidc\Factories\JwksFactory', 'build' ]
factory: [ '@SimpleSAML\Module\oidc\Factories\JwksFactory', 'build' ]

# SSP
SimpleSAML\Database:
factory: [ 'SimpleSAML\Database', 'getInstance' ]
2 changes: 2 additions & 0 deletions src/Controller/Federation/Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace SimpleSAML\Module\oidc\Controller\Federation;

use SimpleSAML\Database;
use SimpleSAML\Module\oidc\Codebooks\RegistrationTypeEnum;
use SimpleSAML\Module\oidc\Factories\CoreFactory;
use SimpleSAML\Module\oidc\Factories\Entities\ClientEntityFactory;
Expand Down Expand Up @@ -33,6 +34,7 @@ public function __construct(
protected ?FederationCache $federationCache,
protected LoggerService $loggerService,
protected Jwks $jwks,
protected Database $database,
protected ClientEntityFactory $clientEntityFactory,
protected CoreFactory $coreFactory,
protected \DateInterval $maxCacheDuration = new \DateInterval('PT30S'),
Expand Down
17 changes: 17 additions & 0 deletions src/ModuleConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class ModuleConfig
final public const OPTION_FEDERATION_ENTITY_STATEMENT_CACHE_DURATION = 'federation_entity_statement_cache_duration';
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';

protected static array $standardScopes = [
ScopesEnum::OpenId->value => [
Expand Down Expand Up @@ -630,4 +631,20 @@ public function getProtocolCacheAdapterArguments(): array
{
return $this->config()->getOptionalArray(self::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS, []);
}

/**
* Get cache duration for user entities (user data). If not set in configuration, it will fall back to SSP session
* duration.
*
* @throws \Exception
*/
public function getUserEntityCacheDuration(): DateInterval
{
return new DateInterval(
$this->config()->getOptionalString(
self::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION,
null,
) ?? "PT{$this->sspConfig()->getInteger('session.duration')}S",
);
}
}
36 changes: 31 additions & 5 deletions src/Repositories/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public function getTableName(): string
return $this->database->applyPrefix(self::TABLE_NAME);
}

public function getCacheKey(string $identifier): string
{
return $this->getTableName() . '_' . $identifier;
}

/**
* @param string $identifier
*
Expand All @@ -56,6 +61,13 @@ public function getTableName(): string
*/
public function getUserEntityByIdentifier(string $identifier): ?UserEntity
{
/** @var ?array $cachedState */
$cachedState = $this->protocolCache?->get(null, $this->getCacheKey($identifier));

if (is_array($cachedState)) {
return $this->userEntityFactory->fromState($cachedState);
}

$stmt = $this->database->read(
"SELECT * FROM {$this->getTableName()} WHERE id = :id",
[
Expand Down Expand Up @@ -99,21 +111,29 @@ public function add(UserEntity $userEntity): void
$stmt,
$userEntity->getState(),
);

$this->protocolCache?->set(
$userEntity->getState(),
$this->moduleConfig->getUserEntityCacheDuration(),
$this->getCacheKey($userEntity->getIdentifier()),
);
}

public function delete(UserEntity $user): void
public function delete(UserEntity $userEntity): void
{
$this->database->write(
"DELETE FROM {$this->getTableName()} WHERE id = :id",
[
'id' => $user->getIdentifier(),
'id' => $userEntity->getIdentifier(),
],
);

$this->protocolCache?->delete($this->getCacheKey($userEntity->getIdentifier()));
}

public function update(UserEntity $user, ?DateTimeImmutable $updatedAt = null): void
public function update(UserEntity $userEntity, ?DateTimeImmutable $updatedAt = null): void
{
$user->setUpdatedAt($updatedAt ?? $this->helpers->dateTime()->getUtc());
$userEntity->setUpdatedAt($updatedAt ?? $this->helpers->dateTime()->getUtc());

$stmt = sprintf(
"UPDATE %s SET claims = :claims, updated_at = :updated_at, created_at = :created_at WHERE id = :id",
Expand All @@ -122,7 +142,13 @@ public function update(UserEntity $user, ?DateTimeImmutable $updatedAt = null):

$this->database->write(
$stmt,
$user->getState(),
$userEntity->getState(),
);

$this->protocolCache?->set(
$userEntity->getState(),
$this->moduleConfig->getUserEntityCacheDuration(),
$this->getCacheKey($userEntity->getIdentifier()),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public function getDatabase(): Database
$moduleConfig,
$database,
null,
$clientEntityFactoryMock
$clientEntityFactoryMock,
);

$this->accessTokenRepository = new AccessTokenRepository(
Expand Down

0 comments on commit 55e0b34

Please sign in to comment.