Skip to content

Commit

Permalink
Merge branch 'release/2.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbilbie committed May 11, 2013
2 parents b16c58b + 9ec5442 commit 5d0b295
Show file tree
Hide file tree
Showing 19 changed files with 580 additions and 87 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 2.1 (released 2013-05-10)

* Moved zetacomponents/database to "suggest" in composer.json. If you rely on this feature you now need to include " zetacomponents/database" into "require" key in your own composer.json. (Issue #51)
* New method in Refresh grant called `rotateRefreshTokens()`. Pass in `true` to issue a new refresh token each time an access token is refreshed. This parameter needs to be set to true in order to request reduced scopes with the new access token. (Issue #47)
* Rename `key` column in oauth_scopes table to `scope` as `key` is a reserved SQL word. (Issue #45)
* The `scope` parameter is no longer required by default as per the RFC. (Issue #43)
* You can now set multiple default scopes by passing an array into `setDefaultScope()`. (Issue #42)
* The password and client credentials grants now allow for multiple sessions per user. (Issue #32)
* Scopes associated to authorization codes are not held in their own table (Issue #44)
* Database schema updates.

## 2.0.5 (released 2013-05-09)

* Fixed `oauth_session_token_scopes` table primary key
Expand Down
11 changes: 6 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
{
"name": "league/oauth2-server",
"description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.",
"version": "2.0.5",
"version": "2.1",
"homepage": "https://github.com/php-loep/oauth2-server",
"license": "MIT",
"require": {
"php": ">=5.3.0",
"zetacomponents/database": "dev-master"
"php": ">=5.3.0"
},
"require-dev": {
"mockery/mockery": ">=0.7.2"
Expand Down Expand Up @@ -43,5 +42,7 @@
"League\\OAuth2\\Server": "src/"
}
},
"suggest": {}
}
"suggest": {
"zetacomponents/database": "Allows use of the build in PDO storage classes"
}
}
28 changes: 19 additions & 9 deletions sql/mysql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ CREATE TABLE `oauth_session_access_tokens` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_session_authcodes` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`session_id` int(10) unsigned NOT NULL,
`auth_code` char(40) NOT NULL,
`auth_code_expires` int(10) unsigned NOT NULL,
`scope_ids` char(255) DEFAULT NULL,
PRIMARY KEY (`session_id`),
CONSTRAINT `f_oaseau_seid` FOREIGN KEY (`session_id`) REFERENCES `oauth_sessions` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
PRIMARY KEY (`id`),
KEY `session_id` (`session_id`),
CONSTRAINT `oauth_session_authcodes_ibfk_1` FOREIGN KEY (`session_id`) REFERENCES `oauth_sessions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_session_redirects` (
Expand All @@ -65,13 +66,13 @@ CREATE TABLE `oauth_session_refresh_tokens` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_scopes` (
`id` SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
`key` VARCHAR(255) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` VARCHAR(255) DEFAULT NULL,
`id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`scope` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `u_oasc_sc` (`key`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
UNIQUE KEY `u_oasc_sc` (`scope`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_session_token_scopes` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
Expand All @@ -82,4 +83,13 @@ CREATE TABLE `oauth_session_token_scopes` (
KEY `f_oasetosc_scid` (`scope_id`),
CONSTRAINT `f_oasetosc_scid` FOREIGN KEY (`scope_id`) REFERENCES `oauth_scopes` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT `f_oasetosc_setoid` FOREIGN KEY (`session_access_token_id`) REFERENCES `oauth_session_access_tokens` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_session_authcode_scopes` (
`oauth_session_authcode_id` int(10) unsigned NOT NULL,
`scope_id` smallint(5) unsigned NOT NULL,
KEY `oauth_session_authcode_id` (`oauth_session_authcode_id`),
KEY `scope_id` (`scope_id`),
CONSTRAINT `oauth_session_authcode_scopes_ibfk_2` FOREIGN KEY (`scope_id`) REFERENCES `oauth_scopes` (`id`) ON DELETE CASCADE,
CONSTRAINT `oauth_session_authcode_scopes_ibfk_1` FOREIGN KEY (`oauth_session_authcode_id`) REFERENCES `oauth_session_authcodes` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
10 changes: 5 additions & 5 deletions src/League/OAuth2/Server/Authorization.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ class Authorization
* Require the "scope" parameter to be in checkAuthoriseParams()
* @var boolean
*/
protected $requireScopeParam = true;
protected $requireScopeParam = false;

/**
* Default scope to be used if none is provided and requireScopeParam is false
* @var string
* Default scope(s) to be used if none is provided
* @var string|array
*/
protected $defaultScope = null;

Expand Down Expand Up @@ -271,7 +271,7 @@ public function getResponseTypes()
* @param boolean $require
* @return void
*/
public function requireScopeParam($require = true)
public function requireScopeParam($require = false)
{
$this->requireScopeParam = $require;
}
Expand All @@ -287,7 +287,7 @@ public function scopeParamRequired()

/**
* Default scope to be used if none is provided and requireScopeParam is false
* @var string
* @var string|array
*/
public function setDefaultScope($default = null)
{
Expand Down
44 changes: 23 additions & 21 deletions src/League/OAuth2/Server/Grant/AuthCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,14 @@ public function checkAuthoriseParams($inputParams = array())
if ($scopes[$i] === '') unset($scopes[$i]); // Remove any junk scopes
}

if ($this->authServer->scopeParamRequired() === true && count($scopes) === 0) {
if ($this->authServer->scopeParamRequired() === true && $this->authServer->getDefaultScope() === null && count($scopes) === 0) {
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_request'), 'scope'), 0);
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope()) {
$scopes = array($this->authServer->getDefaultScope());
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope() !== null) {
if (is_array($this->authServer->getDefaultScope())) {
$scopes = $this->authServer->getDefaultScope();
} else {
$scopes = array($this->authServer->getDefaultScope());
}
}

$authParams['scopes'] = array();
Expand Down Expand Up @@ -189,21 +193,19 @@ public function newAuthoriseRequest($type, $typeId, $authParams = array())
// Remove any old sessions the user might have
$this->authServer->getStorage('session')->deleteSession($authParams['client_id'], $type, $typeId);

// List of scopes IDs
$scopeIds = array();
foreach ($authParams['scopes'] as $scope)
{
$scopeIds[] = $scope['id'];
}

// Create a new session
$sessionId = $this->authServer->getStorage('session')->createSession($authParams['client_id'], $type, $typeId);

// Associate a redirect URI
$this->authServer->getStorage('session')->associateRedirectUri($sessionId, $authParams['redirect_uri']);

// Associate the auth code
$this->authServer->getStorage('session')->associateAuthCode($sessionId, $authCode, time() + $this->authTokenTTL, implode(',', $scopeIds));
$authCodeId = $this->authServer->getStorage('session')->associateAuthCode($sessionId, $authCode, time() + $this->authTokenTTL);

// Associate the scopes to the auth code
foreach ($authParams['scopes'] as $scope) {
$this->authServer->getStorage('session')->associateAuthCodeScope($authCodeId, $scope['id']);
}

return $authCode;
}
Expand Down Expand Up @@ -245,30 +247,30 @@ public function completeFlow($inputParams = null)
}

// Verify the authorization code matches the client_id and the request_uri
$session = $this->authServer->getStorage('session')->validateAuthCode($authParams['client_id'], $authParams['redirect_uri'], $authParams['code']);
$authCodeDetails = $this->authServer->getStorage('session')->validateAuthCode($authParams['client_id'], $authParams['redirect_uri'], $authParams['code']);

if ( ! $session) {
if ( ! $authCodeDetails) {
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_grant'), 'code'), 9);
}

// A session ID was returned so update it with an access token and remove the authorisation code
// Get any associated scopes
$scopes = $this->authServer->getStorage('session')->getAuthCodeScopes($authCodeDetails['authcode_id']);

// A session ID was returned so update it with an access token and remove the authorisation code
$accessToken = SecureKey::make();
$accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL();
$accessTokenExpires = time() + $accessTokenExpiresIn;

// Remove the auth code
$this->authServer->getStorage('session')->removeAuthCode($session['id']);
$this->authServer->getStorage('session')->removeAuthCode($authCodeDetails['session_id']);

// Create an access token
$accessTokenId = $this->authServer->getStorage('session')->associateAccessToken($session['id'], $accessToken, $accessTokenExpires);
$accessTokenId = $this->authServer->getStorage('session')->associateAccessToken($authCodeDetails['session_id'], $accessToken, $accessTokenExpires);

// Associate scopes with the access token
if ( ! is_null($session['scope_ids'])) {
$scopeIds = explode(',', $session['scope_ids']);

foreach ($scopeIds as $scopeId) {
$this->authServer->getStorage('session')->associateScope($accessTokenId, $scopeId);
if (count($scopes) > 0) {
foreach ($scopes as $scope) {
$this->authServer->getStorage('session')->associateScope($accessTokenId, $scope['scope_id']);
}
}

Expand Down
13 changes: 7 additions & 6 deletions src/League/OAuth2/Server/Grant/ClientCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,14 @@ public function completeFlow($inputParams = null)
if ($scopes[$i] === '') unset($scopes[$i]); // Remove any junk scopes
}

if ($this->authServer->scopeParamRequired() === true && count($scopes) === 0) {
if ($this->authServer->scopeParamRequired() === true && $this->authServer->getDefaultScope() === null && count($scopes) === 0) {
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_request'), 'scope'), 0);
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope()) {
$scopes = array($this->authServer->getDefaultScope());
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope() !== null) {
if (is_array($this->authServer->getDefaultScope())) {
$scopes = $this->authServer->getDefaultScope();
} else {
$scopes = array($this->authServer->getDefaultScope());
}
}

$authParams['scopes'] = array();
Expand All @@ -145,9 +149,6 @@ public function completeFlow($inputParams = null)
$accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL();
$accessTokenExpires = time() + $accessTokenExpiresIn;

// Delete any existing sessions just to be sure
$this->authServer->getStorage('session')->deleteSession($authParams['client_id'], 'client', $authParams['client_id']);

// Create a new session
$sessionId = $this->authServer->getStorage('session')->createSession($authParams['client_id'], 'client', $authParams['client_id']);

Expand Down
13 changes: 7 additions & 6 deletions src/League/OAuth2/Server/Grant/Password.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,14 @@ public function completeFlow($inputParams = null)
if ($scopes[$i] === '') unset($scopes[$i]); // Remove any junk scopes
}

if ($this->authServer->scopeParamRequired() === true && count($scopes) === 0) {
if ($this->authServer->scopeParamRequired() === true && $this->authServer->getDefaultScope() === null && count($scopes) === 0) {
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_request'), 'scope'), 0);
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope()) {
$scopes = array($this->authServer->getDefaultScope());
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope() !== null) {
if (is_array($this->authServer->getDefaultScope())) {
$scopes = $this->authServer->getDefaultScope();
} else {
$scopes = array($this->authServer->getDefaultScope());
}
}

$authParams['scopes'] = array();
Expand All @@ -189,9 +193,6 @@ public function completeFlow($inputParams = null)
$accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL();
$accessTokenExpires = time() + $accessTokenExpiresIn;

// Delete any existing sessions just to be sure
$this->authServer->getStorage('session')->deleteSession($authParams['client_id'], 'user', $userId);

// Create a new session
$sessionId = $this->authServer->getStorage('session')->createSession($authParams['client_id'], 'user', $userId);

Expand Down
77 changes: 69 additions & 8 deletions src/League/OAuth2/Server/Grant/RefreshToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ class RefreshToken implements GrantTypeInterface {
*/
protected $refreshTokenTTL = 604800;

/**
* Rotate refresh tokens
* @var boolean
*/
protected $rotateRefreshTokens = false;

/**
* Constructor
* @param Authorization $authServer Authorization server instance
Expand Down Expand Up @@ -111,6 +117,16 @@ public function getRefreshTokenTTL()
return $this->refreshTokenTTL;
}

/**
* When a new access is token, expire the refresh token used and issue a new one.
* @param boolean $rotateRefreshTokens Set to true to enable (default = false)
* @return void
*/
public function rotateRefreshTokens($rotateRefreshTokens = false)
{
$this->rotateRefreshTokens = $rotateRefreshTokens;
}

/**
* Complete the refresh token grant
* @param null|array $inputParams
Expand All @@ -119,7 +135,7 @@ public function getRefreshTokenTTL()
public function completeFlow($inputParams = null)
{
// Get the required params
$authParams = $this->authServer->getParam(array('client_id', 'client_secret', 'refresh_token'), 'post', $inputParams);
$authParams = $this->authServer->getParam(array('client_id', 'client_secret', 'refresh_token', 'scope'), 'post', $inputParams);

if (is_null($authParams['client_id'])) {
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_request'), 'client_id'), 0);
Expand Down Expand Up @@ -159,24 +175,69 @@ public function completeFlow($inputParams = null)
$accessToken = SecureKey::make();
$accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL();
$accessTokenExpires = time() + $accessTokenExpiresIn;
$refreshToken = SecureKey::make();
$refreshTokenExpires = time() + $this->getRefreshTokenTTL();

// Associate the new access token with the session
$newAccessTokenId = $this->authServer->getStorage('session')->associateAccessToken($accessTokenDetails['session_id'], $accessToken, $accessTokenExpires);

foreach ($scopes as $scope) {
$this->authServer->getStorage('session')->associateScope($newAccessTokenId, $scope['id']);
if ($this->rotateRefreshTokens === true) {

// Generate a new refresh token
$refreshToken = SecureKey::make();
$refreshTokenExpires = time() + $this->getRefreshTokenTTL();

// Revoke the old refresh token
$this->authServer->getStorage('session')->removeRefreshToken($authParams['refresh_token']);

// Associate the new refresh token with the new access token
$this->authServer->getStorage('session')->associateRefreshToken($newAccessTokenId, $refreshToken, $refreshTokenExpires, $authParams['client_id']);
}

$this->authServer->getStorage('session')->associateRefreshToken($newAccessTokenId, $refreshToken, $refreshTokenExpires, $authParams['client_id']);
// There isn't a request for reduced scopes so assign the original ones (or we're not rotating scopes)
if ( ! isset($authParams['scope'])) {

return array(
foreach ($scopes as $scope) {
$this->authServer->getStorage('session')->associateScope($newAccessTokenId, $scope['id']);
}

} elseif ( isset($authParams['scope']) && $this->rotateRefreshTokens === true) {

// The request is asking for reduced scopes and rotate tokens is enabled
$reqestedScopes = explode($this->authServer->getScopeDelimeter(), $authParams['scope']);

for ($i = 0; $i < count($reqestedScopes); $i++) {
$reqestedScopes[$i] = trim($reqestedScopes[$i]);
if ($reqestedScopes[$i] === '') unset($reqestedScopes[$i]); // Remove any junk scopes
}

// Check that there aren't any new scopes being included
$existingScopes = array();
foreach ($scopes as $s) {
$existingScopes[] = $s['scope'];
}

foreach ($reqestedScopes as $reqScope) {
if ( ! in_array($reqScope, $existingScopes)) {
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_request'), 'scope'), 0);
}

// Associate with the new access token
$scopeDetails = $this->authServer->getStorage('scope')->getScope($reqScope, $authParams['client_id'], $this->identifier);
$this->authServer->getStorage('session')->associateScope($newAccessTokenId, $scopeDetails['id']);
}
}

$response = array(
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'token_type' => 'bearer',
'expires' => $accessTokenExpires,
'expires_in' => $accessTokenExpiresIn
);

if ($this->rotateRefreshTokens === true) {
$response['refresh_token'] = $refreshToken;
}

return $response;
}

}
Loading

0 comments on commit 5d0b295

Please sign in to comment.