From 8720de48de790695f3c867d2e0a0068131e35d84 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Tue, 14 Aug 2012 15:44:25 +0100 Subject: [PATCH 01/12] Initial update with some PSR-* changes --- src/Oauth2/Resource/Server.php | 206 +++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) diff --git a/src/Oauth2/Resource/Server.php b/src/Oauth2/Resource/Server.php index 15d62fa1..bd6a8984 100644 --- a/src/Oauth2/Resource/Server.php +++ b/src/Oauth2/Resource/Server.php @@ -2,7 +2,213 @@ namespace Oauth2\Resource; +class OAuthResourceServerException extends \Exception +{ + +} + class Server { + /** + * The access token. + * @access private + */ + private $_accessToken = NULL; + + /** + * The scopes the access token has access to. + * @access private + */ + private $_scopes = array(); + + /** + * The type of owner of the access token. + * @access private + */ + private $_type = NULL; + + /** + * The ID of the owner of the access token. + * @access private + */ + private $_typeId = NULL; + + /** + * Server configuration + * @var array + */ + private $config = array( + 'token_key' => 'oauth_token' + ); + + /** + * Constructor + * + * @access public + * @return void + */ + public function __construct($options = null) + { + if ($options !== null) { + $this->config = array_merge($this->config, $options); + } + } + + /** + * Magic method to test if access token represents a particular owner type + * @param [type] $method [description] + * @param [type] $arguements [description] + * @return [type] [description] + */ + public function __call($method, $arguements) + { + if (substr($method, 0, 2) === 'is') + { + if ($this->_type === strtolower(substr($method, 2))) + { + return $this->_typeId; + } + + return false; + } + } + + /** + * Register a database abstrator class + * + * @access public + * @param object $db A class that implements OAuth2ServerDatabase + * @return void + */ + public function registerDbAbstractor($db) + { + $this->db = $db; + } + /** + * Init function + * + * @access public + * @return void + */ + public function init() + { + $accessToken = null; + + // Try and get the access token via an access_token or oauth_token parameter + switch ($server['REQUEST_METHOD']) + { + case 'POST': + $accessToken = isset($_POST[$this->config['token_key']]) ? $_POST[$this->config['token_key']] : null; + break; + + default: + $accessToken = isset($_GET[$this->config['token_key']]) ? $_GET[$this->config['token_key']] : null; + break; + } + + // Try and get an access token from the auth header + $headers = getallheaders(); + if (isset($headers['Authorization'])) + { + $rawToken = trim(str_replace('Bearer', '', $headers['Authorization'])); + if ( ! empty($rawToken)) + { + $accessToken = base64_decode($rawToken); + } + } + + if ($accessToken) + { + $sessionQuery = $this->ci->db->get_where('oauth_sessions', array('access_token' => $accessToken, 'stage' => 'granted')); + + if ($session_query->num_rows() === 1) + { + $session = $session_query->row(); + $this->_accessToken = $session->access_token; + $this->_type = $session->type; + $this->_typeId = $session->type_id; + + $scopes_query = $this->ci->db->get_where('oauth_session_scopes', array('access_token' => $accessToken)); + if ($scopes_query->num_rows() > 0) + { + foreach ($scopes_query->result() as $scope) + { + $this->_scopes[] = $scope->scope; + } + } + } + + else + { + $this->ci->output->set_status_header(403); + $this->ci->output->set_output('Invalid access token'); + } + } + + else + { + $this->ci->output->set_status_header(403); + $this->ci->output->set_output('Missing access token'); + } + } + + /** + * Test if the access token has a specific scope + * + * @param mixed $scopes Scope(s) to check + * + * @access public + * @return string|bool + */ + public function hasScope($scopes) + { + if (is_string($scopes)) + { + if (in_array($scopes, $this->_scopes)) + { + return true; + } + + return false; + } + + elseif (is_array($scopes)) + { + foreach ($scopes as $scope) + { + if ( ! in_array($scope, $this->_scopes)) + { + return false; + } + } + + return true; + } + + return false; + } + + /** + * Call database methods from the abstractor + * + * @return mixed The query result + */ + private function dbcall() + { + if ($this->db === null) { + throw new OAuthResourceServerException('No registered database abstractor'); + } + + if ( ! $this->db instanceof Database) { + throw new OAuthResourceServerException('Registered database abstractor is not an instance of Oauth2\Resource\Database'); + } + + $args = func_get_args(); + $method = $args[0]; + unset($args[0]); + $params = array_values($args); + + return call_user_func_array(array($this->db, $method), $args); + } } \ No newline at end of file From 77ce18df56dc5896bad64cf26fc14fbc27e20d73 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Tue, 14 Aug 2012 15:46:58 +0100 Subject: [PATCH 02/12] Added the resource server database interface --- src/Oauth2/Resource/Database.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Oauth2/Resource/Database.php b/src/Oauth2/Resource/Database.php index c39bb471..a6ff8a2a 100644 --- a/src/Oauth2/Resource/Database.php +++ b/src/Oauth2/Resource/Database.php @@ -4,4 +4,7 @@ namespace Oauth2\Resource; interface Database { + public function validateAccessToken($accessToken); + + public function sessionScopes($sessionId); } \ No newline at end of file From e859f435a17ad6db7cb9fb8823417510f7d29a16 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Tue, 14 Aug 2012 16:28:40 +0100 Subject: [PATCH 03/12] Added docblocks for the database interface --- src/Oauth2/Resource/Database.php | 51 +++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Oauth2/Resource/Database.php b/src/Oauth2/Resource/Database.php index a6ff8a2a..ac91bfa9 100644 --- a/src/Oauth2/Resource/Database.php +++ b/src/Oauth2/Resource/Database.php @@ -4,7 +4,56 @@ namespace Oauth2\Resource; interface Database { + /** + * Validate an access token and return the session details. + * + * Database query: + * + * + * SELECT id, owner_type, owner_id FROM oauth_sessions WHERE access_token = + * $accessToken AND stage = 'granted' AND + * access_token_expires > UNIX_TIMESTAMP(now()) + * + * + * Response: + * + * + * Array + * ( + * [id] => (int) The session ID + * [owner_type] => (string) The session owner type + * [owner_id] => (string) The session owner's ID + * ) + * + * + * @param string $accessToken The access token + * @return array|bool Return an array on success or false on failure + */ public function validateAccessToken($accessToken); - + + /** + * Returns the scopes that the session is authorised with. + * + * Database query: + * + * + * SELECT scope FROM oauth_session_scopes WHERE access_token = + * '291dca1c74900f5f252de351e0105aa3fc91b90b' + * + * + * Response: + * + * + * Array + * ( + * [0] => (string) A scope + * [1] => (string) Another scope + * ... + * ) + * + * + * @param int $sessionId The session ID + * @return array A list of scopes + */ public function sessionScopes($sessionId); } \ No newline at end of file From 519d20f0a51b09fd009d579fb54b1696bee19ae8 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Tue, 14 Aug 2012 16:34:43 +0100 Subject: [PATCH 04/12] Changed indent to spaces --- src/Oauth2/Resource/Database.php | 80 ++++++++++++++++---------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/Oauth2/Resource/Database.php b/src/Oauth2/Resource/Database.php index ac91bfa9..9c5d1b44 100644 --- a/src/Oauth2/Resource/Database.php +++ b/src/Oauth2/Resource/Database.php @@ -4,16 +4,16 @@ namespace Oauth2\Resource; interface Database { - /** - * Validate an access token and return the session details. - * - * Database query: - * - * - * SELECT id, owner_type, owner_id FROM oauth_sessions WHERE access_token = - * $accessToken AND stage = 'granted' AND - * access_token_expires > UNIX_TIMESTAMP(now()) - * + /** + * Validate an access token and return the session details. + * + * Database query: + * + * + * SELECT id, owner_type, owner_id FROM oauth_sessions WHERE access_token = + * $accessToken AND stage = 'granted' AND + * access_token_expires > UNIX_TIMESTAMP(now()) + * * * Response: * @@ -25,35 +25,35 @@ interface Database * [owner_id] => (string) The session owner's ID * ) * - * - * @param string $accessToken The access token - * @return array|bool Return an array on success or false on failure - */ - public function validateAccessToken($accessToken); + * + * @param string $accessToken The access token + * @return array|bool Return an array on success or false on failure + */ + public function validateAccessToken($accessToken); - /** - * Returns the scopes that the session is authorised with. - * - * Database query: - * - * - * SELECT scope FROM oauth_session_scopes WHERE access_token = - * '291dca1c74900f5f252de351e0105aa3fc91b90b' - * - * - * Response: - * - * - * Array - * ( - * [0] => (string) A scope - * [1] => (string) Another scope - * ... - * ) - * - * - * @param int $sessionId The session ID - * @return array A list of scopes - */ - public function sessionScopes($sessionId); + /** + * Returns the scopes that the session is authorised with. + * + * Database query: + * + * + * SELECT scope FROM oauth_session_scopes WHERE access_token = + * '291dca1c74900f5f252de351e0105aa3fc91b90b' + * + * + * Response: + * + * + * Array + * ( + * [0] => (string) A scope + * [1] => (string) Another scope + * ... + * ) + * + * + * @param int $sessionId The session ID + * @return array A list of scopes + */ + public function sessionScopes($sessionId); } \ No newline at end of file From ed3238b862b16e5d3b83a236581cd651530ccdd9 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Mon, 20 Aug 2012 14:19:33 +0100 Subject: [PATCH 05/12] Fixed constance letter casing --- src/Oauth2/Resource/Server.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Oauth2/Resource/Server.php b/src/Oauth2/Resource/Server.php index bd6a8984..89c2e0c6 100644 --- a/src/Oauth2/Resource/Server.php +++ b/src/Oauth2/Resource/Server.php @@ -13,7 +13,7 @@ class Server * The access token. * @access private */ - private $_accessToken = NULL; + private $_accessToken = null; /** * The scopes the access token has access to. @@ -25,13 +25,13 @@ class Server * The type of owner of the access token. * @access private */ - private $_type = NULL; + private $_type = null; /** * The ID of the owner of the access token. * @access private */ - private $_typeId = NULL; + private $_typeId = null; /** * Server configuration From 6fdb6177bcdc9cfd81a43217ebc07608f5b0010c Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Mon, 20 Aug 2012 15:09:33 +0100 Subject: [PATCH 06/12] Lots of fixes --- src/Oauth2/Resource/Server.php | 100 ++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/Oauth2/Resource/Server.php b/src/Oauth2/Resource/Server.php index 89c2e0c6..b9c6ca42 100644 --- a/src/Oauth2/Resource/Server.php +++ b/src/Oauth2/Resource/Server.php @@ -9,6 +9,12 @@ class OAuthResourceServerException extends \Exception class Server { + /** + * Reference to the database abstractor + * @var object + */ + private $_db = null; + /** * The access token. * @access private @@ -37,10 +43,22 @@ class Server * Server configuration * @var array */ - private $config = array( + private $_config = array( 'token_key' => 'oauth_token' ); + /** + * Error codes. + * + * To provide i8ln errors just overwrite the keys + * + * @var array + */ + public $errors = array( + 'missing_access_token' => 'An access token was not presented with the request', + 'invalid_access_token' => 'The access token is not registered with the resource server' + ); + /** * Constructor * @@ -56,21 +74,22 @@ class Server /** * Magic method to test if access token represents a particular owner type - * @param [type] $method [description] - * @param [type] $arguements [description] - * @return [type] [description] + * @param string $method The method name + * @param mixed $arguements The method arguements + * @return bool If method is valid, and access token is owned by the requested party then true, */ - public function __call($method, $arguements) + public function __call($method, $arguements = null) { - if (substr($method, 0, 2) === 'is') - { - if ($this->_type === strtolower(substr($method, 2))) - { + if (substr($method, 0, 2) === 'is') { + + if ($this->_type === strtolower(substr($method, 2))) { return $this->_typeId; } return false; } + + trigger_error('Call to undefined function ' . $method . '()'); } /** @@ -82,7 +101,7 @@ class Server */ public function registerDbAbstractor($db) { - $this->db = $db; + $this->_db = $db; } /** @@ -99,18 +118,18 @@ class Server switch ($server['REQUEST_METHOD']) { case 'POST': - $accessToken = isset($_POST[$this->config['token_key']]) ? $_POST[$this->config['token_key']] : null; + $accessToken = isset($_POST[$this->_config['token_key']]) ? $_POST[$this->_config['token_key']] : null; break; default: - $accessToken = isset($_GET[$this->config['token_key']]) ? $_GET[$this->config['token_key']] : null; + $accessToken = isset($_GET[$this->_config['token_key']]) ? $_GET[$this->_config['token_key']] : null; break; } // Try and get an access token from the auth header $headers = getallheaders(); - if (isset($headers['Authorization'])) - { + if (isset($headers['Authorization'])) { + $rawToken = trim(str_replace('Bearer', '', $headers['Authorization'])); if ( ! empty($rawToken)) { @@ -118,38 +137,29 @@ class Server } } - if ($accessToken) - { - $sessionQuery = $this->ci->db->get_where('oauth_sessions', array('access_token' => $accessToken, 'stage' => 'granted')); - - if ($session_query->num_rows() === 1) + if ($accessToken) { + + $result = $this->_dbCall('validateAccessToken', array($accessToken)); + + if ($result === false) { - $session = $session_query->row(); - $this->_accessToken = $session->access_token; - $this->_type = $session->type; - $this->_typeId = $session->type_id; - - $scopes_query = $this->ci->db->get_where('oauth_session_scopes', array('access_token' => $accessToken)); - if ($scopes_query->num_rows() > 0) - { - foreach ($scopes_query->result() as $scope) - { - $this->_scopes[] = $scope->scope; - } - } + throw new OAuthResourceServerException($this->errors['invalid_access_token']); } - + else { - $this->ci->output->set_status_header(403); - $this->ci->output->set_output('Invalid access token'); + $this->_accessToken = $accessToken; + $this->_type = $result['owner_type']; + $this->_typeId = $result['owner_id']; + + // Get the scopes + $this->_scopes = $this->_dbCall('sessionScopes', array($result['id'])); } - } - - else - { - $this->ci->output->set_status_header(403); - $this->ci->output->set_output('Missing access token'); + + } else { + + throw new OAuthResourceServerException($this->errors['missing_access_token']); + } } @@ -194,13 +204,13 @@ class Server * * @return mixed The query result */ - private function dbcall() + private function _dbCall() { - if ($this->db === null) { + if ($this->_db === null) { throw new OAuthResourceServerException('No registered database abstractor'); } - if ( ! $this->db instanceof Database) { + if ( ! $this->_db instanceof Database) { throw new OAuthResourceServerException('Registered database abstractor is not an instance of Oauth2\Resource\Database'); } @@ -209,6 +219,6 @@ class Server unset($args[0]); $params = array_values($args); - return call_user_func_array(array($this->db, $method), $args); + return call_user_func_array(array($this->_db, $method), $args); } } \ No newline at end of file From 326e96cc1787a724015d3f65750d923e80af964d Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Mon, 20 Aug 2012 15:49:57 +0100 Subject: [PATCH 07/12] Bug fix in dbcall --- src/Oauth2/Authentication/Server.php | 2 +- src/Oauth2/Resource/Server.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Oauth2/Authentication/Server.php b/src/Oauth2/Authentication/Server.php index 1e6ee1f5..733b51e3 100644 --- a/src/Oauth2/Authentication/Server.php +++ b/src/Oauth2/Authentication/Server.php @@ -513,6 +513,6 @@ class Server unset($args[0]); $params = array_values($args); - return call_user_func_array(array($this->db, $method), $args); + return call_user_func_array(array($this->db, $method), $params); } } diff --git a/src/Oauth2/Resource/Server.php b/src/Oauth2/Resource/Server.php index b9c6ca42..ab4626c3 100644 --- a/src/Oauth2/Resource/Server.php +++ b/src/Oauth2/Resource/Server.php @@ -219,6 +219,6 @@ class Server unset($args[0]); $params = array_values($args); - return call_user_func_array(array($this->_db, $method), $args); + return call_user_func_array(array($this->_db, $method), $params); } } \ No newline at end of file From 78424ce100dfe7c07c6d9c9951f642154ebbbce3 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 23 Aug 2012 12:21:59 +0100 Subject: [PATCH 08/12] Added resource server test suite --- build/phpunit.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build/phpunit.xml b/build/phpunit.xml index 4181c278..3a5b9f99 100644 --- a/build/phpunit.xml +++ b/build/phpunit.xml @@ -8,9 +8,12 @@ stopOnIncomplete="false" stopOnSkipped="false"> - + ../tests/authentication + + ../tests/resource + @@ -19,11 +22,8 @@ - + - + \ No newline at end of file From 66ee8df5b178b19fae1fc287463e415fec5faf78 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 23 Aug 2012 12:22:16 +0100 Subject: [PATCH 09/12] Added database mock for resource tests --- tests/resource/database_mock.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/resource/database_mock.php diff --git a/tests/resource/database_mock.php b/tests/resource/database_mock.php new file mode 100644 index 00000000..15f9dc28 --- /dev/null +++ b/tests/resource/database_mock.php @@ -0,0 +1,29 @@ + array( + 'id' => 1, + 'owner_type' => 'user', + 'owner_id' => 123 + )); + + private $sessionScopes = array( + 1 => array( + 'foo', + 'bar' + ) + ); + + public function validateAccessToken($accessToken) + { + return (isset($this->accessTokens[$accessToken])) ? $this->accessTokens[$accessToken] : false; + } + + public function sessionScopes($sessionId) + { + return (isset($this->sessionScopes[$sessionId])) ? $this->sessionScopes[$sessionId] : array(); + } +} \ No newline at end of file From 81a7322933001ee45d4525f301dabf5787a894c9 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 23 Aug 2012 12:22:39 +0100 Subject: [PATCH 10/12] Started resource server unit tests TODO: authentication header test --- tests/resource/server_test.php | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/resource/server_test.php diff --git a/tests/resource/server_test.php b/tests/resource/server_test.php new file mode 100644 index 00000000..1d91536e --- /dev/null +++ b/tests/resource/server_test.php @@ -0,0 +1,77 @@ +server = new Oauth2\Resource\Server(); + $this->db = new ResourceDB(); + + $this->server->registerDbAbstractor($this->db); + } + + function test_init_POST() + { + $_POST['oauth_token'] = 'test12345'; + + $this->server->init(); + + $this->assertEquals($this->server->_accessToken, $_POST['oauth_token']); + $this->assertEquals($this->server->_type, 'user'); + $this->assertEquals($this->server->_typeId, 123); + $this->assertEquals($this->server->_scopes, array('foo', 'bar')); + } + + function test_init_GET() + { + $_GET['oauth_token'] = 'test12345'; + + $this->server->init(); + + $this->assertEquals($this->server->_accessToken, $_GET['oauth_token']); + $this->assertEquals($this->server->_type, 'user'); + $this->assertEquals($this->server->_typeId, 123); + $this->assertEquals($this->server->_scopes, array('foo', 'bar')); + } + + function test_init_header() + { + // Test with authorisation header + } + + /** + * @exception OAuthResourceServerException + */ + function test_init_wrongToken() + { + $_POST['access_token'] = 'test12345'; + + $this->server->init(); + } + + function test_hasScope() + { + $_POST['oauth_token'] = 'test12345'; + + $this->server->init(); + + $this->assertEquals(true, $this->server->hasScope('foo')); + $this->assertEquals(true, $this->server->hasScope('bar')); + $this->assertEquals(true, $this->server->hasScope(array('foo', 'bar'))); + + $this->assertEquals(false, $this->server->hasScope('foobar')); + $this->assertEquals(false, $this->server->hasScope(array('foobar'))); + } + + function test___call() + { + $_POST['oauth_token'] = 'test12345'; + + $this->server->init(); + + $this->assertEquals(123, $this->server->isUser()); + $this->assertEquals(false, $this->server->isMachine()); + } + +} \ No newline at end of file From f53f6ca6097e08ae48df5ed20cad1903c8d57952 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 24 Aug 2012 12:19:31 +0100 Subject: [PATCH 11/12] Added .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..94d6d75a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +/composer.lock \ No newline at end of file From 2c3e8427027661f0c542eb589839b26bea9bdd85 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 24 Aug 2012 12:21:11 +0100 Subject: [PATCH 12/12] Updated .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 94d6d75a..de1a9ba2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor/ -/composer.lock \ No newline at end of file +/composer.lock +/docs/build/ \ No newline at end of file