diff options
| author | Brent Shaffer <bshafs@gmail.com> | 2013-05-28 01:28:48 +0400 |
|---|---|---|
| committer | Brent Shaffer <bshafs@gmail.com> | 2013-05-28 01:28:48 +0400 |
| commit | f58f5adf9ff7f7dbb0e1938bce1e726d94837cc0 (patch) | |
| tree | 4175cff0eeb992de6cf93a70367a94b0f595b426 | |
| parent | d5bb9c8bdbe8ef86c65d6479f81db859f707b865 (diff) | |
| parent | e82dfa379bf021f2b3c00ce7f89a596ae4b0e5d4 (diff) | |
| download | oauth2-server-php-0.8.tar.xz | |
merges develop into trunk for v0.8 tagv0.8
63 files changed, 2018 insertions, 1132 deletions
diff --git a/.travis.yml b/.travis.yml index 517b34c..ad41773 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ php: - 5.2 - 5.3 - 5.4 + - 5.5 env: - SKIP_MONGO_TESTS=1 @@ -3,12 +3,10 @@ oauth2-server-php [](http://travis-ci.org/bshaffer/oauth2-server-php) -A library for implementing an OAuth2 Server in php +An OAuth2.0 Server in PHP! [View the Full Working Demo](http://brentertainment.com/oauth2) ([code](https://github.com/bshaffer/oauth2-server-demo)) -[View the Full Working Demo!](http://brentertainment.com/oauth2) ([code](https://github.com/bshaffer/oauth2-server-demo)) - -Autoloading ------------ +Installation +------------ This library follows the zend [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) standards. A number of autoloaders exist which can autoload this library for that reason, but if you are not using one, you can register the `OAuth2_Autoloader`: @@ -18,12 +16,12 @@ require_once('/path/to/oauth2-server-php/src/OAuth2/Autoloader.php'); OAuth2_Autoloader::register(); ``` -If you use a package library like [Composer](http://getcomposer.php), add the following to `composer.json` +Using [Composer](http://getcomposer.php)? Add the following to `composer.json`: ``` { "require": { - "bshaffer/oauth2-server-php": "v0.7", + "bshaffer/oauth2-server-php": "dev-develop", ... }, ... @@ -32,127 +30,217 @@ If you use a package library like [Composer](http://getcomposer.php), add the fo And then run `composer.phar install` -> Checkout out the tag `v0.7` will ensure your application doesn't break from backwards-compatibility issues, but also this means you -> will not receive the latest changes. To ride the bleeding edge of development, use `dev-develop` instead. +> It is highly recommended you check out the [`v0.8`](https://github.com/bshaffer/oauth2-server-php/tree/v0.8) tag to +> ensure your application doesn't break from backwards-compatibility issues, but also this means you +> will not receive the latest changes. + +Learning OAuth2.0 +----------------- + +If you are new to OAuth2, take a little time first to look at the [Oauth2 Demo Application](http://brentertainment.com/oauth2) and the [source code](https://github.com/bshaffer/oauth2-server-demo), and read up on [OAuth2 Flows](http://drupal.org/node/1958718). For everything else, consult the [OAuth2.0 Specification](http://tools.ietf.org/html/rfc6749) Get Started ----------- -Before getting started, take a look at the [Oauth2 Demo Application](http://brentertainment.com/oauth2) and the [source code](https://github.com/bshaffer/oauth2-server-demo) for a concrete example of this library in action. - -The quickest way to get started is to use the following code, plugging in your database information -to the constructor of `OAuth2_Storage_Pdo`: +Here is an example of a bare-bones OAuth2 Server implementation: ```php $storage = new OAuth2_Storage_Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password)); $server = new OAuth2_Server($storage); -$server->addGrantType(new OAuth2_GrantType_UserCredentials($storage)); // or some other grant type. This is the simplest -$server->handleTokenRequest(OAuth2_Request::createFromGlobals())->send(); +$server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage)); // or any grant type you like! +$server->handleTokenRequest(OAuth2_Request::createFromGlobals(), new OAuth2_Response())->send(); ``` -Let's break this down line by line. The first line is how the OAuth2 data is stored. -There are several built in storage types, for your convenience. To use PDO Storage, -instantiate the `OAuth2_Storage_Pdo` class and provide the database connection arguments: +This library requires you to define a `Storage` object, containing instrutions on how to interact with objects in your storage +layer such as [OAuth Clients](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/ClientInterface.php) and +[Authorization Codes](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/AuthorizationCodeInterface.php). +Built-in storage classes include [PDO](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/Pdo.php), +[Redis](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/Redis.php), and +[Mongo](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/Mongo.php). The interfaces allow (and encourage) +the use of your own Storage objects to fit your application's implementation. + +Once you've created a storage object, pass it to the server object and define which Grant Types your server is to support. See +the list of supported [Grant Types](#grant-types) below. + +The final step, once the Server object is set up, is to handle the incoming request. Consult the [Server Methods](#server-methods), or +follow the [Step-by-Step Walkthrough](#step-by-step-walkthrough) to familiarize yourself with the types of requests involved in +OAuth2.0 workflows. + +Step-by-Step Walkthrough +------------------------ + +The following instructions provide a detailed walkthrough to help you get an OAuth2 server +up and running. To see the codebase of an existing OAuth2 server implementing this library, +check out the [OAuth2 Demo](https://github.com/bshaffer/oauth2-server-demo). + +### Define your Schema + +The quickest way to get started is to use the following schema to create the default database: + +```sql +CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT); +CREATE TABLE oauth_access_tokens (access_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT); +CREATE TABLE oauth_authorization_codes (authorization_code TEXT, client_id TEXT, user_id TEXT, redirect_uri TEXT, expires TIMESTAMP, scope TEXT); +CREATE TABLE oauth_users (username TEXT, password TEXT, first_name TEXT, last_name TEXT); +CREATE TABLE oauth_refresh_tokens (refresh_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT); +``` + +### Create a Token Controller + +The first thing you will do is create the **Token Controller**. This is the URI which returns an OAuth2.0 Token to the client. +Here is an example of a token controller in the file `token.php`: ```php +// error reporting (this is a demo, after all!) +ini_set('display_errors',1);error_reporting(E_ALL); + +// Autoloading (composer is preferred, but for this example let's just do this) +require_once('path/to/oauth2-server-php/src/OAuth2/Autoloader.php'); +OAuth2_Autoloader::register(); + +// $dsn is the Data Source Name for your database, for exmaple "mysql:dbname=my_oauth2_db;host=localhost" $storage = new OAuth2_Storage_Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password)); + +// Pass a storage object or array of storage objects to the OAuth2 server class $server = new OAuth2_Server($storage); -``` -> Note: `$dsn` is the Data Source Name for your database. For example, if you are using MySQL your dsn will -> look something like `mysql:dbname=my_database;host=localhost`. If you are using sqlite, your dsn will look -> something like `sqlite://path/to/my/file.sqlite`. +// Add the "Client Credentials" grant type (it is the simplest of the grant types) +$server->addGrantType(new OAuth2_GrantType_ClientCredentials($storage)); -The next step is to add a grant type. This example uses the "User Credentials" grant type, which grants a token based on -explicit user credentials passed to the request. The [OAuth2 spec](http://tools.ietf.org/html/rfc6749) has no -restrictions on the number of grant types your server can support and therefore you can add more than one grant type to -your $server ... read about this [below](https://github.com/bshaffer/oauth2-server-php#grant-types). Each grant type -also requires storage, so pass the existing storage to the constructor: +// Handle a request for an OAuth2.0 Access Token and send the response to the client +$server->handleTokenRequest(OAuth2_Request::createFromGlobals(), new OAuth2_Response())->send(); +``` -```php -$server->addGrantType(new OAuth2_GrantType_UserCredentials($storage)); +Congratulatons! You have created a **Token Controller**! Do you want to see it in action? Run the following SQL to +create an OAuth Client: + +```sql +INSERT INTO oauth_clients (client_id, client_secret, redirect_uri) VALUES ("testclient", "testpass", "http://fake/"); ``` -Call the `grantAccessToken` method to validate the request for the user credentials grant type. This will return the token -if successful. Access the server's response object to send the successful response back, or the error response if applicable: +Now run the following from the command line: -```php -$server->handleTokenRequest(OAuth2_Request::createFromGlobals())->send(); +```bash +curl -u testclient:testpass http://localhost/token.php -d 'grant_type=client_credentials' ``` -This creates the `OAuth2_Request` object from PHP global variables (most common, you can override this if need be) and sends it to the server -for assessment. The response by default is in json format, and includes the access token if successful, and error codes if not. +> Note: http://localhost/token.php assumes you have the file `token.php` on your local machine, and you have +> set up the "localhost" webhost to point to it. This may vary for your application. -> If you are just getting started, use the SQL below to create the database tables for the default MySQL setup -> ```sql -> CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT); -> CREATE TABLE oauth_access_tokens (access_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT); -> CREATE TABLE oauth_authorization_codes (authorization_code TEXT, client_id TEXT, user_id TEXT, redirect_uri TEXT, expires TIMESTAMP, scope TEXT); -> CREATE TABLE oauth_refresh_tokens (refresh_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT); -> ``` +If everything works, you should receive a response like this: -Server Methods --------------- +```json +{"access_token":"03807cb390319329bdf6c777d4dfae9c0d3b3c35","expires_in":3600,"token_type":"bearer","scope":null} +``` -> ...an end-user (resource owner) can grant a printing -> service (client) access to her protected photos stored at a photo -> sharing service (resource server), without sharing her username and -> password with the printing service. Instead, she authenticates -> directly with a server trusted by the photo sharing service -> (authorization server), which issues the printing service delegation- -> specific credentials (access token). -> -> ~ OAuth2 ([draft #31](http://tools.ietf.org/html/rfc6749#section-1)) +### Create a Resource Controller -Most OAuth2 APIs will have endpoints for `Authorize Requests`, `Token Requests`, and `Resource Requests`. The `OAuth2_Server` object has methods to handle each of these requests. +Now that you are creating tokens, you'll want to validate them in your APIs. Here is an +example of a resource controller in the file `resource.php`: -### Authorize Requests +```php +// error reporting again +ini_set('display_errors',1);error_reporting(E_ALL); -An endpoint requiring the user to authenticate, which redirects back to the client with an `authorization code`. +// Autoloading again +require_once('path/to/oauth2-server-php/src/OAuth2/Autoloader.php'); +OAuth2_Autoloader::register(); -**methods**: +// create your storage again +$storage = new OAuth2_Storage_Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password)); -`handleAuthorizeRequest` - * Receives a request object for an authorize request, returns a response object with the appropriate response +// create your server again +$server = new OAuth2_Server($storage); -`validateAuthorizeRequest` - * Receives a request object, returns false if the incoming request is not a valid Authorize Request. If the request -is valid, returns an array of retrieved client details together with input. -Applications should call this before displaying a login or authorization form to the user +// Handle a request for an OAuth2.0 Access Token and send the response to the client +if (!$server->verifyResourceRequest(OAuth2_Request::createFromGlobals(), new OAuth2_Response())) { + $server->getResponse()->send(); + die; +} +echo json_encode(array('success' => true, 'message' => 'You accessed my APIs!')); +``` -### Token Requests +Now run the following from the command line: -An endpoint which the client uses to exchange the `authorization code` for an `access token`. +```bash +curl http://localhost/resource.php -d 'access_token=YOUR_TOKEN' +``` -**methods**: +> Note: Use the value returned in "access_token" from the previous step in place of YOUR_TOKEN -`grantAccessToken` +If all goes well, you should receive a response like this: - * Receives a request object for a token request, returns a token if the request is valid. +```json +{"success":true,"message":"You accessed my APIs!"} +``` -`handleTokenRequest` +### Create an Authorize Controller - * Receives a request object for a token request, returns a response object for the appropriate response. +Authorize Controllers are the "killer feature" of OAuth2, and allow for your users to authorize +third party applications. Here is an example of an authorize controller in `authorize.php`: -`getClientCredentials` +```php +// error reporting again +ini_set('display_errors',1);error_reporting(E_ALL); - * parses the client credentials from the request and determines if they are valid +// Autoloading again +require_once('path/to/oauth2-server-php/src/OAuth2/Autoloader.php'); +OAuth2_Autoloader::register(); -### Resource Requests +// create your storage again +$storage = new OAuth2_Storage_Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password)); -Any API method requiring oauth2 authentication. The server will validate the incomming request, and then allow -the application to serve back the protected resource. +// create your server again +$server = new OAuth2_Server($storage); -**methods**: +// Add the "Authorization Code" grant type (this is required for authorization flows) +$server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage)); -`verifyResourceRequest` +$request = OAuth2_Request::createFromGlobals(); +$response = new OAuth2_Response(); - * Receives a request object for a resource request, finds the token if it exists, and returns a Boolean for whether -the incomming request is valid +// validate the authorize request +if (!$server->validateAuthorizeRequest($request, $response)) { + $response->send(); + die; +} +// display an authorization form +if (empty($_POST)) { + exit(' +<form method="post"> + <label>Do You Authorize TestClient?</label><br /> + <input type="submit" name="authorized" value="yes"> + <input type="submit" name="authorized" value="no"> +</form>'); +} +// print the authorization code if the user has authorized your client +$is_authorized = ($_POST['authorized'] === 'yes'); +$server->handleAuthorizeRequest($request, $response, $is_authorized); +if ($is_authorized) { + // this is only here so that you get to see your code in the cURL request. Otherwise, we'd redirect back to the client + $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=')+5); + exit("SUCCESS! Authorization Code: $code"); +} +$response->send(); +``` -`getAccessTokenData` +Now paste the following URL in your browser - * Takes a token string as an argument and returns the token data if applicable, or null if the token is invalid +``` +http://localhost/authorize.php?response_type=code&client_id=testclient +``` + +You will be prompted with an authorization form, and receive an authorization code upon clicking "yes" + +> Note: The Authorization Code can now be used to receive an access token from your previously +> created `token.php` endpoint. Just add the following grant type to `token.php`: +> ```php +> $server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage)); +> ``` +> And call this endpoint using the returned authorization code: +> ```bash +> curl -u testclient:testpass http://localhost/token.php -d 'grant_type=authorization_code&redirect_uri=http://fake/&code=YOUR_CODE' +> ``` Grant Types ----------- @@ -184,6 +272,10 @@ Supported grant types are as follows: The client can submit refresh token and recieve a new access token e.g. it may be necessary to do this if the access_token had expired. + 7. [Extension Grant](http://tools.ietf.org/html/rfc6749#section-4.5) + + Create your own grant type by implementing the `OAuth2_GrantTypeInterface` and adding it to the OAuth2 Server object. + When submitting a request for an access_token using either the 'Authorization Code' or 'Resource Owner Password Credential' grant, a refresh_token is provided. However, When using the refresh_token from above to request a new access_token, a new refresh_token is not provided. The spec does not strictly require a refresh_token be granted but it @@ -193,8 +285,7 @@ As a result, the option always_issue_new_refresh_token was added (defaults to FA [OAuth2_GrantType_RefreshToken](src/OAuth2/GrantType/RefreshToken.php) class. So, by default a new refresh token is not issued, but you can easily configure this to do so by setting `'always_issue_new_refresh_token' => true` -If you want to support more than one grant type it is possible to add more than 1 type to the $server as you -initialize, see below. +If you want to support more than one grant type it is possible to add more when the Server object is created: ```php $server->addGrantType(new OAuth2_GrantType_UserCredentials($storage)); @@ -202,7 +293,64 @@ $server->addGrantType(new OAuth2_GrantType_RefreshToken($storage)); $server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage)); ``` -Create a custom grant type by implementing the `OAuth2_GrantTypeInterface` and adding it to the OAuth2 Server object. +Server Methods +-------------- + +> ...an end-user (resource owner) can grant a printing +> service (client) access to her protected photos stored at a photo +> sharing service (resource server), without sharing her username and +> password with the printing service. Instead, she authenticates +> directly with a server trusted by the photo sharing service +> (authorization server), which issues the printing service delegation- +> specific credentials (access token). +> +> ~ OAuth2 ([draft #31](http://tools.ietf.org/html/rfc6749#section-1)) + +Most OAuth2 APIs will have endpoints for `Authorize Requests`, `Token Requests`, and `Resource Requests`. The `OAuth2_Server` object has methods to handle each of these requests. + +### Authorize Requests + +An endpoint requiring the user to authenticate, which redirects back to the client with an `authorization code`. + +**methods**: + +`handleAuthorizeRequest` + * Receives a request object for an authorize request, returns a response object with the appropriate response + +`validateAuthorizeRequest` + * Receives a request object, returns false if the incoming request is not a valid Authorize Request. If the request +is valid, returns an array of retrieved client details together with input. +Applications should call this before displaying a login or authorization form to the user + +### Token Requests + +An endpoint which the client uses to exchange the `authorization code` for an `access token`. + +**methods**: + +`grantAccessToken` + + * Receives a request object for a token request, returns a token if the request is valid. + +`handleTokenRequest` + + * Receives a request object for a token request, returns a response object for the appropriate response. + +### Resource Requests + +Any API method requiring oauth2 authentication. The server will validate the incomming request, and then allow +the application to serve back the protected resource. + +**methods**: + +`verifyResourceRequest` + + * Receives a request object for a resource request, finds the token if it exists, and returns a Boolean for whether +the incomming request is valid + +`getAccessTokenData` + + * Takes a token string as an argument and returns the token data if applicable, or null if the token is invalid The Response Object ------------------- @@ -211,16 +359,19 @@ The response object serves the purpose of making your server OAuth2 compliant. and response body for a valid or invalid oauth request. To use it as it's simplest level, just send the output and exit: ```php +$request = OAuth2_Request::createFromGlobals(); +$response = new OAuth2_Response(); + // will set headers, status code, and json response appropriately for success or failure -$server->grantAccessToken(); -$server->getResponse()->send(); +$server->grantAccessToken($request, $response); +$response->send(); ``` The response object can also be used to customize output. Below, if the request is NOT valid, the error is sent to the browser: ```php -if (!$token = $server->grantAccessToken()) { - $server->getResponse()->send(); +if (!$token = $server->grantAccessToken($request, $response)) { + $response->send(); die(); } echo sprintf('Your token is %s!!', $token); @@ -230,8 +381,7 @@ This will populate the appropriate error headers, and return a json error respon the response object can be used to display the information in any other format: ```php -if (!$token = $server->grantAccessToken()) { - $response = $server->getResponse(); +if (!$token = $server->grantAccessToken($request, $response)) { $parameters = $response->getParameters(); // format as XML header("HTTP/1.1 " . $response->getStatusCode()); @@ -309,10 +459,11 @@ access it: ```php // https://api.example.com/resource-requiring-postonwall-scope $request = OAuth2_Request::createFromGlobals(); +$response = new OAuth2_Response(); $scopeRequired = 'postonwall'; // this resource requires "postonwall" scope -if (!$server->verifyResourceRequest($request, $scopeRequired)) { +if (!$server->verifyResourceRequest($request, $response, $scopeRequired)) { // if the scope required is different from what the token allows, this will send a "401 insufficient_scope" error - $server->getRequest()->send(); + $response->send(); } ``` diff --git a/src/OAuth2/ClientAssertionType/HttpBasic.php b/src/OAuth2/ClientAssertionType/HttpBasic.php index aa1d130..db376b0 100644 --- a/src/OAuth2/ClientAssertionType/HttpBasic.php +++ b/src/OAuth2/ClientAssertionType/HttpBasic.php @@ -5,11 +5,11 @@ * * @author Brent Shaffer <bshafs at gmail dot com> */ -class OAuth2_ClientAssertionType_HttpBasic implements OAuth2_ClientAssertionTypeInterface, OAuth2_Response_ProviderInterface +class OAuth2_ClientAssertionType_HttpBasic implements OAuth2_ClientAssertionTypeInterface { - private $response; private $storage; private $config; + private $clientData; public function __construct(OAuth2_Storage_ClientCredentialsInterface $storage, array $config = array()) { @@ -19,34 +19,36 @@ class OAuth2_ClientAssertionType_HttpBasic implements OAuth2_ClientAssertionType $this->storage = $storage; } - public function getClientDataFromRequest(OAuth2_RequestInterface $request) + public function validateRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { - if (!$clientData = $this->getClientCredentials($request)) { - return null; - } - - if (!isset($clientData['client_id']) || !isset($clientData['client_secret'])) { - throw new LogicException('the clientData array must have "client_id" and "client_secret" values set.'); - } + if (!$clientData = $this->getClientCredentials($request, $response)) { + return false; + } - return $clientData; - } + if (!isset($clientData['client_id']) || !isset($clientData['client_secret'])) { + throw new LogicException('the clientData array must have "client_id" and "client_secret" values set.'); + } - public function validateClientData(array $clientData, $grantTypeIdentifier) - { if ($this->storage->checkClientCredentials($clientData['client_id'], $clientData['client_secret']) === false) { - $this->response = new OAuth2_Response_Error(400, 'invalid_client', 'The client credentials are invalid'); + $response->setError(400, 'invalid_client', 'The client credentials are invalid'); return false; } - if (!$this->storage->checkRestrictedGrantType($clientData['client_id'], $grantTypeIdentifier)) { - $this->response = new OAuth2_Response_Error(400, 'unauthorized_client', 'The grant type is unauthorized for this client_id'); + if (!$this->storage->checkRestrictedGrantType($clientData['client_id'], $request->request('grant_type'))) { + $response->setError(400, 'unauthorized_client', 'The grant type is unauthorized for this client_id'); return false; } + $this->clientData = $clientData; + return true; } + public function getClientId() + { + return $this->clientData['client_id']; + } + /** * Internal function used to get the client credentials from HTTP basic * auth or POST data. @@ -67,7 +69,7 @@ class OAuth2_ClientAssertionType_HttpBasic implements OAuth2_ClientAssertionType * * @ingroup oauth2_section_2 */ - public function getClientCredentials(OAuth2_RequestInterface $request) + public function getClientCredentials(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response = null) { if (!is_null($request->headers('PHP_AUTH_USER')) && !is_null($request->headers('PHP_AUTH_PW'))) { return array('client_id' => $request->headers('PHP_AUTH_USER'), 'client_secret' => $request->headers('PHP_AUTH_PW')); @@ -80,12 +82,10 @@ class OAuth2_ClientAssertionType_HttpBasic implements OAuth2_ClientAssertionType } } - $this->response = new OAuth2_Response_Error(400, 'invalid_client', 'Client credentials were not found in the headers or body'); - return null; - } + if ($response) { + $response->setError(400, 'invalid_client', 'Client credentials were not found in the headers or body'); + } - public function getResponse() - { - return $this->response; + return null; } }
\ No newline at end of file diff --git a/src/OAuth2/ClientAssertionTypeInterface.php b/src/OAuth2/ClientAssertionTypeInterface.php index 9d034c4..93f6005 100644 --- a/src/OAuth2/ClientAssertionTypeInterface.php +++ b/src/OAuth2/ClientAssertionTypeInterface.php @@ -1,8 +1,11 @@ <?php -interface OAuth2_ClientAssertionTypeInterface +/** + * Interface for all OAuth2 Client Assertion Types + * + * @see OAuth2_CompatibilityInterface + */ +interface OAuth2_ClientAssertionTypeInterface extends OAuth2_CompatibilityInterface { - public function getClientDataFromRequest(OAuth2_RequestInterface $request); - public function validateClientData(array $clientData, $grantTypeIdentifier); } diff --git a/src/OAuth2/CompatibilityInterface.php b/src/OAuth2/CompatibilityInterface.php new file mode 100644 index 0000000..6707573 --- /dev/null +++ b/src/OAuth2/CompatibilityInterface.php @@ -0,0 +1,16 @@ +<?php + +/** + * OAuth2_CompatibilityInterface + * This interface exists because PHP 5.2 cannot combine matching + * functions from separate interfaces. + * + * Even though OAuth2_ClientAssertionTypeInterface and OAuth2_GrantTypeInterface + * have common methods, it doesn't actually make sense to extend a base interface. + * However, this is temporarily required for the library to run in PHP 5.2 + * + */ +interface OAuth2_CompatibilityInterface +{ + +}
\ No newline at end of file diff --git a/src/OAuth2/Controller/AuthorizeController.php b/src/OAuth2/Controller/AuthorizeController.php index 7075c3d..701e886 100644 --- a/src/OAuth2/Controller/AuthorizeController.php +++ b/src/OAuth2/Controller/AuthorizeController.php @@ -1,11 +1,10 @@ <?php /** - * @see OAuth2_Controller_AuthorizeControllerInterface + * @see OAuth2_Controller_AuthorizeControllerInterface */ class OAuth2_Controller_AuthorizeController implements OAuth2_Controller_AuthorizeControllerInterface { - private $response; private $clientStorage; private $responseTypes; private $config; @@ -26,7 +25,7 @@ class OAuth2_Controller_AuthorizeController implements OAuth2_Controller_Authori $this->scopeUtil = $scopeUtil; } - public function handleAuthorizeRequest(OAuth2_RequestInterface $request, $is_authorized, $user_id = null) + public function handleAuthorizeRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response, $is_authorized, $user_id = null) { if (!is_bool($is_authorized)) { throw new InvalidArgumentException('Argument "is_authorized" must be a boolean. This method must know if the user has granted access to the client.'); @@ -34,45 +33,42 @@ class OAuth2_Controller_AuthorizeController implements OAuth2_Controller_Authori // We repeat this, because we need to re-validate. In theory, this could be POSTed // by a 3rd-party (because we are not internally enforcing NONCEs, etc) - if (!$params = $this->validateAuthorizeRequest($request)) { - return $this->response; + if (!$params = $this->validateAuthorizeRequest($request, $response)) { + return; } if ($is_authorized === false) { - $this->response = new OAuth2_Response_Redirect($params['redirect_uri'], 302, 'access_denied', "The user denied access to your application", $params['state']); - return $this->response; + $response->setRedirect(302, $params['redirect_uri'], $params['state'], 'access_denied', "The user denied access to your application"); + return; } - if (!$authResult = $this->responseTypes[$params['response_type']]->getAuthorizeResponse($params, $user_id)) { - // an error has occurred along the way, return error response - return $this->response; - } + $authResult = $this->responseTypes[$params['response_type']]->getAuthorizeResponse($params, $user_id); - list($redirect_uri, $result) = $authResult; - $uri = $this->buildUri($redirect_uri, $result); + list($redirect_uri, $uri_params) = $authResult; + $uri = $this->buildUri($redirect_uri, $uri_params); // return redirect response - return $this->response = new OAuth2_Response_Redirect($uri); + $response->setRedirect(302, $uri); } - public function validateAuthorizeRequest(OAuth2_RequestInterface $request) + public function validateAuthorizeRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { // Make sure a valid client id was supplied (we can not redirect because we were unable to verify the URI) if (!$client_id = $request->query("client_id")) { // We don't have a good URI to use - $this->response = new OAuth2_Response_Error(400, 'invalid_client', "No client id supplied"); + $response->setError(400, 'invalid_client', "No client id supplied"); return false; } // Get client details if (!$clientData = $this->clientStorage->getClientDetails($client_id)) { - $this->response = new OAuth2_Response_Error(400, 'invalid_client', 'The client id supplied is invalid'); + $response->setError(400, 'invalid_client', 'The client id supplied is invalid'); return false; } $clientData += array('redirect_uri' => null); // this should be set. We should create ClientData interface if ($clientData === false) { - $this->response = new OAuth2_Response_Error(400, 'invalid_client', "Client id does not exist"); + $response->setError(400, 'invalid_client', "Client id does not exist"); return false; } @@ -81,20 +77,20 @@ class OAuth2_Controller_AuthorizeController implements OAuth2_Controller_Authori // @see http://tools.ietf.org/html/rfc6749#section-4.1.2.1 // @see http://tools.ietf.org/html/rfc6749#section-4.2.2.1 if (!($redirect_uri = $request->query('redirect_uri')) && !($redirect_uri = $clientData['redirect_uri'])) { - $this->response = new OAuth2_Response_Error(400, 'invalid_uri', 'No redirect URI was supplied or stored'); + $response->setError(400, 'invalid_uri', 'No redirect URI was supplied or stored'); return false; } $parts = parse_url($redirect_uri); if (isset($parts['fragment']) && $parts['fragment']) { - $this->response = new OAuth2_Response_Error(400, 'invalid_uri', 'The redirect URI must not contain a fragment'); + $response->setError(400, 'invalid_uri', 'The redirect URI must not contain a fragment'); return false; } // Only need to validate if redirect_uri provided on input and clientData. if ($clientData["redirect_uri"] && $redirect_uri && !$this->validateRedirectUri($redirect_uri, $clientData["redirect_uri"])) { - $this->response = new OAuth2_Response_Error(400, 'redirect_uri_mismatch', 'The redirect URI provided is missing or does not match'); + $response->setError(400, 'redirect_uri_mismatch', 'The redirect URI provided is missing or does not match'); return false; } @@ -108,54 +104,54 @@ class OAuth2_Controller_AuthorizeController implements OAuth2_Controller_Authori // type and client_id are required if (!$response_type || !in_array($response_type, array(self::RESPONSE_TYPE_AUTHORIZATION_CODE, self::RESPONSE_TYPE_ACCESS_TOKEN))) { - $this->response = new OAuth2_Response_Redirect($redirect_uri, 302, 'invalid_request', 'Invalid or missing response type', $state); + $response->setRedirect(302, $redirect_uri, $state, 'invalid_request', 'Invalid or missing response type', null); return false; } if ($response_type == self::RESPONSE_TYPE_AUTHORIZATION_CODE) { if (!isset($this->responseTypes['code'])) { - $this->response = new OAuth2_Response_Redirect($redirect_uri, 302, 'unsupported_response_type', 'authorization code grant type not supported', $state); + $response->setRedirect(302, $redirect_uri, $state, 'unsupported_response_type', 'authorization code grant type not supported', null); return false; } if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'authorization_code')) { - $this->response = new OAuth2_Response_Redirect($redirect_uri, 302, 'unauthorized_client', 'The grant type is unauthorized for this client_id', $state); + $response->setRedirect(302, $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null); return false; } if ($this->responseTypes['code']->enforceRedirect() && !$redirect_uri) { - $this->response = new OAuth2_Response_Error(400, 'redirect_uri_mismatch', 'The redirect URI is mandatory and was not supplied.'); + $response->setError(400, 'redirect_uri_mismatch', 'The redirect URI is mandatory and was not supplied.'); return false; } } if ($response_type == self::RESPONSE_TYPE_ACCESS_TOKEN) { if (!$this->config['allow_implicit']) { - $this->response = new OAuth2_Response_Redirect($redirect_uri, 302, 'unsupported_response_type', 'implicit grant type not supported', $state); + $response->setRedirect(302, $redirect_uri, $state, 'unsupported_response_type', 'implicit grant type not supported', null); return false; } if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'implicit')) { - $this->response = new OAuth2_Response_Redirect($redirect_uri, 302, 'unauthorized_client', 'The grant type is unauthorized for this client_id', $state); + $response->setRedirect(302, $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null); return false; } } // Validate that the requested scope is supported if (false === $scope) { - $this->response = new OAuth2_Response_Redirect($redirect_uri, 302, 'invalid_client', 'This application requires you specify a scope parameter', $state); + $response->setRedirect(302, $redirect_uri, $state, 'invalid_client', 'This application requires you specify a scope parameter', null); return false; } if (!is_null($scope) && !$this->scopeUtil->scopeExists($scope, $client_id)) { - $this->response = new OAuth2_Response_Redirect($redirect_uri, 302, 'invalid_scope', 'An unsupported scope was requested', $state); + $response->setRedirect(302, $redirect_uri, $state, 'invalid_scope', 'An unsupported scope was requested', null); return false; } // Validate state parameter exists (if configured to enforce this) if ($this->config['enforce_state'] && !$state) { - $this->response = new OAuth2_Response_Redirect($redirect_uri, 302, 'invalid_request', 'The state parameter is required'); + $response->setRedirect(302, $redirect_uri, null, 'invalid_request', 'The state parameter is required'); return false; } // Return retrieved client details together with input - return array_merge(array('scope' => $scope, 'state' => null), $clientData, $request->getAllQueryParameters(), array('redirect_uri' => $redirect_uri)); + return array_merge(array('scope' => $scope, 'state' => $state), $clientData, $request->getAllQueryParameters(), array('redirect_uri' => $redirect_uri)); } /** @@ -199,19 +195,27 @@ class OAuth2_Controller_AuthorizeController implements OAuth2_Controller_Authori /** * Internal method for validating redirect URI supplied + * * @param string $inputUri + * The submitted URI to be validated + * * @param string $storedUri + * The allowed URIs to validate against. Can be a space-delimited string of URIs to + * allow for multiple URIs */ private function validateRedirectUri($inputUri, $storedUri) { if (!$inputUri || !$storedUri) { return false; // if either one is missing, assume INVALID } - return strcasecmp(substr($inputUri, 0, strlen($storedUri)), $storedUri) === 0; - } - public function getResponse() - { - return $this->response; + $stored_uris = explode(' ', $storedUri); + + foreach ($stored_uris as $stored) { + if (strcasecmp(substr($inputUri, 0, strlen($stored)), $stored) === 0) { + return true; + } + } + return false; } } diff --git a/src/OAuth2/Controller/AuthorizeControllerInterface.php b/src/OAuth2/Controller/AuthorizeControllerInterface.php index 88c8793..7d9ae68 100644 --- a/src/OAuth2/Controller/AuthorizeControllerInterface.php +++ b/src/OAuth2/Controller/AuthorizeControllerInterface.php @@ -9,14 +9,16 @@ * ex: * > $user_id = $this->somehowDetermineUserId(); * > $is_authorized = $this->somehowDetermineUserAuthorization(); - * > $response = $authorizeController->handleAuthorizeRequest( + * > $response = new OAuth2_Response(); + * > $authorizeController->handleAuthorizeRequest( * > OAuth2_Request::createFromGlobals(), + * > $response, * > $is_authorized, * > $user_id); * > $response->send(); * */ -interface OAuth2_Controller_AuthorizeControllerInterface extends OAuth2_Response_ProviderInterface +interface OAuth2_Controller_AuthorizeControllerInterface { /** * List of possible authentication response types. @@ -30,7 +32,7 @@ interface OAuth2_Controller_AuthorizeControllerInterface extends OAuth2_Response const RESPONSE_TYPE_AUTHORIZATION_CODE = 'code'; const RESPONSE_TYPE_ACCESS_TOKEN = 'token'; - public function handleAuthorizeRequest(OAuth2_RequestInterface $request, $is_authorized, $user_id = null); + public function handleAuthorizeRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response, $is_authorized, $user_id = null); - public function validateAuthorizeRequest(OAuth2_RequestInterface $request); + public function validateAuthorizeRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response); } diff --git a/src/OAuth2/Controller/ResourceController.php b/src/OAuth2/Controller/ResourceController.php index 4dd827d..017b2cf 100644 --- a/src/OAuth2/Controller/ResourceController.php +++ b/src/OAuth2/Controller/ResourceController.php @@ -1,7 +1,7 @@ <?php /** - * @see OAuth2_Controller_ResourceControllerInterface + * @see OAuth2_Controller_ResourceControllerInterface */ class OAuth2_Controller_ResourceController implements OAuth2_Controller_ResourceControllerInterface { @@ -26,52 +26,43 @@ class OAuth2_Controller_ResourceController implements OAuth2_Controller_Resource $this->scopeUtil = $scopeUtil; } - public function verifyResourceRequest(OAuth2_RequestInterface $request, $scope = null) + public function verifyResourceRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response, $scope = null) { - $token_data = $this->getAccessTokenData($request, $scope); + $token = $this->getAccessTokenData($request, $response, $scope); - return (bool) $token_data; + // Check scope, if provided + // If token doesn't have a scope, it's null/empty, or it's insufficient, then throw an error + if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->scopeUtil->checkScope($scope, $token["scope"]))) { + $response->setError(401, 'insufficient_scope', 'The request requires higher privileges than provided by the access token'); + $response->addHttpHeaders(array('WWW-Authenticate' => sprintf('%s, realm="%s", scope="%s"', $this->tokenType->getTokenType(), $this->config['www_realm'], $scope))); + return false; + } + + return (bool) $token; } - public function getAccessTokenData(OAuth2_RequestInterface $request, $scope = null) + public function getAccessTokenData(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { // Get the token parameter - $token_param = $this->tokenType->getAccessTokenParameter($request); + $token_param = $this->tokenType->getAccessTokenParameter($request, $response); if (is_null($token_param)) { - $this->response = $this->tokenType->getResponse(); return null; } // Get the stored token data (from the implementing subclass) - if (!$token = $this->tokenStorage->getAccessToken($token_param)) { - $this->response = new OAuth2_Response_AuthenticationError(401, 'invalid_grant', 'The access token provided is invalid', $this->tokenType->getTokenType(), $this->config['www_realm'], $scope); - return null; - } - // Check we have a well formed token - if (!isset($token["expires"]) || !isset($token["client_id"])) { - $this->response = new OAuth2_Response_AuthenticationError(401, 'invalid_grant', 'Malformed token (missing "expires" or "client_id")', $this->tokenType->getTokenType(), $this->config['www_realm'], $scope); - return null; - } - // Check token expiration (expires is a mandatory paramter) - if (isset($token["expires"]) && time() > $token["expires"]) { - $this->response = new OAuth2_Response_AuthenticationError(401, 'invalid_grant', 'The access token provided has expired', $this->tokenType->getTokenType(), $this->config['www_realm'], $scope); - return null; - } - - // Check scope, if provided - // If token doesn't have a scope, it's null/empty, or it's insufficient, then throw an error - if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->scopeUtil->checkScope($scope, $token["scope"]))) { - $this->response = new OAuth2_Response_AuthenticationError(401, 'insufficient_scope', 'The request requires higher privileges than provided by the access token', $this->tokenType->getTokenType(), $this->config['www_realm'], $scope); - return null; + if (!$token = $this->tokenStorage->getAccessToken($token_param)) { + $response->setError(401, 'invalid_grant', 'The access token provided is invalid', $this->tokenType->getTokenType(), $this->config['www_realm']); + } else if (!isset($token["expires"]) || !isset($token["client_id"])) { + $response->setError(401, 'invalid_grant', 'Malformed token (missing "expires" or "client_id")', $this->tokenType->getTokenType(), $this->config['www_realm']); + } else if (isset($token["expires"]) && time() > $token["expires"]) { + $response->setError(401, 'invalid_grant', 'The access token provided has expired'); + } else { + return $token; } - return $token; - } - - public function getResponse() - { - return $this->response; + $response->addHttpHeaders(array('WWW-Authenticate' => sprintf('%s, realm="%"', $this->tokenType->getTokenType(), $this->config['www_realm']))); + return null; } } diff --git a/src/OAuth2/Controller/ResourceControllerInterface.php b/src/OAuth2/Controller/ResourceControllerInterface.php index f98f697..df87338 100644 --- a/src/OAuth2/Controller/ResourceControllerInterface.php +++ b/src/OAuth2/Controller/ResourceControllerInterface.php @@ -6,16 +6,16 @@ * contains a valid token. * * ex: - * > if (!$resourceController->verifyResourceRequest(OAuth2_Request::createFromGlobals())) { - * > $resourceController->getResponse()->send(); // authorization failed + * > if (!$resourceController->verifyResourceRequest(OAuth2_Request::createFromGlobals(), $response = new OAuth2_Response())) { + * > $response->send(); // authorization failed * > die(); * > } * > return json_encode($resource); // valid token! Send the stuff! * */ -interface OAuth2_Controller_ResourceControllerInterface extends OAuth2_Response_ProviderInterface +interface OAuth2_Controller_ResourceControllerInterface { - public function verifyResourceRequest(OAuth2_RequestInterface $request, $scope = null); + public function verifyResourceRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response, $scope = null); - public function getAccessTokenData(OAuth2_RequestInterface $request); + public function getAccessTokenData(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response); } diff --git a/src/OAuth2/Controller/TokenController.php b/src/OAuth2/Controller/TokenController.php index f1be5da..1652531 100644 --- a/src/OAuth2/Controller/TokenController.php +++ b/src/OAuth2/Controller/TokenController.php @@ -1,7 +1,7 @@ <?php /** - * @see OAuth2_Controller_TokenControllerInterface + * @see OAuth2_Controller_TokenControllerInterface */ class OAuth2_Controller_TokenController implements OAuth2_Controller_TokenControllerInterface { @@ -11,14 +11,14 @@ class OAuth2_Controller_TokenController implements OAuth2_Controller_TokenContro private $grantTypes; private $scopeUtil; - public function __construct($clientAssertionType = null, OAuth2_ResponseType_AccessTokenInterface $accessToken, array $grantTypes = array(), OAuth2_ScopeInterface $scopeUtil = null) + public function __construct(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $grantTypes = array(), OAuth2_ClientAssertionTypeInterface $clientAssertionType = null, OAuth2_ScopeInterface $scopeUtil = null) { - if ($clientAssertionType instanceof OAuth2_Storage_ClientCredentialsInterface) { - // this is for backwards compatibility - $clientAssertionType = new OAuth2_ClientAssertionType_HttpBasic($clientAssertionType); - } - if (!is_null($clientAssertionType) && !$clientAssertionType instanceof OAuth2_ClientAssertionTypeInterface) { - throw new LogicException('$clientAssertionType must be an instance of OAuth2_Storage_ClientCredentialsInterface, OAuth2_ClientAssertionTypeInterface, or null'); + if (is_null($clientAssertionType)) { + foreach ($grantTypes as $grantType) { + if (!$grantType instanceof OAuth2_ClientAssertionTypeInterface) { + throw new InvalidArgumentException('You must supply an instance of OAuth2_ClientAssertionTypeInterface or only use grant types which implement OAuth2_ClientAssertionTypeInterface'); + } + } } $this->clientAssertionType = $clientAssertionType; $this->accessToken = $accessToken; @@ -32,14 +32,15 @@ class OAuth2_Controller_TokenController implements OAuth2_Controller_TokenContro $this->scopeUtil = $scopeUtil; } - public function handleTokenRequest(OAuth2_RequestInterface $request) + public function handleTokenRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { - if ($token = $this->grantAccessToken($request)) { + if ($token = $this->grantAccessToken($request, $response)) { // @see http://tools.ietf.org/html/rfc6749#section-5.1 // server MUST disable caching in headers when tokens are involved - $this->response = new OAuth2_Response($token, 200, array('Cache-Control' => 'no-store', 'Pragma' => 'no-cache')); + $response->setStatusCode(200); + $response->addParameters($token); + $response->addHttpHeaders(array('Cache-Control' => 'no-store', 'Pragma' => 'no-cache')); } - return $this->response; } /** @@ -61,67 +62,71 @@ class OAuth2_Controller_TokenController implements OAuth2_Controller_TokenContro * * @ingroup oauth2_section_4 */ - public function grantAccessToken(OAuth2_RequestInterface $request) + public function grantAccessToken(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { if (strtolower($request->server('REQUEST_METHOD')) != 'post') { - $this->response = new OAuth2_Response_Error(405, 'invalid_request', 'The request method must be POST when requesting an access token', 'http://tools.ietf.org/html/rfc6749#section-3.2'); - $this->response->setHttpHeader( 'Allow', 'POST' ); + $response->setError(405, 'invalid_request', 'The request method must be POST when requesting an access token', '#section-3.2'); + $response->addHttpHeaders(array('Allow' => 'POST')); return null; } - // Determine grant type from request - if (!$grantType = $request->request('grant_type')) { - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'The grant type was not specified in the request'); + /* Determine grant type from request + * and validate the request for that grant type + */ + if (!$grantTypeIdentifier = $request->request('grant_type')) { + $response->setError(400, 'invalid_request', 'The grant type was not specified in the request'); return null; } - if (!isset($this->grantTypes[$grantType])) { + if (!isset($this->grantTypes[$grantTypeIdentifier])) { /* TODO: If this is an OAuth2 supported grant type that we have chosen not to implement, throw a 501 Not Implemented instead */ - $this->response = new OAuth2_Response_Error(400, 'unsupported_grant_type', sprintf('Grant type "%s" not supported', $grantType)); - return null; - } - $grantType = $this->grantTypes[$grantType]; - - // Hack to see if clientAssertionType is part of the grant type - // this should change, but right now changing it will break BC - $clientAssertionType = $grantType instanceof OAuth2_ClientAssertionTypeInterface ? $grantType : $this->clientAssertionType; - - $clientData = $clientAssertionType->getClientDataFromRequest($request); - - if (!$clientData || !$clientAssertionType->validateClientData($clientData, $grantType->getQuerystringIdentifier())) { - $this->response = $this->getObjectResponse($clientAssertionType, new OAuth2_Response_Error(400, 'invalid_request', 'Unable to verify client')); - return null; - } - - // validate the request for the token - if (!$grantType->validateRequest($request)) { - $this->response = $this->getObjectResponse($grantType, new OAuth2_Response_Error(400, 'invalid_request', sprintf('Invalid request for "%s" grant type', $grantType->getQuerystringIdentifier()))); - return null; - } - - if (!$tokenData = $grantType->getTokenDataFromRequest($request)) { - $this->response = $this->getObjectResponse($grantType, new OAuth2_Response_Error(400, 'invalid_grant', sprintf('Unable to retrieve token for "%s" grant type', $grantType->getQuerystringIdentifier()))); + $response->setError(400, 'unsupported_grant_type', sprintf('Grant type "%s" not supported', $grantTypeIdentifier)); return null; } - if (!$grantType->validateTokenData($tokenData, $clientData)) { - $this->response = $this->getObjectResponse($grantType, new OAuth2_Response_Error(400, 'invalid_grant', 'Token is no longer valid')); + $grantType = $this->grantTypes[$grantTypeIdentifier]; + if (!$grantType->validateRequest($request, $response)) { return null; } - if (!isset($tokenData["scope"])) { - $tokenData["scope"] = $this->scopeUtil->getDefaultScope(); - } - - $scope = $this->scopeUtil->getScopeFromRequest($request); - // Check scope, if provided - if (!is_null($scope) && !$this->scopeUtil->checkScope($scope, $tokenData["scope"])) { - $this->response = new OAuth2_Response_Error(400, 'invalid_scope', 'An unsupported scope was requested.'); + /* Retrieve the client information from the request + * ClientAssertionTypes allow for grant types which also assert the client data + * in which case ClientAssertion is handled in the validateRequest method + * + * @see OAuth2_GrantType_JWTBearer + * @see OAuth2_GrantType_ClientCredentials + */ + if ($grantType instanceof OAuth2_ClientAssertionTypeInterface) { + $clientId = $grantType->getClientId(); + } else { + if (!$this->clientAssertionType->validateRequest($request, $response)) { + return null; + } + $clientId = $this->clientAssertionType->getClientId(); + + // validate the Client ID (if applicable) + if (!is_null($storedClientId = $grantType->getClientId()) && $storedClientId != $clientId) { + $response->setError(400, 'invalid_grant', sprintf('%s doesn\'t exist or is invalid for the client', $grantTypeIdentifier)); + return null; + } + } + + /* + * Validate the scope of the token + * If the grant type returns a value for the scope, + * this value must be verified with the scope being requested + */ + $availableScope = $grantType->getScope(); + if (!$requestedScope = $this->scopeUtil->getScopeFromRequest($request)) { + $requestedScope = $availableScope ? $availableScope : $this->scopeUtil->getDefaultScope(); + } + + if (($requestedScope && !$this->scopeUtil->scopeExists($requestedScope, $clientId)) + || ($availableScope && !$this->scopeUtil->checkScope($requestedScope, $availableScope))) { + $response->setError(400, 'invalid_scope', 'An unsupported scope was requested.'); return null; } - $tokenData['user_id'] = isset($tokenData['user_id']) ? $tokenData['user_id'] : null; - - return $grantType->createAccessToken($this->accessToken, $clientData, $tokenData); + return $grantType->createAccessToken($this->accessToken, $clientId, $grantType->getUserId(), $requestedScope); } /** @@ -140,17 +145,4 @@ class OAuth2_Controller_TokenController implements OAuth2_Controller_TokenContro $this->grantTypes[$identifier] = $grantType; } - - public function getResponse() - { - return $this->response; - } - - public function getObjectResponse($object, OAuth2_Response $defaultResponse = null) - { - if ($object instanceof OAuth2_Response_ProviderInterface && $response = $object->getResponse()) { - return $response; - } - return $defaultResponse; - } } diff --git a/src/OAuth2/Controller/TokenControllerInterface.php b/src/OAuth2/Controller/TokenControllerInterface.php index 3af5ac5..4fe9624 100644 --- a/src/OAuth2/Controller/TokenControllerInterface.php +++ b/src/OAuth2/Controller/TokenControllerInterface.php @@ -6,11 +6,11 @@ * It also validates the client's credentials * * ex: - * > $response = $tokenController->handleTokenRequest(OAuth2_Request::createFromGlobals()); + * > $tokenController->handleTokenRequest(OAuth2_Request::createFromGlobals(), $response = new OAuth2_Response()); * > $response->send(); * */ -interface OAuth2_Controller_TokenControllerInterface extends OAuth2_Response_ProviderInterface +interface OAuth2_Controller_TokenControllerInterface { /** * handleTokenRequest @@ -18,8 +18,11 @@ interface OAuth2_Controller_TokenControllerInterface extends OAuth2_Response_Pro * @param $request * OAuth2_RequestInterface - The current http request * - **/ - public function handleTokenRequest(OAuth2_RequestInterface $request); + * @param $response + * OAuth2_ResponseInterface - An instance of OAuth2_ResponseInterface to contain the response data + * + */ + public function handleTokenRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response); - public function grantAccessToken(OAuth2_RequestInterface $request); + public function grantAccessToken(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response); } diff --git a/src/OAuth2/Encryption/JWT.php b/src/OAuth2/Encryption/JWT.php index 2398808..16d3102 100644 --- a/src/OAuth2/Encryption/JWT.php +++ b/src/OAuth2/Encryption/JWT.php @@ -1,9 +1,9 @@ <?php + /** * @link https://github.com/F21/jwt * @author F21 */ - class OAuth2_Encryption_JWT { public function encode($payload, $key, $algo = 'HS256') @@ -33,23 +33,22 @@ class OAuth2_Encryption_JWT list($headb64, $payloadb64, $cryptob64) = $tks; - if (null === ($header = json_decode($this->urlsafeB64Decode($headb64)))){ + if (null === ($header = json_decode($this->urlsafeB64Decode($headb64), true))) { return false; } - if (null === $payload = json_decode($this->urlsafeB64Decode($payloadb64))){ + if (null === $payload = json_decode($this->urlsafeB64Decode($payloadb64), true)) { return false; } $sig = $this->urlsafeB64Decode($cryptob64); if ($verify) { - - if (empty($header->alg)) { + if (!isset($header['alg'])) { return false; } - if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header->alg)) { + if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) { return false; } } @@ -66,7 +65,7 @@ class OAuth2_Encryption_JWT return $this->sign($input, $key, $algo) === $signature; case 'RS256': - return @openssl_verify($input, $signature, $key, 'sha256') === 1; + return openssl_verify($input, $signature, $key, 'sha256') === 1; case 'RS384': return @openssl_verify($input, $signature, $key, 'sha384') === 1; @@ -82,7 +81,6 @@ class OAuth2_Encryption_JWT private function sign($input, $key, $algo = 'HS256') { switch($algo){ - case 'HS256': return hash_hmac('sha256', $input, $key, true); diff --git a/src/OAuth2/GrantType/AuthorizationCode.php b/src/OAuth2/GrantType/AuthorizationCode.php index 6067c5a..55b8fba 100644 --- a/src/OAuth2/GrantType/AuthorizationCode.php +++ b/src/OAuth2/GrantType/AuthorizationCode.php @@ -1,13 +1,13 @@ <?php /** -* -*/ -class OAuth2_GrantType_AuthorizationCode implements OAuth2_GrantTypeInterface, OAuth2_Response_ProviderInterface + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ +class OAuth2_GrantType_AuthorizationCode implements OAuth2_GrantTypeInterface { private $storage; - private $response; - private $code; + private $authCode; public function __construct(OAuth2_Storage_AuthorizationCodeInterface $storage) { @@ -19,65 +19,68 @@ class OAuth2_GrantType_AuthorizationCode implements OAuth2_GrantTypeInterface, O return 'authorization_code'; } - public function validateRequest($request) + public function validateRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { if (!$request->request('code')) { - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'Missing parameter: "code" is required'); + $response->setError(400, 'invalid_request', 'Missing parameter: "code" is required'); return false; } - return true; - } - - public function getTokenDataFromRequest($request) - { - $this->code = $request->request('code'); - if (!$tokenData = $this->storage->getAuthorizationCode($this->code)) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "Authorization code doesn't exist or is invalid for the client"); - return null; + $code = $request->request('code'); + if (!$authCode = $this->storage->getAuthorizationCode($code)) { + $response->setError(400, 'invalid_grant', 'Authorization code doesn\'t exist or is invalid for the client'); + return false; } /* * 4.1.3 - ensure that the "redirect_uri" parameter is present if the "redirect_uri" parameter was included in the initial authorization request * @uri - http://tools.ietf.org/html/rfc6749#section-4.1.3 */ - if (isset($tokenData['redirect_uri']) && $tokenData['redirect_uri']) { - if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != $tokenData['redirect_uri']) { - $this->response = new OAuth2_Response_Error(400, 'redirect_uri_mismatch', "The redirect URI is missing or do not match", "#section-4.1.3"); + if (isset($authCode['redirect_uri']) && $authCode['redirect_uri']) { + if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != $authCode['redirect_uri']) { + $response->setError(400, 'redirect_uri_mismatch', "The redirect URI is missing or do not match", "#section-4.1.3"); return false; } } - return $tokenData; - } + if (!isset($authCode['expires'])) { + throw new Exception('Storage must return authcode with a value for "expires"'); + } - public function validateTokenData($tokenData, array $clientData) - { - // Check the code exists - if ($tokenData === null || $clientData['client_id'] != $tokenData['client_id']) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "Authorization code doesn't exist or is invalid for the client"); + if ($authCode["expires"] < time()) { + $response->setError(400, 'invalid_grant', "The authorization code has expired"); return false; } - if ($tokenData["expires"] < time()) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "The authorization code has expired"); - return false; + if (!isset($authCode['code'])) { + $authCode['code'] = $code; // used to expire the code after the access token is granted } - // Scope is validated in the client class + $this->authCode = $authCode; + return true; } - public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData) + public function getClientId() { - $this->storage->expireAuthorizationCode($this->code); - $token = $accessToken->createAccessToken($clientData['client_id'], $tokenData['user_id'], $tokenData['scope']); + return $this->authCode['client_id']; + } - return $token; + public function getScope() + { + return isset($this->authCode['scope']) ? $this->authCode['scope'] : null; } - public function getResponse() + public function getUserId() { - return $this->response; + return $this->authCode['user_id']; + } + + public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, $client_id, $user_id, $scope) + { + $token = $accessToken->createAccessToken($client_id, $user_id, $scope); + $this->storage->expireAuthorizationCode($this->authCode['code']); + + return $token; } } diff --git a/src/OAuth2/GrantType/ClientCredentials.php b/src/OAuth2/GrantType/ClientCredentials.php index d518414..ab37409 100644 --- a/src/OAuth2/GrantType/ClientCredentials.php +++ b/src/OAuth2/GrantType/ClientCredentials.php @@ -1,40 +1,33 @@ <?php -class OAuth2_GrantType_ClientCredentials implements OAuth2_GrantTypeInterface +/** + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ +class OAuth2_GrantType_ClientCredentials extends OAuth2_ClientAssertionType_HttpBasic implements OAuth2_GrantTypeInterface { public function getQuerystringIdentifier() { return 'client_credentials'; } - public function validateRequest($request) + public function getScope() { - // This has been done in the server class - return true; + return null; } - public function getTokenDataFromRequest($request) + public function getUserId() { - // the only piece to pull is the "scope" parameter - $scope = $request->request('scope'); - return array( - "scope" => $scope - ); + return null; } - public function validateTokenData($tokenData, array $clientData) - { - // Scope is validated in the client class - return true; - } - - public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData) + public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, $client_id, $user_id, $scope) { /* * Client Credentials Grant does NOT include a refresh token * @see http://tools.ietf.org/html/rfc6749#section-4.4.3 */ $includeRefreshToken = false; - return $accessToken->createAccessToken($clientData['client_id'], $tokenData['user_id'], $tokenData['scope'], $includeRefreshToken); + return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken); } } diff --git a/src/OAuth2/GrantType/JWTBearer.php b/src/OAuth2/GrantType/JWTBearer.php index 705856a..6ba2521 100644 --- a/src/OAuth2/GrantType/JWTBearer.php +++ b/src/OAuth2/GrantType/JWTBearer.php @@ -1,17 +1,19 @@ <?php /** -* The JWT bearer authorization grant implements JWT (JSON Web Tokens) as a grant type per the IETF draft. -* @see http://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-04#section-4 -*/ -class OAuth2_GrantType_JWTBearer implements OAuth2_GrantTypeInterface, OAuth2_Response_ProviderInterface, OAuth2_ClientAssertionTypeInterface + * The JWT bearer authorization grant implements JWT (JSON Web Tokens) as a grant type per the IETF draft. + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-04#section-4 + * + * @author F21 + * @author Brent Shaffer <bshafs at gmail dot com> + */ +class OAuth2_GrantType_JWTBearer implements OAuth2_GrantTypeInterface, OAuth2_ClientAssertionTypeInterface { private $storage; - private $response; - private $audience = null; - private $jwt = null; - private $undecodedJWT = null; + private $audience; private $jwtUtil; + private $jwt; /** * Creates an instance of the JWT bearer grant type. @@ -23,7 +25,7 @@ class OAuth2_GrantType_JWTBearer implements OAuth2_GrantTypeInterface, OAuth2_Re * @param OAuth2_Encryption_JWT OPTIONAL $jwtUtil * The class used to decode, encode and verify JWTs. */ - public function __construct(OAuth2_Storage_JWTBearerInterface $storage, $audience, OAuth2_Encryption_JWT $jwtUtil = null) + public function __construct(OAuth2_Storage_JWTBearerInterface $storage, $audience, OAuth2_Encryption_JWT $jwtUtil = null) { $this->storage = $storage; $this->audience = $audience; @@ -37,7 +39,10 @@ class OAuth2_GrantType_JWTBearer implements OAuth2_GrantTypeInterface, OAuth2_Re /** * Returns the grant_type get parameter to identify the grant type request as JWT bearer authorization grant. - * @return The string identifier for grant_type. + * + * @return + * The string identifier for grant_type. + * * @see OAuth2_GrantTypeInterface::getQuerystringIdentifier() */ public function getQuerystringIdentifier() @@ -46,240 +51,137 @@ class OAuth2_GrantType_JWTBearer implements OAuth2_GrantTypeInterface, OAuth2_Re } /** - * Validates the request by making share all GET parameters exists. - * @return TRUE if the request is valid, otherwise FALSE. - * @see OAuth2_GrantTypeInterface::validateRequest() + * Validates the data from the decoded JWT. + * + * @return + * TRUE if the JWT request is valid and can be decoded. Otherwise, FALSE is returned. + * + * @see OAuth2_GrantTypeInterface::getTokenData() */ - public function validateRequest($request) + public function validateRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { if (!$request->request("assertion")) { - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'Missing parameters: "assertion" required'); - - return false; + $response->setError(400, 'invalid_request', 'Missing parameters: "assertion" required'); + return null; } - return true; - } - - /** - * Gets the data from the decoded JWT. - * @return Array containing the token data if the JWT can be decoded. Otherwise, NULL is returned. - * @see OAuth2_GrantTypeInterface::getTokenDataFromRequest() - */ - public function getTokenDataFromRequest($request) - { - if (!$request->request("assertion")) { - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'Missing parameters: "assertion" required'); - + $response->setError(400, 'invalid_request', 'Missing parameters: "assertion" required'); return null; } - //Store the undecoded JWT for later use - $this->undecodedJWT = $request->request('assertion'); + // Store the undecoded JWT for later use + $undecodedJWT = $request->request('assertion'); - //Decode the JWT + // Decode the JWT $jwt = $this->jwtUtil->decode($request->request('assertion'), null, false); if (!$jwt) { - - $this->response = new OAuth2_Response_Error(400, 'invalid_request', "JWT is malformed"); + $response->setError(400, 'invalid_request', "JWT is malformed"); return null; } - $this->jwt = $jwt; - - $tokenData = array(); - - $tokenData['scope'] = $this->getJWTParameter('scope'); - $tokenData['iss'] = $this->getJWTParameter('iss'); - $tokenData['sub'] = $this->getJWTParameter('sub'); - $tokenData['aud'] = $this->getJWTParameter('aud'); - $tokenData['exp'] = $this->getJWTParameter('exp'); - $tokenData['nbf'] = $this->getJWTParameter('nbf'); - $tokenData['iat'] = $this->getJWTParameter('iat'); - $tokenData['jti'] = $this->getJWTParameter('jti'); - $tokenData['typ'] = $this->getJWTParameter('typ'); - - //Other token data in the claim - foreach ($this->jwt as $key => $value) { - if (!array_key_exists($key, $tokenData)) { - $tokenData[$key] = $value; - } - } - - return $tokenData; - } - - - /** - * Helper function to make it easier to return a JWT parameter. - * @param string $parameter - * The JWT parameter to get. - * @param mixed $default - * The value to return if the JWT parameter does not exist. - * @return mixed - * The JWT parameter. - */ - private function getJWTParameter($parameter, $default = null) - { - return isset($this->jwt->$parameter) ? $this->jwt->$parameter : null; - } - - /** - * Return the data used to verify the request. For JWT bearer authorization grants, the 'iss' is synonymous to the 'client_id'. - * The subject is 'sub' and the 'client_secret' is the key to decode the JWT. - * @return array An array of the client data containing the client_id. - * @see OAuth2_ClientAssertionTypeInterface::getClientDataFromRequest() - */ - public function getClientDataFromRequest(OAuth2_RequestInterface $request) - { - $tokenData = $this->getTokenDataFromRequest($request); - - if (!$tokenData) { + // ensure these properties contain a value + // @todo: throw malformed error for missing properties + $jwt = array_merge(array( + 'scope' => null, + 'iss' => null, + 'sub' => null, + 'aud' => null, + 'exp' => null, + 'nbf' => null, + 'iat' => null, + 'jti' => null, + 'typ' => null, + ), $jwt); + + if (!isset($jwt['iss'])) { + $response->setError(400, 'invalid_grant', "Invalid issuer (iss) provided"); return null; } - if (!isset($tokenData['iss'])) { - - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "Invalid issuer (iss) provided"); - + if (!isset($jwt['sub'])) { + $response->setError(400, 'invalid_grant', "Invalid subject (sub) provided"); return null; } - //Get the iss's public key (http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-4.1.1) - $key = $this->storage->getClientKey($tokenData['iss'], $tokenData['sub']); - - if (!$key) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "Invalid issuer (iss) or subject (sub) provided"); - + if (!isset($jwt['exp'])) { + $response->setError(400, 'invalid_grant', "Expiration (exp) time must be present"); return null; } - return array('client_id' => $tokenData['iss'], 'subject' => $tokenData['sub'], 'client_secret' => $key); - } - - /** - * Validates the client data by checking if the client_id exists in storage. It also checks to see if the client_id is associated with a key to decode the JWT. - * @see OAuth2_ClientAssertionTypeInterface::validateClientData() - */ - public function validateClientData(array $clientData, $grantTypeIdentifier) - { - - //Check that all array keys exist - $diff = array_diff(array_keys($clientData), array('client_id', 'subject', 'client_secret')); - - if (!empty($diff)) { - - throw new DomainException('The clientData array is missing one or more of the following: client_id, subject, client_secret'); - - return false; - } - - foreach ($clientData as $key => $value) { - - if (in_array($key, array('client_id', 'client_secret'))) { - - if (!isset($value)) { - throw new LogicException('client_id and client_secret in the clientData array may not be null'); - - return false; - } - } - } - - return true; - } - - /** - * Validates the token data using the rules in the IETF draft. - * @see http://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-04#section-3 - * @see OAuth2_GrantTypeInterface::validateTokenData() - */ - public function validateTokenData($tokenData, array $clientData) - { - // Note: Scope is validated in the client class - - //Check the expiry time - $expiration = $tokenData['exp']; - - if (ctype_digit($expiration)) { - - if ($expiration <= time()) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "JWT has expired"); - - return false; + // Check expiration + if (ctype_digit($jwt['exp'])) { + if ($jwt['exp'] <= time()) { + $response->setError(400, 'invalid_grant', "JWT has expired"); + return null; } - - } elseif (!$expiration) { - - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "Expiration (exp) time must be present"); - - return false; - } else { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "Expiration (exp) time must be a unix time stamp"); - - return false; + $response->setError(400, 'invalid_grant', "Expiration (exp) time must be a unix time stamp"); + return null; } - - //Check the not before time - if ($notBefore = $tokenData['nbf']) { - + // Check the not before time + if ($notBefore = $jwt['nbf']) { if (ctype_digit($notBefore)) { - if ($notBefore > time()) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time"); - - return false; + $response->setError(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time"); + return null; } - } else { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "Not Before (nbf) time must be a unix time stamp"); - - return false; + $response->setError(400, 'invalid_grant', "Not Before (nbf) time must be a unix time stamp"); + return null; } } - //Check the audience if required to match - $aud = $tokenData['aud']; - if (!isset($aud) || ($aud != $this->audience)) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "Invalid audience (aud)"); - - return false; + // Check the audience if required to match + if (!isset($jwt['aud']) || ($jwt['aud'] != $this->audience)) { + $response->setError(400, 'invalid_grant', "Invalid audience (aud)"); + return null; } - //Verify the JWT - $jwt = $this->jwtUtil->decode($this->undecodedJWT, $clientData['client_secret'], true); - - if (!$jwt) { + // Get the iss's public key + // @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-4.1.1 + if (!$key = $this->storage->getClientKey($jwt['iss'], $jwt['sub'])) { + $response->setError(400, 'invalid_grant', "Invalid issuer (iss) or subject (sub) provided"); + return null; + } - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', "JWT failed signature verification"); + // Verify the JWT + if (!$this->jwtUtil->decode($undecodedJWT, $key, true)) { + $response->setError(400, 'invalid_grant', "JWT failed signature verification"); return null; } + $this->jwt = $jwt; + return true; } - /** - * Creates an access token that is NOT associated with a refresh token. - * If a subject (sub) the name of the user/account we are accessing data on behalf of. - * @see OAuth2_GrantTypeInterface::createAccessToken() - */ - public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData) + public function getClientId() { - $includeRefreshToken = false; + return $this->jwt['iss']; + } + + public function getUserId() + { + return $this->jwt['sub']; + } - return $accessToken->createAccessToken($clientData['client_id'], $tokenData['sub'], $tokenData['scope'], $includeRefreshToken); + public function getScope() + { + return $this->jwt['scope']; } /** - * Returns the response of this grant type. - * @see OAuth2_Response_ProviderInterface::getResponse() + * Creates an access token that is NOT associated with a refresh token. + * If a subject (sub) the name of the user/account we are accessing data on behalf of. + * + * @see OAuth2_GrantTypeInterface::createAccessToken() */ - public function getResponse() + public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, $client_id, $user_id, $scope) { - return $this->response; + $includeRefreshToken = false; + return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken); } } diff --git a/src/OAuth2/GrantType/RefreshToken.php b/src/OAuth2/GrantType/RefreshToken.php index 2f5336a..f98ed84 100644 --- a/src/OAuth2/GrantType/RefreshToken.php +++ b/src/OAuth2/GrantType/RefreshToken.php @@ -1,14 +1,14 @@ <?php /** -* -*/ -class OAuth2_GrantType_RefreshToken implements OAuth2_GrantTypeInterface, OAuth2_Response_ProviderInterface + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ +class OAuth2_GrantType_RefreshToken implements OAuth2_GrantTypeInterface { private $storage; - private $response; private $config; - private $oldRefreshToken; + private $refreshToken; public function __construct(OAuth2_Storage_RefreshTokenInterface $storage, $config = array()) { @@ -23,45 +23,45 @@ class OAuth2_GrantType_RefreshToken implements OAuth2_GrantTypeInterface, OAuth2 return 'refresh_token'; } - public function validateRequest($request) + public function validateRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { if (!$request->request("refresh_token")) { - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'Missing parameter: "refresh_token" is required'); - return false; + $response->setError(400, 'invalid_request', 'Missing parameter: "refresh_token" is required'); + return null; } - return true; - } + if (!$refreshToken = $this->storage->getRefreshToken($request->request("refresh_token"))) { + $response->setError(400, 'invalid_grant', 'Invalid refresh token'); + return null; + } - public function getTokenDataFromRequest($request) - { - if (!$stored = $this->storage->getRefreshToken($request->request("refresh_token"))) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', 'Invalid refresh token'); - return false; + if ($refreshToken["expires"] < time()) { + $response->setError(400, 'invalid_grant', 'Refresh token has expired'); + return null; } - return $stored; + // store the refresh token locally so we can delete it when a new refresh token is generated + $this->refreshToken = $refreshToken; + + return true; } - public function validateTokenData($tokenData, array $clientData) + public function getClientId() { - if ($tokenData === null || $clientData['client_id'] != $tokenData["client_id"]) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', 'Invalid refresh token'); - return false; - } - - if ($tokenData["expires"] < time()) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', 'Refresh token has expired'); - return false; - } + return $this->refreshToken['client_id']; + } - // store the refresh token locally so we can delete it when a new refresh token is generated - $this->oldRefreshToken = $tokenData["refresh_token"]; + public function getUserId() + { + return $this->refreshToken['user_id']; + } - return true; + public function getScope() + { + return $this->refreshToken['scope']; } - public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData) + public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, $client_id, $user_id, $scope) { /* * It is optional to force a new refresh token when a refresh token is used. @@ -69,17 +69,12 @@ class OAuth2_GrantType_RefreshToken implements OAuth2_GrantTypeInterface, OAuth2 * @see http://tools.ietf.org/html/rfc6749#section-6 */ $issueNewRefreshToken = $this->config['always_issue_new_refresh_token']; - $token = $accessToken->createAccessToken($clientData['client_id'], $tokenData['user_id'], $tokenData['scope'], $issueNewRefreshToken); + $token = $accessToken->createAccessToken($client_id, $user_id, $scope, $issueNewRefreshToken); if ($issueNewRefreshToken) { - $this->storage->unsetRefreshToken($this->oldRefreshToken); + $this->storage->unsetRefreshToken($this->refreshToken['refresh_token']); } return $token; } - - public function getResponse() - { - return $this->response; - } } diff --git a/src/OAuth2/GrantType/UserCredentials.php b/src/OAuth2/GrantType/UserCredentials.php index 0d4638e..c41e8db 100644 --- a/src/OAuth2/GrantType/UserCredentials.php +++ b/src/OAuth2/GrantType/UserCredentials.php @@ -1,12 +1,13 @@ <?php /** -* -*/ -class OAuth2_GrantType_UserCredentials implements OAuth2_GrantTypeInterface, OAuth2_Response_ProviderInterface + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ +class OAuth2_GrantType_UserCredentials implements OAuth2_GrantTypeInterface { private $storage; - private $response; + private $userInfo; public function __construct(OAuth2_Storage_UserCredentialsInterface $storage) { @@ -18,47 +19,48 @@ class OAuth2_GrantType_UserCredentials implements OAuth2_GrantTypeInterface, OAu return 'password'; } - public function validateRequest($request) + public function validateRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { if (!$request->request("password") || !$request->request("username")) { - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'Missing parameters: "username" and "password" required'); - return false; + $response->setError(400, 'invalid_request', 'Missing parameters: "username" and "password" required'); + return null; } - return true; - } - - public function getTokenDataFromRequest($request) - { if (!$this->storage->checkUserCredentials($request->request("username"), $request->request("password"))) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', 'Invalid username and password combination'); - return false; + $response->setError(400, 'invalid_grant', 'Invalid username and password combination'); + return null; } - $tokenData = $this->storage->getUserDetails($request->request("username")); + $userInfo = $this->storage->getUserDetails($request->request("username")); - // tokenData can be an empty array - if (false === $tokenData || is_null($tokenData)) { - $this->response = new OAuth2_Response_Error(400, 'invalid_grant', 'Unable to retrieve user information'); - return false; + // userInfo can be an empty array + if (false === $userInfo || is_null($userInfo)) { + $response->setError(400, 'invalid_grant', 'Unable to retrieve user information'); + return null; } - return $tokenData; + $this->userInfo = $userInfo; + + return true; } - public function validateTokenData($tokenData, array $clientData) + public function getClientId() { - // Scope is validated in the client class - return true; + return null; + } + + public function getUserId() + { + return $this->userInfo['user_id']; } - public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData) + public function getScope() { - return $accessToken->createAccessToken($clientData['client_id'], $tokenData['user_id'], $tokenData['scope']); + return isset($this->userInfo['scope']) ? $this->userInfo['scope'] : null; } - public function getResponse() + public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, $client_id, $user_id, $scope) { - return $this->response; + return $accessToken->createAccessToken($client_id, $user_id, $scope); } } diff --git a/src/OAuth2/GrantTypeInterface.php b/src/OAuth2/GrantTypeInterface.php index c029a1d..9bc2327 100644 --- a/src/OAuth2/GrantTypeInterface.php +++ b/src/OAuth2/GrantTypeInterface.php @@ -1,10 +1,13 @@ <?php -interface OAuth2_GrantTypeInterface +/** + * Interface for all OAuth2 Grant Types + * + * @see OAuth2_CompatibilityInterface + */ +interface OAuth2_GrantTypeInterface extends OAuth2_CompatibilityInterface { - public function getQuerystringIdentifier(); - public function validateRequest($request); - public function getTokenDataFromRequest($request); - public function validateTokenData($tokenData, array $clientData); - public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, array $clientData, array $tokenData); + public function getUserId(); + public function getScope(); + public function createAccessToken(OAuth2_ResponseType_AccessTokenInterface $accessToken, $client_id, $user_id, $scope); } diff --git a/src/OAuth2/Request.php b/src/OAuth2/Request.php index 8f2ca3e..063966d 100644 --- a/src/OAuth2/Request.php +++ b/src/OAuth2/Request.php @@ -1,10 +1,10 @@ <?php /** -* OAuth2_Request -* This class is taken from the Symfony2 Framework and is part of the Symfony package. -* See Symfony\Component\HttpFoundation\Request (https://github.com/symfony/symfony) -*/ + * OAuth2_Request + * This class is taken from the Symfony2 Framework and is part of the Symfony package. + * See Symfony\Component\HttpFoundation\Request (https://github.com/symfony/symfony) + */ class OAuth2_Request implements OAuth2_RequestInterface { public $attributes; @@ -29,9 +29,9 @@ class OAuth2_Request implements OAuth2_RequestInterface * * @api */ - public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null, array $headers = null) { - $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content, $headers); } /** @@ -49,7 +49,7 @@ class OAuth2_Request implements OAuth2_RequestInterface * * @api */ - public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null, array $headers = null) { $this->request = $request; $this->query = $query; @@ -57,7 +57,8 @@ class OAuth2_Request implements OAuth2_RequestInterface $this->cookies = $cookies; $this->files = $files; $this->server = $server; - $this->headers = $this->getHeadersFromServer($this->server); + $this->content = $content; + $this->headers = is_null($headers) ? $this->getHeadersFromServer($this->server) : $headers; } public function query($name, $default = null) diff --git a/src/OAuth2/Response.php b/src/OAuth2/Response.php index bc88f2e..4ef801a 100644 --- a/src/OAuth2/Response.php +++ b/src/OAuth2/Response.php @@ -1,9 +1,15 @@ <?php /** -* -*/ -class OAuth2_Response + * Class to handle OAuth2 Responses in a graceful way. Use this interface + * to output the proper OAuth2 responses. + * + * @see OAuth2_ResponseInterface + * + * This class borrows heavily from the Symfony2 Framework and is part of the symfony package + * @see Symfony\Component\HttpFoundation\Request (https://github.com/symfony/symfony) + */ +class OAuth2_Response implements OAuth2_ResponseInterface { public $version; protected $statusCode = 200; @@ -119,10 +125,16 @@ class OAuth2_Response return $this->parameters; } - public function setParameters($parameters) + public function setParameters(array $parameters) { $this->parameters = $parameters; } + + public function addParameters(array $parameters) + { + $this->parameters = array_merge($this->parameters, $parameters); + } + public function getParameter($name, $default = null) { return isset($this->parameters[$name]) ? $this->parameters[$name] : $default; @@ -133,7 +145,7 @@ class OAuth2_Response $this->parameters[$name] = $value; } - public function setHttpHeaders($httpHeaders) + public function setHttpHeaders(array $httpHeaders) { $this->httpHeaders = $httpHeaders; } @@ -143,6 +155,11 @@ class OAuth2_Response $this->httpHeaders[$name] = $value; } + public function addHttpHeaders(array $httpHeaders) + { + $this->httpHeaders = array_merge($this->httpHeaders, $httpHeaders); + } + public function getHttpHeaders() { return $this->httpHeaders; @@ -193,7 +210,67 @@ class OAuth2_Response echo $this->getResponseBody($format); } -// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + public function setError($statusCode, $error, $errorDescription = null, $errorUri = null) + { + $parameters = array( + 'error' => $error, + 'error_description' => $errorDescription, + ); + + if (!is_null($errorUri)) { + if (strlen($errorUri) > 0 && $errorUri[0] == '#') { + // we are referencing an oauth bookmark (for brevity) + $errorUri = 'http://tools.ietf.org/html/rfc6749' . $errorUri; + } + $parameters['error_uri'] = $errorUri; + } + + $httpHeaders = array( + 'Cache-Control' => 'no-store' + ); + + $this->setStatusCode($statusCode); + $this->addParameters($parameters); + $this->addHttpHeaders($httpHeaders); + + if (!$this->isClientError() && !$this->isServerError()) { + throw new InvalidArgumentException(sprintf('The HTTP status code is not an error ("%s" given).', $statusCode)); + } + } + + public function setRedirect($statusCode = 302, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null) + { + if (empty($url)) { + throw new InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $parameters = array(); + + if (!is_null($state)) { + $parameters['state'] = $state; + } + + if (!is_null($error)) { + $this->setError(400, $error, $errorDescription, $errorUri); + } + $this->setStatusCode($statusCode); + $this->addParameters($parameters); + + if (count($this->parameters) > 0) { + // add parameters to URL redirection + $parts = parse_url($url); + $sep = isset($parts['query']) && count($parts['query']) > 0 ? '&' : '?'; + $url .= $sep . http_build_query($this->parameters); + } + + $this->addHttpHeaders(array('Location' => $url)); + + if (!$this->isRedirection()) { + throw new InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $statusCode)); + } + } + + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html /** * @return Boolean * @@ -262,6 +339,7 @@ class OAuth2_Response if (count($headers) == 0) { return ''; } + $max = max(array_map('strlen', array_keys($headers))) + 1; $content = ''; ksort($headers); diff --git a/src/OAuth2/Response/AuthenticationError.php b/src/OAuth2/Response/AuthenticationError.php deleted file mode 100644 index dadd4bf..0000000 --- a/src/OAuth2/Response/AuthenticationError.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php - -/** -* -*/ -class OAuth2_Response_AuthenticationError extends OAuth2_Response_Error -{ - public function __construct($statusCode, $error, $errorDescription, $tokenType, $realm, $scope = null) - { - parent::__construct($statusCode, $error, $errorDescription); - $authHeader = sprintf('%s realm="%s"', $tokenType, $realm); - if ($scope) { - $authHeader = sprintf('%s, scope="%s"', $authHeader, $scope); - } - $this->setHttpHeader('WWW-Authenticate', $authHeader); - } -} diff --git a/src/OAuth2/Response/Error.php b/src/OAuth2/Response/Error.php deleted file mode 100644 index 305abd4..0000000 --- a/src/OAuth2/Response/Error.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php - -/** -* -*/ -class OAuth2_Response_Error extends OAuth2_Response -{ - public function __construct($statusCode, $error, $errorDescription, $errorUri = null) - { - $parameters = array( - 'error' => $error, - 'error_description' => $errorDescription, - ); - - if (!is_null($errorUri)) { - if (strlen($errorUri) > 0 && $errorUri[0] == '#') { - // we are referencing an oauth bookmark (for brevity) - $errorUri = 'http://tools.ietf.org/html/rfc6749' . $errorUri; - } - $parameters['error_uri'] = $errorUri; - } - - $httpHeaders = array( - 'Cache-Control' => 'no-store' - ); - - parent::__construct($parameters, $statusCode, $httpHeaders); - - if (!$this->isClientError() && !$this->isServerError()) { - throw new InvalidArgumentException(sprintf('The HTTP status code is not an error ("%s" given).', $statusCode)); - } - } - - public function getError() - { - return $this->parameters['error']; - } - - public function getErrorDescription() - { - return $this->parameters['error_description']; - } - - public function getErrorUri() - { - return $this->parameters['error_uri']; - } -} diff --git a/src/OAuth2/Response/ProviderInterface.php b/src/OAuth2/Response/ProviderInterface.php deleted file mode 100644 index 9b147c0..0000000 --- a/src/OAuth2/Response/ProviderInterface.php +++ /dev/null @@ -1,6 +0,0 @@ -<?php - -interface OAuth2_Response_ProviderInterface -{ - public function getResponse(); -} diff --git a/src/OAuth2/Response/Redirect.php b/src/OAuth2/Response/Redirect.php deleted file mode 100644 index 86ec291..0000000 --- a/src/OAuth2/Response/Redirect.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php - -/** -* -*/ -class OAuth2_Response_Redirect extends OAuth2_Response -{ - public function __construct($url, $statusCode = 302, $error = null, $errorDescription = null, $state = null) - { - if (empty($url)) { - throw new InvalidArgumentException('Cannot redirect to an empty URL.'); - } - - $query = array(); - - if (!is_null($error)) { - $query['error'] = $error; - } - - if (!is_null($errorDescription)) { - $query['error_description'] = $errorDescription; - } - - if (!is_null($state)) { - $query['state'] = $state; - } - - if (count($query) > 0) { - $parts = parse_url($url); - $sep = isset($parts['query']) && count($parts['query']) > 0 ? '&' : '?'; - $url = $url . $sep . http_build_query($query); - } - - $httpHeaders = array( - 'Location' => $url, - ); - - parent::__construct(array(), $statusCode, $httpHeaders); - - if (!$this->isRedirection()) { - throw new InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $statusCode)); - } - } -} diff --git a/src/OAuth2/ResponseInterface.php b/src/OAuth2/ResponseInterface.php new file mode 100644 index 0000000..4aa32cd --- /dev/null +++ b/src/OAuth2/ResponseInterface.php @@ -0,0 +1,20 @@ +<?php + +/** + * Interface which represents an object response. Meant to handle and display the proper OAuth2 Responses + * for errors and successes + * + * @see OAuth2_Response + */ +interface OAuth2_ResponseInterface +{ + public function addParameters(array $parameters); + + public function addHttpHeaders(array $httpHeaders); + + public function setStatusCode($statusCode); + + public function setError($statusCode, $name, $description = null, $uri = null); + + public function setRedirect($statusCode = 302, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null); +}
\ No newline at end of file diff --git a/src/OAuth2/ResponseType/AccessToken.php b/src/OAuth2/ResponseType/AccessToken.php index 30beaf6..b92ead9 100644 --- a/src/OAuth2/ResponseType/AccessToken.php +++ b/src/OAuth2/ResponseType/AccessToken.php @@ -1,8 +1,9 @@ <?php /** -* -*/ + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ class OAuth2_ResponseType_AccessToken implements OAuth2_ResponseType_AccessTokenInterface { private $tokenStorage; @@ -20,7 +21,6 @@ class OAuth2_ResponseType_AccessToken implements OAuth2_ResponseType_AccessToken ), $config); } - // same params as above public function getAuthorizeResponse($params, $user_id = null) { // build the URL to redirect to @@ -44,17 +44,16 @@ class OAuth2_ResponseType_AccessToken implements OAuth2_ResponseType_AccessToken } /** - * Handle the creation of access token, also issue refresh token if support. - * - * This belongs in a separate factory, but to keep it simple, I'm just - * keeping it here. + * Handle the creation of access token, also issue refresh token if supported / desirable. * * @param $client_id * Client identifier related to the access token. + * @param $user_id + * User ID associated with the access token * @param $scope * (optional) Scopes to be stored in space-separated string. - * @param bool $excludeRefreshToken - * If true, the refresh_token will be omitted from the response + * @param bool $includeRefreshToken + * If true, a new refresh_token will be added to the response * * @see http://tools.ietf.org/html/rfc6749#section-5 * @ingroup oauth2_section_5 diff --git a/src/OAuth2/ResponseType/AccessTokenInterface.php b/src/OAuth2/ResponseType/AccessTokenInterface.php index d362893..19180b9 100644 --- a/src/OAuth2/ResponseType/AccessTokenInterface.php +++ b/src/OAuth2/ResponseType/AccessTokenInterface.php @@ -1,6 +1,25 @@ <?php +/** + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ interface OAuth2_ResponseType_AccessTokenInterface extends OAuth2_ResponseTypeInterface { + /** + * Handle the creation of access token, also issue refresh token if supported / desirable. + * + * @param $client_id + * Client identifier related to the access token. + * @param $user_id + * User ID associated with the access token + * @param $scope + * (optional) Scopes to be stored in space-separated string. + * @param bool $includeRefreshToken + * If true, a new refresh_token will be added to the response + * + * @see http://tools.ietf.org/html/rfc6749#section-5 + * @ingroup oauth2_section_5 + */ public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true); } diff --git a/src/OAuth2/ResponseType/AuthorizationCode.php b/src/OAuth2/ResponseType/AuthorizationCode.php index d8a2626..84f2eeb 100644 --- a/src/OAuth2/ResponseType/AuthorizationCode.php +++ b/src/OAuth2/ResponseType/AuthorizationCode.php @@ -1,8 +1,9 @@ <?php /** -* -*/ + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ class OAuth2_ResponseType_AuthorizationCode implements OAuth2_ResponseType_AuthorizationCodeInterface { private $storage; @@ -17,7 +18,7 @@ class OAuth2_ResponseType_AuthorizationCode implements OAuth2_ResponseType_Autho ), $config); } - // same params as above + public function getAuthorizeResponse($params, $user_id = null) { // build the URL to redirect to @@ -35,19 +36,19 @@ class OAuth2_ResponseType_AuthorizationCode implements OAuth2_ResponseType_Autho } /** - * Handle the creation of auth code. - * - * This belongs in a separate factory, but to keep it simple, I'm just - * keeping it here. + * Handle the creation of the authorization code. * * @param $client_id - * Client identifier related to the access token. + * Client identifier related to the authorization code + * @param $user_id + * User ID associated with the authorization code * @param $redirect_uri * An absolute URI to which the authorization server will redirect the * user-agent to when the end-user authorization step is completed. * @param $scope * (optional) Scopes to be stored in space-separated string. * + * @see http://tools.ietf.org/html/rfc6749#section-4 * @ingroup oauth2_section_4 */ public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null) @@ -57,6 +58,10 @@ class OAuth2_ResponseType_AuthorizationCode implements OAuth2_ResponseType_Autho return $code; } + /** + * @return + * TRUE if the grant type requires a redirect_uri, FALSE if not + */ public function enforceRedirect() { return $this->config['enforce_redirect']; diff --git a/src/OAuth2/ResponseType/AuthorizationCodeInterface.php b/src/OAuth2/ResponseType/AuthorizationCodeInterface.php index f139ae3..6a791b3 100644 --- a/src/OAuth2/ResponseType/AuthorizationCodeInterface.php +++ b/src/OAuth2/ResponseType/AuthorizationCodeInterface.php @@ -1,5 +1,9 @@ <?php +/** + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ interface OAuth2_ResponseType_AuthorizationCodeInterface extends OAuth2_ResponseTypeInterface { /** @@ -8,5 +12,21 @@ interface OAuth2_ResponseType_AuthorizationCodeInterface extends OAuth2_Response */ public function enforceRedirect(); + /** + * Handle the creation of the authorization code. + * + * @param $client_id + * Client identifier related to the authorization code + * @param $user_id + * User ID associated with the authorization code + * @param $redirect_uri + * An absolute URI to which the authorization server will redirect the + * user-agent to when the end-user authorization step is completed. + * @param $scope + * (optional) Scopes to be stored in space-separated string. + * + * @see http://tools.ietf.org/html/rfc6749#section-4 + * @ingroup oauth2_section_4 + */ public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null); } diff --git a/src/OAuth2/ScopeInterface.php b/src/OAuth2/ScopeInterface.php index 63fb906..bab9ac6 100644 --- a/src/OAuth2/ScopeInterface.php +++ b/src/OAuth2/ScopeInterface.php @@ -1,8 +1,10 @@ <?php /** -* Class to handle scope implementation logic -*/ + * Class to handle scope implementation logic + * + * @see OAuth2_Storage_ScopeInterface + */ interface OAuth2_ScopeInterface extends OAuth2_Storage_ScopeInterface { /** diff --git a/src/OAuth2/Server.php b/src/OAuth2/Server.php index be115b7..c730607 100644 --- a/src/OAuth2/Server.php +++ b/src/OAuth2/Server.php @@ -1,8 +1,9 @@ <?php /** -* Service class for OAuth -* This class serves only to wrap the other Controller classes +* Server class for OAuth2 +* This class serves as a convience class which wraps the other Controller classes +* * @see OAuth2_Controller_ResourceController * @see OAuth2_Controller_AuthorizeController * @see OAuth2_Controller_TokenController @@ -16,15 +17,15 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, protected $storages; // servers - protected $resourceController; protected $authorizeController; protected $tokenController; + protected $resourceController; // config classes protected $responseTypes; protected $grantTypes; - protected $accessTokenResponseType; protected $scopeUtil; + protected $clientAssertionType; protected $storageMap = array( 'access_token' => 'OAuth2_Storage_AccessTokenInterface', @@ -34,6 +35,7 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, 'refresh_token' => 'OAuth2_Storage_RefreshTokenInterface', 'user_credentials' => 'OAuth2_Storage_UserCredentialsInterface', 'jwt_bearer' => 'OAuth2_Storage_JWTBearerInterface', + 'scope' => 'OAuth2_Storage_ScopeInterface', ); protected $responseTypeMap = array( 'token' => 'OAuth2_ResponseType_AccessTokenInterface', @@ -55,8 +57,14 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, * Response types to use. array keys should be "code" and and "token" for * Access Token and Authorization Code response types * - * @param OAuth2_ResponseType_AccessTokenInterface $accessTokenResponseType - * Response type to use for access token + * @param OAuth2_TokenTypeInterface $tokenType + * The token type object to use. Valid token types are "bearer" and "mac" + * + * @param OAuth2_ScopeInterface $scopeUtil + * The scope utility class to use to validate scope + * + * @param OAuth2_ClientAssertionTypeInterface $clientAssertionType + * The method in which to verify the client identity. Default is HttpBasic * * @return * TRUE if everything in required scope is contained in available scope, @@ -66,7 +74,7 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, * * @ingroup oauth2_section_7 */ - public function __construct($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), OAuth2_ResponseType_AccessTokenInterface $accessTokenResponseType = null, OAuth2_ScopeInterface $scopeUtil = null) + public function __construct($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), OAuth2_TokenTypeInterface $tokenType = null, OAuth2_ScopeInterface $scopeUtil = null, OAuth2_ClientAssertionTypeInterface $clientAssertionType = null) { $storage = is_array($storage) ? $storage : array($storage); $this->storages = array(); @@ -76,7 +84,6 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, // merge all config values. These get passed to our controller objects $this->config = array_merge(array( - 'token_type' => 'bearer', 'access_lifetime' => 3600, 'www_realm' => 'Service', 'token_param_name' => 'access_token', @@ -85,51 +92,21 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, 'allow_implicit' => false, ), $config); - foreach ($responseTypes as $key => $responseType) { - $this->addResponseType($responseType, $key); - } foreach ($grantTypes as $key => $grantType) { $this->addGrantType($grantType, $key); } - $this->accessTokenResponseType = $accessTokenResponseType; - $this->scopeUtil = $scopeUtil; - } - - public function getResourceController() - { - if (is_null($this->resourceController)) { - if (is_null($this->config['token_type'])) { - $this->config['token_type'] = 'bearer'; - } - $tokenType = null; - if ($this->config['token_type'] == 'bearer') { - $config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name'))); - $tokenType = new OAuth2_TokenType_Bearer($config); - } elseif ($this->config['token_type'] == 'mac') { - $tokenType = new OAuth2_TokenType_MAC(); - } else { - throw new LogicException('unrecognized token type: '.$this->config['token_type']); - } - if (!isset($this->storages['access_token'])) { - throw new LogicException("You must supply a storage object implementing OAuth2_Storage_AccessTokenInterface to use the access server"); - } - $config = array_intersect_key($this->config, array('www_realm' => '')); - $this->resourceController = new OAuth2_Controller_ResourceController($tokenType, $this->storages['access_token'], $config, $this->scopeUtil); + foreach ($responseTypes as $key => $responseType) { + $this->addResponseType($responseType, $key); } - return $this->resourceController; + $this->tokenType = $tokenType; + $this->scopeUtil = $scopeUtil; + $this->clientAssertionType = $clientAssertionType; } public function getAuthorizeController() { if (is_null($this->authorizeController)) { - if (!isset($this->storages['client'])) { - throw new LogicException("You must supply a storage object implementing OAuth2_Storage_ClientInterface to use the authorize server"); - } - if (0 == count($this->responseTypes)) { - $this->responseTypes = $this->getDefaultResponseTypes(); - } - $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state'))); - $this->authorizeController = new OAuth2_Controller_AuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->scopeUtil); + $this->authorizeController = $this->createDefaultAuthorizeController(); } return $this->authorizeController; } @@ -137,81 +114,41 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, public function getTokenController() { if (is_null($this->tokenController)) { - if (!isset($this->storages['client_credentials'])) { - throw new LogicException("You must supply a storage object implementing OAuth2_Storage_ClientCredentialsInterface to use the grant server"); - } - if (is_null($this->accessTokenResponseType)) { - if (isset($this->responseTypes['access_token'])) { - $this->accessTokenResponseType = $this->responseTypes['access_token']; - } else { - if (!isset($this->storages['access_token'])) { - throw new LogicException("You must supply a storage object implementing OAuth2_Storage_AccessTokenInterface to use the grant server"); - } - $refreshStorage = null; - if (isset($this->storages['refresh_token'])) { - $refreshStorage = $this->storages['refresh_token']; - } - $config = array_intersect_key($this->config, array_flip(explode(' ', 'token_type access_lifetime refresh_token_lifetime'))); - $this->accessTokenResponseType = new OAuth2_ResponseType_AccessToken($this->storages['access_token'], $refreshStorage, $config); - } - } - if (0 == count($this->grantTypes)) { - $this->grantTypes = $this->getDefaultGrantTypes(); - } - $this->tokenController = new OAuth2_Controller_TokenController($this->storages['client_credentials'], $this->accessTokenResponseType, $this->grantTypes, $this->scopeUtil); + $this->tokenController = $this->createDefaultTokenController(); } return $this->tokenController; } - protected function getDefaultResponseTypes() + public function getResourceController() { - $responseTypes = array(); - if (isset($this->storages['access_token'])) { - $refreshStorage = null; - if (isset($this->storages['refresh_token'])) { - $refreshStorage = $this->storages['refresh_token']; - } - $config = array_intersect_key($this->config, array_flip(explode(' ', 'token_type access_lifetime refresh_token_lifetime'))); - $responseTypes['token'] = new OAuth2_ResponseType_AccessToken($this->storages['access_token'], $refreshStorage, $config); - } - - if (isset($this->storages['authorization_code'])) { - $config = array_intersect_key($this->config, array_flip(explode(' ', 'enforce_redirect auth_code_lifetime'))); - $responseTypes['code'] = new OAuth2_ResponseType_AuthorizationCode($this->storages['authorization_code'], $config); - } - - if (count($responseTypes) == 0) { - throw new LogicException("You must supply an array of response_types in the constructor or implement a OAuth2_Storage_AccessTokenInterface or OAuth2_Storage_AuthorizationCodeInterface storage object"); + if (is_null($this->resourceController)) { + $this->resourceController = $this->createDefaultResourceController(); } - - return $responseTypes; + return $this->resourceController; } - protected function getDefaultGrantTypes() + /** + * every getter deserves a setter + */ + public function setAuthorizeController(OAuth2_Controller_AuthorizeControllerInterface $authorizeController) { - $grantTypes = array(); - - if (isset($this->storages['user_credentials'])) { - $grantTypes['password'] = new OAuth2_GrantType_UserCredentials($this->storages['user_credentials']); - } - - if (isset($this->storages['client_credentials'])) { - $grantTypes['client_credentials'] = new OAuth2_GrantType_ClientCredentials($this->storages['client_credentials']); - } - - if (isset($this->storages['refresh_token'])) { - $grantTypes['refresh_token'] = new OAuth2_GrantType_RefreshToken($this->storages['refresh_token']); - } - - if (isset($this->storages['authorization_code'])) { - $grantTypes['authorization_code'] = new OAuth2_GrantType_AuthorizationCode($this->storages['authorization_code']); - } + $this->authorizeController = $authorizeController; + } - if (count($grantTypes) == 0) { - throw new LogicException("Unable to build default grant types - You must supply an array of grant_types in the constructor"); - } + /** + * every getter deserves a setter + */ + public function setTokenController(OAuth2_Controller_TokenControllerInterface $tokenController) + { + $this->tokenController = $tokenController; + } - return $grantTypes; + /** + * every getter deserves a setter + */ + public function setResourceController(OAuth2_Controller_ResourceControllerInterface $resourceController) + { + $this->resourceController = $resourceController; } /** @@ -234,24 +171,17 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, * * @ingroup oauth2_section_4 */ - public function handleTokenRequest(OAuth2_RequestInterface $request) - { - $value = $this->getTokenController()->handleTokenRequest($request); - $this->response = $this->tokenController->getResponse(); - return $value; - } - - public function grantAccessToken(OAuth2_RequestInterface $request) + public function handleTokenRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { - $value = $this->getTokenController()->grantAccessToken($request); - $this->response = $this->tokenController->getResponse(); - return $value; + $this->response = is_null($response) ? new OAuth2_Response() : $response; + $this->getTokenController()->handleTokenRequest($request, $this->response); + return $this->response; } - public function getClientCredentials(OAuth2_RequestInterface $request) + public function grantAccessToken(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { - $value = $this->getTokenController()->getClientCredentials($request); - $this->response = $this->tokenController->getResponse(); + $this->response = is_null($response) ? new OAuth2_Response() : $response; + $value = $this->getTokenController()->grantAccessToken($request, $this->response); return $value; } @@ -285,11 +215,11 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, * * @ingroup oauth2_section_4 */ - public function handleAuthorizeRequest(OAuth2_RequestInterface $request, $is_authorized, $user_id = null) + public function handleAuthorizeRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response, $is_authorized, $user_id = null) { - $value = $this->getAuthorizeController()->handleAuthorizeRequest($request, $is_authorized, $user_id); - $this->response = $this->authorizeController->getResponse(); - return $value; + $this->response = is_null($response) ? new OAuth2_Response() : $response; + $this->getAuthorizeController()->handleAuthorizeRequest($request, $this->response, $is_authorized, $user_id); + return $this->response; } /** @@ -311,24 +241,24 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, * * @ingroup oauth2_section_3 */ - public function validateAuthorizeRequest(OAuth2_RequestInterface $request) + public function validateAuthorizeRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { - $value = $this->getAuthorizeController()->validateAuthorizeRequest($request); - $this->response = $this->authorizeController->getResponse(); + $this->response = is_null($response) ? new OAuth2_Response() : $response; + $value = $this->getAuthorizeController()->validateAuthorizeRequest($request, $this->response); return $value; } - public function verifyResourceRequest(OAuth2_RequestInterface $request, $scope = null) + public function verifyResourceRequest(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response, $scope = null) { - $value = $this->getResourceController()->verifyResourceRequest($request, $scope); - $this->response = $this->resourceController->getResponse(); + $this->response = is_null($response) ? new OAuth2_Response() : $response; + $value = $this->getResourceController()->verifyResourceRequest($request, $this->response, $scope); return $value; } - public function getAccessTokenData(OAuth2_RequestInterface $request, $scope = null) + public function getAccessTokenData(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { - $value = $this->getResourceController()->getAccessTokenData($request, $scope); - $this->response = $this->resourceController->getResponse(); + $this->response = is_null($response) ? new OAuth2_Response() : $response; + $value = $this->getResourceController()->getAccessTokenData($request, $this->response); return $value; } @@ -346,6 +276,16 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, } } + /** + * Set a storage object for the server + * + * @param $storage + * An object implementing one of the Storage interfaces + * + * @param $key + * If null, the storage is set to the key of each storage interface it implements + * @see storageMap + */ public function addStorage($storage, $key = null) { // if explicitly set to a valid key, do not "magically" set below @@ -358,9 +298,6 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, throw new InvalidArgumentException(sprintf('unknown storage key "%s", must be one of [%s]', $key, implode(', ', array_keys($this->storageMap)))); } else { $set = false; - // set a storage object to each key for the interface it represents - // this means if an object represents more than one storage type, it will be referenced by multiple storage keys - // ex: OAuth2_Storage_Pdo will be set for all the $storageMap keys foreach ($this->storageMap as $type => $interface) { if ($storage instanceof $interface) { $this->storages[$type] = $storage; @@ -398,11 +335,140 @@ class OAuth2_Server implements OAuth2_Controller_ResourceControllerInterface, } } + public function getScopeUtil() + { + if (!$this->scopeUtil) { + $storage = isset($this->storages['scope']) ? $this->storages['scope'] : null; + $this->scopeUtil = new OAuth2_Scope($storage); + } + return $this->scopeUtil; + } + + /** + * every getter deserves a setter + */ public function setScopeUtil($scopeUtil) { $this->scopeUtil = $scopeUtil; } + protected function createDefaultAuthorizeController() + { + if (!isset($this->storages['client'])) { + throw new LogicException("You must supply a storage object implementing OAuth2_Storage_ClientInterface to use the authorize server"); + } + if (0 == count($this->responseTypes)) { + $this->responseTypes = $this->getDefaultResponseTypes(); + } + $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state'))); + return new OAuth2_Controller_AuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil()); + } + + protected function createDefaultTokenController() + { + if (0 == count($this->grantTypes)) { + $this->grantTypes = $this->getDefaultGrantTypes(); + } + + if (is_null($this->clientAssertionType)) { + // see if HttpBasic assertion type is requred. If so, then create it from storage classes. + foreach ($this->grantTypes as $grantType) { + if (!$grantType instanceof OAuth2_ClientAssertionTypeInterface) { + if (!isset($this->storages['client_credentials'])) { + throw new LogicException("You must supply a storage object implementing OAuth2_Storage_ClientCredentialsInterface to use the token server"); + } + $this->clientAssertionType = new OAuth2_ClientAssertionType_HttpBasic($this->storages['client_credentials']); + break; + } + } + } + + return new OAuth2_Controller_TokenController($this->getAccessTokenResponseType(), $this->grantTypes, $this->clientAssertionType, $this->getScopeUtil()); + } + + protected function createDefaultResourceController() + { + if (!isset($this->storages['access_token'])) { + throw new LogicException("You must supply a storage object implementing OAuth2_Storage_AccessTokenInterface to use the resource server"); + } + if (!$this->tokenType) { + $this->tokenType = $this->getDefaultTokenType(); + } + $config = array_intersect_key($this->config, array('www_realm' => '')); + return new OAuth2_Controller_ResourceController($this->tokenType, $this->storages['access_token'], $config, $this->getScopeUtil()); + } + + protected function getDefaultTokenType() + { + $config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name'))); + return new OAuth2_TokenType_Bearer($config); + } + + protected function getDefaultResponseTypes() + { + $responseTypes = array(); + + if (isset($this->storages['access_token'])) { + $responseTypes['token'] = $this->getAccessTokenResponseType(); + } + + if (isset($this->storages['authorization_code'])) { + $config = array_intersect_key($this->config, array_flip(explode(' ', 'enforce_redirect auth_code_lifetime'))); + $responseTypes['code'] = new OAuth2_ResponseType_AuthorizationCode($this->storages['authorization_code'], $config); + } + + if (count($responseTypes) == 0) { + throw new LogicException("You must supply an array of response_types in the constructor or implement a OAuth2_Storage_AccessTokenInterface or OAuth2_Storage_AuthorizationCodeInterface storage object"); + } + + return $responseTypes; + } + + protected function getDefaultGrantTypes() + { + $grantTypes = array(); + + if (isset($this->storages['user_credentials'])) { + $grantTypes['password'] = new OAuth2_GrantType_UserCredentials($this->storages['user_credentials']); + } + + if (isset($this->storages['client_credentials'])) { + $grantTypes['client_credentials'] = new OAuth2_GrantType_ClientCredentials($this->storages['client_credentials']); + } + + if (isset($this->storages['refresh_token'])) { + $grantTypes['refresh_token'] = new OAuth2_GrantType_RefreshToken($this->storages['refresh_token']); + } + + if (isset($this->storages['authorization_code'])) { + $grantTypes['authorization_code'] = new OAuth2_GrantType_AuthorizationCode($this->storages['authorization_code']); + } + + if (count($grantTypes) == 0) { + throw new LogicException("Unable to build default grant types - You must supply an array of grant_types in the constructor"); + } + + return $grantTypes; + } + + protected function getAccessTokenResponseType() + { + if (isset($this->responseTypes['access_token'])) { + return $this->responseTypes['access_token']; + } + if (!isset($this->storages['access_token'])) { + throw new LogicException("You must supply a response type implementing OAuth2_ResponseType_AccessTokenInterface, or a storage object implementing OAuth2_Storage_AccessTokenInterface to use the token server"); + } + $refreshStorage = null; + if (isset($this->storages['refresh_token'])) { + $refreshStorage = $this->storages['refresh_token']; + } + $config = array_intersect_key($this->config, array_flip(explode(' ', 'access_lifetime refresh_token_lifetime'))); + $config['token_type'] = $this->tokenType ? $this->tokenType->getTokenType() : $this->getDefaultTokenType()->getTokenType(); + + return new OAuth2_ResponseType_AccessToken($this->storages['access_token'], $refreshStorage, $config); + } + public function getResponse() { return $this->response; diff --git a/src/OAuth2/Storage/AccessTokenInterface.php b/src/OAuth2/Storage/AccessTokenInterface.php index 4bbad84..6454ab1 100644 --- a/src/OAuth2/Storage/AccessTokenInterface.php +++ b/src/OAuth2/Storage/AccessTokenInterface.php @@ -4,7 +4,7 @@ * Implement this interface to specify where the OAuth2 Server * should get/save access tokens * - * @author Brent Shaffer <bshafs@gmail.com> + * @author Brent Shaffer <bshafs at gmail dot com> */ interface OAuth2_Storage_AccessTokenInterface { diff --git a/src/OAuth2/Storage/AuthorizationCodeInterface.php b/src/OAuth2/Storage/AuthorizationCodeInterface.php index 71fa5a8..16a055d 100644 --- a/src/OAuth2/Storage/AuthorizationCodeInterface.php +++ b/src/OAuth2/Storage/AuthorizationCodeInterface.php @@ -5,7 +5,7 @@ * should get/save authorization codes for the "Authorization Code" * grant type * - * @author Brent Shaffer <bshafs@gmail.com> + * @author Brent Shaffer <bshafs at gmail dot com> */ interface OAuth2_Storage_AuthorizationCodeInterface { diff --git a/src/OAuth2/Storage/ClientCredentialsInterface.php b/src/OAuth2/Storage/ClientCredentialsInterface.php index 45af123..d66da74 100644 --- a/src/OAuth2/Storage/ClientCredentialsInterface.php +++ b/src/OAuth2/Storage/ClientCredentialsInterface.php @@ -4,7 +4,7 @@ * Implement this interface to specify how the OAuth2 Server * should verify client credentials * - * @author Brent Shaffer <bshafs@gmail.com> + * @author Brent Shaffer <bshafs at gmail dot com> */ interface OAuth2_Storage_ClientCredentialsInterface extends OAuth2_Storage_ClientInterface { diff --git a/src/OAuth2/Storage/ClientInterface.php b/src/OAuth2/Storage/ClientInterface.php index 9ca80fe..0f3def5 100644 --- a/src/OAuth2/Storage/ClientInterface.php +++ b/src/OAuth2/Storage/ClientInterface.php @@ -4,7 +4,7 @@ * Implement this interface to specify where the OAuth2 Server * should retrieve client information * - * @author Brent Shaffer <bshafs@gmail.com> + * @author Brent Shaffer <bshafs at gmail dot com> */ interface OAuth2_Storage_ClientInterface { @@ -18,8 +18,9 @@ interface OAuth2_Storage_ClientInterface * Client identifier to be check with. * * @return array - * Client details. Only mandatory item is the "registered redirect URI", and MUST - * return FALSE if the given client does not exist or is invalid. + * Client details. The only mandatory key in the array is "redirect_uri". + * This function MUST return FALSE if the given client does not exist or is + * invalid. "redirect_uri" can be space-delimited to allow for multiple valid uris. * * @ingroup oauth2_section_4 */ diff --git a/src/OAuth2/Storage/JWTBearerInterface.php b/src/OAuth2/Storage/JWTBearerInterface.php index 65001d1..d1cc86f 100644 --- a/src/OAuth2/Storage/JWTBearerInterface.php +++ b/src/OAuth2/Storage/JWTBearerInterface.php @@ -8,11 +8,10 @@ * be the same storage as retrieving clientData * * @author F21 - * @author Brent Shaffer <bshafs@gmail.com> + * @author Brent Shaffer <bshafs at gmail dot com> */ interface OAuth2_Storage_JWTBearerInterface { - /** * Get the public key associated with a client_id * diff --git a/src/OAuth2/Storage/Memory.php b/src/OAuth2/Storage/Memory.php index 32bf485..97acd12 100644 --- a/src/OAuth2/Storage/Memory.php +++ b/src/OAuth2/Storage/Memory.php @@ -6,7 +6,7 @@ * NOTE: This class should never be used in production, and is * a stub class for example use only * - * @author Brent Shaffer <bshafs@gmail.com> + * @author Brent Shaffer <bshafs at gmail dot com> */ class OAuth2_Storage_Memory implements OAuth2_Storage_AuthorizationCodeInterface, OAuth2_Storage_UserCredentialsInterface, OAuth2_Storage_AccessTokenInterface, @@ -20,6 +20,7 @@ class OAuth2_Storage_Memory implements OAuth2_Storage_AuthorizationCodeInterface private $accessTokens; private $jwt; private $supportedScopes; + private $clientSupportedScopes; private $defaultScope; public function __construct($params = array()) @@ -32,6 +33,7 @@ class OAuth2_Storage_Memory implements OAuth2_Storage_AuthorizationCodeInterface 'access_tokens' => array(), 'jwt' => array(), 'default_scope' => null, + 'client_supported_scopes' => array(), 'supported_scopes' => array(), ), $params); @@ -42,6 +44,7 @@ class OAuth2_Storage_Memory implements OAuth2_Storage_AuthorizationCodeInterface $this->accessTokens = $params['access_tokens']; $this->jwt = $params['jwt']; $this->supportedScopes = $params['supported_scopes']; + $this->clientSupportedScopes = $params['client_supported_scopes']; $this->defaultScope = $params['default_scope']; } @@ -76,14 +79,16 @@ class OAuth2_Storage_Memory implements OAuth2_Storage_AuthorizationCodeInterface return isset($this->userCredentials[$username]) && $this->userCredentials[$username] === $password; } - public function setUserCredentials($user_credentials) - { - return isset($this->userCredentials[$username]) ? $this->userCredentials[$username] : null; - } - public function getUserDetails($username) { - return $this->getUser($username); + if (!isset($this->userCredentials[$username])) { + return null; + } + + return array( + 'user_id' => $username, + 'password' => $this->userCredentials[$username], + ); } /* ClientCredentialsInterface */ @@ -151,7 +156,14 @@ class OAuth2_Storage_Memory implements OAuth2_Storage_AuthorizationCodeInterface public function scopeExists($scope, $client_id = null) { $scope = explode(' ', trim($scope)); - return (count(array_diff($scope, $this->supportedScopes)) == 0); + + if (!is_null($client_id) && array_key_exists($client_id, $this->clientSupportedScopes)) { + $allowedScopes = array_merge($this->supportedScopes, $this->clientSupportedScopes[$client_id]); + } else { + $allowedScopes = $this->supportedScopes; + } + + return (count(array_diff($scope, $allowedScopes)) == 0); } public function getDefaultScope() @@ -163,11 +175,8 @@ class OAuth2_Storage_Memory implements OAuth2_Storage_AuthorizationCodeInterface public function getClientKey($client_id, $subject) { if (isset($this->jwt[$client_id])) { - $jwt = $this->jwt[$client_id]; - if ($jwt) { - if ($jwt["subject"] == $subject) { return $jwt["key"]; } diff --git a/src/OAuth2/Storage/Mongo.php b/src/OAuth2/Storage/Mongo.php index 6fd1182..2a2cdfc 100644 --- a/src/OAuth2/Storage/Mongo.php +++ b/src/OAuth2/Storage/Mongo.php @@ -169,9 +169,10 @@ class OAuth2_Storage_Mongo implements OAuth2_Storage_AuthorizationCodeInterface, public function getUserDetails($username) { - $user = $this->getUser($username); - // User details must have a 'user_id' field, to store in token data for example: - $user['user_id'] = $user['username']; + if ($user = $this->getUser($username)) { + $user['user_id'] = $user['username']; + } + return $user; } diff --git a/src/OAuth2/Storage/Pdo.php b/src/OAuth2/Storage/Pdo.php index ca8bcb5..d45d8e4 100644 --- a/src/OAuth2/Storage/Pdo.php +++ b/src/OAuth2/Storage/Pdo.php @@ -3,11 +3,14 @@ /** * Simple PDO storage for all storage types * - * NOTE: This class is meant to get users started - * quickly. If your application requires further + * NOTE: This class is meant to get users started + * quickly. If your application requires further * customization, extend this class or create your own. * - * @author Brent Shaffer <bshafs@gmail.com> + * NOTE: Passwords are stored in plaintext, which is never + * a good idea. Be sure to override this for your application + * + * @author Brent Shaffer <bshafs at gmail dot com> */ class OAuth2_Storage_Pdo implements OAuth2_Storage_AuthorizationCodeInterface, OAuth2_Storage_AccessTokenInterface, OAuth2_Storage_ClientCredentialsInterface, @@ -47,7 +50,7 @@ class OAuth2_Storage_Pdo implements OAuth2_Storage_AuthorizationCodeInterface, ), $config); } - /* ClientCredentialsInterface */ + /* OAuth2_Storage_ClientCredentialsInterface */ public function checkClientCredentials($client_id, $client_secret = null) { $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table'])); @@ -77,7 +80,7 @@ class OAuth2_Storage_Pdo implements OAuth2_Storage_AuthorizationCodeInterface, return true; } - /* AccessTokenInterface */ + /* OAuth2_Storage_AccessTokenInterface */ public function getAccessToken($access_token) { $stmt = $this->db->prepare(sprintf('SELECT * from %s where access_token = :access_token', $this->config['access_token_table'])); @@ -105,7 +108,7 @@ class OAuth2_Storage_Pdo implements OAuth2_Storage_AuthorizationCodeInterface, return $stmt->execute(compact('access_token', 'client_id', 'user_id', 'expires', 'scope')); } - /* AuthorizationCodeInterface */ + /* OAuth2_Storage_AuthorizationCodeInterface */ public function getAuthorizationCode($code) { $stmt = $this->db->prepare(sprintf('SELECT * from %s where authorization_code = :code', $this->config['code_table'])); @@ -140,7 +143,7 @@ class OAuth2_Storage_Pdo implements OAuth2_Storage_AuthorizationCodeInterface, return $stmt->execute(compact('code')); } - /* UserCredentialsInterface */ + /* OAuth2_Storage_UserCredentialsInterface */ public function checkUserCredentials($username, $password) { if ($user = $this->getUser($username)) { @@ -154,7 +157,7 @@ class OAuth2_Storage_Pdo implements OAuth2_Storage_AuthorizationCodeInterface, return $this->getUser($username); } - /* RefreshTokenInterface */ + /* OAuth2_Storage_RefreshTokenInterface */ public function getRefreshToken($refresh_token) { $stmt = $this->db->prepare(sprintf('SELECT * FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table'])); @@ -209,6 +212,7 @@ class OAuth2_Storage_Pdo implements OAuth2_Storage_AuthorizationCodeInterface, return $stmt->execute(compact('username', 'password', 'firstName', 'lastName')); } + /* OAuth2_Storage_JWTBearerInterface */ public function getClientKey($client_id, $subject) { $stmt = $this->db->prepare($sql = sprintf('SELECT public_key from %s where client_id=:client_id AND subject=:subject', $this->config['jwt_table'])); diff --git a/src/OAuth2/Storage/Redis.php b/src/OAuth2/Storage/Redis.php new file mode 100644 index 0000000..bce438e --- /dev/null +++ b/src/OAuth2/Storage/Redis.php @@ -0,0 +1,195 @@ +<?php +/** + * redis storage for all storage types + * + * Register client: + * <code> + * $storage = new OAuth2_Storage_Redis($redis); + * $storage->registerClient($client_id, $client_secret, $redirect_uri); + * </code> + */ +class OAuth2_Storage_Redis implements OAuth2_Storage_AuthorizationCodeInterface, + OAuth2_Storage_AccessTokenInterface, + OAuth2_Storage_ClientCredentialsInterface, + OAuth2_Storage_UserCredentialsInterface, + OAuth2_Storage_RefreshTokenInterface, + OAuth2_Storage_JWTBearerInterface +{ + private $redis; + private $config; + private $cache; + + /** + * Redis Storage! + * + * @param \Predis\Client $redis + * @param array $config + */ + public function __construct($redis, $config=array()) + { + $this->redis = $redis; + $this->config = array_merge(array( + 'client_key' => 'oauth_clients:', + 'access_token_key' => 'oauth_access_tokens:', + 'refresh_token_key' => 'oauth_refresh_tokens:', + 'code_key' => 'oauth_authorization_codes:', + 'user_key' => 'oauth_users:', + 'jwt_key' => 'oauth_jwt:', + ), $config); + } + + protected function getValue($key) + { + if ( isset($this->cache[$key]) ) { + return $this->cache[$key]; + } + $value = $this->redis->get($key); + if ( isset($value) ) { + return json_decode($value, true); + } else { + return false; + } + } + + protected function setValue($key, $value, $expire=0) + { + $this->cache[$key] = $value; + $str = json_encode($value); + if ( $expire > 0 ) { + $seconds = $expire - time(); + return $this->redis->setex($key, $seconds, $str); + } else { + return $this->redis->set($key, $str); + } + } + + protected function expireValue($key) + { + unset($this->cache[$key]); + return $this->redis->expire($key); + } + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + return $this->getValue($this->config['code_key'] . $code); + } + + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null) + { + return $this->setValue( + $this->config['code_key'] . $authorization_code, + compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope'), + $expires + ); + } + + public function expireAuthorizationCode($code) + { + $key = $this->config['code_key'] . $code; + unset($this->cache[$key]); + return $this->expireValue($key); + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + $user = $this->getUserDetails($username); + return $user && $user['password'] === $password; + } + + public function getUserDetails($username) + { + return $this->getUser($username); + } + + public function getUser($username) + { + return $this->getValue($this->config['user_key'] . $username); + } + + public function setUser($username, $password, $first_name = null, $last_name = null) + { + return $this->setValue( + $this->config['user_key'] . $username, + compact('username', 'password', 'first_name', 'last_name') + ); + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + $client = $this->getClientDetails($client_id); + return isset($client['client_secret']) + && $client['client_secret'] == $client_secret; + } + + public function getClientDetails($client_id) + { + return $this->getValue($this->config['client_key'] . $client_id); + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + return in_array($grant_type, (array) $details['grant_types']); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + public function registerClient($client_id, $client_secret, $redirect_uri) + { + return $this->setValue( + $this->config['client_key'] . $client_id, + compact('client_id', 'client_secret', 'redirect_uri') + ); + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + return $this->getValue($this->config['refresh_token_key'] . $refresh_token); + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + return $this->setValue( + $this->config['refresh_token_key'] . $refresh_token, + compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'), + $expires + ); + } + + public function unsetRefreshToken($refresh_token) + { + return $this->expireValue($this->config['refresh_token_key'] . $refresh_token); + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + return $this->getValue($this->config['access_token_key'].$access_token); + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + return $this->setValue( + $this->config['access_token_key'].$access_token, + compact('access_token', 'client_id', 'user_id', 'expires', 'scope'), + $expires + ); + } + + /*JWTBearerInterface */ + public function getClientKey($client_id, $subject) + { + $jwt = $this->getValue($this->config['jwt_key'] . $client_id); + if ( isset($jwt['subject']) && $jwt['subject'] == $subject ) { + return $jwt['key']; + } + return null; + } +} diff --git a/src/OAuth2/Storage/RefreshTokenInterface.php b/src/OAuth2/Storage/RefreshTokenInterface.php index eaff39e..b148ecb 100644 --- a/src/OAuth2/Storage/RefreshTokenInterface.php +++ b/src/OAuth2/Storage/RefreshTokenInterface.php @@ -5,11 +5,10 @@ * should get/save refresh tokens for the "Refresh Token" * grant type * - * @author Brent Shaffer <bshafs@gmail.com> + * @author Brent Shaffer <bshafs at gmail dot com> */ interface OAuth2_Storage_RefreshTokenInterface { - /** * Grant refresh access tokens. * diff --git a/src/OAuth2/Storage/ScopeInterface.php b/src/OAuth2/Storage/ScopeInterface.php index 720b74d..5b2caaa 100644 --- a/src/OAuth2/Storage/ScopeInterface.php +++ b/src/OAuth2/Storage/ScopeInterface.php @@ -5,7 +5,7 @@ * should retrieve data involving the relevent scopes associated * with this implementation. * - * @author Brent Shaffer <bshafs@gmail.com> + * @author Brent Shaffer <bshafs at gmail dot com> */ interface OAuth2_Storage_ScopeInterface { diff --git a/src/OAuth2/Storage/UserCredentialsInterface.php b/src/OAuth2/Storage/UserCredentialsInterface.php index 5ae184a..ea414d3 100644 --- a/src/OAuth2/Storage/UserCredentialsInterface.php +++ b/src/OAuth2/Storage/UserCredentialsInterface.php @@ -5,11 +5,10 @@ * should retrieve user credentials for the * "Resource Owner Password Credentials" grant type * - * @author Brent Shaffer <bshafs@gmail.com> + * @author Brent Shaffer <bshafs at gmail dot com> */ interface OAuth2_Storage_UserCredentialsInterface { - /** * Grant access tokens for basic user credentials. * @@ -47,7 +46,6 @@ interface OAuth2_Storage_UserCredentialsInterface * @return * ARRAY the associated "scope" or "user_id" values if applicable, or an empty array * if this does not apply - * */ public function getUserDetails($username); } diff --git a/src/OAuth2/TokenType/Bearer.php b/src/OAuth2/TokenType/Bearer.php index ad5e344..838286b 100644 --- a/src/OAuth2/TokenType/Bearer.php +++ b/src/OAuth2/TokenType/Bearer.php @@ -3,9 +3,8 @@ /** * */ -class OAuth2_TokenType_Bearer implements OAuth2_TokenTypeInterface, OAuth2_Response_ProviderInterface +class OAuth2_TokenType_Bearer implements OAuth2_TokenTypeInterface { - private $response; private $config; public function __construct(array $config = array()) @@ -43,25 +42,25 @@ class OAuth2_TokenType_Bearer implements OAuth2_TokenTypeInterface, OAuth2_Respo * @see http://code.google.com/p/android/issues/detail?id=6684 * */ - public function getAccessTokenParameter(OAuth2_RequestInterface $request) + public function getAccessTokenParameter(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { $headers = $request->headers('AUTHORIZATION'); // Check that exactly one method was used $methodsUsed = !empty($headers) + !is_null($request->query($this->config['token_param_name'])) + !is_null($request->request($this->config['token_param_name'])); if ($methodsUsed > 1) { - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'Only one method may be used to authenticate at a time (Auth header, GET or POST)'); + $response->setError(400, 'invalid_request', 'Only one method may be used to authenticate at a time (Auth header, GET or POST)'); return null; } if ($methodsUsed == 0) { - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'The access token was not found'); + $response->setError(400, 'invalid_request', 'The access token was not found'); return null; } // HEADER: Get the access token from the header if (!empty($headers)) { if (!preg_match('/' . $this->config['token_bearer_header_name'] . '\s(\S+)/', $headers, $matches)) { - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'Malformed auth header'); + $response->setError(400, 'invalid_request', 'Malformed auth header'); return null; } return $matches[1]; @@ -70,14 +69,14 @@ class OAuth2_TokenType_Bearer implements OAuth2_TokenTypeInterface, OAuth2_Respo if ($request->request($this->config['token_param_name'])) { // POST: Get the token from POST data if (strtolower($request->server('REQUEST_METHOD')) != 'post') { - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'When putting the token in the body, the method must be POST'); + $response->setError(400, 'invalid_request', 'When putting the token in the body, the method must be POST'); return null; } if ($request->server('CONTENT_TYPE') !== null && $request->server('CONTENT_TYPE') != 'application/x-www-form-urlencoded') { // IETF specifies content-type. NB: Not all webservers populate this _SERVER variable // @see http://tools.ietf.org/html/rfc6750#section-2.2 - $this->response = new OAuth2_Response_Error(400, 'invalid_request', 'The content type for POST requests must be "application/x-www-form-urlencoded"'); + $response->setError(400, 'invalid_request', 'The content type for POST requests must be "application/x-www-form-urlencoded"'); return null; } @@ -87,9 +86,4 @@ class OAuth2_TokenType_Bearer implements OAuth2_TokenTypeInterface, OAuth2_Respo // GET method return $request->query($this->config['token_param_name']); } - - public function getResponse() - { - return $this->response; - } } diff --git a/src/OAuth2/TokenType/MAC.php b/src/OAuth2/TokenType/MAC.php index c6c8fe3..60427e0 100644 --- a/src/OAuth2/TokenType/MAC.php +++ b/src/OAuth2/TokenType/MAC.php @@ -10,7 +10,7 @@ class OAuth2_TokenType_MAC implements OAuth2_TokenTypeInterface return 'mac'; } - public function getAccessTokenParameter(OAuth2_RequestInterface $request) + public function getAccessTokenParameter(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response) { throw new LogicException("Not supported"); diff --git a/src/OAuth2/TokenTypeInterface.php b/src/OAuth2/TokenTypeInterface.php index 8960e9e..fe7cfdb 100644 --- a/src/OAuth2/TokenTypeInterface.php +++ b/src/OAuth2/TokenTypeInterface.php @@ -2,6 +2,15 @@ interface OAuth2_TokenTypeInterface { + /** + * Token type identification string + * + * ex: "bearer" or "mac" + */ public function getTokenType(); - public function getAccessTokenParameter(OAuth2_RequestInterface $request); + + /** + * Retrieves the token string from the request object + */ + public function getAccessTokenParameter(OAuth2_RequestInterface $request, OAuth2_ResponseInterface $response); } diff --git a/test/OAuth2/Server/Authorize/BasicValidationTest.php b/test/OAuth2/Controller/Authorize/BasicValidationTest.php index efbd95e..ce6a93a 100644 --- a/test/OAuth2/Server/Authorize/BasicValidationTest.php +++ b/test/OAuth2/Controller/Authorize/BasicValidationTest.php @@ -1,12 +1,12 @@ <?php -class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_TestCase +class OAuth2_Controller_Authorize_BasicValidationTest extends PHPUnit_Framework_TestCase { public function testNoClientIdResponse() { $server = $this->getTestServer(); $request = OAuth2_Request::createFromGlobals(); - $response = $server->handleAuthorizeRequest($request, false); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), false); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_client'); @@ -18,7 +18,7 @@ class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_Test $server = $this->getTestServer(); $request = OAuth2_Request::createFromGlobals(); $request->query['client_id'] = 'Fake Client ID'; // invalid client id - $response = $server->handleAuthorizeRequest($request, false); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), false); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_client'); @@ -30,7 +30,7 @@ class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_Test $server = $this->getTestServer(); $request = OAuth2_Request::createFromGlobals(); $request->query['client_id'] = 'Test Client ID'; // valid client id - $response = $server->handleAuthorizeRequest($request, false); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), false); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_uri'); @@ -43,7 +43,7 @@ class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_Test $request = OAuth2_Request::createFromGlobals(); $request->query['client_id'] = 'Test Client ID'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI - $response = $server->handleAuthorizeRequest($request, false); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), false); $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); @@ -61,7 +61,7 @@ class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_Test $request->query['client_id'] = 'Test Client ID'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI $request->query['response_type'] = 'invalid'; // invalid response type - $response = $server->handleAuthorizeRequest($request, false); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), false); $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); @@ -79,7 +79,7 @@ class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_Test $request->query['client_id'] = 'Test Client ID'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com#fragment'; // valid redirect URI $request->query['response_type'] = 'code'; // invalid response type - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_uri'); @@ -93,7 +93,7 @@ class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_Test $request->query['client_id'] = 'Test Client ID'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI $request->query['response_type'] = 'code'; - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); @@ -114,7 +114,7 @@ class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_Test $request->query['client_id'] = 'Test Client ID'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI $request->query['response_type'] = 'code'; - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 302); $parts = parse_url($response->getHttpHeader('Location')); @@ -124,7 +124,7 @@ class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_Test $this->assertEquals($query['error_description'], 'This application requires you specify a scope parameter'); $request->query['scope'] = 'testscope'; - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 302); $parts = parse_url($response->getHttpHeader('Location')); @@ -134,14 +134,14 @@ class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_Test $this->assertFalse(isset($query['error'])); } - public function testValidateRedirectUri() + public function testInvalidRedirectUri() { $server = $this->getTestServer(); $request = OAuth2_Request::createFromGlobals(); $request->query['client_id'] = 'Test Client ID with Redirect Uri'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com'; // invalid redirect URI $request->query['response_type'] = 'code'; - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 400); @@ -149,6 +149,24 @@ class OAuth2_Server_Authorize_BasicValidationTest extends PHPUnit_Framework_Test $this->assertEquals($response->getParameter('error_description'), 'The redirect URI provided is missing or does not match'); } + public function testMultipleRedirectUris() + { + $server = $this->getTestServer(); + $request = OAuth2_Request::createFromGlobals(); + $request->query['client_id'] = 'Test Client ID with Multiple Redirect Uris'; // valid client id + $request->query['redirect_uri'] = 'http://brentertainment.com'; // valid redirect URI + $request->query['response_type'] = 'code'; + + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); + $this->assertEquals($response->getStatusCode(), 302); + + // call again with different (but still valid) redirect URI + $request->query['redirect_uri'] = 'http://morehazards.com'; + + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); + $this->assertEquals($response->getStatusCode(), 302); + } + private function getTestServer($config = array()) { $storage = OAuth2_Storage_Bootstrap::getInstance()->getMemoryStorage(); diff --git a/test/OAuth2/Server/Authorize/CodeTest.php b/test/OAuth2/Controller/Authorize/CodeTest.php index c099298..3647c74 100644 --- a/test/OAuth2/Server/Authorize/CodeTest.php +++ b/test/OAuth2/Controller/Authorize/CodeTest.php @@ -1,6 +1,6 @@ <?php -class OAuth2_Server_Authorize_CodeTest extends PHPUnit_Framework_TestCase +class OAuth2_Controller_Authorize_CodeTest extends PHPUnit_Framework_TestCase { public function testUserDeniesAccessResponse() { @@ -9,7 +9,7 @@ class OAuth2_Server_Authorize_CodeTest extends PHPUnit_Framework_TestCase $request->query['client_id'] = 'Test Client ID'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI $request->query['response_type'] = 'code'; - $response = $server->handleAuthorizeRequest($request, false); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), false); $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); @@ -27,7 +27,7 @@ class OAuth2_Server_Authorize_CodeTest extends PHPUnit_Framework_TestCase $request->query['client_id'] = 'Test Client ID'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI $request->query['response_type'] = 'code'; - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); @@ -60,7 +60,7 @@ class OAuth2_Server_Authorize_CodeTest extends PHPUnit_Framework_TestCase $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI $request->query['response_type'] = 'code'; $request->query['state'] = 'test'; // valid state string (just needs to be passed back to us) - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 302); @@ -86,7 +86,7 @@ class OAuth2_Server_Authorize_CodeTest extends PHPUnit_Framework_TestCase $request->query['response_type'] = 'code'; $request->query['state'] = 'test'; // valid state string (just needs to be passed back to us) $request->query['fake'] = 'something'; // extra query param - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 302); $this->assertNull($response->getParameter('error')); diff --git a/test/OAuth2/Server/Authorize/ImplicitTest.php b/test/OAuth2/Controller/Authorize/ImplicitTest.php index 30dcb39..b1ea03b 100644 --- a/test/OAuth2/Server/Authorize/ImplicitTest.php +++ b/test/OAuth2/Controller/Authorize/ImplicitTest.php @@ -1,6 +1,6 @@ <?php -class OAuth2_Server_Authorize_ImplicitTest extends PHPUnit_Framework_TestCase +class OAuth2_Controller_Authorize_ImplicitTest extends PHPUnit_Framework_TestCase { public function testImplicitNotAllowedResponse() { @@ -9,7 +9,7 @@ class OAuth2_Server_Authorize_ImplicitTest extends PHPUnit_Framework_TestCase $request->query['client_id'] = 'Test Client ID'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI $request->query['response_type'] = 'token'; // invalid response type - $response = $server->handleAuthorizeRequest($request, false); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), false); $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); @@ -27,7 +27,7 @@ class OAuth2_Server_Authorize_ImplicitTest extends PHPUnit_Framework_TestCase $request->query['client_id'] = 'Test Client ID'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI $request->query['response_type'] = 'token'; // valid response type - $response = $server->handleAuthorizeRequest($request, false); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), false); $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); @@ -45,7 +45,7 @@ class OAuth2_Server_Authorize_ImplicitTest extends PHPUnit_Framework_TestCase $request->query['client_id'] = 'Test Client ID'; // valid client id $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI $request->query['response_type'] = 'token'; // valid response type - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 302); $this->assertNull($response->getParameter('error')); @@ -75,7 +75,7 @@ class OAuth2_Server_Authorize_ImplicitTest extends PHPUnit_Framework_TestCase $request->query['redirect_uri'] = 'http://adobe.com'; // valid redirect URI $request->query['response_type'] = 'token'; // valid response type $request->query['state'] = 'test'; // valid state string (just needs to be passed back to us) - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 302); $this->assertNull($response->getParameter('error')); @@ -99,7 +99,7 @@ class OAuth2_Server_Authorize_ImplicitTest extends PHPUnit_Framework_TestCase $request->query['response_type'] = 'token'; // valid response type $request->query['state'] = 'test'; // valid state string (just needs to be passed back to us) $request->query['fake'] = 'something'; // add extra param to querystring - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); $this->assertEquals($response->getStatusCode(), 302); $this->assertNull($response->getParameter('error')); diff --git a/test/OAuth2/Server/Access/BasicValidationTest.php b/test/OAuth2/Controller/Resource/BasicValidationTest.php index b20045f..faae4bb 100644 --- a/test/OAuth2/Server/Access/BasicValidationTest.php +++ b/test/OAuth2/Controller/Resource/BasicValidationTest.php @@ -1,15 +1,14 @@ <?php -class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCase +class OAuth2_Controller_Resource_BasicValidationTest extends PHPUnit_Framework_TestCase { public function testNoAccessToken() { $server = $this->getTestServer(); $request = OAuth2_Request::createFromGlobals(); - $allow = $server->verifyResourceRequest($request); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); $this->assertFalse($allow); - $response = $server->getResponse(); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); $this->assertEquals($response->getParameter('error_description'), 'The access token was not found'); @@ -20,10 +19,9 @@ class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCas $server = $this->getTestServer(); $request = OAuth2_Request::createFromGlobals(); $request->headers['AUTHORIZATION'] = 'tH1s i5 B0gU5'; - $allow = $server->verifyResourceRequest($request); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); $this->assertFalse($allow); - $response = $server->getResponse(); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); $this->assertEquals($response->getParameter('error_description'), 'Malformed auth header'); @@ -35,10 +33,9 @@ class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCas $request = OAuth2_Request::createFromGlobals(); $request->request['access_token'] = 'TEST'; $request->query['access_token'] = 'TEST'; - $allow = $server->verifyResourceRequest($request); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); $this->assertFalse($allow); - $response = $server->getResponse(); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); $this->assertEquals($response->getParameter('error_description'), 'Only one method may be used to authenticate at a time (Auth header, GET or POST)'); @@ -50,10 +47,9 @@ class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCas $request = OAuth2_Request::createFromGlobals(); $request->server['REQUEST_METHOD'] = 'GET'; $request->request['access_token'] = 'TEST'; - $allow = $server->verifyResourceRequest($request); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); $this->assertFalse($allow); - $response = $server->getResponse(); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); $this->assertEquals($response->getParameter('error_description'), 'When putting the token in the body, the method must be POST'); @@ -66,10 +62,9 @@ class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCas $request->server['REQUEST_METHOD'] = 'POST'; $request->server['CONTENT_TYPE'] = 'application/json'; $request->request['access_token'] = 'TEST'; - $allow = $server->verifyResourceRequest($request); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); $this->assertFalse($allow); - $response = $server->getResponse(); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); $this->assertEquals($response->getParameter('error_description'), 'The content type for POST requests must be "application/x-www-form-urlencoded"'); @@ -80,10 +75,9 @@ class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCas $server = $this->getTestServer(); $request = OAuth2_Request::createFromGlobals(); $request->headers['AUTHORIZATION'] = 'Bearer TESTTOKEN'; - $allow = $server->verifyResourceRequest($request); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); $this->assertFalse($allow); - $response = $server->getResponse(); $this->assertEquals($response->getStatusCode(), 401); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); $this->assertEquals($response->getParameter('error_description'), 'The access token provided is invalid'); @@ -94,10 +88,9 @@ class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCas $server = $this->getTestServer(); $request = OAuth2_Request::createFromGlobals(); $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-expired'; - $allow = $server->verifyResourceRequest($request); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); $this->assertFalse($allow); - $response = $server->getResponse(); $this->assertEquals($response->getStatusCode(), 401); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); $this->assertEquals($response->getParameter('error_description'), 'The access token provided has expired'); @@ -109,10 +102,9 @@ class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCas $request = OAuth2_Request::createFromGlobals(); $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-scope'; $scope = 'outofscope'; - $allow = $server->verifyResourceRequest($request, $scope); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response(), $scope); $this->assertFalse($allow); - $response = $server->getResponse(); $this->assertEquals($response->getStatusCode(), 401); $this->assertEquals($response->getParameter('error'), 'insufficient_scope'); $this->assertEquals($response->getParameter('error_description'), 'The request requires higher privileges than provided by the access token'); @@ -123,10 +115,9 @@ class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCas $server = $this->getTestServer(); $request = OAuth2_Request::createFromGlobals(); $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-malformed'; - $allow = $server->verifyResourceRequest($request); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); $this->assertFalse($allow); - $response = $server->getResponse(); $this->assertEquals($response->getStatusCode(), 401); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); $this->assertEquals($response->getParameter('error_description'), 'Malformed token (missing "expires" or "client_id")'); @@ -137,7 +128,7 @@ class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCas $server = $this->getTestServer(); $request = OAuth2_Request::createFromGlobals(); $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-scope'; - $allow = $server->verifyResourceRequest($request); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); $this->assertTrue($allow); } @@ -147,7 +138,7 @@ class OAuth2_Server_Access_BasicValidationTest extends PHPUnit_Framework_TestCas $request = OAuth2_Request::createFromGlobals(); $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-scope'; $request->query['scope'] = 'testscope'; - $allow = $server->verifyResourceRequest($request); + $allow = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); $this->assertTrue($allow); } diff --git a/test/OAuth2/Server/Grant/BasicValidationTest.php b/test/OAuth2/Controller/Token/BasicValidationTest.php index d6defba..d8a256b 100644 --- a/test/OAuth2/Server/Grant/BasicValidationTest.php +++ b/test/OAuth2/Controller/Token/BasicValidationTest.php @@ -1,12 +1,12 @@ <?php -class OAuth2_Server_Grant_BasicValidationTest extends PHPUnit_Framework_TestCase +class OAuth2_Controller_Token_BasicValidationTest extends PHPUnit_Framework_TestCase { public function testNoGrantType() { // add the test parameters in memory $server = $this->getTestServer(); - $response = $server->handleTokenRequest(OAuth2_Request_TestRequest::createPost()); + $server->handleTokenRequest(OAuth2_Request_TestRequest::createPost(), $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); @@ -20,7 +20,7 @@ class OAuth2_Server_Grant_BasicValidationTest extends PHPUnit_Framework_TestCase $request = OAuth2_Request_TestRequest::createPost(array( 'grant_type' => 'invalid_grant_type', // invalid grant type )); - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'unsupported_grant_type'); @@ -33,8 +33,9 @@ class OAuth2_Server_Grant_BasicValidationTest extends PHPUnit_Framework_TestCase $server = $this->getTestServer(); $request = OAuth2_Request_TestRequest::createPost(array( 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', )); - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_client'); @@ -47,33 +48,44 @@ class OAuth2_Server_Grant_BasicValidationTest extends PHPUnit_Framework_TestCase $server = $this->getTestServer(); $request = OAuth2_Request_TestRequest::createPost(array( 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', 'client_id' => 'Test Client ID', // valid client id )); - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_client'); $this->assertEquals($response->getParameter('error_description'), 'Client credentials were not found in the headers or body'); } - public function testInvalidClientCredentials() + public function testInvalidClientId() { // add the test parameters in memory $server = $this->getTestServer(); $request = OAuth2_Request_TestRequest::createPost(array( 'grant_type' => 'authorization_code', // valid grant type - 'client_id' => 'Fake Client ID', // invalid client id - 'client_secret' => 'Fake Client Secret', // invalid client secret + 'code' => 'testcode', + 'client_id' => 'Fake Client ID', // invalid client id + 'client_secret' => 'TestSecret', // valid client secret )); - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_client'); $this->assertEquals($response->getParameter('error_description'), 'The client credentials are invalid'); + } - // try again with a real client ID, but an invalid secret - $request->request['client_id'] = 'Test Client ID'; // valid client id - $response = $server->handleTokenRequest($request); + public function testInvalidClientSecret() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'Fake Client Secret', // invalid client secret + )); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_client'); @@ -90,7 +102,7 @@ class OAuth2_Server_Grant_BasicValidationTest extends PHPUnit_Framework_TestCase 'client_secret' => 'TestSecret', // valid client secret 'code' => 'testcode', // valid authorization code )); - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertTrue($response instanceof OAuth2_Response); $this->assertEquals($response->getStatusCode(), 200); @@ -101,6 +113,43 @@ class OAuth2_Server_Grant_BasicValidationTest extends PHPUnit_Framework_TestCase $this->assertNotNUll($response->getParameter('token_type')); } + public function testValidClientIdScope() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'clientscope1 clientscope2 scope1 scope2 scope3' + )); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + $this->assertEquals('clientscope1 clientscope2 scope1 scope2 scope3', $response->getParameter('scope')); + } + + public function testInvalidClientIdScope() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'clientscope3 scope1' + )); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested.'); + } + private function getTestServer() { $storage = OAuth2_Storage_Bootstrap::getInstance()->getMemoryStorage(); diff --git a/test/OAuth2/GrantType/AuthorizationCodeTest.php b/test/OAuth2/GrantType/AuthorizationCodeTest.php index f853c23..5bd5831 100644 --- a/test/OAuth2/GrantType/AuthorizationCodeTest.php +++ b/test/OAuth2/GrantType/AuthorizationCodeTest.php @@ -10,7 +10,7 @@ class OAuth2_GrantType_AuthorizationCodeTest extends PHPUnit_Framework_TestCase 'client_id' => 'Test Client ID', // valid client id 'client_secret' => 'TestSecret', // valid client secret )); - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); @@ -21,12 +21,12 @@ class OAuth2_GrantType_AuthorizationCodeTest extends PHPUnit_Framework_TestCase { $server = $this->getTestServer(); $request = OAuth2_Request_TestRequest::createPost(array( - 'grant_type' => 'authorization_code', // valid grant type - 'client_id' => 'Test Client ID', // valid client id + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id 'client_secret' => 'TestSecret', // valid client secret - 'code' => 'InvalidCode', // invalid authorization code + 'code' => 'InvalidCode', // invalid authorization code )); - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -37,27 +37,161 @@ class OAuth2_GrantType_AuthorizationCodeTest extends PHPUnit_Framework_TestCase { $server = $this->getTestServer(); $request = OAuth2_Request_TestRequest::createPost(array( - 'grant_type' => 'authorization_code', // valid grant type - 'client_id' => 'Test Client ID', // valid client id + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id 'client_secret' => 'TestSecret', // valid client secret - 'code' => 'testcode', // valid code + 'code' => 'testcode', // valid code )); - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 200); $this->assertNotNull($response->getParameter('access_token')); // try to use the same code again - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); $this->assertEquals($response->getParameter('error_description'), 'Authorization code doesn\'t exist or is invalid for the client'); } + public function testExpiredCode() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-expired', // expired authorization code + )); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'The authorization code has expired'); + } + + public function testValidCode() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode', // valid code + )); + $token = $server->grantAccessToken($request, new OAuth2_Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidCodeNoScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + )); + $token = $server->grantAccessToken($request, new OAuth2_Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1 scope2'); + } + + public function testValidCodeSameScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'scope2 scope1', + )); + $token = $server->grantAccessToken($request, new OAuth2_Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope2 scope1'); + } + + public function testValidCodeLessScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'scope1', + )); + $token = $server->grantAccessToken($request, new OAuth2_Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidCodeDifferentScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'scope3', + )); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested.'); + } + + public function testValidCodeInvalidScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested.'); + } + + public function testValidClientDifferentCode() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Some Other Client', // valid client id + 'client_secret' => 'TestSecret3', // valid client secret + 'code' => 'testcode', // valid code + )); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'authorization_code doesn\'t exist or is invalid for the client'); + } + private function getTestServer() { - $storage = new OAuth2_Storage_Memory(json_decode(file_get_contents(dirname(__FILE__).'/../../config/storage.json'), true)); + $storage = OAuth2_Storage_Bootstrap::getInstance()->getMemoryStorage(); $server = new OAuth2_Server($storage); $server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage)); diff --git a/test/OAuth2/GrantType/ClientCredentialsTest.php b/test/OAuth2/GrantType/ClientCredentialsTest.php index b184e2d..0fcbe18 100644 --- a/test/OAuth2/GrantType/ClientCredentialsTest.php +++ b/test/OAuth2/GrantType/ClientCredentialsTest.php @@ -10,43 +10,59 @@ class OAuth2_GrantType_ClientCredentialsTest extends PHPUnit_Framework_TestCase 'client_id' => 'Test Client ID', // valid client id 'client_secret' => 'FakeSecret', // valid client secret )); - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_client'); $this->assertEquals($response->getParameter('error_description'), 'The client credentials are invalid'); } - public function testValidCredentialsWithScope() + public function testValidCredentials() { $server = $this->getTestServer(); $request = OAuth2_Request_TestRequest::createPost(array( 'grant_type' => 'client_credentials', // valid grant type 'client_id' => 'Test Client ID', // valid client id 'client_secret' => 'TestSecret', // valid client secret - 'scope' => 'scope', )); - $token = $server->grantAccessToken($request); + $token = $server->grantAccessToken($request, new OAuth2_Response()); $this->assertNotNull($token); - $this->assertArrayHasKey('access_token', $token); $this->assertArrayHasKey('scope', $token); - $this->assertEquals($token['scope'], 'scope'); + $this->assertNull($token['scope']); } - public function testValidCredentialsNoScope() + public function testValidCredentialsWithScope() { $server = $this->getTestServer(); $request = OAuth2_Request_TestRequest::createPost(array( 'grant_type' => 'client_credentials', // valid grant type 'client_id' => 'Test Client ID', // valid client id 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'scope1', )); - $token = $server->grantAccessToken($request); + $token = $server->grantAccessToken($request, new OAuth2_Response()); $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); $this->assertArrayHasKey('scope', $token); - $this->assertNull($token['scope']); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidCredentialsInvalidScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested.'); } public function testValidCredentialsInHeader() @@ -56,7 +72,7 @@ class OAuth2_GrantType_ClientCredentialsTest extends PHPUnit_Framework_TestCase $headers = array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('Test Client ID:TestSecret'), 'REQUEST_METHOD' => 'POST'); $params = array('grant_type' => 'client_credentials'); $request = new OAuth2_Request(array(), $params, array(), array(), array(), $headers); - $token = $server->grantAccessToken($request); + $token = $server->grantAccessToken($request, new OAuth2_Response()); $this->assertNotNull($token); $this->assertArrayHasKey('access_token', $token); @@ -66,7 +82,7 @@ class OAuth2_GrantType_ClientCredentialsTest extends PHPUnit_Framework_TestCase $headers = array('PHP_AUTH_USER' => 'Test Client ID', 'PHP_AUTH_PW' => 'TestSecret', 'REQUEST_METHOD' => 'POST'); $params = array('grant_type' => 'client_credentials'); $request = new OAuth2_Request(array(), $params, array(), array(), array(), $headers); - $token = $server->grantAccessToken($request); + $token = $server->grantAccessToken($request, new OAuth2_Response()); $this->assertNotNull($token); $this->assertArrayHasKey('access_token', $token); @@ -81,7 +97,7 @@ class OAuth2_GrantType_ClientCredentialsTest extends PHPUnit_Framework_TestCase 'client_id' => 'Test Client ID', // valid client id 'client_secret' => 'TestSecret', // valid client secret )); - $token = $server->grantAccessToken($request); + $token = $server->grantAccessToken($request, new OAuth2_Response()); $this->assertNotNull($token); $this->assertArrayHasKey('access_token', $token); @@ -96,7 +112,7 @@ class OAuth2_GrantType_ClientCredentialsTest extends PHPUnit_Framework_TestCase 'client_id' => 'Test Client ID', // valid client id 'client_secret' => 'TestSecret', // valid client secret )); - $token = $server->grantAccessToken($request); + $token = $server->grantAccessToken($request, new OAuth2_Response()); $this->assertNotNull($token); $this->assertArrayHasKey('access_token', $token); @@ -105,7 +121,7 @@ class OAuth2_GrantType_ClientCredentialsTest extends PHPUnit_Framework_TestCase private function getTestServer() { - $storage = new OAuth2_Storage_Memory(json_decode(file_get_contents(dirname(__FILE__).'/../../config/storage.json'), true)); + $storage = OAuth2_Storage_Bootstrap::getInstance()->getMemoryStorage(); $server = new OAuth2_Server($storage); $server->addGrantType(new OAuth2_GrantType_ClientCredentials($storage)); diff --git a/test/OAuth2/GrantType/JWTBearerTest.php b/test/OAuth2/GrantType/JWTBearerTest.php index 4d13145..a50b229 100644 --- a/test/OAuth2/GrantType/JWTBearerTest.php +++ b/test/OAuth2/GrantType/JWTBearerTest.php @@ -2,6 +2,29 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase { + private $privateKey; + + public function setUp() + { + $this->privateKey = <<<EOD +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC5/SxVlE8gnpFqCxgl2wjhzY7ucEi00s0kUg3xp7lVEvgLgYcA +nHiWp+gtSjOFfH2zsvpiWm6Lz5f743j/FEzHIO1owR0p4d9pOaJK07d01+RzoQLO +IQAgXrr4T1CCWUesncwwPBVCyy2Mw3Nmhmr9MrF8UlvdRKBxriRnlP3qJQIDAQAB +AoGAVgJJVU4fhYMu1e5JfYAcTGfF+Gf+h3iQm4JCpoUcxMXf5VpB9ztk3K7LRN5y +kwFuFALpnUAarRcUPs0D8FoP4qBluKksbAtgHkO7bMSH9emN+mH4le4qpFlR7+P1 +3fLE2Y19IBwPwEfClC+TpJvuog6xqUYGPlg6XLq/MxQUB4ECQQDgovP1v+ONSeGS +R+NgJTR47noTkQT3M2izlce/OG7a+O0yw6BOZjNXqH2wx3DshqMcPUFrTjibIClP +l/tEQ3ShAkEA0/TdBYDtXpNNjqg0R9GVH2pw7Kh68ne6mZTuj0kCgFYpUF6L6iMm +zXamIJ51rTDsTyKTAZ1JuAhAsK/M2BbDBQJAKQ5fXEkIA+i+64dsDUR/hKLBeRYG +PFAPENONQGvGBwt7/s02XV3cgGbxIgAxqWkqIp0neb9AJUoJgtyaNe3GQQJANoL4 +QQ0af0NVJAZgg8QEHTNL3aGrFSbzx8IE5Lb7PLRsJa5bP5lQxnDoYuU+EI/Phr62 +niisp/b/ZDGidkTMXQJBALeRsH1I+LmICAvWXpLKa9Gv0zGCwkuIJLiUbV9c6CVh +suocCAteQwL5iW2gA4AnYr5OGeHFsEl7NCQcwfPZpJ0= +-----END RSA PRIVATE KEY----- +EOD; + } + public function testMalformedJWT() { $server = $this->getTestServer(); @@ -15,8 +38,7 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase $request->request['assertion'] = $jwt; - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); @@ -34,8 +56,7 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase $jwt = $this->getJWT() . 'notSupposeToBeHere'; $request->request['assertion'] = $jwt; - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -53,8 +74,7 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase $jwt = $this->getJWT(1234); $request->request['assertion'] = $jwt; - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -72,8 +92,7 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase $jwt = $this->getJWT('badtimestamp'); $request->request['assertion'] = $jwt; - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -89,8 +108,7 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase //Do not pass the assert (JWT) - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); @@ -108,8 +126,7 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase $jwt = $this->getJWT(null, time() + 10000); $request->request['assertion'] = $jwt; - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -127,8 +144,7 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase $jwt = $this->getJWT(null, 'notatimestamp'); $request->request['assertion'] = $jwt; - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -143,8 +159,7 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase 'assertion' => $this->getJWT(), )); - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -159,8 +174,7 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase 'assertion' => $this->getJWT(null, null, null, 'bad_client_id'), )); - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -175,8 +189,7 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase 'assertion' => $this->getJWT(null, null, 'anotheruser@ourdomain,com'), )); - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -191,14 +204,75 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase 'assertion' => $this->getJWT(null, null, null, 'Missing Key Cli,nt'), )); - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); $this->assertEquals($response->getParameter('error_description'), 'Invalid issuer (iss) or subject (sub) provided'); } + public function testValidJwt() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(), // valid assertion + )); + + $token = $server->grantAccessToken($request, new OAuth2_Response()); + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidJwtWithScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, null, 'Test Client ID', 'scope1'), // valid assertion + )); + $token = $server->grantAccessToken($request, new OAuth2_Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidJwtInvalidScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, null, 'Test Client ID', 'invalid-scope'), // valid assertion with invalid scope + )); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested.'); + } + + public function testJwtUtil() + { + $storage = OAuth2_Storage_Bootstrap::getInstance()->getMemoryStorage(); + $jwtUtil = new OAuth2_Encryption_JWT(); + $client_id = 'Test Client ID'; + $params = $this->getJWTParams(null, null, null, $client_id); + + + if (version_compare(PHP_VERSION, '5.3.3') <= 0) { + $encoded = $jwtUtil->encode($params, 'mysecretkey', 'HS256'); + $client_id .= ' PHP-5.2'; + } else { + $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256'); + } + + $payload = $jwtUtil->decode($encoded, $storage->getClientKey($client_id, "testuser@ourdomain.com")); + + $this->assertEquals($params, $payload); + } + /** * Generates a JWT * @param $exp The expiration date. If the current time is greater than the exp, the JWT is invalid. @@ -207,83 +281,55 @@ class OAuth2_GrantType_JWTBearerTest extends PHPUnit_Framework_TestCase * @param $iss The issuer, usually the client_id. * @return string */ - private function getJWT($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID') + private function getJWTParams($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID', $scope = null) { - $privateKey = <<<EOD ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/yMgbkPnOnrXt -rcbxZVtTUXdYedy28V6Td04F2h6Meh+XAQrpifoxTTF5c3QgcHq9+3ZcQH/tBnSA -NY+aXa37mo0e325I0v9cR40g/fbZLV0qp+4vVboWYfSlppiDYritM6JAaLEjzmQ0 -OXGgI7gXGUZwHSXNa0DGfHnKo0hJ2PoEcJYLxVDLHcMcZ4rJ9yh3y3hCQQnAfN4U -dhQg8SJlWr2ziwofyDFOeuai7M0jburTAx7z93JmWn//Q14pLr4855csGKLq5gv8 -dajXopD++2ez6X1Y0W3g9owdwWyFmsHv7BUcD09zBmRDVRm+jD9PPlZc6j1k9sBN -6rA9VhdTAgMBAAECggEARJi7QmjjgQzxg5bhjpwzQyTjbCkCUgkZS+Oja6JtxM81 -l1xPG+M3MwUqSgeKpMO2k1drKWoEl7H0X5tIxuz0+wZ3rXA7UR7vKYGKMB/GeD2q -kkRIhHPEQJc+2zUwXeYkqgbK5QDNQ0JiQu+8/YtpIBHxSM6UI9pV6+i/40Pt6kNr -/v1nGqu2KfwVEJ7O5moFdyLaR6QHFc9hxUxeAdcC6QOEoAp59+8UBkXFe5sK6VNO -iRBgnAIrppopkDG2XTb2zA1dZFYnUmgeLtL9lDIBv2KOxUCI5KZe5wTaRQzLRVrG -2DFvE4HrBIl3hAoy/wDhVPkw/mEfk9Y+8lKhdYC5gQKBgQDexfU1z03yV4DA5sxB -8ZLEiJBkfPPNwhgY85/Thb4Xd4bDq7mPIkqWtIXSrWBKC9+uF3cN3Aq65rU/hZ8k -n9CqZdkYfGyBburwhTfd/9n/LNXjDDsrmvBqBf2yxOReLKNU+Xp8/Opov/bMrwS5 -BBMCVSnkXqke6GJuUEd+fHI6wQKBgQDcY5ZWxFCn2u8+VTw7rA2465cxDjfVBWOP -99lI+vzSjeoxqsfnD27yxiVlMlct09X2oF2Z8NRr1Ekb+SFzPoANfqj1B6Knsp0w -3FivvCABCVPEXtLIdlfibsewtgTs3Kx2IvzS1XL1gN+awHiIut47deAAuZIZWqvm -skESO+F7EwKBgELOzxjSEKgqwbwX+w2TqtYxtkvMhTkhiiSBe17t7vIOsGWh7EYW -nJPRk4h44jWSlgQZmWSYpsciRRzFr9JF80gGvzJurgOrBd4XtYdoITI7efFbS6tq -Dctd/JVOIsZzUJA9ORSerJW5bAQ6QIpTxHegnq2UDdftDQfUH3y5SXCBAoGAI+9h -DhjIhfYe2G0RezFcs3BGoMsOs39DcrAZD9tM6hAVxa9xFO3hS1iftFZ2/JPz6VjQ -qk8oc8STfyoqGB5yxsCGUB3Emc937gWfuFfWaBTbQcOsXt9dCSDDEr87IlwBCuo9 -iyseqYUv8I29mZ3OqqdXtQaNGfE84kribVSyOV0CgYAbsee2ZZ1ufzSC5olW9nQh -5dGELbq4DhlpJg8etWc6GIn87ITOjx4xDSDh75YS8PHDZVjctCWUhgtBzvcYVlpQ -xZwD+bNYZXcbzzExGIh+YIkfpvY5rqCNRgN84YRYB585949H+rYIXB04Pou5UVuK -Fnh+zeEVijg18pMvVScrBw== ------END PRIVATE KEY----- -EOD; - //Since PHP 5.2 does not have OpenSSL support on Travis CI, we will test it using the HS256 algorithm //We also provided PHP 5.2 specific data for it in storage.json - $newPHP = true; - if (version_compare(PHP_VERSION, '5.3.3') <= 0) { + // add "5.2" identifier onto the client name $iss .= ' PHP-5.2'; - $newPHP = false; - $privateKey = 'mysecretkey'; } if (!$exp) { $exp = time() + 1000; } - if(!$sub){ + if (!$sub) { $sub = "testuser@ourdomain.com"; } $params = array( - 'iss' => $iss, - 'exp' => $exp, - 'iat' => time(), - 'sub' => $sub, - 'aud' => 'http://myapp.com/oauth/auth', - 'scope' => 'view_friends_list' - ); + 'iss' => $iss, + 'exp' => $exp, + 'iat' => time(), + 'sub' => $sub, + 'aud' => 'http://myapp.com/oauth/auth', + 'scope' => $scope, + ); if ($nbf) { $params['nbf'] = $nbf; } + return $params; + } + + private function getJWT($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID', $scope = null) + { + $params = $this->getJWTParams($exp, $nbf, $sub, $iss, $scope); + $jwtUtil = new OAuth2_Encryption_JWT(); - if ($newPHP){ - return $jwtUtil->encode($params, $privateKey, 'RS256'); - }else{ - return $jwtUtil->encode($params, $privateKey, 'HS256'); + if (version_compare(PHP_VERSION, '5.3.3') <= 0) { + return $jwtUtil->encode($params, 'mysecretkey', 'HS256'); } + return $jwtUtil->encode($params, $this->privateKey, 'RS256'); } private function getTestServer($audience = 'http://myapp.com/oauth/auth') { - $storage = new OAuth2_Storage_Memory(json_decode(file_get_contents(dirname(__FILE__).'/../../config/storage.json'), true)); + $storage = OAuth2_Storage_Bootstrap::getInstance()->getMemoryStorage(); $server = new OAuth2_Server($storage); $server->addGrantType(new OAuth2_GrantType_JWTBearer($storage, $audience)); diff --git a/test/OAuth2/GrantType/RefreshTokenTest.php b/test/OAuth2/GrantType/RefreshTokenTest.php index 56af171..e2c818c 100644 --- a/test/OAuth2/GrantType/RefreshTokenTest.php +++ b/test/OAuth2/GrantType/RefreshTokenTest.php @@ -14,8 +14,7 @@ class OAuth2_GrantType_RefreshTokenTest extends PHPUnit_Framework_TestCase 'client_id' => 'Test Client ID', // valid client id 'client_secret' => 'TestSecret', // valid client secret )); - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); @@ -33,8 +32,7 @@ class OAuth2_GrantType_RefreshTokenTest extends PHPUnit_Framework_TestCase 'client_secret' => 'TestSecret', // valid client secret 'refresh_token' => 'fake-token', // invalid refresh token )); - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -52,7 +50,7 @@ class OAuth2_GrantType_RefreshTokenTest extends PHPUnit_Framework_TestCase 'client_secret' => 'TestSecret', // valid client secret 'refresh_token' => 'test-refreshtoken', // valid refresh token )); - $token = $server->grantAccessToken($request); + $token = $server->grantAccessToken($request, new OAuth2_Response()); $this->assertTrue(isset($token['refresh_token']), 'refresh token should always refresh'); $refresh_token = $this->storage->getRefreshToken($token['refresh_token']); @@ -75,16 +73,102 @@ class OAuth2_GrantType_RefreshTokenTest extends PHPUnit_Framework_TestCase 'client_secret' => 'TestSecret', // valid client secret 'refresh_token' => 'test-refreshtoken', // valid refresh token )); - $token = $server->grantAccessToken($request); + $token = $server->grantAccessToken($request, new OAuth2_Response()); $this->assertFalse(isset($token['refresh_token']), 'refresh token should not be returned'); $used_token = $this->storage->getRefreshToken('test-refreshtoken'); $this->assertNotNull($used_token, 'the refresh token used is still valid'); } + public function testValidRefreshTokenSameScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'scope2 scope1', + )); + $token = $server->grantAccessToken($request, new OAuth2_Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope2 scope1'); + } + + public function testValidRefreshTokenLessScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'scope1', + )); + $token = $server->grantAccessToken($request, new OAuth2_Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidRefreshTokenDifferentScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'scope3', + )); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested.'); + } + + public function testValidRefreshTokenInvalidScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested.'); + } + + public function testValidClientDifferentRefreshToken() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Some Other Client', // valid client id + 'client_secret' => 'TestSecret3', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'refresh_token doesn\'t exist or is invalid for the client'); + } + private function getTestServer() { - $this->storage = new OAuth2_Storage_Memory(json_decode(file_get_contents(dirname(__FILE__).'/../../config/storage.json'), true)); + $this->storage = OAuth2_Storage_Bootstrap::getInstance()->getMemoryStorage(); $server = new OAuth2_Server($this->storage); return $server; diff --git a/test/OAuth2/GrantType/UserCredentialsTest.php b/test/OAuth2/GrantType/UserCredentialsTest.php index 74ad222..ac3234d 100644 --- a/test/OAuth2/GrantType/UserCredentialsTest.php +++ b/test/OAuth2/GrantType/UserCredentialsTest.php @@ -11,8 +11,7 @@ class OAuth2_GrantType_UserCredentialsTest extends PHPUnit_Framework_TestCase 'client_secret' => 'TestSecret', // valid client secret 'password' => 'testpass', // valid password )); - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); @@ -28,8 +27,7 @@ class OAuth2_GrantType_UserCredentialsTest extends PHPUnit_Framework_TestCase 'client_secret' => 'TestSecret', // valid client secret 'username' => 'test-username', // valid username )); - $server->grantAccessToken($request); - $response = $server->getResponse(); + $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_request'); @@ -46,8 +44,7 @@ class OAuth2_GrantType_UserCredentialsTest extends PHPUnit_Framework_TestCase 'username' => 'fake-username', // valid username 'password' => 'testpass', // valid password )); - $ret = $server->grantAccessToken($request); - $response = $server->getResponse(); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); @@ -62,19 +59,71 @@ class OAuth2_GrantType_UserCredentialsTest extends PHPUnit_Framework_TestCase 'client_id' => 'Test Client ID', // valid client id 'client_secret' => 'TestSecret', // valid client secret 'username' => 'test-username', // valid username - 'password' => 'fakepass', // valid password + 'password' => 'fakepass', // invalid password )); - $ret = $server->grantAccessToken($request); - $response = $server->getResponse(); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 400); $this->assertEquals($response->getParameter('error'), 'invalid_grant'); $this->assertEquals($response->getParameter('error_description'), 'Invalid username and password combination'); } + public function testValidCredentials() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + )); + $token = $server->grantAccessToken($request, new OAuth2_Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidCredentialsWithScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + 'scope' => 'scope1', // valid scope + )); + $token = $server->grantAccessToken($request, new OAuth2_Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidCredentialsInvalidScope() + { + $server = $this->getTestServer(); + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new OAuth2_Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested.'); + } + private function getTestServer() { - $storage = new OAuth2_Storage_Memory(json_decode(file_get_contents(dirname(__FILE__).'/../../config/storage.json'), true)); + $storage = OAuth2_Storage_Bootstrap::getInstance()->getMemoryStorage(); $server = new OAuth2_Server($storage); $server->addGrantType(new OAuth2_GrantType_UserCredentials($storage)); diff --git a/test/OAuth2/RequestTest.php b/test/OAuth2/RequestTest.php index 36c510e..3d8319a 100644 --- a/test/OAuth2/RequestTest.php +++ b/test/OAuth2/RequestTest.php @@ -8,11 +8,11 @@ class OAuth2_RequestTest extends PHPUnit_Framework_TestCase $server = $this->getTestServer(); // Smoke test for override request class - // $response = $server->handleTokenRequest($request); + // $server->handleTokenRequest($request, $response = new OAuth2_Response()); // $this->assertInstanceOf('OAuth2_Response', $response); - // $response = $server->handleAuthorizeRequest($request, true); + // $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); // $this->assertInstanceOf('OAuth2_Response', $response); - // $response = $server->verifyResourceRequest($request); + // $response = $server->verifyResourceRequest($request, $response = new OAuth2_Response()); // $this->assertTrue(is_bool($response)); /*** make some valid requests ***/ @@ -24,7 +24,7 @@ class OAuth2_RequestTest extends PHPUnit_Framework_TestCase 'client_secret' => 'TestSecret', 'code' => 'testcode', )); - $response = $server->handleTokenRequest($request); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); $this->assertEquals($response->getStatusCode(), 200); $this->assertNull($response->getParameter('error')); $this->assertNotNUll($response->getParameter('access_token')); diff --git a/test/OAuth2/Util/ScopeTest.php b/test/OAuth2/ScopeTest.php index 36d0c68..36d0c68 100644 --- a/test/OAuth2/Util/ScopeTest.php +++ b/test/OAuth2/ScopeTest.php diff --git a/test/OAuth2/ServerTest.php b/test/OAuth2/ServerTest.php index b83cf7e..7d8e35a 100644 --- a/test/OAuth2/ServerTest.php +++ b/test/OAuth2/ServerTest.php @@ -5,7 +5,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase /** * @expectedException LogicException OAuth2_Storage_ClientInterface **/ - public function testGetAuthorizeServerWithNoClientStorageThrowsException() + public function testGetAuthorizeControllerWithNoClientStorageThrowsException() { // must set Client Storage $server = new OAuth2_Server(); @@ -15,7 +15,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase /** * @expectedException LogicException OAuth2_Storage_AccessTokenInterface **/ - public function testGetAuthorizeServerWithNoAccessTokenStorageThrowsException() + public function testGetAuthorizeControllerWithNoAccessTokenStorageThrowsException() { // must set AccessToken or AuthorizationCode $server = new OAuth2_Server(); @@ -23,7 +23,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase $server->getAuthorizeController(); } - public function testGetAuthorizeServerWithClientStorageAndAccessTokenResponseType() + public function testGetAuthorizeControllerWithClientStorageAndAccessTokenResponseType() { // must set AccessToken or AuthorizationCode $server = new OAuth2_Server(); @@ -33,7 +33,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase $this->assertNotNull($server->getAuthorizeController()); } - public function testGetAuthorizeServerWithClientStorageAndAuthorizationCodeResponseType() + public function testGetAuthorizeControllerWithClientStorageAndAuthorizationCodeResponseType() { // must set AccessToken or AuthorizationCode $server = new OAuth2_Server(); @@ -43,7 +43,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase $this->assertNotNull($server->getAuthorizeController()); } - public function testGetAuthorizeServerWithClientStorageAndAccessTokenStorage() + public function testGetAuthorizeControllerWithClientStorageAndAccessTokenStorage() { // must set AccessToken or AuthorizationCode $server = new OAuth2_Server(); @@ -53,7 +53,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase $this->assertNotNull($server->getAuthorizeController()); } - public function testGetAuthorizeServerWithClientStorageAndAuthorizationCodeStorage() + public function testGetAuthorizeControllerWithClientStorageAndAuthorizationCodeStorage() { // must set AccessToken or AuthorizationCode $server = new OAuth2_Server(); @@ -64,25 +64,35 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase } /** + * @expectedException LogicException grant_types + **/ + public function testGetTokenControllerWithGrantTypeStorageThrowsException() + { + $server = new OAuth2_Server(); + $server->getTokenController(); + } + + /** * @expectedException LogicException OAuth2_Storage_ClientCredentialsInterface **/ - public function testGetGrantServerWithNoClientCredentialsStorageThrowsException() + public function testGetTokenControllerWithNoClientCredentialsStorageThrowsException() { $server = new OAuth2_Server(); + $server->addStorage($this->getMock('OAuth2_Storage_UserCredentialsInterface')); $server->getTokenController(); } /** * @expectedException LogicException OAuth2_Storage_AccessTokenInterface **/ - public function testGetGrantServerWithNoAccessTokenStorageThrowsException() + public function testGetTokenControllerWithNoAccessTokenStorageThrowsException() { $server = new OAuth2_Server(); $server->addStorage($this->getMock('OAuth2_Storage_ClientCredentialsInterface')); $server->getTokenController(); } - public function testGetGrantServerWithAccessTokenAndClientCredentialsStorage() + public function testGetTokenControllerWithAccessTokenAndClientCredentialsStorage() { $server = new OAuth2_Server(); $server->addStorage($this->getMock('OAuth2_Storage_AccessTokenInterface')); @@ -90,7 +100,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase $server->getTokenController(); } - public function testGetGrantServerAccessTokenStorageAndClientCredentialsStorageAndGrantTypes() + public function testGetTokenControllerAccessTokenStorageAndClientCredentialsStorageAndGrantTypes() { $server = new OAuth2_Server(); $server->addStorage($this->getMock('OAuth2_Storage_AccessTokenInterface')); @@ -102,13 +112,13 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase /** * @expectedException LogicException OAuth2_Storage_AccessTokenInterface **/ - public function testGetAccessServerWithNoAccessTokenStorageThrowsException() + public function testGetResourceControllerWithNoAccessTokenStorageThrowsException() { $server = new OAuth2_Server(); $server->getResourceController(); } - public function testGetAccessServerWithAccessTokenStorage() + public function testGetResourceControllerWithAccessTokenStorage() { $server = new OAuth2_Server(); $server->addStorage($this->getMock('OAuth2_Storage_AccessTokenInterface')); @@ -121,7 +131,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase public function testAddingStorageWithInvalidClass() { $server = new OAuth2_Server(); - $server->addStorage($this->getMock('OAuth2_ScopeInterface')); + $server->addStorage(new StdClass()); } /** @@ -173,7 +183,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase $storage ->expects($this->any()) ->method('checkRestrictedGrantType') - ->will($this->returnValue(TRUE)); + ->will($this->returnValue(true)); // add with the "code" key explicitly set $codeType = new OAuth2_ResponseType_AuthorizationCode($storage); @@ -181,7 +191,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase $server->addStorage($storage); $server->addResponseType($codeType); $request = new OAuth2_Request(array('response_type' => 'code', 'client_id' => 'some_client', 'redirect_uri' => 'http://example.com')); - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); // the response is successful $this->assertEquals($response->getStatusCode(), 302); @@ -194,7 +204,7 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase $codeType = new OAuth2_ResponseType_AuthorizationCode($storage); $server = new OAuth2_Server(array($storage), array(), array(), array($codeType)); $request = new OAuth2_Request(array('response_type' => 'code', 'client_id' => 'some_client', 'redirect_uri' => 'http://example.com')); - $response = $server->handleAuthorizeRequest($request, true); + $server->handleAuthorizeRequest($request, $response = new OAuth2_Response(), true); // the response is successful $this->assertEquals($response->getStatusCode(), 302); @@ -204,6 +214,30 @@ class OAuth2_ServerTest extends PHPUnit_Framework_TestCase $this->assertFalse(isset($query['error'])); } + public function testCustomClientAssertionType() + { + $request = OAuth2_Request_TestRequest::createPost(array( + 'grant_type' => 'authorization_code', + 'client_id' =>'Test Client ID', + 'code' => 'testcode', + )); + // verify the mock clientAssertionType was called as expected + $clientAssertionType = $this->getMock('OAuth2_ClientAssertionTypeInterface', array('validateRequest', 'getClientId')); + $clientAssertionType + ->expects($this->once()) + ->method('validateRequest') + ->will($this->returnValue(true)); + $clientAssertionType + ->expects($this->once()) + ->method('getClientId') + ->will($this->returnValue('Test Client ID')); + + // create mock storage + $storage = OAuth2_Storage_Bootstrap::getInstance()->getMemoryStorage(); + $server = new OAuth2_Server(array($storage), array(), array(), array(), null, null, $clientAssertionType); + $server->handleTokenRequest($request, $response = new OAuth2_Response()); + } + /** * @expectedException InvalidArgumentException OAuth2_ResponseType_AuthorizationCodeInterface **/ diff --git a/test/OAuth2/StorageTest.php b/test/OAuth2/StorageTest.php index 0f18cff..26d0886 100644 --- a/test/OAuth2/StorageTest.php +++ b/test/OAuth2/StorageTest.php @@ -212,7 +212,7 @@ class OAuth2_StorageTest extends PHPUnit_Framework_TestCase $this->assertFalse($storage->checkUserCredentials('fakeusername', 'testpass')); // invalid username - $this->assertFalse($storage->getUser('fakeusername')); + $this->assertFalse($storage->getUserDetails('fakeusername')); // ensure all properties are set $user = $storage->getUser('testusername'); @@ -227,15 +227,18 @@ class OAuth2_StorageTest extends PHPUnit_Framework_TestCase public function provideStorage() { + $memory = OAuth2_Storage_Bootstrap::getInstance()->getMemoryStorage(); $mysql = OAuth2_Storage_Bootstrap::getInstance()->getMysqlPdo(); $sqlite = OAuth2_Storage_Bootstrap::getInstance()->getSqlitePdo(); $mongo = OAuth2_Storage_Bootstrap::getInstance()->getMongo(); + $redis = OAuth2_Storage_Bootstrap::getInstance()->getRedisStorage(); // will add multiple storage types later return array( array($sqlite), array($mysql), - array($mongo) + array($mongo), + array($redis) ); } } diff --git a/test/config/storage.json b/test/config/storage.json index 967991f..439dcd0 100644 --- a/test/config/storage.json +++ b/test/config/storage.json @@ -4,8 +4,20 @@ "client_id": "Test Client ID", "user_id": "", "redirect_uri": "", + "expires": "9999999999" + }, + "testcode-with-scope": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "", "expires": "9999999999", - "scope": "" + "scope": "scope1 scope2" + }, + "testcode-expired": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "", + "expires": "1356998400" } }, "client_credentials" : { @@ -15,6 +27,13 @@ "Test Client ID with Redirect Uri": { "secret": "TestSecret2", "redirect_uri": "http://brentertainment.com" + }, + "Test Client ID with Multiple Redirect Uris": { + "secret": "TestSecret2", + "redirect_uri": "http://brentertainment.com http://morehazards.com" + }, + "Test Some Other Client": { + "secret": "TestSecret3" } }, "user_credentials" : { @@ -27,6 +46,13 @@ "user_id": "test-username", "expires": 99999999900, "scope": null + }, + "test-refreshtoken-with-scope": { + "refresh_token": "test-refreshtoken", + "client_id": "Test Client ID", + "user_id": "test-username", + "expires": 99999999900, + "scope": "scope1 scope2" } }, "access_tokens" : { @@ -48,26 +74,31 @@ "scope": "testscope" } }, - "jwt": { "Test Client ID": { - "key": "-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAstgzbq1Tyozg6Wn/ugyw\\nng4J2wPOaSJwPWlFFff92K60rQrrCDDUSVeuhC0+Tjn7Lq35gMZrsWazl3oj4KAH BBFZqBPejER0Z/t1wgZka97uooW2BclxUMjL6sFW92BLm2f2Po/SDjKdGRwt0+kd\\nj2MgmdqIDu7KTNLPVS/3zk0Ln+Ett92pbODhUliYHkVFy/ZZtoX66lggLjZXGdBP\\n2pLx/cAR09B0m1QDBvkzGewmF6Hl0gXqKSBQel1vnR8Pp2c8Fjp3sDB65hiStXmf\\na6NpMZBAb4vBp39SyRrbXiNH7GbYoIiBBtgnhVg4zHAG6btsEyAtmVM0aMhn01TK\\nHQIDAQAB\\n-----END PUBLIC KEY-----", + "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5/SxVlE8gnpFqCxgl2wjhzY7u\ncEi00s0kUg3xp7lVEvgLgYcAnHiWp+gtSjOFfH2zsvpiWm6Lz5f743j/FEzHIO1o\nwR0p4d9pOaJK07d01+RzoQLOIQAgXrr4T1CCWUesncwwPBVCyy2Mw3Nmhmr9MrF8\nUlvdRKBxriRnlP3qJQIDAQAB\n-----END PUBLIC KEY-----", "subject": "testuser@ourdomain.com" }, - "Test Client ID PHP-5.2": { "key": "mysecretkey", "subject": "testuser@ourdomain.com" }, - "Missing Key Client": { "key": null, "subject": "testuser@ourdomain.com" }, - "Missing Key Client PHP-5.2": { "key": null, "subject": "testuser@ourdomain.com" } - } + }, + "client_supported_scopes" : { + "Test Client ID" : ["clientscope1", "clientscope2"], + "Test Client ID 2" : ["clientscope3"] + }, + "supported_scopes" : [ + "scope1", + "scope2", + "scope3" + ] }
\ No newline at end of file diff --git a/test/lib/OAuth2/Storage/Bootstrap.php b/test/lib/OAuth2/Storage/Bootstrap.php index 92ea4b6..be02a60 100644 --- a/test/lib/OAuth2/Storage/Bootstrap.php +++ b/test/lib/OAuth2/Storage/Bootstrap.php @@ -33,6 +33,11 @@ class OAuth2_Storage_Bootstrap return new OAuth2_Storage_Memory(json_decode(file_get_contents(dirname(__FILE__).'/../../../config/storage.json'), true)); } + public function getRedisStorage() + { + return new OAuth2_Storage_Redis(new OAuth2_Storage_MockRedisClient()); + } + public function getMysqlPdo() { if (!$this->mysql) { diff --git a/test/lib/OAuth2/Storage/MockRedisClient.php b/test/lib/OAuth2/Storage/MockRedisClient.php new file mode 100644 index 0000000..9ecaa20 --- /dev/null +++ b/test/lib/OAuth2/Storage/MockRedisClient.php @@ -0,0 +1,63 @@ +<?php +class OAuth2_Storage_MockRedisClient +{ + public function __construct() + { + $data = array( + 'oauth_clients:oauth_test_client' => array( + 'client_id' => 'oauth_test_client', + 'client_secret' => "testpass", + 'redirect_uri' => '', + ), + 'oauth_access_tokens:testtoken' => array( + 'access_token' => 'testtoken', + 'client_id' => "Some Client", + 'user_id' => '', + 'expires' => 0, + 'scope' => '' + ), + 'oauth_authorization_codes:testcode' => array( + 'client_id' => "Some Client", + 'authorization_code' => 'testcode', + 'user_id' => '', + 'redirect_uri' => '', + 'expires' => 0, + 'scope' => '' + ), + 'oauth_users:testuser' => array( + 'username' => 'testuser', + 'password' => "password", + 'first_name' => '', + 'last_name' => '' + ) + ); + foreach ( $data as $name => $val ) { + $data[$name] = json_encode($val); + } + $this->data = $data; + } + + function get($key) + { + return isset($this->data[$key]) ? $this->data[$key] : null; + } + + function set ($key, $value) + { + $this->data[$key] = $value; + return true; + } + + function setex($key, $expires, $value) + { + + $this->data[$key] = $value; + return true; + } + + function expire($key) + { + unset($this->data[$key]); + return true; + } +} |
