Merge branch 'release/0.1'

This commit is contained in:
Alex Bilbie 2012-08-27 15:52:20 +01:00
commit d7ae0f316e
19 changed files with 2214 additions and 4 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/vendor/
/composer.lock
/docs/build/
/build/logs/
/build/coverage/

View File

@ -1,7 +1,43 @@
# PHP OAuth server
# PHP OAuth Framework
The goal of this project is to develop a standards compliant [OAuth 2](http://tools.ietf.org/wg/oauth/draft-ietf-oauth-v2/) server that supports a number of different authentication flows, and two extensions, [JSON web tokens](http://tools.ietf.org/wg/oauth/draft-ietf-oauth-json-web-token/) and [SAML assertions](http://tools.ietf.org/wg/oauth/draft-ietf-oauth-saml2-bearer/).
The goal of this project is to develop a standards compliant [OAuth 2](http://tools.ietf.org/wg/oauth/draft-ietf-oauth-v2/) authentication server, resource server and client library with support for a major OAuth 2 providers.
The library will be a [composer](http://getcomposer.org/) package and will be framework agnostic.
## Package Installation
This code will be developed as part of the [Linkey](http://linkey.blogs.lincoln.ac.uk) project which has been funded by [JISC](http://jisc.ac.uk) under the access and identity management programme.
The framework is provided as a Composer package which can be installed by adding the package to your composer.json file:
```javascript
{
"require": {
"lncd\Oauth2": "*"
}
}
```
## Package Integration
Check out the [wiki](https://github.com/lncd/OAuth2/wiki)
## Current Features
### Authentication Server
The authentication server is a flexible class that supports the standard authorization code grant.
### Resource Server
The resource server allows you to secure your API endpoints by checking for a valid OAuth access token in the request and ensuring the token has the correct permission to access resources.
## Future Goals
### Authentication Server
* Support for [JSON web tokens](http://tools.ietf.org/wg/oauth/draft-ietf-oauth-json-web-token/).
* Support for [SAML assertions](http://tools.ietf.org/wg/oauth/draft-ietf-oauth-saml2-bearer/).
---
This code will be developed as part of the [Linkey](http://linkey.blogs.lincoln.ac.uk) project which has been funded by [JISC](http://jisc.ac.uk) under the Access and Identity Management programme.

142
build.xml Normal file
View File

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="PHP OAuth 2.0 Server" default="build">
<target name="build" depends="prepare,lint,phploc,pdepend,phpmd-ci,phpcs-ci,phpcpd,composer,phpunit,phpdox,phpcb"/>
<target name="build-parallel" depends="prepare,lint,tools-parallel,phpcb"/>
<target name="minimal" depends="prepare,lint,phploc,pdepend,phpcpd,composer,phpunit,phpdox,phpcb" />
<target name="tools-parallel" description="Run tools in parallel">
<parallel threadCount="2">
<sequential>
<antcall target="pdepend"/>
<antcall target="phpmd-ci"/>
</sequential>
<antcall target="phpcpd"/>
<antcall target="phpcs-ci"/>
<antcall target="phploc"/>
<antcall target="phpdox"/>
</parallel>
</target>
<target name="clean" description="Cleanup build artifacts">
<delete dir="${basedir}/build/api"/>
<delete dir="${basedir}/build/code-browser"/>
<delete dir="${basedir}/build/coverage"/>
<delete dir="${basedir}/build/logs"/>
<delete dir="${basedir}/build/pdepend"/>
</target>
<target name="prepare" depends="clean" description="Prepare for build">
<mkdir dir="${basedir}/build/api"/>
<mkdir dir="${basedir}/build/code-browser"/>
<mkdir dir="${basedir}/build/coverage"/>
<mkdir dir="${basedir}/build/logs"/>
<mkdir dir="${basedir}/build/pdepend"/>
<mkdir dir="${basedir}/build/phpdox"/>
</target>
<target name="lint">
<apply executable="php" failonerror="true">
<arg value="-l" />
<fileset dir="${basedir}/src">
<include name="**/*.php" />
<modified />
</fileset>
</apply>
</target>
<target name="phploc" description="Measure project size using PHPLOC">
<exec executable="phploc">
<arg value="--log-csv" />
<arg value="${basedir}/build/logs/phploc.csv" />
<arg path="${basedir}/src" />
</exec>
</target>
<target name="pdepend" description="Calculate software metrics using PHP_Depend">
<exec executable="pdepend">
<arg value="--jdepend-xml=${basedir}/build/logs/jdepend.xml" />
<arg value="--jdepend-chart=${basedir}/build/pdepend/dependencies.svg" />
<arg value="--overview-pyramid=${basedir}/build/pdepend/overview-pyramid.svg" />
<arg path="${basedir}/src" />
</exec>
</target>
<target name="phpmd" description="Perform project mess detection using PHPMD and print human readable output. Intended for usage on the command line before committing.">
<exec executable="phpmd">
<arg path="${basedir}/src" />
<arg value="text" />
<arg value="${basedir}/build/phpmd.xml" />
</exec>
</target>
<target name="phpmd-ci" description="Perform project mess detection using PHPMD creating a log file for the continuous integration server">
<exec executable="phpmd">
<arg path="${basedir}/src" />
<arg value="xml" />
<arg value="${basedir}/build/phpmd.xml" />
<arg value="--reportfile" />
<arg value="${basedir}/build/logs/pmd.xml" />
</exec>
</target>
<target name="phpcs" description="Find coding standard violations using PHP_CodeSniffer and print human readable output. Intended for usage on the command line before committing.">
<exec executable="phpcs">
<arg value="--standard=${basedir}/build/phpcs.xml" />
<arg value="--extensions=php" />
<arg value="--ignore=third_party/CIUnit" />
<arg path="${basedir}/src" />
</exec>
</target>
<target name="phpcs-ci" description="Find coding standard violations using PHP_CodeSniffer creating a log file for the continuous integration server">
<exec executable="phpcs" output="/dev/null">
<arg value="--report=checkstyle" />
<arg value="--report-file=${basedir}/build/logs/checkstyle.xml" />
<arg value="--standard=${basedir}/build/phpcs.xml" />
<arg value="--extensions=php" />
<arg value="--ignore=third_party/CIUnit" />
<arg path="${basedir}/src" />
</exec>
</target>
<target name="phpcpd" description="Find duplicate code using PHPCPD">
<exec executable="phpcpd">
<arg value="--log-pmd" />
<arg value="${basedir}/build/logs/pmd-cpd.xml" />
<arg path="${basedir}/src" />
</exec>
</target>
<target name="composer" description="Install Composer requirements">
<exec executable="composer.phar" failonerror="true">
<arg value="install" />
<arg value="--dev" />
</exec>
</target>
<target name="phpunit" description="Run unit tests with PHPUnit">
<exec executable="${basedir}/vendor/bin/phpunit" failonerror="true">
<arg value="--configuration" />
<arg value="${basedir}/build/phpunit.xml" />
</exec>
</target>
<target name="phpdox" description="Generate API documentation using phpDox">
<exec executable="phpdox"/>
</target>
<target name="phpcb" description="Aggregate tool output with PHP_CodeBrowser">
<exec executable="phpcb">
<arg value="--log" />
<arg path="${basedir}/build/logs" />
<arg value="--source" />
<arg path="${basedir}/src" />
<arg value="--output" />
<arg path="${basedir}/build/code-browser" />
</exec>
</target>
</project>

8
build/phpcs.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<ruleset name="PHP_CodeSniffer">
<description>PHP_CodeSniffer configuration</description>
<rule ref="PSR2"/>
</ruleset>

14
build/phpmd.xml Normal file
View File

@ -0,0 +1,14 @@
<ruleset name="OAuth 2.0 Server"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>
Ruleset for OAuth 2.0 server
</description>
<!-- Import the entire unused code rule set -->
<rule ref="rulesets/unusedcode.xml" />
</ruleset>

23
build/phpunit.xml Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false">
<testsuites>
<testsuite name="Authentication Server">
<directory suffix="test.php">../tests/authentication</directory>
</testsuite>
<testsuite name="Resource Server">
<directory suffix="test.php">../tests/resource</directory>
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory suffix=".php">PEAR_INSTALL_DIR</directory>
<directory suffix=".php">PHP_LIBDIR</directory>
<directory suffix=".php">../vendor/composer</directory>
</blacklist>
</filter>
<logging>
<log type="coverage-html" target="coverage" title="lncd/OAuth" charset="UTF-8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70"/>
<log type="coverage-clover" target="logs/clover.xml"/>
<log type="junit" target="logs/junit.xml" logIncompleteSkipped="false"/>
</logging>
</phpunit>

40
composer.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "lncd/Oauth2",
"description": "OAuth 2.0 Framework",
"version": "0.1",
"homepage": "https://github.com/lncd/OAuth2",
"license": "MIT",
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"EHER/PHPUnit": "*"
},
"repositories": [
{
"type": "git",
"url": "https://github.com/lncd/OAuth2"
}
],
"keywords": [
"oauth",
"oauth2",
"server",
"authorization",
"authentication",
"resource"
],
"authors": [
{
"name": "Alex Bilbie",
"email": "oauth2server@alexbilbie.com",
"homepage": "http://www.httpster.org",
"role": "Developer"
}
],
"autoload": {
"psr-0": {
"Oauth2": "src/"
}
}
}

20
license.txt Normal file
View File

@ -0,0 +1,20 @@
MIT License
Copyright (C) 2012 University of Lincoln
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,320 @@
<?php
namespace Oauth2\Authentication;
interface Database
{
/**
* Validate a client
*
* Database query:
*
* <code>
* # Client ID + redirect URI
* SELECT clients.id FROM clients LEFT JOIN client_endpoints ON
* client_endpoints.client_id = clients.id WHERE clients.id = $clientId AND
* client_endpoints.redirect_uri = $redirectUri
*
* # Client ID + client secret
* SELECT clients.id FROM clients WHERE clients.id = $clientId AND
* clients.secret = $clientSecret
*
* # Client ID + client secret + redirect URI
* SELECT clients.id FROM clients LEFT JOIN client_endpoints ON
* client_endpoints.client_id = clients.id WHERE clients.id = $clientId AND
* clients.secret = $clientSecret AND client_endpoints.redirect_uri =
* $redirectUri
* </code>
*
* @param string $clientId The client's ID
* @param string $clientSecret The client's secret (default = "null")
* @param string $redirectUri The client's redirect URI (default = "null")
* @return [type] [description]
*/
public function validateClient(
$clientId,
$clientSecret = null,
$redirectUri = null
);
/**
* Create a new OAuth session
*
* Database query:
*
* <code>
* INSERT INTO oauth_sessions (client_id, redirect_uri, owner_type,
* owner_id, auth_code, access_token, stage, first_requested, last_updated)
* VALUES ($clientId, $redirectUri, $type, $typeId, $authCode,
* $accessToken, $stage, UNIX_TIMESTAMP(NOW()), UNIX_TIMESTAMP(NOW()))
* </code>
*
* @param string $clientId The client ID
* @param string $redirectUri The redirect URI
* @param string $type The session owner's type (default = "user")
* @param string $typeId The session owner's ID (default = "null")
* @param string $authCode The authorisation code (default = "null")
* @param string $accessToken The access token (default = "null")
* @param string $stage The stage of the session (default ="request")
* @return [type] [description]
*/
public function newSession(
$clientId,
$redirectUri,
$type = 'user',
$typeId = null,
$authCode = null,
$accessToken = null,
$accessTokenExpire = null,
$stage = 'requested'
);
/**
* Update an OAuth session
*
* Database query:
*
* <code>
* UPDATE oauth_sessions SET auth_code = $authCode, access_token =
* $accessToken, stage = $stage, last_updated = UNIX_TIMESTAMP(NOW()) WHERE
* id = $sessionId
* </code>
*
* @param string $sessionId The session ID
* @param string $authCode The authorisation code (default = "null")
* @param string $accessToken The access token (default = "null")
* @param string $stage The stage of the session (default ="request")
* @return void
*/
public function updateSession(
$sessionId,
$authCode = null,
$accessToken = null,
$accessTokenExpire = null,
$stage = 'requested'
);
/**
* Delete an OAuth session
*
* <code>
* DELETE FROM oauth_sessions WHERE client_id = $clientId AND owner_type =
* $type AND owner_id = $typeId
* </code>
*
* @param string $clientId The client ID
* @param string $type The session owner's type
* @param string $typeId The session owner's ID
* @return [type] [description]
*/
public function deleteSession(
$clientId,
$type,
$typeId
);
/**
* Validate that an authorisation code is valid
*
* Database query:
*
* <code>
* SELECT id FROM oauth_sessions WHERE client_id = $clientID AND
* redirect_uri = $redirectUri AND auth_code = $authCode
* </code>
*
* Response:
*
* <code>
* Array
* (
* [id] => (int) The session ID
* [client_id] => (string) The client ID
* [redirect_uri] => (string) The redirect URI
* [owner_type] => (string) The session owner type
* [owner_id] => (string) The session owner's ID
* [auth_code] => (string) The authorisation code
* [stage] => (string) The session's stage
* [first_requested] => (int) Unix timestamp of the time the session was
* first generated
* [last_updated] => (int) Unix timestamp of the time the session was
* last updated
* )
* </code>
*
* @param string $clientId The client ID
* @param string $redirectUri The redirect URI
* @param string $authCode The authorisation code
* @return int|bool Returns the session ID if the auth code
* is valid otherwise returns false
*/
public function validateAuthCode(
$clientId,
$redirectUri,
$authCode
);
/**
* Return the session ID for a given session owner and client combination
*
* Database query:
*
* <code>
* SELECT id FROM oauth_sessions WHERE client_id = $clientId
* AND owner_type = $type AND owner_id = $typeId
* </code>
*
* @param string $type The session owner's type
* @param string $typeId The session owner's ID
* @param string $clientId The client ID
* @return string|null Return the session ID as an integer if
* found otherwise returns false
*/
public function hasSession(
$type,
$typeId,
$clientId
);
/**
* Return the access token for a given session
*
* Database query:
*
* <code>
* SELECT access_token FROM oauth_sessions WHERE id = $sessionId
* </code>
*
* @param int $sessionId The OAuth session ID
* @return string|null Returns the access token as a string if
* found otherwise returns null
*/
public function getAccessToken($sessionId);
/**
* Removes an authorisation code associated with a session
*
* Database query:
*
* <code>
* UPDATE oauth_sessions SET auth_code = NULL WHERE id = $sessionId
* </code>
*
* @param int $sessionId The OAuth session ID
* @return void
*/
public function removeAuthCode($sessionId);
/**
* Sets a sessions access token
*
* Database query:
*
* <code>
* UPDATE oauth_sessions SET access_token = $accessToken WHERE id =
* $sessionId
* </code>
*
* @param int $sessionId The OAuth session ID
* @param string $accessToken The access token
* @return void
*/
public function setAccessToken(
$sessionId,
$accessToken
);
/**
* Associates a session with a scope
*
* Database query:
*
* <code>
* INSERT INTO oauth_session_scopes (session_id, scope) VALUE ($sessionId,
* $scope)
* </code>
*
* @param int $sessionId The session ID
* @param string $scope The scope
* @return void
*/
public function addSessionScope(
$sessionId,
$scope
);
/**
* Return information about a scope
*
* Database query:
*
* <code>
* SELECT * FROM scopes WHERE scope = $scope
* </code>
*
* Response:
*
* <code>
* Array
* (
* [id] => (int) The scope's ID
* [scope] => (string) The scope itself
* [name] => (string) The scope's name
* [description] => (string) The scope's description
* )
* </code>
*
* @param string $scope The scope
* @return array
*/
public function getScope($scope);
/**
* Associate a session's scopes with an access token
*
* Database query:
*
* <code>
* UPDATE oauth_session_scopes SET access_token = $accessToken WHERE
* session_id = $sessionId
* </code>
*
* @param int $sessionId The session ID
* @param string $accessToken The access token
* @return void
*/
public function updateSessionScopeAccessToken(
$sessionId,
$accessToken
);
/**
* Return the scopes associated with an access token
*
* Database query:
*
* <code>
* SELECT scopes.scope, scopes.name, scopes.description FROM
* oauth_session_scopes JOIN scopes ON oauth_session_scopes.scope =
* scopes.scope WHERE access_token = $accessToken
* </code>
*
* Response:
*
* <code>
* Array
* (
* [0] => Array
* (
* [scope] => (string) The scope
* [name] => (string) The scope's name
* [description] => (string) The scope's description
* )
* )
* </code>
*
* @param string $accessToken The access token
* @return array
*/
public function accessTokenScopes($accessToken);
}

View File

@ -0,0 +1,517 @@
<?php
namespace Oauth2\Authentication;
class OAuthServerClientException extends \Exception
{
}
class OAuthServerUserException extends \Exception
{
}
class OAuthServerException extends \Exception
{
}
class Server
{
/**
* Reference to the database abstractor
* @var object
*/
private $db = null;
/**
* Server configuration
* @var array
*/
private $config = array(
'scope_delimeter' => ',',
'access_token_ttl' => null
);
/**
* Supported response types
* @var array
*/
private $response_types = array(
'code'
);
/**
* Supported grant types
* @var array
*/
private $grant_types = array(
'authorization_code'
);
/**
* Exception error codes
* @var array
*/
public $exceptionCodes = array(
0 => 'invalid_request',
1 => 'unauthorized_client',
2 => 'access_denied',
3 => 'unsupported_response_type',
4 => 'invalid_scope',
5 => 'server_error',
6 => 'temporarily_unavailable',
7 => 'unsupported_grant_type',
8 => 'invalid_client',
9 => 'invalid_grant'
);
/**
* Error codes.
*
* To provide i8ln errors just overwrite the keys
*
* @var array
*/
public $errors = array(
'invalid_request' => 'The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Check the "%s" parameter.',
'unauthorized_client' => 'The client is not authorized to request an access token using this method.',
'access_denied' => 'The resource owner or authorization server denied the request.',
'unsupported_response_type' => 'The authorization server does not support obtaining an access token using this method.',
'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',
'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.'
);
/**
* Constructor
*
* @access public
* @param array $options Optional list of options to overwrite the defaults
* @return void
*/
public function __construct($options = null)
{
if ($options !== null) {
$this->options = array_merge($this->config, $options);
}
}
/**
* Register a database abstrator class
*
* @access public
* @param object $db A class that implements OAuth2ServerDatabase
* @return void
*/
public function registerDbAbstractor($db)
{
$this->db = $db;
}
/**
* Check client authorise parameters
*
* @access public
* @param array $authParams Optional array of parsed $_GET keys
* @return array Authorise request parameters
*/
public function checkClientAuthoriseParams($authParams = null)
{
$params = array();
// Client ID
if ( ! isset($authParams['client_id']) && ! isset($_GET['client_id'])) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_request'], 'client_id'), 0);
} else {
$params['client_id'] = (isset($authParams['client_id'])) ? $authParams['client_id'] : $_GET['client_id'];
}
// Redirect URI
if ( ! isset($authParams['redirect_uri']) && ! isset($_GET['redirect_uri'])) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_request'], 'redirect_uri'), 0);
} else {
$params['redirect_uri'] = (isset($authParams['redirect_uri'])) ? $authParams['redirect_uri'] : $_GET['redirect_uri'];
}
// Validate client ID and redirect URI
$clientDetails = $this->dbcall('validateClient', $params['client_id'], null, $params['redirect_uri']);
if ($clientDetails === false) {
throw new OAuthServerClientException($this->errors['invalid_client'], 8);
}
// Response type
if ( ! isset($authParams['response_type']) && ! isset($_GET['response_type'])) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_request'], 'response_type'), 0);
} else {
$params['response_type'] = (isset($authParams['response_type'])) ? $authParams['response_type'] : $_GET['response_type'];
// Ensure response type is one that is recognised
if ( ! in_array($params['response_type'], $this->response_types)) {
throw new OAuthServerClientException($this->errors['unsupported_response_type'], 3);
}
}
// Get and validate scopes
if (isset($authParams['scope']) || isset($_GET['scope'])) {
$scopes = (isset($_GET['scope'])) ? $_GET['scope'] : $authParams['scope'];
$scopes = explode($this->config['scope_delimeter'], $scopes);
// Remove any junk scopes
for ($i = 0; $i < count($scopes); $i++) {
$scopes[$i] = trim($scopes[$i]);
if ($scopes[$i] === '') {
unset($scopes[$i]);
}
}
if (count($scopes) === 0) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_request'], 'scope'), 0);
}
$params['scopes'] = array();
foreach ($scopes as $scope) {
$scopeDetails = $this->dbcall('getScope', $scope);
if ($scopeDetails === false) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_scope'], $scope), 4);
}
$params['scopes'][] = $scopeDetails;
}
}
return $params;
}
/**
* Parse a new authorise request
*
* @param string $type The session owner's type
* @param string $typeId The session owner's ID
* @param array $authoriseParams The authorise request $_GET parameters
* @return string An authorisation code
*/
public function newAuthoriseRequest($type, $typeId, $authoriseParams)
{
// Remove any old sessions the user might have
$this->dbcall('deleteSession',
$authoriseParams['client_id'],
$type,
$typeId
);
// Create the new auth code
$authCode = $this->newAuthCode(
$authoriseParams['client_id'],
'user',
$typeId,
$authoriseParams['redirect_uri'],
$authoriseParams['scopes']
);
return $authCode;
}
/**
* Generate a unique code
*
* Generate a unique code for an authorisation code, or token
*
* @return string A unique code
*/
private function generateCode()
{
return sha1(uniqid(microtime()));
}
/**
* Create a new authorisation code
*
* @param string $clientId The client ID
* @param string $type The type of the owner of the session
* @param string $typeId The session owner's ID
* @param string $redirectUri The redirect URI
* @param array $scopes The requested scopes
* @param string $accessToken The access token (default = null)
* @return string An authorisation code
*/
private function newAuthCode($clientId, $type, $typeId, $redirectUri, $scopes = array(), $accessToken = null)
{
$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) {
$this->dbcall('updateSession',
$clientId,
$type,
$typeId,
$authCode,
$accessToken,
'request'
);
} 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,
'request'
);
// Add the scopes
foreach ($scopes as $key => $scope) {
$this->dbcall('addSessionScope', $sessionId, $scope['scope']);
}
}
return $authCode;
}
/**
* Issue an access token
*
* @access public
*
* @param array $authParams Optional array of parsed $_POST keys
*
* @return array Authorise request parameters
*/
public function issueAccessToken($authParams = null)
{
$params = array();
if ( ! isset($authParams['grant_type']) && ! isset($_POST['grant_type'])) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_request'], 'grant_type'), 0);
} else {
$params['grant_type'] = (isset($authParams['grant_type'])) ? $authParams['grant_type'] : $_POST['grant_type'];
// Ensure grant type is one that is recognised
if ( ! in_array($params['grant_type'], $this->grant_types)) {
throw new OAuthServerClientException($this->errors['unsupported_grant_type'], 7);
}
}
switch ($params['grant_type'])
{
case 'authorization_code': // Authorization code grant
return $this->completeAuthCodeGrant($authParams, $params);
break;
case 'refresh_token': // Refresh token
case 'password': // Resource owner password credentials grant
case 'client_credentials': // Client credentials grant
default: // Unsupported
throw new OAuthServerException($this->errors['server_error'] . 'Tried to process an unsuppported grant type.', 5);
break;
}
}
/**
* Complete the authorisation code 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 completeAuthCodeGrant($authParams = array(), $params = array())
{
// Client ID
if ( ! isset($authParams['client_id']) && ! isset($_POST['client_id'])) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_request'], 'client_id'), 0);
} else {
$params['client_id'] = (isset($authParams['client_id'])) ? $authParams['client_id'] : $_POST['client_id'];
}
// Client secret
if ( ! isset($authParams['client_secret']) && ! isset($_POST['client_secret'])) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_request'], 'client_secret'), 0);
} else {
$params['client_secret'] = (isset($authParams['client_secret'])) ? $authParams['client_secret'] : $_POST['client_secret'];
}
// Redirect URI
if ( ! isset($authParams['redirect_uri']) && ! isset($_POST['redirect_uri'])) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_request'], 'redirect_uri'), 0);
} else {
$params['redirect_uri'] = (isset($authParams['redirect_uri'])) ? $authParams['redirect_uri'] : $_POST['redirect_uri'];
}
// Validate client ID and redirect URI
$clientDetails = $this->dbcall('validateClient',
$params['client_id'],
$params['client_secret'],
$params['redirect_uri']
);
if ($clientDetails === false) {
throw new OAuthServerClientException($this->errors['invalid_client'], 8);
}
// The authorization code
if ( ! isset($authParams['code']) && ! isset($_POST['code'])) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_request'], 'code'), 0);
} else {
$params['code'] = (isset($authParams['code'])) ? $authParams['code'] : $_POST['code'];
}
// Verify the authorization code matches the client_id and the
// request_uri
$session = $this->dbcall('validateAuthCode',
$params['client_id'],
$params['redirect_uri'],
$params['code']
);
if ( ! $session) {
throw new OAuthServerClientException(sprintf($this->errors['invalid_grant'], 'code'), 9);
} else {
// A session ID was returned so update it with an access token,
// remove the authorisation code, change the stage to 'granted'
$accessToken = $this->generateCode();
$accessTokenExpires = ($this->config['access_token_ttl'] === null) ? null : time() + $this->config['access_token_ttl'];
$this->dbcall('updateSession',
$session['id'],
null,
$accessToken,
$accessTokenExpires,
'granted'
);
// Update the session's scopes to reference the access token
$this->dbcall('updateSessionScopeAccessToken',
$session['id'],
$accessToken
);
return array(
'access_token' => $accessToken,
'token_type' => 'bearer',
'expires_in' => $this->config['access_token_ttl']
);
}
}
/**
* Generates the redirect uri with appended params
*
* @param string $redirectUri The redirect URI
* @param array $params The parameters to be appended to the URL
* @param string $query_delimeter The query string delimiter (default: ?)
*
* @return string The updated redirect URI
*/
public function redirectUri($redirectUri, $params = array(), $queryDelimeter = '?')
{
if (strstr($redirectUri, $queryDelimeter)) {
$redirectUri = $redirectUri . '&' . http_build_query($params);
} else {
$redirectUri = $redirectUri . $queryDelimeter . http_build_query($params);
}
return $redirectUri;
}
/**
* Call database methods from the abstractor
*
* @return mixed The query result
*/
private function dbcall()
{
if ($this->db === null) {
throw new OAuthServerException('No registered database abstractor');
}
if ( ! $this->db instanceof Database) {
throw new OAuthServerException('Registered database abstractor is not an instance of Oauth2\Authentication\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,59 @@
<?php
namespace Oauth2\Resource;
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

@ -0,0 +1,225 @@
<?php
namespace Oauth2\Resource;
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
if (function_exists('getallheaders')) {
$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', $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', $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);
}
}

59
src/sql/database.sql Normal file
View File

@ -0,0 +1,59 @@
-- Create syntax for TABLE 'clients'
CREATE TABLE `clients` (
`id` varchar(40) NOT NULL DEFAULT '',
`secret` varchar(40) NOT NULL DEFAULT '',
`name` varchar(255) NOT NULL DEFAULT '',
`auto_approve` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Create syntax for TABLE 'client_endpoints'
CREATE TABLE `client_endpoints` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`client_id` varchar(40) NOT NULL DEFAULT '',
`redirect_uri` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `client_id` (`client_id`),
CONSTRAINT `client_endpoints_ibfk_1` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Create syntax for TABLE 'oauth_sessions'
CREATE TABLE `oauth_sessions` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`client_id` varchar(32) NOT NULL DEFAULT '',
`redirect_uri` varchar(250) NOT NULL DEFAULT '',
`owner_type` enum('user','client') NOT NULL DEFAULT 'user',
`owner_id` varchar(255) DEFAULT NULL,
`auth_code` varchar(40) DEFAULT '',
`access_token` varchar(40) DEFAULT '',
`access_token_expires` int(10) DEFAULT NULL,
`stage` enum('requested','granted') NOT NULL DEFAULT 'requested',
`first_requested` int(10) unsigned NOT NULL,
`last_updated` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `client_id` (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Create syntax for TABLE 'scopes'
CREATE TABLE `scopes` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`scope` varchar(255) NOT NULL DEFAULT '',
`name` varchar(255) NOT NULL DEFAULT '',
`description` varchar(255) DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `scope` (`scope`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Create syntax for TABLE 'oauth_session_scopes'
CREATE TABLE `oauth_session_scopes` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`session_id` int(11) unsigned NOT NULL,
`access_token` varchar(40) NOT NULL DEFAULT '',
`scope` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `session_id` (`session_id`),
KEY `access_token` (`access_token`),
KEY `scope` (`scope`),
CONSTRAINT `oauth_session_scopes_ibfk_3` FOREIGN KEY (`scope`) REFERENCES `scopes` (`scope`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `oauth_session_scopes_ibfk_4` FOREIGN KEY (`session_id`) REFERENCES `oauth_sessions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

0
src/sql/index.html Normal file
View File

1
test Executable file
View File

@ -0,0 +1 @@
vendor/bin/phpunit --coverage-text --configuration build/phpunit.xml

View File

@ -0,0 +1,191 @@
<?php
use Oauth2\Authentication\Database;
class OAuthdb implements Database
{
private $sessions = array();
private $sessions_client_type_id = array();
private $sessions_code = array();
private $session_scopes = array();
private $clients = array(0 => array(
'client_id' => 'test',
'client_secret' => 'test',
'redirect_uri' => 'http://example.com/test',
'name' => 'Test Client'
));
private $scopes = array('test' => array(
'id' => 1,
'scope' => 'test',
'name' => 'test',
'description' => 'test'
));
public function validateClient(
$clientId,
$clientSecret = null,
$redirectUri = null
)
{
if ($clientId !== $this->clients[0]['client_id'])
{
return false;
}
if ($clientSecret !== null && $clientSecret !== $this->clients[0]['client_secret'])
{
return false;
}
if ($redirectUri !== null && $redirectUri !== $this->clients[0]['redirect_uri'])
{
return false;
}
return $this->clients[0];
}
public function newSession(
$clientId,
$redirectUri,
$type = 'user',
$typeId = null,
$authCode = null,
$accessToken = null,
$accessTokenExpire = null,
$stage = 'requested'
)
{
$id = count($this->sessions);
$this->sessions[$id] = array(
'id' => $id,
'client_id' => $clientId,
'redirect_uri' => $redirectUri,
'owner_type' => $type,
'owner_id' => $typeId,
'auth_code' => $authCode,
'access_token' => $accessToken,
'access_token_expire' => $accessTokenExpire,
'stage' => $stage
);
$this->sessions_client_type_id[$clientId . ':' . $type . ':' . $typeId] = $id;
$this->sessions_code[$clientId . ':' . $redirectUri . ':' . $authCode] = $id;
return true;
}
public function updateSession(
$sessionId,
$authCode = null,
$accessToken = null,
$accessTokenExpire = null,
$stage = 'requested'
)
{
$this->sessions[$sessionId]['auth_code'] = $authCode;
$this->sessions[$sessionId]['access_token'] = $accessToken;
$this->sessions[$sessionId]['access_token_expire'] = $accessTokenExpire;
$this->sessions[$sessionId]['stage'] = $stage;
return true;
}
public function deleteSession(
$clientId,
$type,
$typeId
)
{
$key = $clientId . ':' . $type . ':' . $typeId;
if (isset($this->sessions_client_type_id[$key]))
{
unset($this->sessions[$this->sessions_client_type_id[$key]]);
}
return true;
}
public function validateAuthCode(
$clientId,
$redirectUri,
$authCode
)
{
$key = $clientId . ':' . $redirectUri . ':' . $authCode;
if (isset($this->sessions_code[$key]))
{
return $this->sessions[$this->sessions_code[$key]];
}
return false;
}
public function hasSession(
$type,
$typeId,
$clientId
)
{
die('not implemented hasSession');
}
public function getAccessToken($sessionId)
{
die('not implemented getAccessToken');
}
public function removeAuthCode($sessionId)
{
die('not implemented removeAuthCode');
}
public function setAccessToken(
$sessionId,
$accessToken
)
{
die('not implemented setAccessToken');
}
public function addSessionScope(
$sessionId,
$scope
)
{
if ( ! isset($this->session_scopes[$sessionId]))
{
$this->session_scopes[$sessionId] = array();
}
$this->session_scopes[$sessionId][] = $scope;
return true;
}
public function getScope($scope)
{
if ( ! isset($this->scopes[$scope]))
{
return false;
}
return $this->scopes[$scope];
}
public function updateSessionScopeAccessToken(
$sessionId,
$accessToken
)
{
return true;
}
public function accessTokenScopes($accessToken)
{
die('not implemented accessTokenScopes');
}
}

View File

@ -0,0 +1,398 @@
<?php
class Authentication_Server_test extends PHPUnit_Framework_TestCase {
function setUp()
{
$this->oauth = new Oauth2\Authentication\Server();
require_once('database_mock.php');
$this->oauthdb = new OAuthdb();
$this->assertInstanceOf('Oauth2\Authentication\Database', $this->oauthdb);
$this->oauth->registerDbAbstractor($this->oauthdb);
}
function test_generateCode()
{
$reflector = new ReflectionClass($this->oauth);
$method = $reflector->getMethod('generateCode');
$method->setAccessible(true);
$result = $method->invoke($this->oauth);
$result2 = $method->invoke($this->oauth);
$this->assertEquals(40, strlen($result));
$this->assertNotEquals($result, $result2);
}
function test_redirectUri()
{
$result1 = $this->oauth->redirectUri('http://example.com/foo');
$result2 = $this->oauth->redirectUri('http://example.com/foo', array('foo' => 'bar'));
$result3 = $this->oauth->redirectUri('http://example.com/foo', array('foo' => 'bar'), '#');
$this->assertEquals('http://example.com/foo?', $result1);
$this->assertEquals('http://example.com/foo?foo=bar', $result2);
$this->assertEquals('http://example.com/foo#foo=bar', $result3);
}
function test_checkClientAuthoriseParams_GET()
{
$_GET['client_id'] = 'test';
$_GET['redirect_uri'] = 'http://example.com/test';
$_GET['response_type'] = 'code';
$_GET['scope'] = 'test';
$expect = array(
'client_id' => 'test',
'redirect_uri' => 'http://example.com/test',
'response_type' => 'code',
'scopes' => array(
0 => array(
'id' => 1,
'scope' => 'test',
'name' => 'test',
'description' => 'test'
)
)
);
$result = $this->oauth->checkClientAuthoriseParams();
$this->assertEquals($expect, $result);
}
function test_checkClientAuthoriseParams_PassedParams()
{
unset($_GET['client_id']);
unset($_GET['redirect_uri']);
unset($_GET['response_type']);
unset($_GET['scope']);
$params = array(
'client_id' => 'test',
'redirect_uri' => 'http://example.com/test',
'response_type' => 'code',
'scope' => 'test'
);
$this->assertEquals(array(
'client_id' => 'test',
'redirect_uri' => 'http://example.com/test',
'response_type' => 'code',
'scopes' => array(0 => array(
'id' => 1,
'scope' => 'test',
'name' => 'test',
'description' => 'test'
))
), $this->oauth->checkClientAuthoriseParams($params));
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 0
*/
function test_checkClientAuthoriseParams_missingClientId()
{
$this->oauth->checkClientAuthoriseParams();
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 0
*/
function test_checkClientAuthoriseParams_missingRedirectUri()
{
$_GET['client_id'] = 'test';
$this->oauth->checkClientAuthoriseParams();
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 0
*/
function test_checkClientAuthoriseParams_missingResponseType()
{
$_GET['client_id'] = 'test';
$_GET['redirect_uri'] = 'http://example.com/test';
$this->oauth->checkClientAuthoriseParams();
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 0
*/
function test_checkClientAuthoriseParams_missingScopes()
{
$_GET['client_id'] = 'test';
$_GET['redirect_uri'] = 'http://example.com/test';
$_GET['response_type'] = 'code';
$_GET['scope'] = ' ';
$this->oauth->checkClientAuthoriseParams();
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 4
*/
function test_checkClientAuthoriseParams_invalidScopes()
{
$_GET['client_id'] = 'test';
$_GET['redirect_uri'] = 'http://example.com/test';
$_GET['response_type'] = 'code';
$_GET['scope'] = 'blah';
$this->oauth->checkClientAuthoriseParams();
}
function test_newAuthoriseRequest()
{
$result = $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'
))
));
$this->assertEquals(40, strlen($result));
}
function test_newAuthoriseRequest_isUnique()
{
$result1 = $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'
))
));
$result2 = $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'
))
));
$this->assertNotEquals($result1, $result2);
}
function test_issueAccessToken_POST()
{
$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(3, $result);
$this->assertArrayHasKey('access_token', $result);
$this->assertArrayHasKey('token_type', $result);
$this->assertArrayHasKey('expires_in', $result);
}
function test_issueAccessToken_PassedParams()
{
$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(3, $result);
$this->assertArrayHasKey('access_token', $result);
$this->assertArrayHasKey('token_type', $result);
$this->assertArrayHasKey('expires_in', $result);
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 0
*/
function test_issueAccessToken_missingGrantType()
{
$this->oauth->issueAccessToken();
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 7
*/
function test_issueAccessToken_unsupportedGrantType()
{
$params['grant_type'] = 'blah';
$this->oauth->issueAccessToken($params);
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 0
*/
function test_completeAuthCodeGrant_missingClientId()
{
$reflector = new ReflectionClass($this->oauth);
$method = $reflector->getMethod('completeAuthCodeGrant');
$method->setAccessible(true);
$method->invoke($this->oauth);
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 0
*/
function test_completeAuthCodeGrant_missingClientSecret()
{
$reflector = new ReflectionClass($this->oauth);
$method = $reflector->getMethod('completeAuthCodeGrant');
$method->setAccessible(true);
$authParams['client_id'] = 'test';
$method->invoke($this->oauth, $authParams);
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 0
*/
function test_completeAuthCodeGrant_missingRedirectUri()
{
$reflector = new ReflectionClass($this->oauth);
$method = $reflector->getMethod('completeAuthCodeGrant');
$method->setAccessible(true);
$authParams['client_id'] = 'test';
$authParams['client_secret'] = 'test';
$method->invoke($this->oauth, $authParams);
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 8
*/
function test_completeAuthCodeGrant_invalidClient()
{
$reflector = new ReflectionClass($this->oauth);
$method = $reflector->getMethod('completeAuthCodeGrant');
$method->setAccessible(true);
$authParams['client_id'] = 'test';
$authParams['client_secret'] = 'test123';
$authParams['redirect_uri'] = 'http://example.com/test';
$method->invoke($this->oauth, $authParams);
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 0
*/
function test_completeAuthCodeGrant_missingCode()
{
$reflector = new ReflectionClass($this->oauth);
$method = $reflector->getMethod('completeAuthCodeGrant');
$method->setAccessible(true);
$authParams['client_id'] = 'test';
$authParams['client_secret'] = 'test';
$authParams['redirect_uri'] = 'http://example.com/test';
$method->invoke($this->oauth, $authParams);
}
/**
* @expectedException Oauth2\Authentication\OAuthServerClientException
* @expectedExceptionCode 9
*/
function test_completeAuthCodeGrant_invalidCode()
{
$reflector = new ReflectionClass($this->oauth);
$method = $reflector->getMethod('completeAuthCodeGrant');
$method->setAccessible(true);
$authParams['client_id'] = 'test';
$authParams['client_secret'] = 'test';
$authParams['redirect_uri'] = 'http://example.com/test';
$authParams['code'] = 'blah';
$method->invoke($this->oauth, $authParams);
}
/**
* @expectedException Oauth2\Authentication\OAuthServerException
* @expectedExceptionMessage No registered database abstractor
*/
function test_noRegisteredDatabaseAbstractor()
{
$reflector = new ReflectionClass($this->oauth);
$method = $reflector->getMethod('dbcall');
$method->setAccessible(true);
$dbAbstractor = $reflector->getProperty('db');
$dbAbstractor->setAccessible(true);
$dbAbstractor->setValue($this->oauth, null);
$result = $method->invoke($this->oauth);
}
/**
* @expectedException Oauth2\Authentication\OAuthServerException
* @expectedExceptionMessage Registered database abstractor is not an instance of Oauth2\Authentication\Database
*/
function test_invalidRegisteredDatabaseAbstractor()
{
$fake = new stdClass;
$this->oauth->registerDbAbstractor($fake);
$reflector = new ReflectionClass($this->oauth);
$method = $reflector->getMethod('dbcall');
$method->setAccessible(true);
$result = $method->invoke($this->oauth);
}
}

View File

@ -0,0 +1,31 @@
<?php
use Oauth2\Resource\Database;
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,121 @@
<?php
class Resource_Server_test extends PHPUnit_Framework_TestCase {
function setUp()
{
require_once('database_mock.php');
$this->server = new Oauth2\Resource\Server();
$this->db = new ResourceDB();
$this->assertInstanceOf('Oauth2\Resource\Database', $this->db);
$this->server->registerDbAbstractor($this->db);
}
function test_init_POST()
{
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST['oauth_token'] = 'test12345';
$this->server->init();
$reflector = new ReflectionClass($this->server);
$_accessToken = $reflector->getProperty('_accessToken');
$_accessToken->setAccessible(true);
$_type = $reflector->getProperty('_type');
$_type->setAccessible(true);
$_typeId = $reflector->getProperty('_typeId');
$_typeId->setAccessible(true);
$_scopes = $reflector->getProperty('_scopes');
$_scopes->setAccessible(true);
$this->assertEquals($_accessToken->getValue($this->server), $_POST['oauth_token']);
$this->assertEquals($_type->getValue($this->server), 'user');
$this->assertEquals($_typeId->getValue($this->server), 123);
$this->assertEquals($_scopes->getValue($this->server), array('foo', 'bar'));
}
function test_init_GET()
{
$_GET['oauth_token'] = 'test12345';
$this->server->init();
$reflector = new ReflectionClass($this->server);
$_accessToken = $reflector->getProperty('_accessToken');
$_accessToken->setAccessible(true);
$_type = $reflector->getProperty('_type');
$_type->setAccessible(true);
$_typeId = $reflector->getProperty('_typeId');
$_typeId->setAccessible(true);
$_scopes = $reflector->getProperty('_scopes');
$_scopes->setAccessible(true);
$this->assertEquals($_accessToken->getValue($this->server), $_GET['oauth_token']);
$this->assertEquals($_type->getValue($this->server), 'user');
$this->assertEquals($_typeId->getValue($this->server), 123);
$this->assertEquals($_scopes->getValue($this->server), array('foo', 'bar'));
}
function test_init_header()
{
// Test with authorisation header
$this->markTestIncomplete('Authorisation header test has not been implemented yet.');
}
/**
* @expectedException \Oauth2\Resource\OAuthResourceServerException
* @expectedExceptionMessage An access token was not presented with the request
*/
function test_init_missingToken()
{
$this->server->init();
}
/**
* @expectedException \Oauth2\Resource\OAuthResourceServerException
* @expectedExceptionMessage The access token is not registered with the resource server
*/
function test_init_wrongToken()
{
$_POST['oauth_token'] = 'blah';
$_SERVER['REQUEST_METHOD'] = 'POST';
$this->server->init();
}
function test_hasScope()
{
$_POST['oauth_token'] = 'test12345';
$_SERVER['REQUEST_METHOD'] = 'POST';
$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';
$_SERVER['REQUEST_METHOD'] = 'POST';
$this->server->init();
$this->assertEquals(123, $this->server->isUser());
$this->assertEquals(false, $this->server->isMachine());
}
}