diff options
| author | Brent Shaffer <bshafs@gmail.com> | 2014-08-28 06:13:42 +0400 |
|---|---|---|
| committer | Brent Shaffer <bshafs@gmail.com> | 2014-08-28 06:13:42 +0400 |
| commit | 74fcc75e47614c1417c750e907a44567d9ceee1f (patch) | |
| tree | 88b56d61f275dd284d9174705558aa90bf31b588 | |
| parent | a7f02c9b7b4a18e54a71f93436aa4a6091f399a7 (diff) | |
| parent | 6a30b9b67ae35c228e852109ed743bf34da22568 (diff) | |
| download | oauth2-server-php-1.5.tar.xz | |
Merge pull request #446 from bshaffer/developv1.5
Tag v1.5
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 +} |
