diff --git a/sql/oauth2.sql b/sql/oauth2.sql new file mode 100644 index 0000000..0d708e8 --- /dev/null +++ b/sql/oauth2.sql @@ -0,0 +1,95 @@ +CREATE TABLE `oauth_clients` ( + `id` CHAR(40) NOT NULL, + `secret` CHAR(40) NOT NULL, + `name` VARCHAR(255) NOT NULL, + `auto_approve` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `u_oacl_clse_clid` (`secret`,`id`) +) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; + +CREATE TABLE `oauth_client_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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; + +CREATE TABLE `oauth_sessions` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `client_id` char(40) NOT NULL, + `owner_type` enum('user','client') NOT NULL DEFAULT 'user', + `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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; + +CREATE TABLE `oauth_session_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, + 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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; + +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, + 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 COLLATE utf8_unicode_ci; + +CREATE TABLE `oauth_session_redirects` ( + `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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; + +CREATE TABLE `oauth_session_refresh_tokens` ( + `session_access_token_id` int(10) unsigned NOT NULL, + `refresh_token` char(40) NOT NULL, + `refresh_token_expires` int(10) unsigned NOT NULL, + `client_id` char(40) NOT NULL, + PRIMARY KEY (`session_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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; + +CREATE TABLE `oauth_scopes` ( + `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` (`scope`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci; + +CREATE TABLE `oauth_session_token_scopes` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `session_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`), + 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 COLLATE utf8_unicode_ci; + +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 COLLATE utf8_unicode_ci; diff --git a/src/Auth.php b/src/Auth.php deleted file mode 100644 index 8bbadfd..0000000 --- a/src/Auth.php +++ /dev/null @@ -1,45 +0,0 @@ -db->fetch($sql, $parameters); + + if (count($results) === 1) + { + $client = new ClientEntity($this->server); + + $client->hydrate([ + 'id' => $results[0]['id'], + 'name' => $results[0]['name'] + ]); + + return $client; + } + + return null; } 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(); + if (count($result) === 1) { + $client = new ClientEntity($this->server); + $client->hydrate([ + 'id' => $result[0]['id'], + 'name' => $result[0]['name'] + ]); + + return $client; + } + + return null; + */ } } diff --git a/src/OAuth2/Resource.php b/src/OAuth2/Resource.php new file mode 100644 index 0000000..6c1cea3 --- /dev/null +++ b/src/OAuth2/Resource.php @@ -0,0 +1,55 @@ +setSessionStorage(new SessionStorage); + $server->setAccessTokenStorage(new AccessTokenStorage); + $server->setClientStorage(new ClientStorage); + $server->setScopeStorage(new ScopeStorage); + + $passwordGrant = new PasswordGrant; + $passwordGrant->setVerifyCredentialsCallback(function ($username, $password) + { + $user = new User(['email' => $username]); + + return $user->count() + && password_verify($password, $user->record['password']); + }); + + $server->addGrantType($passwordGrant); + + // @todo Add grant types listed in the config. Password is always added + + $response = $server->issueAccessToken(); + } + catch (\Exception $e) + { + // @todo Set error code's accordingly. + + throw new \Exception($e->getMessage(), $e->httpStatusCode); + } + + break; + + default: + throw new \Exception('Not Found.', 404); + break; + } + } +} + diff --git a/src/OAuth2/ScopeStorage.php b/src/OAuth2/ScopeStorage.php index d6c15aa..6cf7532 100644 --- a/src/OAuth2/ScopeStorage.php +++ b/src/OAuth2/ScopeStorage.php @@ -5,7 +5,7 @@ namespace Pickles\OAuth2; use \League\OAuth2\Server\Storage\Adapter; use \League\OAuth2\Server\Storage\ScopeInterface; -class ScopeStorage extends Adapter implements ScopeInterface +class ScopeStorage extends StorageAdapter implements ScopeInterface { public function get($scope, $grant_type = null, $client_id = null) { diff --git a/src/OAuth2/SessionStorage.php b/src/OAuth2/SessionStorage.php index 313d68b..17b1639 100644 --- a/src/OAuth2/SessionStorage.php +++ b/src/OAuth2/SessionStorage.php @@ -9,7 +9,7 @@ use \League\OAuth2\Server\Entity\SessionEntity; use \League\OAuth2\Server\Storage\Adapter; use \League\OAuth2\Server\Storage\SessionInterface; -class SessionStorage extends Adapter implements SessionInterface +class SessionStorage extends StorageAdapter implements SessionInterface { public function getByAccessToken(AccessTokenEntity $access_token) { diff --git a/src/OAuth2/StorageAdapter.php b/src/OAuth2/StorageAdapter.php new file mode 100644 index 0000000..27ab436 --- /dev/null +++ b/src/OAuth2/StorageAdapter.php @@ -0,0 +1,20 @@ +config = Config::getInstance(); + $this->db = Database::getInstance(); + } +} + diff --git a/src/Resource.php b/src/Resource.php index b22d163..a8e04ad 100644 --- a/src/Resource.php +++ b/src/Resource.php @@ -92,6 +92,7 @@ class Resource extends Object } // Check auth if flag is explicitly true or is true for the method + /* if ($this->auth === true || (isset($this->auth[$method]) && $this->auth[$method])) { @@ -122,38 +123,12 @@ class Resource extends Object } break; - case 'oauth2': - /* - if (!Auth::oauth2()) - { - throw new \Exception('Invalid access token.', 401); - } - */ - - $server = new \League\OAuth2\Server\AuthorizationServer; - - $server->setSessionStorage(new OAuth2\SessionStorage); - $server->setAccessTokenStorage(new OAuth2\AccessTokenStorage); - $server->setClientStorage(new OAuth2\ClientStorage); - $server->setScopeStorage(new OAuth2\ScopeStorage); - - $passwordGrant = new \League\OAuth2\Server\Grant\PasswordGrant(); - $passwordGrant->setVerifyCredentialsCallback(function ($username, $password) - { - // implement logic here to validate a username and - // password, return an ID if valid, otherwise return false - return false; - }); - - var_dump(microtime()); - exit('EOF'); - break; - default: throw new \Exception('Invalid authentication strategy.', 401); break; } } + */ // Hack together some new globals if (in_array($method, ['PUT', 'DELETE'])) diff --git a/src/Router.php b/src/Router.php index 45cb0e6..d737f87 100644 --- a/src/Router.php +++ b/src/Router.php @@ -22,7 +22,7 @@ namespace Pickles; * module asks for it, and loads the viewer that the module requested. Default * values are present to make things easier on the user. * - * @usage new Router(); + * @usage new Pickles\Router; */ class Router extends Object { @@ -42,34 +42,42 @@ class Router extends Object // Grabs the requested page $request = $_REQUEST['request']; $components = explode('/', $request); - $version = array_shift($components); $nouns = []; $uids = []; - $_SERVER['__version'] = substr($version, 1); - - // Loops through the components to determine nouns and IDs - foreach ($components as $index => $component) + // Checks if we're trying to rock some OAuth + if ($components[0] == 'oauth') { - if ($index % 2) - { - $uids[end($nouns)] = $component; - } - else - { - $nouns[] = $component; - } + $class = 'Pickles\OAuth2\Resource'; } - - // Creates our class name - array_unshift($nouns, '', $this->config['pickles']['namespace'], 'Resources', $version); - $class = implode('\\', $nouns); - - // @todo Make namespace mandatory - // Strips preceding slashs when there is no namespace - if (strpos($class, '\\\\') === 0) + else { - $class = substr($class, 2); + $version = array_shift($components); + $_SERVER['__version'] = substr($version, 1); + + // Loops through the components to determine nouns and IDs + foreach ($components as $index => $component) + { + if ($index % 2) + { + $uids[end($nouns)] = $component; + } + else + { + $nouns[] = $component; + } + } + + // Creates our class name + array_unshift($nouns, '', $this->config['pickles']['namespace'], 'Resources', $version); + $class = implode('\\', $nouns); + + // @todo Make namespace mandatory + // Strips preceding slashs when there is no namespace + if (strpos($class, '\\\\') === 0) + { + $class = substr($class, 2); + } } // Checks that the file is present and contains our class @@ -86,7 +94,7 @@ class Router extends Object // Creates a resource object if we don't have one if (!isset($resource)) { - $resource = new Resource(); + $resource = new Resource; } $code = $e->getCode();