From 94945ec49ebc4d37ea00ce95b2adfaf7ceb13ed4 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Wed, 2 Jan 2013 19:14:22 +0000 Subject: [PATCH] Added support for refresh tokens, user credentials and client credentials grant. 100% unit test code coverage for authentication. Fixes #2 --- src/Oauth2/Authentication/Database.php | 9 +- src/Oauth2/Authentication/Server.php | 422 +++++++++++++------- tests/authentication/database_mock.php | 19 +- tests/authentication/server_test.php | 520 ++++++++++++++++++++++++- 4 files changed, 805 insertions(+), 165 deletions(-) diff --git a/src/Oauth2/Authentication/Database.php b/src/Oauth2/Authentication/Database.php index e3babb80..b9de679b 100644 --- a/src/Oauth2/Authentication/Database.php +++ b/src/Oauth2/Authentication/Database.php @@ -129,6 +129,7 @@ interface Database $typeId ); + public function validateRefreshToken($refreshToken, $clientId); /** * Update the refresh token @@ -138,16 +139,16 @@ interface Database * * UPDATE oauth_sessions SET access_token = $newAccessToken, refresh_token = * $newRefreshToken, access_toke_expires = $accessTokenExpires, last_updated = UNIX_TIMESTAMP(NOW()) WHERE - * refresh_token = $currentRefreshToken + * id = $sessionId * * - * @param string $currentRefreshToken The session's current refresh token + * @param string $sessionId The session ID * @param string $newAccessToken The new access token for this session * @param string $newRefreshToken The new refresh token for the session * @param int $accessTokenExpires The UNIX timestamp of when the new token expires - * @return bool Whether the $currentRefreshToken was valid or not. + * @return void */ - public function refreshToken($currentRefreshToken, $newAccessToken, $newRefreshToken, $accessTokenExpires); + public function updateRefreshToken($sessionId, $newAccessToken, $newRefreshToken, $accessTokenExpires); /** * Validate that an authorisation code is valid diff --git a/src/Oauth2/Authentication/Server.php b/src/Oauth2/Authentication/Server.php index d799cf60..82a2800a 100644 --- a/src/Oauth2/Authentication/Server.php +++ b/src/Oauth2/Authentication/Server.php @@ -47,9 +47,14 @@ class Server * @var array */ private $_grantTypes = array( - 'authorization_code', - 'user_credentials', - 'refresh_token', + 'authorization_code' => false, + 'client_credentials' => false, + 'password' => false, + 'refresh_token' => false, + ); + + private $_grantTypeCallbacks = array( + 'password' => null ); /** @@ -84,11 +89,11 @@ class Server 'invalid_scope' => 'The requested scope is invalid, unknown, or malformed. Check the "%s" scope.', 'server_error' => 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.', 'temporarily_unavailable' => 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.', - 'unsupported_grant_type' => 'The authorization grant type is not supported by the authorization server', + 'unsupported_grant_type' => 'The authorization grant type "%s" is not supported by the authorization server', 'invalid_client' => 'Client authentication failed', 'invalid_grant' => 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. Check the "%s" parameter.', - 'invalid_credentials' => 'Invalid Credentials', - 'invalid_refresh' => 'Invalid Refresh Token', + 'invalid_credentials' => 'The user credentials were incorrect.', + 'invalid_refresh' => 'The refresh token is invalid.', ); /** @@ -101,7 +106,7 @@ class Server public function __construct($options = null) { if ($options !== null) { - $this->options = array_merge($this->_config, $options); + $this->_config = array_merge($this->_config, $options); } } @@ -117,6 +122,27 @@ class Server $this->_db = $db; } + /** + * Enable a grant type + * + * @access public + * @return void + */ + public function enableGrantType($type, $callback = null) + { + if (isset($this->_grantTypes[$type])) { + $this->_grantTypes[$type] = true; + } + + if (in_array($type, array_keys($this->_grantTypeCallbacks))) { + if (is_null($callback) || ! is_callable($callback)) { + throw new ServerException('No registered callback function for grant type `'.$type.'`'); + } + + $this->_grantTypeCallbacks[$type] = $callback; + } + } + /** * Check client authorise parameters * @@ -237,7 +263,7 @@ class Server ); // Create the new auth code - $authCode = $this->newAuthCode( + $authCode = $this->_newAuthCode( $authoriseParams['client_id'], 'user', $typeId, @@ -255,7 +281,7 @@ class Server * * @return string A unique code */ - private function generateCode() + private function _generateCode() { return sha1(uniqid(microtime())); } @@ -271,54 +297,35 @@ class Server * @param string $accessToken The access token (default = null) * @return string An authorisation code */ - private function newAuthCode($clientId, $type, $typeId, $redirectUri, $scopes = array(), $accessToken = null, $refreshToken = null) + private function _newAuthCode($clientId, $type, $typeId, $redirectUri, $scopes = array()) { - $authCode = $this->generateCode(); + $authCode = $this->_generateCode(); - // If an access token exists then update the existing session with the - // new authorisation code otherwise create a new session - if ($accessToken !== null) { + // Delete any existing sessions just to be sure + $this->_dbCall('deleteSession', $clientId, $type, $typeId); + + // Create a new session + $sessionId = $this->_dbCall( + 'newSession', + $clientId, + $redirectUri, + $type, + $typeId, + $authCode, + null, + null, + 'requested' + ); + + // Add the scopes + foreach ($scopes as $key => $scope) { $this->_dbCall( - 'updateSession', - $clientId, - $type, - $typeId, - $authCode, - $accessToken, - $refreshToken, - 'requested' + 'addSessionScope', + $sessionId, + $scope['scope'] ); - } else { - - // Delete any existing sessions just to be sure - $this->_dbCall('deleteSession', $clientId, $type, $typeId); - - // Create a new session - $sessionId = $this->_dbCall( - 'newSession', - $clientId, - $redirectUri, - $type, - $typeId, - $authCode, - null, - null, - 'requested' - ); - - // Add the scopes - foreach ($scopes as $key => $scope) { - - $this->_dbCall( - 'addSessionScope', - $sessionId, - $scope['scope'] - ); - - } - } return $authCode; @@ -335,8 +342,6 @@ class Server */ public function issueAccessToken($authParams = null) { - $params = array(); - if ( ! isset($authParams['grant_type']) && ! isset($_POST['grant_type'])) { throw new ClientException(sprintf($this->errors['invalid_request'], 'grant_type'), 0); } @@ -345,33 +350,35 @@ class Server $authParams['grant_type'] : $_POST['grant_type']; - // Ensure grant type is one that is recognised - if ( ! in_array($params['grant_type'], $this->_grantTypes)) { - throw new ClientException($this->errors['unsupported_grant_type'], 7); + // Ensure grant type is one that is recognised and is enabled + if ( ! in_array($params['grant_type'], array_keys($this->_grantTypes)) || $this->_grantTypes[$params['grant_type']] !== true) { + throw new ClientException(sprintf($this->errors['unsupported_grant_type'], $params['grant_type']), 7); } switch ($params['grant_type']) { - case 'authorization_code': // Authorization code grant - return $this->completeAuthCodeGrant($authParams, $params); + return $this->_completeAuthCodeGrant($authParams, $params); break; - case 'user_credentials': - return $this->completeUserCredentialsGrant($authParams, $params); - break; - - case 'refresh_token': // Refresh token - return $this->completeRefreshTokenGrant($authParams, $params); + case 'client_credentials': // Client credentials grant + return $this->_completeClientCredentialsGrant($authParams, $params); break; case 'password': // Resource owner password credentials grant - case 'client_credentials': // Client credentials grant + return $this->_completeUserCredentialsGrant($authParams, $params); + break; + case 'refresh_token': // Refresh token grant + return $this->_completeRefreshTokenGrant($authParams, $params); + break; + + // @codeCoverageIgnoreStart default: // Unsupported throw new ServerException($this->errors['server_error'] . 'Tried to process an unsuppported grant type.', 5); break; } + // @codeCoverageIgnoreEnd } /** @@ -384,7 +391,7 @@ class Server * * @return array Authorise request parameters */ - private function completeAuthCodeGrant($authParams = array(), $params = array()) + private function _completeAuthCodeGrant($authParams = array(), $params = array()) { // Client ID if ( ! isset($authParams['client_id']) && ! isset($_POST['client_id'])) { @@ -449,8 +456,10 @@ class Server // A session ID was returned so update it with an access token, // remove the authorisation code, change the stage to 'granted' - $accessToken = $this->generateCode(); - $refreshToken = $this->generateCode(); + $accessToken = $this->_generateCode(); + $refreshToken = ($this->_grantTypes['refresh_token']) ? + $this->_generateCode() : + null; $accessTokenExpires = time() + $this->_config['access_token_ttl']; $accessTokenExpiresIn = $this->_config['access_token_ttl']; @@ -473,34 +482,35 @@ class Server $refreshToken ); - return array( + $response = array( 'access_token' => $accessToken, - 'refresh_token' => $refreshToken, 'token_type' => 'bearer', 'expires' => $accessTokenExpires, 'expires_in' => $accessTokenExpiresIn ); + + if ($this->_grantTypes['refresh_token']) { + $response['refresh_token'] = $refreshToken; + } + + return $response; } /** - * Complete the user credentials grant + * Complete the resource owner password credentials grant * * @access private * @param array $authParams Array of parsed $_POST keys * @param array $params Generated parameters from issueAccessToken() * @return array Authorise request parameters */ - private function completeUserCredentialsGrant($authParams = array(), $params = array()) + private function _completeClientCredentialsGrant($authParams = array(), $params = array()) { - $params = array(); - - if ( ! isset($authParams['user_auth_callback'])) { - throw new \InvalidArgumentException('You must set a user_auth_callback when using the user_credentials grant type.'); - } - // Client ID if ( ! isset($authParams['client_id']) && ! isset($_POST['client_id'])) { + // @codeCoverageIgnoreStart throw new ClientException(sprintf($this->errors['invalid_request'], 'client_id'), 0); + // @codeCoverageIgnoreEnd } $params['client_id'] = (isset($authParams['client_id'])) ? @@ -509,13 +519,16 @@ class Server // Client secret if ( ! isset($authParams['client_secret']) && ! isset($_POST['client_secret'])) { + // @codeCoverageIgnoreStart throw new ClientException(sprintf($this->errors['invalid_request'], 'client_secret'), 0); + // @codeCoverageIgnoreEnd } + $params['client_secret'] = (isset($authParams['client_secret'])) ? $authParams['client_secret'] : $_POST['client_secret']; - // Validate client ID and redirect URI + // Validate client ID and client secret $clientDetails = $this->_dbCall( 'validateClient', $params['client_id'], @@ -524,72 +537,187 @@ class Server ); if ($clientDetails === false) { - throw new \Oauth2\Authentication\ClientException($this->errors['invalid_client'], 8); + // @codeCoverageIgnoreStart + throw new ClientException($this->errors['invalid_client'], 8); + // @codeCoverageIgnoreEnd } - // Check for grant - if ( ! isset($_POST['grant_type'])) { - throw new \Oauth2\Authentication\ClientException(sprintf($this->errors['invalid_request'], 'client_id'), 0); + // Generate an access token + $accessToken = $this->_generateCode(); + $refreshToken = ($this->_grantTypes['refresh_token']) ? + $this->_generateCode() : + null; + + $accessTokenExpires = time() + $this->_config['access_token_ttl']; + $accessTokenExpiresIn = $this->_config['access_token_ttl']; + + // Delete any existing sessions just to be sure + $this->_dbCall('deleteSession', $params['client_id'], 'client', $params['client_id']); + + // Create a new session + $this->_dbCall('newSession', $params['client_id'], null, 'client', $params['client_id'], null, $accessToken, $refreshToken, $accessTokenExpires, 'granted'); + + $response = array( + 'access_token' => $accessToken, + 'token_type' => 'bearer', + 'expires' => $accessTokenExpires, + 'expires_in' => $accessTokenExpiresIn + ); + + if ($this->_grantTypes['refresh_token']) { + $response['refresh_token'] = $refreshToken; } - $params['grant_type'] = $_POST['grant_type']; + return $response; + } - if ($params['grant_type'] == 'user_credentials') { - // Check if user's u+p are correct - $userId = call_user_func($authParams['user_auth_callback']); - - if ($userId === false) { - throw new \Oauth2\Authentication\ClientException($this->errors['invalid_credentials'], 0); - } - - // Generate an access token - $accessToken = $this->generateCode(); - $refreshToken = $this->generateCode(); - - $accessTokenExpires = time() + $this->_config['access_token_ttl']; - $accessTokenExpiresIn = $this->_config['access_token_ttl']; - - // Delete any existing sessions just to be sure - $this->_dbCall('deleteSession', $params['client_id'], 'user', $userId); - - // Create a new session - $this->_dbCall('newSession', $params['client_id'], null, 'user', $userId, null, $accessToken, $refreshToken, $accessTokenExpires, 'granted'); - - return array( - 'access_token' => $accessToken, - 'refresh_token' => $refreshToken, - 'token_type' => 'bearer', - 'expires' => $accessTokenExpires, - 'expires_in' => $accessTokenExpiresIn - ); - - } else { - throw new \Oauth2\Authentication\ClientException($this->errors['unsupported_grant_type'], 7); + /** + * Complete the resource owner password credentials grant + * + * @access private + * @param array $authParams Array of parsed $_POST keys + * @param array $params Generated parameters from issueAccessToken() + * @return array Authorise request parameters + */ + private function _completeUserCredentialsGrant($authParams = array(), $params = array()) + { + // Client ID + if ( ! isset($authParams['client_id']) && ! isset($_POST['client_id'])) { + // @codeCoverageIgnoreStart + throw new ClientException(sprintf($this->errors['invalid_request'], 'client_id'), 0); + // @codeCoverageIgnoreEnd } + $params['client_id'] = (isset($authParams['client_id'])) ? + $authParams['client_id'] : + $_POST['client_id']; + + // Client secret + if ( ! isset($authParams['client_secret']) && ! isset($_POST['client_secret'])) { + // @codeCoverageIgnoreStart + throw new ClientException(sprintf($this->errors['invalid_request'], 'client_secret'), 0); + // @codeCoverageIgnoreEnd + } + + $params['client_secret'] = (isset($authParams['client_secret'])) ? + $authParams['client_secret'] : + $_POST['client_secret']; + + // Validate client ID and client secret + $clientDetails = $this->_dbCall( + 'validateClient', + $params['client_id'], + $params['client_secret'], + null + ); + + if ($clientDetails === false) { + // @codeCoverageIgnoreStart + throw new ClientException($this->errors['invalid_client'], 8); + // @codeCoverageIgnoreEnd + } + + // User's username + if ( ! isset($authParams['username']) && ! isset($_POST['username'])) { + throw new ClientException(sprintf($this->errors['invalid_request'], 'username'), 0); + } + + $params['username'] = (isset($authParams['username'])) ? + $authParams['username'] : + $_POST['username']; + + // User's password + if ( ! isset($authParams['password']) && ! isset($_POST['password'])) { + throw new ClientException(sprintf($this->errors['invalid_request'], 'password'), 0); + } + + $params['password'] = (isset($authParams['password'])) ? + $authParams['password'] : + $_POST['password']; + + // Check if user's username and password are correct + $userId = call_user_func($this->_grantTypeCallbacks['password'], $params['username'], $params['password']); + + if ($userId === false) { + throw new \Oauth2\Authentication\ClientException($this->errors['invalid_credentials'], 0); + } + + // Generate an access token + $accessToken = $this->_generateCode(); + $refreshToken = ($this->_grantTypes['refresh_token']) ? + $this->_generateCode() : + null; + + $accessTokenExpires = time() + $this->_config['access_token_ttl']; + $accessTokenExpiresIn = $this->_config['access_token_ttl']; + + // Delete any existing sessions just to be sure + $this->_dbCall('deleteSession', $params['client_id'], 'user', $userId); + + // Create a new session + $this->_dbCall('newSession', $params['client_id'], null, 'user', $userId, null, $accessToken, $refreshToken, $accessTokenExpires, 'granted'); + + $response = array( + 'access_token' => $accessToken, + 'token_type' => 'bearer', + 'expires' => $accessTokenExpires, + 'expires_in' => $accessTokenExpiresIn + ); + + if ($this->_grantTypes['refresh_token']) { + $response['refresh_token'] = $refreshToken; + } + + return $response; } /** * Complete the refresh token grant * * @access private - * * @param array $authParams Array of parsed $_POST keys * @param array $params Generated parameters from issueAccessToken() - * * @return array Authorise request parameters */ - private function completeRefreshTokenGrant($authParams = array(), $params = array()) + private function _completeRefreshTokenGrant($authParams = array(), $params = array()) { - $params = array(); - - // Check for grant - if ( ! isset($_POST['grant_type'])) { - throw new \Oauth2\Authentication\ClientException(sprintf($this->errors['invalid_request'], 'grant_type'), 0); + // Client ID + if ( ! isset($authParams['client_id']) && ! isset($_POST['client_id'])) { + // @codeCoverageIgnoreStart + throw new ClientException(sprintf($this->errors['invalid_request'], 'client_id'), 0); + // @codeCoverageIgnoreEnd } - $params['grant_type'] = $_POST['grant_type']; + $params['client_id'] = (isset($authParams['client_id'])) ? + $authParams['client_id'] : + $_POST['client_id']; + // Client secret + if ( ! isset($authParams['client_secret']) && ! isset($_POST['client_secret'])) { + // @codeCoverageIgnoreStart + throw new ClientException(sprintf($this->errors['invalid_request'], 'client_secret'), 0); + // @codeCoverageIgnoreEnd + } + + $params['client_secret'] = (isset($authParams['client_secret'])) ? + $authParams['client_secret'] : + $_POST['client_secret']; + + // Validate client ID and client secret + $clientDetails = $this->_dbCall( + 'validateClient', + $params['client_id'], + $params['client_secret'], + null + ); + + if ($clientDetails === false) { + // @codeCoverageIgnoreStart + throw new ClientException($this->errors['invalid_client'], 8); + // @codeCoverageIgnoreEnd + } + + // Refresh token if ( ! isset($authParams['refresh_token']) && ! isset($_POST['refresh_token'])) { throw new ClientException(sprintf($this->errors['invalid_request'], 'refresh_token'), 0); } @@ -598,34 +726,30 @@ class Server $authParams['refresh_token'] : $_POST['refresh_token']; - if ($params['grant_type'] == 'refresh_token') { + // Validate refresh token + $sessionId = $this->_dbCall('validateRefreshToken', $params['refresh_token'], $params['client_id']); - // Generate an access token - $accessToken = $this->generateCode(); - $refreshToken = $this->generateCode(); - - $accessTokenExpires = time() + $this->_config['access_token_ttl']; - $accessTokenExpiresIn = $this->_config['access_token_ttl']; - - // Delete any existing sessions just to be sure - $result = $this->_dbCall('refreshToken', $params['refresh_token'], $accessToken, $refreshToken, $accessTokenExpires); - - if ( ! $result) { - throw new \Oauth2\Authentication\ClientException($this->errors['invalid_refresh'], 0); - } - - return array( - 'access_token' => $accessToken, - 'refresh_token' => $refreshToken, - 'token_type' => 'bearer', - 'expires' => $accessTokenExpires, - 'expires_in' => $accessTokenExpiresIn - ); - - } else { - throw new \Oauth2\Authentication\ClientException($this->errors['unsupported_grant_type'], 7); + if ($sessionId === false) { + throw new \Oauth2\Authentication\ClientException($this->errors['invalid_refresh'], 0); } + // Generate new tokens + $accessToken = $this->_generateCode(); + $refreshToken = $this->_generateCode(); + + $accessTokenExpires = time() + $this->_config['access_token_ttl']; + $accessTokenExpiresIn = $this->_config['access_token_ttl']; + + // Update the tokens + $this->_dbCall('updateRefreshToken', $sessionId, $accessToken, $refreshToken, $accessTokenExpires); + + return array( + 'access_token' => $accessToken, + 'refresh_token' => $refreshToken, + 'token_type' => 'bearer', + 'expires' => $accessTokenExpires, + 'expires_in' => $accessTokenExpiresIn + ); } /** diff --git a/tests/authentication/database_mock.php b/tests/authentication/database_mock.php index a3a712a0..594d2665 100644 --- a/tests/authentication/database_mock.php +++ b/tests/authentication/database_mock.php @@ -67,7 +67,7 @@ class OAuthdb implements Database { $this->sessions[$sessionId]['auth_code'] = $authCode; $this->sessions[$sessionId]['access_token'] = $accessToken; - $this->sessions[$sessionId]['refresh_token'] = $accessToken; + $this->sessions[$sessionId]['refresh_token'] = $refreshToken; $this->sessions[$sessionId]['access_token_expire'] = $accessTokenExpire; $this->sessions[$sessionId]['stage'] = $stage; @@ -148,4 +148,21 @@ class OAuthdb implements Database { die('not implemented accessTokenScopes'); } + + public function validateRefreshToken($refreshToken, $clientId) + { + if ($refreshToken !== $this->sessions[0]['refresh_token']) + { + return false; + } + + return true; + } + + public function updateRefreshToken($sessionId, $newAccessToken, $newRefreshToken, $accessTokenExpires) + { + $this->sessions[$sessionId]['access_token'] = $newAccessToken; + $this->sessions[$sessionId]['refresh_token'] = $newRefreshToken; + $this->sessions[$sessionId]['access_token_expire'] = $accessTokenExpires; + } } \ No newline at end of file diff --git a/tests/authentication/server_test.php b/tests/authentication/server_test.php index b94d897e..ef677c7e 100644 --- a/tests/authentication/server_test.php +++ b/tests/authentication/server_test.php @@ -15,10 +15,24 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase $this->oauth->registerDbAbstractor($this->oauthdb); } + public function test_setupWithOptions() + { + $o = new Oauth2\Authentication\Server(array( + 'access_token_ttl' => 86400 + )); + + $reflector = new ReflectionClass($o); + $param = $reflector->getProperty('_config'); + $param->setAccessible(true); + $array = $param->getValue($o); + + $this->assertEquals(86400, $array['access_token_ttl']); + } + public function test_generateCode() { $reflector = new ReflectionClass($this->oauth); - $method = $reflector->getMethod('generateCode'); + $method = $reflector->getMethod('_generateCode'); $method->setAccessible(true); $result = $method->invoke($this->oauth); @@ -164,6 +178,34 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase $this->oauth->checkClientAuthoriseParams(); } + /** + * @expectedException Oauth2\Authentication\ClientException + * @expectedExceptionCode 8 + */ + public function test_checkClientAuthoriseParams_invalidClient() + { + $_GET['client_id'] = 'test'; + $_GET['redirect_uri'] = 'http://example.com/test2'; + $_GET['response_type'] = 'code'; + $_GET['scope'] = 'blah'; + + $this->oauth->checkClientAuthoriseParams(); + } + + /** + * @expectedException Oauth2\Authentication\ClientException + * @expectedExceptionCode 3 + */ + public function test_checkClientAuthoriseParams_invalidResponseType() + { + $_GET['client_id'] = 'test'; + $_GET['redirect_uri'] = 'http://example.com/test'; + $_GET['response_type'] = 'blah'; + $_GET['scope'] = 'blah'; + + $this->oauth->checkClientAuthoriseParams(); + } + public function test_newAuthoriseRequest() { $result = $this->oauth->newAuthoriseRequest('user', '123', array( @@ -207,7 +249,11 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase $this->assertNotEquals($result1, $result2); } - public function test_issueAccessToken_POST() + /** + * @expectedException Oauth2\Authentication\ClientException + * @expectedExceptionCode 7 + */ + public function test_issueAccessTokenNoRegisteredGrant() { $auth_code = $this->oauth->newAuthoriseRequest('user', '123', array( 'client_id' => 'test', @@ -227,16 +273,38 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase $_POST['code'] = $auth_code; $result = $this->oauth->issueAccessToken(); + } - $this->assertCount(5, $result); + public function test_issueAccessToken_POST_authorization_code() + { + $auth_code = $this->oauth->newAuthoriseRequest('user', '123', array( + 'client_id' => 'test', + 'redirect_uri' => 'http://example.com/test', + 'scopes' => array(array( + 'id' => 1, + 'scope' => 'test', + 'name' => 'test', + 'description' => 'test' + )) + )); + + $_POST['client_id'] = 'test'; + $_POST['client_secret'] = 'test'; + $_POST['redirect_uri'] = 'http://example.com/test'; + $_POST['grant_type'] = 'authorization_code'; + $_POST['code'] = $auth_code; + + $this->oauth->enableGrantType('authorization_code'); + $result = $this->oauth->issueAccessToken(); + + $this->assertCount(4, $result); $this->assertArrayHasKey('access_token', $result); $this->assertArrayHasKey('token_type', $result); $this->assertArrayHasKey('expires_in', $result); $this->assertArrayHasKey('expires', $result); - $this->assertArrayHasKey('refresh_token', $result); } - public function test_issueAccessToken_PassedParams() + public function test_issueAccessToken_PassedParams_authorization_code() { $auth_code = $this->oauth->newAuthoriseRequest('user', '123', array( 'client_id' => 'test', @@ -255,6 +323,38 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase $params['grant_type'] = 'authorization_code'; $params['code'] = $auth_code; + $this->oauth->enableGrantType('authorization_code'); + $result = $this->oauth->issueAccessToken($params); + + $this->assertCount(4, $result); + $this->assertArrayHasKey('access_token', $result); + $this->assertArrayHasKey('token_type', $result); + $this->assertArrayHasKey('expires_in', $result); + $this->assertArrayHasKey('expires', $result); + } + + public function test_issueAccessToken_refresh_token() + { + $this->oauth->enableGrantType('authorization_code'); + $this->oauth->enableGrantType('refresh_token'); + + $auth_code = $this->oauth->newAuthoriseRequest('user', '123', array( + 'client_id' => 'test', + 'redirect_uri' => 'http://example.com/test', + 'scopes' => array(array( + 'id' => 1, + 'scope' => 'test', + 'name' => 'test', + 'description' => 'test' + )) + )); + + $params['client_id'] = 'test'; + $params['client_secret'] = 'test'; + $params['redirect_uri'] = 'http://example.com/test'; + $params['grant_type'] = 'authorization_code'; + $params['code'] = $auth_code; + $result = $this->oauth->issueAccessToken($params); $this->assertCount(5, $result); @@ -263,6 +363,404 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase $this->assertArrayHasKey('expires_in', $result); $this->assertArrayHasKey('expires', $result); $this->assertArrayHasKey('refresh_token', $result); + + // Wait for a few seconds for the access token to age + sleep(1); + + // Refresh the token + $params2['client_id'] = 'test'; + $params2['client_secret'] = 'test'; + $params2['redirect_uri'] = 'http://example.com/test'; + $params2['grant_type'] = 'refresh_token'; + $params2['refresh_token'] = $result['refresh_token']; + + $result2 = $this->oauth->issueAccessToken($params2); + + $this->assertCount(5, $result2); + $this->assertArrayHasKey('access_token', $result2); + $this->assertArrayHasKey('token_type', $result2); + $this->assertArrayHasKey('expires_in', $result2); + $this->assertArrayHasKey('expires', $result2); + $this->assertArrayHasKey('refresh_token', $result2); + + $this->assertNotEquals($result['access_token'], $result2['access_token']); + $this->assertNotEquals($result['refresh_token'], $result2['refresh_token']); + $this->assertNotEquals($result['expires'], $result2['expires']); + $this->assertEquals($result['expires_in'], $result2['expires_in']); + $this->assertEquals($result['token_type'], $result2['token_type']); + } + + public function test_issueAccessToken_client_credentials() + { + $this->oauth->enableGrantType('client_credentials'); + + $auth_code = $this->oauth->newAuthoriseRequest('user', '123', array( + 'client_id' => 'test', + 'redirect_uri' => 'http://example.com/test', + 'scopes' => array(array( + 'id' => 1, + 'scope' => 'test', + 'name' => 'test', + 'description' => 'test' + )) + )); + + $params['client_id'] = 'test'; + $params['client_secret'] = 'test'; + $params['redirect_uri'] = 'http://example.com/test'; + $params['grant_type'] = 'client_credentials'; + $params['code'] = $auth_code; + + $result = $this->oauth->issueAccessToken($params); + + $this->assertCount(4, $result); + $this->assertArrayHasKey('access_token', $result); + $this->assertArrayHasKey('token_type', $result); + $this->assertArrayHasKey('expires_in', $result); + $this->assertArrayHasKey('expires', $result); + } + + public function test_issueAccessToken_client_credentialsPOST() + { + $this->oauth->enableGrantType('client_credentials'); + + $auth_code = $this->oauth->newAuthoriseRequest('user', '123', array( + 'client_id' => 'test', + 'redirect_uri' => 'http://example.com/test', + 'scopes' => array(array( + 'id' => 1, + 'scope' => 'test', + 'name' => 'test', + 'description' => 'test' + )) + )); + + $_POST['client_id'] = 'test'; + $_POST['client_secret'] = 'test'; + $_POST['redirect_uri'] = 'http://example.com/test'; + $_POST['grant_type'] = 'client_credentials'; + $_POST['code'] = $auth_code; + + $result = $this->oauth->issueAccessToken(); + + $this->assertCount(4, $result); + $this->assertArrayHasKey('access_token', $result); + $this->assertArrayHasKey('token_type', $result); + $this->assertArrayHasKey('expires_in', $result); + $this->assertArrayHasKey('expires', $result); + } + + public function test_issueAccessToken_client_credentials_withRefreshToken() + { + $this->oauth->enableGrantType('client_credentials'); + $this->oauth->enableGrantType('refresh_token'); + + $auth_code = $this->oauth->newAuthoriseRequest('user', '123', array( + 'client_id' => 'test', + 'redirect_uri' => 'http://example.com/test', + 'scopes' => array(array( + 'id' => 1, + 'scope' => 'test', + 'name' => 'test', + 'description' => 'test' + )) + )); + + $params['client_id'] = 'test'; + $params['client_secret'] = 'test'; + $params['redirect_uri'] = 'http://example.com/test'; + $params['grant_type'] = 'client_credentials'; + $params['code'] = $auth_code; + + $result = $this->oauth->issueAccessToken($params); + + $this->assertCount(5, $result); + $this->assertArrayHasKey('access_token', $result); + $this->assertArrayHasKey('token_type', $result); + $this->assertArrayHasKey('expires_in', $result); + $this->assertArrayHasKey('expires', $result); + $this->assertArrayHasKey('refresh_token', $result); + } + + public function test_issueAccessToken_refresh_tokenPOST() + { + $this->oauth->enableGrantType('authorization_code'); + $this->oauth->enableGrantType('refresh_token'); + + $auth_code = $this->oauth->newAuthoriseRequest('user', '123', array( + 'client_id' => 'test', + 'redirect_uri' => 'http://example.com/test', + 'scopes' => array(array( + 'id' => 1, + 'scope' => 'test', + 'name' => 'test', + 'description' => 'test' + )) + )); + + $_POST['client_id'] = 'test'; + $_POST['client_secret'] = 'test'; + $_POST['redirect_uri'] = 'http://example.com/test'; + $_POST['grant_type'] = 'authorization_code'; + $_POST['code'] = $auth_code; + + $result = $this->oauth->issueAccessToken(); + + $this->assertCount(5, $result); + $this->assertArrayHasKey('access_token', $result); + $this->assertArrayHasKey('token_type', $result); + $this->assertArrayHasKey('expires_in', $result); + $this->assertArrayHasKey('expires', $result); + $this->assertArrayHasKey('refresh_token', $result); + + // Wait for a few seconds for the access token to age + sleep(1); + + // Refresh the token + $_POST['client_id'] = 'test'; + $_POST['client_secret'] = 'test'; + $_POST['redirect_uri'] = 'http://example.com/test'; + $_POST['grant_type'] = 'refresh_token'; + $_POST['refresh_token'] = $result['refresh_token']; + + $result2 = $this->oauth->issueAccessToken(); + + $this->assertCount(5, $result2); + $this->assertArrayHasKey('access_token', $result2); + $this->assertArrayHasKey('token_type', $result2); + $this->assertArrayHasKey('expires_in', $result2); + $this->assertArrayHasKey('expires', $result2); + $this->assertArrayHasKey('refresh_token', $result2); + + $this->assertNotEquals($result['access_token'], $result2['access_token']); + $this->assertNotEquals($result['refresh_token'], $result2['refresh_token']); + $this->assertNotEquals($result['expires'], $result2['expires']); + $this->assertEquals($result['expires_in'], $result2['expires_in']); + $this->assertEquals($result['token_type'], $result2['token_type']); + } + + /** + * @expectedException Oauth2\Authentication\ClientException + * @expectedExceptionCode 0 + */ + public function test_issueAccessToken_refresh_tokenMissingToken() + { + $this->oauth->enableGrantType('authorization_code'); + $this->oauth->enableGrantType('refresh_token'); + + $auth_code = $this->oauth->newAuthoriseRequest('user', '123', array( + 'client_id' => 'test', + 'redirect_uri' => 'http://example.com/test', + 'scopes' => array(array( + 'id' => 1, + 'scope' => 'test', + 'name' => 'test', + 'description' => 'test' + )) + )); + + $_POST['client_id'] = 'test'; + $_POST['client_secret'] = 'test'; + $_POST['redirect_uri'] = 'http://example.com/test'; + $_POST['grant_type'] = 'authorization_code'; + $_POST['code'] = $auth_code; + + $result = $this->oauth->issueAccessToken(); + + $this->assertCount(5, $result); + $this->assertArrayHasKey('access_token', $result); + $this->assertArrayHasKey('token_type', $result); + $this->assertArrayHasKey('expires_in', $result); + $this->assertArrayHasKey('expires', $result); + $this->assertArrayHasKey('refresh_token', $result); + + // Wait for a few seconds for the access token to age + sleep(1); + + // Refresh the token + $_POST['client_id'] = 'test'; + $_POST['client_secret'] = 'test'; + $_POST['redirect_uri'] = 'http://example.com/test'; + $_POST['grant_type'] = 'refresh_token'; + + $result2 = $this->oauth->issueAccessToken(); + } + + /** + * @expectedException Oauth2\Authentication\ClientException + * @expectedExceptionCode 0 + */ + public function test_issueAccessToken_invalid_refresh_token() + { + $this->oauth->enableGrantType('authorization_code'); + $this->oauth->enableGrantType('refresh_token'); + + $auth_code = $this->oauth->newAuthoriseRequest('user', '123', array( + 'client_id' => 'test', + 'redirect_uri' => 'http://example.com/test', + 'scopes' => array(array( + 'id' => 1, + 'scope' => 'test', + 'name' => 'test', + 'description' => 'test' + )) + )); + + $params['client_id'] = 'test'; + $params['client_secret'] = 'test'; + $params['redirect_uri'] = 'http://example.com/test'; + $params['grant_type'] = 'authorization_code'; + $params['code'] = $auth_code; + + $result = $this->oauth->issueAccessToken($params); + + $this->assertCount(5, $result); + $this->assertArrayHasKey('access_token', $result); + $this->assertArrayHasKey('token_type', $result); + $this->assertArrayHasKey('expires_in', $result); + $this->assertArrayHasKey('expires', $result); + $this->assertArrayHasKey('refresh_token', $result); + + // Wait for a few seconds for the access token to age + sleep(1); + + // Refresh the token + $params2['client_id'] = 'test'; + $params2['client_secret'] = 'test'; + $params2['redirect_uri'] = 'http://example.com/test'; + $params2['grant_type'] = 'refresh_token'; + $params2['refresh_token'] = 'blah'; + + $result2 = $this->oauth->issueAccessToken($params2); + } + + /** + * @expectedException Oauth2\Authentication\ServerException + * @expectedExceptionCode 0 + */ + public function test_issueAccessToken_password_grant_Missing_Callback() + { + $this->oauth->enableGrantType('password'); + } + + public function test_issueAccessToken_password_grant() + { + $this->oauth->enableGrantType('password', function(){ + return true; + }); + + $params['client_id'] = 'test'; + $params['client_secret'] = 'test'; + $params['grant_type'] = 'password'; + $params['username'] = 'alexbilbie'; + $params['password'] = 'helloworld'; + + $result = $this->oauth->issueAccessToken($params); + + $this->assertCount(4, $result); + $this->assertArrayHasKey('access_token', $result); + $this->assertArrayHasKey('token_type', $result); + $this->assertArrayHasKey('expires_in', $result); + $this->assertArrayHasKey('expires', $result); + } + + public function test_issueAccessToken_password_grantPOST() + { + $this->oauth->enableGrantType('password', function(){ + return true; + }); + + $_POST['client_id'] = 'test'; + $_POST['client_secret'] = 'test'; + $_POST['grant_type'] = 'password'; + $_POST['username'] = 'alexbilbie'; + $_POST['password'] = 'helloworld'; + + $result = $this->oauth->issueAccessToken(); + + $this->assertCount(4, $result); + $this->assertArrayHasKey('access_token', $result); + $this->assertArrayHasKey('token_type', $result); + $this->assertArrayHasKey('expires_in', $result); + $this->assertArrayHasKey('expires', $result); + } + + public function test_issueAccessToken_password_grant_withRefreshToken() + { + $this->oauth->enableGrantType('password', function(){ + return true; + }); + + $this->oauth->enableGrantType('refresh_token'); + + $params['client_id'] = 'test'; + $params['client_secret'] = 'test'; + $params['grant_type'] = 'password'; + $params['username'] = 'alexbilbie'; + $params['password'] = 'helloworld'; + + $result = $this->oauth->issueAccessToken($params); + + $this->assertCount(5, $result); + $this->assertArrayHasKey('access_token', $result); + $this->assertArrayHasKey('token_type', $result); + $this->assertArrayHasKey('expires_in', $result); + $this->assertArrayHasKey('expires', $result); + $this->assertArrayHasKey('refresh_token', $result); + } + + /** + * @expectedException Oauth2\Authentication\ClientException + * @expectedExceptionCode 0 + */ + public function test_issueAccessToken_password_grant_wrongCreds() + { + $this->oauth->enableGrantType('password', function(){ + return false; + }); + + $params['client_id'] = 'test'; + $params['client_secret'] = 'test'; + $params['grant_type'] = 'password'; + $params['username'] = 'alexbilbie'; + $params['password'] = 'helloworld'; + + $result = $this->oauth->issueAccessToken($params); + } + + /** + * @expectedException Oauth2\Authentication\ClientException + * @expectedExceptionCode 0 + */ + public function test_issueAccessToken_password_grant_missingUsername() + { + $this->oauth->enableGrantType('password', function(){ + return true; + }); + + $params['client_id'] = 'test'; + $params['client_secret'] = 'test'; + $params['grant_type'] = 'password'; + + $result = $this->oauth->issueAccessToken($params); + } + + /** + * @expectedException Oauth2\Authentication\ClientException + * @expectedExceptionCode 0 + */ + public function test_issueAccessToken_password_grant_missingPassword() + { + $this->oauth->enableGrantType('password', function(){ + return true; + }); + + $params['client_id'] = 'test'; + $params['client_secret'] = 'test'; + $params['grant_type'] = 'password'; + $params['username'] = 'alexbilbie'; + + $result = $this->oauth->issueAccessToken($params); } /** @@ -292,7 +790,7 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase public function test_completeAuthCodeGrant_missingClientId() { $reflector = new ReflectionClass($this->oauth); - $method = $reflector->getMethod('completeAuthCodeGrant'); + $method = $reflector->getMethod('_completeAuthCodeGrant'); $method->setAccessible(true); $method->invoke($this->oauth); @@ -305,7 +803,7 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase public function test_completeAuthCodeGrant_missingClientSecret() { $reflector = new ReflectionClass($this->oauth); - $method = $reflector->getMethod('completeAuthCodeGrant'); + $method = $reflector->getMethod('_completeAuthCodeGrant'); $method->setAccessible(true); $authParams['client_id'] = 'test'; @@ -320,7 +818,7 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase public function test_completeAuthCodeGrant_missingRedirectUri() { $reflector = new ReflectionClass($this->oauth); - $method = $reflector->getMethod('completeAuthCodeGrant'); + $method = $reflector->getMethod('_completeAuthCodeGrant'); $method->setAccessible(true); $authParams['client_id'] = 'test'; @@ -336,7 +834,7 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase public function test_completeAuthCodeGrant_invalidClient() { $reflector = new ReflectionClass($this->oauth); - $method = $reflector->getMethod('completeAuthCodeGrant'); + $method = $reflector->getMethod('_completeAuthCodeGrant'); $method->setAccessible(true); $authParams['client_id'] = 'test'; @@ -353,7 +851,7 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase public function test_completeAuthCodeGrant_missingCode() { $reflector = new ReflectionClass($this->oauth); - $method = $reflector->getMethod('completeAuthCodeGrant'); + $method = $reflector->getMethod('_completeAuthCodeGrant'); $method->setAccessible(true); $authParams['client_id'] = 'test'; @@ -370,7 +868,7 @@ class Authentication_Server_test extends PHPUnit_Framework_TestCase public function test_completeAuthCodeGrant_invalidCode() { $reflector = new ReflectionClass($this->oauth); - $method = $reflector->getMethod('completeAuthCodeGrant'); + $method = $reflector->getMethod('_completeAuthCodeGrant'); $method->setAccessible(true); $authParams['client_id'] = 'test';