diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 2d1b4ce5..be7dde19 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -35,6 +35,13 @@ class RefreshTokenGrant extends AbstractGrant */ protected $refreshTokenTTL = 604800; + /** + * Rotate token (default = true) + * + * @var integer + */ + protected $refreshTokenRotate = true; + /** * Set the TTL of the refresh token * @@ -57,6 +64,26 @@ class RefreshTokenGrant extends AbstractGrant return $this->refreshTokenTTL; } + /** + * Set the rotation boolean of the refresh token + * + * @return int + */ + public function setRefreshTokenRotation($refreshTokenRotate) + { + $this->refreshTokenRotate = $refreshTokenRotate; + } + + /** + * Get rotation boolean of the refresh token + * + * @return int + */ + public function shouldRefreshTokenRotate() + { + return $this->refreshTokenRotate; + } + /** * {@inheritdoc} */ @@ -146,17 +173,21 @@ class RefreshTokenGrant extends AbstractGrant $this->server->getTokenType()->setParam('access_token', $newAccessToken->getId()); $this->server->getTokenType()->setParam('expires_in', $this->getAccessTokenTTL()); - // Expire the old refresh token - $oldRefreshToken->expire(); + if ($this->shouldRefreshTokenRotate()) { + // Expire the old refresh token + $oldRefreshToken->expire(); - // Generate a new refresh token - $newRefreshToken = new RefreshTokenEntity($this->server); - $newRefreshToken->setId(SecureKey::generate()); - $newRefreshToken->setExpireTime($this->getRefreshTokenTTL() + time()); - $newRefreshToken->setAccessToken($newAccessToken); - $newRefreshToken->save(); + // Generate a new refresh token + $newRefreshToken = new RefreshTokenEntity($this->server); + $newRefreshToken->setId(SecureKey::generate()); + $newRefreshToken->setExpireTime($this->getRefreshTokenTTL() + time()); + $newRefreshToken->setAccessToken($newAccessToken); + $newRefreshToken->save(); - $this->server->getTokenType()->setParam('refresh_token', $newRefreshToken->getId()); + $this->server->getTokenType()->setParam('refresh_token', $newRefreshToken->getId()); + } else { + $this->server->getTokenType()->setParam('refresh_token', $oldRefreshToken->getId()); + } return $this->server->getTokenType()->generateResponse(); } diff --git a/tests/unit/Grant/RefreshTokenGrantTest.php b/tests/unit/Grant/RefreshTokenGrantTest.php index be7a02df..bd5ae5ab 100644 --- a/tests/unit/Grant/RefreshTokenGrantTest.php +++ b/tests/unit/Grant/RefreshTokenGrantTest.php @@ -421,4 +421,81 @@ class RefreshTokenGrantTest extends \PHPUnit_Framework_TestCase $server->issueAccessToken(); } + + public function testCompleteFlowRotateRefreshToken() + { + $_POST = [ + 'grant_type' => 'refresh_token', + 'client_id' => 'testapp', + 'client_secret' => 'foobar', + 'refresh_token' => 'refresh_token', + ]; + + $server = new AuthorizationServer(); + $grant = new RefreshTokenGrant(); + + $clientStorage = M::mock('League\OAuth2\Server\Storage\ClientInterface'); + $clientStorage->shouldReceive('setServer'); + $clientStorage->shouldReceive('get')->andReturn( + (new ClientEntity($server))->hydrate(['id' => 'testapp']) + ); + + $sessionStorage = M::mock('League\OAuth2\Server\Storage\SessionInterface'); + $sessionStorage->shouldReceive('setServer'); + $sessionStorage->shouldReceive('getScopes')->shouldReceive('getScopes')->andReturn([]); + $sessionStorage->shouldReceive('associateScope'); + $sessionStorage->shouldReceive('getByAccessToken')->andReturn( + (new SessionEntity($server)) + ); + + $accessTokenStorage = M::mock('League\OAuth2\Server\Storage\AccessTokenInterface'); + $accessTokenStorage->shouldReceive('setServer'); + $accessTokenStorage->shouldReceive('get')->andReturn( + (new AccessTokenEntity($server)) + ); + $accessTokenStorage->shouldReceive('delete'); + $accessTokenStorage->shouldReceive('create'); + $accessTokenStorage->shouldReceive('getScopes')->andReturn([ + (new ScopeEntity($server))->hydrate(['id' => 'foo']), + ]); + $accessTokenStorage->shouldReceive('associateScope'); + + $refreshTokenStorage = M::mock('League\OAuth2\Server\Storage\RefreshTokenInterface'); + $refreshTokenStorage->shouldReceive('setServer'); + $refreshTokenStorage->shouldReceive('associateScope'); + $refreshTokenStorage->shouldReceive('delete'); + $refreshTokenStorage->shouldReceive('create'); + $refreshTokenStorage->shouldReceive('get')->andReturn( + (new RefreshTokenEntity($server))->setId('refresh_token')->setExpireTime(time() + 86400) + ); + + $scopeStorage = M::mock('League\OAuth2\Server\Storage\ScopeInterface'); + $scopeStorage->shouldReceive('setServer'); + $scopeStorage->shouldReceive('get')->andReturn( + (new ScopeEntity($server))->hydrate(['id' => 'foo']) + ); + + $server->setClientStorage($clientStorage); + $server->setScopeStorage($scopeStorage); + $server->setSessionStorage($sessionStorage); + $server->setAccessTokenStorage($accessTokenStorage); + $server->setRefreshTokenStorage($refreshTokenStorage); + + $server->addGrantType($grant); + + $response = $server->issueAccessToken(); + $this->assertTrue(array_key_exists('access_token', $response)); + $this->assertTrue(array_key_exists('refresh_token', $response)); + $this->assertTrue(array_key_exists('token_type', $response)); + $this->assertTrue(array_key_exists('expires_in', $response)); + $this->assertNotEquals($response['refresh_token'], $_POST['refresh_token']); + + $grant->setRefreshTokenRotation(false); + $response = $server->issueAccessToken(); + $this->assertTrue(array_key_exists('access_token', $response)); + $this->assertTrue(array_key_exists('refresh_token', $response)); + $this->assertTrue(array_key_exists('token_type', $response)); + $this->assertTrue(array_key_exists('expires_in', $response)); + $this->assertEquals($response['refresh_token'], $_POST['refresh_token']); + } }