Skip to content

Commit 2d94f74

Browse files
authored
Move to dedicated AuthCodeEntity Factory (#257)
* Move to dedicated AuthCodeEntity Factory --------- Co-authored-by: Marko Ivančić <[email protected]>
1 parent e4a81a3 commit 2d94f74

File tree

10 files changed

+250
-101
lines changed

10 files changed

+250
-101
lines changed

src/Entities/AuthCodeEntity.php

Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,14 @@
1616
namespace SimpleSAML\Module\oidc\Entities;
1717

1818
use DateTimeImmutable;
19+
use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface;
1920
use League\OAuth2\Server\Entities\Traits\EntityTrait;
2021
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
2122
use PDO;
2223
use SimpleSAML\Module\oidc\Entities\Interfaces\AuthCodeEntityInterface;
23-
use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface;
2424
use SimpleSAML\Module\oidc\Entities\Interfaces\MementoInterface;
2525
use SimpleSAML\Module\oidc\Entities\Traits\OidcAuthCodeTrait;
2626
use SimpleSAML\Module\oidc\Entities\Traits\RevokeTokenTrait;
27-
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
28-
use SimpleSAML\Module\oidc\Utils\TimestampGenerator;
2927

3028
class AuthCodeEntity implements AuthCodeEntityInterface, MementoInterface
3129
{
@@ -35,49 +33,26 @@ class AuthCodeEntity implements AuthCodeEntityInterface, MementoInterface
3533
use RevokeTokenTrait;
3634

3735
/**
38-
* @throws \Exception
39-
* @throws \JsonException
40-
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
36+
* @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes
4137
*/
42-
public static function fromState(array $state): self
43-
{
44-
$authCode = new self();
45-
46-
if (
47-
!is_string($state['scopes']) ||
48-
!is_string($state['id']) ||
49-
!is_string($state['expires_at']) ||
50-
!is_a($state['client'], ClientEntityInterface::class)
51-
) {
52-
throw OidcServerException::serverError('Invalid Auth Code Entity state');
53-
}
54-
55-
$stateScopes = json_decode($state['scopes'], true, 512, JSON_THROW_ON_ERROR);
56-
57-
if (!is_array($stateScopes)) {
58-
throw OidcServerException::serverError('Invalid Auth Code Entity state: scopes');
59-
}
60-
61-
$scopes = array_map(
62-
/**
63-
* @return ScopeEntity
64-
*/
65-
fn(string $scope) => ScopeEntity::fromData($scope),
66-
$stateScopes,
67-
);
68-
69-
$authCode->identifier = $state['id'];
70-
$authCode->scopes = $scopes;
71-
$authCode->expiryDateTime = DateTimeImmutable::createFromMutable(
72-
TimestampGenerator::utc($state['expires_at']),
73-
);
74-
$authCode->userIdentifier = empty($state['user_id']) ? null : (string)$state['user_id'];
75-
$authCode->client = $state['client'];
76-
$authCode->isRevoked = (bool) $state['is_revoked'];
77-
$authCode->redirectUri = empty($state['redirect_uri']) ? null : (string)$state['redirect_uri'];
78-
$authCode->nonce = empty($state['nonce']) ? null : (string)$state['nonce'];
79-
80-
return $authCode;
38+
public function __construct(
39+
string $id,
40+
OAuth2ClientEntityInterface $client,
41+
array $scopes,
42+
DateTimeImmutable $expiryDateTime,
43+
string $userIdentifier = null,
44+
string $redirectUri = null,
45+
string $nonce = null,
46+
bool $isRevoked = false,
47+
) {
48+
$this->identifier = $id;
49+
$this->client = $client;
50+
$this->scopes = $scopes;
51+
$this->expiryDateTime = $expiryDateTime;
52+
$this->userIdentifier = $userIdentifier;
53+
$this->redirectUri = $redirectUri;
54+
$this->nonce = $nonce;
55+
$this->isRevoked = $isRevoked;
8156
}
8257

8358
/**
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\oidc\Factories\Entities;
6+
7+
use DateTimeImmutable;
8+
use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface;
9+
use SimpleSAML\Module\oidc\Entities\AuthCodeEntity;
10+
use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface;
11+
use SimpleSAML\Module\oidc\Entities\ScopeEntity;
12+
use SimpleSAML\Module\oidc\Helpers;
13+
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
14+
15+
class AuthCodeEntityFactory
16+
{
17+
public function __construct(
18+
protected readonly Helpers $helpers,
19+
) {
20+
}
21+
22+
/**
23+
* @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes
24+
*/
25+
public function fromData(
26+
string $id,
27+
OAuth2ClientEntityInterface $client,
28+
array $scopes,
29+
DateTimeImmutable $expiryDateTime,
30+
string $userIdentifier = null,
31+
string $redirectUri = null,
32+
string $nonce = null,
33+
bool $isRevoked = false,
34+
): AuthCodeEntity {
35+
return new AuthCodeEntity(
36+
$id,
37+
$client,
38+
$scopes,
39+
$expiryDateTime,
40+
$userIdentifier,
41+
$redirectUri,
42+
$nonce,
43+
$isRevoked,
44+
);
45+
}
46+
47+
/**
48+
* @throws \Exception
49+
* @throws \JsonException
50+
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
51+
*/
52+
public function fromState(array $state): AuthCodeEntity
53+
{
54+
if (
55+
!is_string($state['scopes']) ||
56+
!is_string($state['id']) ||
57+
!is_string($state['expires_at']) ||
58+
!is_a($state['client'], ClientEntityInterface::class)
59+
) {
60+
throw OidcServerException::serverError('Invalid Auth Code Entity state');
61+
}
62+
63+
$stateScopes = json_decode($state['scopes'], true, 512, JSON_THROW_ON_ERROR);
64+
65+
if (!is_array($stateScopes)) {
66+
throw OidcServerException::serverError('Invalid Auth Code Entity state: scopes');
67+
}
68+
69+
$scopes = array_map(
70+
/**
71+
* @return ScopeEntity
72+
*/
73+
fn(string $scope) => ScopeEntity::fromData($scope),
74+
$stateScopes,
75+
);
76+
77+
$id = $state['id'];
78+
$client = $state['client'];
79+
$expiryDateTime = $this->helpers->dateTime()->getUtc($state['expires_at']);
80+
$userIdentifier = empty($state['user_id']) ? null : (string)$state['user_id'];
81+
$redirectUri = empty($state['redirect_uri']) ? null : (string)$state['redirect_uri'];
82+
$nonce = empty($state['nonce']) ? null : (string)$state['nonce'];
83+
$isRevoked = (bool) $state['is_revoked'];
84+
85+
return $this->fromData(
86+
$id,
87+
$client,
88+
$scopes,
89+
$expiryDateTime,
90+
$userIdentifier,
91+
$redirectUri,
92+
$nonce,
93+
$isRevoked,
94+
);
95+
}
96+
}

src/Factories/Grant/AuthCodeGrantFactory.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
namespace SimpleSAML\Module\oidc\Factories\Grant;
1818

1919
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
20+
use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory;
2021
use SimpleSAML\Module\oidc\ModuleConfig;
2122
use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository;
2223
use SimpleSAML\Module\oidc\Repositories\AuthCodeRepository;
@@ -35,6 +36,7 @@ public function __construct(
3536
private readonly RequestRulesManager $requestRulesManager,
3637
private readonly RequestParamsResolver $requestParamsResolver,
3738
private readonly AccessTokenEntityFactory $accessTokenEntityFactory,
39+
private readonly AuthCodeEntityFactory $authCodeEntityFactory,
3840
) {
3941
}
4042

@@ -51,6 +53,7 @@ public function build(): AuthCodeGrant
5153
$this->requestRulesManager,
5254
$this->requestParamsResolver,
5355
$this->accessTokenEntityFactory,
56+
$this->authCodeEntityFactory,
5457
);
5558
$authCodeGrant->setRefreshTokenTTL($this->moduleConfig->getRefreshTokenDuration());
5659

src/Repositories/AuthCodeRepository.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use SimpleSAML\Error\Error;
2222
use SimpleSAML\Module\oidc\Entities\AuthCodeEntity;
2323
use SimpleSAML\Module\oidc\Entities\Interfaces\AuthCodeEntityInterface;
24+
use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory;
2425
use SimpleSAML\Module\oidc\ModuleConfig;
2526
use SimpleSAML\Module\oidc\Repositories\Interfaces\AuthCodeRepositoryInterface;
2627
use SimpleSAML\Module\oidc\Utils\TimestampGenerator;
@@ -30,6 +31,7 @@ class AuthCodeRepository extends AbstractDatabaseRepository implements AuthCodeR
3031
public function __construct(
3132
ModuleConfig $moduleConfig,
3233
protected readonly ClientRepository $clientRepository,
34+
protected readonly AuthCodeEntityFactory $authCodeEntityFactory,
3335
) {
3436
parent::__construct($moduleConfig);
3537
}
@@ -46,7 +48,7 @@ public function getTableName(): string
4648
*/
4749
public function getNewAuthCode(): AuthCodeEntityInterface
4850
{
49-
return new AuthCodeEntity();
51+
throw new RuntimeException('Not implemented. Use AuthCodeEntityFactory instead.');
5052
}
5153

5254
/**
@@ -93,7 +95,7 @@ public function findById(string $codeId): ?AuthCodeEntityInterface
9395
$data = current($rows);
9496
$data['client'] = $this->clientRepository->findById((string)$data['client_id']);
9597

96-
return AuthCodeEntity::fromState($data);
98+
return $this->authCodeEntityFactory->fromState($data);
9799
}
98100

99101
/**

src/Server/Grants/AuthCodeGrant.php

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface;
2626
use SimpleSAML\Module\oidc\Entities\UserEntity;
2727
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
28+
use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory;
2829
use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface;
2930
use SimpleSAML\Module\oidc\Repositories\Interfaces\AuthCodeRepositoryInterface;
3031
use SimpleSAML\Module\oidc\Repositories\Interfaces\RefreshTokenRepositoryInterface;
@@ -160,6 +161,7 @@ public function __construct(
160161
protected RequestRulesManager $requestRulesManager,
161162
protected RequestParamsResolver $requestParamsResolver,
162163
AccessTokenEntityFactory $accessTokenEntityFactory,
164+
protected AuthCodeEntityFactory $authCodeEntityFactory,
163165
) {
164166
parent::__construct($authCodeRepository, $refreshTokenRepository, $authCodeTTL);
165167

@@ -317,27 +319,17 @@ protected function issueOidcAuthCode(
317319
throw OidcServerException::serverError('Unexpected auth code repository entity type.');
318320
}
319321

320-
$authCode = $this->authCodeRepository->getNewAuthCode();
321-
322-
if (!is_a($authCode, AuthCodeEntityInterface::class)) {
323-
throw OidcServerException::serverError('Unexpected auth code entity type.');
324-
}
325-
326-
$authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL));
327-
$authCode->setClient($client);
328-
$authCode->setUserIdentifier($userIdentifier);
329-
$authCode->setRedirectUri($redirectUri);
330-
if (null !== $nonce) {
331-
$authCode->setNonce($nonce);
332-
}
333-
334-
foreach ($scopes as $scope) {
335-
$authCode->addScope($scope);
336-
}
337-
338322
while ($maxGenerationAttempts-- > 0) {
339-
$authCode->setIdentifier($this->generateUniqueIdentifier());
340323
try {
324+
$authCode = $this->authCodeEntityFactory->fromData(
325+
$this->generateUniqueIdentifier(),
326+
$client,
327+
$scopes,
328+
(new DateTimeImmutable())->add($authCodeTTL),
329+
$userIdentifier,
330+
$redirectUri,
331+
$nonce,
332+
);
341333
$this->authCodeRepository->persistNewAuthCode($authCode);
342334

343335
return $authCode;

src/Services/Container.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use SimpleSAML\Module\oidc\Factories\ClaimTranslatorExtractorFactory;
4040
use SimpleSAML\Module\oidc\Factories\CryptKeyFactory;
4141
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
42+
use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory;
4243
use SimpleSAML\Module\oidc\Factories\Entities\ClientEntityFactory;
4344
use SimpleSAML\Module\oidc\Factories\FederationFactory;
4445
use SimpleSAML\Module\oidc\Factories\FormFactory;
@@ -200,7 +201,14 @@ public function __construct()
200201
$userRepository = new UserRepository($moduleConfig);
201202
$this->services[UserRepository::class] = $userRepository;
202203

203-
$authCodeRepository = new AuthCodeRepository($moduleConfig, $clientRepository);
204+
$authCodeEntityFactory = new AuthCodeEntityFactory($helpers);
205+
$this->services[AuthCodeEntityFactory::class] = $authCodeEntityFactory;
206+
207+
$authCodeRepository = new AuthCodeRepository(
208+
$moduleConfig,
209+
$clientRepository,
210+
$authCodeEntityFactory,
211+
);
204212
$this->services[AuthCodeRepository::class] = $authCodeRepository;
205213

206214
$cryptKeyFactory = new CryptKeyFactory($moduleConfig);
@@ -341,6 +349,7 @@ public function __construct()
341349
$requestRuleManager,
342350
$requestParamsResolver,
343351
$accessTokenEntityFactory,
352+
$authCodeEntityFactory,
344353
);
345354
$this->services[AuthCodeGrant::class] = $authCodeGrantFactory->build();
346355

tests/integration/src/Repositories/Traits/RevokeTokenByAuthCodeIdTraitTest.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,11 @@ public function setUp(): void
107107
'id' => self::ACCESS_TOKEN_ID,
108108
'scopes' => '{"openid":"openid","profile":"profile"}',
109109
'expires_at' => date('Y-m-d H:i:s', time() - 60), // expired...
110-
'user_id' => 'user123',
110+
'user_id' => self::USER_ID,
111111
'client_id' => self::CLIENT_ID,
112-
'is_revoked' => false,
113-
'auth_code_id' => 'authCode123',
112+
'is_revoked' => [false, PDO::PARAM_BOOL],
113+
'auth_code_id' => self::AUTH_CODE_ID,
114+
'requested_claims' => '[]',
114115
];
115116

116117
$this->accessTokenEntityMock = $this->createMock(AccessTokenEntity::class);
@@ -264,8 +265,8 @@ public static function loadMySqlDatabase(): array
264265
public static function databaseToTest(): array
265266
{
266267
return [
267-
//'PostgreSql' => ['pgConfig'],
268-
//'MySql' => ['mysqlConfig'],
268+
'PostgreSql' => ['pgConfig'],
269+
'MySql' => ['mysqlConfig'],
269270
'Sqlite' => ['sqliteConfig'],
270271
];
271272
}

0 commit comments

Comments
 (0)