Compare commits

...

44 Commits
4.0.5 ... 4.1.3

Author SHA1 Message Date
Alex Bilbie
34a6b66b8c More .travis.yml updates 2015-03-22 23:19:36 +00:00
Alex Bilbie
61738a7fe2 Added fast_finish: true to .travis.yml 2015-03-22 23:13:41 +00:00
Alex Bilbie
51184259d1 Merge pull request #323 from rdohms/interface-docs
Updated Interface Docs
2015-03-20 11:43:47 +00:00
rdohms
b21de11429 Updated Interface Docs
Made phpdocs match expectations like null when not found and using array notation for indicating array of <object>
2015-03-20 11:33:03 +01:00
Alex Bilbie
cf6e86c9d4 Merge pull request #319 from Fuxy22/patch-1
Fixed missing session scope
2015-03-13 11:03:05 +00:00
Alex Bilbie
f6fdbc7142 Added PHP 7.0 testing 2015-03-03 22:00:47 +00:00
Norbert Fuksz
7f7f45662a Fixed missing session scope
Close #297
2015-03-02 17:47:48 +00:00
Alex Bilbie
f92a68cc72 Merge branch 'master' of github.com:thephpleague/oauth2-server 2015-02-22 19:47:44 +00:00
Alex Bilbie
295d8ffa24 Updated league/event to ~2.1. Fixes #311 2015-02-22 19:47:27 +00:00
Alex Bilbie
3d08140651 Merge pull request #300 from vvllaadd/patch-2
Probable bug
2015-02-22 19:45:14 +00:00
Alex Bilbie
ec8a8393ee Merge pull request #310 from ismailbaskin/master
typo
2015-02-10 10:03:53 +00:00
Ismail BASKIN
3869b8f406 typo 2015-02-10 10:28:57 +02:00
Alex Bilbie
7da7484008 Added security section 2015-02-05 16:14:59 +00:00
Alex Bilbie
b42ba4af17 Merge pull request #303 from hannesvdvreken/fix/consistent-use-and-fqcn
Boyscouting the php docs to always use FQCNs
2015-01-23 10:47:26 +00:00
Hannes Van De Vreken
dd795a82f4 Changed the order and added missing throws 2015-01-23 11:21:12 +01:00
Hannes Van De Vreken
166362d3cd Boyscouting the php docs to always use FQCNs 2015-01-23 11:17:19 +01:00
Vlad
d43391564c Probable bug
AccessTokenStorage::delete should delete the token, not the scope associated with the token
2015-01-15 14:20:54 +01:00
Alex Bilbie
ea6edf572a Changelog update 2015-01-01 12:56:20 +00:00
Alex Bilbie
19b64c2e65 Merge pull request #290 from sarciszewski/patch-1
Remove side-effects in hash_equals()
2015-01-01 12:52:03 +00:00
Scott Arciszewski
612775466c Remove side-effects in hash_equals()
This is functionally identical, but without the side-effect of defining a function in the current namespace.

Also, it uses absolute function reference (`\hash_equals` instead of `hash_equals`) because if someone defined `League\OAuth2\Server\TokenType\hash_equals()` elsewhere, it would try that first.

Kudos for using `hash_equals()` in your original design for this feature. Many OAuth2 implementations neglect this nuance :)
2015-01-01 01:34:22 -05:00
Alex Bilbie
740ea24e08 Changelog update 2014-12-31 16:03:26 +00:00
Alex Bilbie
e1c14abf6c Lowered symfony/http-foundation to ~2.4 so Laravel can use it 2014-12-31 15:51:52 +00:00
Alex Bilbie
d1aae27359 Version bump 2014-12-27 23:01:11 +00:00
Alex Bilbie
80aeaf9200 Merge branch 'Symplicity-master' into release/4.1.0 2014-12-27 23:00:17 +00:00
Alex Bilbie
282bb20cc8 Fix docblocks + method name 2014-12-27 23:00:11 +00:00
Alex Bilbie
b727be55a2 Merge branch 'master' of https://github.com/Symplicity/oauth2-server into Symplicity-master 2014-12-27 22:57:08 +00:00
Alex Bilbie
cf80a2d6ce README update 2014-12-27 22:55:30 +00:00
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
Dave Walker
851c7c0eb1 Per the spec:
The authorization server MAY issue a new refresh token, in which case
   the client MUST discard the old refresh token and replace it with the
   new refresh token.  The authorization server MAY revoke the old
   refresh token after issuing a new refresh token to the client.  If a
   new refresh token is issued, the refresh token scope MUST be
   identical to that of the refresh token included by the client in the
   request.

This commit allows users to specifiy the time before the Refresh Token
expire time to issue a new Refresh Token.

alter method names, naming convention(?)
2014-12-21 18:51:52 -05: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
23 changed files with 574 additions and 55 deletions

3
.gitignore vendored
View File

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

View File

@@ -1,10 +1,22 @@
language: php
sudo: false
cache:
directories:
- vendor
php:
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:
allow_failures:
- php: 7.0
fast_finish: true
install:
- travis_retry composer install --no-interaction --prefer-source
@@ -33,4 +45,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

@@ -1,5 +1,20 @@
# Changelog
## 4.1.2 (released 2015-01-01)
* Remove side-effects in hash_equals() implementation (Issue #290)
## 4.1.1 (released 2014-12-31)
* Changed `symfony/http-foundation` dependency version to `~2.4` so package can be installed in Laravel `4.1.*`
## 4.1.0 (released 2014-12-27)
* Added MAC token support (Issue #158)
* Fixed example init code (Issue #280)
* Toggle refresh token rotation (Issue #286)
* Docblock fixes
## 4.0.5 (released 2014-12-15)
* Prevent duplicate session in auth code grant (Issue #282)

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.
@@ -22,9 +22,11 @@ You can also define your own grants.
In addition it supports the following token types:
* Bearer tokens
* MAC tokens (coming soon)
* MAC tokens
* JSON web tokens (coming soon)
You can also create you own tokens.
## Requirements
@@ -53,6 +55,10 @@ Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-server/blob/mas
Bugs and feature request are tracked on [GitHub](https://github.com/thephpleague/oauth2-server/issues)
## Security
If you discover any security related issues, please email hello@alexbilbie.com instead of using the issue tracker.
## License
This package is released under the MIT License. See the bundled [LICENSE](https://github.com/thephpleague/oauth2-server/blob/master/LICENSE) file for details.

View File

@@ -5,8 +5,8 @@
"license": "MIT",
"require": {
"php": ">=5.4.0",
"symfony/http-foundation": "~2.5",
"league/event": "1.0.*"
"symfony/http-foundation": "~2.4",
"league/event": "~2.1"
},
"require-dev": {
"phpunit/phpunit": "4.3.*",

View File

@@ -86,7 +86,7 @@ class AccessTokenStorage extends AbstractStorage implements AccessTokenInterface
*/
public function delete(AccessTokenEntity $token)
{
Capsule::table('oauth_access_token_scopes')
Capsule::table('oauth_access_tokens')
->where('access_token', $token->getId())
->delete();
}

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

@@ -12,7 +12,7 @@
namespace League\OAuth2\Server\Entity;
/**
* Access token entity class
* Auth Code entity class
*/
class AuthCodeEntity extends AbstractTokenEntity
{

View File

@@ -148,7 +148,6 @@ class AuthCodeGrant extends AbstractGrant
$session = new SessionEntity($this->server);
$session->setOwner($type, $typeId);
$session->associateClient($authParams['client']);
$session->save();
// Create a new auth code
$authCode = new AuthCodeEntity($this->server);
@@ -158,8 +157,10 @@ class AuthCodeGrant extends AbstractGrant
foreach ($authParams['scopes'] as $scope) {
$authCode->associateScope($scope);
$session->associateScope($scope);
}
$session->save();
$authCode->setSession($session);
$authCode->save();

View File

@@ -35,6 +35,13 @@ class RefreshTokenGrant extends AbstractGrant
*/
protected $refreshTokenTTL = 604800;
/**
* Rotate token (default = true)
*
* @var integer
*/
protected $refreshTokenRotate = true;
/**
* Set the TTL of the refresh token
*
@@ -57,6 +64,25 @@ class RefreshTokenGrant extends AbstractGrant
return $this->refreshTokenTTL;
}
/**
* Set the rotation boolean of the refresh token
* @param bool $refreshTokenRotate
*/
public function setRefreshTokenRotation($refreshTokenRotate = true)
{
$this->refreshTokenRotate = $refreshTokenRotate;
}
/**
* Get rotation boolean of the refresh token
*
* @return bool
*/
public function shouldRotateRefreshTokens()
{
return $this->refreshTokenRotate;
}
/**
* {@inheritdoc}
*/
@@ -146,17 +172,21 @@ class RefreshTokenGrant extends AbstractGrant
$this->server->getTokenType()->setParam('access_token', $newAccessToken->getId());
$this->server->getTokenType()->setParam('expires_in', $this->getAccessTokenTTL());
// Expire the old refresh token
$oldRefreshToken->expire();
if ($this->shouldRotateRefreshTokens()) {
// Expire the old refresh token
$oldRefreshToken->expire();
// Generate a new refresh token
$newRefreshToken = new RefreshTokenEntity($this->server);
$newRefreshToken->setId(SecureKey::generate());
$newRefreshToken->setExpireTime($this->getRefreshTokenTTL() + time());
$newRefreshToken->setAccessToken($newAccessToken);
$newRefreshToken->save();
// Generate a new refresh token
$newRefreshToken = new RefreshTokenEntity($this->server);
$newRefreshToken->setId(SecureKey::generate());
$newRefreshToken->setExpireTime($this->getRefreshTokenTTL() + time());
$newRefreshToken->setAccessToken($newAccessToken);
$newRefreshToken->save();
$this->server->getTokenType()->setParam('refresh_token', $newRefreshToken->getId());
$this->server->getTokenType()->setParam('refresh_token', $newRefreshToken->getId());
} else {
$this->server->getTokenType()->setParam('refresh_token', $oldRefreshToken->getId());
}
return $this->server->getTokenType()->generateResponse();
}

View File

@@ -12,6 +12,8 @@
namespace League\OAuth2\Server;
use League\OAuth2\Server\Entity\AccessTokenEntity;
use League\OAuth2\Server\Exception\AccessDeniedException;
use League\OAuth2\Server\Exception\InvalidRequestException;
use League\OAuth2\Server\Storage\AccessTokenInterface;
use League\OAuth2\Server\Storage\ClientInterface;
use League\OAuth2\Server\Storage\ScopeInterface;
@@ -40,10 +42,10 @@ class ResourceServer extends AbstractServer
/**
* Initialise the resource server
*
* @param SessionInterface $sessionStorage
* @param AccessTokenInterface $accessTokenStorage
* @param ClientInterface $clientStorage
* @param ScopeInterface $scopeStorage
* @param \League\OAuth2\Server\Storage\SessionInterface $sessionStorage
* @param \League\OAuth2\Server\Storage\AccessTokenInterface $accessTokenStorage
* @param \League\OAuth2\Server\Storage\ClientInterface $clientStorage
* @param \League\OAuth2\Server\Storage\ScopeInterface $scopeStorage
*
* @return self
*/
@@ -93,31 +95,32 @@ class ResourceServer extends AbstractServer
/**
* Checks if the access token is valid or not
*
* @param bool $headersOnly Limit Access Token to Authorization header only
* @param AccessTokenEntity|null $accessToken Access Token
* @param bool $headerOnly Limit Access Token to Authorization header
* @param \League\OAuth2\Server\Entity\AccessTokenEntity|null $accessToken Access Token
*
* @throws \League\OAuth2\Server\Exception\AccessDeniedException
* @throws \League\OAuth2\Server\Exception\InvalidRequestException
*
* @return bool
*
* @throws
*/
public function isValidRequest($headersOnly = true, $accessToken = null)
public function isValidRequest($headerOnly = true, $accessToken = null)
{
$accessTokenString = ($accessToken !== null)
? $accessToken
: $this->determineAccessToken($headersOnly);
: $this->determineAccessToken($headerOnly);
// Set the access token
$this->accessToken = $this->getAccessTokenStorage()->get($accessTokenString);
// Ensure the access token exists
if (!$this->accessToken instanceof AccessTokenEntity) {
throw new Exception\AccessDeniedException();
throw new AccessDeniedException();
}
// Check the access token hasn't expired
// Ensure the auth code hasn't expired
if ($this->accessToken->isExpired() === true) {
throw new Exception\AccessDeniedException();
throw new AccessDeniedException();
}
return true;
@@ -126,24 +129,24 @@ class ResourceServer extends AbstractServer
/**
* Reads in the access token from the headers
*
* @param bool $headersOnly Limit Access Token to Authorization header only
* @param bool $headerOnly Limit Access Token to Authorization header
*
* @throws Exception\InvalidRequestException Thrown if there is no access token presented
* @throws \League\OAuth2\Server\Exception\InvalidRequestException Thrown if there is no access token presented
*
* @return string
*/
public function determineAccessToken($headersOnly = false)
public function determineAccessToken($headerOnly = false)
{
if ($this->getRequest()->headers->get('Authorization') !== null) {
$accessToken = $this->getTokenType()->determineAccessTokenInHeader($this->getRequest());
} elseif ($headersOnly === false) {
} elseif ($headerOnly === false) {
$accessToken = ($this->getRequest()->server->get('REQUEST_METHOD') === 'GET')
? $this->getRequest()->query->get($this->tokenKey)
: $this->getRequest()->request->get($this->tokenKey);
}
if (empty($accessToken)) {
throw new Exception\InvalidRequestException('access token');
throw new InvalidRequestException('access token');
}
return $accessToken;

View File

@@ -24,7 +24,7 @@ interface AccessTokenInterface extends StorageInterface
*
* @param string $token The access token
*
* @return \League\OAuth2\Server\Entity\AccessTokenEntity
* @return \League\OAuth2\Server\Entity\AccessTokenEntity | null
*/
public function get($token);
@@ -33,7 +33,7 @@ interface AccessTokenInterface extends StorageInterface
*
* @param \League\OAuth2\Server\Entity\AccessTokenEntity $token The access token
*
* @return array Array of \League\OAuth2\Server\Entity\ScopeEntity
* @return \League\OAuth2\Server\Entity\ScopeEntity[] Array of \League\OAuth2\Server\Entity\ScopeEntity
*/
public function getScopes(AccessTokenEntity $token);

View File

@@ -24,7 +24,7 @@ interface AuthCodeInterface extends StorageInterface
*
* @param string $code
*
* @return \League\OAuth2\Server\Entity\AuthCodeEntity
* @return \League\OAuth2\Server\Entity\AuthCodeEntity | null
*/
public function get($code);
@@ -45,7 +45,7 @@ interface AuthCodeInterface extends StorageInterface
*
* @param \League\OAuth2\Server\Entity\AuthCodeEntity $token The auth code
*
* @return array Array of \League\OAuth2\Server\Entity\ScopeEntity
* @return \League\OAuth2\Server\Entity\ScopeEntity[] Array of \League\OAuth2\Server\Entity\ScopeEntity
*/
public function getScopes(AuthCodeEntity $token);

View File

@@ -26,7 +26,7 @@ interface ClientInterface extends StorageInterface
* @param string $redirectUri The client's redirect URI (default = "null")
* @param string $grantType The grant type used (default = "null")
*
* @return \League\OAuth2\Server\Entity\ClientEntity
* @return \League\OAuth2\Server\Entity\ClientEntity | null
*/
public function get($clientId, $clientSecret = null, $redirectUri = null, $grantType = null);
@@ -35,7 +35,7 @@ interface ClientInterface extends StorageInterface
*
* @param \League\OAuth2\Server\Entity\SessionEntity $session The session
*
* @return \League\OAuth2\Server\Entity\ClientEntity
* @return \League\OAuth2\Server\Entity\ClientEntity | null
*/
public function getBySession(SessionEntity $session);
}

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);
}

View File

@@ -23,7 +23,7 @@ interface RefreshTokenInterface extends StorageInterface
*
* @param string $token
*
* @return \League\OAuth2\Server\Entity\RefreshTokenEntity
* @return \League\OAuth2\Server\Entity\RefreshTokenEntity | null
*/
public function get($token);

View File

@@ -23,7 +23,7 @@ interface ScopeInterface extends StorageInterface
* @param string $grantType The grant type used in the request (default = "null")
* @param string $clientId The client sending the request (default = "null")
*
* @return \League\OAuth2\Server\Entity\ScopeEntity
* @return \League\OAuth2\Server\Entity\ScopeEntity | null
*/
public function get($scope, $grantType = null, $clientId = null);
}

View File

@@ -26,7 +26,7 @@ interface SessionInterface extends StorageInterface
*
* @param \League\OAuth2\Server\Entity\AccessTokenEntity $accessToken The access token
*
* @return \League\OAuth2\Server\Entity\SessionEntity
* @return \League\OAuth2\Server\Entity\SessionEntity | null
*/
public function getByAccessToken(AccessTokenEntity $accessToken);
@@ -35,7 +35,7 @@ interface SessionInterface extends StorageInterface
*
* @param \League\OAuth2\Server\Entity\AuthCodeEntity $authCode The auth code
*
* @return \League\OAuth2\Server\Entity\SessionEntity
* @return \League\OAuth2\Server\Entity\SessionEntity | null
*/
public function getByAuthCode(AuthCodeEntity $authCode);
@@ -44,7 +44,7 @@ interface SessionInterface extends StorageInterface
*
* @param \League\OAuth2\Server\Entity\SessionEntity
*
* @return array Array of \League\OAuth2\Server\Entity\ScopeEntity
* @return \League\OAuth2\Server\Entity\ScopeEntity[] Array of \League\OAuth2\Server\Entity\ScopeEntity
*/
public function getScopes(SessionEntity $session);

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

@@ -0,0 +1,145 @@
<?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')) {
return \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;
}
}

View File

@@ -421,4 +421,81 @@ class RefreshTokenGrantTest extends \PHPUnit_Framework_TestCase
$server->issueAccessToken();
}
public function testCompleteFlowRotateRefreshToken()
{
$_POST = [
'grant_type' => 'refresh_token',
'client_id' => 'testapp',
'client_secret' => 'foobar',
'refresh_token' => 'refresh_token',
];
$server = new AuthorizationServer();
$grant = new RefreshTokenGrant();
$clientStorage = M::mock('League\OAuth2\Server\Storage\ClientInterface');
$clientStorage->shouldReceive('setServer');
$clientStorage->shouldReceive('get')->andReturn(
(new ClientEntity($server))->hydrate(['id' => 'testapp'])
);
$sessionStorage = M::mock('League\OAuth2\Server\Storage\SessionInterface');
$sessionStorage->shouldReceive('setServer');
$sessionStorage->shouldReceive('getScopes')->shouldReceive('getScopes')->andReturn([]);
$sessionStorage->shouldReceive('associateScope');
$sessionStorage->shouldReceive('getByAccessToken')->andReturn(
(new SessionEntity($server))
);
$accessTokenStorage = M::mock('League\OAuth2\Server\Storage\AccessTokenInterface');
$accessTokenStorage->shouldReceive('setServer');
$accessTokenStorage->shouldReceive('get')->andReturn(
(new AccessTokenEntity($server))
);
$accessTokenStorage->shouldReceive('delete');
$accessTokenStorage->shouldReceive('create');
$accessTokenStorage->shouldReceive('getScopes')->andReturn([
(new ScopeEntity($server))->hydrate(['id' => 'foo']),
]);
$accessTokenStorage->shouldReceive('associateScope');
$refreshTokenStorage = M::mock('League\OAuth2\Server\Storage\RefreshTokenInterface');
$refreshTokenStorage->shouldReceive('setServer');
$refreshTokenStorage->shouldReceive('associateScope');
$refreshTokenStorage->shouldReceive('delete');
$refreshTokenStorage->shouldReceive('create');
$refreshTokenStorage->shouldReceive('get')->andReturn(
(new RefreshTokenEntity($server))->setId('refresh_token')->setExpireTime(time() + 86400)
);
$scopeStorage = M::mock('League\OAuth2\Server\Storage\ScopeInterface');
$scopeStorage->shouldReceive('setServer');
$scopeStorage->shouldReceive('get')->andReturn(
(new ScopeEntity($server))->hydrate(['id' => 'foo'])
);
$server->setClientStorage($clientStorage);
$server->setScopeStorage($scopeStorage);
$server->setSessionStorage($sessionStorage);
$server->setAccessTokenStorage($accessTokenStorage);
$server->setRefreshTokenStorage($refreshTokenStorage);
$server->addGrantType($grant);
$response = $server->issueAccessToken();
$this->assertTrue(array_key_exists('access_token', $response));
$this->assertTrue(array_key_exists('refresh_token', $response));
$this->assertTrue(array_key_exists('token_type', $response));
$this->assertTrue(array_key_exists('expires_in', $response));
$this->assertNotEquals($response['refresh_token'], $_POST['refresh_token']);
$grant->setRefreshTokenRotation(false);
$response = $server->issueAccessToken();
$this->assertTrue(array_key_exists('access_token', $response));
$this->assertTrue(array_key_exists('refresh_token', $response));
$this->assertTrue(array_key_exists('token_type', $response));
$this->assertTrue(array_key_exists('expires_in', $response));
$this->assertEquals($response['refresh_token'], $_POST['refresh_token']);
}
}

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);
}
}