summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrent Shaffer <bshafs@gmail.com>2014-08-28 06:13:42 +0400
committerBrent Shaffer <bshafs@gmail.com>2014-08-28 06:13:42 +0400
commit74fcc75e47614c1417c750e907a44567d9ceee1f (patch)
tree88b56d61f275dd284d9174705558aa90bf31b588
parenta7f02c9b7b4a18e54a71f93436aa4a6091f399a7 (diff)
parent6a30b9b67ae35c228e852109ed743bf34da22568 (diff)
downloadoauth2-server-php-1.5.tar.xz
Merge pull request #446 from bshaffer/developv1.5
Tag v1.5
-rw-r--r--.travis.yml33
-rw-r--r--composer.json3
-rw-r--r--src/OAuth2/ClientAssertionType/HttpBasic.php19
-rw-r--r--src/OAuth2/Controller/AuthorizeController.php53
-rw-r--r--src/OAuth2/Controller/ResourceController.php4
-rw-r--r--src/OAuth2/GrantType/AuthorizationCode.php3
-rw-r--r--src/OAuth2/GrantType/JwtBearer.php9
-rw-r--r--src/OAuth2/GrantType/RefreshToken.php18
-rw-r--r--src/OAuth2/GrantType/UserCredentials.php3
-rw-r--r--src/OAuth2/OpenID/Controller/AuthorizeController.php19
-rw-r--r--src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php3
-rw-r--r--src/OAuth2/OpenID/Controller/UserInfoController.php3
-rw-r--r--src/OAuth2/OpenID/GrantType/AuthorizationCode.php3
-rw-r--r--src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php17
-rw-r--r--src/OAuth2/OpenID/ResponseType/IdToken.php1
-rw-r--r--src/OAuth2/OpenID/ResponseType/IdTokenInterface.php8
-rw-r--r--src/OAuth2/OpenID/ResponseType/TokenIdToken.php1
-rw-r--r--src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php21
-rw-r--r--src/OAuth2/Request.php1
-rw-r--r--src/OAuth2/Response.php6
-rw-r--r--src/OAuth2/ResponseInterface.php2
-rw-r--r--src/OAuth2/ResponseType/AccessToken.php39
-rw-r--r--src/OAuth2/ResponseType/AccessTokenInterface.php12
-rw-r--r--src/OAuth2/ResponseType/AuthorizationCode.php4
-rw-r--r--src/OAuth2/ResponseType/AuthorizationCodeInterface.php14
-rw-r--r--src/OAuth2/ResponseType/CryptoToken.php33
-rw-r--r--src/OAuth2/Scope.php4
-rw-r--r--src/OAuth2/Server.php26
-rw-r--r--src/OAuth2/Storage/AccessTokenInterface.php15
-rw-r--r--src/OAuth2/Storage/AuthorizationCodeInterface.php18
-rw-r--r--src/OAuth2/Storage/Cassandra.php9
-rw-r--r--src/OAuth2/Storage/ClientInterface.php24
-rw-r--r--src/OAuth2/Storage/CryptoToken.php27
-rw-r--r--src/OAuth2/Storage/DynamoDB.php518
-rw-r--r--src/OAuth2/Storage/Pdo.php86
-rw-r--r--src/OAuth2/Storage/ScopeInterface.php2
-rw-r--r--src/OAuth2/TokenType/Bearer.php4
-rw-r--r--test/OAuth2/Controller/AuthorizeControllerTest.php6
-rw-r--r--test/OAuth2/Controller/ResourceControllerTest.php4
-rw-r--r--test/OAuth2/Controller/TokenControllerTest.php2
-rw-r--r--test/OAuth2/GrantType/UserCredentialsTest.php1
-rw-r--r--test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php156
-rw-r--r--test/OAuth2/OpenID/Controller/UserInfoControllerTest.php14
-rw-r--r--test/OAuth2/OpenID/ResponseType/TokenIdTokenTest.php2
-rw-r--r--test/OAuth2/ResponseTest.php2
-rw-r--r--test/OAuth2/ResponseType/CryptoTokenTest.php43
-rw-r--r--test/OAuth2/ScopeTest.php1
-rw-r--r--test/OAuth2/ServerTest.php28
-rw-r--r--test/OAuth2/Storage/PdoTest.php2
-rw-r--r--test/OAuth2/Storage/ScopeTest.php6
-rw-r--r--test/cleanup.php15
-rw-r--r--test/config/storage.json2
-rw-r--r--test/lib/OAuth2/Storage/BaseTest.php6
-rw-r--r--test/lib/OAuth2/Storage/Bootstrap.php421
-rw-r--r--test/lib/OAuth2/Storage/NullStorage.php2
55 files changed, 1478 insertions, 300 deletions
diff --git a/.travis.yml b/.travis.yml
index cfa65e9..4046941 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,21 +1,24 @@
language: php
-
php:
- - 5.3
- - 5.4
- - 5.5
- - 5.6
- - hhvm
-
+- 5.3
+- 5.4
+- 5.5
+- 5.6
+- hhvm
env:
+ matrix:
- SKIP_MONGO_TESTS=1
-
+ global:
+ - secure: Bc5ZqvZ1YYpoPZNNuU2eCB8DS6vBYrAdfBtTenBs5NSxzb+Vjven4kWakbzaMvZjb/Ib7Uph7DGuOtJXpmxnvBXPLd707LZ89oFWN/yqQlZKCcm8iErvJCB5XL+/ONHj2iPdR242HJweMcat6bMCwbVWoNDidjtWMH0U2mYFy3M=
+ - secure: R3bXlymyFiY2k2jf7+fv/J8i34wtXTkmD4mCr5Ps/U+vn9axm2VtvR2Nj+r7LbRjn61gzFE/xIVjYft/wOyBOYwysrfriydrnRVS0owh6y+7EyOyQWbRX11vVQMf8o31QCQE5BY58V5AJZW3MjoOL0FVlTgySJiJvdw6Pv18v+E=
services:
- - mongodb
- - redis-server
- - cassandra
-
+- mongodb
+- redis-server
+- cassandra
before_script:
- - psql -c 'create database oauth2_server_php;' -U postgres
- - composer require predis/predis:dev-master
- - composer require thobbs/phpcassa:dev-master
+- psql -c 'create database oauth2_server_php;' -U postgres
+- composer require predis/predis:dev-master
+- composer require thobbs/phpcassa:dev-master
+- composer require aws/aws-sdk-php:dev-master
+after_script:
+- php test/cleanup.php \ No newline at end of file
diff --git a/composer.json b/composer.json
index df74ea2..6e2abe2 100644
--- a/composer.json
+++ b/composer.json
@@ -20,6 +20,7 @@
},
"suggest": {
"predis/predis": "Required to use the Redis storage engine",
- "thobbs/phpcassa": "Required to use the Cassandra storage engine"
+ "thobbs/phpcassa": "Required to use the Cassandra storage engine",
+ "aws/aws-sdk-php": "Required to use the DynamoDB storage engine"
}
}
diff --git a/src/OAuth2/ClientAssertionType/HttpBasic.php b/src/OAuth2/ClientAssertionType/HttpBasic.php
index 0e76154..0ecb7e1 100644
--- a/src/OAuth2/ClientAssertionType/HttpBasic.php
+++ b/src/OAuth2/ClientAssertionType/HttpBasic.php
@@ -19,16 +19,14 @@ class HttpBasic implements ClientAssertionTypeInterface
protected $config;
/**
- * @param OAuth2\Storage\ClientCredentialsInterface $clientStorage
- * REQUIRED Storage class for retrieving client credentials information
- * @param array $config
- * OPTIONAL Configuration options for the server
- * @code
- * $config = array(
- * 'allow_credentials_in_request_body' => true, // whether to look for credentials in the POST body in addition to the Authorize HTTP Header
- * 'allow_public_clients' => true // if true, "public clients" (clients without a secret) may be authenticated
- * );
- * @endcode
+ * @param OAuth2\Storage\ClientCredentialsInterface $clientStorage REQUIRED Storage class for retrieving client credentials information
+ * @param array $config OPTIONAL Configuration options for the server
+ * <code>
+ * $config = array(
+ * 'allow_credentials_in_request_body' => true, // whether to look for credentials in the POST body in addition to the Authorize HTTP Header
+ * 'allow_public_clients' => true // if true, "public clients" (clients without a secret) may be authenticated
+ * );
+ * </code>
*/
public function __construct(ClientCredentialsInterface $storage, array $config = array())
{
@@ -110,6 +108,7 @@ class HttpBasic implements ClientAssertionTypeInterface
* client_secret can be null if the client's password is an empty string
* @see http://tools.ietf.org/html/rfc6749#section-2.3.1
*/
+
return array('client_id' => $request->request('client_id'), 'client_secret' => $request->request('client_secret'));
}
}
diff --git a/src/OAuth2/Controller/AuthorizeController.php b/src/OAuth2/Controller/AuthorizeController.php
index e669824..d57d38e 100644
--- a/src/OAuth2/Controller/AuthorizeController.php
+++ b/src/OAuth2/Controller/AuthorizeController.php
@@ -25,23 +25,19 @@ class AuthorizeController implements AuthorizeControllerInterface
protected $scopeUtil;
/**
- * @param OAuth2\Storage\ClientInterface $clientStorage
- * REQUIRED Instance of OAuth2\Storage\ClientInterface to retrieve client information
- * @param array $responseTypes
- * OPTIONAL Array of OAuth2\ResponseType\ResponseTypeInterface objects. Valid array
- * keys are "code" and "token"
- * @param array $config
- * OPTIONAL Configuration options for the server
- * @code
- * $config = array(
- * 'allow_implicit' => false, // if the controller should allow the "implicit" grant type
- * 'enforce_state' => true // if the controller should require the "state" parameter
- * 'require_exact_redirect_uri' => true, // if the controller should require an exact match on the "redirect_uri" parameter
- * 'redirect_status_code' => 302, // HTTP status code to use for redirect responses
- * );
- * @endcode
- * @param OAuth2\ScopeInterface $scopeUtil
- * OPTIONAL Instance of OAuth2\ScopeInterface to validate the requested scope
+ * @param OAuth2\Storage\ClientInterface $clientStorage REQUIRED Instance of OAuth2\Storage\ClientInterface to retrieve client information
+ * @param array $responseTypes OPTIONAL Array of OAuth2\ResponseType\ResponseTypeInterface objects. Valid array
+ * keys are "code" and "token"
+ * @param array $config OPTIONAL Configuration options for the server
+ * <code>
+ * $config = array(
+ * 'allow_implicit' => false, // if the controller should allow the "implicit" grant type
+ * 'enforce_state' => true // if the controller should require the "state" parameter
+ * 'require_exact_redirect_uri' => true, // if the controller should require an exact match on the "redirect_uri" parameter
+ * 'redirect_status_code' => 302, // HTTP status code to use for redirect responses
+ * );
+ * </code>
+ * @param OAuth2\ScopeInterface $scopeUtil OPTIONAL Instance of OAuth2\ScopeInterface to validate the requested scope
*/
public function __construct(ClientInterface $clientStorage, array $responseTypes = array(), array $config = array(), ScopeInterface $scopeUtil = null)
{
@@ -81,7 +77,7 @@ class AuthorizeController implements AuthorizeControllerInterface
// the user declined access to the client's application
if ($is_authorized === false) {
$redirect_uri = $this->redirect_uri ?: $registered_redirect_uri;
- $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->state, 'access_denied', "The user denied access to your application");
+ $this->setNotAuthorizedResponse($request, $response, $redirect_uri, $user_id);
return;
}
@@ -105,6 +101,13 @@ class AuthorizeController implements AuthorizeControllerInterface
$response->setRedirect($this->config['redirect_status_code'], $uri);
}
+ protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null)
+ {
+ $error = 'access_denied';
+ $error_message = 'The user denied access to your application';
+ $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->state, $error, $error_message);
+ }
+
/*
* We have made this protected so this class can be extended to add/modify
* these parameters
@@ -264,10 +267,8 @@ class AuthorizeController implements AuthorizeControllerInterface
/**
* Build the absolute URI based on supplied URI and parameters.
*
- * @param $uri
- * An absolute URI.
- * @param $params
- * Parameters to be append as GET.
+ * @param $uri An absolute URI.
+ * @param $params Parameters to be append as GET.
*
* @return
* An absolute URI with supplied parameters.
@@ -311,11 +312,9 @@ class AuthorizeController implements AuthorizeControllerInterface
/**
* Internal method for validating redirect URI supplied
*
- * @param string $inputUri
- * The submitted URI to be validated
- * @param string $registeredUriString
- * The allowed URI(s) to validate against. Can be a space-delimited string of URIs to
- * allow for multiple URIs
+ * @param string $inputUri The submitted URI to be validated
+ * @param string $registeredUriString The allowed URI(s) to validate against. Can be a space-delimited string of URIs to
+ * allow for multiple URIs
*
* @see http://tools.ietf.org/html/rfc6749#section-3.1.2
*/
diff --git a/src/OAuth2/Controller/ResourceController.php b/src/OAuth2/Controller/ResourceController.php
index 9623b9f..e858818 100644
--- a/src/OAuth2/Controller/ResourceController.php
+++ b/src/OAuth2/Controller/ResourceController.php
@@ -81,9 +81,9 @@ class ResourceController implements ResourceControllerInterface
if (!$token = $this->tokenStorage->getAccessToken($token_param)) {
$response->setError(401, 'invalid_token', 'The access token provided is invalid');
} elseif (!isset($token["expires"]) || !isset($token["client_id"])) {
- $response->setError(401, 'invalid_token', 'Malformed token (missing "expires")');
+ $response->setError(401, 'malformed_token', 'Malformed token (missing "expires")');
} elseif (time() > $token["expires"]) {
- $response->setError(401, 'invalid_token', 'The access token provided has expired');
+ $response->setError(401, 'expired_token', 'The access token provided has expired');
} else {
return $token;
}
diff --git a/src/OAuth2/GrantType/AuthorizationCode.php b/src/OAuth2/GrantType/AuthorizationCode.php
index 69f291e..e899520 100644
--- a/src/OAuth2/GrantType/AuthorizationCode.php
+++ b/src/OAuth2/GrantType/AuthorizationCode.php
@@ -17,8 +17,7 @@ class AuthorizationCode implements GrantTypeInterface
protected $authCode;
/**
- * @param OAuth2\Storage\AuthorizationCodeInterface $storage
- * REQUIRED Storage class for retrieving authorization code information
+ * @param OAuth2\Storage\AuthorizationCodeInterface $storage REQUIRED Storage class for retrieving authorization code information
*/
public function __construct(AuthorizationCodeInterface $storage)
{
diff --git a/src/OAuth2/GrantType/JwtBearer.php b/src/OAuth2/GrantType/JwtBearer.php
index 534feae..ebc5d5a 100644
--- a/src/OAuth2/GrantType/JwtBearer.php
+++ b/src/OAuth2/GrantType/JwtBearer.php
@@ -29,12 +29,9 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
/**
* Creates an instance of the JWT bearer grant type.
*
- * @param OAuth2\Storage\JWTBearerInterface $storage
- * A valid storage interface that implements storage hooks for the JWT bearer grant type.
- * @param string $audience
- * The audience to validate the token against. This is usually the full URI of the OAuth token requests endpoint.
- * @param OAuth2\Encryption\JWT OPTIONAL $jwtUtil
- * The class used to decode, encode and verify JWTs.
+ * @param OAuth2\Storage\JWTBearerInterface $storage A valid storage interface that implements storage hooks for the JWT bearer grant type.
+ * @param string $audience The audience to validate the token against. This is usually the full URI of the OAuth token requests endpoint.
+ * @param OAuth2\Encryption\JWT $jwtUtil OPTONAL The class used to decode, encode and verify JWTs.
*/
public function __construct(JwtBearerInterface $storage, $audience, EncryptionInterface $jwtUtil = null)
{
diff --git a/src/OAuth2/GrantType/RefreshToken.php b/src/OAuth2/GrantType/RefreshToken.php
index 71b1bdb..d6691df 100644
--- a/src/OAuth2/GrantType/RefreshToken.php
+++ b/src/OAuth2/GrantType/RefreshToken.php
@@ -19,15 +19,13 @@ class RefreshToken implements GrantTypeInterface
protected $config;
/**
- * @param OAuth2\Storage\RefreshTokenInterface $storage
- * REQUIRED Storage class for retrieving refresh token information
- * @param array $config
- * OPTIONAL Configuration options for the server
- * @code
- * $config = array(
- * 'always_issue_new_refresh_token' => true, // whether to issue a new refresh token upon successful token request
- * );
- * @endcode
+ * @param OAuth2\Storage\RefreshTokenInterface $storage REQUIRED Storage class for retrieving refresh token information
+ * @param array $config OPTIONAL Configuration options for the server
+ * <code>
+ * $config = array(
+ * 'always_issue_new_refresh_token' => true, // whether to issue a new refresh token upon successful token request
+ * );
+ * </code>
*/
public function __construct(RefreshTokenInterface $storage, $config = array())
{
@@ -80,7 +78,7 @@ class RefreshToken implements GrantTypeInterface
public function getScope()
{
- return $this->refreshToken['scope'];
+ return isset($this->refreshToken['scope']) ? $this->refreshToken['scope'] : null;
}
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
diff --git a/src/OAuth2/GrantType/UserCredentials.php b/src/OAuth2/GrantType/UserCredentials.php
index 5d72aaf..f165538 100644
--- a/src/OAuth2/GrantType/UserCredentials.php
+++ b/src/OAuth2/GrantType/UserCredentials.php
@@ -18,8 +18,7 @@ class UserCredentials implements GrantTypeInterface
protected $storage;
/**
- * @param OAuth2\Storage\UserCredentialsInterface $storage
- * REQUIRED Storage class for retrieving user credentials information
+ * @param OAuth2\Storage\UserCredentialsInterface $storage REQUIRED Storage class for retrieving user credentials information
*/
public function __construct(UserCredentialsInterface $storage)
{
diff --git a/src/OAuth2/OpenID/Controller/AuthorizeController.php b/src/OAuth2/OpenID/Controller/AuthorizeController.php
index 4850ebe..c6fe4f9 100644
--- a/src/OAuth2/OpenID/Controller/AuthorizeController.php
+++ b/src/OAuth2/OpenID/Controller/AuthorizeController.php
@@ -13,6 +13,25 @@ class AuthorizeController extends BaseAuthorizeController implements AuthorizeCo
{
private $nonce;
+ protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null)
+ {
+ $prompt = $request->query('prompt', 'consent');
+ if ($prompt == 'none') {
+ if (is_null($user_id)) {
+ $error = 'login_required';
+ $error_message = 'The user must log in';
+ } else {
+ $error = 'interaction_required';
+ $error_message = 'The user must grant access to your application';
+ }
+ } else {
+ $error = 'consent_required';
+ $error_message = 'The user denied access to your application';
+ }
+
+ $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->getState(), $error, $error_message);
+ }
+
protected function buildAuthorizeParameters($request, $response, $user_id)
{
if (!$params = parent::buildAuthorizeParameters($request, $response, $user_id)) {
diff --git a/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php b/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php
index af47cd0..62704e7 100644
--- a/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php
+++ b/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php
@@ -2,9 +2,6 @@
namespace OAuth2\OpenID\Controller;
-use OAuth2\RequestInterface;
-use OAuth2\ResponseInterface;
-
interface AuthorizeControllerInterface
{
const RESPONSE_TYPE_ID_TOKEN = 'id_token';
diff --git a/src/OAuth2/OpenID/Controller/UserInfoController.php b/src/OAuth2/OpenID/Controller/UserInfoController.php
index 28e429e..30cb942 100644
--- a/src/OAuth2/OpenID/Controller/UserInfoController.php
+++ b/src/OAuth2/OpenID/Controller/UserInfoController.php
@@ -2,6 +2,7 @@
namespace OAuth2\OpenID\Controller;
+use OAuth2\Scope;
use OAuth2\TokenType\TokenTypeInterface;
use OAuth2\Storage\AccessTokenInterface;
use OAuth2\OpenID\Storage\UserClaimsInterface;
@@ -52,6 +53,6 @@ class UserInfoController extends ResourceController implements UserInfoControlle
$claims += array(
'sub' => $token['user_id'],
);
- $response->setParameters($claims);
+ $response->addParameters($claims);
}
}
diff --git a/src/OAuth2/OpenID/GrantType/AuthorizationCode.php b/src/OAuth2/OpenID/GrantType/AuthorizationCode.php
index 4a5da01..8ed1edc 100644
--- a/src/OAuth2/OpenID/GrantType/AuthorizationCode.php
+++ b/src/OAuth2/OpenID/GrantType/AuthorizationCode.php
@@ -3,10 +3,7 @@
namespace OAuth2\OpenID\GrantType;
use OAuth2\GrantType\AuthorizationCode as BaseAuthorizationCode;
-use OAuth2\Storage\AuthorizationCodeInterface;
use OAuth2\ResponseType\AccessTokenInterface;
-use OAuth2\RequestInterface;
-use OAuth2\ResponseInterface;
/**
*
diff --git a/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php b/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php
index 7b8de7c..ea47792 100644
--- a/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php
+++ b/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php
@@ -13,17 +13,12 @@ interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface
/**
* 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.
- * @param $id_token
- * (optional) The OpenID Connect id_token.
+ * @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.
+ * @param $id_token OPTIONAL The OpenID Connect id_token.
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @ingroup oauth2_section_4
diff --git a/src/OAuth2/OpenID/ResponseType/IdToken.php b/src/OAuth2/OpenID/ResponseType/IdToken.php
index 8d907bc..4696155 100644
--- a/src/OAuth2/OpenID/ResponseType/IdToken.php
+++ b/src/OAuth2/OpenID/ResponseType/IdToken.php
@@ -4,7 +4,6 @@ namespace OAuth2\OpenID\ResponseType;
use OAuth2\Encryption\EncryptionInterface;
use OAuth2\Encryption\Jwt;
-use OAuth2\Storage\RefreshTokenInterface;
use OAuth2\Storage\PublicKeyInterface;
use OAuth2\OpenID\Storage\UserClaimsInterface;
diff --git a/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php b/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php
index 3dfb57e..0bd2f83 100644
--- a/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php
+++ b/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php
@@ -15,10 +15,10 @@ interface IdTokenInterface extends ResponseTypeInterface
* If the Implicit Flow is used, the token and id_token are generated and
* returned together.
*
- * @param string $client_id The client id.
- * @param string $user_id The user id.
- * @param string $nonce OPTIONAL The nonce.
- * @param string $userClaims OPTIONAL Claims about the user.
+ * @param string $client_id The client id.
+ * @param string $user_id The user id.
+ * @param string $nonce OPTIONAL The nonce.
+ * @param string $userClaims OPTIONAL Claims about the user.
* @param string $access_token OPTIONAL The access token, if known.
*
* @return string The ID Token represented as a JSON Web Token (JWT).
diff --git a/src/OAuth2/OpenID/ResponseType/TokenIdToken.php b/src/OAuth2/OpenID/ResponseType/TokenIdToken.php
index 67f2a4a..5567569 100644
--- a/src/OAuth2/OpenID/ResponseType/TokenIdToken.php
+++ b/src/OAuth2/OpenID/ResponseType/TokenIdToken.php
@@ -3,7 +3,6 @@
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\AccessTokenInterface;
-use OAuth2\ResponseType\ResponseTypeInterface;
class TokenIdToken implements TokenIdTokenInterface
{
diff --git a/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php b/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php
index 3c6d00b..51dd867 100644
--- a/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php
+++ b/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php
@@ -23,20 +23,13 @@ interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface
*
* Required for OAuth2::GRANT_TYPE_AUTH_CODE.
*
- * @param $code
- * Authorization code to be stored.
- * @param $client_id
- * Client identifier to be stored.
- * @param $user_id
- * User identifier to be stored.
- * @param string $redirect_uri
- * Redirect URI(s) to be stored in a space-separated string.
- * @param int $expires
- * Expiration to be stored as a Unix timestamp.
- * @param string $scope
- * (optional) Scopes to be stored in space-separated string.
- * @param string $id_token
- * (optional) The OpenID Connect id_token.
+ * @param $code authorization code to be stored.
+ * @param $client_id client identifier to be stored.
+ * @param $user_id user identifier to be stored.
+ * @param string $redirect_uri redirect URI(s) to be stored in a space-separated string.
+ * @param int $expires expiration to be stored as a Unix timestamp.
+ * @param string $scope OPTIONAL scopes to be stored in space-separated string.
+ * @param string $id_token OPTIONAL the OpenID Connect id_token.
*
* @ingroup oauth2_section_4
*/
diff --git a/src/OAuth2/Request.php b/src/OAuth2/Request.php
index b37c591..faec0ce 100644
--- a/src/OAuth2/Request.php
+++ b/src/OAuth2/Request.php
@@ -82,6 +82,7 @@ class Request implements RequestInterface
{
$headers = array_change_key_case($this->headers);
$name = strtolower($name);
+
return isset($headers[$name]) ? $headers[$name] : $default;
}
diff --git a/src/OAuth2/Response.php b/src/OAuth2/Response.php
index d746789..d8eabe7 100644
--- a/src/OAuth2/Response.php
+++ b/src/OAuth2/Response.php
@@ -181,13 +181,13 @@ class Response implements ResponseInterface
// this only works for single-level arrays
$xml = new \SimpleXMLElement('<response/>');
foreach ($this->parameters as $key => $param) {
- $xml->addChild($param, $key);
+ $xml->addChild($key, $param);
}
return $xml->asXML();
}
- throw new \InvalidArgumentException(sprintf('The format %s is not supported'));
+ throw new \InvalidArgumentException(sprintf('The format %s is not supported', $format));
}
@@ -243,7 +243,7 @@ class Response implements ResponseInterface
}
}
- public function setRedirect($statusCode = 302, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null)
+ public function setRedirect($statusCode, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null)
{
if (empty($url)) {
throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
diff --git a/src/OAuth2/ResponseInterface.php b/src/OAuth2/ResponseInterface.php
index 842002b..c99b5f7 100644
--- a/src/OAuth2/ResponseInterface.php
+++ b/src/OAuth2/ResponseInterface.php
@@ -18,7 +18,7 @@ interface ResponseInterface
public function setError($statusCode, $name, $description = null, $uri = null);
- public function setRedirect($statusCode = 302, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null);
+ public function setRedirect($statusCode, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null);
public function getParameter($name);
}
diff --git a/src/OAuth2/ResponseType/AccessToken.php b/src/OAuth2/ResponseType/AccessToken.php
index 0fb1121..b05bf87 100644
--- a/src/OAuth2/ResponseType/AccessToken.php
+++ b/src/OAuth2/ResponseType/AccessToken.php
@@ -16,19 +16,16 @@ class AccessToken implements AccessTokenInterface
protected $config;
/**
- * @param OAuth2\Storage\AccessTokenInterface $tokenStorage
- * REQUIRED Storage class for saving access token information
- * @param OAuth2\Storage\RefreshTokenInterface $refreshStorage
- * OPTIONAL Storage class for saving refresh token information
- * @param array $config
- * OPTIONAL Configuration options for the server
- * @code
- * $config = array(
- * 'token_type' => 'bearer', // token type identifier
- * 'access_lifetime' => 3600, // time before access token expires
- * 'refresh_token_lifetime' => 1209600, // time before refresh token expires
- * );
- * @endcode
+ * @param OAuth2\Storage\AccessTokenInterface $tokenStorage REQUIRED Storage class for saving access token information
+ * @param OAuth2\Storage\RefreshTokenInterface $refreshStorage OPTIONAL Storage class for saving refresh token information
+ * @param array $config OPTIONAL Configuration options for the server
+ * <code>
+ * $config = array(
+ * 'token_type' => 'bearer', // token type identifier
+ * 'access_lifetime' => 3600, // time before access token expires
+ * 'refresh_token_lifetime' => 1209600, // time before refresh token expires
+ * );
+ * </endcode>
*/
public function __construct(AccessTokenStorageInterface $tokenStorage, RefreshTokenInterface $refreshStorage = null, array $config = array())
{
@@ -67,14 +64,10 @@ class AccessToken implements AccessTokenInterface
/**
* 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
+ * @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
@@ -124,9 +117,9 @@ class AccessToken implements AccessTokenInterface
$tokenLen = 40;
if (function_exists('mcrypt_create_iv')) {
$randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM);
- } else if (function_exists('openssl_random_pseudo_bytes')) {
+ } elseif (function_exists('openssl_random_pseudo_bytes')) {
$randomData = openssl_random_pseudo_bytes(100);
- } else if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
+ } elseif (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
$randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true);
} else {
$randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
diff --git a/src/OAuth2/ResponseType/AccessTokenInterface.php b/src/OAuth2/ResponseType/AccessTokenInterface.php
index 9190327..2ba4a4f 100644
--- a/src/OAuth2/ResponseType/AccessTokenInterface.php
+++ b/src/OAuth2/ResponseType/AccessTokenInterface.php
@@ -11,14 +11,10 @@ interface AccessTokenInterface extends 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
+ * @param $client_id client identifier related to the access token.
+ * @param $user_id user ID associated with the access token
+ * @param $scope OPTONAL 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
diff --git a/src/OAuth2/ResponseType/AuthorizationCode.php b/src/OAuth2/ResponseType/AuthorizationCode.php
index 51bd437..6a305fd 100644
--- a/src/OAuth2/ResponseType/AuthorizationCode.php
+++ b/src/OAuth2/ResponseType/AuthorizationCode.php
@@ -87,9 +87,9 @@ class AuthorizationCode implements AuthorizationCodeInterface
$tokenLen = 40;
if (function_exists('mcrypt_create_iv')) {
$randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM);
- } else if (function_exists('openssl_random_pseudo_bytes')) {
+ } elseif (function_exists('openssl_random_pseudo_bytes')) {
$randomData = openssl_random_pseudo_bytes(100);
- } else if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
+ } elseif (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
$randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true);
} else {
$randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
diff --git a/src/OAuth2/ResponseType/AuthorizationCodeInterface.php b/src/OAuth2/ResponseType/AuthorizationCodeInterface.php
index 9d5b90f..df777e2 100644
--- a/src/OAuth2/ResponseType/AuthorizationCodeInterface.php
+++ b/src/OAuth2/ResponseType/AuthorizationCodeInterface.php
@@ -17,15 +17,11 @@ interface AuthorizationCodeInterface extends ResponseTypeInterface
/**
* 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.
+ * @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
diff --git a/src/OAuth2/ResponseType/CryptoToken.php b/src/OAuth2/ResponseType/CryptoToken.php
index 3f5ca37..08ff853 100644
--- a/src/OAuth2/ResponseType/CryptoToken.php
+++ b/src/OAuth2/ResponseType/CryptoToken.php
@@ -51,7 +51,7 @@ class CryptoToken extends AccessToken
* @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
+ * 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
@@ -70,21 +70,6 @@ class CryptoToken extends AccessToken
);
/*
- * Issue a refresh token also, if we support them
- *
- * Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface
- * is supplied in the constructor
- */
- if ($includeRefreshToken && $this->refreshStorage) {
- $cryptoToken["refresh_token"] = $this->generateRefreshToken();
- $expires = 0;
- if ($this->config['refresh_token_lifetime'] > 0) {
- $expires = time() + $this->config['refresh_token_lifetime'];
- }
- $this->refreshStorage->setRefreshToken($cryptoToken['refresh_token'], $client_id, $user_id, $expires, $scope);
- }
-
- /*
* Encode the token data into a single access_token string
*/
$access_token = $this->encodeToken($cryptoToken, $client_id);
@@ -105,6 +90,22 @@ class CryptoToken extends AccessToken
'scope' => $scope
);
+ /*
+ * Issue a refresh token also, if we support them
+ *
+ * Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface
+ * is supplied in the constructor
+ */
+ if ($includeRefreshToken && $this->refreshStorage) {
+ $refresh_token = $this->generateRefreshToken();
+ $expires = 0;
+ if ($this->config['refresh_token_lifetime'] > 0) {
+ $expires = time() + $this->config['refresh_token_lifetime'];
+ }
+ $this->refreshStorage->setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope);
+ $token['refresh_token'] = $refresh_token;
+ }
+
return $token;
}
diff --git a/src/OAuth2/Scope.php b/src/OAuth2/Scope.php
index 5c792bb..c44350b 100644
--- a/src/OAuth2/Scope.php
+++ b/src/OAuth2/Scope.php
@@ -68,10 +68,10 @@ class Scope implements ScopeInterface
$nonReservedScopes = array_diff($scope, $reservedScope);
if (count($nonReservedScopes) == 0) {
return true;
- }
- else {
+ } else {
// Check the storage for non-reserved scopes.
$nonReservedScopes = implode(' ', $nonReservedScopes);
+
return $this->storage->scopeExists($nonReservedScopes);
}
}
diff --git a/src/OAuth2/Server.php b/src/OAuth2/Server.php
index b138a55..3434a7b 100644
--- a/src/OAuth2/Server.php
+++ b/src/OAuth2/Server.php
@@ -83,22 +83,15 @@ class Server implements ResourceControllerInterface,
);
/**
- * @param mixed $storage
- * array - array of Objects to implement storage
- * OAuth2\Storage object implementing all required storage types (ClientCredentialsInterface and AccessTokenInterface as a minimum)
- * @param array $config
- * specify a different token lifetime, token header name, etc
- * @param array $grantTypes
- * An array of OAuth2\GrantType\GrantTypeInterface to use for granting access tokens
- * @param array $responseTypes
- * Response types to use. array keys should be "code" and and "token" for
- * Access Token and Authorization Code response types
- * @param OAuth2\TokenType\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\ClientAssertionType\ClientAssertionTypeInterface $clientAssertionType
- * The method in which to verify the client identity. Default is HttpBasic
+ * @param mixed $storage (array or OAuth2\Storage) - single object or array of objects implementing the
+ * required storage types (ClientCredentialsInterface and AccessTokenInterface as a minimum)
+ * @param array $config specify a different token lifetime, token header name, etc
+ * @param array $grantTypes An array of OAuth2\GrantType\GrantTypeInterface to use for granting access tokens
+ * @param array $responseTypes Response types to use. array keys should be "code" and and "token" for
+ * Access Token and Authorization Code response types
+ * @param OAuth2\TokenType\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\ClientAssertionType\ClientAssertionTypeInterface $clientAssertionType The method in which to verify the client identity. Default is HttpBasic
*
* @ingroup oauth2_section_7
*/
@@ -719,6 +712,7 @@ class Server implements ResourceControllerInterface,
}
$config = array_intersect_key($this->config, array_flip(explode(' ', 'issuer id_lifetime')));
+
return new IdToken($this->storages['user_claims'], $this->storages['public_key'], $config);
}
diff --git a/src/OAuth2/Storage/AccessTokenInterface.php b/src/OAuth2/Storage/AccessTokenInterface.php
index b55c3d6..d382f89 100644
--- a/src/OAuth2/Storage/AccessTokenInterface.php
+++ b/src/OAuth2/Storage/AccessTokenInterface.php
@@ -36,16 +36,11 @@ interface AccessTokenInterface
*
* We need to store access token data as we create and verify tokens.
*
- * @param $oauth_token
- * oauth_token to be stored.
- * @param $client_id
- * Client identifier to be stored.
- * @param $user_id
- * User identifier to be stored.
- * @param int $expires
- * Expiration to be stored as a Unix timestamp.
- * @param string $scope
- * (optional) Scopes to be stored in space-separated string.
+ * @param $oauth_token oauth_token to be stored.
+ * @param $client_id client identifier to be stored.
+ * @param $user_id user identifier to be stored.
+ * @param int $expires expiration to be stored as a Unix timestamp.
+ * @param string $scope OPTIONAL Scopes to be stored in space-separated string.
*
* @ingroup oauth2_section_4
*/
diff --git a/src/OAuth2/Storage/AuthorizationCodeInterface.php b/src/OAuth2/Storage/AuthorizationCodeInterface.php
index d59a61f..3beb0e4 100644
--- a/src/OAuth2/Storage/AuthorizationCodeInterface.php
+++ b/src/OAuth2/Storage/AuthorizationCodeInterface.php
@@ -59,18 +59,12 @@ interface AuthorizationCodeInterface
*
* Required for OAuth2::GRANT_TYPE_AUTH_CODE.
*
- * @param $code
- * Authorization code to be stored.
- * @param $client_id
- * Client identifier to be stored.
- * @param $user_id
- * User identifier to be stored.
- * @param string $redirect_uri
- * Redirect URI(s) to be stored in a space-separated string.
- * @param int $expires
- * Expiration to be stored as a Unix timestamp.
- * @param string $scope
- * (optional) Scopes to be stored in space-separated string.
+ * @param string $code Authorization code to be stored.
+ * @param mixed $client_id Client identifier to be stored.
+ * @param mixed $user_id User identifier to be stored.
+ * @param string $redirect_uri Redirect URI(s) to be stored in a space-separated string.
+ * @param int $expires Expiration to be stored as a Unix timestamp.
+ * @param string $scope OPTIONAL Scopes to be stored in space-separated string.
*
* @ingroup oauth2_section_4
*/
diff --git a/src/OAuth2/Storage/Cassandra.php b/src/OAuth2/Storage/Cassandra.php
index f6d1e05..9587b4c 100644
--- a/src/OAuth2/Storage/Cassandra.php
+++ b/src/OAuth2/Storage/Cassandra.php
@@ -50,7 +50,7 @@ class Cassandra implements AuthorizationCodeInterface,
* Cassandra Storage! uses phpCassa
*
* @param \phpcassa\ConnectionPool $cassandra
- * @param array $config
+ * @param array $config
*/
public function __construct($connection = array(), array $config = array())
{
@@ -112,14 +112,14 @@ class Cassandra implements AuthorizationCodeInterface,
$seconds = $expire - time();
// __data key set as C* requires a field, note: max TTL can only be 630720000 seconds
$cf->insert($key, array('__data' => $str), null, $seconds);
- } catch(\Exception $e) {
+ } catch (\Exception $e) {
return false;
}
} else {
try {
// __data key set as C* requires a field
$cf->insert($key, array('__data' => $str));
- } catch(\Exception $e) {
+ } catch (\Exception $e) {
return false;
}
}
@@ -135,7 +135,7 @@ class Cassandra implements AuthorizationCodeInterface,
try {
// __data key set as C* requires a field
$cf->remove($key, array('__data'));
- } catch(\Exception $e) {
+ } catch (\Exception $e) {
return false;
}
@@ -364,4 +364,3 @@ class Cassandra implements AuthorizationCodeInterface,
throw new \Exception('setJti() for the Cassandra driver is currently unimplemented.');
}
}
-
diff --git a/src/OAuth2/Storage/ClientInterface.php b/src/OAuth2/Storage/ClientInterface.php
index ec8d7fd..09a5bff 100644
--- a/src/OAuth2/Storage/ClientInterface.php
+++ b/src/OAuth2/Storage/ClientInterface.php
@@ -20,18 +20,18 @@ interface ClientInterface
* Client identifier to be check with.
*
* @return array
- * 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.
- * @code
- * return array(
- * "redirect_uri" => REDIRECT_URI, // REQUIRED redirect_uri registered for the client
- * "client_id" => CLIENT_ID, // OPTIONAL the client id
- * "grant_types" => GRANT_TYPES, // OPTIONAL an array of restricted grant types
- * "user_id" => USER_ID, // OPTIONAL the user identifier associated with this client
- * "scope" => SCOPE, // OPTIONAL the scopes allowed for this client
- * );
- * @endcode
+ * 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.
+ * <code>
+ * return array(
+ * "redirect_uri" => REDIRECT_URI, // REQUIRED redirect_uri registered for the client
+ * "client_id" => CLIENT_ID, // OPTIONAL the client id
+ * "grant_types" => GRANT_TYPES, // OPTIONAL an array of restricted grant types
+ * "user_id" => USER_ID, // OPTIONAL the user identifier associated with this client
+ * "scope" => SCOPE, // OPTIONAL the scopes allowed for this client
+ * );
+ * </code>
*
* @ingroup oauth2_section_4
*/
diff --git a/src/OAuth2/Storage/CryptoToken.php b/src/OAuth2/Storage/CryptoToken.php
index f107e45..31ebb65 100644
--- a/src/OAuth2/Storage/CryptoToken.php
+++ b/src/OAuth2/Storage/CryptoToken.php
@@ -16,28 +16,11 @@ class CryptoToken implements CryptoTokenInterface
protected $encryptionUtil;
/**
- * @param string $publicKey
- * the public key encryption to use
- *
- * @param string $privateKey (optional)
- * (optional) the private key to use to sign tokens
- * this is only required for token granting, and can be omitted for resource servers,
- * as only the publc key is required for crypto token verification
- *
- * @param array $config
- * (optional) configuration array. Valid parameters are
- * - encryption_algorithm
- * the algorithm to use for encryption. This is passed to the
- * EncryptionInterface object.
- * @see OAuth2\Encryption\Jwt::verifySignature
- *
- * @param OAuth2\Storage\AccessTokenInterface $tokenStorage
- * (optional) persist the access token to another storage. This is useful if
- * you want to retain access token grant information somewhere, but
- * is not necessary when using this grant type.
- *
- * @param OAuth2\Encryption\EncryptionInterface $encryptionUtil
- * (optional) class to use for "encode" and "decode" functions.
+ * @param OAuth2\Encryption\PublicKeyInterface $publicKeyStorage the public key encryption to use
+ * @param OAuth2\Storage\AccessTokenInterface $tokenStorage OPTIONAL persist the access token to another storage. This is useful if
+ * you want to retain access token grant information somewhere, but
+ * is not necessary when using this grant type.
+ * @param OAuth2\Encryption\EncryptionInterface $encryptionUtil OPTIONAL class to use for "encode" and "decode" functions.
*/
public function __construct(PublicKeyInterface $publicKeyStorage, AccessTokenInterface $tokenStorage = null, EncryptionInterface $encryptionUtil = null)
{
diff --git a/src/OAuth2/Storage/DynamoDB.php b/src/OAuth2/Storage/DynamoDB.php
new file mode 100644
index 0000000..fe06428
--- /dev/null
+++ b/src/OAuth2/Storage/DynamoDB.php
@@ -0,0 +1,518 @@
+<?php
+
+namespace OAuth2\Storage;
+
+use Aws\DynamoDb\DynamoDbClient;
+
+use OAuth2\OpenID\Storage\UserClaimsInterface;
+use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
+/**
+ * DynamoDB storage for all storage types
+ *
+ * To use, install "aws/aws-sdk-php" via composer
+ * <code>
+ * composer require aws/aws-sdk-php:dev-master
+ * </code>
+ *
+ * Once this is done, instantiate the DynamoDB client
+ * <code>
+ * $storage = new OAuth2\Storage\Dynamodb(array("key" => "YOURKEY", "secret" => "YOURSECRET", "region" => "YOURREGION"));
+ * </code>
+ *
+ * Table :
+ * - oauth_access_tokens (primary hash key : access_token)
+ * - oauth_authorization_codes (primary hash key : authorization_code)
+ * - oauth_clients (primary hash key : client_id)
+ * - oauth_jwt (primary hash key : client_id, primary range key : subject)
+ * - oauth_public_keys (primary hash key : client_id)
+ * - oauth_refresh_tokens (primary hash key : refresh_token)
+ * - oauth_scopes (primary hash key : scope, secondary index : is_default-index hash key is_default)
+ * - oauth_users (primary hash key : username)
+ *
+ * @author Frederic AUGUSTE <frederic.auguste at gmail dot com>
+ */
+class DynamoDB implements
+ AuthorizationCodeInterface,
+ AccessTokenInterface,
+ ClientCredentialsInterface,
+ UserCredentialsInterface,
+ RefreshTokenInterface,
+ JwtBearerInterface,
+ ScopeInterface,
+ PublicKeyInterface,
+ UserClaimsInterface,
+ OpenIDAuthorizationCodeInterface
+{
+ protected $client;
+ protected $config;
+
+ public function __construct($connection, $config = array())
+ {
+ if (!($connection instanceof DynamoDbClient)) {
+ if (!is_array($connection)) {
+ throw new \InvalidArgumentException('First argument to OAuth2\Storage\Dynamodb must be an instance a configuration array containt key, secret, region');
+ }
+ if (!array_key_exists("key",$connection) || !array_key_exists("secret",$connection) || !array_key_exists("region",$connection) ) {
+ throw new \InvalidArgumentException('First argument to OAuth2\Storage\Dynamodb must be an instance a configuration array containt key, secret, region');
+ }
+ $this->client = DynamoDbClient::factory(array(
+ 'key' => $connection["key"],
+ 'secret' => $connection["secret"],
+ 'region' =>$connection["region"]
+ ));
+ } else {
+ $this->client = $connection;
+ }
+
+ $this->config = array_merge(array(
+ 'client_table' => 'oauth_clients',
+ 'access_token_table' => 'oauth_access_tokens',
+ 'refresh_token_table' => 'oauth_refresh_tokens',
+ 'code_table' => 'oauth_authorization_codes',
+ 'user_table' => 'oauth_users',
+ 'jwt_table' => 'oauth_jwt',
+ 'scope_table' => 'oauth_scopes',
+ 'public_key_table' => 'oauth_public_keys',
+ ), $config);
+ }
+
+ /* OAuth2\Storage\ClientCredentialsInterface */
+ public function checkClientCredentials($client_id, $client_secret = null)
+ {
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['client_table'],
+ "Key" => array('client_id' => array('S' => $client_id))
+ ));
+
+ return $result->count()==1 && $result["Item"]["client_secret"]["S"] == $client_secret;
+ }
+
+ public function isPublicClient($client_id)
+ {
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['client_table'],
+ "Key" => array('client_id' => array('S' => $client_id))
+ ));
+
+ if ($result->count()==0) {
+ return false ;
+ }
+
+ return empty($result["Item"]["client_secret"]);
+ }
+
+ /* OAuth2\Storage\ClientInterface */
+ public function getClientDetails($client_id)
+ {
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['client_table'],
+ "Key" => array('client_id' => array('S' => $client_id))
+ ));
+ if ($result->count()==0) {
+ return false ;
+ }
+ $result = $this->dynamo2array($result);
+ foreach (array('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id') as $key => $val) {
+ if (!array_key_exists ($val, $result)) {
+ $result[$val] = null;
+ }
+ }
+
+ return $result;
+ }
+
+ public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null)
+ {
+ $clientData = compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id');
+ $clientData = array_filter($clientData, function ($value) { return !is_null($value); });
+
+ $result = $this->client->putItem(array(
+ 'TableName' => $this->config['client_table'],
+ 'Item' => $this->client->formatAttributes($clientData)
+ ));
+
+ return true;
+ }
+
+ public function checkRestrictedGrantType($client_id, $grant_type)
+ {
+ $details = $this->getClientDetails($client_id);
+ if (isset($details['grant_types'])) {
+ $grant_types = explode(' ', $details['grant_types']);
+
+ return in_array($grant_type, (array) $grant_types);
+ }
+
+ // if grant_types are not defined, then none are restricted
+ return true;
+ }
+
+ /* OAuth2\Storage\AccessTokenInterface */
+ public function getAccessToken($access_token)
+ {
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['access_token_table'],
+ "Key" => array('access_token' => array('S' => $access_token))
+ ));
+ if ($result->count()==0) {
+ return false ;
+ }
+ $token = $this->dynamo2array($result);
+ if (array_key_exists ('expires', $token)) {
+ $token['expires'] = strtotime($token['expires']);
+ }
+
+ return $token;
+ }
+
+ public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null)
+ {
+ // convert expires to datestring
+ $expires = date('Y-m-d H:i:s', $expires);
+
+ $clientData = compact('access_token', 'client_id', 'user_id', 'expires', 'scope');
+ $clientData = array_filter($clientData, function ($value) { return !is_null($value); });
+
+ $result = $this->client->putItem(array(
+ 'TableName' => $this->config['access_token_table'],
+ 'Item' => $this->client->formatAttributes($clientData)
+ ));
+
+ return true;
+
+ }
+
+ /* OAuth2\Storage\AuthorizationCodeInterface */
+ public function getAuthorizationCode($code)
+ {
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['code_table'],
+ "Key" => array('authorization_code' => array('S' => $code))
+ ));
+ if ($result->count()==0) {
+ return false ;
+ }
+ $token = $this->dynamo2array($result);
+ if (!array_key_exists("id_token", $token )) {
+ $token['id_token'] = null;
+ }
+ $token['expires'] = strtotime($token['expires']);
+
+ return $token;
+
+ }
+
+ public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
+ {
+ // convert expires to datestring
+ $expires = date('Y-m-d H:i:s', $expires);
+
+ $clientData = compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'id_token', 'scope');
+ $clientData = array_filter($clientData, function ($value) { return !is_null($value); });
+
+ $result = $this->client->putItem(array(
+ 'TableName' => $this->config['code_table'],
+ 'Item' => $this->client->formatAttributes($clientData)
+ ));
+
+ return true;
+ }
+
+ public function expireAuthorizationCode($code)
+ {
+
+ $result = $this->client->deleteItem(array(
+ 'TableName' => $this->config['code_table'],
+ 'Key' => $this->client->formatAttributes(array("authorization_code" => $code))
+ ));
+
+ return true;
+ }
+
+ /* OAuth2\Storage\UserCredentialsInterface */
+ public function checkUserCredentials($username, $password)
+ {
+ if ($user = $this->getUser($username)) {
+ return $this->checkPassword($user, $password);
+ }
+
+ return false;
+ }
+
+ public function getUserDetails($username)
+ {
+ return $this->getUser($username);
+ }
+
+ /* UserClaimsInterface */
+ public function getUserClaims($user_id, $claims)
+ {
+ if (!$userDetails = $this->getUserDetails($user_id)) {
+ return false;
+ }
+
+ $claims = explode(' ', trim($claims));
+ $userClaims = array();
+
+ // for each requested claim, if the user has the claim, set it in the response
+ $validClaims = explode(' ', self::VALID_CLAIMS);
+ foreach ($validClaims as $validClaim) {
+ if (in_array($validClaim, $claims)) {
+ if ($validClaim == 'address') {
+ // address is an object with subfields
+ $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails);
+ } else {
+ $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails));
+ }
+ }
+ }
+
+ return $userClaims;
+ }
+
+ protected function getUserClaim($claim, $userDetails)
+ {
+ $userClaims = array();
+ $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim)));
+ $claimValues = explode(' ', $claimValuesString);
+
+ foreach ($claimValues as $value) {
+ if ($value == 'email_verified') {
+ $userClaims[$value] = $userDetails[$value]=='true' ? true : false;
+ } else {
+ $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null;
+ }
+ }
+
+ return $userClaims;
+ }
+
+ /* OAuth2\Storage\RefreshTokenInterface */
+ public function getRefreshToken($refresh_token)
+ {
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['refresh_token_table'],
+ "Key" => array('refresh_token' => array('S' => $refresh_token))
+ ));
+ if ($result->count()==0) {
+ return false ;
+ }
+ $token = $this->dynamo2array($result);
+ $token['expires'] = strtotime($token['expires']);
+
+ return $token;
+ }
+
+ public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null)
+ {
+ // convert expires to datestring
+ $expires = date('Y-m-d H:i:s', $expires);
+
+ $clientData = compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope');
+ $clientData = array_filter($clientData, function ($value) { return !is_null($value); });
+
+ $result = $this->client->putItem(array(
+ 'TableName' => $this->config['refresh_token_table'],
+ 'Item' => $this->client->formatAttributes($clientData)
+ ));
+
+ return true;
+ }
+
+ public function unsetRefreshToken($refresh_token)
+ {
+ $result = $this->client->deleteItem(array(
+ 'TableName' => $this->config['refresh_token_table'],
+ 'Key' => $this->client->formatAttributes(array("refresh_token" => $refresh_token))
+ ));
+
+ return true;
+ }
+
+ // plaintext passwords are bad! Override this for your application
+ protected function checkPassword($user, $password)
+ {
+ return $user['password'] == sha1($password);
+ }
+
+ public function getUser($username)
+ {
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['user_table'],
+ "Key" => array('username' => array('S' => $username))
+ ));
+ if ($result->count()==0) {
+ return false ;
+ }
+ $token = $this->dynamo2array($result);
+ $token['user_id'] = $username;
+
+ return $token;
+ }
+
+ public function setUser($username, $password, $first_name = null, $last_name = null)
+ {
+ // do not store in plaintext
+ $password = sha1($password);
+
+ $clientData = compact('username', 'password', 'first_name', 'last_name');
+ $clientData = array_filter($clientData, function ($value) { return !is_null($value); });
+
+ $result = $this->client->putItem(array(
+ 'TableName' => $this->config['user_table'],
+ 'Item' => $this->client->formatAttributes($clientData)
+ ));
+
+ return true;
+
+ }
+
+ /* ScopeInterface */
+ public function scopeExists($scope)
+ {
+ $scope = explode(' ', $scope);
+ $scope_query = array();
+ $count = 0;
+ foreach ($scope as $key => $val) {
+ $result = $this->client->query(array(
+ 'TableName' => $this->config['scope_table'],
+ 'Select' => 'COUNT',
+ 'KeyConditions' => array(
+ 'scope' => array(
+ 'AttributeValueList' => array(array('S' => $val)),
+ 'ComparisonOperator' => 'EQ'
+ )
+ )
+ ));
+ $count += $result['Count'];
+ }
+
+ return $count == count($scope);
+ }
+
+ public function getDefaultScope($client_id = null)
+ {
+
+ $result = $this->client->query(array(
+ 'TableName' => $this->config['scope_table'],
+ 'IndexName' => 'is_default-index',
+ 'Select' => 'ALL_ATTRIBUTES',
+ 'KeyConditions' => array(
+ 'is_default' => array(
+ 'AttributeValueList' => array(array('S' => 'true')),
+ 'ComparisonOperator' => 'EQ',
+ ),
+ )
+ ));
+ $defaultScope = array();
+ if ($result->count() > 0) {
+ $array = $result->toArray();
+ foreach ($array["Items"] as $item) {
+ $defaultScope[] = $item['scope']['S'];
+ }
+
+ return implode(' ', $defaultScope);
+ }
+
+ return null;
+ }
+
+ /* JWTBearerInterface */
+ public function getClientKey($client_id, $subject)
+ {
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['jwt_table'],
+ "Key" => array('client_id' => array('S' => $client_id), 'subject' => array('S' => $subject))
+ ));
+ if ($result->count()==0) {
+ return false ;
+ }
+ $token = $this->dynamo2array($result);
+
+ return $token['public_key'];
+ }
+
+ public function getClientScope($client_id)
+ {
+ if (!$clientDetails = $this->getClientDetails($client_id)) {
+ return false;
+ }
+
+ if (isset($clientDetails['scope'])) {
+ return $clientDetails['scope'];
+ }
+
+ return null;
+ }
+
+ public function getJti($client_id, $subject, $audience, $expires, $jti)
+ {
+ //TODO not use.
+ }
+
+ public function setJti($client_id, $subject, $audience, $expires, $jti)
+ {
+ //TODO not use.
+ }
+
+ /* PublicKeyInterface */
+ public function getPublicKey($client_id = '0')
+ {
+
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['public_key_table'],
+ "Key" => array('client_id' => array('S' => $client_id))
+ ));
+ if ($result->count()==0) {
+ return false ;
+ }
+ $token = $this->dynamo2array($result);
+
+ return $token['public_key'];
+
+ }
+
+ public function getPrivateKey($client_id = '0')
+ {
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['public_key_table'],
+ "Key" => array('client_id' => array('S' => $client_id))
+ ));
+ if ($result->count()==0) {
+ return false ;
+ }
+ $token = $this->dynamo2array($result);
+
+ return $token['private_key'];
+ }
+
+ public function getEncryptionAlgorithm($client_id = null)
+ {
+ $result = $this->client->getItem(array(
+ "TableName"=> $this->config['public_key_table'],
+ "Key" => array('client_id' => array('S' => $client_id))
+ ));
+ if ($result->count()==0) {
+ return 'RS256' ;
+ }
+ $token = $this->dynamo2array($result);
+
+ return $token['encryption_algorithm'];
+ }
+
+ /**
+ * Transform dynamodb resultset to an array.
+ * @param $dynamodbResult
+ * @return $array
+ */
+ private function dynamo2array($dynamodbResult)
+ {
+ $result = array();
+ foreach ($dynamodbResult["Item"] as $key => $val) {
+ $result[$key] = $val["S"];
+ $result[] = $val["S"];
+ }
+
+ return $result;
+ }
+}
diff --git a/src/OAuth2/Storage/Pdo.php b/src/OAuth2/Storage/Pdo.php
index 0cdd52e..17016e7 100644
--- a/src/OAuth2/Storage/Pdo.php
+++ b/src/OAuth2/Storage/Pdo.php
@@ -4,6 +4,7 @@ namespace OAuth2\Storage;
use OAuth2\OpenID\Storage\UserClaimsInterface;
use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
+
/**
* Simple PDO storage for all storage types
*
@@ -16,7 +17,8 @@ use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeI
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
-class Pdo implements AuthorizationCodeInterface,
+class Pdo implements
+ AuthorizationCodeInterface,
AccessTokenInterface,
ClientCredentialsInterface,
UserCredentialsInterface,
@@ -242,7 +244,7 @@ class Pdo implements AuthorizationCodeInterface,
// address is an object with subfields
$userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails);
} else {
- $userClaims = array_merge($this->getUserClaim($validClaim, $userDetails));
+ $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails));
}
}
}
@@ -442,4 +444,84 @@ class Pdo implements AuthorizationCodeInterface,
return 'RS256';
}
+
+ /**
+ * DDL to create OAuth2 database and tables for PDO storage
+ *
+ * @see https://github.com/dsquier/oauth2-server-php-mysql
+ */
+ public function getBuildSql($dbName = 'oauth2_server_php')
+ {
+ $sql = "
+ CREATE TABLE {$this->config['client_table']} (
+ client_id VARCHAR(80) NOT NULL,
+ client_secret VARCHAR(80) NOT NULL,
+ redirect_uri VARCHAR(2000),
+ grant_types VARCHAR(80),
+ scope VARCHAR(4000),
+ user_id VARCHAR(80),
+ PRIMARY KEY (client_id)
+ );
+
+ CREATE TABLE {$this->config['access_token_table']} (
+ access_token VARCHAR(40) NOT NULL,
+ client_id VARCHAR(80) NOT NULL,
+ user_id VARCHAR(80),
+ expires TIMESTAMP NOT NULL,
+ scope VARCHAR(4000),
+ PRIMARY KEY (access_token)
+ );
+
+ CREATE TABLE {$this->config['code_table']} (
+ authorization_code VARCHAR(40) NOT NULL,
+ client_id VARCHAR(80) NOT NULL,
+ user_id VARCHAR(80),
+ redirect_uri VARCHAR(2000),
+ expires TIMESTAMP NOT NULL,
+ scope VARCHAR(4000),
+ id_token VARCHAR(1000),
+ PRIMARY KEY (authorization_code)
+ );
+
+ CREATE TABLE {$this->config['refresh_token_table']} (
+ refresh_token VARCHAR(40) NOT NULL,
+ client_id VARCHAR(80) NOT NULL,
+ user_id VARCHAR(80),
+ expires TIMESTAMP NOT NULL,
+ scope VARCHAR(4000),
+ PRIMARY KEY (refresh_token)
+ );
+
+ CREATE TABLE {$this->config['user_table']} (
+ username VARCHAR(80),
+ password VARCHAR(80),
+ first_name VARCHAR(80),
+ last_name VARCHAR(80),
+ email VARCHAR(80),
+ email_verified BOOLEAN,
+ scope VARCHAR(4000)
+ );
+
+ CREATE TABLE {$this->config['scope_table']} (
+ scope VARCHAR(80) NOT NULL,
+ is_default BOOLEAN,
+ PRIMARY KEY (scope)
+ );
+
+ CREATE TABLE {$this->config['jwt_table']} (
+ client_id VARCHAR(80) NOT NULL,
+ subject VARCHAR(80),
+ public_key VARCHAR(2000) NOT NULL
+ );
+
+ CREATE TABLE {$this->config['public_key_table']} (
+ client_id VARCHAR(80),
+ public_key VARCHAR(2000),
+ private_key VARCHAR(2000),
+ encryption_algorithm VARCHAR(100) DEFAULT 'RS256'
+ )
+";
+
+ return $sql;
+ }
}
diff --git a/src/OAuth2/Storage/ScopeInterface.php b/src/OAuth2/Storage/ScopeInterface.php
index e88ff7f..a829226 100644
--- a/src/OAuth2/Storage/ScopeInterface.php
+++ b/src/OAuth2/Storage/ScopeInterface.php
@@ -28,7 +28,7 @@ interface ScopeInterface
* request_error is returned by the server to force a
* scope request by the client. By returning "null",
* opt out of requiring scopes
- *
+ *
* @param $client_id
* An optional client id that can be used to return customized default scopes.
*
diff --git a/src/OAuth2/TokenType/Bearer.php b/src/OAuth2/TokenType/Bearer.php
index 0672572..0ec9fec 100644
--- a/src/OAuth2/TokenType/Bearer.php
+++ b/src/OAuth2/TokenType/Bearer.php
@@ -35,7 +35,7 @@ class Bearer implements TokenTypeInterface
$headers = $request->headers('AUTHORIZATION');
// check the header, then the querystring, then the request body
- return !empty($headers) || (bool)($request->request($this->config['token_param_name'])) || (bool)($request->query($this->config['token_param_name']));
+ return !empty($headers) || (bool) ($request->request($this->config['token_param_name'])) || (bool) ($request->query($this->config['token_param_name']));
}
/**
@@ -70,7 +70,7 @@ class Bearer implements TokenTypeInterface
*
* @see http://tools.ietf.org/html/rfc6750#section-3.1
*/
- $methodsUsed = !empty($headers) + (bool)($request->query($this->config['token_param_name'])) + (bool)($request->request($this->config['token_param_name']));
+ $methodsUsed = !empty($headers) + (bool) ($request->query($this->config['token_param_name'])) + (bool) ($request->request($this->config['token_param_name']));
if ($methodsUsed > 1) {
$response->setError(400, 'invalid_request', 'Only one method may be used to authenticate at a time (Auth header, GET or POST)');
diff --git a/test/OAuth2/Controller/AuthorizeControllerTest.php b/test/OAuth2/Controller/AuthorizeControllerTest.php
index 1bce0ef..c2745d6 100644
--- a/test/OAuth2/Controller/AuthorizeControllerTest.php
+++ b/test/OAuth2/Controller/AuthorizeControllerTest.php
@@ -203,7 +203,7 @@ class AuthorizeControllerTest extends \PHPUnit_Framework_TestCase
$request = new Request(array(
'client_id' => 'Test Client ID with Redirect Uri Parts', // valid client id
'response_type' => 'code',
- 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth',
+ 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true',
'state' => 'xyz',
));
@@ -221,7 +221,7 @@ class AuthorizeControllerTest extends \PHPUnit_Framework_TestCase
$request = new Request(array(
'client_id' => 'Test Client ID with Redirect Uri Parts', // valid client id
'response_type' => 'code',
- 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&hereisa=querystring',
+ 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true&hereisa=querystring',
));
$server->handleAuthorizeRequest($request, $response = new Response(), true);
@@ -239,7 +239,7 @@ class AuthorizeControllerTest extends \PHPUnit_Framework_TestCase
$request = new Request(array(
'client_id' => 'Test Client ID with Redirect Uri Parts', // valid client id
'response_type' => 'code',
- 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&hereisa=querystring',
+ 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true&hereisa=querystring',
'state' => 'xyz',
));
diff --git a/test/OAuth2/Controller/ResourceControllerTest.php b/test/OAuth2/Controller/ResourceControllerTest.php
index fb25550..ee6d96f 100644
--- a/test/OAuth2/Controller/ResourceControllerTest.php
+++ b/test/OAuth2/Controller/ResourceControllerTest.php
@@ -100,7 +100,7 @@ class ResourceControllerTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($allow);
$this->assertEquals($response->getStatusCode(), 401);
- $this->assertEquals($response->getParameter('error'), 'invalid_token');
+ $this->assertEquals($response->getParameter('error'), 'expired_token');
$this->assertEquals($response->getParameter('error_description'), 'The access token provided has expired');
}
@@ -132,7 +132,7 @@ class ResourceControllerTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($allow);
$this->assertEquals($response->getStatusCode(), 401);
- $this->assertEquals($response->getParameter('error'), 'invalid_token');
+ $this->assertEquals($response->getParameter('error'), 'malformed_token');
$this->assertEquals($response->getParameter('error_description'), 'Malformed token (missing "expires")');
}
diff --git a/test/OAuth2/Controller/TokenControllerTest.php b/test/OAuth2/Controller/TokenControllerTest.php
index f41a95c..d53181d 100644
--- a/test/OAuth2/Controller/TokenControllerTest.php
+++ b/test/OAuth2/Controller/TokenControllerTest.php
@@ -11,7 +11,7 @@ use OAuth2\Scope;
use OAuth2\Request\TestRequest;
use OAuth2\Response;
-class Controller_TokenControllerTest extends \PHPUnit_Framework_TestCase
+class TokenControllerTest extends \PHPUnit_Framework_TestCase
{
public function testNoGrantType()
{
diff --git a/test/OAuth2/GrantType/UserCredentialsTest.php b/test/OAuth2/GrantType/UserCredentialsTest.php
index 447caf5..18943d0 100644
--- a/test/OAuth2/GrantType/UserCredentialsTest.php
+++ b/test/OAuth2/GrantType/UserCredentialsTest.php
@@ -138,7 +138,6 @@ class UserCredentialsTest extends \PHPUnit_Framework_TestCase
'password' => 'testpass', // valid password
));
-
$token = $server->grantAccessToken($request, $response = new Response());
$this->assertNotNull($token);
diff --git a/test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php b/test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php
new file mode 100644
index 0000000..4b217c5
--- /dev/null
+++ b/test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace OAuth2\OpenID\Controller;
+
+use OAuth2\OpenID\Controller\AuthorizeController;
+use OAuth2\OpenID\ResponseType\IdToken;
+use OAuth2\OpenID\ResponseType\TokenIdToken;
+use OAuth2\ResponseType\AccessToken;
+use OAuth2\Storage\Bootstrap;
+use OAuth2\Server;
+use OAuth2\Request;
+use OAuth2\Response;
+
+class AuthorizeControllerTest extends \PHPUnit_Framework_TestCase
+{
+ public function testValidateAuthorizeRequest()
+ {
+ $server = $this->getTestServer();
+
+ $response = new Response();
+ $request = new Request(array(
+ 'client_id' => 'Test Client ID', // valid client id
+ 'redirect_uri' => 'http://adobe.com', // valid redirect URI
+ 'response_type' => 'id_token',
+ 'state' => 'af0ifjsldkj',
+ 'nonce' => 'n-0S6_WzA2Mj',
+ ));
+
+ // Test valid id_token request
+ $server->handleAuthorizeRequest($request, $response, true);
+
+ $parts = parse_url($response->getHttpHeader('Location'));
+ parse_str($parts['fragment'], $query);
+
+ $this->assertEquals('n-0S6_WzA2Mj', $server->getAuthorizeController()->getNonce());
+ $this->assertEquals($query['state'], 'af0ifjsldkj');
+
+ $this->assertArrayHasKey('id_token', $query);
+ $this->assertArrayHasKey('state', $query);
+ $this->assertArrayNotHasKey('access_token', $query);
+ $this->assertArrayNotHasKey('expires_in', $query);
+ $this->assertArrayNotHasKey('token_type', $query);
+
+ // Test valid token id_token request
+ $request->query['response_type'] = 'token id_token';
+ $server->handleAuthorizeRequest($request, $response, true);
+
+ $parts = parse_url($response->getHttpHeader('Location'));
+ parse_str($parts['fragment'], $query);
+
+ $this->assertEquals('n-0S6_WzA2Mj', $server->getAuthorizeController()->getNonce());
+ $this->assertEquals($query['state'], 'af0ifjsldkj');
+
+ $this->assertArrayHasKey('access_token', $query);
+ $this->assertArrayHasKey('expires_in', $query);
+ $this->assertArrayHasKey('token_type', $query);
+ $this->assertArrayHasKey('state', $query);
+ $this->assertArrayHasKey('id_token', $query);
+ }
+
+ public function testMissingNonce()
+ {
+ $server = $this->getTestServer();
+ $authorize = $server->getAuthorizeController();
+
+ $response = new Response();
+ $request = new Request(array(
+ 'client_id' => 'Test Client ID', // valid client id
+ 'redirect_uri' => 'http://adobe.com', // valid redirect URI
+ 'response_type' => 'id_token',
+ 'state' => 'xyz',
+ ));
+
+ // Test missing nonce for 'id_token' response type
+ $server->handleAuthorizeRequest($request, $response, true);
+
+ $params = $response->getParameters();
+
+ $this->assertEquals($params['error'], 'invalid_nonce');
+ $this->assertEquals($params['error_description'], 'This application requires you specify a nonce parameter');
+
+ // Test missing nonce for 'id_token' response type
+ $request->query['response_type'] = 'token id_token';
+ $server->handleAuthorizeRequest($request, $response, true);
+
+ $params = $response->getParameters();
+
+ $this->assertEquals($params['error'], 'invalid_nonce');
+ $this->assertEquals($params['error_description'], 'This application requires you specify a nonce parameter');
+ }
+
+ public function testNotGrantedApplication()
+ {
+ $server = $this->getTestServer();
+
+ $response = new Response();
+ $request = new Request(array(
+ 'client_id' => 'Test Client ID', // valid client id
+ 'redirect_uri' => 'http://adobe.com', // valid redirect URI
+ 'response_type' => 'id_token',
+ 'state' => 'af0ifjsldkj',
+ 'nonce' => 'n-0S6_WzA2Mj',
+ ));
+
+ // Test not approved application
+ $server->handleAuthorizeRequest($request, $response, false);
+
+ $params = $response->getParameters();
+
+ $this->assertEquals($params['error'], 'consent_required');
+ $this->assertEquals($params['error_description'], 'The user denied access to your application');
+
+ // Test not approved application with prompt parameter
+ $request->query['prompt'] = 'none';
+ $server->handleAuthorizeRequest($request, $response, false);
+
+ $params = $response->getParameters();
+
+ $this->assertEquals($params['error'], 'login_required');
+ $this->assertEquals($params['error_description'], 'The user must log in');
+
+ // Test not approved application with user_id set
+ $request->query['prompt'] = 'none';
+ $server->handleAuthorizeRequest($request, $response, false, 'some-user-id');
+
+ $params = $response->getParameters();
+
+ $this->assertEquals($params['error'], 'interaction_required');
+ $this->assertEquals($params['error_description'], 'The user must grant access to your application');
+ }
+
+ public function testNeedsIdToken()
+ {
+ $server = $this->getTestServer();
+ $authorize = $server->getAuthorizeController();
+
+ $this->assertTrue($authorize->needsIdToken('openid'));
+ $this->assertTrue($authorize->needsIdToken('openid profile'));
+ $this->assertFalse($authorize->needsIdToken(''));
+ $this->assertFalse($authorize->needsIdToken('some-scope'));
+ }
+
+ private function getTestServer($config = array())
+ {
+ $config += array(
+ 'use_openid_connect' => true,
+ 'issuer' => 'anzev',
+ 'allow_implicit' => true
+ );
+
+ $storage = Bootstrap::getInstance()->getMemoryStorage();
+ $server = new Server($storage, $config);
+
+ return $server;
+ }
+}
diff --git a/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php b/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php
index 939b715..90fdd0b 100644
--- a/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php
+++ b/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php
@@ -2,14 +2,25 @@
namespace OAuth2\OpenID\Controller;
+use OAuth2\OpenID\Controller\UserInfoController;
use OAuth2\Storage\Bootstrap;
use OAuth2\Server;
-use OAuth2\GrantType\AuthorizationCode;
use OAuth2\Request;
use OAuth2\Response;
class UserInfoControllerTest extends \PHPUnit_Framework_TestCase
{
+ public function testCreateController()
+ {
+ $tokenType = new \OAuth2\TokenType\Bearer();
+ $storage = new \OAuth2\Storage\Memory();
+ $controller = new UserInfoController($tokenType, $storage, $storage);
+
+ $response = new Response();
+ $controller->handleUserInfoRequest(new Request(), $response);
+ $this->assertEquals(401, $response->getStatusCode());
+ }
+
public function testValidToken()
{
$server = $this->getTestServer();
@@ -28,6 +39,7 @@ class UserInfoControllerTest extends \PHPUnit_Framework_TestCase
{
$storage = Bootstrap::getInstance()->getMemoryStorage();
$server = new Server($storage, $config);
+
return $server;
}
}
diff --git a/test/OAuth2/OpenID/ResponseType/TokenIdTokenTest.php b/test/OAuth2/OpenID/ResponseType/TokenIdTokenTest.php
index 488eb08..f687cff 100644
--- a/test/OAuth2/OpenID/ResponseType/TokenIdTokenTest.php
+++ b/test/OAuth2/OpenID/ResponseType/TokenIdTokenTest.php
@@ -50,7 +50,7 @@ class TokenIdTokenTest extends \PHPUnit_Framework_TestCase
// Each part is a base64url encoded json string.
$part = str_replace(array('-', '_'), array('+', '/'), $part);
$part = base64_decode($part);
- $part = json_decode($part, TRUE);
+ $part = json_decode($part, true);
}
list($header, $claims, $signature) = $parts;
diff --git a/test/OAuth2/ResponseTest.php b/test/OAuth2/ResponseTest.php
index c36156f..b814900 100644
--- a/test/OAuth2/ResponseTest.php
+++ b/test/OAuth2/ResponseTest.php
@@ -12,6 +12,6 @@ class ResponseTest extends \PHPUnit_Framework_TestCase
));
$string = $response->getResponseBody('xml');
- $this->assertContains('<response><bar>foo</bar><oates>halland</oates></response>', $string);
+ $this->assertContains('<response><foo>bar</foo><halland>oates</halland></response>', $string);
}
}
diff --git a/test/OAuth2/ResponseType/CryptoTokenTest.php b/test/OAuth2/ResponseType/CryptoTokenTest.php
index 560890b..d1ab89e 100644
--- a/test/OAuth2/ResponseType/CryptoTokenTest.php
+++ b/test/OAuth2/ResponseType/CryptoTokenTest.php
@@ -8,6 +8,8 @@ use OAuth2\Request\TestRequest;
use OAuth2\Storage\Bootstrap;
use OAuth2\Storage\CryptoToken as CryptoTokenStorage;
use OAuth2\GrantType\ClientCredentials;
+use OAuth2\GrantType\UserCredentials;
+use OAuth2\GrantType\RefreshToken;
class CryptoTokenTest extends \PHPUnit_Framework_TestCase
{
@@ -69,6 +71,47 @@ class CryptoTokenTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($resourceServer->verifyResourceRequest($request));
}
+ public function testCryptoTokenWithRefreshToken()
+ {
+ $server = $this->getTestServer();
+
+ // add "UserCredentials" grant type and "CryptoToken" response type
+ // and ensure "CryptoToken" response type has "RefreshToken" storage
+ $memoryStorage = Bootstrap::getInstance()->getMemoryStorage();
+ $server->addGrantType(new UserCredentials($memoryStorage));
+ $server->addGrantType(new RefreshToken($memoryStorage));
+ $server->addResponseType(new CryptoToken($memoryStorage, $memoryStorage, $memoryStorage), 'token');
+
+ $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
+ ));
+
+ // make the call to grant a crypto token
+ $server->handleTokenRequest($request, $response = new Response());
+ $this->assertNotNull($cryptoToken = $response->getParameter('access_token'));
+ $this->assertNotNull($refreshToken = $response->getParameter('refresh_token'));
+
+ // decode token and make sure refresh_token isn't set
+ list($header, $payload, $signature) = explode('.', $cryptoToken);
+ $decodedToken = json_decode(base64_decode($payload), true);
+ $this->assertFalse(array_key_exists('refresh_token', $decodedToken));
+
+ // use the refresh token to get another access token
+ $request = TestRequest::createPost(array(
+ 'grant_type' => 'refresh_token',
+ 'client_id' => 'Test Client ID', // valid client id
+ 'client_secret' => 'TestSecret', // valid client secret
+ 'refresh_token' => $refreshToken,
+ ));
+
+ $server->handleTokenRequest($request, $response = new Response());
+ $this->assertNotNull($response->getParameter('access_token'));
+ }
+
private function getTestServer()
{
$memoryStorage = Bootstrap::getInstance()->getMemoryStorage();
diff --git a/test/OAuth2/ScopeTest.php b/test/OAuth2/ScopeTest.php
index 00ec7b6..99f9cf6 100644
--- a/test/OAuth2/ScopeTest.php
+++ b/test/OAuth2/ScopeTest.php
@@ -3,7 +3,6 @@
namespace OAuth2;
use OAuth2\Storage\Memory;
-use OAuth2\Storage\Bootstrap;
class ScopeTest extends \PHPUnit_Framework_TestCase
{
diff --git a/test/OAuth2/ServerTest.php b/test/OAuth2/ServerTest.php
index d3c8069..fd824fc 100644
--- a/test/OAuth2/ServerTest.php
+++ b/test/OAuth2/ServerTest.php
@@ -389,6 +389,34 @@ class ServerTest extends \PHPUnit_Framework_TestCase
}
/**
+ * Test setting "always_issue_new_refresh_token" on a server level
+ *
+ * @see test/OAuth2/GrantType/RefreshTokenTest::testValidRefreshTokenWithNewRefreshTokenInResponse
+ **/
+ public function testValidRefreshTokenWithNewRefreshTokenInResponse()
+ {
+ $storage = Bootstrap::getInstance()->getMemoryStorage();
+ $server = new Server($storage, array('always_issue_new_refresh_token' => true));
+
+ $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', // valid refresh token
+ ));
+ $token = $server->grantAccessToken($request, new Response());
+ $this->assertTrue(isset($token['refresh_token']), 'refresh token should always refresh');
+
+ $refresh_token = $storage->getRefreshToken($token['refresh_token']);
+ $this->assertNotNull($refresh_token);
+ $this->assertEquals($refresh_token['refresh_token'], $token['refresh_token']);
+ $this->assertEquals($refresh_token['client_id'], $request->request('client_id'));
+ $this->assertTrue($token['refresh_token'] != 'test-refreshtoken', 'the refresh token returned is not the one used');
+ $used_token = $storage->getRefreshToken('test-refreshtoken');
+ $this->assertFalse($used_token, 'the refresh token used is no longer valid');
+ }
+
+ /**
* @expectedException InvalidArgumentException OAuth2\ResponseType\AuthorizationCodeInterface
**/
public function testAddingUnknownResponseTypeThrowsException()
diff --git a/test/OAuth2/Storage/PdoTest.php b/test/OAuth2/Storage/PdoTest.php
index f6c0549..57eb390 100644
--- a/test/OAuth2/Storage/PdoTest.php
+++ b/test/OAuth2/Storage/PdoTest.php
@@ -2,8 +2,6 @@
namespace OAuth2\Storage;
-use OAuth2\Encryption\Jwt;
-
class PdoTest extends BaseTest
{
public function testCreatePdoStorageUsingPdoClass()
diff --git a/test/OAuth2/Storage/ScopeTest.php b/test/OAuth2/Storage/ScopeTest.php
index 2f7a588..fd1edeb 100644
--- a/test/OAuth2/Storage/ScopeTest.php
+++ b/test/OAuth2/Storage/ScopeTest.php
@@ -44,6 +44,10 @@ class ScopeTest extends BaseTest
// test getting default scope
$scopeUtil = new Scope($storage);
- $this->assertEquals($scopeUtil->getDefaultScope(), 'defaultscope1 defaultscope2');
+ $expected = explode(' ', $scopeUtil->getDefaultScope());
+ $actual = explode(' ', 'defaultscope1 defaultscope2');
+ sort($expected);
+ sort($actual);
+ $this->assertEquals($expected, $actual);
}
}
diff --git a/test/cleanup.php b/test/cleanup.php
new file mode 100644
index 0000000..4042eb2
--- /dev/null
+++ b/test/cleanup.php
@@ -0,0 +1,15 @@
+<?php
+
+require_once(dirname(__FILE__).'/../src/OAuth2/Autoloader.php');
+OAuth2\Autoloader::register();
+
+// register test classes
+OAuth2\Autoloader::register(dirname(__FILE__).'/lib');
+
+// register vendors if possible
+if (file_exists(__DIR__.'/../vendor/autoload.php')) {
+ require_once(__DIR__.'/../vendor/autoload.php');
+}
+
+// remove the dynamoDB database that was created for this build
+OAuth2\Storage\Bootstrap::getInstance()->cleanupTravisDynamoDb(); \ No newline at end of file
diff --git a/test/config/storage.json b/test/config/storage.json
index 82a7924..2d43c07 100644
--- a/test/config/storage.json
+++ b/test/config/storage.json
@@ -48,7 +48,7 @@
},
"Test Client ID with Redirect Uri Parts": {
"client_secret": "TestSecret4",
- "redirect_uri": "http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth"
+ "redirect_uri": "http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true"
},
"Test Some Other Client": {
"client_secret": "TestSecret3"
diff --git a/test/lib/OAuth2/Storage/BaseTest.php b/test/lib/OAuth2/Storage/BaseTest.php
index f7de3c7..579997c 100644
--- a/test/lib/OAuth2/Storage/BaseTest.php
+++ b/test/lib/OAuth2/Storage/BaseTest.php
@@ -6,13 +6,14 @@ abstract class BaseTest extends \PHPUnit_Framework_TestCase
{
public function provideStorage()
{
+ $memory = Bootstrap::getInstance()->getMemoryStorage();
+ $sqlite = Bootstrap::getInstance()->getSqlitePdo();
$mysql = Bootstrap::getInstance()->getMysqlPdo();
$postgres = Bootstrap::getInstance()->getPostgresPdo();
- $sqlite = Bootstrap::getInstance()->getSqlitePdo();
$mongo = Bootstrap::getInstance()->getMongo();
$redis = Bootstrap::getInstance()->getRedisStorage();
$cassandra = Bootstrap::getInstance()->getCassandraStorage();
- $memory = Bootstrap::getInstance()->getMemoryStorage();
+ $dynamodb = Bootstrap::getInstance()->getDynamoDbStorage();
/* hack until we can fix "default_scope" dependencies in other tests */
$memory->defaultScope = 'defaultscope1 defaultscope2';
@@ -25,6 +26,7 @@ abstract class BaseTest extends \PHPUnit_Framework_TestCase
array($mongo),
array($redis),
array($cassandra),
+ array($dynamodb),
);
}
}
diff --git a/test/lib/OAuth2/Storage/Bootstrap.php b/test/lib/OAuth2/Storage/Bootstrap.php
index 8d7fe0d..129ca2f 100644
--- a/test/lib/OAuth2/Storage/Bootstrap.php
+++ b/test/lib/OAuth2/Storage/Bootstrap.php
@@ -4,6 +4,8 @@ namespace OAuth2\Storage;
class Bootstrap
{
+ const DYNAMODB_PHP_VERSION = '5.5';
+
protected static $instance;
private $mysql;
private $sqlite;
@@ -12,6 +14,7 @@ class Bootstrap
private $redis;
private $cassandra;
private $configDir;
+ private $dynamodb;
public function __construct()
{
@@ -51,8 +54,6 @@ class Bootstrap
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->populatePostgresDb($pdo);
$this->postgres = new Pdo($pdo);
- } else {
- $this->postgres = new NullStorage('Postgres', 'Unable to connect to postgres on localhost with user "postgres"');
}
} else {
$this->postgres = new NullStorage('Postgres', 'Missing postgres PDO extension.');
@@ -69,7 +70,7 @@ class Bootstrap
return $pdo;
} catch (\PDOException $e) {
- exit($e->getMessage());
+ $this->postgres = new NullStorage('Postgres', $e->getMessage());
}
}
@@ -113,12 +114,20 @@ class Bootstrap
public function getMysqlPdo()
{
if (!$this->mysql) {
- $pdo = new \PDO('mysql:host=localhost;', 'root');
- $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
- $this->removeMysqlDb($pdo);
- $this->createMysqlDb($pdo);
+ $pdo = null;
+ try {
+ $pdo = new \PDO('mysql:host=localhost;', 'root');
+ } catch (\PDOException $e) {
+ $this->mysql = new NullStorage('MySQL', 'Unable to connect to MySQL on root@localhost');
+ }
- $this->mysql = new Pdo($pdo);
+ if ($pdo) {
+ $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ $this->removeMysqlDb($pdo);
+ $this->createMysqlDb($pdo);
+
+ $this->mysql = new Pdo($pdo);
+ }
}
return $this->mysql;
@@ -127,7 +136,7 @@ class Bootstrap
public function getMongo()
{
if (!$this->mongo) {
- $skipMongo = isset($_SERVER['SKIP_MONGO_TESTS']) && $_SERVER['SKIP_MONGO_TESTS'];
+ $skipMongo = $this->getEnvVar('SKIP_MONGO_TESTS');
if (!$skipMongo && class_exists('MongoClient')) {
$mongo = new \MongoClient('mongodb://localhost:27017', array('connect' => false));
if ($this->testMongoConnection($mongo)) {
@@ -277,14 +286,10 @@ class Bootstrap
public function runPdoSql(\PDO $pdo)
{
- $pdo->exec('CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT, grant_types TEXT, scope TEXT, user_id TEXT, public_key TEXT)');
- $pdo->exec('CREATE TABLE oauth_access_tokens (access_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT)');
- $pdo->exec('CREATE TABLE oauth_authorization_codes (authorization_code TEXT, client_id TEXT, user_id TEXT, redirect_uri TEXT, expires TIMESTAMP, scope TEXT, id_token TEXT)');
- $pdo->exec('CREATE TABLE oauth_users (username TEXT, password TEXT, first_name TEXT, last_name TEXT, scope TEXT, email TEXT, email_verified BOOLEAN)');
- $pdo->exec('CREATE TABLE oauth_refresh_tokens (refresh_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT)');
- $pdo->exec('CREATE TABLE oauth_scopes (scope TEXT, is_default BOOLEAN)');
- $pdo->exec('CREATE TABLE oauth_public_keys (client_id TEXT, public_key TEXT, private_key TEXT, encryption_algorithm VARCHAR(100) DEFAULT \'RS256\')');
- $pdo->exec('CREATE TABLE oauth_jwt (client_id VARCHAR(80), subject VARCHAR(80), public_key VARCHAR(2000))');
+ $storage = new Pdo($pdo);
+ foreach (explode(';', $storage->getBuildSql()) as $statement) {
+ $result = $pdo->exec($statement);
+ }
// set up scopes
$sql = 'INSERT INTO oauth_scopes (scope) VALUES (?)';
@@ -305,12 +310,12 @@ class Bootstrap
$pdo->prepare($sql)->execute(array('oauth_test_client', 'testpass', null, 'implicit password'));
// set up misc
- $sql = 'INSERT INTO oauth_access_tokens (access_token, client_id, user_id) VALUES (?, ?, ?)';
- $pdo->prepare($sql)->execute(array('testtoken', 'Some Client', null));
- $pdo->prepare($sql)->execute(array('accesstoken-openid-connect', 'Some Client', 'testuser'));
+ $sql = 'INSERT INTO oauth_access_tokens (access_token, client_id, expires, user_id) VALUES (?, ?, ?, ?)';
+ $pdo->prepare($sql)->execute(array('testtoken', 'Some Client', date('Y-m-d H:i:s', strtotime('+1 hour')), null));
+ $pdo->prepare($sql)->execute(array('accesstoken-openid-connect', 'Some Client', date('Y-m-d H:i:s', strtotime('+1 hour')), 'testuser'));
- $sql = 'INSERT INTO oauth_authorization_codes (authorization_code, client_id) VALUES (?, ?)';
- $pdo->prepare($sql)->execute(array('testcode', 'Some Client'));
+ $sql = 'INSERT INTO oauth_authorization_codes (authorization_code, client_id, expires) VALUES (?, ?, ?)';
+ $pdo->prepare($sql)->execute(array('testcode', 'Some Client', date('Y-m-d H:i:s', strtotime('+1 hour'))));
$sql = 'INSERT INTO oauth_users (username, password, email, email_verified) VALUES (?, ?, ?, ?)';
$pdo->prepare($sql)->execute(array('testuser', 'password', 'testuser@test.com', true));
@@ -326,7 +331,6 @@ class Bootstrap
$pdo->prepare($sql)->execute(array('oauth_test_client', 'test_subject', $this->getTestPublicKey()));
}
-
public function getSqliteDir()
{
return $this->configDir. '/test.sqlite';
@@ -407,4 +411,375 @@ class Bootstrap
{
return file_get_contents(__DIR__.'/../../../config/keys/id_rsa');
}
+
+ public function getDynamoDbStorage()
+ {
+ if (!$this->dynamodb) {
+ // only run once per travis build
+ if (true == $this->getEnvVar('TRAVIS')) {
+ if (self::DYNAMODB_PHP_VERSION != $this->getEnvVar('TRAVIS_PHP_VERSION')) {
+ $this->dynamodb = new NullStorage('DynamoDb', 'Skipping for travis.ci - only run once per build');
+
+ return;
+ }
+ }
+ if (class_exists('\Aws\DynamoDb\DynamoDbClient')) {
+ if ($client = $this->getDynamoDbClient()) {
+ // travis runs a unique set of tables per build, to avoid conflict
+ $prefix = '';
+ if ($build_id = $this->getEnvVar('TRAVIS_JOB_NUMBER')) {
+ $prefix = sprintf('build_%s_', $build_id);
+ } else {
+ if (!$this->deleteDynamoDb($client, $prefix, true)) {
+ return $this->dynamodb = new NullStorage('DynamoDb', 'Timed out while waiting for DynamoDB deletion (30 seconds)');
+ }
+ }
+ $this->createDynamoDb($client, $prefix);
+ $this->populateDynamoDb($client, $prefix);
+ $config = array(
+ 'client_table' => $prefix.'oauth_clients',
+ 'access_token_table' => $prefix.'oauth_access_tokens',
+ 'refresh_token_table' => $prefix.'oauth_refresh_tokens',
+ 'code_table' => $prefix.'oauth_authorization_codes',
+ 'user_table' => $prefix.'oauth_users',
+ 'jwt_table' => $prefix.'oauth_jwt',
+ 'scope_table' => $prefix.'oauth_scopes',
+ 'public_key_table' => $prefix.'oauth_public_keys',
+ );
+ $this->dynamodb = new DynamoDB($client, $config);
+ } elseif (!$this->dynamodb) {
+ $this->dynamodb = new NullStorage('DynamoDb', 'unable to connect to DynamoDB');
+ }
+ } else {
+ $this->dynamodb = new NullStorage('DynamoDb', 'Missing DynamoDB library. Please run "composer.phar require aws/aws-sdk-php:dev-master');
+ }
+ }
+
+ return $this->dynamodb;
+ }
+
+ private function getDynamoDbClient()
+ {
+ $config = array();
+ // check for environment variables
+ if (($key = $this->getEnvVar('AWS_ACCESS_KEY_ID')) && ($secret = $this->getEnvVar('AWS_SECRET_KEY'))) {
+ $config['key'] = $key;
+ $config['secret'] = $secret;
+ } else {
+ // fall back on ~/.aws/credentials file
+ // @see http://docs.aws.amazon.com/aws-sdk-php/guide/latest/credentials.html#credential-profiles
+ if (!file_exists($this->getEnvVar('HOME') . '/.aws/credentials')) {
+ $this->dynamodb = new NullStorage('DynamoDb', 'No aws credentials file found, and no AWS_ACCESS_KEY_ID or AWS_SECRET_KEY environment variable set');
+
+ return;
+ }
+
+ // set profile in AWS_PROFILE environment variable, defaults to "default"
+ $config['profile'] = $this->getEnvVar('AWS_PROFILE', 'default');
+ }
+
+ // set region in AWS_REGION environment variable, defaults to "us-east-1"
+ $config['region'] = $this->getEnvVar('AWS_REGION', \Aws\Common\Enum\Region::US_EAST_1);
+
+ return \Aws\DynamoDb\DynamoDbClient::factory($config);
+ }
+
+ private function deleteDynamoDb(\Aws\DynamoDb\DynamoDbClient $client, $prefix = null, $waitForDeletion = false)
+ {
+ $tablesList = explode(' ', 'oauth_access_tokens oauth_authorization_codes oauth_clients oauth_jwt oauth_public_keys oauth_refresh_tokens oauth_scopes oauth_users');
+ $nbTables = count($tablesList);
+
+ // Delete all table.
+ foreach ($tablesList as $key => $table) {
+ try {
+ $client->deleteTable(array('TableName' => $prefix.$table));
+ } catch (\Aws\DynamoDb\Exception\DynamoDbException $e) {
+ // Table does not exist : nothing to do
+ }
+ }
+
+ // Wait for deleting
+ if ($waitForDeletion) {
+ $retries = 5;
+ $nbTableDeleted = 0;
+ while ($nbTableDeleted != $nbTables) {
+ $nbTableDeleted = 0;
+ foreach ($tablesList as $key => $table) {
+ try {
+ $result = $client->describeTable(array('TableName' => $prefix.$table));
+ } catch (\Aws\DynamoDb\Exception\DynamoDbException $e) {
+ // Table does not exist : nothing to do
+ $nbTableDeleted++;
+ }
+ }
+ if ($nbTableDeleted != $nbTables) {
+ if ($retries < 0) {
+ // we are tired of waiting
+ return false;
+ }
+ sleep(5);
+ echo "Sleeping 5 seconds for DynamoDB ($retries more retries)...\n";
+ $retries--;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private function createDynamoDb(\Aws\DynamoDb\DynamoDbClient $client, $prefix = null)
+ {
+ $tablesList = explode(' ', 'oauth_access_tokens oauth_authorization_codes oauth_clients oauth_jwt oauth_public_keys oauth_refresh_tokens oauth_scopes oauth_users');
+ $nbTables = count($tablesList);
+ $client->createTable(array(
+ 'TableName' => $prefix.'oauth_access_tokens',
+ 'AttributeDefinitions' => array(
+ array('AttributeName' => 'access_token','AttributeType' => 'S')
+ ),
+ 'KeySchema' => array(array('AttributeName' => 'access_token','KeyType' => 'HASH')),
+ 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1)
+ ));
+
+ $client->createTable(array(
+ 'TableName' => $prefix.'oauth_authorization_codes',
+ 'AttributeDefinitions' => array(
+ array('AttributeName' => 'authorization_code','AttributeType' => 'S')
+ ),
+ 'KeySchema' => array(array('AttributeName' => 'authorization_code','KeyType' => 'HASH')),
+ 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1)
+ ));
+
+ $client->createTable(array(
+ 'TableName' => $prefix.'oauth_clients',
+ 'AttributeDefinitions' => array(
+ array('AttributeName' => 'client_id','AttributeType' => 'S')
+ ),
+ 'KeySchema' => array(array('AttributeName' => 'client_id','KeyType' => 'HASH')),
+ 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1)
+ ));
+
+ $client->createTable(array(
+ 'TableName' => $prefix.'oauth_jwt',
+ 'AttributeDefinitions' => array(
+ array('AttributeName' => 'client_id','AttributeType' => 'S'),
+ array('AttributeName' => 'subject','AttributeType' => 'S')
+ ),
+ 'KeySchema' => array(
+ array('AttributeName' => 'client_id','KeyType' => 'HASH'),
+ array('AttributeName' => 'subject','KeyType' => 'RANGE')
+ ),
+ 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1)
+ ));
+
+ $client->createTable(array(
+ 'TableName' => $prefix.'oauth_public_keys',
+ 'AttributeDefinitions' => array(
+ array('AttributeName' => 'client_id','AttributeType' => 'S')
+ ),
+ 'KeySchema' => array(array('AttributeName' => 'client_id','KeyType' => 'HASH')),
+ 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1)
+ ));
+
+ $client->createTable(array(
+ 'TableName' => $prefix.'oauth_refresh_tokens',
+ 'AttributeDefinitions' => array(
+ array('AttributeName' => 'refresh_token','AttributeType' => 'S')
+ ),
+ 'KeySchema' => array(array('AttributeName' => 'refresh_token','KeyType' => 'HASH')),
+ 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1)
+ ));
+
+ $client->createTable(array(
+ 'TableName' => $prefix.'oauth_scopes',
+ 'AttributeDefinitions' => array(
+ array('AttributeName' => 'scope','AttributeType' => 'S'),
+ array('AttributeName' => 'is_default','AttributeType' => 'S')
+ ),
+ 'KeySchema' => array(array('AttributeName' => 'scope','KeyType' => 'HASH')),
+ 'GlobalSecondaryIndexes' => array(
+ array(
+ 'IndexName' => 'is_default-index',
+ 'KeySchema' => array(array('AttributeName' => 'is_default', 'KeyType' => 'HASH')),
+ 'Projection' => array('ProjectionType' => 'ALL'),
+ 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1)
+ ),
+ ),
+ 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1)
+ ));
+
+ $client->createTable(array(
+ 'TableName' => $prefix.'oauth_users',
+ 'AttributeDefinitions' => array(array('AttributeName' => 'username','AttributeType' => 'S')),
+ 'KeySchema' => array(array('AttributeName' => 'username','KeyType' => 'HASH')),
+ 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1)
+ ));
+
+ // Wait for creation
+ $nbTableCreated = 0;
+ while ($nbTableCreated != $nbTables) {
+ $nbTableCreated = 0;
+ foreach ($tablesList as $key => $table) {
+ try {
+ $result = $client->describeTable(array('TableName' => $prefix.$table));
+ if ($result['Table']['TableStatus'] == 'ACTIVE') {
+ $nbTableCreated++;
+ }
+ } catch (\Aws\DynamoDb\Exception\DynamoDbException $e) {
+ // Table does not exist : nothing to do
+ $nbTableCreated++;
+ }
+ }
+ if ($nbTableCreated != $nbTables) {
+ sleep(1);
+ }
+ }
+ }
+
+ private function populateDynamoDb($client, $prefix = null)
+ {
+ // set up scopes
+ foreach (explode(' ', 'supportedscope1 supportedscope2 supportedscope3 supportedscope4 clientscope1 clientscope2 clientscope3') as $supportedScope) {
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_scopes',
+ 'Item' => array('scope' => array('S' => $supportedScope))
+ ));
+ }
+
+ foreach (array('defaultscope1', 'defaultscope2') as $defaultScope) {
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_scopes',
+ 'Item' => array('scope' => array('S' => $defaultScope), 'is_default' => array('S' => "true"))
+ ));
+ }
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_clients',
+ 'Item' => array(
+ 'client_id' => array('S' => 'Test Client ID'),
+ 'client_secret' => array('S' => 'TestSecret'),
+ 'scope' => array('S' => 'clientscope1 clientscope2')
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_clients',
+ 'Item' => array(
+ 'client_id' => array('S' => 'Test Client ID 2'),
+ 'client_secret' => array('S' => 'TestSecret'),
+ 'scope' => array('S' => 'clientscope1 clientscope2 clientscope3')
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_clients',
+ 'Item' => array(
+ 'client_id' => array('S' => 'Test Default Scope Client ID'),
+ 'client_secret' => array('S' => 'TestSecret'),
+ 'scope' => array('S' => 'clientscope1 clientscope2')
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_clients',
+ 'Item' => array(
+ 'client_id' => array('S' => 'oauth_test_client'),
+ 'client_secret' => array('S' => 'testpass'),
+ 'grant_types' => array('S' => 'implicit password')
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_access_tokens',
+ 'Item' => array(
+ 'access_token' => array('S' => 'testtoken'),
+ 'client_id' => array('S' => 'Some Client'),
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_access_tokens',
+ 'Item' => array(
+ 'access_token' => array('S' => 'accesstoken-openid-connect'),
+ 'client_id' => array('S' => 'Some Client'),
+ 'user_id' => array('S' => 'testuser'),
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_authorization_codes',
+ 'Item' => array(
+ 'authorization_code' => array('S' => 'testcode'),
+ 'client_id' => array('S' => 'Some Client'),
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_users',
+ 'Item' => array(
+ 'username' => array('S' => 'testuser'),
+ 'password' => array('S' => 'password'),
+ 'email' => array('S' => 'testuser@test.com'),
+ 'email_verified' => array('S' => 'true'),
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_public_keys',
+ 'Item' => array(
+ 'client_id' => array('S' => 'ClientID_One'),
+ 'public_key' => array('S' => 'client_1_public'),
+ 'private_key' => array('S' => 'client_1_private'),
+ 'encryption_algorithm' => array('S' => 'RS256'),
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_public_keys',
+ 'Item' => array(
+ 'client_id' => array('S' => 'ClientID_Two'),
+ 'public_key' => array('S' => 'client_2_public'),
+ 'private_key' => array('S' => 'client_2_private'),
+ 'encryption_algorithm' => array('S' => 'RS256'),
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_public_keys',
+ 'Item' => array(
+ 'client_id' => array('S' => '0'),
+ 'public_key' => array('S' => $this->getTestPublicKey()),
+ 'private_key' => array('S' => $this->getTestPrivateKey()),
+ 'encryption_algorithm' => array('S' => 'RS256'),
+ )
+ ));
+
+ $client->putItem(array(
+ 'TableName' => $prefix.'oauth_jwt',
+ 'Item' => array(
+ 'client_id' => array('S' => 'oauth_test_client'),
+ 'subject' => array('S' => 'test_subject'),
+ 'public_key' => array('S' => $this->getTestPublicKey()),
+ )
+ ));
+ }
+
+ public function cleanupTravisDynamoDb($prefix = null)
+ {
+ if (is_null($prefix)) {
+ // skip this when not applicable
+ if (!$this->getEnvVar('TRAVIS') || self::DYNAMODB_PHP_VERSION != $this->getEnvVar('TRAVIS_PHP_VERSION')) {
+ return;
+ }
+
+ $prefix = sprintf('build_%s_', $this->getEnvVar('TRAVIS_JOB_NUMBER'));
+ }
+
+ $client = $this->getDynamoDbClient();
+ $this->deleteDynamoDb($client, $prefix);
+ }
+
+ private function getEnvVar($var, $default = null)
+ {
+ return isset($_SERVER[$var]) ? $_SERVER[$var] : (getenv($var) ?: $default);
+ }
}
diff --git a/test/lib/OAuth2/Storage/NullStorage.php b/test/lib/OAuth2/Storage/NullStorage.php
index 46a200c..6caa620 100644
--- a/test/lib/OAuth2/Storage/NullStorage.php
+++ b/test/lib/OAuth2/Storage/NullStorage.php
@@ -29,4 +29,4 @@ class NullStorage extends Memory
return $this->name;
}
-} \ No newline at end of file
+}