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
92 changes: 15 additions & 77 deletions src/Entities/UserEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,85 +16,22 @@

namespace SimpleSAML\Module\oidc\Entities;

use DateTime;
use DateTimeImmutable;
use League\OAuth2\Server\Entities\UserEntityInterface;
use SimpleSAML\Module\oidc\Entities\Interfaces\ClaimSetInterface;
use SimpleSAML\Module\oidc\Entities\Interfaces\MementoInterface;
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
use SimpleSAML\Module\oidc\Utils\TimestampGenerator;

/**
* @psalm-suppress PropertyNotSetInConstructor
*/
class UserEntity implements UserEntityInterface, MementoInterface, ClaimSetInterface
{
/**
* @var string
*/
private string $identifier;

/**
* @var array
*/
private array $claims;

/**
* @var DateTime
*/
private DateTime $createdAt;

/**
* @var DateTime
*/
private DateTime $updatedAt;

private function __construct()
{
}

/**
* @throws \Exception
*/
public static function fromData(string $identifier, array $claims = []): self
{
$user = new self();

$user->identifier = $identifier;
$user->createdAt = TimestampGenerator::utc();
$user->updatedAt = $user->createdAt;
$user->claims = $claims;

return $user;
}

/**
* @throws \Exception
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
*/
public static function fromState(array $state): self
{
$user = new self();

if (
!is_string($state['id']) ||
!is_string($state['claims']) ||
!is_string($state['updated_at']) ||
!is_string($state['created_at'])
) {
throw OidcServerException::serverError('Invalid user entity data');
}

$user->identifier = $state['id'];
$claims = json_decode($state['claims'], true, 512, JSON_INVALID_UTF8_SUBSTITUTE);

if (!is_array($claims)) {
throw OidcServerException::serverError('Invalid user entity data');
}
$user->claims = $claims;
$user->updatedAt = TimestampGenerator::utc($state['updated_at']);
$user->createdAt = TimestampGenerator::utc($state['created_at']);

return $user;
public function __construct(
private readonly string $identifier,
private readonly DateTimeImmutable $createdAt,
private DateTimeImmutable $updatedAt,
private array $claims = [],
) {
}

/**
Expand All @@ -120,23 +57,24 @@ public function getClaims(): array
return $this->claims;
}

/**
* @throws \Exception
*/
public function setClaims(array $claims): self
{
$this->claims = $claims;
$this->updatedAt = TimestampGenerator::utc();

return $this;
}

public function getUpdatedAt(): DateTime
public function getUpdatedAt(): DateTimeImmutable
{
return $this->updatedAt;
}

public function getCreatedAt(): DateTime
public function setUpdatedAt(DateTimeImmutable $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}

public function getCreatedAt(): DateTimeImmutable
{
return $this->createdAt;
}
Expand Down
60 changes: 60 additions & 0 deletions src/Factories/Entities/UserEntityFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\Module\oidc\Factories\Entities;

use SimpleSAML\Module\oidc\Entities\UserEntity;
use SimpleSAML\Module\oidc\Helpers;
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;

class UserEntityFactory
{
public function __construct(
protected readonly Helpers $helpers,
) {
}

public function fromData(string $identifier, array $claims = []): UserEntity
{
$createdAt = $updatedAt = $this->helpers->dateTime()->getUtc();

return new UserEntity(
$identifier,
$createdAt,
$updatedAt,
$claims,
);
}

/**
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
*/
public function fromState(array $state): UserEntity
{
if (
!is_string($state['id']) ||
!is_string($state['claims']) ||
!is_string($state['updated_at']) ||
!is_string($state['created_at'])
) {
throw OidcServerException::serverError('Invalid user entity data');
}

$identifier = $state['id'];
$claims = json_decode($state['claims'], true, 512, JSON_INVALID_UTF8_SUBSTITUTE);

if (!is_array($claims)) {
throw OidcServerException::serverError('Invalid user entity data');
}
$updatedAt = $this->helpers->dateTime()->getUtc($state['updated_at']);
$createdAt = $this->helpers->dateTime()->getUtc($state['created_at']);

return new UserEntity(
$identifier,
$createdAt,
$updatedAt,
$claims,
);
}
}
18 changes: 16 additions & 2 deletions src/Repositories/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,29 @@

namespace SimpleSAML\Module\oidc\Repositories;

use DateTimeImmutable;
use Exception;
use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
use SimpleSAML\Module\oidc\Entities\UserEntity;
use SimpleSAML\Module\oidc\Factories\Entities\UserEntityFactory;
use SimpleSAML\Module\oidc\Helpers;
use SimpleSAML\Module\oidc\ModuleConfig;
use SimpleSAML\Module\oidc\Repositories\Interfaces\IdentityProviderInterface;

class UserRepository extends AbstractDatabaseRepository implements UserRepositoryInterface, IdentityProviderInterface
{
final public const TABLE_NAME = 'oidc_user';

public function __construct(
ModuleConfig $moduleConfig,
protected readonly Helpers $helpers,
protected readonly UserEntityFactory $userEntityFactory,
) {
parent::__construct($moduleConfig);
}

public function getTableName(): string
{
return $this->database->applyPrefix(self::TABLE_NAME);
Expand Down Expand Up @@ -57,7 +69,7 @@ public function getUserEntityByIdentifier(string $identifier): ?UserEntity
return null;
}

return UserEntity::fromState($row);
return $this->userEntityFactory->fromState($row);
}

/**
Expand Down Expand Up @@ -95,8 +107,10 @@ public function delete(UserEntity $user): void
);
}

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

$stmt = sprintf(
"UPDATE %s SET claims = :claims, updated_at = :updated_at, created_at = :created_at WHERE id = :id",
$this->getTableName(),
Expand Down
5 changes: 4 additions & 1 deletion src/Services/AuthenticationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface;
use SimpleSAML\Module\oidc\Entities\UserEntity;
use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory;
use SimpleSAML\Module\oidc\Factories\Entities\UserEntityFactory;
use SimpleSAML\Module\oidc\Factories\ProcessingChainFactory;
use SimpleSAML\Module\oidc\Helpers;
use SimpleSAML\Module\oidc\ModuleConfig;
Expand Down Expand Up @@ -66,6 +67,7 @@ public function __construct(
private readonly StateService $stateService,
private readonly Helpers $helpers,
private readonly RequestParamsResolver $requestParamsResolver,
private readonly UserEntityFactory $userEntityFactory,
) {
$this->userIdAttr = $this->moduleConfig->getUserIdentifierAttribute();
}
Expand Down Expand Up @@ -140,13 +142,14 @@ public function getAuthenticateUser(
}

$userId = (string)$claims[$this->userIdAttr][0];

$user = $this->userRepository->getUserEntityByIdentifier($userId);

if ($user) {
$user->setClaims($claims);
$this->userRepository->update($user);
} else {
$user = UserEntity::fromData($userId, $claims);
$user = $this->userEntityFactory->fromData($userId, $claims);
$this->userRepository->add($user);
}

Expand Down
11 changes: 10 additions & 1 deletion src/Services/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
use SimpleSAML\Module\oidc\Factories\Entities\ClaimSetEntityFactory;
use SimpleSAML\Module\oidc\Factories\Entities\ClientEntityFactory;
use SimpleSAML\Module\oidc\Factories\Entities\RefreshTokenEntityFactory;
use SimpleSAML\Module\oidc\Factories\Entities\UserEntityFactory;
use SimpleSAML\Module\oidc\Factories\FederationFactory;
use SimpleSAML\Module\oidc\Factories\FormFactory;
use SimpleSAML\Module\oidc\Factories\Grant\AuthCodeGrantFactory;
Expand Down Expand Up @@ -205,7 +206,14 @@ public function __construct()
$clientRepository = new ClientRepository($moduleConfig, $clientEntityFactory);
$this->services[ClientRepository::class] = $clientRepository;

$userRepository = new UserRepository($moduleConfig);
$userEntityFactory = new UserEntityFactory($helpers);
$this->services[UserEntityFactory::class] = $userEntityFactory;

$userRepository = new UserRepository(
$moduleConfig,
$helpers,
$userEntityFactory,
);
$this->services[UserRepository::class] = $userRepository;

$authCodeEntityFactory = new AuthCodeEntityFactory($helpers);
Expand Down Expand Up @@ -277,6 +285,7 @@ public function __construct()
$stateService,
$helpers,
$requestParamsResolver,
$userEntityFactory,
);
$this->services[AuthenticationService::class] = $authenticationService;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use SimpleSAML\Module\oidc\Entities\UserEntity;
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
use SimpleSAML\Module\oidc\Factories\Entities\ClientEntityFactory;
use SimpleSAML\Module\oidc\Factories\Entities\UserEntityFactory;
use SimpleSAML\Module\oidc\Helpers;
use SimpleSAML\Module\oidc\ModuleConfig;
use SimpleSAML\Module\oidc\Repositories\AbstractDatabaseRepository;
Expand Down Expand Up @@ -171,9 +172,14 @@ public function getDatabase(): Database
$this->mock->getDatabase()->write('DELETE from ' . $clientRepositoryMock->getTableName());
$clientRepositoryMock->add($client);


$user = UserEntity::fromData(self::USER_ID);
$userRepositoryMock = new UserRepository($moduleConfig);
$createUpdatedAt = new \DateTimeImmutable();
$helpers = new Helpers();
$user = new UserEntity(self::USER_ID, $createUpdatedAt, $createUpdatedAt, []);
$userRepositoryMock = new UserRepository(
$moduleConfig,
$helpers,
new UserEntityFactory($helpers),
);
$this->mock->getDatabase()->write('DELETE from ' . $userRepositoryMock->getTableName());
$userRepositoryMock->add($user);
}
Expand Down
Loading