diff --git a/routing/services/services.yml b/routing/services/services.yml index da36c690..0d5e8a48 100644 --- a/routing/services/services.yml +++ b/routing/services/services.yml @@ -35,6 +35,9 @@ services: SimpleSAML\Module\oidc\Bridges\: resource: '../../src/Bridges/*' + SimpleSAML\Module\oidc\Server\TokenIssuers\: + resource: '../../src/Server/TokenIssuers/*' + SimpleSAML\Module\oidc\ModuleConfig: ~ SimpleSAML\Module\oidc\Helpers: ~ SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection: ~ diff --git a/src/Entities/RefreshTokenEntity.php b/src/Entities/RefreshTokenEntity.php index 047dfa48..c2094c12 100644 --- a/src/Entities/RefreshTokenEntity.php +++ b/src/Entities/RefreshTokenEntity.php @@ -24,8 +24,6 @@ use SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface; use SimpleSAML\Module\oidc\Entities\Traits\AssociateWithAuthCodeTrait; use SimpleSAML\Module\oidc\Entities\Traits\RevokeTokenTrait; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; -use SimpleSAML\Module\oidc\Utils\TimestampGenerator; class RefreshTokenEntity implements RefreshTokenEntityInterface { @@ -34,31 +32,18 @@ class RefreshTokenEntity implements RefreshTokenEntityInterface use RevokeTokenTrait; use AssociateWithAuthCodeTrait; - /** - * @throws \Exception - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - */ - public static function fromState(array $state): RefreshTokenEntityInterface - { - $refreshToken = new self(); - - if ( - !is_string($state['id']) || - !is_string($state['expires_at']) || - !is_a($state['access_token'], AccessTokenEntityInterface::class) - ) { - throw OidcServerException::serverError('Invalid Refresh Token state'); - } - - $refreshToken->identifier = $state['id']; - $refreshToken->expiryDateTime = DateTimeImmutable::createFromMutable( - TimestampGenerator::utc($state['expires_at']), - ); - $refreshToken->accessToken = $state['access_token']; - $refreshToken->isRevoked = (bool) $state['is_revoked']; - $refreshToken->authCodeId = empty($state['auth_code_id']) ? null : (string)$state['auth_code_id']; - - return $refreshToken; + public function __construct( + string $id, + DateTimeImmutable $expiryDateTime, + AccessTokenEntityInterface $accessTokenEntity, + ?string $authCodeId = null, + bool $isRevoked = false, + ) { + $this->setIdentifier($id); + $this->setExpiryDateTime($expiryDateTime); + $this->setAccessToken($accessTokenEntity); + $this->setAuthCodeId($authCodeId); + $this->isRevoked = $isRevoked; } public function getState(): array diff --git a/src/Factories/Entities/RefreshTokenEntityFactory.php b/src/Factories/Entities/RefreshTokenEntityFactory.php new file mode 100644 index 00000000..5c5af49a --- /dev/null +++ b/src/Factories/Entities/RefreshTokenEntityFactory.php @@ -0,0 +1,63 @@ +helpers->dateTime()->getUtc($state['expires_at']); + $accessToken = $state['access_token']; + $isRevoked = (bool) $state['is_revoked']; + $authCodeId = empty($state['auth_code_id']) ? null : (string)$state['auth_code_id']; + + return $this->fromData( + $id, + $expiryDateTime, + $accessToken, + $authCodeId, + $isRevoked, + ); + } +} diff --git a/src/Factories/Grant/AuthCodeGrantFactory.php b/src/Factories/Grant/AuthCodeGrantFactory.php index e2d811ed..f519b687 100644 --- a/src/Factories/Grant/AuthCodeGrantFactory.php +++ b/src/Factories/Grant/AuthCodeGrantFactory.php @@ -24,6 +24,7 @@ use SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository; use SimpleSAML\Module\oidc\Server\Grants\AuthCodeGrant; use SimpleSAML\Module\oidc\Server\RequestRules\RequestRulesManager; +use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; class AuthCodeGrantFactory @@ -37,6 +38,7 @@ public function __construct( private readonly RequestParamsResolver $requestParamsResolver, private readonly AccessTokenEntityFactory $accessTokenEntityFactory, private readonly AuthCodeEntityFactory $authCodeEntityFactory, + private readonly RefreshTokenIssuer $refreshTokenIssuer, ) { } @@ -54,6 +56,7 @@ public function build(): AuthCodeGrant $this->requestParamsResolver, $this->accessTokenEntityFactory, $this->authCodeEntityFactory, + $this->refreshTokenIssuer, ); $authCodeGrant->setRefreshTokenTTL($this->moduleConfig->getRefreshTokenDuration()); diff --git a/src/Factories/Grant/RefreshTokenGrantFactory.php b/src/Factories/Grant/RefreshTokenGrantFactory.php index c598c8cb..c12c26bb 100644 --- a/src/Factories/Grant/RefreshTokenGrantFactory.php +++ b/src/Factories/Grant/RefreshTokenGrantFactory.php @@ -19,6 +19,7 @@ use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository; use SimpleSAML\Module\oidc\Server\Grants\RefreshTokenGrant; +use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer; class RefreshTokenGrantFactory { @@ -26,12 +27,18 @@ public function __construct( private readonly ModuleConfig $moduleConfig, private readonly RefreshTokenRepository $refreshTokenRepository, private readonly AccessTokenEntityFactory $accessTokenEntityFactory, + private readonly RefreshTokenIssuer $refreshTokenIssuer, ) { } public function build(): RefreshTokenGrant { - $refreshTokenGrant = new RefreshTokenGrant($this->refreshTokenRepository, $this->accessTokenEntityFactory); + $refreshTokenGrant = new RefreshTokenGrant( + $this->refreshTokenRepository, + $this->accessTokenEntityFactory, + $this->refreshTokenIssuer, + ); + $refreshTokenGrant->setRefreshTokenTTL($this->moduleConfig->getRefreshTokenDuration()); return $refreshTokenGrant; diff --git a/src/Helpers.php b/src/Helpers.php index 80817630..7a5e153a 100644 --- a/src/Helpers.php +++ b/src/Helpers.php @@ -8,6 +8,7 @@ use SimpleSAML\Module\oidc\Helpers\Client; use SimpleSAML\Module\oidc\Helpers\DateTime; use SimpleSAML\Module\oidc\Helpers\Http; +use SimpleSAML\Module\oidc\Helpers\Random; use SimpleSAML\Module\oidc\Helpers\Str; class Helpers @@ -17,6 +18,7 @@ class Helpers protected static ?DateTime $dateTIme = null; protected static ?Str $str = null; protected static ?Arr $arr = null; + protected static ?Random $random = null; public function http(): Http { @@ -44,4 +46,9 @@ public function arr(): Arr { return static::$arr ??= new Arr(); } + + public function random(): Random + { + return static::$random ??= new Random(); + } } diff --git a/src/Helpers/Random.php b/src/Helpers/Random.php new file mode 100644 index 00000000..f6c0b68d --- /dev/null +++ b/src/Helpers/Random.php @@ -0,0 +1,27 @@ +accessTokenRepository->findById((string)$data['access_token_id']); - return RefreshTokenEntity::fromState($data); + return $this->refreshTokenEntityFactory->fromState($data); } /** diff --git a/src/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php index 59695c67..aec720b9 100644 --- a/src/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -21,6 +21,7 @@ use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\AuthCodeEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface; use SimpleSAML\Module\oidc\Entities\UserEntity; @@ -56,6 +57,7 @@ use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\AuthTimeResponseTypeInterface; use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\NonceResponseTypeInterface; use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\SessionIdResponseTypeInterface; +use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer; use SimpleSAML\Module\oidc\Utils\Arr; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\Module\oidc\Utils\ScopeHelper; @@ -162,6 +164,7 @@ public function __construct( protected RequestParamsResolver $requestParamsResolver, AccessTokenEntityFactory $accessTokenEntityFactory, protected AuthCodeEntityFactory $authCodeEntityFactory, + protected RefreshTokenIssuer $refreshTokenIssuer, ) { parent::__construct($authCodeRepository, $refreshTokenRepository, $authCodeTTL); @@ -747,34 +750,15 @@ protected function issueRefreshToken( OAuth2AccessTokenEntityInterface $accessToken, string $authCodeId = null, ): ?RefreshTokenEntityInterface { - if (! is_a($this->refreshTokenRepository, RefreshTokenRepositoryInterface::class)) { - throw OidcServerException::serverError('Unexpected refresh token repository entity type.'); - } - - $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); - - if ($refreshToken === null) { - return null; - } - - $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add($this->refreshTokenTTL)); - $refreshToken->setAccessToken($accessToken); - $refreshToken->setAuthCodeId($authCodeId); - - $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; - - while ($maxGenerationAttempts-- > 0) { - $refreshToken->setIdentifier($this->generateUniqueIdentifier()); - try { - $this->refreshTokenRepository->persistNewRefreshToken($refreshToken); - break; - } catch (UniqueTokenIdentifierConstraintViolationException $e) { - if ($maxGenerationAttempts === 0) { - throw $e; - } - } + if (! is_a($accessToken, AccessTokenEntityInterface::class)) { + throw OidcServerException::serverError('Unexpected access token entity type.'); } - return $refreshToken; + return $this->refreshTokenIssuer->issue( + $accessToken, + $this->refreshTokenTTL, + $authCodeId, + self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS, + ); } } diff --git a/src/Server/Grants/RefreshTokenGrant.php b/src/Server/Grants/RefreshTokenGrant.php index 75cde90c..ec7b58d1 100644 --- a/src/Server/Grants/RefreshTokenGrant.php +++ b/src/Server/Grants/RefreshTokenGrant.php @@ -5,13 +5,17 @@ namespace SimpleSAML\Module\oidc\Server\Grants; use Exception; +use League\OAuth2\Server\Entities\AccessTokenEntityInterface as OAuth2AccessTokenEntityInterface; use League\OAuth2\Server\Grant\RefreshTokenGrant as OAuth2RefreshTokenGrant; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface; +use SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface; use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\Grants\Traits\IssueAccessTokenTrait; +use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer; use function is_null; use function json_decode; @@ -48,6 +52,7 @@ class RefreshTokenGrant extends OAuth2RefreshTokenGrant public function __construct( RefreshTokenRepositoryInterface $refreshTokenRepository, AccessTokenEntityFactory $accessTokenEntityFactory, + protected readonly RefreshTokenIssuer $refreshTokenIssuer, ) { parent::__construct($refreshTokenRepository); $this->accessTokenEntityFactory = $accessTokenEntityFactory; @@ -95,4 +100,20 @@ protected function validateOldRefreshToken(ServerRequestInterface $request, $cli return $refreshTokenData; } + + protected function issueRefreshToken( + OAuth2AccessTokenEntityInterface $accessToken, + string $authCodeId = null, + ): ?RefreshTokenEntityInterface { + if (! is_a($accessToken, AccessTokenEntityInterface::class)) { + throw OidcServerException::serverError('Unexpected access token entity type.'); + } + + return $this->refreshTokenIssuer->issue( + $accessToken, + $this->refreshTokenTTL, + $authCodeId, + self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS, + ); + } } diff --git a/src/Server/TokenIssuers/AbstractTokenIssuer.php b/src/Server/TokenIssuers/AbstractTokenIssuer.php new file mode 100644 index 00000000..5b13d703 --- /dev/null +++ b/src/Server/TokenIssuers/AbstractTokenIssuer.php @@ -0,0 +1,17 @@ + 0) { + try { + $refreshToken = $this->refreshTokenEntityFactory->fromData( + $this->helpers->random()->getIdentifier(), + (new DateTimeImmutable())->add($refreshTokenTtl), + $accessToken, + $authCodeId, + ); + $this->refreshTokenRepository->persistNewRefreshToken($refreshToken); + return $refreshToken; + } catch (UniqueTokenIdentifierConstraintViolationException $e) { + if ($maxGenerationAttempts === 0) { + $this->logger->error('Maximum generation attempts reached.', [ + 'maxGenerationAttempts' => $maxGenerationAttempts, + 'accessTokenId' => $accessToken->getIdentifier(), + 'authCodeId' => $authCodeId, + ]); + throw $e; + } + } + } + + $this->logger->error('Unable to issue refresh token.', [ + 'accessTokenId' => $accessToken->getIdentifier(), + 'authCodeId' => $authCodeId, + ]); + + return null; + } +} diff --git a/src/Services/Container.php b/src/Services/Container.php index 7cc08c13..b4400a87 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -42,6 +42,7 @@ use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory; 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\FederationFactory; use SimpleSAML\Module\oidc\Factories\FormFactory; use SimpleSAML\Module\oidc\Factories\Grant\AuthCodeGrantFactory; @@ -92,6 +93,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\UiLocalesRule; use SimpleSAML\Module\oidc\Server\ResponseTypes\IdTokenResponse; +use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer; use SimpleSAML\Module\oidc\Server\Validators\BearerTokenValidator; use SimpleSAML\Module\oidc\Stores\Session\LogoutTicketStoreBuilder; use SimpleSAML\Module\oidc\Stores\Session\LogoutTicketStoreDb; @@ -238,7 +240,14 @@ public function __construct() ); $this->services[AccessTokenRepository::class] = $accessTokenRepository; - $refreshTokenRepository = new RefreshTokenRepository($moduleConfig, $accessTokenRepository); + $refreshTokenEntityFactory = new RefreshTokenEntityFactory($helpers); + $this->services[RefreshTokenEntityFactory::class] = $refreshTokenEntityFactory; + + $refreshTokenRepository = new RefreshTokenRepository( + $moduleConfig, + $accessTokenRepository, + $refreshTokenEntityFactory, + ); $this->services[RefreshTokenRepository::class] = $refreshTokenRepository; $scopeRepository = new ScopeRepository($moduleConfig); @@ -346,6 +355,14 @@ public function __construct() $this->services[Helpers::class] = $helpers; + $refreshTokenIssuer = new RefreshTokenIssuer( + $helpers, + $refreshTokenRepository, + $refreshTokenEntityFactory, + $loggerService, + ); + $this->services[RefreshTokenIssuer::class] = $refreshTokenIssuer; + $authCodeGrantFactory = new AuthCodeGrantFactory( $moduleConfig, $authCodeRepository, @@ -355,6 +372,7 @@ public function __construct() $requestParamsResolver, $accessTokenEntityFactory, $authCodeEntityFactory, + $refreshTokenIssuer, ); $this->services[AuthCodeGrant::class] = $authCodeGrantFactory->build(); @@ -375,6 +393,7 @@ public function __construct() $moduleConfig, $refreshTokenRepository, $accessTokenEntityFactory, + $refreshTokenIssuer, ); $this->services[RefreshTokenGrant::class] = $refreshTokenGrantFactory->build(); diff --git a/src/Utils/UniqueIdentifierGenerator.php b/src/Utils/UniqueIdentifierGenerator.php index 5c82c49a..d160caff 100644 --- a/src/Utils/UniqueIdentifierGenerator.php +++ b/src/Utils/UniqueIdentifierGenerator.php @@ -7,6 +7,9 @@ use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use Throwable; +/** + * TODO mivanci Move to Helpers\Random + */ class UniqueIdentifierGenerator { /** diff --git a/tests/unit/src/Entities/RefreshTokenEntityTest.php b/tests/unit/src/Entities/RefreshTokenEntityTest.php index 8c7e4ef7..7abed99f 100644 --- a/tests/unit/src/Entities/RefreshTokenEntityTest.php +++ b/tests/unit/src/Entities/RefreshTokenEntityTest.php @@ -4,6 +4,8 @@ namespace SimpleSAML\Test\Module\oidc\unit\Entities; +use DateTimeImmutable; +use DateTimeZone; use PDO; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -16,33 +18,37 @@ */ class RefreshTokenEntityTest extends TestCase { + protected string $id; + protected DateTimeImmutable $expiryDateTime; protected MockObject $accessTokenEntityMock; - protected array $state; + protected false $isRevoked; + protected string $authCodeId; /** * @throws \Exception */ protected function setUp(): void { + $this->id = 'id'; + $this->expiryDateTime = new DateTimeImmutable('1970-01-01 00:00:00', new DateTimeZone('UTC')); $this->accessTokenEntityMock = $this->createMock(AccessTokenEntity::class); $this->accessTokenEntityMock->method('getIdentifier')->willReturn('access_token_id'); - - $this->state = [ - 'id' => 'id', - 'expires_at' => '1970-01-01 00:00:00', - 'access_token' => $this->accessTokenEntityMock, - 'is_revoked' => false, - 'auth_code_id' => '123', - ]; + $this->isRevoked = false; + $this->authCodeId = 'auth_code_id'; } /** * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ - protected function prepareMockedInstance(array $state = null): RefreshTokenEntityInterface + protected function mock(): RefreshTokenEntityInterface { - $state ??= $this->state; - return RefreshTokenEntity::fromState($state); + return new RefreshTokenEntity( + $this->id, + $this->expiryDateTime, + $this->accessTokenEntityMock, + $this->authCodeId, + $this->isRevoked, + ); } /** @@ -52,7 +58,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( RefreshTokenEntity::class, - $this->prepareMockedInstance(), + $this->mock(), ); } @@ -62,13 +68,13 @@ public function testItIsInitializable(): void public function testCanGetState(): void { $this->assertSame( - $this->prepareMockedInstance()->getState(), + $this->mock()->getState(), [ - 'id' => 'id', + 'id' => $this->id, 'expires_at' => '1970-01-01 00:00:00', - 'access_token_id' => 'access_token_id', - 'is_revoked' => [$this->state['is_revoked'], PDO::PARAM_BOOL], - 'auth_code_id' => '123', + 'access_token_id' => $this->accessTokenEntityMock->getIdentifier(), + 'is_revoked' => [$this->isRevoked, PDO::PARAM_BOOL], + 'auth_code_id' => $this->authCodeId, ], ); } diff --git a/tests/unit/src/Repositories/RefreshTokenRepositoryTest.php b/tests/unit/src/Repositories/RefreshTokenRepositoryTest.php index eaaf25d2..8eb21fba 100644 --- a/tests/unit/src/Repositories/RefreshTokenRepositoryTest.php +++ b/tests/unit/src/Repositories/RefreshTokenRepositoryTest.php @@ -16,16 +16,18 @@ namespace SimpleSAML\Test\Module\oidc\unit\Repositories; use DateTimeImmutable; +use DateTimeZone; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use RuntimeException; use SimpleSAML\Configuration; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; +use SimpleSAML\Module\oidc\Entities\RefreshTokenEntity; +use SimpleSAML\Module\oidc\Factories\Entities\RefreshTokenEntityFactory; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository; use SimpleSAML\Module\oidc\Services\DatabaseMigration; -use SimpleSAML\Module\oidc\Utils\TimestampGenerator; /** * @covers \SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository @@ -40,6 +42,8 @@ class RefreshTokenRepositoryTest extends TestCase protected RefreshTokenRepository $repository; protected MockObject $accessTokenMock; protected MockObject $accessTokenRepositoryMock; + protected MockObject $refreshTokenEntityFactoryMock; + protected MockObject $refreshTokenEntityMock; /** * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException @@ -67,10 +71,14 @@ protected function setUp(): void $this->accessTokenMock = $this->createMock(AccessTokenEntity::class); $this->accessTokenMock->method('getIdentifier')->willReturn(self::ACCESS_TOKEN_ID); $this->accessTokenRepositoryMock = $this->createMock(AccessTokenRepository::class); + $this->refreshTokenEntityFactoryMock = $this->createMock(RefreshTokenEntityFactory::class); + + $this->refreshTokenEntityMock = $this->createMock(RefreshTokenEntity::class); $this->repository = new RefreshTokenRepository( new ModuleConfig(), $this->accessTokenRepositoryMock, + $this->refreshTokenEntityFactoryMock, ); } @@ -87,13 +95,19 @@ public function testGetTableName(): void */ public function testAddAndFound(): void { - $refreshToken = $this->repository->getNewRefreshToken(); - $refreshToken->setIdentifier(self::REFRESH_TOKEN_ID); - $refreshToken->setExpiryDateTime(DateTimeImmutable::createFromMutable(TimestampGenerator::utc('yesterday'))); - $refreshToken->setAccessToken($this->accessTokenMock); - + $refreshToken = new RefreshTokenEntity( + self::REFRESH_TOKEN_ID, + new DateTimeImmutable('yesterday', new DateTimeZone('UTC')), + $this->accessTokenMock, + ); $this->repository->persistNewRefreshToken($refreshToken); + $this->refreshTokenEntityFactoryMock->expects($this->once()) + ->method('fromState') + ->with($this->callback(function (array $state): bool { + return $state['id'] === self::REFRESH_TOKEN_ID; + }))->willReturn($refreshToken); + $this->accessTokenRepositoryMock->method('findById')->willReturn($this->accessTokenMock); $foundRefreshToken = $this->repository->findById(self::REFRESH_TOKEN_ID); @@ -115,7 +129,17 @@ public function testAddAndNotFound(): void */ public function testRevokeToken(): void { + $revokedRefreshTokenMock = $this->createMock(RefreshTokenEntity::class); + $revokedRefreshTokenMock->method('isRevoked')->willReturn(true); $this->accessTokenRepositoryMock->method('findById')->willReturn($this->accessTokenMock); + $this->refreshTokenEntityMock->expects($this->once())->method('revoke'); + $this->refreshTokenEntityFactoryMock->expects($this->atLeastOnce()) + ->method('fromState') + ->with($this->callback(function (array $state): bool { + return $state['id'] === self::REFRESH_TOKEN_ID; + })) + ->willReturnOnConsecutiveCalls($this->refreshTokenEntityMock, $revokedRefreshTokenMock); + $this->repository->revokeRefreshToken(self::REFRESH_TOKEN_ID); $isRevoked = $this->repository->isRefreshTokenRevoked(self::REFRESH_TOKEN_ID); diff --git a/tests/unit/src/Server/Grants/AuthCodeGrantTest.php b/tests/unit/src/Server/Grants/AuthCodeGrantTest.php index fcb2f061..a0f1b8ed 100644 --- a/tests/unit/src/Server/Grants/AuthCodeGrantTest.php +++ b/tests/unit/src/Server/Grants/AuthCodeGrantTest.php @@ -15,6 +15,7 @@ use SimpleSAML\Module\oidc\Repositories\Interfaces\RefreshTokenRepositoryInterface; use SimpleSAML\Module\oidc\Server\Grants\AuthCodeGrant; use SimpleSAML\Module\oidc\Server\RequestRules\RequestRulesManager; +use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; /** @@ -30,7 +31,8 @@ class AuthCodeGrantTest extends TestCase protected Stub $moduleConfigStub; protected Stub $requestParamsResolverStub; protected Stub $accessTokenEntityFactoryStub; - protected Stub $authCodeEntityFactory; + protected Stub $authCodeEntityFactoryStub; + protected Stub $refreshTokenIssuerStub; /** * @throws \Exception @@ -45,7 +47,8 @@ protected function setUp(): void $this->moduleConfigStub = $this->createStub(ModuleConfig::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->accessTokenEntityFactoryStub = $this->createStub(AccessTokenEntityFactory::class); - $this->authCodeEntityFactory = $this->createStub(AuthcodeEntityFactory::class); + $this->authCodeEntityFactoryStub = $this->createStub(AuthcodeEntityFactory::class); + $this->refreshTokenIssuerStub = $this->createStub(RefreshTokenIssuer::class); } /** @@ -63,7 +66,8 @@ public function testCanCreateInstance(): void $this->requestRulesManagerStub, $this->requestParamsResolverStub, $this->accessTokenEntityFactoryStub, - $this->authCodeEntityFactory, + $this->authCodeEntityFactoryStub, + $this->refreshTokenIssuerStub, ), ); }