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 1bd97a5d3..d04dc904d 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 @@ public function getResponseTypes() * @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 504a729a8..99f90a7ff 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -152,7 +152,7 @@ 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()); diff --git a/tests/authorization/AuthCodeGrantTest.php b/tests/authorization/AuthCodeGrantTest.php index 62861b064..fd6b19271 100644 --- a/tests/authorization/AuthCodeGrantTest.php +++ b/tests/authorization/AuthCodeGrantTest.php @@ -156,6 +156,7 @@ public function test_checkAuthoriseParams_missingScopes() $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 f0dfcf7f3..ce5110dfb 100644 --- a/src/League/OAuth2/Server/Grant/ClientCredentials.php +++ b/src/League/OAuth2/Server/Grant/ClientCredentials.php @@ -122,7 +122,7 @@ 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()); diff --git a/src/League/OAuth2/Server/Grant/Password.php b/src/League/OAuth2/Server/Grant/Password.php index e59f5ecfa..da3b9f1e3 100644 --- a/src/League/OAuth2/Server/Grant/Password.php +++ b/src/League/OAuth2/Server/Grant/Password.php @@ -166,7 +166,7 @@ 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()); 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 d04dc904d..fd11316f8 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 @@ 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) { diff --git a/src/League/OAuth2/Server/Grant/AuthCode.php b/src/League/OAuth2/Server/Grant/AuthCode.php index 99f90a7ff..b88370993 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -154,8 +154,12 @@ public function checkAuthoriseParams($inputParams = array()) 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 ce5110dfb..027a51d92 100644 --- a/src/League/OAuth2/Server/Grant/ClientCredentials.php +++ b/src/League/OAuth2/Server/Grant/ClientCredentials.php @@ -124,8 +124,12 @@ public function completeFlow($inputParams = null) 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 da3b9f1e3..eff20f321 100644 --- a/src/League/OAuth2/Server/Grant/Password.php +++ b/src/League/OAuth2/Server/Grant/Password.php @@ -168,8 +168,12 @@ public function completeFlow($inputParams = null) 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 fd6b19271..7cec3dedd 100644 --- a/tests/authorization/AuthCodeGrantTest.php +++ b/tests/authorization/AuthCodeGrantTest.php @@ -197,6 +197,41 @@ public function test_checkAuthoriseParams_defaultScope() )); $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 d6bbb4192..753c73e5e 100644 --- a/tests/authorization/ClientCredentialsGrantTest.php +++ b/tests/authorization/ClientCredentialsGrantTest.php @@ -146,6 +146,47 @@ public function test_issueAccessToken_clientCredentialsGrant_defaultScope() $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 3f5f79faf..a73054f86 100644 --- a/tests/authorization/PasswordGrantTest.php +++ b/tests/authorization/PasswordGrantTest.php @@ -338,6 +338,54 @@ public function test_issueAccessToken_passwordGrant_defaultScope() $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 ca03ac188..e66b22058 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 19b27ec50..0c3d4ec91 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Scope.php +++ b/src/League/OAuth2/Server/Storage/PDO/Scope.php @@ -10,7 +10,7 @@ public function getScope($scope, $clientId = null, $grantType = null) { $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 @@ public function getScope($scope, $clientId = null, $grantType = null) 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 34b35b136..15eb214b5 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 027a51d92..363dfb539 100644 --- a/src/League/OAuth2/Server/Grant/ClientCredentials.php +++ b/src/League/OAuth2/Server/Grant/ClientCredentials.php @@ -149,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']); diff --git a/src/League/OAuth2/Server/Grant/Password.php b/src/League/OAuth2/Server/Grant/Password.php index eff20f321..9cbb90e94 100644 --- a/src/League/OAuth2/Server/Grant/Password.php +++ b/src/League/OAuth2/Server/Grant/Password.php @@ -193,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); 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 3f16b0746..311ce3f12 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -125,6 +125,15 @@ public function validateAccessToken($accessToken) 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 0ac099530..30b0a6e17 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -185,6 +185,20 @@ public function validateAuthCode($clientId, $redirectUri, $authCode); */ 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 cf5dfe3b2..6142f3f29 100644 --- a/src/League/OAuth2/Server/Grant/RefreshToken.php +++ b/src/League/OAuth2/Server/Grant/RefreshToken.php @@ -119,7 +119,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); @@ -159,15 +159,50 @@ public function completeFlow($inputParams = null) $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 ae05dfba3..3f32acae8 100644 --- a/tests/authorization/RefreshTokenTest.php +++ b/tests/authorization/RefreshTokenTest.php @@ -183,6 +183,7 @@ public function test_issueAccessToken_refreshTokenGrant_passedInput() $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 @@ public function test_issueAccessToken_refreshTokenGrant() $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 @@ public function test_issueAccessToken_refreshTokenGrant_customExpiresIn() $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 @@ public function test_issueAccessToken_refreshTokenGrant_customExpiresIn() $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 6142f3f29..78e952695 100644 --- a/src/League/OAuth2/Server/Grant/RefreshToken.php +++ b/src/League/OAuth2/Server/Grant/RefreshToken.php @@ -186,7 +186,7 @@ public function completeFlow($inputParams = null) } // 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 0b2d4eb25..84bb7c769 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 0499ae613..d847aafcf 100644 --- a/src/League/OAuth2/Server/Resource.php +++ b/src/League/OAuth2/Server/Resource.php @@ -243,7 +243,22 @@ public function hasScope($scopes) 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 d847aafcf..be9022084 100644 --- a/src/League/OAuth2/Server/Resource.php +++ b/src/League/OAuth2/Server/Resource.php @@ -250,15 +250,12 @@ protected function determineAccessToken() // 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 508566332..c05966bf3 100644 --- a/tests/resource/ResourceServerTest.php +++ b/tests/resource/ResourceServerTest.php @@ -83,6 +83,24 @@ public function test_determineAccessToken_missingToken() $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 @@ public function test_determineAccessToken_fromHeader() $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 78e952695..7fe22460e 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 @@ 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 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 7fe22460e..19e299d84 100644 --- a/src/League/OAuth2/Server/Grant/RefreshToken.php +++ b/src/League/OAuth2/Server/Grant/RefreshToken.php @@ -124,7 +124,7 @@ public function getRefreshTokenTTL() */ 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 19e299d84..99d759b02 100644 --- a/src/League/OAuth2/Server/Grant/RefreshToken.php +++ b/src/League/OAuth2/Server/Grant/RefreshToken.php @@ -176,24 +176,32 @@ public function completeFlow($inputParams = null) $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 @@ public function completeFlow($inputParams = null) } } - // 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 3f32acae8..12dad3855 100644 --- a/tests/authorization/RefreshTokenTest.php +++ b/tests/authorization/RefreshTokenTest.php @@ -204,7 +204,6 @@ public function test_issueAccessToken_refreshTokenGrant_passedInput() $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 @@ public function test_issueAccessToken_refreshTokenGrant() '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 @@ public function test_issueAccessToken_refreshTokenGrant_customExpiresIn() $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 @@ public function test_issueAccessToken_refreshTokenGrant_newScopes() $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 @@ public function test_issueAccessToken_refreshTokenGrant_badNewScopes() $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 30b0a6e17..521fe750a 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -241,6 +241,19 @@ public function validateRefreshToken($refreshToken, $clientId); 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 521fe750a..36c335fed 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -254,6 +254,36 @@ public function getAccessToken($accessTokenId); * @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 b88370993..50fdad82c 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -193,13 +193,6 @@ 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); @@ -207,7 +200,12 @@ public function newAuthoriseRequest($type, $typeId, $authParams = array()) $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 36c335fed..af4e0e3a6 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -102,17 +102,16 @@ public function associateRefreshToken($accessTokenId, $refreshToken, $expireTime * 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 af4e0e3a6..cd328cbb2 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -133,27 +133,18 @@ public function removeAuthCode($sessionId); * 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 311ce3f12..877165229 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -70,17 +70,18 @@ public function associateRefreshToken($accessTokenId, $refreshToken, $expireTime $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 877165229..d03446d6e 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -161,6 +161,16 @@ public function getAccessToken($accessTokenId) 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 cd328cbb2..af4e0e3a6 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -133,18 +133,27 @@ public function removeAuthCode($sessionId); * 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 af4e0e3a6..1cce0c5bd 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -260,7 +260,7 @@ public function associateAuthCodeScope($sessionId, $scopeId); * 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 @@ public function associateAuthCodeScope($sessionId, $scopeId); * ) * * - * @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 1cce0c5bd..883e87c7c 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -133,7 +133,7 @@ public function removeAuthCode($sessionId); * 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 @@ public function removeAuthCode($sessionId); * * * 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 d03446d6e..af0c6becf 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -171,6 +171,17 @@ public function associateAuthCodeScope($sessionId, $scopeId) $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 af0c6becf..7ecdbff26 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -97,12 +97,12 @@ public function validateAuthCode($clientId, $redirectUri, $authCode) { $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 50fdad82c..471c5a193 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -247,23 +247,24 @@ 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 + // 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 471c5a193..b4d7cbdec 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -253,7 +253,8 @@ public function completeFlow($inputParams = null) 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 @@ public function completeFlow($inputParams = null) $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 e66b22058..3d62612c0 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 3d62612c0..0d56e8c85 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 0d56e8c85..b51a2c4b4 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 b4d7cbdec..70447a41d 100644 --- a/src/League/OAuth2/Server/Grant/AuthCode.php +++ b/src/League/OAuth2/Server/Grant/AuthCode.php @@ -200,7 +200,7 @@ public function newAuthoriseRequest($type, $typeId, $authParams = array()) $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 7ecdbff26..abde8b2bc 100644 --- a/src/League/OAuth2/Server/Storage/PDO/Session.php +++ b/src/League/OAuth2/Server/Storage/PDO/Session.php @@ -161,12 +161,12 @@ public function getAccessToken($accessTokenId) 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 883e87c7c..08cd4c53e 100644 --- a/src/League/OAuth2/Server/Storage/SessionInterface.php +++ b/src/League/OAuth2/Server/Storage/SessionInterface.php @@ -245,14 +245,15 @@ public function getAccessToken($accessTokenId); * 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 b51a2c4b4..552b5e020 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 7cec3dedd..ee6a5af9f 100644 --- a/tests/authorization/AuthCodeGrantTest.php +++ b/tests/authorization/AuthCodeGrantTest.php @@ -376,7 +376,8 @@ function test_newAuthoriseRequest() $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 baf0af003..f4bd2db4f 100644 --- a/tests/authorization/AuthServerTest.php +++ b/tests/authorization/AuthServerTest.php @@ -358,13 +358,14 @@ public function test_issueAccessToken_passedInput() )); $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 @@ public function test_issueAccessToken() $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 @@ public function test_issueAccessToken_customExpiresIn() $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 @@ public function test_issueAccessToken_HTTP_auth() $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 12dad3855..f4882454e 100644 --- a/tests/authorization/RefreshTokenTest.php +++ b/tests/authorization/RefreshTokenTest.php @@ -42,6 +42,8 @@ public function test_issueAccessToken_with_refresh_token() $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 84bb7c769..2d37cdd98 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 4310c1833..f18643fd7 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