Merge branch 'refs/heads/feature/resource' into develop

This commit is contained in:
Alex Bilbie 2012-08-24 12:21:30 +01:00
commit e8962f543d
7 changed files with 384 additions and 7 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/vendor/
/composer.lock
/docs/build/

View File

@ -8,9 +8,12 @@
stopOnIncomplete="false" stopOnIncomplete="false"
stopOnSkipped="false"> stopOnSkipped="false">
<testsuites> <testsuites>
<testsuite name="Test Suite"> <testsuite name="Authentication Server">
<directory suffix="test.php">../tests/authentication</directory> <directory suffix="test.php">../tests/authentication</directory>
</testsuite> </testsuite>
<testsuite name="Resource Server">
<directory suffix="test.php">../tests/resource</directory>
</testsuite>
</testsuites> </testsuites>
<filters> <filters>
<blacklist> <blacklist>
@ -19,11 +22,8 @@
</blacklist> </blacklist>
</filters> </filters>
<logging> <logging>
<log type="coverage-html" target="coverage" title="lncd/OAuth" <log type="coverage-html" target="coverage" title="lncd/OAuth" charset="UTF-8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70"/>
charset="UTF-8" yui="true" highlight="true"
lowUpperBound="35" highLowerBound="70"/>
<log type="coverage-clover" target="logs/clover.xml"/> <log type="coverage-clover" target="logs/clover.xml"/>
<log type="junit" target="logs/junit.xml" <log type="junit" target="logs/junit.xml" logIncompleteSkipped="false"/>
logIncompleteSkipped="false"/>
</logging> </logging>
</phpunit> </phpunit>

View File

@ -513,6 +513,6 @@ class Server
unset($args[0]); unset($args[0]);
$params = array_values($args); $params = array_values($args);
return call_user_func_array(array($this->db, $method), $args); return call_user_func_array(array($this->db, $method), $params);
} }
} }

View File

@ -4,4 +4,56 @@ namespace Oauth2\Resource;
interface Database interface Database
{ {
/**
* Validate an access token and return the session details.
*
* Database query:
*
* <code>
* SELECT id, owner_type, owner_id FROM oauth_sessions WHERE access_token =
* $accessToken AND stage = 'granted' AND
* access_token_expires > UNIX_TIMESTAMP(now())
* </code>
*
* Response:
*
* <code>
* Array
* (
* [id] => (int) The session ID
* [owner_type] => (string) The session owner type
* [owner_id] => (string) The session owner's ID
* )
* </code>
*
* @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:
*
* <code>
* SELECT scope FROM oauth_session_scopes WHERE access_token =
* '291dca1c74900f5f252de351e0105aa3fc91b90b'
* </code>
*
* Response:
*
* <code>
* Array
* (
* [0] => (string) A scope
* [1] => (string) Another scope
* ...
* )
* </code>
*
* @param int $sessionId The session ID
* @return array A list of scopes
*/
public function sessionScopes($sessionId);
} }

View File

@ -2,7 +2,223 @@
namespace Oauth2\Resource; namespace Oauth2\Resource;
class Server class OAuthResourceServerException extends \Exception
{ {
} }
class Server
{
/**
* Reference to the database abstractor
* @var object
*/
private $_db = null;
/**
* 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'
);
/**
* 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
*
* @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 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 = null)
{
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 . '()');
}
/**
* 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) {
$result = $this->_dbCall('validateAccessToken', array($accessToken));
if ($result === false)
{
throw new OAuthResourceServerException($this->errors['invalid_access_token']);
}
else
{
$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 {
throw new OAuthResourceServerException($this->errors['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), $params);
}
}

View File

@ -0,0 +1,29 @@
<?php
use Oauth2\Resource\Server;
class ResourceDB implements Database
{
private $accessTokens = array('test12345' => 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();
}
}

View File

@ -0,0 +1,77 @@
<?php
class Server_test extends PHPUnit_Framework_TestCase {
function setUp()
{
require_once('database_mock.php');
$this->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());
}
}