From 851c7c0eb1acd35ddccb1f5f33d1b8e5513dd75e Mon Sep 17 00:00:00 2001 From: Dave Walker Date: Fri, 19 Dec 2014 00:22:53 -0500 Subject: [PATCH] Per the spec: The authorization server MAY issue a new refresh token, in which case the client MUST discard the old refresh token and replace it with the new refresh token. The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client. If a new refresh token is issued, the refresh token scope MUST be identical to that of the refresh token included by the client in the request. This commit allows users to specifiy the time before the Refresh Token expire time to issue a new Refresh Token. alter method names, naming convention(?) --- src/Grant/RefreshTokenGrant.php | 49 +++++++++++--- tests/unit/Grant/RefreshTokenGrantTest.php | 77 ++++++++++++++++++++++ 2 files changed, 117 insertions(+), 9 deletions(-) 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']); + } }