From 49a713eb35dd69f9fdc98c151d0fb70a39cd7cad Mon Sep 17 00:00:00 2001 From: Josh Sherman Date: Mon, 13 Oct 2014 22:50:43 -0400 Subject: [PATCH 1/9] Finished up password grant --- src/OAuth2/Resource.php | 13 +++++++++---- src/Resource.php | 21 ++------------------- src/Router.php | 6 ++++++ 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/OAuth2/Resource.php b/src/OAuth2/Resource.php index 7f949b9..68a3a73 100644 --- a/src/OAuth2/Resource.php +++ b/src/OAuth2/Resource.php @@ -5,13 +5,12 @@ namespace Pickles\OAuth2; use \League\OAuth2\Server\AuthorizationServer; use \League\OAuth2\Server\Grant\PasswordGrant; use \Pickles\App\Models\User; +use \Pickles\Config; class Resource extends \Pickles\Resource { - public function __construct() + public function POST() { - parent::__construct(); - if (!isset($this->config['oauth'][$_SERVER['__version']])) { throw new \Exception('Forbidden.', 403); @@ -48,7 +47,11 @@ class Resource extends \Pickles\Resource $grant->setVerifyCredentialsCallback(function ($username, $password) { - $user = new User(['email' => $username]); + $user = new User([ + 'conditions' => [ + 'email' => $username, + ], + ]); return $user->count() && password_verify($password, $user->record['password']); @@ -64,6 +67,8 @@ class Resource extends \Pickles\Resource $server->addGrantType($grant); $response = $server->issueAccessToken(); + + return $response; } catch (\Exception $e) { diff --git a/src/Resource.php b/src/Resource.php index 9857cb2..1b94fa2 100644 --- a/src/Resource.php +++ b/src/Resource.php @@ -26,15 +26,6 @@ namespace Pickles; */ class Resource extends Object { - /** - * HTTPS - * - * Whether or not the page should be loaded via HTTP Secure. - * - * @var boolean defaults to false - */ - public $https = false; - /** * Filter * @@ -83,14 +74,6 @@ class Resource extends Object try { - // Determines if we need to serve over HTTP or HTTPS - if (($this->https === true - || (isset($this->https[$method]) && $this->https[$method])) - && (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == false)) - { - throw new \Exception('HTTPS is required.', 400); - } - // Check auth if flag is explicitly true or is true for the method if ($this->auth === true || (isset($this->auth[$method]) && $this->auth[$method])) @@ -101,7 +84,7 @@ class Resource extends Object } } - // Hack together some new globals + // Hacks together some new globals if (in_array($method, ['PUT', 'DELETE'])) { $GLOBALS['_' . $method] = []; @@ -337,7 +320,7 @@ class Resource extends Object { http_response_code($this->status); header('Content-Type: application/json'); - header('X-Powered-By: Pickles v2 - https://picklesphp.com'); + header('X-Powered-By: Pickles (http://picklesphp.com)'); $meta = [ 'status' => $this->status, diff --git a/src/Router.php b/src/Router.php index c41baf7..c9b0a90 100644 --- a/src/Router.php +++ b/src/Router.php @@ -39,6 +39,12 @@ class Router extends Object try { + // Secure by default + if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == false) + { + throw new \Exception('HTTPS is required.', 400); + } + // Grabs the requested page $request = $_REQUEST['request']; $components = explode('/', $request); From c244e02d464d93f35606691c65ad1c434dee7356 Mon Sep 17 00:00:00 2001 From: Josh Sherman Date: Tue, 14 Oct 2014 07:11:03 -0400 Subject: [PATCH 2/9] Implementing storage interfaces --- src/OAuth2/ClientStorage.php | 22 ++++++++++++---------- src/Resource.php | 21 +++++++++++++++++++-- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/OAuth2/ClientStorage.php b/src/OAuth2/ClientStorage.php index fe02603..dddfeda 100644 --- a/src/OAuth2/ClientStorage.php +++ b/src/OAuth2/ClientStorage.php @@ -55,25 +55,27 @@ class ClientStorage extends StorageAdapter implements ClientInterface public function getBySession(SessionEntity $session) { - /* - $result = Capsule::table('oauth_clients') - ->select(['oauth_clients.id', 'oauth_clients.name']) - ->join('oauth_sessions', 'oauth_clients.id', '=', 'oauth_sessions.client_id') - ->where('oauth_sessions.id', $session->getId()) - ->get(); + $sql = 'SELECT oauth_clients.id, oauth_clients.name' + . ' FROM oauth_clients' + . ' JOIN oauth_sessions' + . ' ON oauth_clients.id = oauth_sessions.client_id' + . ' WHERE oauth_sessions.id = ?'; - if (count($result) === 1) { + $results = $this->db->fetch($sql, [$session->getId()]); + + if (count($results) === 1) + { $client = new ClientEntity($this->server); + $client->hydrate([ - 'id' => $result[0]['id'], - 'name' => $result[0]['name'] + 'id' => $results[0]['id'], + 'name' => $results[0]['name'] ]); return $client; } return null; - */ } } diff --git a/src/Resource.php b/src/Resource.php index 1b94fa2..4ca265f 100644 --- a/src/Resource.php +++ b/src/Resource.php @@ -14,6 +14,12 @@ namespace Pickles; +use \League\OAuth2\Server\ResourceServer; +use Pickles\OAuth2\AccessTokenStorage; +use Pickles\OAuth2\ClientStorage; +use Pickles\OAuth2\ScopeStorage; +use Pickles\OAuth2\SessionStorage; + /** * Resource Class * @@ -74,11 +80,22 @@ class Resource extends Object try { - // Check auth if flag is explicitly true or is true for the method + // Checks if auth flag is explicity true or true for the method if ($this->auth === true || (isset($this->auth[$method]) && $this->auth[$method])) { - if (!isset($this->config['oauth2'][$_SERVER['__version']])) + if (isset($this->config['oauth'][$_SERVER['__version']])) + { + $server = new ResourceServer( + new SessionStorage, + new AccessTokenStorage, + new ClientStorage, + new ScopeStorage + ); + + $server->isValidRequest(); + } + else { throw new \Exception('Authentication is not configured properly.', 401); } From dc06f3732074b1b14746069507511484c8a3ac28 Mon Sep 17 00:00:00 2001 From: Josh Sherman Date: Tue, 14 Oct 2014 22:05:52 -0400 Subject: [PATCH 3/9] Updated the interfaces and dropped oauth version Seems the oauth lib's stable version is 3.2, dropped down from the 4 version to that. --- composer.json | 2 +- src/OAuth2/AccessTokenStorage.php | 48 ++++++++++++++++++++++ src/OAuth2/ClientStorage.php | 2 +- src/OAuth2/ScopeStorage.php | 11 +++++ src/OAuth2/SessionStorage.php | 67 +++++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index dd8cc94..da9531c 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ }, "require": { "php": ">=5.4", - "league/oauth2-server": "4.*" + "league/oauth2-server": "3.2.*" }, "autoload": { "psr-4": { diff --git a/src/OAuth2/AccessTokenStorage.php b/src/OAuth2/AccessTokenStorage.php index d6af720..7ecd943 100644 --- a/src/OAuth2/AccessTokenStorage.php +++ b/src/OAuth2/AccessTokenStorage.php @@ -3,6 +3,7 @@ namespace Pickles\OAuth2; use \League\OAuth2\Server\Entity\AbstractTokenEntity; +use \League\OAuth2\Server\Entity\AccessTokenEntity; use \League\OAuth2\Server\Entity\ScopeEntity; use \League\OAuth2\Server\Storage\AccessTokenInterface; @@ -10,27 +11,74 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface { public function get($token) { + $sql = 'SELECT oauth_session_access_tokens.*' + . ' FROM oauth_session_access_tokens' + . ' WHERE access_token = ?' + . ' AND access_token_expires >= ?;'; + $results = $this->db->fetch($sql, [$token, time()]); + + if (count($results) === 1) + { + return (new AccessTokenEntity($this->server)) + ->setId($results[0]['access_token']) + ->setExpireTime($results[0]['access_token_expires']); + } + + return null; } public function getScopes(AbstractTokenEntity $token) { + $sql = 'SELECT oauth_scopes.id, oauth_scopes.description' + . ' FROM oauth_session_token_scopes' + . ' INNER JOIN oauth_scopes' + . ' ON oauth_session_token_scopes.scope_id = oauth_scopes.id' + . ' WHERE oauth_session_token_scopes.session_access_token_id = ?;'; + $results = $this->db->fetch($sql, [$token->getId()]); + $response = []; + + if (count($results) > 0) + { + foreach ($results as $row) + { + $response[] = (new ScopeEntity($this->server))->hydrate([ + 'id' => $row['id'], + 'description' => $row['description'] + ]); + } + } + + return $response; } public function create($token, $expiration, $session_id) { + $sql = 'INSERT INTO oauth_session_access_tokens' + . ' (access_token, session_id, access_token_expires)' + . ' VALUES' + . ' (?, ?, ?);'; + $this->db->execute($sql, [$token, $session_id, $expiration]); } public function associateScope(AbstractTokenEntity $token, ScopeEntity $scope) { + $sql = 'INSERT INTO oauth_session_token_scopes' + . ' (access_token, scope)' + . ' VALUES' + . ' (?, ?);'; + $this->db->execute($sql, [$token->getId(), $scope->getId()]); } public function delete(AbstractTokenEntity $token) { + $sql = 'DELETE FROM oauth_session_token_scopes' + . ' WHERE access_token = ?;'; + $this->db->execute($sql, [$token->getId()]); } } diff --git a/src/OAuth2/ClientStorage.php b/src/OAuth2/ClientStorage.php index dddfeda..8a38ffc 100644 --- a/src/OAuth2/ClientStorage.php +++ b/src/OAuth2/ClientStorage.php @@ -57,7 +57,7 @@ class ClientStorage extends StorageAdapter implements ClientInterface { $sql = 'SELECT oauth_clients.id, oauth_clients.name' . ' FROM oauth_clients' - . ' JOIN oauth_sessions' + . ' INNER JOIN oauth_sessions' . ' ON oauth_clients.id = oauth_sessions.client_id' . ' WHERE oauth_sessions.id = ?'; diff --git a/src/OAuth2/ScopeStorage.php b/src/OAuth2/ScopeStorage.php index 6cf7532..c614eb7 100644 --- a/src/OAuth2/ScopeStorage.php +++ b/src/OAuth2/ScopeStorage.php @@ -9,7 +9,18 @@ class ScopeStorage extends StorageAdapter implements ScopeInterface { public function get($scope, $grant_type = null, $client_id = null) { + $sql = 'SELECT * FROM oauth_scopes WHERE id = ?;'; + $results = $this->db->fetch($sql, [$scope]); + if (count($results) === 0) + { + return null; + } + + return (new ScopeEntity($this->server))->hydrate([ + 'id' => $result[0]['id'], + 'description' => $result[0]['description'], + ]); } } diff --git a/src/OAuth2/SessionStorage.php b/src/OAuth2/SessionStorage.php index 17b1639..68b015a 100644 --- a/src/OAuth2/SessionStorage.php +++ b/src/OAuth2/SessionStorage.php @@ -13,27 +13,94 @@ class SessionStorage extends StorageAdapter implements SessionInterface { public function getByAccessToken(AccessTokenEntity $access_token) { + $sql = 'SELECT oauth_sessions.id, oauth_sessions.owner_type,' + . ' oauth_sessions.owner_id, oauth_sessions.client_id,' + . ' oauth_sessions.client_redirect_uri' + . ' FROM oauth_sessions' + . ' INNER JOIN oauth_session_access_tokens' + . ' ON oauth_session_access_tokens.session_id = oauth_sessions.id' + . ' WHERE oauth_session_access_tokens.access_token = ?;'; + $results = $this->db->fetch($sql, [$access_token->getId()]); + + if (count($results) === 1) + { + $session = new SessionEntity($this->server); + $session->setId($result[0]['id']); + $session->setOwner($result[0]['owner_type'], $result[0]['owner_id']); + + return $session; + } + + return null; } public function getByAuthCode(AuthCodeEntity $auth_code) { + $sql = 'SELECT oauth_sessions.id, oauth_sessions.owner_type,' + . ' oauth_sessions.owner_id, oauth_sessions.client_id,' + . ' oauth_sessions.client_redirect_uri' + . ' FROM oauth_sessions' + . ' INNER JOIN oauth_authcodes' + . ' ON oauth_auth_codes.session_id = oauth_sessions.id' + . ' WHERE oauth_auth_codes.auth_code = ?;'; + $results = $this->db->fetch($sql, [$auth_code->getId()]); + + if (count($results) === 1) + { + $session = new SessionEntity($this->server); + $session->setId($result[0]['id']); + $session->setOwner($result[0]['owner_type'], $result[0]['owner_id']); + + return $session; + } + + return null; } public function getScopes(SessionEntity $session) { + $sql = 'SELECT oauth_sessions.*' + . ' FROM oauth_sessions' + . ' INNER JOIN oauth_session_token_scopes' + . ' ON oauth_sessions.id = oauth_session_token_scopes.session_access_token_id' + . ' INNER JOIN oauth_scopes' + . ' ON oauth_scopes.id = oauth_session_token_scopes.scope_id' + . ' WHERE oauth_sessions.id = ?;'; + $results = $this->db->fetch($sql, [$session->getId()]); + $scopes = []; + + foreach ($results as $scope) + { + $scopes[] = (new ScopeEntity($this->server))->hydrate([ + 'id' => $scope['id'], + 'description' => $scope['description'], + ]); + } + + return $scopes; } public function create($owner_type, $owner_id, $client_id, $client_redirect_uri = null) { + $sql = 'INSERT INTO oauth_sessions' + . ' (owner_type, owner_id, client_id)' + . ' VALUES' + . ' (?, ?, ?);'; + return $this->db->execute($sql, [$owner_type, $owner_id, $client_id]); } public function associateScope(SessionEntity $session, ScopeEntity $scope) { + $sql = 'INSERT INTO oauth_session_token_scopes' + . ' (session_access_token_id, scope_id)' + . ' VALUES' + . ' (?, ?);'; + $this->db->execute($sql, [$session->getId(), $scope->getId()]); } } From 84231d943429371206d3b6fed60298d723441487 Mon Sep 17 00:00:00 2001 From: Josh Sherman Date: Tue, 14 Oct 2014 22:24:46 -0400 Subject: [PATCH 4/9] Swapped oauth lib --- composer.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index da9531c..dd82443 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,14 @@ { - "name": "picklesphp/pickles", + "name": "picklesphp/pickles", "description": "Pickles is a PHP framework for building kick-ass services", - "type": "library", - "keywords": ["framework", "api", "soa", "oauth"], - "homepage": "http://picklesphp.com", - "license": "MIT", + "type": "library", + "keywords": ["framework", "api", "soa", "oauth"], + "homepage": "http://picklesphp.com", + "license": "MIT", "authors": [ { - "name": "Josh Sherman", - "email": "josh@gravityblvd.com", + "name": "Josh Sherman", + "email": "josh@gravityblvd.com", "homepage": "http://joshtronic.com" } ], @@ -18,12 +18,12 @@ }, "minimum-stability" : "dev", "require-dev": { - "phpunit/phpunit": "dev-master", + "phpunit/phpunit": "dev-master", "satooshi/php-coveralls": "dev-master" }, "require": { - "php": ">=5.4", - "league/oauth2-server": "3.2.*" + "php": ">=5.4", + "bshaffer/oauth2-server-php": "v1.5" }, "autoload": { "psr-4": { From 32e7ae5f0fcc3206163d87abf17f815bb2b3be02 Mon Sep 17 00:00:00 2001 From: Josh Sherman Date: Wed, 15 Oct 2014 07:08:39 -0400 Subject: [PATCH 5/9] Switched oauth lib Wasn't pleased to find that the new lib used the username as the primary key across a bunch of tables. Not ideal IMO. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index dd82443..51764cd 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ }, "require": { "php": ">=5.4", - "bshaffer/oauth2-server-php": "v1.5" + "thephpleague/oauth2-server": "4.0.*" }, "autoload": { "psr-4": { From 08284b0f350f8bfb92c463739cb393ea6fc1f371 Mon Sep 17 00:00:00 2001 From: Josh Sherman Date: Wed, 15 Oct 2014 07:18:09 -0400 Subject: [PATCH 6/9] Fixed dependency version --- composer.json | 2 +- composer.lock | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 51764cd..fe830d1 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ }, "require": { "php": ">=5.4", - "thephpleague/oauth2-server": "4.0.*" + "league/oauth2-server": "4.0.x-dev" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index ff3f1b9..5adfeaf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "76708e9b1bd8a87135b6c5b4c0e38a2a", + "hash": "f919c496ec07285f990ccb4efab8cf18", "packages": [ { "name": "league/event", @@ -1434,6 +1434,7 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { + "league/oauth2-server": 20, "phpunit/phpunit": 20, "satooshi/php-coveralls": 20 }, From f235f4a520f787b8905a5d5a2d50387d41cb4342 Mon Sep 17 00:00:00 2001 From: Josh Sherman Date: Wed, 15 Oct 2014 07:48:34 -0400 Subject: [PATCH 7/9] Tweaking the schema --- sql/oauth2.sql | 90 +++++++++++++++++++++---------- src/OAuth2/AccessTokenStorage.php | 22 ++++---- src/OAuth2/ClientStorage.php | 6 +-- src/OAuth2/SessionStorage.php | 20 +++---- 4 files changed, 86 insertions(+), 52 deletions(-) diff --git a/sql/oauth2.sql b/sql/oauth2.sql index 0d708e8..5c4fbc1 100644 --- a/sql/oauth2.sql +++ b/sql/oauth2.sql @@ -7,13 +7,16 @@ CREATE TABLE `oauth_clients` ( UNIQUE KEY `u_oacl_clse_clid` (`secret`,`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; -CREATE TABLE `oauth_client_endpoints` ( +CREATE TABLE `oauth_endpoints` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `client_id` char(40) NOT NULL, `redirect_uri` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `i_oaclen_clid` (`client_id`), - CONSTRAINT `f_oaclen_clid` FOREIGN KEY (`client_id`) REFERENCES `oauth_clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE + CONSTRAINT `f_oaclen_clid` + FOREIGN KEY (`client_id`) + REFERENCES `oauth_clients` (`id`) + ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; CREATE TABLE `oauth_sessions` ( @@ -23,46 +26,64 @@ CREATE TABLE `oauth_sessions` ( `owner_id` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `i_uase_clid_owty_owid` (`client_id`,`owner_type`,`owner_id`), - CONSTRAINT `f_oase_clid` FOREIGN KEY (`client_id`) REFERENCES `oauth_clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE + CONSTRAINT `f_oase_clid` + FOREIGN KEY (`client_id`) + REFERENCES `oauth_clients` (`id`) + ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; -CREATE TABLE `oauth_session_access_tokens` ( +CREATE TABLE `oauth_access_tokens` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `session_id` int(10) unsigned NOT NULL, `access_token` char(40) NOT NULL, - `access_token_expires` int(10) unsigned NOT NULL, + `expires_at` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `u_oaseacto_acto_seid` (`access_token`,`session_id`), KEY `f_oaseto_seid` (`session_id`), - CONSTRAINT `f_oaseto_seid` FOREIGN KEY (`session_id`) REFERENCES `oauth_sessions` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION + CONSTRAINT `f_oaseto_seid` + FOREIGN KEY (`session_id`) + REFERENCES `oauth_sessions` (`id`) + ON DELETE CASCADE ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; -CREATE TABLE `oauth_session_authcodes` ( +CREATE TABLE `oauth_authorization_codes` ( `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, + `authorization_code` char(40) NOT NULL, + `expires_at` int(10) unsigned NOT NULL, 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 + CONSTRAINT `oauth_authorization_codes_ibfk_1` + FOREIGN KEY (`session_id`) + REFERENCES `oauth_sessions` (`id`) + ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; -CREATE TABLE `oauth_session_redirects` ( +CREATE TABLE `oauth_redirect_uris` ( `session_id` int(10) unsigned NOT NULL, `redirect_uri` varchar(255) NOT NULL, PRIMARY KEY (`session_id`), - CONSTRAINT `f_oasere_seid` FOREIGN KEY (`session_id`) REFERENCES `oauth_sessions` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION + CONSTRAINT `f_oasere_seid` + FOREIGN KEY (`session_id`) + REFERENCES `oauth_sessions` (`id`) + ON DELETE CASCADE ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; -CREATE TABLE `oauth_session_refresh_tokens` ( - `session_access_token_id` int(10) unsigned NOT NULL, +CREATE TABLE `oauth_refresh_tokens` ( + `access_token_id` int(10) unsigned NOT NULL, `refresh_token` char(40) NOT NULL, - `refresh_token_expires` int(10) unsigned NOT NULL, + `expires_at` int(10) unsigned NOT NULL, `client_id` char(40) NOT NULL, - PRIMARY KEY (`session_access_token_id`), + PRIMARY KEY (`access_token_id`), KEY `client_id` (`client_id`), - CONSTRAINT `oauth_session_refresh_tokens_ibfk_1` FOREIGN KEY (`client_id`) REFERENCES `oauth_clients` (`id`) ON DELETE CASCADE, - CONSTRAINT `f_oasetore_setoid` FOREIGN KEY (`session_access_token_id`) REFERENCES `oauth_session_access_tokens` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION + CONSTRAINT `oauth_refresh_tokens_ibfk_1` + FOREIGN KEY (`client_id`) + REFERENCES `oauth_clients` (`id`) + ON DELETE CASCADE, + CONSTRAINT `f_oasetore_setoid` + FOREIGN KEY (`access_token_id`) + REFERENCES `oauth_access_tokens` (`id`) + ON DELETE CASCADE ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; CREATE TABLE `oauth_scopes` ( @@ -74,22 +95,35 @@ CREATE TABLE `oauth_scopes` ( UNIQUE KEY `u_oasc_sc` (`scope`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; -CREATE TABLE `oauth_session_token_scopes` ( +CREATE TABLE `oauth_access_token_scopes` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `session_access_token_id` int(10) unsigned DEFAULT NULL, + `access_token_id` int(10) unsigned DEFAULT NULL, `scope_id` smallint(5) unsigned NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `u_setosc_setoid_scid` (`session_access_token_id`,`scope_id`), + UNIQUE KEY `u_setosc_setoid_scid` (`access_token_id`,`scope_id`), 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 + 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 (`access_token_id`) + REFERENCES `oauth_access_tokens` (`id`) + ON DELETE CASCADE ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; -CREATE TABLE `oauth_session_authcode_scopes` ( - `oauth_session_authcode_id` int(10) unsigned NOT NULL, +CREATE TABLE `oauth_authorization_code_scopes` ( + `authorization_code_id` int(10) unsigned NOT NULL, `scope_id` smallint(5) unsigned NOT NULL, - KEY `oauth_session_authcode_id` (`oauth_session_authcode_id`), + KEY `authorization_code_id` (`authorization_code_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 + CONSTRAINT `oauth_authorization_code_scopes_ibfk_2` + FOREIGN KEY (`scope_id`) + REFERENCES `oauth_scopes` (`id`) + ON DELETE CASCADE, + CONSTRAINT `oauth_authorization_code_scopes_ibfk_1` + FOREIGN KEY (`authorization_code_id`) + REFERENCES `oauth_authorization_codes` (`id`) + ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; + diff --git a/src/OAuth2/AccessTokenStorage.php b/src/OAuth2/AccessTokenStorage.php index 7ecd943..04d8dc0 100644 --- a/src/OAuth2/AccessTokenStorage.php +++ b/src/OAuth2/AccessTokenStorage.php @@ -11,10 +11,10 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface { public function get($token) { - $sql = 'SELECT oauth_session_access_tokens.*' - . ' FROM oauth_session_access_tokens' + $sql = 'SELECT oauth_access_tokens.*' + . ' FROM oauth_access_tokens' . ' WHERE access_token = ?' - . ' AND access_token_expires >= ?;'; + . ' AND expires_at >= ?;'; $results = $this->db->fetch($sql, [$token, time()]); @@ -22,7 +22,7 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface { return (new AccessTokenEntity($this->server)) ->setId($results[0]['access_token']) - ->setExpireTime($results[0]['access_token_expires']); + ->setExpireTime($results[0]['expires_at']); } return null; @@ -31,10 +31,10 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface public function getScopes(AbstractTokenEntity $token) { $sql = 'SELECT oauth_scopes.id, oauth_scopes.description' - . ' FROM oauth_session_token_scopes' + . ' FROM oauth_access_token_scopes' . ' INNER JOIN oauth_scopes' - . ' ON oauth_session_token_scopes.scope_id = oauth_scopes.id' - . ' WHERE oauth_session_token_scopes.session_access_token_id = ?;'; + . ' ON oauth_access_token_scopes.scope_id = oauth_scopes.id' + . ' WHERE oauth_access_token_scopes.access_token_id = ?;'; $results = $this->db->fetch($sql, [$token->getId()]); $response = []; @@ -55,8 +55,8 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface public function create($token, $expiration, $session_id) { - $sql = 'INSERT INTO oauth_session_access_tokens' - . ' (access_token, session_id, access_token_expires)' + $sql = 'INSERT INTO oauth_access_tokens' + . ' (access_token, session_id, expires_at)' . ' VALUES' . ' (?, ?, ?);'; @@ -65,7 +65,7 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface public function associateScope(AbstractTokenEntity $token, ScopeEntity $scope) { - $sql = 'INSERT INTO oauth_session_token_scopes' + $sql = 'INSERT INTO oauth_access_token_scopes' . ' (access_token, scope)' . ' VALUES' . ' (?, ?);'; @@ -75,7 +75,7 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface public function delete(AbstractTokenEntity $token) { - $sql = 'DELETE FROM oauth_session_token_scopes' + $sql = 'DELETE FROM oauth_access_token_scopes' . ' WHERE access_token = ?;'; $this->db->execute($sql, [$token->getId()]); diff --git a/src/OAuth2/ClientStorage.php b/src/OAuth2/ClientStorage.php index 8a38ffc..1a32583 100644 --- a/src/OAuth2/ClientStorage.php +++ b/src/OAuth2/ClientStorage.php @@ -16,8 +16,8 @@ class ClientStorage extends StorageAdapter implements ClientInterface if ($redirect_uri) { $sql .= ', oauth_client_redirect_uris.*' - . ' INNER JOIN oauth_client_redirect_uris' - . ' ON oauth_clients.id = oauth_client_redirect_uris.client_id'; + . ' INNER JOIN oauth_redirect_uris' + . ' ON oauth_clients.id = oauth_redirect_uris.client_id'; } $sql .= ' FROM oauth_clients WHERE oauth_clients.id = ?'; @@ -32,7 +32,7 @@ class ClientStorage extends StorageAdapter implements ClientInterface if ($redirect_uri) { - $sql .= 'AND oauth_client_redirect_uris.redirect_uri = ?'; + $sql .= 'AND oauth_redirect_uris.redirect_uri = ?'; $parameters[] = $redirect_uri; } diff --git a/src/OAuth2/SessionStorage.php b/src/OAuth2/SessionStorage.php index 68b015a..22a6786 100644 --- a/src/OAuth2/SessionStorage.php +++ b/src/OAuth2/SessionStorage.php @@ -17,9 +17,9 @@ class SessionStorage extends StorageAdapter implements SessionInterface . ' oauth_sessions.owner_id, oauth_sessions.client_id,' . ' oauth_sessions.client_redirect_uri' . ' FROM oauth_sessions' - . ' INNER JOIN oauth_session_access_tokens' - . ' ON oauth_session_access_tokens.session_id = oauth_sessions.id' - . ' WHERE oauth_session_access_tokens.access_token = ?;'; + . ' INNER JOIN oauth_access_tokens' + . ' ON oauth_access_tokens.session_id = oauth_sessions.id' + . ' WHERE oauth_access_tokens.access_token = ?;'; $results = $this->db->fetch($sql, [$access_token->getId()]); @@ -41,9 +41,9 @@ class SessionStorage extends StorageAdapter implements SessionInterface . ' oauth_sessions.owner_id, oauth_sessions.client_id,' . ' oauth_sessions.client_redirect_uri' . ' FROM oauth_sessions' - . ' INNER JOIN oauth_authcodes' - . ' ON oauth_auth_codes.session_id = oauth_sessions.id' - . ' WHERE oauth_auth_codes.auth_code = ?;'; + . ' INNER JOIN oauth_authorization_codes' + . ' ON oauth_authorization_codes.session_id = oauth_sessions.id' + . ' WHERE oauth_authorization_codes.authorization_code = ?;'; $results = $this->db->fetch($sql, [$auth_code->getId()]); @@ -63,10 +63,10 @@ class SessionStorage extends StorageAdapter implements SessionInterface { $sql = 'SELECT oauth_sessions.*' . ' FROM oauth_sessions' - . ' INNER JOIN oauth_session_token_scopes' - . ' ON oauth_sessions.id = oauth_session_token_scopes.session_access_token_id' + . ' INNER JOIN oauth_access_token_scopes' + . ' ON oauth_sessions.id = oauth_access_token_scopes.access_token_id' . ' INNER JOIN oauth_scopes' - . ' ON oauth_scopes.id = oauth_session_token_scopes.scope_id' + . ' ON oauth_scopes.id = oauth_access_token_scopes.scope_id' . ' WHERE oauth_sessions.id = ?;'; $results = $this->db->fetch($sql, [$session->getId()]); @@ -95,7 +95,7 @@ class SessionStorage extends StorageAdapter implements SessionInterface public function associateScope(SessionEntity $session, ScopeEntity $scope) { - $sql = 'INSERT INTO oauth_session_token_scopes' + $sql = 'INSERT INTO oauth_access_token_scopes' . ' (session_access_token_id, scope_id)' . ' VALUES' . ' (?, ?);'; From 9e2e4f75f392f54ad167fdd254f639833645d6c9 Mon Sep 17 00:00:00 2001 From: Josh Sherman Date: Wed, 15 Oct 2014 07:56:25 -0400 Subject: [PATCH 8/9] Tweaked schema some more. --- src/OAuth2/AccessTokenStorage.php | 22 +++++++++++----------- src/OAuth2/SessionStorage.php | 16 ++++++++-------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/OAuth2/AccessTokenStorage.php b/src/OAuth2/AccessTokenStorage.php index 7ecd943..04d8dc0 100644 --- a/src/OAuth2/AccessTokenStorage.php +++ b/src/OAuth2/AccessTokenStorage.php @@ -11,10 +11,10 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface { public function get($token) { - $sql = 'SELECT oauth_session_access_tokens.*' - . ' FROM oauth_session_access_tokens' + $sql = 'SELECT oauth_access_tokens.*' + . ' FROM oauth_access_tokens' . ' WHERE access_token = ?' - . ' AND access_token_expires >= ?;'; + . ' AND expires_at >= ?;'; $results = $this->db->fetch($sql, [$token, time()]); @@ -22,7 +22,7 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface { return (new AccessTokenEntity($this->server)) ->setId($results[0]['access_token']) - ->setExpireTime($results[0]['access_token_expires']); + ->setExpireTime($results[0]['expires_at']); } return null; @@ -31,10 +31,10 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface public function getScopes(AbstractTokenEntity $token) { $sql = 'SELECT oauth_scopes.id, oauth_scopes.description' - . ' FROM oauth_session_token_scopes' + . ' FROM oauth_access_token_scopes' . ' INNER JOIN oauth_scopes' - . ' ON oauth_session_token_scopes.scope_id = oauth_scopes.id' - . ' WHERE oauth_session_token_scopes.session_access_token_id = ?;'; + . ' ON oauth_access_token_scopes.scope_id = oauth_scopes.id' + . ' WHERE oauth_access_token_scopes.access_token_id = ?;'; $results = $this->db->fetch($sql, [$token->getId()]); $response = []; @@ -55,8 +55,8 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface public function create($token, $expiration, $session_id) { - $sql = 'INSERT INTO oauth_session_access_tokens' - . ' (access_token, session_id, access_token_expires)' + $sql = 'INSERT INTO oauth_access_tokens' + . ' (access_token, session_id, expires_at)' . ' VALUES' . ' (?, ?, ?);'; @@ -65,7 +65,7 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface public function associateScope(AbstractTokenEntity $token, ScopeEntity $scope) { - $sql = 'INSERT INTO oauth_session_token_scopes' + $sql = 'INSERT INTO oauth_access_token_scopes' . ' (access_token, scope)' . ' VALUES' . ' (?, ?);'; @@ -75,7 +75,7 @@ class AccessTokenStorage extends StorageAdapter implements AccessTokenInterface public function delete(AbstractTokenEntity $token) { - $sql = 'DELETE FROM oauth_session_token_scopes' + $sql = 'DELETE FROM oauth_access_token_scopes' . ' WHERE access_token = ?;'; $this->db->execute($sql, [$token->getId()]); diff --git a/src/OAuth2/SessionStorage.php b/src/OAuth2/SessionStorage.php index 68b015a..e9e72f6 100644 --- a/src/OAuth2/SessionStorage.php +++ b/src/OAuth2/SessionStorage.php @@ -17,9 +17,9 @@ class SessionStorage extends StorageAdapter implements SessionInterface . ' oauth_sessions.owner_id, oauth_sessions.client_id,' . ' oauth_sessions.client_redirect_uri' . ' FROM oauth_sessions' - . ' INNER JOIN oauth_session_access_tokens' - . ' ON oauth_session_access_tokens.session_id = oauth_sessions.id' - . ' WHERE oauth_session_access_tokens.access_token = ?;'; + . ' INNER JOIN oauth_access_tokens' + . ' ON oauth_access_tokens.session_id = oauth_sessions.id' + . ' WHERE oauth_access_tokens.access_token = ?;'; $results = $this->db->fetch($sql, [$access_token->getId()]); @@ -63,10 +63,10 @@ class SessionStorage extends StorageAdapter implements SessionInterface { $sql = 'SELECT oauth_sessions.*' . ' FROM oauth_sessions' - . ' INNER JOIN oauth_session_token_scopes' - . ' ON oauth_sessions.id = oauth_session_token_scopes.session_access_token_id' + . ' INNER JOIN oauth_access_token_scopes' + . ' ON oauth_sessions.id = oauth_access_token_scopes.access_token_id' . ' INNER JOIN oauth_scopes' - . ' ON oauth_scopes.id = oauth_session_token_scopes.scope_id' + . ' ON oauth_scopes.id = oauth_access_token_scopes.scope_id' . ' WHERE oauth_sessions.id = ?;'; $results = $this->db->fetch($sql, [$session->getId()]); @@ -95,8 +95,8 @@ class SessionStorage extends StorageAdapter implements SessionInterface public function associateScope(SessionEntity $session, ScopeEntity $scope) { - $sql = 'INSERT INTO oauth_session_token_scopes' - . ' (session_access_token_id, scope_id)' + $sql = 'INSERT INTO oauth_access_token_scopes' + . ' (access_token_id, scope_id)' . ' VALUES' . ' (?, ?);'; From a40041acc689f735ae799c47e2b93fcda6c68b83 Mon Sep 17 00:00:00 2001 From: Josh Sherman Date: Thu, 16 Oct 2014 07:30:32 -0400 Subject: [PATCH 9/9] Implemented refresh tokens Right now it's hardcoded to always return a refresh token when you issue an access token. Should think about making this an optional workflow or committing to it being turned on indefinitely. --- src/OAuth2/RefreshTokenStorage.php | 56 ++++++++++++++++++++++++++++++ src/OAuth2/Resource.php | 7 ++++ 2 files changed, 63 insertions(+) create mode 100644 src/OAuth2/RefreshTokenStorage.php diff --git a/src/OAuth2/RefreshTokenStorage.php b/src/OAuth2/RefreshTokenStorage.php new file mode 100644 index 0000000..4bd6182 --- /dev/null +++ b/src/OAuth2/RefreshTokenStorage.php @@ -0,0 +1,56 @@ += ?;'; + + $results = $this->db->fetch($sql, [$token, time()]); + + if (count($results) === 1) + { + return (new RefreshTokenEntity($this->server)) + ->setId($result[0]['refresh_token']) + ->setExpireTime($result[0]['expires_at']) + ->setAccessTokenId($result[0]['access_token_id']); + } + + return null; + } + + public function create($token, $expiration, $access_token) + { + $sql = 'SELECT id FROM oauth_access_tokens WHERE access_token = ?;'; + $results = $this->db->fetch($sql, [$access_token]); + $token_id = $results[0]['id']; + + $sql = 'INSERT INTO oauth_refresh_tokens' + . ' (refresh_token, access_token_id, expires_at, client_id)' + . ' VALUES' + . ' (?, ?, ?, ?);'; + + $this->db->execute($sql, [ + $token, + $token_id, + $expiration, + $this->server->getRequest()->request->get('client_id', null), + ]); + } + + public function delete(RefreshTokenEntity $token) + { + $sql = 'DELETE FROM oauth_refresh_tokens WHERE refresh_token = ?;'; + + $this->db->execute($sql, [$token->getId()]); + } +} + diff --git a/src/OAuth2/Resource.php b/src/OAuth2/Resource.php index 68a3a73..e34b9b8 100644 --- a/src/OAuth2/Resource.php +++ b/src/OAuth2/Resource.php @@ -4,6 +4,7 @@ namespace Pickles\OAuth2; use \League\OAuth2\Server\AuthorizationServer; use \League\OAuth2\Server\Grant\PasswordGrant; +use \League\OAuth2\Server\Grant\RefreshTokenGrant; use \Pickles\App\Models\User; use \Pickles\Config; @@ -27,6 +28,7 @@ class Resource extends \Pickles\Resource $server->setAccessTokenStorage(new AccessTokenStorage); $server->setClientStorage(new ClientStorage); $server->setScopeStorage(new ScopeStorage); + $server->setRefreshTokenStorage(new RefreshTokenStorage); switch ($_REQUEST['grant_type']) { @@ -44,6 +46,8 @@ class Resource extends \Pickles\Resource case 'password': $grant = new PasswordGrant; + $grant->setAccessTokenTTL(3600); + // @todo ^^^ check config and use that value $grant->setVerifyCredentialsCallback(function ($username, $password) { @@ -66,6 +70,9 @@ class Resource extends \Pickles\Resource $server->addGrantType($grant); + $refreshTokenGrant = new RefreshTokenGrant; + $server->addGrantType($refreshTokenGrant); + $response = $server->issueAccessToken(); return $response;