Skip to content

Commit

Permalink
fixes #11 - only returns refresh tokens at the expected times
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer committed Dec 20, 2012
1 parent 766eb06 commit 81ceb46
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 32 deletions.
11 changes: 5 additions & 6 deletions src/OAuth2/Controller/GrantController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function __construct(OAuth2_Storage_ClientCredentialsInterface $clientSto
foreach ($grantTypes as $grantType) {
$this->addGrantType($grantType);
}

if (is_null($util)) {
$util = new OAuth2_Util();
}
Expand Down Expand Up @@ -62,6 +63,7 @@ public function grantAccessToken(OAuth2_RequestInterface $request)
}
$grantType = $this->grantTypes[$grantType];

// get and validate client authorization from the request
if (!$clientData = $this->getClientCredentials($request)) {
return null;
}
Expand All @@ -80,6 +82,7 @@ public function grantAccessToken(OAuth2_RequestInterface $request)
return null;
}

// validate the request for the token
if (!$grantType->validateRequest($request)) {
if ($grantType instanceof OAuth2_Response_ProviderInterface && $response = $grantType->getResponse()) {
$this->response = $response;
Expand Down Expand Up @@ -120,13 +123,9 @@ public function grantAccessToken(OAuth2_RequestInterface $request)
return null;
}

$user_id = isset($tokenData['user_id']) ? $tokenData['user_id'] : null;
$token = $this->accessToken->createAccessToken($clientData['client_id'], $user_id, $tokenData['scope']);

// TODO: Remove this
$grantType->finishGrantRequest($token);
$tokenData['user_id'] = isset($tokenData['user_id']) ? $tokenData['user_id'] : null;

return $token;
return $grantType->createAccessToken($this->accessToken, $clientData, $tokenData);
}

/**
Expand Down
6 changes: 4 additions & 2 deletions src/OAuth2/GrantType/AuthorizationCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,10 @@ public function validateTokenData($tokenData, array $clientData)
return true;
}

public function finishGrantRequest($token)
{}
public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData)
{
return $accessToken->createAccessToken($clientData['client_id'], $tokenData['user_id'], $tokenData['scope']);
}

public function getResponse()
{
Expand Down
11 changes: 9 additions & 2 deletions src/OAuth2/GrantType/ClientCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ public function validateTokenData($tokenData, array $clientData)
return true;
}

public function finishGrantRequest($token)
{}
public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData)
{
/*
* Client Credentials Grant does NOT include a refresh token
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.4.3
*/
$includeRefreshToken = false;
return $accessToken->createAccessToken($clientData['client_id'], $tokenData['user_id'], $tokenData['scope'], $includeRefreshToken);
}
}
22 changes: 19 additions & 3 deletions src/OAuth2/GrantType/RefreshToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ class OAuth2_GrantType_RefreshToken implements OAuth2_GrantTypeInterface, OAuth2
{
private $storage;
private $response;
private $config;
private $oldRefreshToken;

public function __construct(OAuth2_Storage_RefreshTokenInterface $storage)
public function __construct(OAuth2_Storage_RefreshTokenInterface $storage, $config = array())
{
$this->config = array_merge(array(
'always_issue_new_refresh_token' => false
), $config);
$this->storage = $storage;
}

Expand Down Expand Up @@ -57,9 +61,21 @@ public function validateTokenData($tokenData, array $clientData)
return true;
}

public function finishGrantRequest($token)
public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData)
{
$this->storage->unsetRefreshToken($this->oldRefreshToken);
/*
* It is optional to force a new refresh token when a refresh token is used.
* However, if a new refresh token is issued, the old one MUST be expired
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-6
*/
$issueNewRefreshToken = $this->config['always_issue_new_refresh_token'];
$token = $accessToken->createAccessToken($clientData['client_id'], $tokenData['user_id'], $tokenData['scope'], $issueNewRefreshToken);

if ($issueNewRefreshToken) {
$this->storage->unsetRefreshToken($this->oldRefreshToken);
}

return $token;
}

public function getResponse()
Expand Down
6 changes: 4 additions & 2 deletions src/OAuth2/GrantType/UserCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ public function validateTokenData($tokenData, array $clientData)
return true;
}

public function finishGrantRequest($token)
{}
public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData)
{
return $accessToken->createAccessToken($clientData['client_id'], $tokenData['user_id'], $tokenData['scope']);
}

public function getResponse()
{
Expand Down
2 changes: 1 addition & 1 deletion src/OAuth2/GrantTypeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ public function getQuerystringIdentifier();
public function validateRequest($request);
public function getTokenDataFromRequest($request);
public function validateTokenData($tokenData, array $clientData);
public function finishGrantRequest($token);
public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData);
}
23 changes: 17 additions & 6 deletions src/OAuth2/ResponseType/AccessToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ public function getAuthorizeResponse($params, $user_id = null)

$params += array('scope' => null, 'state' => null);

// should this call from a grant type?
$result["fragment"] = $this->createAccessToken($params['client_id'], $user_id, $params['scope']);
/*
* a refresh token MUST NOT be included in the fragment
*
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.2.2
*/
$includeRefreshToken = false;
$result["fragment"] = $this->createAccessToken($params['client_id'], $user_id, $params['scope'], $includeRefreshToken);

if (isset($params['state'])) {
$result["fragment"]["state"] = $params['state'];
Expand All @@ -48,11 +53,13 @@ public function getAuthorizeResponse($params, $user_id = null)
* Client identifier related to the access token.
* @param $scope
* (optional) Scopes to be stored in space-separated string.
* @param bool $excludeRefreshToken
* If true, the refresh_token will be omitted from the response
*
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5
* @ingroup oauth2_section_5
*/
public function createAccessToken($client_id, $user_id, $scope = null)
public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
{
$token = array(
"access_token" => $this->generateAccessToken(),
Expand All @@ -63,9 +70,13 @@ public function createAccessToken($client_id, $user_id, $scope = null)

$this->tokenStorage->setAccessToken($token["access_token"], $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);

// Issue a refresh token also, if we support them
/* TODO: How do we handle this? */
if ($this->refreshStorage) {
/*
* Issue a refresh token also, if we support them
*
* Refresh Tokens are considered supported if an instance of OAuth2_Storage_RefreshTokenInterface
* is supplied in the constructor
*/
if ($includeRefreshToken && $this->refreshStorage) {
$token["refresh_token"] = $this->generateRefreshToken();
$this->refreshStorage->setRefreshToken($token['refresh_token'], $client_id, $user_id, time() + $this->config['refresh_token_lifetime'], $scope);
}
Expand Down
2 changes: 1 addition & 1 deletion src/OAuth2/ResponseType/AccessTokenInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

interface OAuth2_ResponseType_AccessTokenInterface extends OAuth2_ResponseTypeInterface
{
public function createAccessToken($client_id, $user_id, $scope = null);
public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true);
}
21 changes: 15 additions & 6 deletions src/OAuth2/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

/**
* Service class for OAuth
* Inspired by oauth2-php (https://github.com/quizlet/oauth2-php)
* This class serves only to wrap the other Controller classes
* @see OAuth2_Controller_AccessController
* @see OAuth2_Controller_AuthorizeController
* @see OAuth2_Controller_GrantController
*/
class OAuth2_Server implements OAuth2_Controller_AccessControllerInterface,
OAuth2_Controller_AuthorizeControllerInterface, OAuth2_Controller_GrantControllerInterface
Expand All @@ -27,14 +30,18 @@ class OAuth2_Server implements OAuth2_Controller_AccessControllerInterface,
* array - array of Objects to implement storage
* OAuth2_Storage object implementing all required storage types (ClientCredentialsInterface and AccessTokenInterface as a minimum)
*
* @param array $config
* specify a different token lifetime, token header name, etc
*
* @param array $grantTypes
* An array of OAuth2_GrantTypeInterface to use for granting access tokens
*
* @param array $config
* specify a different token lifetime, token header name, etc
* @param array $responseTypes
* Response types to use. array keys should be "code" and and "token" for
* Access Token and Authorization Code response types
*
* @param array $response
* Send in an instance of OAuth2_ResponseInterface to use a different response object
* @param OAuth2_ResponseType_AccessTokenInterface $accessTokenResponseType
* Response type to use for access token
*
* @return
* TRUE if everything in required scope is contained in available scope,
Expand All @@ -44,7 +51,7 @@ class OAuth2_Server implements OAuth2_Controller_AccessControllerInterface,
*
* @ingroup oauth2_section_7
*/
public function __construct($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), OAuth2_ResponseType_AccessToken $accessTokenResponseType = null)
public function __construct($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), OAuth2_ResponseType_AccessTokenInterface $accessTokenResponseType = null)
{
$validStorage = array(
'access_token' => 'OAuth2_Storage_AccessTokenInterface',
Expand Down Expand Up @@ -270,8 +277,10 @@ public function getClientCredentials(OAuth2_RequestInterface $request)
* list of space-delimited strings.
* - state: (optional) An opaque value used by the client to maintain
* state between the request and callback.
*
* @param $is_authorized
* TRUE or FALSE depending on whether the user authorized the access.
*
* @param $user_id
* Identifier of user who authorized the client
*
Expand Down
31 changes: 28 additions & 3 deletions test/OAuth2/GrantType/RefreshTokenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class OAuth2_GrantType_RefreshTokenTest extends PHPUnit_Framework_TestCase
public function testNoRefreshToken()
{
$server = $this->getTestServer();
$server->addGrantType(new OAuth2_GrantType_RefreshToken($this->storage));

$request = OAuth2_Request::createFromGlobals();
$request->query['grant_type'] = 'refresh_token'; // valid grant type
$request->query['client_id'] = 'Test Client ID'; // valid client id
Expand All @@ -22,6 +24,8 @@ public function testNoRefreshToken()
public function testInvalidRefreshToken()
{
$server = $this->getTestServer();
$server->addGrantType(new OAuth2_GrantType_RefreshToken($this->storage));

$request = OAuth2_Request::createFromGlobals();
$request->query['grant_type'] = 'refresh_token'; // valid grant type
$request->query['client_id'] = 'Test Client ID'; // valid client id
Expand All @@ -35,28 +39,49 @@ public function testInvalidRefreshToken()
$this->assertEquals($response->getParameter('error_description'), 'Invalid refresh token');
}

public function testValidRefreshToken()
public function testValidRefreshTokenWithNewRefreshTokenInResponse()
{
$server = $this->getTestServer();
$server->addGrantType(new OAuth2_GrantType_RefreshToken($this->storage, array('always_issue_new_refresh_token' => true)));

$request = OAuth2_Request::createFromGlobals();
$request->query['grant_type'] = 'refresh_token'; // valid grant type
$request->query['client_id'] = 'Test Client ID'; // valid client id
$request->query['client_secret'] = 'TestSecret'; // valid client secret
$request->query['refresh_token'] = 'test-refreshtoken'; // valid client secret
$token = $server->grantAccessToken($request);
$this->assertTrue(isset($token['refresh_token']));
$this->assertTrue(isset($token['refresh_token']), 'refresh token should always refresh');

$refresh_token = $this->storage->getRefreshToken($token['refresh_token']);
$this->assertNotNull($refresh_token);
$this->assertEquals($refresh_token['refresh_token'], $token['refresh_token']);
$this->assertEquals($refresh_token['client_id'], $request->query('client_id'));
$this->assertTrue($token['refresh_token'] != 'test-refreshtoken', 'the refresh token returned is not the one used');
$used_token = $this->storage->getRefreshToken('test-refreshtoken');
$this->assertNull($used_token, 'the refresh token used is no longer valid');
}

public function testValidRefreshTokenWithNoRefreshTokenInResponse()
{
$server = $this->getTestServer();
$server->addGrantType(new OAuth2_GrantType_RefreshToken($this->storage, array('always_issue_new_refresh_token' => false)));

$request = OAuth2_Request::createFromGlobals();
$request->query['grant_type'] = 'refresh_token'; // valid grant type
$request->query['client_id'] = 'Test Client ID'; // valid client id
$request->query['client_secret'] = 'TestSecret'; // valid client secret
$request->query['refresh_token'] = 'test-refreshtoken'; // valid client secret
$token = $server->grantAccessToken($request);
$this->assertFalse(isset($token['refresh_token']), 'refresh token should not be returned');

$used_token = $this->storage->getRefreshToken('test-refreshtoken');
$this->assertNotNull($used_token, 'the refresh token used is still valid');
}

private function getTestServer()
{
$this->storage = new OAuth2_Storage_Memory(json_decode(file_get_contents(dirname(__FILE__).'/../../config/storage.json'), true));
$server = new OAuth2_Server($this->storage);
$server->addGrantType(new OAuth2_GrantType_RefreshToken($this->storage));

return $server;
}
Expand Down

0 comments on commit 81ceb46

Please sign in to comment.