Skip to content

Commit c59a036

Browse files
committed
Introduce RefreshTokenIssuer
1 parent b84f87b commit c59a036

File tree

12 files changed

+186
-50
lines changed

12 files changed

+186
-50
lines changed

routing/services/services.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ services:
3535
SimpleSAML\Module\oidc\Bridges\:
3636
resource: '../../src/Bridges/*'
3737

38+
SimpleSAML\Module\oidc\Server\TokenIssuers\:
39+
resource: '../../src/Server/TokenIssuers/*'
40+
3841
SimpleSAML\Module\oidc\ModuleConfig: ~
3942
SimpleSAML\Module\oidc\Helpers: ~
4043
SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection: ~

src/Factories/Grant/AuthCodeGrantFactory.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@
1818

1919
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
2020
use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory;
21-
use SimpleSAML\Module\oidc\Factories\Entities\RefreshTokenEntityFactory;
2221
use SimpleSAML\Module\oidc\ModuleConfig;
2322
use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository;
2423
use SimpleSAML\Module\oidc\Repositories\AuthCodeRepository;
2524
use SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository;
2625
use SimpleSAML\Module\oidc\Server\Grants\AuthCodeGrant;
2726
use SimpleSAML\Module\oidc\Server\RequestRules\RequestRulesManager;
28-
use SimpleSAML\Module\oidc\Services\LoggerService;
27+
use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer;
2928
use SimpleSAML\Module\oidc\Utils\RequestParamsResolver;
3029

3130
class AuthCodeGrantFactory
@@ -39,8 +38,7 @@ public function __construct(
3938
private readonly RequestParamsResolver $requestParamsResolver,
4039
private readonly AccessTokenEntityFactory $accessTokenEntityFactory,
4140
private readonly AuthCodeEntityFactory $authCodeEntityFactory,
42-
private readonly RefreshTokenEntityFactory $refreshTokenEntityFactory,
43-
private readonly LoggerService $logger,
41+
private readonly RefreshTokenIssuer $refreshTokenIssuer,
4442
) {
4543
}
4644

@@ -58,8 +56,7 @@ public function build(): AuthCodeGrant
5856
$this->requestParamsResolver,
5957
$this->accessTokenEntityFactory,
6058
$this->authCodeEntityFactory,
61-
$this->refreshTokenEntityFactory,
62-
$this->logger,
59+
$this->refreshTokenIssuer,
6360
);
6461
$authCodeGrant->setRefreshTokenTTL($this->moduleConfig->getRefreshTokenDuration());
6562

src/Factories/Grant/RefreshTokenGrantFactory.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,26 @@
1919
use SimpleSAML\Module\oidc\ModuleConfig;
2020
use SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository;
2121
use SimpleSAML\Module\oidc\Server\Grants\RefreshTokenGrant;
22+
use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer;
2223

2324
class RefreshTokenGrantFactory
2425
{
2526
public function __construct(
2627
private readonly ModuleConfig $moduleConfig,
2728
private readonly RefreshTokenRepository $refreshTokenRepository,
2829
private readonly AccessTokenEntityFactory $accessTokenEntityFactory,
30+
private readonly RefreshTokenIssuer $refreshTokenIssuer,
2931
) {
3032
}
3133

3234
public function build(): RefreshTokenGrant
3335
{
34-
$refreshTokenGrant = new RefreshTokenGrant($this->refreshTokenRepository, $this->accessTokenEntityFactory);
36+
$refreshTokenGrant = new RefreshTokenGrant(
37+
$this->refreshTokenRepository,
38+
$this->accessTokenEntityFactory,
39+
$this->refreshTokenIssuer,
40+
);
41+
3542
$refreshTokenGrant->setRefreshTokenTTL($this->moduleConfig->getRefreshTokenDuration());
3643

3744
return $refreshTokenGrant;

src/Helpers.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use SimpleSAML\Module\oidc\Helpers\Client;
99
use SimpleSAML\Module\oidc\Helpers\DateTime;
1010
use SimpleSAML\Module\oidc\Helpers\Http;
11+
use SimpleSAML\Module\oidc\Helpers\Random;
1112
use SimpleSAML\Module\oidc\Helpers\Str;
1213

1314
class Helpers
@@ -17,6 +18,7 @@ class Helpers
1718
protected static ?DateTime $dateTIme = null;
1819
protected static ?Str $str = null;
1920
protected static ?Arr $arr = null;
21+
protected static ?Random $random = null;
2022

2123
public function http(): Http
2224
{
@@ -44,4 +46,9 @@ public function arr(): Arr
4446
{
4547
return static::$arr ??= new Arr();
4648
}
49+
50+
public function random(): Random
51+
{
52+
return static::$random ??= new Random();
53+
}
4754
}

src/Helpers/Random.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\oidc\Helpers;
6+
7+
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
8+
use Throwable;
9+
10+
class Random
11+
{
12+
/**
13+
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
14+
*/
15+
public function getIdentifier(int $length = 40): string
16+
{
17+
if ($length < 1) {
18+
throw OidcServerException::serverError('Random string length can not be less than 1');
19+
}
20+
21+
try {
22+
return bin2hex(random_bytes($length));
23+
} catch (Throwable $e) {
24+
throw OidcServerException::serverError('Could not generate a random string', $e);
25+
}
26+
}
27+
}

src/Server/Grants/AuthCodeGrant.php

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
use SimpleSAML\Module\oidc\Entities\UserEntity;
2828
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
2929
use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory;
30-
use SimpleSAML\Module\oidc\Factories\Entities\RefreshTokenEntityFactory;
3130
use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface;
3231
use SimpleSAML\Module\oidc\Repositories\Interfaces\AuthCodeRepositoryInterface;
3332
use SimpleSAML\Module\oidc\Repositories\Interfaces\RefreshTokenRepositoryInterface;
@@ -58,7 +57,7 @@
5857
use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\AuthTimeResponseTypeInterface;
5958
use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\NonceResponseTypeInterface;
6059
use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\SessionIdResponseTypeInterface;
61-
use SimpleSAML\Module\oidc\Services\LoggerService;
60+
use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer;
6261
use SimpleSAML\Module\oidc\Utils\Arr;
6362
use SimpleSAML\Module\oidc\Utils\RequestParamsResolver;
6463
use SimpleSAML\Module\oidc\Utils\ScopeHelper;
@@ -165,8 +164,7 @@ public function __construct(
165164
protected RequestParamsResolver $requestParamsResolver,
166165
AccessTokenEntityFactory $accessTokenEntityFactory,
167166
protected AuthCodeEntityFactory $authCodeEntityFactory,
168-
protected RefreshTokenEntityFactory $refreshTokenEntityFactory,
169-
protected LoggerService $logger,
167+
protected RefreshTokenIssuer $refreshTokenIssuer,
170168
) {
171169
parent::__construct($authCodeRepository, $refreshTokenRepository, $authCodeTTL);
172170

@@ -752,37 +750,15 @@ protected function issueRefreshToken(
752750
OAuth2AccessTokenEntityInterface $accessToken,
753751
string $authCodeId = null,
754752
): ?RefreshTokenEntityInterface {
755-
if (! is_a($this->refreshTokenRepository, RefreshTokenRepositoryInterface::class)) {
756-
throw OidcServerException::serverError('Unexpected refresh token repository entity type.');
757-
}
758753
if (! is_a($accessToken, AccessTokenEntityInterface::class)) {
759754
throw OidcServerException::serverError('Unexpected access token entity type.');
760755
}
761756

762-
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
763-
764-
while ($maxGenerationAttempts-- > 0) {
765-
try {
766-
$refreshToken = $this->refreshTokenEntityFactory->fromData(
767-
$this->generateUniqueIdentifier(),
768-
(new DateTimeImmutable())->add($this->refreshTokenTTL),
769-
$accessToken,
770-
$authCodeId,
771-
);
772-
$this->refreshTokenRepository->persistNewRefreshToken($refreshToken);
773-
return $refreshToken;
774-
} catch (UniqueTokenIdentifierConstraintViolationException $e) {
775-
if ($maxGenerationAttempts === 0) {
776-
throw $e;
777-
}
778-
}
779-
}
780-
781-
$this->logger->error('Unable to issue refresh token.', [
782-
'accessTokenId' => $accessToken->getIdentifier(),
783-
'authCodeId' => $authCodeId,
784-
]);
785-
786-
return null;
757+
return $this->refreshTokenIssuer->issue(
758+
$accessToken,
759+
$this->refreshTokenTTL,
760+
$authCodeId,
761+
self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS,
762+
);
787763
}
788764
}

src/Server/Grants/RefreshTokenGrant.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
namespace SimpleSAML\Module\oidc\Server\Grants;
66

77
use Exception;
8+
use League\OAuth2\Server\Entities\AccessTokenEntityInterface as OAuth2AccessTokenEntityInterface;
89
use League\OAuth2\Server\Grant\RefreshTokenGrant as OAuth2RefreshTokenGrant;
910
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
1011
use League\OAuth2\Server\RequestEvent;
1112
use Psr\Http\Message\ServerRequestInterface;
13+
use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface;
14+
use SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface;
1215
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
1316
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
1417
use SimpleSAML\Module\oidc\Server\Grants\Traits\IssueAccessTokenTrait;
18+
use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer;
1519

1620
use function is_null;
1721
use function json_decode;
@@ -48,6 +52,7 @@ class RefreshTokenGrant extends OAuth2RefreshTokenGrant
4852
public function __construct(
4953
RefreshTokenRepositoryInterface $refreshTokenRepository,
5054
AccessTokenEntityFactory $accessTokenEntityFactory,
55+
protected readonly RefreshTokenIssuer $refreshTokenIssuer,
5156
) {
5257
parent::__construct($refreshTokenRepository);
5358
$this->accessTokenEntityFactory = $accessTokenEntityFactory;
@@ -95,4 +100,20 @@ protected function validateOldRefreshToken(ServerRequestInterface $request, $cli
95100

96101
return $refreshTokenData;
97102
}
103+
104+
protected function issueRefreshToken(
105+
OAuth2AccessTokenEntityInterface $accessToken,
106+
string $authCodeId = null,
107+
): ?RefreshTokenEntityInterface {
108+
if (! is_a($accessToken, AccessTokenEntityInterface::class)) {
109+
throw OidcServerException::serverError('Unexpected access token entity type.');
110+
}
111+
112+
return $this->refreshTokenIssuer->issue(
113+
$accessToken,
114+
$this->refreshTokenTTL,
115+
$authCodeId,
116+
self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS,
117+
);
118+
}
98119
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\oidc\Server\TokenIssuers;
6+
7+
use SimpleSAML\Module\oidc\Helpers;
8+
9+
abstract class AbstractTokenIssuer
10+
{
11+
public const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 5;
12+
13+
public function __construct(
14+
protected readonly Helpers $helpers,
15+
) {
16+
}
17+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\oidc\Server\TokenIssuers;
6+
7+
use DateInterval;
8+
use DateTimeImmutable;
9+
use League\OAuth2\Server\Entities\AccessTokenEntityInterface as Oauth2TokenEntityInterface;
10+
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
11+
use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface;
12+
use SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface;
13+
use SimpleSAML\Module\oidc\Factories\Entities\RefreshTokenEntityFactory;
14+
use SimpleSAML\Module\oidc\Helpers;
15+
use SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository;
16+
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
17+
use SimpleSAML\Module\oidc\Services\LoggerService;
18+
19+
class RefreshTokenIssuer extends AbstractTokenIssuer
20+
{
21+
public function __construct(
22+
Helpers $helpers,
23+
protected readonly RefreshTokenRepository $refreshTokenRepository,
24+
protected readonly RefreshTokenEntityFactory $refreshTokenEntityFactory,
25+
protected readonly LoggerService $logger,
26+
) {
27+
parent::__construct($helpers);
28+
}
29+
30+
/**
31+
* @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException
32+
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
33+
* @throws \League\OAuth2\Server\Exception\OAuthServerException
34+
*/
35+
public function issue(
36+
Oauth2TokenEntityInterface $accessToken,
37+
DateInterval $refreshTokenTtl,
38+
string $authCodeId = null,
39+
int $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS,
40+
): ?RefreshTokenEntityInterface {
41+
if (! is_a($accessToken, AccessTokenEntityInterface::class)) {
42+
throw OidcServerException::serverError('Unexpected access token entity type.');
43+
}
44+
45+
while ($maxGenerationAttempts-- > 0) {
46+
try {
47+
$refreshToken = $this->refreshTokenEntityFactory->fromData(
48+
$this->helpers->random()->getIdentifier(),
49+
(new DateTimeImmutable())->add($refreshTokenTtl),
50+
$accessToken,
51+
$authCodeId,
52+
);
53+
$this->refreshTokenRepository->persistNewRefreshToken($refreshToken);
54+
return $refreshToken;
55+
} catch (UniqueTokenIdentifierConstraintViolationException $e) {
56+
if ($maxGenerationAttempts === 0) {
57+
$this->logger->error('Maximum generation attempts reached.', [
58+
'maxGenerationAttempts' => $maxGenerationAttempts,
59+
'accessTokenId' => $accessToken->getIdentifier(),
60+
'authCodeId' => $authCodeId,
61+
]);
62+
throw $e;
63+
}
64+
}
65+
}
66+
67+
$this->logger->error('Unable to issue refresh token.', [
68+
'accessTokenId' => $accessToken->getIdentifier(),
69+
'authCodeId' => $authCodeId,
70+
]);
71+
72+
return null;
73+
}
74+
}

src/Services/Container.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule;
9494
use SimpleSAML\Module\oidc\Server\RequestRules\Rules\UiLocalesRule;
9595
use SimpleSAML\Module\oidc\Server\ResponseTypes\IdTokenResponse;
96+
use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer;
9697
use SimpleSAML\Module\oidc\Server\Validators\BearerTokenValidator;
9798
use SimpleSAML\Module\oidc\Stores\Session\LogoutTicketStoreBuilder;
9899
use SimpleSAML\Module\oidc\Stores\Session\LogoutTicketStoreDb;
@@ -354,6 +355,14 @@ public function __construct()
354355

355356
$this->services[Helpers::class] = $helpers;
356357

358+
$refreshTokenIssuer = new RefreshTokenIssuer(
359+
$helpers,
360+
$refreshTokenRepository,
361+
$refreshTokenEntityFactory,
362+
$loggerService,
363+
);
364+
$this->services[RefreshTokenIssuer::class] = $refreshTokenIssuer;
365+
357366
$authCodeGrantFactory = new AuthCodeGrantFactory(
358367
$moduleConfig,
359368
$authCodeRepository,
@@ -363,8 +372,7 @@ public function __construct()
363372
$requestParamsResolver,
364373
$accessTokenEntityFactory,
365374
$authCodeEntityFactory,
366-
$refreshTokenEntityFactory,
367-
$loggerService,
375+
$refreshTokenIssuer,
368376
);
369377
$this->services[AuthCodeGrant::class] = $authCodeGrantFactory->build();
370378

@@ -385,6 +393,7 @@ public function __construct()
385393
$moduleConfig,
386394
$refreshTokenRepository,
387395
$accessTokenEntityFactory,
396+
$refreshTokenIssuer,
388397
);
389398
$this->services[RefreshTokenGrant::class] = $refreshTokenGrantFactory->build();
390399

0 commit comments

Comments
 (0)