Compare commits

...

16 Commits
4.0.5 ... 4.1.0

Author SHA1 Message Date
Alex Bilbie
72a5c1794a Remove unused namespace 2014-12-27 22:50:13 +00:00
Alex Bilbie
707c85b0d6 Fixes and tests 2014-12-27 22:26:31 +00:00
Alex Bilbie
c56562b0b8 PSR fixes 2014-12-27 21:38:01 +00:00
Alex Bilbie
d0b2498b43 Ignore PHPStorm 2014-12-27 21:35:45 +00:00
Alex Bilbie
17be6f4549 Added MacTokenInterface 2014-12-27 21:35:45 +00:00
Alex Bilbie
b50fbff1e3 Update docblock 2014-12-27 21:35:45 +00:00
Alex Bilbie
7375a348c6 PHP code fix 2014-12-27 21:35:45 +00:00
Alex Bilbie
ae5dd9ce65 Added MAC TokenType 2014-12-27 21:35:45 +00:00
Alex Bilbie
f9e56ff62a Added MAC storage getter and setter 2014-12-27 21:35:45 +00:00
Alex Bilbie
1bcf7ee20f Update .travis.yml 2014-12-26 17:03:35 +00:00
Alex Bilbie
bee9c6a51d Added Gitter.im 2014-12-26 16:59:09 +00:00
Alex Bilbie
7fff4a8fe8 Merge pull request #280 from danprime/master
Fix Example Init Code
2014-12-17 10:10:50 +00:00
Alex Bilbie
44ac01ee0e Merge pull request #284 from mortenhauberg/fix-misspelling
Changed "paremter" to "parameter"
2014-12-16 19:48:40 +00:00
mortenhauberg
60bd334b46 Changed "paremter" to "parameter" 2014-12-16 19:04:03 +01:00
Daniel Tse
2653a174bb Update init.php 2014-12-12 10:25:52 -07:00
Daniel Tse
676fb4c06a Fix column declarations and references so that foreign keys and references work. 2014-12-11 15:50:42 -07:00
9 changed files with 391 additions and 12 deletions

3
.gitignore vendored
View File

@@ -11,4 +11,5 @@
/tests/codecept/tests/_log
oauth2-server.paw
/output_*/
/_site
/_site
.idea

View File

@@ -33,4 +33,12 @@ branches:
- master
env:
global:
secure: "C4wD/BQefKSu9W594iyLp+IBCjlM8kKlmp+nXKXnZGi0L8IkV3m4mmNOb8PExxGMhZ3mlev5DnU4Uoh4oJaUxnkR1FpX4dSEpyzU3VknUzSE2yZOlL+bdCw3o85TGoCcp/+ReJCOw5sncxTskJKHlW1YMa33FznaXwLNoImpjTg="
secure: "C4wD/BQefKSu9W594iyLp+IBCjlM8kKlmp+nXKXnZGi0L8IkV3m4mmNOb8PExxGMhZ3mlev5DnU4Uoh4oJaUxnkR1FpX4dSEpyzU3VknUzSE2yZOlL+bdCw3o85TGoCcp/+ReJCOw5sncxTskJKHlW1YMa33FznaXwLNoImpjTg="
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/7de0ca12596cd5268f30
on_success: always # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false

View File

@@ -5,7 +5,7 @@
[![Build Status](https://img.shields.io/travis/thephpleague/oauth2-server/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/oauth2-server)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/oauth2-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-server/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-server)
[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-server.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-server)
[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-server.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-server) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/thephpleague/oauth2-server?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
A standards compliant [OAuth 2.0](http://tools.ietf.org/wg/oauth/draft-ietf-oauth-v2/) authorization server and resource server written in PHP which makes working with OAuth 2.0 trivial. You can easily configure an OAuth 2.0 server to protect your API with access tokens, or allow clients to request new access tokens and refresh them.

View File

@@ -102,7 +102,7 @@ Capsule::table('oauth_scopes')->insert([
print 'Creating sessions table'.PHP_EOL;
Capsule::schema()->create('oauth_sessions', function ($table) {
$table->increments('id');
$table->increments('id')->unsigned();
$table->string('owner_type');
$table->string('owner_id');
$table->string('client_id');
@@ -135,7 +135,7 @@ print 'Creating access tokens table'.PHP_EOL;
Capsule::schema()->create('oauth_access_tokens', function ($table) {
$table->string('access_token')->primary();
$table->integer('session_id');
$table->integer('session_id')->unsigned();
$table->integer('expire_time');
$table->foreign('session_id')->references('id')->on('oauth_sessions')->onDelete('cascade');
@@ -168,7 +168,7 @@ Capsule::schema()->create('oauth_refresh_tokens', function ($table) {
$table->integer('expire_time');
$table->string('access_token');
$table->foreign('access_token')->references('id')->on('oauth_access_tokens')->onDelete('cascade');
$table->foreign('access_token')->references('access_token')->on('oauth_access_tokens')->onDelete('cascade');
});
/******************************************************************************/
@@ -177,7 +177,7 @@ print 'Creating auth codes table'.PHP_EOL;
Capsule::schema()->create('oauth_auth_codes', function ($table) {
$table->string('auth_code')->primary();
$table->integer('session_id');
$table->integer('session_id')->unsigned();
$table->integer('expire_time');
$table->string('client_redirect_uri');
@@ -189,7 +189,7 @@ Capsule::schema()->create('oauth_auth_codes', function ($table) {
print 'Creating oauth access token scopes table'.PHP_EOL;
Capsule::schema()->create('oauth_access_token_scopes', function ($table) {
$table->increments('id');
$table->increments('id')->unsigned();
$table->string('access_token');
$table->string('scope');
@@ -240,8 +240,8 @@ Capsule::schema()->create('oauth_auth_code_scopes', function ($table) {
print 'Creating oauth session scopes table'.PHP_EOL;
Capsule::schema()->create('oauth_session_scopes', function ($table) {
$table->increments('id');
$table->string('session_id');
$table->increments('id')->unsigned();
$table->integer('session_id')->unsigned();
$table->string('scope');
$table->foreign('session_id')->references('id')->on('oauth_sessions')->onDelete('cascade');

View File

@@ -15,6 +15,7 @@ use League\Event\Emitter;
use League\OAuth2\Server\Storage\AccessTokenInterface;
use League\OAuth2\Server\Storage\AuthCodeInterface;
use League\OAuth2\Server\Storage\ClientInterface;
use League\OAuth2\Server\Storage\MacTokenInterface;
use League\OAuth2\Server\Storage\RefreshTokenInterface;
use League\OAuth2\Server\Storage\ScopeInterface;
use League\OAuth2\Server\Storage\SessionInterface;
@@ -75,6 +76,11 @@ abstract class AbstractServer
*/
protected $clientStorage;
/**
* @var \League\OAuth2\Server\Storage\MacTokenInterface
*/
protected $macStorage;
/**
* Token type
*
@@ -332,4 +338,20 @@ abstract class AbstractServer
{
return $this->tokenType;
}
/**
* @return MacTokenInterface
*/
public function getMacStorage()
{
return $this->macStorage;
}
/**
* @param MacTokenInterface $macStorage
*/
public function setMacStorage(MacTokenInterface $macStorage)
{
$this->macStorage = $macStorage;
}
}

View File

@@ -181,7 +181,7 @@ class AuthorizationServer extends AbstractServer
}
/**
* Require the "state" paremter in checkAuthoriseParams()
* Require the "state" parameter in checkAuthoriseParams()
*
* @return bool
*/
@@ -191,7 +191,7 @@ class AuthorizationServer extends AbstractServer
}
/**
* Require the "state" paremter in checkAuthoriseParams()
* Require the "state" parameter in checkAuthoriseParams()
*
* @param boolean $require
*

View File

@@ -0,0 +1,34 @@
<?php
/**
* OAuth 2.0 MAC Token Interface
*
* @package league/oauth2-server
* @author Alex Bilbie <hello@alexbilbie.com>
* @copyright Copyright (c) Alex Bilbie
* @license http://mit-license.org/
* @link https://github.com/thephpleague/oauth2-server
*/
namespace League\OAuth2\Server\Storage;
/**
* MacTokenInterface
*/
interface MacTokenInterface extends StorageInterface
{
/**
* Create a MAC key linked to an access token
* @param string $macKey
* @param string $accessToken
* @return void
*/
public function create($macKey, $accessToken);
/**
* Get a MAC key by access token
* @param string $accessToken
* @return string
*/
public function getByAccessToken($accessToken);
}

149
src/TokenType/MAC.php Normal file
View File

@@ -0,0 +1,149 @@
<?php
/**
* OAuth 2.0 MAC Token Type
*
* @package league/oauth2-server
* @author Alex Bilbie <hello@alexbilbie.com>
* @copyright Copyright (c) Alex Bilbie
* @license http://mit-license.org/
* @link https://github.com/thephpleague/oauth2-server
*/
namespace League\OAuth2\Server\TokenType;
use League\OAuth2\Server\Util\SecureKey;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
/**
* MAC Token Type
*/
class MAC extends AbstractTokenType implements TokenTypeInterface
{
/**
* {@inheritdoc}
*/
public function generateResponse()
{
$macKey = SecureKey::generate();
$this->server->getMacStorage()->create($macKey, $this->getParam('access_token'));
$response = [
'access_token' => $this->getParam('access_token'),
'token_type' => 'mac',
'expires_in' => $this->getParam('expires_in'),
'mac_key' => $macKey,
'mac_algorithm' => 'hmac-sha-256',
];
return $response;
}
/**
* {@inheritdoc}
*/
public function determineAccessTokenInHeader(Request $request)
{
if ($request->headers->has('Authorization') === false) {
return;
}
$header = $request->headers->get('Authorization');
if (substr($header, 0, 4) !== 'MAC ') {
return;
}
// Find all the parameters expressed in the header
$paramsRaw = explode(',', substr($header, 4));
$params = new ParameterBag();
array_map(function ($param) use (&$params) {
$param = trim($param);
preg_match_all('/([a-zA-Z]*)="([\w=]*)"/', $param, $matches);
// @codeCoverageIgnoreStart
if (count($matches) !== 3) {
return;
}
// @codeCoverageIgnoreEnd
$key = reset($matches[1]);
$value = trim(reset($matches[2]));
if (empty($value)) {
return;
}
$params->set($key, $value);
}, $paramsRaw);
// Validate parameters
if ($params->has('id') === false || $params->has('ts') === false || $params->has('nonce') === false || $params->has('mac') === false) {
return;
}
if ((int) $params->get('ts') !== time()) {
return;
}
$accessToken = $params->get('id');
$timestamp = (int) $params->get('ts');
$nonce = $params->get('nonce');
$signature = $params->get('mac');
// Try to find the MAC key for the access token
$macKey = $this->server->getMacStorage()->getByAccessToken($accessToken);
if ($macKey === null) {
return;
}
// Calculate and compare the signature
$calculatedSignatureParts = [
$timestamp,
$nonce,
strtoupper($request->getMethod()),
$request->getUri(),
$request->getHost(),
$request->getPort(),
];
if ($params->has('ext')) {
$calculatedSignatureParts[] = $params->get('ext');
}
$calculatedSignature = base64_encode(hash_hmac('sha256', implode("\n", $calculatedSignatureParts), $macKey));
// Return the access token if the signature matches
return ($this->hash_equals($calculatedSignature, $signature)) ? $accessToken : null;
}
/**
* Prevent timing attack
* @param string $knownString
* @param string $userString
* @return bool
*/
private function hash_equals($knownString, $userString)
{
if (!function_exists('hash_equals')) {
function hash_equals($knownString, $userString)
{
if (strlen($knownString) !== strlen($userString)) {
return false;
}
$len = strlen($knownString);
$result = 0;
for ($i = 0; $i < $len; $i++) {
$result |= (ord($knownString[$i]) ^ ord($userString[$i]));
}
// They are only identical strings if $result is exactly 0...
return 0 === $result;
}
}
return hash_equals($knownString, $userString);
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace LeagueTests\TokenType;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Entity\AccessTokenEntity;
use League\OAuth2\Server\TokenType\MAC;
use Mockery as M;
use Symfony\Component\HttpFoundation\Request;
class MacTest extends \PHPUnit_Framework_TestCase
{
public function testGenerateResponse()
{
$macStorage = M::mock('\League\OAuth2\Server\Storage\MacTokenInterface');
$macStorage->shouldReceive('create');
$server = new AuthorizationServer();
$server->setMacStorage($macStorage);
$tokenType = new MAC();
$tokenType->setServer($server);
$accessToken = new AccessTokenEntity($server);
$accessToken->setId(uniqid());
$accessToken->setExpireTime(time());
$tokenType->setParam('access_token', $accessToken->getId());
$tokenType->setParam('expires_in', 3600);
$response = $tokenType->generateResponse();
$this->assertEquals($accessToken->getId(), $response['access_token']);
$this->assertEquals('mac', $response['token_type']);
$this->assertEquals(3600, $response['expires_in']);
$this->assertEquals('hmac-sha-256', $response['mac_algorithm']);
$this->assertArrayHasKey('mac_key', $response);
}
public function testDetermineAccessTokenInHeaderValid()
{
$macStorage = M::mock('\League\OAuth2\Server\Storage\MacTokenInterface');
$macStorage->shouldReceive('getByAccessToken')->andReturn('abcdef');
$server = new AuthorizationServer();
$server->setMacStorage($macStorage);
$ts = time();
$request = Request::createFromGlobals();
$calculatedSignatureParts = [
$ts,
'foo',
strtoupper($request->getMethod()),
$request->getUri(),
$request->getHost(),
$request->getPort(),
'ext'
];
$calculatedSignature = base64_encode(hash_hmac('sha256', implode("\n", $calculatedSignatureParts), 'abcdef'));
$request->headers->set('Authorization', sprintf('MAC id="foo", nonce="foo", ts="%s", mac="%s", ext="ext"', $ts, $calculatedSignature));
$tokenType = new MAC();
$tokenType->setServer($server);
$response = $tokenType->determineAccessTokenInHeader($request);
$this->assertEquals('foo', $response);
}
public function testDetermineAccessTokenInHeaderMissingHeader()
{
$macStorage = M::mock('\League\OAuth2\Server\Storage\MacTokenInterface');
$macStorage->shouldReceive('getByAccessToken')->andReturn('abcdef');
$server = new AuthorizationServer();
$server->setMacStorage($macStorage);
$request = Request::createFromGlobals();
$tokenType = new MAC();
$tokenType->setServer($server);
$response = $tokenType->determineAccessTokenInHeader($request);
$this->assertEquals(null, $response);
}
public function testDetermineAccessTokenInHeaderMissingAuthMac()
{
$macStorage = M::mock('\League\OAuth2\Server\Storage\MacTokenInterface');
$macStorage->shouldReceive('getByAccessToken')->andReturn('abcdef');
$server = new AuthorizationServer();
$server->setMacStorage($macStorage);
$request = Request::createFromGlobals();
$request->headers->set('Authorization', '');
$tokenType = new MAC();
$tokenType->setServer($server);
$response = $tokenType->determineAccessTokenInHeader($request);
$this->assertEquals(null, $response);
}
public function testDetermineAccessTokenInHeaderInvalidParam()
{
$macStorage = M::mock('\League\OAuth2\Server\Storage\MacTokenInterface');
$macStorage->shouldReceive('getByAccessToken')->andReturn('abcdef');
$server = new AuthorizationServer();
$server->setMacStorage($macStorage);
$request = Request::createFromGlobals();
$request->headers->set('Authorization', 'MAC ');
$tokenType = new MAC();
$tokenType->setServer($server);
$response = $tokenType->determineAccessTokenInHeader($request);
$this->assertEquals(null, $response);
}
public function testDetermineAccessTokenInHeaderMismatchTimestamp()
{
$macStorage = M::mock('\League\OAuth2\Server\Storage\MacTokenInterface');
$macStorage->shouldReceive('getByAccessToken')->andReturn('abcdef');
$server = new AuthorizationServer();
$server->setMacStorage($macStorage);
$ts = time() - 100;
$request = Request::createFromGlobals();
$request->headers->set('Authorization', sprintf('MAC id="foo", nonce="foo", ts="%s", mac="%s", ext="ext"', $ts, 'foo'));
$tokenType = new MAC();
$tokenType->setServer($server);
$response = $tokenType->determineAccessTokenInHeader($request);
$this->assertEquals(null, $response);
}
public function testDetermineAccessTokenInHeaderMissingMacKey()
{
$macStorage = M::mock('\League\OAuth2\Server\Storage\MacTokenInterface');
$macStorage->shouldReceive('getByAccessToken')->andReturn(null);
$server = new AuthorizationServer();
$server->setMacStorage($macStorage);
$ts = time();
$request = Request::createFromGlobals();
$request->headers->set('Authorization', sprintf('MAC id="foo", nonce="foo", ts="%s", mac="%s", ext="ext"', $ts, 'foo'));
$tokenType = new MAC();
$tokenType->setServer($server);
$response = $tokenType->determineAccessTokenInHeader($request);
$this->assertEquals(null, $response);
}
}