From ddefb2ee160173af1923660962271dd1f56e3432 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 9 May 2013 10:02:41 -0700 Subject: [PATCH 01/37] Set the scope parameter to not be required by default. Fixes #43 --- src/League/OAuth2/Server/Authorization.php | 6 +++--- src/League/OAuth2/Server/Grant/AuthCode.php | 2 +- tests/authorization/AuthCodeGrantTest.php | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/League/OAuth2/Server/Authorization.php b/src/League/OAuth2/Server/Authorization.php index 1bd97a5d..d04dc904 100644 --- a/src/League/OAuth2/Server/Authorization.php +++ b/src/League/OAuth2/Server/Authorization.php @@ -59,10 +59,10 @@ 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 + * Default scope to be used if none is provided * @var string */ protected $defaultScope = null; @@ -271,7 +271,7 @@ class Authorization * @param boolean $require * @return void */ - public function requireScopeParam($require = true) + public function requireScopeParam($require = false) { $this->requireScopeParam = $require; } diff --git a/src/League/OAuth2/Server/Grant/AuthCode.php b/src/League/OAuth2/Server/Grant/AuthCode.php index 504a729a..99f90a7f 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -152,7 +152,7 @@ class AuthCode implements GrantTypeInterface { 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()); diff --git a/tests/authorization/AuthCodeGrantTest.php b/tests/authorization/AuthCodeGrantTest.php index 62861b06..fd6b1927 100644 --- a/tests/authorization/AuthCodeGrantTest.php +++ b/tests/authorization/AuthCodeGrantTest.php @@ -156,6 +156,7 @@ class Auth_Code_Grant_Test extends PHPUnit_Framework_TestCase $g = new League\OAuth2\Server\Grant\AuthCode($a); $a->addGrantType($g); $a->addGrantType(new League\OAuth2\Server\Grant\AuthCode($a)); + $a->requireScopeParam(true); $g->checkAuthoriseParams(array( 'client_id' => 1234, From 351c2e97ea5367238e37a17cf9d90c11109e9afc Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 9 May 2013 10:06:26 -0700 Subject: [PATCH 02/37] If scope parameter is required and there are not requested scopes AND there is no default scope set then fail Should have been included in with previous commit --- src/League/OAuth2/Server/Grant/ClientCredentials.php | 2 +- src/League/OAuth2/Server/Grant/Password.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/League/OAuth2/Server/Grant/ClientCredentials.php b/src/League/OAuth2/Server/Grant/ClientCredentials.php index f0dfcf7f..ce5110df 100644 --- a/src/League/OAuth2/Server/Grant/ClientCredentials.php +++ b/src/League/OAuth2/Server/Grant/ClientCredentials.php @@ -122,7 +122,7 @@ class ClientCredentials implements GrantTypeInterface { 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()); diff --git a/src/League/OAuth2/Server/Grant/Password.php b/src/League/OAuth2/Server/Grant/Password.php index e59f5ecf..da3b9f1e 100644 --- a/src/League/OAuth2/Server/Grant/Password.php +++ b/src/League/OAuth2/Server/Grant/Password.php @@ -166,7 +166,7 @@ class Password implements GrantTypeInterface { 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()); From 7035792325c525cbe25b7d621f4558854a9fd02c Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 9 May 2013 10:15:36 -0700 Subject: [PATCH 03/37] Allow for multiple default scopes. Fixes #42 --- src/League/OAuth2/Server/Authorization.php | 6 +-- src/League/OAuth2/Server/Grant/AuthCode.php | 8 +++- .../OAuth2/Server/Grant/ClientCredentials.php | 8 +++- src/League/OAuth2/Server/Grant/Password.php | 8 +++- tests/authorization/AuthCodeGrantTest.php | 35 ++++++++++++++ .../ClientCredentialsGrantTest.php | 41 ++++++++++++++++ tests/authorization/PasswordGrantTest.php | 48 +++++++++++++++++++ 7 files changed, 145 insertions(+), 9 deletions(-) diff --git a/src/League/OAuth2/Server/Authorization.php b/src/League/OAuth2/Server/Authorization.php index d04dc904..fd11316f 100644 --- a/src/League/OAuth2/Server/Authorization.php +++ b/src/League/OAuth2/Server/Authorization.php @@ -62,8 +62,8 @@ class Authorization protected $requireScopeParam = false; /** - * Default scope to be used if none is provided - * @var string + * Default scope(s) to be used if none is provided + * @var string|array */ protected $defaultScope = null; @@ -287,7 +287,7 @@ class Authorization /** * Default scope to be used if none is provided and requireScopeParam is false - * @var string + * @var string|array */ public function setDefaultScope($default = null) { diff --git a/src/League/OAuth2/Server/Grant/AuthCode.php b/src/League/OAuth2/Server/Grant/AuthCode.php index 99f90a7f..b8837099 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -154,8 +154,12 @@ class AuthCode implements GrantTypeInterface { 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(); diff --git a/src/League/OAuth2/Server/Grant/ClientCredentials.php b/src/League/OAuth2/Server/Grant/ClientCredentials.php index ce5110df..027a51d9 100644 --- a/src/League/OAuth2/Server/Grant/ClientCredentials.php +++ b/src/League/OAuth2/Server/Grant/ClientCredentials.php @@ -124,8 +124,12 @@ class ClientCredentials implements GrantTypeInterface { 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(); diff --git a/src/League/OAuth2/Server/Grant/Password.php b/src/League/OAuth2/Server/Grant/Password.php index da3b9f1e..eff20f32 100644 --- a/src/League/OAuth2/Server/Grant/Password.php +++ b/src/League/OAuth2/Server/Grant/Password.php @@ -168,8 +168,12 @@ class Password implements GrantTypeInterface { 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(); diff --git a/tests/authorization/AuthCodeGrantTest.php b/tests/authorization/AuthCodeGrantTest.php index fd6b1927..7cec3ded 100644 --- a/tests/authorization/AuthCodeGrantTest.php +++ b/tests/authorization/AuthCodeGrantTest.php @@ -197,6 +197,41 @@ class Auth_Code_Grant_Test extends PHPUnit_Framework_TestCase )); $this->assertArrayHasKey('scopes', $params); + $this->assertEquals(1, count($params['scopes'])); + } + + public function test_checkAuthoriseParams_defaultScopeArray() + { + $this->client->shouldReceive('getClient')->andReturn(array( + 'client_id' => 1234, + 'client_secret' => 5678, + 'redirect_uri' => 'http://foo/redirect', + 'name' => 'Example Client' + )); + + $this->scope->shouldReceive('getScope')->andReturn(array( + 'id' => 1, + 'scope' => 'foo', + 'name' => 'Foo Name', + 'description' => 'Foo Name Description' + )); + + $a = $this->returnDefault(); + $g = new League\OAuth2\Server\Grant\AuthCode($a); + $a->addGrantType($g); + $a->addGrantType(new League\OAuth2\Server\Grant\AuthCode($a)); + $a->setDefaultScope(array('test.scope', 'test.scope2')); + $a->requireScopeParam(false); + + $params = $g->checkAuthoriseParams(array( + 'client_id' => 1234, + 'redirect_uri' => 'http://foo/redirect', + 'response_type' => 'code', + 'scope' => '' + )); + + $this->assertArrayHasKey('scopes', $params); + $this->assertEquals(2, count($params['scopes'])); } /** diff --git a/tests/authorization/ClientCredentialsGrantTest.php b/tests/authorization/ClientCredentialsGrantTest.php index d6bbb419..753c73e5 100644 --- a/tests/authorization/ClientCredentialsGrantTest.php +++ b/tests/authorization/ClientCredentialsGrantTest.php @@ -146,6 +146,47 @@ class Client_Credentials_Grant_Test extends PHPUnit_Framework_TestCase $this->assertArrayHasKey('expires_in', $v); } + public function test_issueAccessToken_clientCredentialsGrant_defaultScopeArray() + { + $this->scope->shouldReceive('getScope')->andReturn(array( + 'id' => 1, + 'key' => 'foo', + 'name' => 'Foo Name', + 'description' => 'Foo Name Description' + )); + + $this->client->shouldReceive('getClient')->andReturn(array( + 'client_id' => 1234, + 'client_secret' => 5678, + 'redirect_uri' => 'http://foo/redirect', + 'name' => 'Example Client' + )); + + $this->client->shouldReceive('validateRefreshToken')->andReturn(1); + $this->session->shouldReceive('validateAuthCode')->andReturn(1); + $this->session->shouldReceive('createSession')->andReturn(1); + $this->session->shouldReceive('deleteSession')->andReturn(null); + $this->session->shouldReceive('associateScope')->andReturn(null); + $this->session->shouldReceive('associateAccessToken')->andReturn(1); + + $a = $this->returnDefault(); + $a->addGrantType(new League\OAuth2\Server\Grant\ClientCredentials($a)); + $a->requireScopeParam(false); + $a->setDefaultScope(array('foobar', 'barfoo')); + + $v = $a->issueAccessToken(array( + 'grant_type' => 'client_credentials', + 'client_id' => 1234, + 'client_secret' => 5678, + 'scope' => '' + )); + + $this->assertArrayHasKey('access_token', $v); + $this->assertArrayHasKey('token_type', $v); + $this->assertArrayHasKey('expires', $v); + $this->assertArrayHasKey('expires_in', $v); + } + /** * @expectedException League\OAuth2\Server\Exception\ClientException * @expectedExceptionCode 4 diff --git a/tests/authorization/PasswordGrantTest.php b/tests/authorization/PasswordGrantTest.php index 3f5f79fa..a73054f8 100644 --- a/tests/authorization/PasswordGrantTest.php +++ b/tests/authorization/PasswordGrantTest.php @@ -338,6 +338,54 @@ class Password_Grant_Test extends PHPUnit_Framework_TestCase $this->assertArrayHasKey('expires_in', $v); } + public function test_issueAccessToken_passwordGrant_defaultScopeArray() + { + $this->scope->shouldReceive('getScope')->andReturn(array( + 'id' => 1, + 'scope' => 'foo', + 'name' => 'Foo Name', + 'description' => 'Foo Name Description' + )); + + $this->client->shouldReceive('getClient')->andReturn(array( + 'client_id' => 1234, + 'client_secret' => 5678, + 'redirect_uri' => 'http://foo/redirect', + 'name' => 'Example Client' + )); + + $this->client->shouldReceive('validateRefreshToken')->andReturn(1); + $this->session->shouldReceive('validateAuthCode')->andReturn(1); + $this->session->shouldReceive('createSession')->andReturn(1); + $this->session->shouldReceive('deleteSession')->andReturn(null); + $this->session->shouldReceive('updateRefreshToken')->andReturn(null); + $this->session->shouldReceive('associateScope')->andReturn(null); + $this->session->shouldReceive('associateAccessToken')->andReturn(1); + + $testCredentials = function() { return 1; }; + + $a = $this->returnDefault(); + $pgrant = new League\OAuth2\Server\Grant\Password($a); + $pgrant->setVerifyCredentialsCallback($testCredentials); + $a->addGrantType($pgrant); + $a->requireScopeParam(false); + $a->setDefaultScope(array('foobar', 'barfoo')); + + $v = $a->issueAccessToken(array( + 'grant_type' => 'password', + 'client_id' => 1234, + 'client_secret' => 5678, + 'username' => 'foo', + 'password' => 'bar', + 'scope' => '' + )); + + $this->assertArrayHasKey('access_token', $v); + $this->assertArrayHasKey('token_type', $v); + $this->assertArrayHasKey('expires', $v); + $this->assertArrayHasKey('expires_in', $v); + } + public function test_issueAccessToken_passwordGrant_goodScope() { $this->scope->shouldReceive('getScope')->andReturn(array( From d677b765b2a55e8e94556f3a55a4d70a26088ac4 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 9 May 2013 10:23:24 -0700 Subject: [PATCH 04/37] Renamed scopes.key to scopes.scope. Updated ScopeInterface and PDO/Scope. Fixes #45 --- sql/mysql.sql | 12 ++++++------ src/League/OAuth2/Server/Storage/PDO/Scope.php | 4 ++-- src/League/OAuth2/Server/Storage/ScopeInterface.php | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/sql/mysql.sql b/sql/mysql.sql index ca03ac18..e66b2205 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -65,13 +65,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_key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_session_token_scopes` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, diff --git a/src/League/OAuth2/Server/Storage/PDO/Scope.php b/src/League/OAuth2/Server/Storage/PDO/Scope.php index 19b27ec5..0c3d4ec9 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Scope.php +++ b/src/League/OAuth2/Server/Storage/PDO/Scope.php @@ -10,7 +10,7 @@ class Scope implements ScopeInterface { $db = \ezcDbInstance::get(); - $stmt = $db->prepare('SELECT * FROM oauth_scopes WHERE oauth_scopes.key = :scope'); + $stmt = $db->prepare('SELECT * FROM oauth_scopes WHERE oauth_scopes.scope = :scope'); $stmt->bindValue(':scope', $scope); $stmt->execute(); @@ -22,7 +22,7 @@ class Scope implements ScopeInterface return array( 'id' => $row->id, - 'scope' => $row->key, + 'scope' => $row->scope, 'name' => $row->name, 'description' => $row->description ); diff --git a/src/League/OAuth2/Server/Storage/ScopeInterface.php b/src/League/OAuth2/Server/Storage/ScopeInterface.php index 34b35b13..15eb214b 100644 --- a/src/League/OAuth2/Server/Storage/ScopeInterface.php +++ b/src/League/OAuth2/Server/Storage/ScopeInterface.php @@ -19,7 +19,7 @@ interface ScopeInterface * Example SQL query: * * - * SELECT * FROM oauth_scopes WHERE oauth_scopes.key = :scope + * SELECT * FROM oauth_scopes WHERE scope = :scope * * * Response: @@ -28,7 +28,7 @@ interface ScopeInterface * Array * ( * [id] => (int) The scope's ID - * [key] => (string) The scope itself + * [scope] => (string) The scope itself * [name] => (string) The scope's name * [description] => (string) The scope's description * ) From 76f2f6a5e13fcca960959d9a662a0a609941d130 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 9 May 2013 10:43:44 -0700 Subject: [PATCH 05/37] Don't delete old sessions when issuing new access tokens using the Password or Client Credential grants. Fixes #32 --- src/League/OAuth2/Server/Grant/ClientCredentials.php | 3 --- src/League/OAuth2/Server/Grant/Password.php | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/League/OAuth2/Server/Grant/ClientCredentials.php b/src/League/OAuth2/Server/Grant/ClientCredentials.php index 027a51d9..363dfb53 100644 --- a/src/League/OAuth2/Server/Grant/ClientCredentials.php +++ b/src/League/OAuth2/Server/Grant/ClientCredentials.php @@ -149,9 +149,6 @@ class ClientCredentials implements GrantTypeInterface { $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']); diff --git a/src/League/OAuth2/Server/Grant/Password.php b/src/League/OAuth2/Server/Grant/Password.php index eff20f32..9cbb90e9 100644 --- a/src/League/OAuth2/Server/Grant/Password.php +++ b/src/League/OAuth2/Server/Grant/Password.php @@ -193,9 +193,6 @@ class Password implements GrantTypeInterface { $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); From 6d8eb9d05e673a12c91e51e28e7c0f6a01bad958 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 9 May 2013 11:40:29 -0700 Subject: [PATCH 06/37] Added removeRefreshToken method to SessionInterface --- src/League/OAuth2/Server/Storage/PDO/Session.php | 9 +++++++++ .../OAuth2/Server/Storage/SessionInterface.php | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/League/OAuth2/Server/Storage/PDO/Session.php b/src/League/OAuth2/Server/Storage/PDO/Session.php index 3f16b074..311ce3f1 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -125,6 +125,15 @@ class Session implements SessionInterface return ($result === false) ? false : (array) $result; } + public function removeRefreshToken($refreshToken) + { + $db = \ezcDbInstance::get(); + + $stmt = $db->prepare('DELETE FROM `oauth_session_refresh_tokens` WHERE refresh_token = :refreshToken'); + $stmt->bindValue(':refreshToken', $refreshToken); + $stmt->execute(); + } + public function validateRefreshToken($refreshToken, $clientId) { $db = \ezcDbInstance::get(); diff --git a/src/League/OAuth2/Server/Storage/SessionInterface.php b/src/League/OAuth2/Server/Storage/SessionInterface.php index 0ac09953..30b0a6e1 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -185,6 +185,20 @@ interface SessionInterface */ public function validateAccessToken($accessToken); + /** + * Removes a refresh token + * + * Example SQL query: + * + * + * DELETE FROM `oauth_session_refresh_tokens` WHERE refresh_token = :refreshToken + * + * + * @param string $refreshToken The refresh token to be removed + * @return void + */ + public function removeRefreshToken($refreshToken); + /** * Validate a refresh token * From f4bcfee687df85bd4e0b68b1126d99b98abea042 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 9 May 2013 11:41:55 -0700 Subject: [PATCH 07/37] Update associated scopes if requested in refresh access token. Fixes #47 --- .../OAuth2/Server/Grant/RefreshToken.php | 41 ++++++++- tests/authorization/RefreshTokenTest.php | 88 +++++++++++++++++++ 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/src/League/OAuth2/Server/Grant/RefreshToken.php b/src/League/OAuth2/Server/Grant/RefreshToken.php index cf5dfe3b..6142f3f2 100644 --- a/src/League/OAuth2/Server/Grant/RefreshToken.php +++ b/src/League/OAuth2/Server/Grant/RefreshToken.php @@ -119,7 +119,7 @@ class RefreshToken implements GrantTypeInterface { 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); @@ -159,15 +159,50 @@ class RefreshToken implements GrantTypeInterface { $accessToken = SecureKey::make(); $accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL(); $accessTokenExpires = time() + $accessTokenExpiresIn; + + // 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 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']); + // There isn't a request for reduced scopes so assign the original ones + if ( ! isset($authParams['scope'])) { + foreach ($scopes as $scope) { + $this->authServer->getStorage('session')->associateScope($newAccessTokenId, $scope['id']); + } + } else { + + // The request is asking for reduced scopes + $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 = []; + 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']); + } } + // Associate the new refresh token with the new access token $this->authServer->getStorage('session')->associateRefreshToken($newAccessTokenId, $refreshToken, $refreshTokenExpires, $authParams['client_id']); return array( diff --git a/tests/authorization/RefreshTokenTest.php b/tests/authorization/RefreshTokenTest.php index ae05dfba..3f32acae 100644 --- a/tests/authorization/RefreshTokenTest.php +++ b/tests/authorization/RefreshTokenTest.php @@ -183,6 +183,7 @@ class Refresh_Token_test extends PHPUnit_Framework_TestCase $this->session->shouldReceive('updateRefreshToken')->andReturn(null); $this->session->shouldReceive('associateAccessToken')->andReturn(1); $this->session->shouldReceive('associateRefreshToken')->andReturn(1); + $this->session->shouldReceive('removeRefreshToken')->andReturn(1); $this->session->shouldReceive('getAccessToken')->andReturn(null); $this->session->shouldReceive('getScopes')->andReturn(array()); @@ -226,6 +227,7 @@ class Refresh_Token_test extends PHPUnit_Framework_TestCase $this->session->shouldReceive('getScopes')->andReturn(array('id' => 1)); $this->session->shouldReceive('associateAccessToken')->andReturn(1); $this->session->shouldReceive('associateRefreshToken')->andReturn(1); + $this->session->shouldReceive('removeRefreshToken')->andReturn(1); $this->session->shouldReceive('associateScope')->andReturn(null); $a = $this->returnDefault(); @@ -265,6 +267,7 @@ class Refresh_Token_test extends PHPUnit_Framework_TestCase $this->session->shouldReceive('getScopes')->andReturn(array('id' => 1)); $this->session->shouldReceive('associateAccessToken')->andReturn(1); $this->session->shouldReceive('associateRefreshToken')->andReturn(1); + $this->session->shouldReceive('removeRefreshToken')->andReturn(1); $this->session->shouldReceive('associateScope')->andReturn(null); $a = $this->returnDefault(); @@ -290,4 +293,89 @@ class Refresh_Token_test extends PHPUnit_Framework_TestCase $this->assertEquals(30, $v['expires_in']); $this->assertEquals(time()+30, $v['expires']); } + + public function test_issueAccessToken_refreshTokenGrant_newScopes() + { + $this->client->shouldReceive('getClient')->andReturn(array( + 'client_id' => 1234, + 'client_secret' => 5678, + 'redirect_uri' => 'http://foo/redirect', + 'name' => 'Example Client' + )); + + $this->session->shouldReceive('validateRefreshToken')->andReturn(1); + $this->session->shouldReceive('validateAuthCode')->andReturn(1); + $this->session->shouldReceive('updateSession')->andReturn(null); + $this->session->shouldReceive('updateRefreshToken')->andReturn(null); + $this->session->shouldReceive('getAccessToken')->andReturn(null); + $this->session->shouldReceive('getScopes')->andReturn(array(array('id' => 1, 'scope' => 'foo'), array('id' => 2, 'scope' => 'bar'))); + $this->session->shouldReceive('associateAccessToken')->andReturn(1); + $this->session->shouldReceive('associateRefreshToken')->andReturn(1); + $this->session->shouldReceive('removeRefreshToken')->andReturn(1); + $this->session->shouldReceive('associateScope')->andReturn(null); + $this->scope->shouldReceive('getScope')->andReturn(array('id' => 1, 'scope' => 'foo')); + + $a = $this->returnDefault(); + $grant = new League\OAuth2\Server\Grant\RefreshToken($a); + $grant->setAccessTokenTTL(30); + $a->addGrantType($grant); + + $v = $a->issueAccessToken(array( + 'grant_type' => 'refresh_token', + 'client_id' => 1234, + 'client_secret' => 5678, + 'refresh_token' => 'abcdef', + 'scope' => 'foo' + )); + + $this->assertArrayHasKey('access_token', $v); + $this->assertArrayHasKey('token_type', $v); + $this->assertArrayHasKey('expires', $v); + $this->assertArrayHasKey('expires_in', $v); + $this->assertArrayHasKey('refresh_token', $v); + + $this->assertNotEquals($a->getAccessTokenTTL(), $v['expires_in']); + $this->assertNotEquals(time()+$a->getAccessTokenTTL(), $v['expires']); + $this->assertEquals(30, $v['expires_in']); + $this->assertEquals(time()+30, $v['expires']); + } + + /** + * @expectedException League\OAuth2\Server\Exception\ClientException + * @expectedExceptionCode 0 + */ + public function test_issueAccessToken_refreshTokenGrant_badNewScopes() + { + $this->client->shouldReceive('getClient')->andReturn(array( + 'client_id' => 1234, + 'client_secret' => 5678, + 'redirect_uri' => 'http://foo/redirect', + 'name' => 'Example Client' + )); + + $this->session->shouldReceive('validateRefreshToken')->andReturn(1); + $this->session->shouldReceive('validateAuthCode')->andReturn(1); + $this->session->shouldReceive('updateSession')->andReturn(null); + $this->session->shouldReceive('updateRefreshToken')->andReturn(null); + $this->session->shouldReceive('getAccessToken')->andReturn(null); + $this->session->shouldReceive('getScopes')->andReturn(array(array('id' => 1, 'scope' => 'foo'), array('id' => 2, 'scope' => 'bar'))); + $this->session->shouldReceive('associateAccessToken')->andReturn(1); + $this->session->shouldReceive('associateRefreshToken')->andReturn(1); + $this->session->shouldReceive('removeRefreshToken')->andReturn(1); + $this->session->shouldReceive('associateScope')->andReturn(null); + $this->scope->shouldReceive('getScope')->andReturn(array('id' => 1, 'scope' => 'foo')); + + $a = $this->returnDefault(); + $grant = new League\OAuth2\Server\Grant\RefreshToken($a); + $grant->setAccessTokenTTL(30); + $a->addGrantType($grant); + + $a->issueAccessToken(array( + 'grant_type' => 'refresh_token', + 'client_id' => 1234, + 'client_secret' => 5678, + 'refresh_token' => 'abcdef', + 'scope' => 'foobar' + )); + } } \ No newline at end of file From 41a712537084190d9a902b6edd0c000a32668a17 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 9 May 2013 11:48:21 -0700 Subject: [PATCH 08/37] Accidentally used PHP 5.4 style bracket --- src/League/OAuth2/Server/Grant/RefreshToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/League/OAuth2/Server/Grant/RefreshToken.php b/src/League/OAuth2/Server/Grant/RefreshToken.php index 6142f3f2..78e95269 100644 --- a/src/League/OAuth2/Server/Grant/RefreshToken.php +++ b/src/League/OAuth2/Server/Grant/RefreshToken.php @@ -186,7 +186,7 @@ class RefreshToken implements GrantTypeInterface { } // Check that there aren't any new scopes being included - $existingScopes = []; + $existingScopes = array(); foreach ($scopes as $s) { $existingScopes[] = $s['scope']; } From 3e5b4a1735e6a0d7eeb63dbadba4fe5540450d0b Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 10:13:17 -0700 Subject: [PATCH 09/37] Move zetacomponents/database to "suggest" in composer.json. Fixes #51 --- composer.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 0b2d4eb2..84bb7c76 100644 --- a/composer.json +++ b/composer.json @@ -5,8 +5,7 @@ "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" @@ -43,5 +42,7 @@ "League\\OAuth2\\Server": "src/" } }, - "suggest": {} + "suggest": { + "zetacomponents/database": "Allows use of the build in PDO storage classes" + } } From b88ef8256361e11ded874349eff615b5c45aebf3 Mon Sep 17 00:00:00 2001 From: ziege Date: Fri, 10 May 2013 20:00:01 +0200 Subject: [PATCH 10/37] Fixed two probems in access token check 1) The method returned the wrong result in case when the access token itself contained the string "Bearer". 2) When using cURL, the request is sometimes send twice (in my case when the first request returned a 404 error), and the Authorization header of the second request is doubled, so that you get a "Authorization: Bearer XXX, Bearer XXX". This case is checked now. (BTW: Tested with the current PHP version 5.4.15 on Windows.) --- src/League/OAuth2/Server/Resource.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/League/OAuth2/Server/Resource.php b/src/League/OAuth2/Server/Resource.php index 0499ae61..d847aafc 100644 --- a/src/League/OAuth2/Server/Resource.php +++ b/src/League/OAuth2/Server/Resource.php @@ -243,7 +243,22 @@ class Resource protected function determineAccessToken() { if ($header = $this->getRequest()->header('Authorization')) { - $accessToken = trim(str_replace('Bearer', '', $header)); + // Check for special case, because cURL sometimes does an + // internal second request and doubles the authorization header, + // which always resulted in an error. + // + // 1st request: Authorization: Bearer XXX + // 2nd request: Authorization: Bearer XXX, Bearer XXX + if (strpos($header, ',') !== false) { + $accessTokens = array(); + foreach (explode(',', $header) as $header_part) { + $accessTokens[] = trim(preg_replace('/^(?:\s+)?Bearer\s+/', '', $header_part)); + } + // take always the first one + $accessToken = $accessTokens[0]; + } else { + $accessToken = trim(preg_replace('/^(?:\s+)?Bearer\s+/', '', $header)); + } } else { $method = $this->getRequest()->server('REQUEST_METHOD'); $accessToken = $this->getRequest()->{$method}($this->tokenKey); From 8c4019693b1a56e72e27826611bf0244dcdb712a Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 12:57:06 -0700 Subject: [PATCH 11/37] Updated @ziege's patch to overcome awkward access token definition requirement (i.e. access token can have a space in it) and also optimised code. Fixes #52 --- src/League/OAuth2/Server/Resource.php | 11 +++---- tests/resource/ResourceServerTest.php | 41 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/League/OAuth2/Server/Resource.php b/src/League/OAuth2/Server/Resource.php index d847aafc..be902208 100644 --- a/src/League/OAuth2/Server/Resource.php +++ b/src/League/OAuth2/Server/Resource.php @@ -250,15 +250,12 @@ class Resource // 1st request: Authorization: Bearer XXX // 2nd request: Authorization: Bearer XXX, Bearer XXX if (strpos($header, ',') !== false) { - $accessTokens = array(); - foreach (explode(',', $header) as $header_part) { - $accessTokens[] = trim(preg_replace('/^(?:\s+)?Bearer\s+/', '', $header_part)); - } - // take always the first one - $accessToken = $accessTokens[0]; + $headerPart = explode(',', $header); + $accessToken = preg_replace('/^(?:\s+)?Bearer(\s{1})/', '', $headerPart[0]); } else { - $accessToken = trim(preg_replace('/^(?:\s+)?Bearer\s+/', '', $header)); + $accessToken = preg_replace('/^(?:\s+)?Bearer(\s{1})/', '', $header); } + $accessToken = ($accessToken === 'Bearer') ? '' : $accessToken; } else { $method = $this->getRequest()->server('REQUEST_METHOD'); $accessToken = $this->getRequest()->{$method}($this->tokenKey); diff --git a/tests/resource/ResourceServerTest.php b/tests/resource/ResourceServerTest.php index 50856633..c05966bf 100644 --- a/tests/resource/ResourceServerTest.php +++ b/tests/resource/ResourceServerTest.php @@ -83,6 +83,24 @@ class Resource_Server_test extends PHPUnit_Framework_TestCase $method->invoke($s); } + /** + * @expectedException League\OAuth2\Server\Exception\InvalidAccessTokenException + */ + public function test_determineAccessToken_brokenCurlRequest() + { + $_SERVER['HTTP_AUTHORIZATION'] = 'Bearer, Bearer abcdef'; + $request = new League\OAuth2\Server\Util\Request(array(), array(), array(), array(), $_SERVER); + + $s = $this->returnDefault(); + $s->setRequest($request); + + $reflector = new ReflectionClass($s); + $method = $reflector->getMethod('determineAccessToken'); + $method->setAccessible(true); + + $method->invoke($s); + } + public function test_determineAccessToken_fromHeader() { $request = new League\OAuth2\Server\Util\Request(); @@ -106,6 +124,29 @@ class Resource_Server_test extends PHPUnit_Framework_TestCase $this->assertEquals('abcdef', $result); } + public function test_determineAccessToken_fromBrokenCurlHeader() + { + $request = new League\OAuth2\Server\Util\Request(); + + $requestReflector = new ReflectionClass($request); + $param = $requestReflector->getProperty('headers'); + $param->setAccessible(true); + $param->setValue($request, array( + 'Authorization' => 'Bearer abcdef, Bearer abcdef' + )); + $s = $this->returnDefault(); + $s->setRequest($request); + + $reflector = new ReflectionClass($s); + + $method = $reflector->getMethod('determineAccessToken'); + $method->setAccessible(true); + + $result = $method->invoke($s); + + $this->assertEquals('abcdef', $result); + } + public function test_determineAccessToken_fromMethod() { $s = $this->returnDefault(); From 2552b73b171300fcd50f16110876f6bc5df36507 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:00:40 -0700 Subject: [PATCH 12/37] Added rotateRefreshTokens() method --- src/League/OAuth2/Server/Grant/RefreshToken.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/League/OAuth2/Server/Grant/RefreshToken.php b/src/League/OAuth2/Server/Grant/RefreshToken.php index 78e95269..7fe22460 100644 --- a/src/League/OAuth2/Server/Grant/RefreshToken.php +++ b/src/League/OAuth2/Server/Grant/RefreshToken.php @@ -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 @@ -111,6 +117,16 @@ class RefreshToken implements GrantTypeInterface { 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 From eac33d50b3edafc32e1c4d125de1e61c9409c798 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:12:43 -0700 Subject: [PATCH 13/37] Added missing semicolon --- src/League/OAuth2/Server/Grant/RefreshToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/League/OAuth2/Server/Grant/RefreshToken.php b/src/League/OAuth2/Server/Grant/RefreshToken.php index 7fe22460..19e299d8 100644 --- a/src/League/OAuth2/Server/Grant/RefreshToken.php +++ b/src/League/OAuth2/Server/Grant/RefreshToken.php @@ -124,7 +124,7 @@ class RefreshToken implements GrantTypeInterface { */ public function rotateRefreshTokens($rotateRefreshTokens = false) { - $this->rotateRefreshTokens = $rotateRefreshTokens + $this->rotateRefreshTokens = $rotateRefreshTokens; } /** From ce51821043935bcae60356bdf3d9edff182c86cb Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:13:06 -0700 Subject: [PATCH 14/37] If rotateRefreshTokens() is true then associate new access tokens --- .../OAuth2/Server/Grant/RefreshToken.php | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/League/OAuth2/Server/Grant/RefreshToken.php b/src/League/OAuth2/Server/Grant/RefreshToken.php index 19e299d8..99d759b0 100644 --- a/src/League/OAuth2/Server/Grant/RefreshToken.php +++ b/src/League/OAuth2/Server/Grant/RefreshToken.php @@ -176,24 +176,32 @@ class RefreshToken implements GrantTypeInterface { $accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL(); $accessTokenExpires = time() + $accessTokenExpiresIn; - // 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 access token with the session $newAccessTokenId = $this->authServer->getStorage('session')->associateAccessToken($accessTokenDetails['session_id'], $accessToken, $accessTokenExpires); - // There isn't a request for reduced scopes so assign the original ones + 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']); + } + + // There isn't a request for reduced scopes so assign the original ones (or we're not rotating scopes) if ( ! isset($authParams['scope'])) { + foreach ($scopes as $scope) { $this->authServer->getStorage('session')->associateScope($newAccessTokenId, $scope['id']); } - } else { - // The request is asking for reduced scopes + } 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++) { @@ -218,16 +226,18 @@ class RefreshToken implements GrantTypeInterface { } } - // Associate the new refresh token with the new access token - $this->authServer->getStorage('session')->associateRefreshToken($newAccessTokenId, $refreshToken, $refreshTokenExpires, $authParams['client_id']); - - return array( + $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; } } From fdb89fb5e46c537bba469260795d3af3628c54d5 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:13:11 -0700 Subject: [PATCH 15/37] Updated tests --- tests/authorization/RefreshTokenTest.php | 46 ++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/tests/authorization/RefreshTokenTest.php b/tests/authorization/RefreshTokenTest.php index 3f32acae..12dad385 100644 --- a/tests/authorization/RefreshTokenTest.php +++ b/tests/authorization/RefreshTokenTest.php @@ -204,7 +204,6 @@ class Refresh_Token_test extends PHPUnit_Framework_TestCase $this->assertArrayHasKey('token_type', $v); $this->assertArrayHasKey('expires', $v); $this->assertArrayHasKey('expires_in', $v); - $this->assertArrayHasKey('refresh_token', $v); $this->assertEquals($a->getAccessTokenTTL(), $v['expires_in']); $this->assertEquals(time()+$a->getAccessTokenTTL(), $v['expires']); @@ -240,6 +239,48 @@ class Refresh_Token_test extends PHPUnit_Framework_TestCase 'refresh_token' => 'abcdef', )); + $this->assertArrayHasKey('access_token', $v); + $this->assertArrayHasKey('token_type', $v); + $this->assertArrayHasKey('expires', $v); + $this->assertArrayHasKey('expires_in', $v); + + $this->assertEquals($a->getAccessTokenTTL(), $v['expires_in']); + $this->assertEquals(time()+$a->getAccessTokenTTL(), $v['expires']); + } + + public function test_issueAccessToken_refreshTokenGrant_rotateTokens() + { + $this->client->shouldReceive('getClient')->andReturn(array( + 'client_id' => 1234, + 'client_secret' => 5678, + 'redirect_uri' => 'http://foo/redirect', + 'name' => 'Example Client' + )); + + $this->session->shouldReceive('validateRefreshToken')->andReturn(1); + $this->session->shouldReceive('validateAuthCode')->andReturn(1); + $this->session->shouldReceive('updateSession')->andReturn(null); + $this->session->shouldReceive('updateRefreshToken')->andReturn(null); + $this->session->shouldReceive('getAccessToken')->andReturn(null); + $this->session->shouldReceive('getScopes')->andReturn(array('id' => 1)); + $this->session->shouldReceive('associateAccessToken')->andReturn(1); + $this->session->shouldReceive('associateRefreshToken')->andReturn(1); + $this->session->shouldReceive('removeRefreshToken')->andReturn(1); + $this->session->shouldReceive('associateScope')->andReturn(null); + + $a = $this->returnDefault(); + + $rt = new League\OAuth2\Server\Grant\RefreshToken($a); + $rt->rotateRefreshTokens(true); + $a->addGrantType($rt); + + $v = $a->issueAccessToken(array( + 'grant_type' => 'refresh_token', + 'client_id' => 1234, + 'client_secret' => 5678, + 'refresh_token' => 'abcdef', + )); + $this->assertArrayHasKey('access_token', $v); $this->assertArrayHasKey('token_type', $v); $this->assertArrayHasKey('expires', $v); @@ -286,7 +327,6 @@ class Refresh_Token_test extends PHPUnit_Framework_TestCase $this->assertArrayHasKey('token_type', $v); $this->assertArrayHasKey('expires', $v); $this->assertArrayHasKey('expires_in', $v); - $this->assertArrayHasKey('refresh_token', $v); $this->assertNotEquals($a->getAccessTokenTTL(), $v['expires_in']); $this->assertNotEquals(time()+$a->getAccessTokenTTL(), $v['expires']); @@ -318,6 +358,7 @@ class Refresh_Token_test extends PHPUnit_Framework_TestCase $a = $this->returnDefault(); $grant = new League\OAuth2\Server\Grant\RefreshToken($a); $grant->setAccessTokenTTL(30); + $grant->rotateRefreshTokens(true); $a->addGrantType($grant); $v = $a->issueAccessToken(array( @@ -368,6 +409,7 @@ class Refresh_Token_test extends PHPUnit_Framework_TestCase $a = $this->returnDefault(); $grant = new League\OAuth2\Server\Grant\RefreshToken($a); $grant->setAccessTokenTTL(30); + $grant->rotateRefreshTokens(true); $a->addGrantType($grant); $a->issueAccessToken(array( From accb80289f17708d32b5ede8e71020825712fc85 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:50:13 -0700 Subject: [PATCH 16/37] Added associateAuthCodeScope() method --- .../OAuth2/Server/Storage/SessionInterface.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/League/OAuth2/Server/Storage/SessionInterface.php b/src/League/OAuth2/Server/Storage/SessionInterface.php index 30b0a6e1..521fe750 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -241,6 +241,19 @@ interface SessionInterface public function getAccessToken($accessTokenId); /** + * Associate scopes with an auth code (bound to the session) + * + * Example SQL query: + * + * + * INSERT INTO `oauth_session_authcode_scopes` (`session_id`, `scope_id`) VALUES (:sessionId, :scopeId) + * + * + * @param int $sessionId The session ID + * @param int $scopeId The scope ID + * @return void + */ + public function associateAuthCodeScope($sessionId, $scopeId); * Associate a scope with an access token * * Example SQL query: From 9372cc85d0526f1644a00c5657f937b70a56a075 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:50:34 -0700 Subject: [PATCH 17/37] Added getAuthCodeScopes() method --- .../Server/Storage/SessionInterface.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/League/OAuth2/Server/Storage/SessionInterface.php b/src/League/OAuth2/Server/Storage/SessionInterface.php index 521fe750..36c335fe 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -254,6 +254,36 @@ interface SessionInterface * @return void */ public function associateAuthCodeScope($sessionId, $scopeId); + + /** + * Get the scopes associated with an auth code + * + * Example SQL query: + * + * + * SELECT scope_id FROM `oauth_session_authcode_scopes` WHERE session_id = :sessionId + * + * + * Expected response: + * + * + * array( + * array( + * 'scope_id' => (int) + * ), + * array( + * 'scope_id' => (int) + * ), + * ... + * ) + * + * + * @param int $sessionId The session ID + * @return array + */ + public function getAuthCodeScopes($sessionId); + + /** * Associate a scope with an access token * * Example SQL query: From aa8d38108fcdbd343fe111cf642d3483b570aa0b Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:53:21 -0700 Subject: [PATCH 18/37] Associate scopes to auth codes in separate method. Creating an auth code now returns an ID --- src/League/OAuth2/Server/Grant/AuthCode.php | 14 ++++++-------- .../OAuth2/Server/Storage/SessionInterface.php | 9 ++++----- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/League/OAuth2/Server/Grant/AuthCode.php b/src/League/OAuth2/Server/Grant/AuthCode.php index b8837099..50fdad82 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -193,13 +193,6 @@ class AuthCode implements GrantTypeInterface { // 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); @@ -207,7 +200,12 @@ class AuthCode implements GrantTypeInterface { $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, implode(',', $scopeIds)); + + // Associate the scopes to the auth code + foreach ($authParams['scopes'] as $scope) { + $this->authServer->getStorage('session')->associateAuthCodeScope($authCodeId, $scope['id']); + } return $authCode; } diff --git a/src/League/OAuth2/Server/Storage/SessionInterface.php b/src/League/OAuth2/Server/Storage/SessionInterface.php index 36c335fe..af4e0e3a 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -102,17 +102,16 @@ interface SessionInterface * Example SQL query: * * - * INSERT INTO oauth_session_authcodes (session_id, auth_code, auth_code_expires, scope_ids) - * VALUE (:sessionId, :authCode, :authCodeExpires, :scopeIds) + * INSERT INTO oauth_session_authcodes (session_id, auth_code, auth_code_expires) + * VALUE (:sessionId, :authCode, :authCodeExpires) * * * @param int $sessionId The session ID * @param string $authCode The authorization code * @param int $expireTime Unix timestamp of the access token expiry time - * @param string $scopeIds Comma seperated list of scope IDs to be later associated (default = null) - * @return void + * @return int The auth code ID */ - public function associateAuthCode($sessionId, $authCode, $expireTime, $scopeIds = null); + public function associateAuthCode($sessionId, $authCode, $expireTime); /** * Remove an associated authorization token from a session From 51138f8738ac82c537da197e09c311f934259100 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:53:52 -0700 Subject: [PATCH 19/37] Return the session_id for validateAuthCode instead of an array --- .../OAuth2/Server/Storage/SessionInterface.php | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/League/OAuth2/Server/Storage/SessionInterface.php b/src/League/OAuth2/Server/Storage/SessionInterface.php index af4e0e3a..cd328cbb 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -133,27 +133,18 @@ interface SessionInterface * Example SQL query: * * - * SELECT oauth_sessions.id, oauth_session_authcodes.scope_ids FROM oauth_sessions + * SELECT oauth_sessions.id FROM oauth_sessions * JOIN oauth_session_authcodes ON oauth_session_authcodes.`session_id` = oauth_sessions.id - * JOIN oauth_session_redirects ON oauth_session_redirects.`session_id` = oauth_sessions.id WHERE - * oauth_sessions.client_id = :clientId AND oauth_session_authcodes.`auth_code` = :authCode + * JOIN oauth_session_redirects ON oauth_session_redirects.`session_id` = oauth_sessions.id + * WHERE oauth_sessions.client_id = :clientId AND oauth_session_authcodes.`auth_code` = :authCode * AND `oauth_session_authcodes`.`auth_code_expires` >= :time AND * `oauth_session_redirects`.`redirect_uri` = :redirectUri * * - * Expected response: - * - * - * array( - * 'id' => (int), // the session ID - * 'scope_ids' => (string) - * ) - * - * * @param string $clientId The client ID * @param string $redirectUri The redirect URI * @param string $authCode The authorization code - * @return array|bool False if invalid or array as above + * @return int|bool False if invalid or the session ID */ public function validateAuthCode($clientId, $redirectUri, $authCode); From 410ad09b5c2415840c0b43da1f3d139aaa613fa3 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:56:38 -0700 Subject: [PATCH 20/37] Updated PDO associateAuthCode --- src/League/OAuth2/Server/Storage/PDO/Session.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/League/OAuth2/Server/Storage/PDO/Session.php b/src/League/OAuth2/Server/Storage/PDO/Session.php index 311ce3f1..87716522 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -70,17 +70,18 @@ class Session implements SessionInterface $stmt->execute(); } - public function associateAuthCode($sessionId, $authCode, $expireTime, $scopeIds = null) + public function associateAuthCode($sessionId, $authCode, $expireTime) { $db = \ezcDbInstance::get(); - $stmt = $db->prepare('INSERT INTO oauth_session_authcodes (session_id, auth_code, auth_code_expires, scope_ids) - VALUE (:sessionId, :authCode, :authCodeExpires, :scopeIds)'); + $stmt = $db->prepare('INSERT INTO oauth_session_authcodes (session_id, auth_code, auth_code_expires) + VALUE (:sessionId, :authCode, :authCodeExpires)'); $stmt->bindValue(':sessionId', $sessionId); $stmt->bindValue(':authCode', $authCode); $stmt->bindValue(':authCodeExpires', $expireTime); - $stmt->bindValue(':scopeIds', $scopeIds); $stmt->execute(); + + return $db->lastInsertId(); } public function removeAuthCode($sessionId) From 591139f44d8ac150d95ea57e0395dfb4e75ab84a Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:57:12 -0700 Subject: [PATCH 21/37] Added associateAuthCodeScope to PDO --- src/League/OAuth2/Server/Storage/PDO/Session.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/League/OAuth2/Server/Storage/PDO/Session.php b/src/League/OAuth2/Server/Storage/PDO/Session.php index 87716522..d03446d6 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -161,6 +161,16 @@ class Session implements SessionInterface return ($result === false) ? false : (array) $result; } + public function associateAuthCodeScope($sessionId, $scopeId) + { + $db = \ezcDbInstance::get(); + + $stmt = $db->prepare('INSERT INTO `oauth_session_authcode_scopes` (`session_id`, `scope_id`) VALUES (:sessionId, :scopeId)'); + $stmt->bindValue(':sessionId', $sessionId); + $stmt->bindValue(':scopeId', $scopeId); + $stmt->execute(); + } + public function associateScope($accessTokenId, $scopeId) { $db = \ezcDbInstance::get(); From c66c8092f98ae6dd6ea25b978a9cdad528efc657 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 16:57:39 -0700 Subject: [PATCH 22/37] Revert "Return the session_id for validateAuthCode instead of an array" This reverts commit 51138f8738ac82c537da197e09c311f934259100. --- .../OAuth2/Server/Storage/SessionInterface.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/League/OAuth2/Server/Storage/SessionInterface.php b/src/League/OAuth2/Server/Storage/SessionInterface.php index cd328cbb..af4e0e3a 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -133,18 +133,27 @@ interface SessionInterface * Example SQL query: * * - * SELECT oauth_sessions.id FROM oauth_sessions + * SELECT oauth_sessions.id, oauth_session_authcodes.scope_ids FROM oauth_sessions * JOIN oauth_session_authcodes ON oauth_session_authcodes.`session_id` = oauth_sessions.id - * JOIN oauth_session_redirects ON oauth_session_redirects.`session_id` = oauth_sessions.id - * WHERE oauth_sessions.client_id = :clientId AND oauth_session_authcodes.`auth_code` = :authCode + * JOIN oauth_session_redirects ON oauth_session_redirects.`session_id` = oauth_sessions.id WHERE + * oauth_sessions.client_id = :clientId AND oauth_session_authcodes.`auth_code` = :authCode * AND `oauth_session_authcodes`.`auth_code_expires` >= :time AND * `oauth_session_redirects`.`redirect_uri` = :redirectUri * * + * Expected response: + * + * + * array( + * 'id' => (int), // the session ID + * 'scope_ids' => (string) + * ) + * + * * @param string $clientId The client ID * @param string $redirectUri The redirect URI * @param string $authCode The authorization code - * @return int|bool False if invalid or the session ID + * @return array|bool False if invalid or array as above */ public function validateAuthCode($clientId, $redirectUri, $authCode); From 8d06a7b685b2ad105ce8f88aac9e99369e98ddb8 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:06:05 -0700 Subject: [PATCH 23/37] Updated getAuthCodeScopes() in SessionInterface --- src/League/OAuth2/Server/Storage/SessionInterface.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/League/OAuth2/Server/Storage/SessionInterface.php b/src/League/OAuth2/Server/Storage/SessionInterface.php index af4e0e3a..1cce0c5b 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -260,7 +260,7 @@ interface SessionInterface * Example SQL query: * * - * SELECT scope_id FROM `oauth_session_authcode_scopes` WHERE session_id = :sessionId + * SELECT scope_id FROM `oauth_session_authcode_scopes` WHERE oauth_session_authcode_id = :authCodeId * * * Expected response: @@ -277,10 +277,10 @@ interface SessionInterface * ) * * - * @param int $sessionId The session ID + * @param int $oauthSessionAuthCodeId The session ID * @return array */ - public function getAuthCodeScopes($sessionId); + public function getAuthCodeScopes($oauthSessionAuthCodeId); /** * Associate a scope with an access token From 11022e16ef1d3276a3754f7f5ae9b90e79e67343 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:06:44 -0700 Subject: [PATCH 24/37] Updated validateAuthCode() in SessionInterface --- src/League/OAuth2/Server/Storage/SessionInterface.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/League/OAuth2/Server/Storage/SessionInterface.php b/src/League/OAuth2/Server/Storage/SessionInterface.php index 1cce0c5b..883e87c7 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -133,7 +133,7 @@ interface SessionInterface * Example SQL query: * * - * SELECT oauth_sessions.id, oauth_session_authcodes.scope_ids FROM oauth_sessions + * SELECT oauth_sessions.id AS session_id, oauth_session_authcodes.id AS authcode_id FROM oauth_sessions * JOIN oauth_session_authcodes ON oauth_session_authcodes.`session_id` = oauth_sessions.id * JOIN oauth_session_redirects ON oauth_session_redirects.`session_id` = oauth_sessions.id WHERE * oauth_sessions.client_id = :clientId AND oauth_session_authcodes.`auth_code` = :authCode @@ -145,8 +145,8 @@ interface SessionInterface * * * array( - * 'id' => (int), // the session ID - * 'scope_ids' => (string) + * 'session_id' => (int) + * 'authcode_id' => (int) * ) * * From 3ea3eb5ebd5851cd676caca97790220f3effb75d Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:07:06 -0700 Subject: [PATCH 25/37] Implemented getAuthCodeScopes() in PDO Session --- src/League/OAuth2/Server/Storage/PDO/Session.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/League/OAuth2/Server/Storage/PDO/Session.php b/src/League/OAuth2/Server/Storage/PDO/Session.php index d03446d6..af0c6bec 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -171,6 +171,17 @@ class Session implements SessionInterface $stmt->execute(); } + public function getAuthCodeScopes($oauthSessionAuthCodeId) + { + $db = \ezcDbInstance::get(); + + $stmt = $db->prepare('SELECT scope_id FROM `oauth_session_authcode_scopes` WHERE oauth_session_authcode_id = :authCodeId'); + $stmt->bindValue(':authCodeId', $oauthSessionAuthCodeId); + $stmt->execute(); + + return $stmt->fetchAll(); + } + public function associateScope($accessTokenId, $scopeId) { $db = \ezcDbInstance::get(); From a01810d8fa7236d74a91c4938ea84bd024b266e3 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:07:29 -0700 Subject: [PATCH 26/37] Updated validateAuthCode in PDO Session --- src/League/OAuth2/Server/Storage/PDO/Session.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/League/OAuth2/Server/Storage/PDO/Session.php b/src/League/OAuth2/Server/Storage/PDO/Session.php index af0c6bec..7ecdbff2 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -97,12 +97,12 @@ class Session implements SessionInterface { $db = \ezcDbInstance::get(); - $stmt = $db->prepare('SELECT oauth_sessions.id, oauth_session_authcodes.scope_ids FROM oauth_sessions JOIN - oauth_session_authcodes ON oauth_session_authcodes.`session_id` = oauth_sessions.id JOIN - oauth_session_redirects ON oauth_session_redirects.`session_id` = oauth_sessions.id WHERE - oauth_sessions.client_id = :clientId AND oauth_session_authcodes.`auth_code` = :authCode AND - `oauth_session_authcodes`.`auth_code_expires` >= :time AND `oauth_session_redirects`.`redirect_uri` - = :redirectUri'); + $stmt = $db->prepare('SELECT oauth_sessions.id AS session_id, oauth_session_authcodes.id AS authcode_id + FROM oauth_sessions JOIN oauth_session_authcodes ON oauth_session_authcodes.`session_id` + = oauth_sessions.id JOIN oauth_session_redirects ON oauth_session_redirects.`session_id` + = oauth_sessions.id WHERE oauth_sessions.client_id = :clientId AND oauth_session_authcodes.`auth_code` + = :authCode AND `oauth_session_authcodes`.`auth_code_expires` >= :time AND + `oauth_session_redirects`.`redirect_uri` = :redirectUri'); $stmt->bindValue(':clientId', $clientId); $stmt->bindValue(':redirectUri', $redirectUri); $stmt->bindValue(':authCode', $authCode); From 7373f312da626e6f3aae8359cbce020ac8c7343c Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:08:10 -0700 Subject: [PATCH 27/37] Updated variable name --- src/League/OAuth2/Server/Grant/AuthCode.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/League/OAuth2/Server/Grant/AuthCode.php b/src/League/OAuth2/Server/Grant/AuthCode.php index 50fdad82..471c5a19 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -247,23 +247,24 @@ class AuthCode implements GrantTypeInterface { } // 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 + // 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'])) { From ba2dc90f3b334cba6b2026681288e4a5c46a5197 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:08:20 -0700 Subject: [PATCH 28/37] Altered associateScope logic --- src/League/OAuth2/Server/Grant/AuthCode.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/League/OAuth2/Server/Grant/AuthCode.php b/src/League/OAuth2/Server/Grant/AuthCode.php index 471c5a19..b4d7cbde 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -253,7 +253,8 @@ class AuthCode implements GrantTypeInterface { 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(); @@ -267,11 +268,9 @@ class AuthCode implements GrantTypeInterface { $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']); } } From c57c4b1b4f19cbc617f5d6bd812d02b70e7d127c Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:19:53 -0700 Subject: [PATCH 29/37] Fixed key name --- sql/mysql.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/mysql.sql b/sql/mysql.sql index e66b2205..3d62612c 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -70,7 +70,7 @@ CREATE TABLE `oauth_scopes` ( `name` varchar(255) NOT NULL, `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `u_oasc_sc` (`scope_key`) + UNIQUE KEY `u_oasc_sc` (`scope`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_session_token_scopes` ( From 252afddbd33ad19e171011789dd3732c375230b0 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:24:31 -0700 Subject: [PATCH 30/37] Updated oauth_session_authcodes table. Added id field, remove scope_ids field --- sql/mysql.sql | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sql/mysql.sql b/sql/mysql.sql index 3d62612c..0d56e8c8 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -38,12 +38,11 @@ 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_session_redirects` ( From ca599437f61c24d1fed0f3e54ff9a55408818397 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:24:46 -0700 Subject: [PATCH 31/37] Added oauth_session_authcode_scopes --- sql/mysql.sql | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sql/mysql.sql b/sql/mysql.sql index 0d56e8c8..b51a2c4b 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -81,4 +81,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; \ No newline at end of file From d531a37412839afa140ea777037851bf7d986a84 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:26:23 -0700 Subject: [PATCH 32/37] Don't add scope IDs --- src/League/OAuth2/Server/Grant/AuthCode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/League/OAuth2/Server/Grant/AuthCode.php b/src/League/OAuth2/Server/Grant/AuthCode.php index b4d7cbde..70447a41 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -200,7 +200,7 @@ class AuthCode implements GrantTypeInterface { $this->authServer->getStorage('session')->associateRedirectUri($sessionId, $authParams['redirect_uri']); // Associate the auth code - $authCodeId = $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) { From ef4a138237a0f3cb7a2f7be697597771ed3f2233 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:29:28 -0700 Subject: [PATCH 33/37] Fixed associateAuthCodeScope() query --- src/League/OAuth2/Server/Storage/PDO/Session.php | 6 +++--- src/League/OAuth2/Server/Storage/SessionInterface.php | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/League/OAuth2/Server/Storage/PDO/Session.php b/src/League/OAuth2/Server/Storage/PDO/Session.php index 7ecdbff2..abde8b2b 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -161,12 +161,12 @@ class Session implements SessionInterface return ($result === false) ? false : (array) $result; } - public function associateAuthCodeScope($sessionId, $scopeId) + public function associateAuthCodeScope($authCodeId, $scopeId) { $db = \ezcDbInstance::get(); - $stmt = $db->prepare('INSERT INTO `oauth_session_authcode_scopes` (`session_id`, `scope_id`) VALUES (:sessionId, :scopeId)'); - $stmt->bindValue(':sessionId', $sessionId); + $stmt = $db->prepare('INSERT INTO `oauth_session_authcode_scopes` (`oauth_session_authcode_id`, `scope_id`) VALUES (:authCodeId, :scopeId)'); + $stmt->bindValue(':authCodeId', $authCodeId); $stmt->bindValue(':scopeId', $scopeId); $stmt->execute(); } diff --git a/src/League/OAuth2/Server/Storage/SessionInterface.php b/src/League/OAuth2/Server/Storage/SessionInterface.php index 883e87c7..08cd4c53 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -245,14 +245,15 @@ interface SessionInterface * Example SQL query: * * - * INSERT INTO `oauth_session_authcode_scopes` (`session_id`, `scope_id`) VALUES (:sessionId, :scopeId) + * INSERT INTO `oauth_session_authcode_scopes` (`oauth_session_authcode_id`, `scope_id`) VALUES + * (:authCodeId, :scopeId) * * - * @param int $sessionId The session ID - * @param int $scopeId The scope ID + * @param int $authCodeId The auth code ID + * @param int $scopeId The scope ID * @return void */ - public function associateAuthCodeScope($sessionId, $scopeId); + public function associateAuthCodeScope($authCodeId, $scopeId); /** * Get the scopes associated with an auth code From 86fb02d218946b0aa7b319086a8ed146308f05b4 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:32:39 -0700 Subject: [PATCH 34/37] Added cascading relationship between oauth_sessions_authcodes and oauth_sessions --- sql/mysql.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sql/mysql.sql b/sql/mysql.sql index b51a2c4b..552b5e02 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -42,7 +42,9 @@ CREATE TABLE `oauth_session_authcodes` ( `session_id` int(10) unsigned NOT NULL, `auth_code` char(40) NOT NULL, `auth_code_expires` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`) + 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` ( From f5251a6080d3fa9ed3127d66803fac3bcf60ed27 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:39:29 -0700 Subject: [PATCH 35/37] Updated sessions --- tests/authorization/AuthCodeGrantTest.php | 3 ++- tests/authorization/AuthServerTest.php | 11 +++++++++-- tests/authorization/RefreshTokenTest.php | 2 ++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/authorization/AuthCodeGrantTest.php b/tests/authorization/AuthCodeGrantTest.php index 7cec3ded..ee6a5af9 100644 --- a/tests/authorization/AuthCodeGrantTest.php +++ b/tests/authorization/AuthCodeGrantTest.php @@ -376,7 +376,8 @@ class Auth_Code_Grant_Test extends PHPUnit_Framework_TestCase $this->session->shouldReceive('createSession')->andReturn(1); $this->session->shouldReceive('associateScope')->andReturn(null); $this->session->shouldReceive('associateRedirectUri')->andReturn(null); - $this->session->shouldReceive('associateAuthCode')->andReturn(null); + $this->session->shouldReceive('associateAuthCode')->andReturn(1); + $this->session->shouldReceive('associateAuthCodeScope')->andReturn(null); $a = $this->returnDefault(); $g = new League\OAuth2\Server\Grant\AuthCode($a); diff --git a/tests/authorization/AuthServerTest.php b/tests/authorization/AuthServerTest.php index baf0af00..f4bd2db4 100644 --- a/tests/authorization/AuthServerTest.php +++ b/tests/authorization/AuthServerTest.php @@ -358,13 +358,14 @@ class Authorization_Server_test extends PHPUnit_Framework_TestCase )); $this->session->shouldReceive('validateAuthCode')->andReturn(array( - 'id' => 1, - 'scope_ids' => '1' + 'session_id' => 1, + 'authcode_id' => 1 )); $this->session->shouldReceive('updateSession')->andReturn(null); $this->session->shouldReceive('removeAuthCode')->andReturn(null); $this->session->shouldReceive('associateAccessToken')->andReturn(1); $this->session->shouldReceive('associateScope')->andReturn(null); + $this->session->shouldReceive('getAuthCodeScopes')->andReturn(array('scope_id' => 1)); $a = $this->returnDefault(); $a->addGrantType(new League\OAuth2\Server\Grant\AuthCode($a)); @@ -399,6 +400,8 @@ class Authorization_Server_test extends PHPUnit_Framework_TestCase $this->session->shouldReceive('updateSession')->andReturn(null); $this->session->shouldReceive('removeAuthCode')->andReturn(null); $this->session->shouldReceive('associateAccessToken')->andReturn(1); + $this->session->shouldReceive('getAuthCodeScopes')->andReturn(array('scope_id' => 1)); + $this->session->shouldReceive('associateScope')->andReturn(null); $a = $this->returnDefault(); $a->addGrantType(new League\OAuth2\Server\Grant\AuthCode($a)); @@ -436,6 +439,8 @@ class Authorization_Server_test extends PHPUnit_Framework_TestCase $this->session->shouldReceive('updateSession')->andReturn(null); $this->session->shouldReceive('removeAuthCode')->andReturn(null); $this->session->shouldReceive('associateAccessToken')->andReturn(1); + $this->session->shouldReceive('getAuthCodeScopes')->andReturn(array('scope_id' => 1)); + $this->session->shouldReceive('associateScope')->andReturn(null); $a = $this->returnDefault(); $grant = new League\OAuth2\Server\Grant\AuthCode($a); @@ -477,6 +482,8 @@ class Authorization_Server_test extends PHPUnit_Framework_TestCase $this->session->shouldReceive('updateSession')->andReturn(null); $this->session->shouldReceive('removeAuthCode')->andReturn(null); $this->session->shouldReceive('associateAccessToken')->andReturn(1); + $this->session->shouldReceive('getAuthCodeScopes')->andReturn(array('scope_id' => 1)); + $this->session->shouldReceive('associateScope')->andReturn(null); $a = $this->returnDefault(); $a->addGrantType(new League\OAuth2\Server\Grant\AuthCode($a)); diff --git a/tests/authorization/RefreshTokenTest.php b/tests/authorization/RefreshTokenTest.php index 12dad385..f4882454 100644 --- a/tests/authorization/RefreshTokenTest.php +++ b/tests/authorization/RefreshTokenTest.php @@ -42,6 +42,8 @@ class Refresh_Token_test extends PHPUnit_Framework_TestCase $this->session->shouldReceive('removeAuthCode')->andReturn(null); $this->session->shouldReceive('associateAccessToken')->andReturn(1); $this->session->shouldReceive('associateRefreshToken')->andReturn(1); + $this->session->shouldReceive('associateScope')->andReturn(null); + $this->session->shouldReceive('getAuthCodeScopes')->andReturn(array('scope_id' => 1)); $a = $this->returnDefault(); $a->addGrantType(new League\OAuth2\Server\Grant\AuthCode($a)); From 4112913813c2310fa9e4974953c38fde1fea9131 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:58:57 -0700 Subject: [PATCH 36/37] Version bump --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 84bb7c76..2d37cdd9 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "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": { @@ -45,4 +45,4 @@ "suggest": { "zetacomponents/database": "Allows use of the build in PDO storage classes" } -} +} \ No newline at end of file From 9ec5442f9048fd784284fbe7f1fe57c0407e9fb3 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 10 May 2013 17:59:08 -0700 Subject: [PATCH 37/37] Updated changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4310c183..f18643fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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