diff options
51 files changed, 1598 insertions, 388 deletions
diff --git a/src/OAuth2/Autoloader.php b/src/OAuth2/Autoloader.php index eff8461..24ac68b 100644 --- a/src/OAuth2/Autoloader.php +++ b/src/OAuth2/Autoloader.php @@ -31,7 +31,7 @@ class Autoloader /** * Handles autoloading of classes. * - * @param string $class A class name. + * @param string $class A class name. * * @return boolean Returns true if the class has been loaded */ diff --git a/src/OAuth2/ClientAssertionType/HttpBasic.php b/src/OAuth2/ClientAssertionType/HttpBasic.php index a42dea0..2e8c89d 100644 --- a/src/OAuth2/ClientAssertionType/HttpBasic.php +++ b/src/OAuth2/ClientAssertionType/HttpBasic.php @@ -38,11 +38,13 @@ class HttpBasic implements ClientAssertionTypeInterface if ($this->storage->checkClientCredentials($clientData['client_id'], $clientData['client_secret']) === false) { $response->setError(400, 'invalid_client', 'The client credentials are invalid'); + return false; } if (!$this->storage->checkRestrictedGrantType($clientData['client_id'], $request->request('grant_type'))) { $response->setError(400, 'unauthorized_client', 'The grant type is unauthorized for this client_id'); + return false; } @@ -89,6 +91,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', '')); } } @@ -99,4 +102,4 @@ class HttpBasic implements ClientAssertionTypeInterface return null; } -}
\ No newline at end of file +} diff --git a/src/OAuth2/Controller/AuthorizeController.php b/src/OAuth2/Controller/AuthorizeController.php index 7a66443..93ecf30 100644 --- a/src/OAuth2/Controller/AuthorizeController.php +++ b/src/OAuth2/Controller/AuthorizeController.php @@ -81,6 +81,7 @@ class AuthorizeController implements AuthorizeControllerInterface 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"); + return; } @@ -113,12 +114,14 @@ class AuthorizeController implements AuthorizeControllerInterface if (!$client_id = $request->query("client_id")) { // We don't have a good URI to use $response->setError(400, 'invalid_client', "No client id supplied"); + return false; } // Get client details if (!$clientData = $this->clientStorage->getClientDetails($client_id)) { $response->setError(400, 'invalid_client', 'The client id supplied is invalid'); + return false; } @@ -133,12 +136,14 @@ class AuthorizeController implements AuthorizeControllerInterface $parts = parse_url($supplied_redirect_uri); if (isset($parts['fragment']) && $parts['fragment']) { $response->setError(400, 'invalid_uri', 'The redirect URI must not contain a fragment'); + return false; } // validate against the registered redirect uri(s) if available if ($registered_redirect_uri && !$this->validateRedirectUri($supplied_redirect_uri, $registered_redirect_uri)) { $response->setError(400, 'redirect_uri_mismatch', 'The redirect URI provided is missing or does not match', '#section-3.1.2'); + return false; } $redirect_uri = $supplied_redirect_uri; @@ -146,11 +151,13 @@ class AuthorizeController implements AuthorizeControllerInterface // use the registered redirect_uri if none has been supplied, if possible if (!$registered_redirect_uri) { $response->setError(400, 'invalid_uri', 'No redirect URI was supplied or stored'); + return false; } if (count(explode(' ', $registered_redirect_uri)) > 1) { $response->setError(400, 'invalid_uri', 'A redirect URI must be supplied when multiple redirect URIs are registered', '#section-3.1.2.3'); + return false; } $redirect_uri = $registered_redirect_uri; @@ -166,19 +173,23 @@ class AuthorizeController implements AuthorizeControllerInterface // type and client_id are required if (!$response_type || !in_array($response_type, array(self::RESPONSE_TYPE_AUTHORIZATION_CODE, self::RESPONSE_TYPE_ACCESS_TOKEN))) { $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_request', 'Invalid or missing response type', null); + return false; } if ($response_type == self::RESPONSE_TYPE_AUTHORIZATION_CODE) { if (!isset($this->responseTypes['code'])) { $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'authorization code grant type not supported', null); + return false; } if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'authorization_code')) { $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null); + return false; } if ($this->responseTypes['code']->enforceRedirect() && !$redirect_uri) { $response->setError(400, 'redirect_uri_mismatch', 'The redirect URI is mandatory and was not supplied'); + return false; } } @@ -186,10 +197,12 @@ class AuthorizeController implements AuthorizeControllerInterface if ($response_type == self::RESPONSE_TYPE_ACCESS_TOKEN) { if (!$this->config['allow_implicit']) { $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'implicit grant type not supported', null); + return false; } if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'implicit')) { $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null); + return false; } } @@ -197,17 +210,20 @@ class AuthorizeController implements AuthorizeControllerInterface // Validate that the requested scope is supported if (false === $scope) { $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_client', 'This application requires you specify a scope parameter', null); + return false; } if (!is_null($scope) && !$this->scopeUtil->scopeExists($scope, $client_id)) { $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_scope', 'An unsupported scope was requested', null); + return false; } // Validate state parameter exists (if configured to enforce this) if ($this->config['enforce_state'] && !$state) { $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, null, 'invalid_request', 'The state parameter is required'); + return false; } @@ -240,7 +256,7 @@ class AuthorizeController implements AuthorizeControllerInterface $parse_url = parse_url($uri); // Add our params to the parsed uri - foreach ( $params as $k => $v ) { + foreach ($params as $k => $v) { if (isset($parse_url[$k])) { $parse_url[$k] .= "&" . http_build_query($v); } else { @@ -293,6 +309,7 @@ class AuthorizeController implements AuthorizeControllerInterface } } } + return false; } diff --git a/src/OAuth2/Controller/ResourceController.php b/src/OAuth2/Controller/ResourceController.php index e407b54..862e364 100644 --- a/src/OAuth2/Controller/ResourceController.php +++ b/src/OAuth2/Controller/ResourceController.php @@ -38,7 +38,7 @@ class ResourceController implements ResourceControllerInterface public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null) { - $token = $this->getAccessTokenData($request, $response, $scope); + $token = $this->getAccessTokenData($request, $response); // Check if we have token data if (is_null($token)) { @@ -61,6 +61,7 @@ class ResourceController implements ResourceControllerInterface $response->getParameter('error_description') ) )); + return false; } @@ -79,9 +80,9 @@ class ResourceController implements ResourceControllerInterface // Check token expiration (expires is a mandatory paramter) if (!$token = $this->tokenStorage->getAccessToken($token_param)) { $response->setError(401, 'invalid_token', 'The access token provided is invalid'); - } else if (!isset($token["expires"]) || !isset($token["client_id"])) { + } elseif (!isset($token["expires"]) || !isset($token["client_id"])) { $response->setError(401, 'invalid_token', 'Malformed token (missing "expires" or "client_id")'); - } else if (time() > $token["expires"]) { + } elseif (time() > $token["expires"]) { $response->setError(401, 'invalid_token', 'The access token provided has expired'); } else { return $token; @@ -98,6 +99,7 @@ class ResourceController implements ResourceControllerInterface } $response->addHttpHeaders(array('WWW-Authenticate' => $authHeader)); + return null; } diff --git a/src/OAuth2/Controller/TokenController.php b/src/OAuth2/Controller/TokenController.php index 3419f6f..a615354 100644 --- a/src/OAuth2/Controller/TokenController.php +++ b/src/OAuth2/Controller/TokenController.php @@ -74,6 +74,7 @@ class TokenController implements TokenControllerInterface if (strtolower($request->server('REQUEST_METHOD')) != 'post') { $response->setError(405, 'invalid_request', 'The request method must be POST when requesting an access token', '#section-3.2'); $response->addHttpHeaders(array('Allow' => 'POST')); + return null; } @@ -82,11 +83,13 @@ class TokenController implements TokenControllerInterface */ if (!$grantTypeIdentifier = $request->request('grant_type')) { $response->setError(400, 'invalid_request', 'The grant type was not specified in the request'); + return null; } if (!isset($this->grantTypes[$grantTypeIdentifier])) { /* TODO: If this is an OAuth2 supported grant type that we have chosen not to implement, throw a 501 Not Implemented instead */ $response->setError(400, 'unsupported_grant_type', sprintf('Grant type "%s" not supported', $grantTypeIdentifier)); + return null; } @@ -121,6 +124,7 @@ class TokenController implements TokenControllerInterface // validate the Client ID (if applicable) if (!is_null($storedClientId = $grantType->getClientId()) && $storedClientId != $clientId) { $response->setError(400, 'invalid_grant', sprintf('%s doesn\'t exist or is invalid for the client', $grantTypeIdentifier)); + return null; } } @@ -136,6 +140,7 @@ class TokenController implements TokenControllerInterface if (!$availableScope) { if (false === $defaultScope = $this->scopeUtil->getDefaultScope($clientId)) { $response->setError(400, 'invalid_scope', 'This application requires you specify a scope parameter'); + return null; } } @@ -145,6 +150,7 @@ class TokenController implements TokenControllerInterface if (($requestedScope && !$this->scopeUtil->scopeExists($requestedScope, $clientId)) || ($availableScope && !$this->scopeUtil->checkScope($requestedScope, $availableScope))) { $response->setError(400, 'invalid_scope', 'An unsupported scope was requested'); + return null; } diff --git a/src/OAuth2/Encryption/EncryptionInterface.php b/src/OAuth2/Encryption/EncryptionInterface.php new file mode 100644 index 0000000..dc8e0d2 --- /dev/null +++ b/src/OAuth2/Encryption/EncryptionInterface.php @@ -0,0 +1,9 @@ +<?php + +namespace OAuth2\Encryption; + +interface EncryptionInterface +{ + public function encode($payload, $key, $algorithm = null); + public function decode($payload, $key, $algorithm = null); +} diff --git a/src/OAuth2/Encryption/Jwt.php b/src/OAuth2/Encryption/Jwt.php index 67a137e..641a2e9 100644 --- a/src/OAuth2/Encryption/Jwt.php +++ b/src/OAuth2/Encryption/Jwt.php @@ -6,7 +6,7 @@ namespace OAuth2\Encryption; * @link https://github.com/F21/jwt * @author F21 */ -class Jwt +class Jwt implements EncryptionInterface { public function encode($payload, $key, $algo = 'HS256') { @@ -60,7 +60,7 @@ class Jwt private function verifySignature($signature, $input, $key, $algo = 'HS256') { - switch($algo){ + switch ($algo) { case'HS256': case'HS384': case'HS512': @@ -82,7 +82,7 @@ class Jwt private function sign($input, $key, $algo = 'HS256') { - switch($algo){ + switch ($algo) { case 'HS256': return hash_hmac('sha256', $input, $key, true); @@ -121,6 +121,7 @@ class Jwt $b64 = str_replace(array('+', '/', '\r', '\n', '='), array('-', '_'), $b64); + return $b64; } @@ -129,6 +130,7 @@ class Jwt $b64 = str_replace(array('-', '_'), array('+', '/'), $b64); + return base64_decode($b64); } } diff --git a/src/OAuth2/GrantType/AuthorizationCode.php b/src/OAuth2/GrantType/AuthorizationCode.php index b0c3d5f..60d80cb 100644 --- a/src/OAuth2/GrantType/AuthorizationCode.php +++ b/src/OAuth2/GrantType/AuthorizationCode.php @@ -30,12 +30,14 @@ class AuthorizationCode implements GrantTypeInterface { if (!$request->request('code')) { $response->setError(400, 'invalid_request', 'Missing parameter: "code" is required'); + return false; } $code = $request->request('code'); if (!$authCode = $this->storage->getAuthorizationCode($code)) { $response->setError(400, 'invalid_grant', 'Authorization code doesn\'t exist or is invalid for the client'); + return false; } @@ -46,6 +48,7 @@ class AuthorizationCode implements GrantTypeInterface if (isset($authCode['redirect_uri']) && $authCode['redirect_uri']) { if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != $authCode['redirect_uri']) { $response->setError(400, 'redirect_uri_mismatch', "The redirect URI is missing or do not match", "#section-4.1.3"); + return false; } } @@ -56,6 +59,7 @@ class AuthorizationCode implements GrantTypeInterface if ($authCode["expires"] < time()) { $response->setError(400, 'invalid_grant', "The authorization code has expired"); + return false; } diff --git a/src/OAuth2/GrantType/ClientCredentials.php b/src/OAuth2/GrantType/ClientCredentials.php index ea0122d..0a73e53 100644 --- a/src/OAuth2/GrantType/ClientCredentials.php +++ b/src/OAuth2/GrantType/ClientCredentials.php @@ -12,6 +12,8 @@ use OAuth2\ResponseType\AccessTokenInterface; */ class ClientCredentials extends HttpBasic implements GrantTypeInterface { + private $clientData; + public function getQuerystringIdentifier() { return 'client_credentials'; @@ -19,12 +21,16 @@ class ClientCredentials extends HttpBasic implements GrantTypeInterface public function getScope() { - return null; + $this->loadClientData(); + + return isset($this->clientData['scope']) ? $this->clientData['scope'] : null; } public function getUserId() { - return null; + $this->loadClientData(); + + return isset($this->clientData['user_id']) ? $this->clientData['user_id'] : null; } public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) @@ -34,6 +40,14 @@ class ClientCredentials extends HttpBasic implements GrantTypeInterface * @see http://tools.ietf.org/html/rfc6749#section-4.4.3 */ $includeRefreshToken = false; + return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken); } + + private function loadClientData() + { + if (!$this->clientData) { + $this->clientData = $this->storage->getClientDetails($this->getClientId()); + } + } } diff --git a/src/OAuth2/GrantType/JwtBearer.php b/src/OAuth2/GrantType/JwtBearer.php index 62823df..38fdc8b 100644 --- a/src/OAuth2/GrantType/JwtBearer.php +++ b/src/OAuth2/GrantType/JwtBearer.php @@ -72,6 +72,7 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface { if (!$request->request("assertion")) { $response->setError(400, 'invalid_request', 'Missing parameters: "assertion" required'); + return null; } @@ -83,6 +84,7 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface if (!$jwt) { $response->setError(400, 'invalid_request', "JWT is malformed"); + return null; } @@ -102,16 +104,19 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface if (!isset($jwt['iss'])) { $response->setError(400, 'invalid_grant', "Invalid issuer (iss) provided"); + return null; } if (!isset($jwt['sub'])) { $response->setError(400, 'invalid_grant', "Invalid subject (sub) provided"); + return null; } if (!isset($jwt['exp'])) { $response->setError(400, 'invalid_grant', "Expiration (exp) time must be present"); + return null; } @@ -119,10 +124,12 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface if (ctype_digit($jwt['exp'])) { if ($jwt['exp'] <= time()) { $response->setError(400, 'invalid_grant', "JWT has expired"); + return null; } } else { $response->setError(400, 'invalid_grant', "Expiration (exp) time must be a unix time stamp"); + return null; } @@ -131,10 +138,12 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface if (ctype_digit($notBefore)) { if ($notBefore > time()) { $response->setError(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time"); + return null; } } else { $response->setError(400, 'invalid_grant', "Not Before (nbf) time must be a unix time stamp"); + return null; } } @@ -142,19 +151,37 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface // Check the audience if required to match if (!isset($jwt['aud']) || ($jwt['aud'] != $this->audience)) { $response->setError(400, 'invalid_grant', "Invalid audience (aud)"); + return null; } + // Check the jti (nonce) + // @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-13#section-4.1.7 + if (isset($jwt['jti'])) { + $jti = $this->storage->getJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']); + + //Reject if jti is used and jwt is still valid (exp parameter has not expired). + if ($jti && $jti['expires'] > time()) { + $response->setError(400, 'invalid_grant', "JSON Token Identifier (jti) has already been used"); + + return null; + } else { + $this->storage->setJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']); + } + } + // Get the iss's public key // @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-4.1.1 if (!$key = $this->storage->getClientKey($jwt['iss'], $jwt['sub'])) { $response->setError(400, 'invalid_grant', "Invalid issuer (iss) or subject (sub) provided"); + return null; } // Verify the JWT if (!$this->jwtUtil->decode($undecodedJWT, $key, true)) { $response->setError(400, 'invalid_grant', "JWT failed signature verification"); + return null; } @@ -187,6 +214,7 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) { $includeRefreshToken = false; + return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken); } } diff --git a/src/OAuth2/GrantType/RefreshToken.php b/src/OAuth2/GrantType/RefreshToken.php index e7de5ea..c866a80 100644 --- a/src/OAuth2/GrantType/RefreshToken.php +++ b/src/OAuth2/GrantType/RefreshToken.php @@ -35,16 +35,19 @@ class RefreshToken implements GrantTypeInterface { if (!$request->request("refresh_token")) { $response->setError(400, 'invalid_request', 'Missing parameter: "refresh_token" is required'); + return null; } if (!$refreshToken = $this->storage->getRefreshToken($request->request("refresh_token"))) { $response->setError(400, 'invalid_grant', 'Invalid refresh token'); + return null; } if ($refreshToken["expires"] < time()) { $response->setError(400, 'invalid_grant', 'Refresh token has expired'); + return null; } diff --git a/src/OAuth2/GrantType/UserCredentials.php b/src/OAuth2/GrantType/UserCredentials.php index e2557c5..526fdbd 100644 --- a/src/OAuth2/GrantType/UserCredentials.php +++ b/src/OAuth2/GrantType/UserCredentials.php @@ -31,11 +31,13 @@ class UserCredentials implements GrantTypeInterface { if (!$request->request("password") || !$request->request("username")) { $response->setError(400, 'invalid_request', 'Missing parameters: "username" and "password" required'); + return null; } if (!$this->storage->checkUserCredentials($request->request("username"), $request->request("password"))) { $response->setError(400, 'invalid_grant', 'Invalid username and password combination'); + return null; } @@ -43,6 +45,7 @@ class UserCredentials implements GrantTypeInterface if (empty($userInfo)) { $response->setError(400, 'invalid_grant', 'Unable to retrieve user information'); + return null; } diff --git a/src/OAuth2/Response.php b/src/OAuth2/Response.php index 858446e..271bc4e 100644 --- a/src/OAuth2/Response.php +++ b/src/OAuth2/Response.php @@ -181,6 +181,7 @@ class Response implements ResponseInterface // this only works for single-level arrays $xml = new \SimpleXMLElement('<response/>'); array_walk($this->parameters, array($xml, 'addChild')); + return $xml->asXML(); } diff --git a/src/OAuth2/ResponseInterface.php b/src/OAuth2/ResponseInterface.php index 2548dc7..61da477 100644 --- a/src/OAuth2/ResponseInterface.php +++ b/src/OAuth2/ResponseInterface.php @@ -21,4 +21,4 @@ interface ResponseInterface public function setRedirect($statusCode = 302, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null); public function getParameter($name); -}
\ No newline at end of file +} diff --git a/src/OAuth2/ResponseType/AccessToken.php b/src/OAuth2/ResponseType/AccessToken.php index 1c4fa2d..c82d9ed 100644 --- a/src/OAuth2/ResponseType/AccessToken.php +++ b/src/OAuth2/ResponseType/AccessToken.php @@ -13,6 +13,7 @@ class AccessToken implements AccessTokenInterface { protected $tokenStorage; protected $refreshStorage; + protected $config; public function __construct(AccessTokenStorageInterface $tokenStorage, RefreshTokenInterface $refreshStorage = null, array $config = array()) { @@ -102,11 +103,12 @@ class AccessToken implements AccessTokenInterface protected function generateAccessToken() { $tokenLen = 40; - if (file_exists('/dev/urandom')) { // Get 100 bytes of random data + if (@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); } + return substr(hash('sha512', $randomData), 0, $tokenLen); } diff --git a/src/OAuth2/ResponseType/AuthorizationCode.php b/src/OAuth2/ResponseType/AuthorizationCode.php index b439637..c39d6a0 100644 --- a/src/OAuth2/ResponseType/AuthorizationCode.php +++ b/src/OAuth2/ResponseType/AuthorizationCode.php @@ -22,7 +22,6 @@ class AuthorizationCode implements AuthorizationCodeInterface ), $config); } - public function getAuthorizeResponse($params, $user_id = null) { // build the URL to redirect to @@ -59,6 +58,7 @@ class AuthorizationCode implements AuthorizationCodeInterface { $code = $this->generateAuthorizationCode(); $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope); + return $code; } @@ -90,6 +90,7 @@ class AuthorizationCode implements AuthorizationCodeInterface } else { $randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true); } + return substr(hash('sha512', $randomData), 0, $tokenLen); } } diff --git a/src/OAuth2/ResponseType/CryptoToken.php b/src/OAuth2/ResponseType/CryptoToken.php new file mode 100644 index 0000000..dd6896c --- /dev/null +++ b/src/OAuth2/ResponseType/CryptoToken.php @@ -0,0 +1,114 @@ +<?php + +namespace OAuth2\ResponseType; + +use OAuth2\Encryption\EncryptionInterface; +use OAuth2\Encryption\Jwt; +use OAuth2\Storage\AccessTokenInterface as AccessTokenStorageInterface; +use OAuth2\Storage\RefreshTokenInterface; +use OAuth2\Storage\PublicKeyInterface; +use OAuth2\Storage\Memory; + +/** + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ +class CryptoToken extends AccessToken +{ + protected $publicKeyStorage; + protected $encryptionUtil; + + /** + * @param $config + * - store_encrypted_token_string (bool true) + * whether the entire encrypted string is stored, + * or just the token ID is stored + */ + public function __construct(PublicKeyInterface $publicKeyStorage = null, AccessTokenStorageInterface $tokenStorage = null, RefreshTokenInterface $refreshStorage = null, array $config = array(), EncryptionInterface $encryptionUtil = null) + { + $this->publicKeyStorage = $publicKeyStorage; + $config = array_merge(array( + 'store_encrypted_token_string' => true, + ), $config); + if (is_null($tokenStorage)) { + // a pass-thru, so we can call the parent constructor + $tokenStorage = new Memory(); + } + if (is_null($encryptionUtil)) { + $encryptionUtil = new Jwt(); + } + $this->encryptionUtil = $encryptionUtil; + parent::__construct($tokenStorage, $refreshStorage, $config); + } + + /** + * Handle the creation of access token, also issue refresh token if supported / desirable. + * + * @param $client_id + * Client identifier related to the access token. + * @param $user_id + * User ID associated with the access token + * @param $scope + * (optional) Scopes to be stored in space-separated string. + * @param bool $includeRefreshToken + * If true, a new refresh_token will be added to the response + * + * @see http://tools.ietf.org/html/rfc6749#section-5 + * @ingroup oauth2_section_5 + */ + public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true) + { + // token to encrypt + $expires = time() + $this->config['access_lifetime']; + $cryptoToken = array( + 'id' => $this->generateAccessToken(), + 'client_id' => $client_id, + 'user_id' => $user_id, + 'expires' => $expires, + 'token_type' => $this->config['token_type'], + '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) { + $cryptoToken["refresh_token"] = $this->generateRefreshToken(); + $this->refreshStorage->setRefreshToken($cryptoToken['refresh_token'], $client_id, $user_id, time() + $this->config['refresh_token_lifetime'], $scope); + } + + /* + * Encode the token data into a single access_token string + */ + $access_token = $this->encodeToken($cryptoToken, $client_id); + + /* + * Save the token to a secondary storage. This is implemented on the + * OAuth2\Storage\CryptoToken side, and will not actually store anything, + * if no secondary storage has been supplied + */ + $token_to_store = $this->config['store_encrypted_token_string'] ? $access_token : $cryptoToken['id']; + $this->tokenStorage->setAccessToken($token_to_store, $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope); + + // token to return to the client + $token = array( + 'access_token' => $access_token, + 'expires_in' => $this->config['access_lifetime'], + 'token_type' => $this->config['token_type'], + 'scope' => $scope + ); + + return $token; + } + + protected function encodeToken(array $token, $client_id = null) + { + $private_key = $this->publicKeyStorage->getPrivateKey($client_id); + $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id); + + return $this->encryptionUtil->encode($token, $private_key, $algorithm); + } +} diff --git a/src/OAuth2/Scope.php b/src/OAuth2/Scope.php index a8119ba..6639346 100644 --- a/src/OAuth2/Scope.php +++ b/src/OAuth2/Scope.php @@ -47,6 +47,7 @@ class Scope implements ScopeInterface { $required_scope = explode(' ', trim($required_scope)); $available_scope = explode(' ', trim($available_scope)); + return (count(array_diff($required_scope, $available_scope)) == 0); } diff --git a/src/OAuth2/Server.php b/src/OAuth2/Server.php index 87e34e7..2d12c5b 100644 --- a/src/OAuth2/Server.php +++ b/src/OAuth2/Server.php @@ -103,6 +103,7 @@ class Server implements ResourceControllerInterface, 'require_exact_redirect_uri' => true, 'allow_implicit' => false, 'allow_credentials_in_request_body' => true, + 'always_issue_new_refresh_token' => false, ), $config); foreach ($grantTypes as $key => $grantType) { @@ -121,6 +122,7 @@ class Server implements ResourceControllerInterface, if (is_null($this->authorizeController)) { $this->authorizeController = $this->createDefaultAuthorizeController(); } + return $this->authorizeController; } @@ -129,6 +131,7 @@ class Server implements ResourceControllerInterface, if (is_null($this->tokenController)) { $this->tokenController = $this->createDefaultTokenController(); } + return $this->tokenController; } @@ -137,6 +140,7 @@ class Server implements ResourceControllerInterface, if (is_null($this->resourceController)) { $this->resourceController = $this->createDefaultResourceController(); } + return $this->resourceController; } @@ -188,6 +192,7 @@ class Server implements ResourceControllerInterface, { $this->response = is_null($response) ? new Response() : $response; $this->getTokenController()->handleTokenRequest($request, $this->response); + return $this->response; } @@ -195,6 +200,7 @@ class Server implements ResourceControllerInterface, { $this->response = is_null($response) ? new Response() : $response; $value = $this->getTokenController()->grantAccessToken($request, $this->response); + return $value; } @@ -230,6 +236,7 @@ class Server implements ResourceControllerInterface, { $this->response = $response; $this->getAuthorizeController()->handleAuthorizeRequest($request, $this->response, $is_authorized, $user_id); + return $this->response; } @@ -256,6 +263,7 @@ class Server implements ResourceControllerInterface, { $this->response = is_null($response) ? new Response() : $response; $value = $this->getAuthorizeController()->validateAuthorizeRequest($request, $this->response); + return $value; } @@ -263,6 +271,7 @@ class Server implements ResourceControllerInterface, { $this->response = is_null($response) ? new Response() : $response; $value = $this->getResourceController()->verifyResourceRequest($request, $this->response, $scope); + return $value; } @@ -270,6 +279,7 @@ class Server implements ResourceControllerInterface, { $this->response = is_null($response) ? new Response() : $response; $value = $this->getResourceController()->getAccessTokenData($request, $this->response); + return $value; } @@ -352,6 +362,7 @@ class Server implements ResourceControllerInterface, $storage = isset($this->storages['scope']) ? $this->storages['scope'] : null; $this->scopeUtil = new Scope($storage); } + return $this->scopeUtil; } @@ -372,6 +383,7 @@ class Server implements ResourceControllerInterface, $this->responseTypes = $this->getDefaultResponseTypes(); } $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state require_exact_redirect_uri'))); + return new AuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil()); } @@ -407,12 +419,14 @@ class Server implements ResourceControllerInterface, $this->tokenType = $this->getDefaultTokenType(); } $config = array_intersect_key($this->config, array('www_realm' => '')); + return new ResourceController($this->tokenType, $this->storages['access_token'], $config, $this->getScopeUtil()); } protected function getDefaultTokenType() { $config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name'))); + return new Bearer($config); } @@ -449,7 +463,8 @@ class Server implements ResourceControllerInterface, } if (isset($this->storages['refresh_token'])) { - $grantTypes['refresh_token'] = new RefreshToken($this->storages['refresh_token']); + $config = array_intersect_key($this->config, array('always_issue_new_refresh_token' => '')); + $grantTypes['refresh_token'] = new RefreshToken($this->storages['refresh_token'], $config); } if (isset($this->storages['authorization_code'])) { diff --git a/src/OAuth2/Storage/ClientInterface.php b/src/OAuth2/Storage/ClientInterface.php index 671025e..e9d0c72 100644 --- a/src/OAuth2/Storage/ClientInterface.php +++ b/src/OAuth2/Storage/ClientInterface.php @@ -28,6 +28,7 @@ interface ClientInterface * "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 * ); * @endcode * diff --git a/src/OAuth2/Storage/CryptoToken.php b/src/OAuth2/Storage/CryptoToken.php new file mode 100644 index 0000000..3883c7b --- /dev/null +++ b/src/OAuth2/Storage/CryptoToken.php @@ -0,0 +1,77 @@ +<?php + +namespace OAuth2\Storage; + +use OAuth2\Encryption\EncryptionInterface; +use OAuth2\Encryption\Jwt; + +/** + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ +class CryptoToken implements AccessTokenInterface +{ + protected $publicKeyStorage; + protected $tokenStorage; + 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. + */ + public function __construct(PublicKeyInterface $publicKeyStorage, AccessTokenInterface $tokenStorage = null, EncryptionInterface $encryptionUtil = null) + { + $this->publicKeyStorage = $publicKeyStorage; + $this->tokenStorage = $tokenStorage; + if (is_null($encryptionUtil)) { + $encryptionUtil = new Jwt; + } + $this->encryptionUtil = $encryptionUtil; + } + + public function getAccessToken($oauth_token) + { + // just decode the token, don't verify + if (!$tokenData = $this->encryptionUtil->decode($oauth_token, null, false)) { + return false; + } + + $client_id = isset($tokenData['client_id']) ? $tokenData['client_id'] : null; + $public_key = $this->publicKeyStorage->getPublicKey($client_id); + $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id); + + // now that we have the client_id, verify the token + if (false === $this->encryptionUtil->decode($oauth_token, $public_key, $algorithm)) { + return false; + } + + return $tokenData; + } + + public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null) + { + if ($this->tokenStorage) { + return $this->tokenStorage->setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope); + } + } +} diff --git a/src/OAuth2/Storage/JwtBearerInterface.php b/src/OAuth2/Storage/JwtBearerInterface.php index 6b9dc3a..fcb1fe2 100644 --- a/src/OAuth2/Storage/JwtBearerInterface.php +++ b/src/OAuth2/Storage/JwtBearerInterface.php @@ -18,11 +18,58 @@ interface JwtBearerInterface * Get the public key associated with a client_id * * @param $client_id - * Client identifier to be check with. + * Client identifier to be checked with. * * @return * STRING Return the public key for the client_id if it exists, and MUST return FALSE if it doesn't. - * @endcode */ public function getClientKey($client_id, $subject); + + /** + * Get a jti (JSON token identifier)by matching against the client_id, subject, audience and expiration. + * + * @param $client_id + * Client identifier to match. + * + * @param $subject + * The subject to match. + * + * @param $audience + * The audience to match. + * + * @param $expiration + * The expiration of the jti. + * + * @param $jti + * The jti to match. + * + * @return + * An associative array as below, and return NULL if the jti does not exist. + * - issuer: Stored client identifier. + * - subject: Stored subject. + * - audience: Stored audience. + * - expires: Stored expiration in unix timestamp. + * - jti: The stored jti. + */ + public function getJti($client_id, $subject, $audience, $expiration, $jti); + + /** + * Store a used jti so that we can check against it to prevent replay attacks. + + * @param $client_id + * Client identifier to insert. + * + * @param $subject + * The subject to insert. + * + * @param $audience + * The audience to insert. + * + * @param $expiration + * The expiration of the jti. + * + * @param $jti + * The jti to insert. + */ + public function setJti($client_id, $subject, $audience, $expiration, $jti); } diff --git a/src/OAuth2/Storage/Memory.php b/src/OAuth2/Storage/Memory.php index 45df708..7f5a0c7 100644 --- a/src/OAuth2/Storage/Memory.php +++ b/src/OAuth2/Storage/Memory.php @@ -16,7 +16,8 @@ class Memory implements AuthorizationCodeInterface, ClientCredentialsInterface, RefreshTokenInterface, JwtBearerInterface, - ScopeInterface + ScopeInterface, + PublicKeyInterface { private $authorizationCodes; private $userCredentials; @@ -24,10 +25,12 @@ class Memory implements AuthorizationCodeInterface, private $refreshTokens; private $accessTokens; private $jwt; + private $jti; private $supportedScopes; private $clientSupportedScopes; private $clientDefaultScopes; private $defaultScope; + private $keys; public function __construct($params = array()) { @@ -38,10 +41,12 @@ class Memory implements AuthorizationCodeInterface, 'refresh_tokens' => array(), 'access_tokens' => array(), 'jwt' => array(), + 'jti' => array(), 'default_scope' => null, 'client_supported_scopes' => array(), 'client_default_scopes' => array(), 'supported_scopes' => array(), + 'keys' => array(), ), $params); $this->authorizationCodes = $params['authorization_codes']; @@ -50,10 +55,12 @@ class Memory implements AuthorizationCodeInterface, $this->refreshTokens = $params['refresh_tokens']; $this->accessTokens = $params['access_tokens']; $this->jwt = $params['jwt']; + $this->jti = $params['jti']; $this->supportedScopes = $params['supported_scopes']; $this->clientSupportedScopes = $params['client_supported_scopes']; $this->clientDefaultScopes = $params['client_default_scopes']; $this->defaultScope = $params['default_scope']; + $this->keys = $params['keys']; } /* AuthorizationCodeInterface */ @@ -89,6 +96,7 @@ class Memory implements AuthorizationCodeInterface, public function checkUserCredentials($username, $password) { $userDetails = $this->getUserDetails($username); + return $userDetails && $userDetails['password'] && $userDetails['password'] === $password; } @@ -150,9 +158,17 @@ class Memory implements AuthorizationCodeInterface, return true; } - public function setClientCredentials($client_credentials) + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $user_id = null) { - $this->clientCredentials = $client_credentials; + $this->clientCredentials[$client_id] = array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'user_id' => $user_id, + ); + + return true; } /* RefreshTokenInterface */ @@ -208,7 +224,7 @@ class Memory implements AuthorizationCodeInterface, { if ($client_id && array_key_exists($client_id, $this->clientDefaultScopes)) { return implode(' ', $this->clientDefaultScopes[$client_id]); - }else{ + } else { return $this->defaultScope; } } @@ -227,4 +243,68 @@ class Memory implements AuthorizationCodeInterface, return false; } + + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + foreach ($this->jti as $storedJti) { + if ($storedJti['issuer'] == $client_id && $storedJti['subject'] == $subject && $storedJti['audience'] == $audience && $storedJti['expires'] == $expires && $storedJti['jti'] == $jti) { + return array('issuer' => $storedJti['issuer'], + 'subject' => $storedJti['subject'], + 'audience' => $storedJti['audience'], + 'expires' => $storedJti['expires'], + 'jti' => $storedJti['jti'] + ); + } + } + + return null; + } + + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + $this->jti[] = array('issuer' => $client_id, 'subject' => $subject, 'audience' => $audience, 'expires' => $expires, 'jti' => $jti); + } + + /*PublicKeyInterface */ + public function getPublicKey($client_id = null) + { + if (isset($this->keys[$client_id])) { + return $this->keys[$client_id]['public_key']; + } + + // use a global encryption pair + if (isset($this->keys['public_key'])) { + return $this->keys['public_key']; + } + + return false; + } + + public function getPrivateKey($client_id = null) + { + if (isset($this->keys[$client_id])) { + return $this->keys[$client_id]['private_key']; + } + + // use a global encryption pair + if (isset($this->keys['private_key'])) { + return $this->keys['private_key']; + } + + return false; + } + + public function getEncryptionAlgorithm($client_id = null) + { + if (isset($this->keys[$client_id]['encryption_algorithm'])) { + return $this->keys[$client_id]['encryption_algorithm']; + } + + // use a global encryption algorithm + if (isset($this->keys['encryption_algorithm'])) { + return $this->keys['encryption_algorithm']; + } + + return 'RS256'; + } } diff --git a/src/OAuth2/Storage/Mongo.php b/src/OAuth2/Storage/Mongo.php index 0ada22d..3cc70c6 100644 --- a/src/OAuth2/Storage/Mongo.php +++ b/src/OAuth2/Storage/Mongo.php @@ -5,8 +5,12 @@ namespace OAuth2\Storage; /** * Simple MongoDB storage for all storage types * - * NOTE: This class should never be used in production, and is - * a stub class for example use only + * NOTE: This class is meant to get users started + * quickly. If your application requires further + * customization, extend this class or create your own. + * + * NOTE: Passwords are stored in plaintext, which is never + * a good idea. Be sure to override this for your application * * @author Julien Chaumond <chaumond@gmail.com> */ @@ -24,8 +28,7 @@ class Mongo implements AuthorizationCodeInterface, { if ($connection instanceof \MongoDB) { $this->db = $connection; - } - else { + } else { if (!is_array($connection)) { throw new \InvalidArgumentException('First argument to OAuth2_Storage_Mongo must be an instance of MongoDB or a configuration array'); } @@ -60,6 +63,7 @@ class Mongo implements AuthorizationCodeInterface, if ($result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) { return $result['client_secret'] == $client_secret; } + return false; } @@ -70,6 +74,33 @@ class Mongo implements AuthorizationCodeInterface, return is_null($result) ? false : $result; } + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $user_id = null) + { + if ($this->getClientDetails($client_id)) { + $this->collection('client_table')->update( + array('client_id' => $client_id), + array('$set' => array( + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'user_id' => $user_id, + )) + ); + } else { + $this->collection('client_table')->insert( + array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'user_id' => $user_id, + ) + ); + } + + return true; + } + public function checkRestrictedGrantType($client_id, $grant_type) { $details = $this->getClientDetails($client_id); @@ -172,6 +203,7 @@ class Mongo implements AuthorizationCodeInterface, if ($user = $this->getUser($username)) { return $this->checkPassword($user, $password); } + return false; } @@ -262,4 +294,16 @@ class Mongo implements AuthorizationCodeInterface, return $result; } + + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('getJti() for the MongoDB driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('setJti() for the MongoDB driver is currently unimplemented.'); + } } diff --git a/src/OAuth2/Storage/Pdo.php b/src/OAuth2/Storage/Pdo.php index 49f977a..cd19b4c 100644 --- a/src/OAuth2/Storage/Pdo.php +++ b/src/OAuth2/Storage/Pdo.php @@ -19,7 +19,9 @@ class Pdo implements AuthorizationCodeInterface, ClientCredentialsInterface, UserCredentialsInterface, RefreshTokenInterface, - JwtBearerInterface + JwtBearerInterface, + ScopeInterface, + PublicKeyInterface { protected $db; protected $config; @@ -51,7 +53,9 @@ class Pdo implements AuthorizationCodeInterface, 'refresh_token_table' => 'oauth_refresh_tokens', 'code_table' => 'oauth_authorization_codes', 'user_table' => 'oauth_users', - 'jwt_table' => 'oauth_jwt', + 'jwt_table' => 'oauth_jwt', + 'scope_table' => 'oauth_scopes', + 'public_key_table' => 'oauth_public_keys', ), $config); } @@ -63,7 +67,7 @@ class Pdo implements AuthorizationCodeInterface, $result = $stmt->fetch(); // make this extensible - return $result['client_secret'] == $client_secret; + return $result && $result['client_secret'] == $client_secret; } public function getClientDetails($client_id) @@ -74,6 +78,18 @@ class Pdo implements AuthorizationCodeInterface, return $stmt->fetch(); } + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $user_id = null) + { + // if it exists, update it. + if ($this->getClientDetails($client_id)) { + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_secret=:client_secret, redirect_uri=:redirect_uri, grant_types=:grant_types, user_id=:user_id where client_id=:client_id', $this->config['client_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (client_id, client_secret, redirect_uri, grant_types, user_id) VALUES (:client_id, :client_secret, :redirect_uri, :grant_types, :user_id)', $this->config['client_table'])); + } + + return $stmt->execute(compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'user_id')); + } + public function checkRestrictedGrantType($client_id, $grant_type) { $details = $this->getClientDetails($client_id); @@ -112,6 +128,7 @@ class Pdo implements AuthorizationCodeInterface, } else { $stmt = $this->db->prepare(sprintf('INSERT INTO %s (access_token, client_id, expires, user_id, scope) VALUES (:access_token, :client_id, :expires, :user_id, :scope)', $this->config['access_token_table'])); } + return $stmt->execute(compact('access_token', 'client_id', 'user_id', 'expires', 'scope')); } @@ -140,6 +157,7 @@ class Pdo implements AuthorizationCodeInterface, } else { $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope)', $this->config['code_table'])); } + return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope')); } @@ -156,6 +174,7 @@ class Pdo implements AuthorizationCodeInterface, if ($user = $this->getUser($username)) { return $this->checkPassword($user, $password); } + return false; } @@ -227,15 +246,103 @@ class Pdo implements AuthorizationCodeInterface, } else { $stmt = $this->db->prepare(sprintf('INSERT INTO %s (username, password, first_name, last_name) VALUES (:username, :password, :firstName, :lastName)', $this->config['user_table'])); } + return $stmt->execute(compact('username', 'password', 'firstName', 'lastName')); } - /* OAuth2_Storage_JWTBearerInterface */ + /* ScopeInterface */ + public function scopeExists($scope, $client_id = null) + { + $scope = explode(' ', $scope); + $stmt = $this->db->prepare($sql = sprintf('SELECT scope FROM %s WHERE type="supported" AND (client_id=:client_id OR client_id IS NULL) ORDER BY client_id IS NOT NULL DESC', $this->config['scope_table'])); + $stmt->execute(compact('client_id')); + + if ($result = $stmt->fetch()) { + $supportedScope = explode(' ', $result['scope']); + } else { + $supportedScope = array(); + } + + return (count(array_diff($scope, $supportedScope)) == 0); + } + + public function getDefaultScope($client_id = null) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT scope FROM %s WHERE type="default" AND (client_id=:client_id OR client_id IS NULL) ORDER BY client_id IS NOT NULL DESC', $this->config['scope_table'])); + $stmt->execute(compact('client_id')); + + if ($result = $stmt->fetch()) { + return $result['scope']; + } + + return null; + } + + /* JWTBearerInterface */ public function getClientKey($client_id, $subject) { $stmt = $this->db->prepare($sql = sprintf('SELECT public_key from %s where client_id=:client_id AND subject=:subject', $this->config['jwt_table'])); $stmt->execute(array('client_id' => $client_id, 'subject' => $subject)); + return $stmt->fetch(); } + + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT* FROM %s WHERE issuer=:client_id AND subject=:subject AND audience=:audience AND expires=:expires AND jti=:jti', $this->config['jti_table'])); + + $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti')); + + if ($result = $stmt->fetch()) { + return array('issuer' => $result['issuer'], + 'subject' => $result['subject'], + 'audience' => $result['audience'], + 'expires' => $result['expires'], + 'jti' => $result['jti'] + ); + } + + return null; + } + + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (issuer, subject, audience, expires, jti) VALUES (:client_id, :subject, :audience, :expires, :jti)', $this->config['jti_table'])); + + return $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti')); + } + + /* PublicKeyInterface */ + public function getPublicKey($client_id = null) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT public_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); + + $stmt->execute(compact('client_id')); + if ($result = $stmt->fetch()) { + return $result['public_key']; + } + } + + public function getPrivateKey($client_id = null) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT private_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); + + $stmt->execute(compact('client_id')); + if ($result = $stmt->fetch()) { + return $result['private_key']; + } + } + + public function getEncryptionAlgorithm($client_id = null) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT encryption_algorithm FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); + + $stmt->execute(compact('client_id')); + if ($result = $stmt->fetch()) { + return $result['encryption_algorithm']; + } + + return 'RS256'; + } } diff --git a/src/OAuth2/Storage/PublicKeyInterface.php b/src/OAuth2/Storage/PublicKeyInterface.php new file mode 100644 index 0000000..108418d --- /dev/null +++ b/src/OAuth2/Storage/PublicKeyInterface.php @@ -0,0 +1,16 @@ +<?php + +namespace OAuth2\Storage; + +/** + * Implement this interface to specify where the OAuth2 Server + * should get public/private key information + * + * @author Brent Shaffer <bshafs at gmail dot com> + */ +interface PublicKeyInterface +{ + public function getPublicKey($client_id = null); + public function getPrivateKey($client_id = null); + public function getEncryptionAlgorithm($client_id = null); +} diff --git a/src/OAuth2/Storage/Redis.php b/src/OAuth2/Storage/Redis.php index 440c3a2..55d300c 100644 --- a/src/OAuth2/Storage/Redis.php +++ b/src/OAuth2/Storage/Redis.php @@ -8,7 +8,7 @@ namespace OAuth2\Storage; * Register client: * <code> * $storage = new OAuth2_Storage_Redis($redis); - * $storage->registerClient($client_id, $client_secret, $redirect_uri); + * $storage->setClientDetails($client_id, $client_secret, $redirect_uri); * </code> */ class Redis implements AuthorizationCodeInterface, @@ -16,17 +16,23 @@ class Redis implements AuthorizationCodeInterface, ClientCredentialsInterface, UserCredentialsInterface, RefreshTokenInterface, - JwtBearerInterface + JwtBearerInterface, + ScopeInterface { - private $redis; - private $config; + private $cache; + /* The redis client */ + protected $redis; + + /* Configuration array */ + protected $config; + /** * Redis Storage! * * @param \Predis\Client $redis - * @param array $config + * @param array $config */ public function __construct($redis, $config=array()) { @@ -38,6 +44,7 @@ class Redis implements AuthorizationCodeInterface, 'code_key' => 'oauth_authorization_codes:', 'user_key' => 'oauth_users:', 'jwt_key' => 'oauth_jwt:', + 'scope_key' => 'oauth_scopes:', ), $config); } @@ -58,8 +65,9 @@ class Redis implements AuthorizationCodeInterface, { $this->cache[$key] = $value; $str = json_encode($value); - if ( $expire > 0 ) { + if ($expire > 0) { $seconds = $expire - time(); + return $this->redis->setex($key, $seconds, $str); } else { return $this->redis->set($key, $str); @@ -69,6 +77,7 @@ class Redis implements AuthorizationCodeInterface, protected function expireValue($key) { unset($this->cache[$key]); + return $this->redis->expire($key); } @@ -91,6 +100,7 @@ class Redis implements AuthorizationCodeInterface, { $key = $this->config['code_key'] . $code; unset($this->cache[$key]); + return $this->expireValue($key); } @@ -98,6 +108,7 @@ class Redis implements AuthorizationCodeInterface, public function checkUserCredentials($username, $password) { $user = $this->getUserDetails($username); + return $user && $user['password'] === $password; } @@ -129,7 +140,10 @@ class Redis implements AuthorizationCodeInterface, /* ClientCredentialsInterface */ public function checkClientCredentials($client_id, $client_secret = null) { - $client = $this->getClientDetails($client_id); + if (!$client = $this->getClientDetails($client_id)) { + return false; + } + return isset($client['client_secret']) && $client['client_secret'] == $client_secret; } @@ -139,6 +153,14 @@ class Redis implements AuthorizationCodeInterface, return $this->getValue($this->config['client_key'] . $client_id); } + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $user_id = null) + { + return $this->setValue( + $this->config['client_key'] . $client_id, + compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'user_id') + ); + } + public function checkRestrictedGrantType($client_id, $grant_type) { $details = $this->getClientDetails($client_id); @@ -152,14 +174,6 @@ class Redis implements AuthorizationCodeInterface, return true; } - public function registerClient($client_id, $client_secret, $redirect_uri) - { - return $this->setValue( - $this->config['client_key'] . $client_id, - compact('client_id', 'client_secret', 'redirect_uri') - ); - } - /* RefreshTokenInterface */ public function getRefreshToken($refresh_token) { @@ -195,6 +209,27 @@ class Redis implements AuthorizationCodeInterface, ); } + /* ScopeInterface */ + public function scopeExists($scope, $client_id = null) + { + $scope = explode(' ', $scope); + if (is_null($client_id) || !$result = $this->getValue($this->config['scope_key'].'supported:'.$client_id)) { + $result = $this->getValue($this->config['scope_key'].'supported:global'); + } + $supportedScope = explode(' ', (string) $result); + + return (count(array_diff($scope, $supportedScope)) == 0); + } + + public function getDefaultScope($client_id = null) + { + if (is_null($client_id) || !$result = $this->getValue($this->config['scope_key'].'default:'.$client_id)) { + $result = $this->getValue($this->config['scope_key'].'default:global'); + } + + return $result; + } + /*JWTBearerInterface */ public function getClientKey($client_id, $subject) { @@ -202,6 +237,19 @@ class Redis implements AuthorizationCodeInterface, if ( isset($jwt['subject']) && $jwt['subject'] == $subject ) { return $jwt['key']; } + return null; } + + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs redis implementation. + throw new \Exception('getJti() for the Redis driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs redis implementation. + throw new \Exception('setJti() for the Redis driver is currently unimplemented.'); + } } diff --git a/src/OAuth2/Storage/RefreshTokenInterface.php b/src/OAuth2/Storage/RefreshTokenInterface.php index 4b04e36..1f0c6c9 100644 --- a/src/OAuth2/Storage/RefreshTokenInterface.php +++ b/src/OAuth2/Storage/RefreshTokenInterface.php @@ -26,6 +26,7 @@ interface RefreshTokenInterface * invalid: * - refresh_token: Stored refresh token identifier. * - client_id: Stored client identifier. + * - user_id: Stored user identifier. * - expires: Stored expiration unix timestamp. * - scope: (optional) Stored scope values in space-separated string. * @@ -50,6 +51,8 @@ interface RefreshTokenInterface * Refresh token to be stored. * @param $client_id * Client identifier to be stored. + * @param $user_id + * User identifier to be stored. * @param $expires * expires to be stored. * @param $scope diff --git a/src/OAuth2/TokenType/Bearer.php b/src/OAuth2/TokenType/Bearer.php index 919302f..4355275 100644 --- a/src/OAuth2/TokenType/Bearer.php +++ b/src/OAuth2/TokenType/Bearer.php @@ -22,7 +22,7 @@ class Bearer implements TokenTypeInterface public function getTokenType() { - return 'bearer'; + return 'Bearer'; } /** @@ -55,10 +55,12 @@ class Bearer implements TokenTypeInterface $methodsUsed = !empty($headers) + !is_null($request->query($this->config['token_param_name'])) + !is_null($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)'); + return null; } if ($methodsUsed == 0) { $response->setStatusCode(401); + return null; } @@ -66,8 +68,10 @@ class Bearer implements TokenTypeInterface if (!empty($headers)) { if (!preg_match('/' . $this->config['token_bearer_header_name'] . '\s(\S+)/', $headers, $matches)) { $response->setError(400, 'invalid_request', 'Malformed auth header'); + return null; } + return $matches[1]; } @@ -75,6 +79,7 @@ class Bearer implements TokenTypeInterface // POST: Get the token from POST data if (strtolower($request->server('REQUEST_METHOD')) != 'post') { $response->setError(400, 'invalid_request', 'When putting the token in the body, the method must be POST'); + return null; } @@ -87,6 +92,7 @@ class Bearer implements TokenTypeInterface // IETF specifies content-type. NB: Not all webservers populate this _SERVER variable // @see http://tools.ietf.org/html/rfc6750#section-2.2 $response->setError(400, 'invalid_request', 'The content type for POST requests must be "application/x-www-form-urlencoded"'); + return null; } diff --git a/test/OAuth2/Encryption/JwtTest.php b/test/OAuth2/Encryption/JwtTest.php new file mode 100644 index 0000000..b10c61b --- /dev/null +++ b/test/OAuth2/Encryption/JwtTest.php @@ -0,0 +1,63 @@ +<?php + +namespace OAuth2\Encryption; + +use OAuth2\Storage\Bootstrap; + +class JwtTest extends \PHPUnit_Framework_TestCase +{ + private $privateKey; + + public function setUp() + { + $this->privateKey = <<<EOD +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC5/SxVlE8gnpFqCxgl2wjhzY7ucEi00s0kUg3xp7lVEvgLgYcA +nHiWp+gtSjOFfH2zsvpiWm6Lz5f743j/FEzHIO1owR0p4d9pOaJK07d01+RzoQLO +IQAgXrr4T1CCWUesncwwPBVCyy2Mw3Nmhmr9MrF8UlvdRKBxriRnlP3qJQIDAQAB +AoGAVgJJVU4fhYMu1e5JfYAcTGfF+Gf+h3iQm4JCpoUcxMXf5VpB9ztk3K7LRN5y +kwFuFALpnUAarRcUPs0D8FoP4qBluKksbAtgHkO7bMSH9emN+mH4le4qpFlR7+P1 +3fLE2Y19IBwPwEfClC+TpJvuog6xqUYGPlg6XLq/MxQUB4ECQQDgovP1v+ONSeGS +R+NgJTR47noTkQT3M2izlce/OG7a+O0yw6BOZjNXqH2wx3DshqMcPUFrTjibIClP +l/tEQ3ShAkEA0/TdBYDtXpNNjqg0R9GVH2pw7Kh68ne6mZTuj0kCgFYpUF6L6iMm +zXamIJ51rTDsTyKTAZ1JuAhAsK/M2BbDBQJAKQ5fXEkIA+i+64dsDUR/hKLBeRYG +PFAPENONQGvGBwt7/s02XV3cgGbxIgAxqWkqIp0neb9AJUoJgtyaNe3GQQJANoL4 +QQ0af0NVJAZgg8QEHTNL3aGrFSbzx8IE5Lb7PLRsJa5bP5lQxnDoYuU+EI/Phr62 +niisp/b/ZDGidkTMXQJBALeRsH1I+LmICAvWXpLKa9Gv0zGCwkuIJLiUbV9c6CVh +suocCAteQwL5iW2gA4AnYr5OGeHFsEl7NCQcwfPZpJ0= +-----END RSA PRIVATE KEY----- +EOD; + } + + /** @dataProvider provideClientCredentials */ + public function testJwtUtil($client_id, $client_key) + { + $jwtUtil = new Jwt(); + + $params = array( + 'iss' => $client_id, + 'exp' => time() + 1000, + 'iat' => time(), + 'sub' => 'testuser@ourdomain.com', + 'aud' => 'http://myapp.com/oauth/auth', + 'scope' => null, + ); + + $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256'); + + $payload = $jwtUtil->decode($encoded, $client_key); + + $this->assertEquals($params, $payload); + } + + public function provideClientCredentials() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $client_id = 'Test Client ID'; + $client_key = $storage->getClientKey($client_id, "testuser@ourdomain.com"); + + return array( + array($client_id, $client_key), + ); + } +} diff --git a/test/OAuth2/GrantType/ClientCredentialsTest.php b/test/OAuth2/GrantType/ClientCredentialsTest.php index 8077e61..f0d46cc 100644 --- a/test/OAuth2/GrantType/ClientCredentialsTest.php +++ b/test/OAuth2/GrantType/ClientCredentialsTest.php @@ -127,6 +127,27 @@ class ClientCredentialsTest extends \PHPUnit_Framework_TestCase $this->assertNotNull($token['access_token']); } + public function testClientUserIdIsSetInAccessToken() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Client ID With User ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + + // verify the user_id was associated with the token + $storage = $server->getStorage('client'); + $token = $storage->getAccessToken($token['access_token']); + $this->assertNotNull($token); + $this->assertArrayHasKey('user_id', $token); + $this->assertEquals($token['user_id'], 'brent@brentertainment.com'); + } + private function getTestServer() { $storage = Bootstrap::getInstance()->getMemoryStorage(); diff --git a/test/OAuth2/GrantType/JWTBearerTest.php b/test/OAuth2/GrantType/JWTBearerTest.php index 50fa77e..4b36ef2 100644 --- a/test/OAuth2/GrantType/JWTBearerTest.php +++ b/test/OAuth2/GrantType/JWTBearerTest.php @@ -261,24 +261,51 @@ EOD; $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested'); } - public function testJwtUtil() + public function testValidJti() { - $storage = Bootstrap::getInstance()->getMemoryStorage(); - $jwtUtil = new Jwt(); - $client_id = 'Test Client ID'; - $params = $this->getJWTParams(null, null, null, $client_id); + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, 'testuser@ourdomain.com', 'Test Client ID', null, 'unused_jti'), // valid assertion with invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } - if (version_compare(PHP_VERSION, '5.3.3') <= 0) { - $encoded = $jwtUtil->encode($params, 'mysecretkey', 'HS256'); - $client_id .= ' PHP-5.2'; - } else { - $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256'); - } + public function testInvalidJti() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(99999999900, null, 'testuser@ourdomain.com', 'Test Client ID', null, 'used_jti'), // valid assertion with invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); - $payload = $jwtUtil->decode($encoded, $storage->getClientKey($client_id, "testuser@ourdomain.com")); + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JSON Token Identifier (jti) has already been used'); + } - $this->assertEquals($params, $payload); + public function testJtiReplayAttack() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(99999999900, null, 'testuser@ourdomain.com', 'Test Client ID', null, 'totally_new_jti'), // valid assertion with invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + + //Replay the same request + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JSON Token Identifier (jti) has already been used'); } /** @@ -289,15 +316,8 @@ EOD; * @param $iss The issuer, usually the client_id. * @return string */ - private function getJWTParams($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID', $scope = null) + private function getJWT($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID', $scope = null, $jti = null) { - //Since PHP 5.2 does not have OpenSSL support on Travis CI, we will test it using the HS256 algorithm - //We also provided PHP 5.2 specific data for it in storage.json - if (version_compare(PHP_VERSION, '5.3.3') <= 0) { - // add "5.2" identifier onto the client name - $iss .= ' PHP-5.2'; - } - if (!$exp) { $exp = time() + 1000; } @@ -319,19 +339,12 @@ EOD; $params['nbf'] = $nbf; } - return $params; - } - - private function getJWT($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID', $scope = null) - { - $params = $this->getJWTParams($exp, $nbf, $sub, $iss, $scope); + if ($jti) { + $params['jti'] = $jti; + } $jwtUtil = new Jwt(); - if (version_compare(PHP_VERSION, '5.3.3') <= 0) { - return $jwtUtil->encode($params, 'mysecretkey', 'HS256'); - } - return $jwtUtil->encode($params, $this->privateKey, 'RS256'); } diff --git a/test/OAuth2/ResponseType/CryptoTokenTest.php b/test/OAuth2/ResponseType/CryptoTokenTest.php new file mode 100644 index 0000000..32f81a1 --- /dev/null +++ b/test/OAuth2/ResponseType/CryptoTokenTest.php @@ -0,0 +1,88 @@ +<?php + +namespace OAuth2\ResponseType; + +use OAuth2\Server; +use OAuth2\Response; +use OAuth2\Request\TestRequest; +use OAuth2\Storage\Bootstrap; +use OAuth2\Storage\CryptoToken as CryptoTokenStorage; +use OAuth2\GrantType\ClientCredentials; + +class CryptoTokenTest extends \PHPUnit_Framework_TestCase +{ + public function testGrantCryptoToken() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertNotNull($response->getParameter('access_token')); + $this->assertEquals(2, substr_count($response->getParameter('access_token'), '.')); + } + + public function testAccessResourceWithCryptoToken() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + $this->assertNotNull($cryptoToken = $response->getParameter('access_token')); + + // make a call to the resource server using the crypto token + $request = TestRequest::createPost(array( + 'access_token' => $cryptoToken, + )); + + $this->assertTrue($server->verifyResourceRequest($request)); + } + + public function testAccessResourceWithCryptoTokenUsingSecondaryStorage() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + $this->assertNotNull($cryptoToken = $response->getParameter('access_token')); + + // make a call to the resource server using the crypto token + $request = TestRequest::createPost(array( + 'access_token' => $cryptoToken, + )); + + // create a resource server with the "memory" storage from the grant server + $resourceServer = new Server($server->getStorage('client_credentials')); + + $this->assertTrue($resourceServer->verifyResourceRequest($request)); + } + + private function getTestServer() + { + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + + $storage = array( + 'access_token' => new CryptoTokenStorage($memoryStorage), + 'client_credentials' => $memoryStorage, + ); + $server = new Server($storage); + $server->addGrantType(new ClientCredentials($memoryStorage)); + + // make the "token" response type a CryptoToken + $server->addResponseType(new CryptoToken($memoryStorage, $memoryStorage)); + + return $server; + } +} diff --git a/test/OAuth2/ServerTest.php b/test/OAuth2/ServerTest.php index 5960235..b61f851 100644 --- a/test/OAuth2/ServerTest.php +++ b/test/OAuth2/ServerTest.php @@ -265,6 +265,35 @@ class ServerTest extends \PHPUnit_Framework_TestCase $this->assertEquals($config['allow_credentials_in_request_body'], false); } + public function testRefreshTokenConfig() + { + // create mock storage + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server1 = new Server(array($storage)); + $server2 = new Server(array($storage), array('always_issue_new_refresh_token' => true)); + + $server1->getTokenController(); + $refreshToken1 = $server1->getGrantType('refresh_token'); + + $server2->getTokenController(); + $refreshToken2 = $server2->getGrantType('refresh_token'); + + $reflection1 = new \ReflectionClass($refreshToken1); + $prop1 = $reflection1->getProperty('config'); + $prop1->setAccessible(true); + + $reflection2 = new \ReflectionClass($refreshToken2); + $prop2 = $reflection2->getProperty('config'); + $prop2->setAccessible(true); + + // get the private "config" property + $config1 = $prop1->getValue($refreshToken1); + $config2 = $prop2->getValue($refreshToken2); + + $this->assertEquals($config1['always_issue_new_refresh_token'], false); + $this->assertEquals($config2['always_issue_new_refresh_token'], true); + } + /** * @expectedException InvalidArgumentException OAuth2\ResponseType\AuthorizationCodeInterface **/ diff --git a/test/OAuth2/Storage/AccessTokenTest.php b/test/OAuth2/Storage/AccessTokenTest.php new file mode 100644 index 0000000..17c9cd9 --- /dev/null +++ b/test/OAuth2/Storage/AccessTokenTest.php @@ -0,0 +1,51 @@ +<?php + +namespace OAuth2\Storage; + +class AccessTokenTest extends BaseTest +{ + /** @dataProvider provideStorage */ + public function testSetAccessToken(AccessTokenInterface $storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + // assert token we are about to add does not exist + $token = $storage->getAccessToken('newtoken'); + $this->assertFalse($token); + + // add new token + $expires = time() + 20; + $success = $storage->setAccessToken('newtoken', 'client ID', 'SOMEUSERID', $expires); + $this->assertTrue($success); + + $token = $storage->getAccessToken('newtoken'); + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('client_id', $token); + $this->assertArrayHasKey('user_id', $token); + $this->assertArrayHasKey('expires', $token); + $this->assertEquals($token['access_token'], 'newtoken'); + $this->assertEquals($token['client_id'], 'client ID'); + $this->assertEquals($token['user_id'], 'SOMEUSERID'); + $this->assertEquals($token['expires'], $expires); + + // change existing token + $expires = time() + 42; + $success = $storage->setAccessToken('newtoken', 'client ID2', 'SOMEOTHERID', $expires); + $this->assertTrue($success); + + $token = $storage->getAccessToken('newtoken'); + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('client_id', $token); + $this->assertArrayHasKey('user_id', $token); + $this->assertArrayHasKey('expires', $token); + $this->assertEquals($token['access_token'], 'newtoken'); + $this->assertEquals($token['client_id'], 'client ID2'); + $this->assertEquals($token['user_id'], 'SOMEOTHERID'); + $this->assertEquals($token['expires'], $expires); + } +} diff --git a/test/OAuth2/Storage/AuthorizationCodeTest.php b/test/OAuth2/Storage/AuthorizationCodeTest.php new file mode 100644 index 0000000..9f57714 --- /dev/null +++ b/test/OAuth2/Storage/AuthorizationCodeTest.php @@ -0,0 +1,72 @@ +<?php + +namespace OAuth2\Storage; + +class AuthorizationCodeTest extends BaseTest +{ + /** @dataProvider provideStorage */ + public function testGetAuthorizationCode(AuthorizationCodeInterface $storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + // nonexistant client_id + $details = $storage->getAuthorizationCode('faketoken'); + $this->assertFalse($details); + + // valid client_id + $details = $storage->getAuthorizationCode('testtoken'); + $this->assertNotNull($details); + } + + /** @dataProvider provideStorage */ + public function testSetAuthorizationCode(AuthorizationCodeInterface $storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + // assert code we are about to add does not exist + $code = $storage->getAuthorizationCode('newcode'); + $this->assertFalse($code); + + // add new code + $expires = time() + 20; + $success = $storage->setAuthorizationCode('newcode', 'client ID', 'SOMEUSERID', 'http://example.com', $expires); + $this->assertTrue($success); + + $code = $storage->getAuthorizationCode('newcode'); + $this->assertNotNull($code); + $this->assertArrayHasKey('authorization_code', $code); + $this->assertArrayHasKey('client_id', $code); + $this->assertArrayHasKey('user_id', $code); + $this->assertArrayHasKey('redirect_uri', $code); + $this->assertArrayHasKey('expires', $code); + $this->assertEquals($code['authorization_code'], 'newcode'); + $this->assertEquals($code['client_id'], 'client ID'); + $this->assertEquals($code['user_id'], 'SOMEUSERID'); + $this->assertEquals($code['redirect_uri'], 'http://example.com'); + $this->assertEquals($code['expires'], $expires); + + // change existing code + $expires = time() + 42; + $success = $storage->setAuthorizationCode('newcode', 'client ID2', 'SOMEOTHERID', 'http://example.org', $expires); + $this->assertTrue($success); + + $code = $storage->getAuthorizationCode('newcode'); + $this->assertNotNull($code); + $this->assertArrayHasKey('authorization_code', $code); + $this->assertArrayHasKey('client_id', $code); + $this->assertArrayHasKey('user_id', $code); + $this->assertArrayHasKey('redirect_uri', $code); + $this->assertArrayHasKey('expires', $code); + $this->assertEquals($code['authorization_code'], 'newcode'); + $this->assertEquals($code['client_id'], 'client ID2'); + $this->assertEquals($code['user_id'], 'SOMEOTHERID'); + $this->assertEquals($code['redirect_uri'], 'http://example.org'); + $this->assertEquals($code['expires'], $expires); + } +} diff --git a/test/OAuth2/Storage/ClientCredentialsTest.php b/test/OAuth2/Storage/ClientCredentialsTest.php new file mode 100644 index 0000000..ecaf6f6 --- /dev/null +++ b/test/OAuth2/Storage/ClientCredentialsTest.php @@ -0,0 +1,27 @@ +<?php + +namespace OAuth2\Storage; + +class ClientCredentialsTest extends BaseTest +{ + /** @dataProvider provideStorage */ + public function testCheckClientCredentials(ClientCredentialsInterface $storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + // nonexistant client_id + $pass = $storage->checkClientCredentials('fakeclient', 'testpass'); + $this->assertFalse($pass); + + // invalid password + $pass = $storage->checkClientCredentials('oauth_test_client', 'invalidcredentials'); + $this->assertFalse($pass); + + // valid credentials + $pass = $storage->checkClientCredentials('oauth_test_client', 'testpass'); + $this->assertTrue($pass); + } +} diff --git a/test/OAuth2/Storage/ClientTest.php b/test/OAuth2/Storage/ClientTest.php new file mode 100644 index 0000000..4d95c6c --- /dev/null +++ b/test/OAuth2/Storage/ClientTest.php @@ -0,0 +1,84 @@ +<?php + +namespace OAuth2\Storage; + +class ClientTest extends BaseTest +{ + /** @dataProvider provideStorage */ + public function testGetClientDetails(ClientInterface $storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + // nonexistant client_id + $details = $storage->getClientDetails('fakeclient'); + $this->assertFalse($details); + + // valid client_id + $details = $storage->getClientDetails('oauth_test_client'); + $this->assertNotNull($details); + $this->assertArrayHasKey('client_id', $details); + $this->assertArrayHasKey('client_secret', $details); + $this->assertArrayHasKey('redirect_uri', $details); + } + + /** @dataProvider provideStorage */ + public function testCheckRestrictedGrantType(ClientInterface $storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + + // Check invalid + $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'authorization_code'); + $this->assertFalse($pass); + + // Check valid + $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'implicit'); + $this->assertTrue($pass); + } + + /** @dataProvider provideStorage */ + public function testGetAccessToken(ClientInterface $storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + // nonexistant client_id + $details = $storage->getAccessToken('faketoken'); + $this->assertFalse($details); + + // valid client_id + $details = $storage->getAccessToken('testtoken'); + $this->assertNotNull($details); + } + + /** @dataProvider provideStorage */ + public function testSaveClient(ClientInterface $storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + + $clientId = 'some-client-'.rand(); + + // create a new client + $success = $storage->setClientDetails($clientId, 'somesecret', 'http://test.com', 'client_credentials', 'brent@brentertainment.com'); + $this->assertTrue($success); + + // valid client_id + $details = $storage->getClientDetails($clientId); + $this->assertEquals($details['client_secret'], 'somesecret'); + $this->assertEquals($details['redirect_uri'], 'http://test.com'); + $this->assertEquals($details['grant_types'], 'client_credentials'); + $this->assertEquals($details['user_id'], 'brent@brentertainment.com'); + } +} diff --git a/test/OAuth2/Storage/CryptoTokenTest.php b/test/OAuth2/Storage/CryptoTokenTest.php new file mode 100644 index 0000000..4a63b7e --- /dev/null +++ b/test/OAuth2/Storage/CryptoTokenTest.php @@ -0,0 +1,45 @@ +<?php + +namespace OAuth2\Storage; + +use OAuth2\Encryption\Jwt; + +class CryptoTokenTest extends BaseTest +{ + /** @dataProvider provideStorage */ + public function testSetAccessToken($storage) + { + $publicKeyStorage = Bootstrap::getInstance()->getMemoryStorage(); + $encryptionUtil = new Jwt(); + + $cryptoToken = array( + 'access_token' => rand(), + 'expires' => time() + 100, + 'scope' => 'foo', + ); + + $token = $encryptionUtil->encode($cryptoToken, $publicKeyStorage->getPrivateKey(), $publicKeyStorage->getEncryptionAlgorithm()); + + $this->assertNotNull($token); + + $tokenData = $storage->getAccessToken($token); + + $this->assertTrue(is_array($tokenData)); + + /* assert the decoded token is the same */ + $this->assertEquals($tokenData['access_token'], $cryptoToken['access_token']); + $this->assertEquals($tokenData['expires'], $cryptoToken['expires']); + $this->assertEquals($tokenData['scope'], $cryptoToken['scope']); + } + + // @TODO - use the BaseTest provideStorage, and add support for storages which omit certain interfaces + public function provideStorage() + { + $memory = Bootstrap::getInstance()->getMemoryStorage(); + $storage = new CryptoToken($memory); + + return array( + array($storage) + ); + } +} diff --git a/test/OAuth2/Storage/PublicKeyTest.php b/test/OAuth2/Storage/PublicKeyTest.php new file mode 100644 index 0000000..d08c9c0 --- /dev/null +++ b/test/OAuth2/Storage/PublicKeyTest.php @@ -0,0 +1,22 @@ +<?php + +namespace OAuth2\Storage; + +class PublicKeyTest extends BaseTest +{ + /** @dataProvider provideStorage */ + public function testSetAccessToken($storage) + { + if (is_null($storage) || !$storage instanceof PublicKeyInterface) { + return $this->markTestSkipped('Invalid storage for public key test'); + } + + $configDir = Bootstrap::getInstance()->getConfigDir(); + $globalPublicKey = file_get_contents($configDir.'/keys/id_rsa.pub'); + $globalPrivateKey = file_get_contents($configDir.'/keys/id_rsa'); + + /* assert values from storage */ + $this->assertEquals($storage->getPublicKey(), $globalPublicKey); + $this->assertEquals($storage->getPrivateKey(), $globalPrivateKey); + } +} diff --git a/test/OAuth2/Storage/RefreshTokenTest.php b/test/OAuth2/Storage/RefreshTokenTest.php new file mode 100644 index 0000000..7777bb9 --- /dev/null +++ b/test/OAuth2/Storage/RefreshTokenTest.php @@ -0,0 +1,35 @@ +<?php + +namespace OAuth2\Storage; + +class RefreshTokenTest extends BaseTest +{ + /** @dataProvider provideStorage */ + public function testSetRefreshToken(RefreshTokenInterface $storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + // assert token we are about to add does not exist + $token = $storage->getRefreshToken('refreshtoken'); + $this->assertFalse($token); + + // add new token + $expires = time() + 20; + $success = $storage->setRefreshToken('refreshtoken', 'client ID', 'SOMEUSERID', $expires); + $this->assertTrue($success); + + $token = $storage->getRefreshToken('refreshtoken'); + $this->assertNotNull($token); + $this->assertArrayHasKey('refresh_token', $token); + $this->assertArrayHasKey('client_id', $token); + $this->assertArrayHasKey('user_id', $token); + $this->assertArrayHasKey('expires', $token); + $this->assertEquals($token['refresh_token'], 'refreshtoken'); + $this->assertEquals($token['client_id'], 'client ID'); + $this->assertEquals($token['user_id'], 'SOMEUSERID'); + $this->assertEquals($token['expires'], $expires); + } +} diff --git a/test/OAuth2/Storage/ScopeTest.php b/test/OAuth2/Storage/ScopeTest.php new file mode 100644 index 0000000..a34ce54 --- /dev/null +++ b/test/OAuth2/Storage/ScopeTest.php @@ -0,0 +1,94 @@ +<?php + +namespace OAuth2\Storage; + +use OAuth2\Scope; + +class ScopeTest extends BaseTest +{ + /** @dataProvider provideStorage */ + public function testScopeExists($storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + + if (!$storage instanceof ScopeInterface) { + $this->markTestSkipped('Skipping incompatible storage'); + + return; + } + + //Test getting scopes with a client_id + $scopeUtil = new Scope($storage); + $this->assertTrue($scopeUtil->scopeExists('clientscope1 clientscope2', 'Test Client ID')); + $this->assertFalse($scopeUtil->scopeExists('clientscope1 clientscope2 clientscope3', 'Test Client ID')); + $this->assertTrue($scopeUtil->scopeExists('clientscope3', 'Test Client ID 2')); + } + + /** @dataProvider provideStorage */ + public function testGetDefaultScope($storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + + if (!$storage instanceof ScopeInterface) { + $this->markTestSkipped('Skipping incompatible storage'); + + return; + } + + // test getting default scope + $scopeUtil = new Scope($storage); + $this->assertEquals($scopeUtil->getDefaultScope(), 'defaultscope1 defaultscope2'); + } + + /** @dataProvider provideStorage */ + public function testClientScopeExists($storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + + if (!$storage instanceof ScopeInterface) { + $this->markTestSkipped('Skipping incompatible storage'); + + return; + } + + // test getting scopes with a client_id + $scopeUtil = new Scope($storage); + $this->assertTrue($scopeUtil->scopeExists('supportedscope1')); + $this->assertTrue($scopeUtil->scopeExists('supportedscope2')); + $this->assertTrue($scopeUtil->scopeExists('supportedscope1 supportedscope2')); + $this->assertFalse($scopeUtil->scopeExists('bogusscope')); + } + + /** @dataProvider provideStorage */ + public function testGetDefaultClientScope($storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + + if (!$storage instanceof ScopeInterface) { + $this->markTestSkipped('Skipping incompatible storage'); + + return; + } + + // test getting scopes with a client_id + $scopeUtil = new Scope($storage); + $this->assertEquals($scopeUtil->getDefaultScope('Test Default Scope Client ID'), 'clientscope1 clientscope2'); + $this->assertEquals($scopeUtil->getDefaultScope('Test Default Scope Client ID 2'), 'clientscope3'); + } +} diff --git a/test/OAuth2/Storage/UserCredentialsTest.php b/test/OAuth2/Storage/UserCredentialsTest.php new file mode 100644 index 0000000..ee1c721 --- /dev/null +++ b/test/OAuth2/Storage/UserCredentialsTest.php @@ -0,0 +1,39 @@ +<?php + +namespace OAuth2\Storage; + +class UserCredentialsTest extends BaseTest +{ + /** @dataProvider provideStorage */ + public function testCheckUserCredentials(UserCredentialsInterface $storage = null) + { + if (is_null($storage)) { + $this->markTestSkipped('Unable to load class Mongo_Client'); + + return; + } + // create a new user for testing + $success = $storage->setUser('testusername', 'testpass', 'Test', 'User'); + $this->assertTrue($success); + + // correct credentials + $this->assertTrue($storage->checkUserCredentials('testusername', 'testpass')); + // invalid password + $this->assertFalse($storage->checkUserCredentials('testusername', 'fakepass')); + // invalid username + $this->assertFalse($storage->checkUserCredentials('fakeusername', 'testpass')); + + // invalid username + $this->assertFalse($storage->getUserDetails('fakeusername')); + + // ensure all properties are set + $user = $storage->getUserDetails('testusername'); + $this->assertTrue($user !== false); + $this->assertArrayHasKey('user_id', $user); + $this->assertArrayHasKey('first_name', $user); + $this->assertArrayHasKey('last_name', $user); + $this->assertEquals($user['user_id'], 'testusername'); + $this->assertEquals($user['first_name'], 'Test'); + $this->assertEquals($user['last_name'], 'User'); + } +} diff --git a/test/OAuth2/StorageTest.php b/test/OAuth2/StorageTest.php deleted file mode 100644 index 9a36cb8..0000000 --- a/test/OAuth2/StorageTest.php +++ /dev/null @@ -1,271 +0,0 @@ -<?php - -namespace OAuth2; - -use OAuth2\Storage\ClientCredentialsInterface; -use OAuth2\Storage\ClientInterface; -use OAuth2\Storage\AccessTokenInterface; -use OAuth2\Storage\RefreshTokenInterface; -use OAuth2\Storage\AuthorizationCodeInterface; -use OAuth2\Storage\Bootstrap; - -class StorageTest extends \PHPUnit_Framework_TestCase -{ - /** @dataProvider provideStorage */ - public function testCheckClientCredentials(ClientCredentialsInterface $storage = null) - { - if (is_null($storage)) { - $this->markTestSkipped('Unable to load class Mongo_Client'); - return; - } - // nonexistant client_id - $pass = $storage->checkClientCredentials('fakeclient', 'testpass'); - $this->assertFalse($pass); - - // invalid password - $pass = $storage->checkClientCredentials('oauth_test_client', 'invalidcredentials'); - $this->assertFalse($pass); - - // valid credentials - $pass = $storage->checkClientCredentials('oauth_test_client', 'testpass'); - $this->assertTrue($pass); - } - - /** @dataProvider provideStorage */ - public function testGetClientDetails(ClientInterface $storage = null) - { - if (is_null($storage)) { - $this->markTestSkipped('Unable to load class Mongo_Client'); - return; - } - // nonexistant client_id - $details = $storage->getClientDetails('fakeclient'); - $this->assertFalse($details); - - // valid client_id - $details = $storage->getClientDetails('oauth_test_client'); - $this->assertNotNull($details); - $this->assertArrayHasKey('client_id', $details); - $this->assertArrayHasKey('client_secret', $details); - $this->assertArrayHasKey('redirect_uri', $details); - } - - /** @dataProvider provideStorage */ - public function testCheckRestrictedGrantType(ClientInterface $storage = null) - { - if (is_null($storage)) { - $this->markTestSkipped('Unable to load class Mongo_Client'); - return; - } - - // Check invalid - $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'authorization_code'); - $this->assertFalse($pass); - - // Check valid - $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'implicit'); - $this->assertTrue($pass); - } - - /** @dataProvider provideStorage */ - public function testGetAccessToken(ClientInterface $storage = null) - { - if (is_null($storage)) { - $this->markTestSkipped('Unable to load class Mongo_Client'); - return; - } - // nonexistant client_id - $details = $storage->getAccessToken('faketoken'); - $this->assertFalse($details); - - // valid client_id - $details = $storage->getAccessToken('testtoken'); - $this->assertNotNull($details); - } - - /** @dataProvider provideStorage */ - public function testSetAccessToken(AccessTokenInterface $storage = null) - { - if (is_null($storage)) { - $this->markTestSkipped('Unable to load class Mongo_Client'); - return; - } - // assert token we are about to add does not exist - $token = $storage->getAccessToken('newtoken'); - $this->assertFalse($token); - - // add new token - $expires = time() + 20; - $success = $storage->setAccessToken('newtoken', 'client ID', 'SOMEUSERID', $expires); - $this->assertTrue($success); - - $token = $storage->getAccessToken('newtoken'); - $this->assertNotNull($token); - $this->assertArrayHasKey('access_token', $token); - $this->assertArrayHasKey('client_id', $token); - $this->assertArrayHasKey('user_id', $token); - $this->assertArrayHasKey('expires', $token); - $this->assertEquals($token['access_token'], 'newtoken'); - $this->assertEquals($token['client_id'], 'client ID'); - $this->assertEquals($token['user_id'], 'SOMEUSERID'); - $this->assertEquals($token['expires'], $expires); - - // change existing token - $expires = time() + 42; - $success = $storage->setAccessToken('newtoken', 'client ID2', 'SOMEOTHERID', $expires); - $this->assertTrue($success); - - $token = $storage->getAccessToken('newtoken'); - $this->assertNotNull($token); - $this->assertArrayHasKey('access_token', $token); - $this->assertArrayHasKey('client_id', $token); - $this->assertArrayHasKey('user_id', $token); - $this->assertArrayHasKey('expires', $token); - $this->assertEquals($token['access_token'], 'newtoken'); - $this->assertEquals($token['client_id'], 'client ID2'); - $this->assertEquals($token['user_id'], 'SOMEOTHERID'); - $this->assertEquals($token['expires'], $expires); - } - - /** @dataProvider provideStorage */ - public function testSetRefreshToken(RefreshTokenInterface $storage = null) - { - if (is_null($storage)) { - $this->markTestSkipped('Unable to load class Mongo_Client'); - return; - } - // assert token we are about to add does not exist - $token = $storage->getRefreshToken('refreshtoken'); - $this->assertFalse($token); - - // add new token - $expires = time() + 20; - $success = $storage->setRefreshToken('refreshtoken', 'client ID', 'SOMEUSERID', $expires); - $this->assertTrue($success); - - $token = $storage->getRefreshToken('refreshtoken'); - $this->assertNotNull($token); - $this->assertArrayHasKey('refresh_token', $token); - $this->assertArrayHasKey('client_id', $token); - $this->assertArrayHasKey('user_id', $token); - $this->assertArrayHasKey('expires', $token); - $this->assertEquals($token['refresh_token'], 'refreshtoken'); - $this->assertEquals($token['client_id'], 'client ID'); - $this->assertEquals($token['user_id'], 'SOMEUSERID'); - $this->assertEquals($token['expires'], $expires); - } - - /** @dataProvider provideStorage */ - public function testGetAuthorizationCode(AuthorizationCodeInterface $storage = null) - { - if (is_null($storage)) { - $this->markTestSkipped('Unable to load class Mongo_Client'); - return; - } - // nonexistant client_id - $details = $storage->getAuthorizationCode('faketoken'); - $this->assertFalse($details); - - // valid client_id - $details = $storage->getAuthorizationCode('testtoken'); - $this->assertNotNull($details); - } - - /** @dataProvider provideStorage */ - public function testSetAuthorizationCode(AuthorizationCodeInterface $storage = null) - { - if (is_null($storage)) { - $this->markTestSkipped('Unable to load class Mongo_Client'); - return; - } - // assert code we are about to add does not exist - $code = $storage->getAuthorizationCode('newcode'); - $this->assertFalse($code); - - // add new code - $expires = time() + 20; - $success = $storage->setAuthorizationCode('newcode', 'client ID', 'SOMEUSERID', 'http://example.com', $expires); - $this->assertTrue($success); - - $code = $storage->getAuthorizationCode('newcode'); - $this->assertNotNull($code); - $this->assertArrayHasKey('authorization_code', $code); - $this->assertArrayHasKey('client_id', $code); - $this->assertArrayHasKey('user_id', $code); - $this->assertArrayHasKey('redirect_uri', $code); - $this->assertArrayHasKey('expires', $code); - $this->assertEquals($code['authorization_code'], 'newcode'); - $this->assertEquals($code['client_id'], 'client ID'); - $this->assertEquals($code['user_id'], 'SOMEUSERID'); - $this->assertEquals($code['redirect_uri'], 'http://example.com'); - $this->assertEquals($code['expires'], $expires); - - // change existing code - $expires = time() + 42; - $success = $storage->setAuthorizationCode('newcode', 'client ID2', 'SOMEOTHERID', 'http://example.org', $expires); - $this->assertTrue($success); - - $code = $storage->getAuthorizationCode('newcode'); - $this->assertNotNull($code); - $this->assertArrayHasKey('authorization_code', $code); - $this->assertArrayHasKey('client_id', $code); - $this->assertArrayHasKey('user_id', $code); - $this->assertArrayHasKey('redirect_uri', $code); - $this->assertArrayHasKey('expires', $code); - $this->assertEquals($code['authorization_code'], 'newcode'); - $this->assertEquals($code['client_id'], 'client ID2'); - $this->assertEquals($code['user_id'], 'SOMEOTHERID'); - $this->assertEquals($code['redirect_uri'], 'http://example.org'); - $this->assertEquals($code['expires'], $expires); - } - - /** @dataProvider provideStorage */ - public function testCheckUserCredentials($storage) - { - if (is_null($storage)) { - $this->markTestSkipped('Unable to load class Mongo_Client'); - return; - } - // create a new user for testing - $success = $storage->setUser('testusername', 'testpass', 'Test', 'User'); - $this->assertTrue($success); - - // correct credentials - $this->assertTrue($storage->checkUserCredentials('testusername', 'testpass')); - // invalid password - $this->assertFalse($storage->checkUserCredentials('testusername', 'fakepass')); - // invalid username - $this->assertFalse($storage->checkUserCredentials('fakeusername', 'testpass')); - - // invalid username - $this->assertFalse($storage->getUserDetails('fakeusername')); - - // ensure all properties are set - $user = $storage->getUserDetails('testusername'); - $this->assertTrue($user !== false); - $this->assertArrayHasKey('user_id', $user); - $this->assertArrayHasKey('first_name', $user); - $this->assertArrayHasKey('last_name', $user); - $this->assertEquals($user['user_id'], 'testusername'); - $this->assertEquals($user['first_name'], 'Test'); - $this->assertEquals($user['last_name'], 'User'); - } - - public function provideStorage() - { - $memory = Bootstrap::getInstance()->getMemoryStorage(); - $mysql = Bootstrap::getInstance()->getMysqlPdo(); - $sqlite = Bootstrap::getInstance()->getSqlitePdo(); - $mongo = Bootstrap::getInstance()->getMongo(); - $redis = Bootstrap::getInstance()->getRedisStorage(); - - // will add multiple storage types later - return array( - array($memory), - array($sqlite), - array($mysql), - array($mongo), - array($redis) - ); - } -} diff --git a/test/config/keys/id_rsa b/test/config/keys/id_rsa new file mode 100644 index 0000000..e8b9eff --- /dev/null +++ b/test/config/keys/id_rsa @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLsNjP+uAt2eO0cc5J9H5XV +8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdwizIum8j0KzpsGYH5qReN +QDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJuBe+FQpZTs8DewwIDAQAB +AoGARfNxNknmtx/n1bskZ/01iZRzAge6BLEE0LV6Q4gS7mkRZu/Oyiv39Sl5vUlA ++WdGxLjkBwKNjxGN8Vxw9/ASd8rSsqeAUYIwAeifXrHhj5DBPQT/pDPkeFnp9B1w +C6jo+3AbBQ4/b0ONSIEnCL2xGGglSIAxO17T1ViXp7lzXPECQQDe63nkRdWM0OCb +oaHQPT3E26224maIstrGFUdt9yw3yJf4bOF7TtiPLlLuHsTTge3z+fG6ntC0xG56 +1cl37C3ZAkEA2HdVcRGugNp/qmVz4LJTpD+WZKi73PLAO47wDOrYh9Pn2I6fcEH0 +CPnggt1ko4ujvGzFTvRH64HXa6aPCv1j+wJBAMQMah3VQPNf/DlDVFEUmw9XeBZg +VHaifX851aEjgXLp6qVj9IYCmLiLsAmVa9rr6P7p8asD418nZlaHUHE0eDkCQQCr +uxis6GMx1Ka971jcJX2X696LoxXPd0KsvXySMupv79yagKPa8mgBiwPjrnK+EPVo +cj6iochA/bSCshP/mwFrAkBHEKPi6V6gb94JinCT7x3weahbdp6bJ6/nzBH/p9VA +HoT1JtwNFhGv9BCjmDydshQHfSWpY9NxlccBKL7ITm8R +-----END RSA PRIVATE KEY-----
\ No newline at end of file diff --git a/test/config/keys/id_rsa.pub b/test/config/keys/id_rsa.pub new file mode 100644 index 0000000..1ac15f5 --- /dev/null +++ b/test/config/keys/id_rsa.pub @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICiDCCAfGgAwIBAgIBADANBgkqhkiG9w0BAQQFADA9MQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCVVQxITAfBgNVBAoTGFZpZ25ldHRlIENvcnBvcmF0aW9uIFNCWDAe +Fw0xMTEwMTUwMzE4MjdaFw0zMTEwMTAwMzE4MjdaMD0xCzAJBgNVBAYTAlVTMQsw +CQYDVQQIEwJVVDEhMB8GA1UEChMYVmlnbmV0dGUgQ29ycG9yYXRpb24gU0JYMIGf +MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLs +NjP+uAt2eO0cc5J9H5XV8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdw +izIum8j0KzpsGYH5qReNQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJu +Be+FQpZTs8DewwIDAQABo4GXMIGUMB0GA1UdDgQWBBRe8hrEXm+Yim4YlD5Nx+1K +vCYs9DBlBgNVHSMEXjBcgBRe8hrEXm+Yim4YlD5Nx+1KvCYs9KFBpD8wPTELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMSEwHwYDVQQKExhWaWduZXR0ZSBDb3Jwb3Jh +dGlvbiBTQliCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBjhyRD +lM7vnLn6drgQVftW5V9nDFAyPAuiGvMIKFSbiAf1PxXCRn5sfJquwWKsJUi4ZGNl +aViXdFmN6/F13PSM+yg63tpKy0fYqMbTM+Oe5WuSHkSW1VuYNHV+24adgNk/FRDL +FRrlM1f6s9VTLWvwGItjfrof0Ba8Uq7ZDSb9Xg== +-----END CERTIFICATE-----
\ No newline at end of file diff --git a/test/config/storage.json b/test/config/storage.json index db6f840..f2db151 100644 --- a/test/config/storage.json +++ b/test/config/storage.json @@ -48,6 +48,10 @@ "Test Client ID Empty Secret": { "client_secret": "" }, + "Client ID With User ID": { + "client_secret": "TestSecret", + "user_id": "brent@brentertainment.com" + }, "oauth_test_client": { "client_secret": "testpass", "grant_types": "implicit password" @@ -97,23 +101,32 @@ } }, "jwt": { - "Test Client ID": { - "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5/SxVlE8gnpFqCxgl2wjhzY7u\ncEi00s0kUg3xp7lVEvgLgYcAnHiWp+gtSjOFfH2zsvpiWm6Lz5f743j/FEzHIO1o\nwR0p4d9pOaJK07d01+RzoQLOIQAgXrr4T1CCWUesncwwPBVCyy2Mw3Nmhmr9MrF8\nUlvdRKBxriRnlP3qJQIDAQAB\n-----END PUBLIC KEY-----", - "subject": "testuser@ourdomain.com" - }, - "Test Client ID PHP-5.2": { - "key": "mysecretkey", - "subject": "testuser@ourdomain.com" - }, - "Missing Key Client": { - "key": null, - "subject": "testuser@ourdomain.com" - }, - "Missing Key Client PHP-5.2": { - "key": null, - "subject": "testuser@ourdomain.com" - } + "Test Client ID": { + "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5/SxVlE8gnpFqCxgl2wjhzY7u\ncEi00s0kUg3xp7lVEvgLgYcAnHiWp+gtSjOFfH2zsvpiWm6Lz5f743j/FEzHIO1o\nwR0p4d9pOaJK07d01+RzoQLOIQAgXrr4T1CCWUesncwwPBVCyy2Mw3Nmhmr9MrF8\nUlvdRKBxriRnlP3qJQIDAQAB\n-----END PUBLIC KEY-----", + "subject": "testuser@ourdomain.com" + }, + "Test Client ID PHP-5.2": { + "key": "mysecretkey", + "subject": "testuser@ourdomain.com" + }, + "Missing Key Client": { + "key": null, + "subject": "testuser@ourdomain.com" + }, + "Missing Key Client PHP-5.2": { + "key": null, + "subject": "testuser@ourdomain.com" + } }, + "jti": [ + { + "issuer": "Test Client ID", + "subject": "testuser@ourdomain.com", + "audience": "http://myapp.com/oauth/auth", + "expires": 99999999900, + "jti": "used_jti" + } + ], "client_supported_scopes" : { "Test Client ID" : ["clientscope1", "clientscope2"], "Test Client ID 2" : ["clientscope3"] @@ -125,6 +138,14 @@ "supported_scopes" : [ "scope1", "scope2", - "scope3" - ] + "scope3", + "supportedscope1", + "supportedscope2", + "supportedscope3", + "supportedscope4" + ], + "keys": { + "public_key": "-----BEGIN CERTIFICATE-----\nMIICiDCCAfGgAwIBAgIBADANBgkqhkiG9w0BAQQFADA9MQswCQYDVQQGEwJVUzEL\nMAkGA1UECBMCVVQxITAfBgNVBAoTGFZpZ25ldHRlIENvcnBvcmF0aW9uIFNCWDAe\nFw0xMTEwMTUwMzE4MjdaFw0zMTEwMTAwMzE4MjdaMD0xCzAJBgNVBAYTAlVTMQsw\nCQYDVQQIEwJVVDEhMB8GA1UEChMYVmlnbmV0dGUgQ29ycG9yYXRpb24gU0JYMIGf\nMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLs\nNjP+uAt2eO0cc5J9H5XV8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdw\nizIum8j0KzpsGYH5qReNQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJu\nBe+FQpZTs8DewwIDAQABo4GXMIGUMB0GA1UdDgQWBBRe8hrEXm+Yim4YlD5Nx+1K\nvCYs9DBlBgNVHSMEXjBcgBRe8hrEXm+Yim4YlD5Nx+1KvCYs9KFBpD8wPTELMAkG\nA1UEBhMCVVMxCzAJBgNVBAgTAlVUMSEwHwYDVQQKExhWaWduZXR0ZSBDb3Jwb3Jh\ndGlvbiBTQliCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBjhyRD\nlM7vnLn6drgQVftW5V9nDFAyPAuiGvMIKFSbiAf1PxXCRn5sfJquwWKsJUi4ZGNl\naViXdFmN6/F13PSM+yg63tpKy0fYqMbTM+Oe5WuSHkSW1VuYNHV+24adgNk/FRDL\nFRrlM1f6s9VTLWvwGItjfrof0Ba8Uq7ZDSb9Xg==\n-----END CERTIFICATE-----", + "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLsNjP+uAt2eO0cc5J9H5XV\n8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdwizIum8j0KzpsGYH5qReN\nQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJuBe+FQpZTs8DewwIDAQAB\nAoGARfNxNknmtx/n1bskZ/01iZRzAge6BLEE0LV6Q4gS7mkRZu/Oyiv39Sl5vUlA\n+WdGxLjkBwKNjxGN8Vxw9/ASd8rSsqeAUYIwAeifXrHhj5DBPQT/pDPkeFnp9B1w\nC6jo+3AbBQ4/b0ONSIEnCL2xGGglSIAxO17T1ViXp7lzXPECQQDe63nkRdWM0OCb\noaHQPT3E26224maIstrGFUdt9yw3yJf4bOF7TtiPLlLuHsTTge3z+fG6ntC0xG56\n1cl37C3ZAkEA2HdVcRGugNp/qmVz4LJTpD+WZKi73PLAO47wDOrYh9Pn2I6fcEH0\nCPnggt1ko4ujvGzFTvRH64HXa6aPCv1j+wJBAMQMah3VQPNf/DlDVFEUmw9XeBZg\nVHaifX851aEjgXLp6qVj9IYCmLiLsAmVa9rr6P7p8asD418nZlaHUHE0eDkCQQCr\nuxis6GMx1Ka971jcJX2X696LoxXPd0KsvXySMupv79yagKPa8mgBiwPjrnK+EPVo\ncj6iochA/bSCshP/mwFrAkBHEKPi6V6gb94JinCT7x3weahbdp6bJ6/nzBH/p9VA\nHoT1JtwNFhGv9BCjmDydshQHfSWpY9NxlccBKL7ITm8R\n-----END RSA PRIVATE KEY-----" + } }
\ No newline at end of file diff --git a/test/lib/OAuth2/Request/TestRequest.php b/test/lib/OAuth2/Request/TestRequest.php index aa471f2..5e0d081 100644 --- a/test/lib/OAuth2/Request/TestRequest.php +++ b/test/lib/OAuth2/Request/TestRequest.php @@ -58,6 +58,7 @@ class TestRequest implements RequestInterface { $request = new self(); $request->setPost($params); + return $request; } } diff --git a/test/lib/OAuth2/Storage/BaseTest.php b/test/lib/OAuth2/Storage/BaseTest.php new file mode 100644 index 0000000..2b09265 --- /dev/null +++ b/test/lib/OAuth2/Storage/BaseTest.php @@ -0,0 +1,29 @@ +<?php + +namespace OAuth2\Storage; + +abstract class BaseTest extends \PHPUnit_Framework_TestCase +{ + public function provideStorage() + { + $mysql = Bootstrap::getInstance()->getMysqlPdo(); + $sqlite = Bootstrap::getInstance()->getSqlitePdo(); + $mongo = Bootstrap::getInstance()->getMongo(); + $redis = Bootstrap::getInstance()->getRedisStorage(); + + /* hack until we can fix "default_scope" dependencies in other tests */ + // $memory = Bootstrap::getInstance()->getMemoryStorage(); + $memoryConfig = json_decode(file_get_contents(__DIR__.'/../../../config/storage.json'), true); + $memoryConfig['default_scope'] = 'defaultscope1 defaultscope2'; + $memory = new Memory($memoryConfig); + + // will add multiple storage types later + return array( + array($memory), + array($sqlite), + array($mysql), + array($mongo), + array($redis) + ); + } +} diff --git a/test/lib/OAuth2/Storage/Bootstrap.php b/test/lib/OAuth2/Storage/Bootstrap.php index 9b0839f..673a01a 100644 --- a/test/lib/OAuth2/Storage/Bootstrap.php +++ b/test/lib/OAuth2/Storage/Bootstrap.php @@ -8,12 +8,19 @@ class Bootstrap private $mysql; private $sqlite; private $mongo; + private $configDir; + + public function __construct() + { + $this->configDir = __DIR__.'/../../../config'; + } public static function getInstance() { if (!self::$instance) { self::$instance = new self(); } + return self::$instance; } @@ -27,12 +34,13 @@ class Bootstrap $this->sqlite = new Pdo($pdo); } + return $this->sqlite; } public function getMemoryStorage() { - return new Memory(json_decode(file_get_contents(dirname(__FILE__).'/../../../config/storage.json'), true)); + return new Memory(json_decode(file_get_contents($this->configDir. '/storage.json'), true)); } public function getRedisStorage() @@ -50,6 +58,7 @@ class Bootstrap $this->mysql = new Pdo($pdo); } + return $this->mysql; } @@ -66,22 +75,13 @@ class Bootstrap $this->mongo = new Mongo($db); } } + return $this->mongo; } private function createSqliteDb(\PDO $pdo) { - $pdo->exec('CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT, grant_types 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)'); - $pdo->exec('CREATE TABLE oauth_users (username TEXT, password TEXT, first_name TEXT, last_name TEXT)'); - $pdo->exec('CREATE TABLE oauth_refresh_tokens (refresh_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT)'); - - // test data - $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret, grant_types) VALUES ("oauth_test_client", "testpass", "implicit password")'); - $pdo->exec('INSERT INTO oauth_access_tokens (access_token, client_id) VALUES ("testtoken", "Some Client")'); - $pdo->exec('INSERT INTO oauth_authorization_codes (authorization_code, client_id) VALUES ("testcode", "Some Client")'); - $pdo->exec('INSERT INTO oauth_users (username, password) VALUES ("testuser", "password")'); + $this->runPdoSql($pdo); } private function removeSqliteDb() @@ -95,17 +95,45 @@ class Bootstrap { $pdo->exec('CREATE DATABASE oauth2_server_php'); $pdo->exec('USE oauth2_server_php'); - $pdo->exec('CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT, grant_types TEXT)'); + $this->runPdoSql($pdo); + } + + public function runPdoSql(\PDO $pdo) + { + $pdo->exec('CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT, grant_types TEXT, user_id TEXT)'); $pdo->exec('CREATE TABLE oauth_access_tokens (access_token TEXT, client_id TEXT, user_id TEXT, expires DATETIME, scope TEXT)'); $pdo->exec('CREATE TABLE oauth_authorization_codes (authorization_code TEXT, client_id TEXT, user_id TEXT, redirect_uri TEXT, expires DATETIME, scope TEXT)'); $pdo->exec('CREATE TABLE oauth_users (username TEXT, password TEXT, first_name TEXT, last_name TEXT)'); $pdo->exec('CREATE TABLE oauth_refresh_tokens (refresh_token TEXT, client_id TEXT, user_id TEXT, expires DATETIME, scope TEXT)'); - - // test data + $pdo->exec('CREATE TABLE oauth_scopes (type TEXT, scope TEXT, client_id TEXT)'); + $pdo->exec('CREATE TABLE oauth_public_keys (client_id TEXT, public_key TEXT, private_key TEXT, encryption_algorithm VARCHAR(100) DEFAULT "RS256")'); + + // set up scopes + $pdo->exec('INSERT INTO oauth_scopes (type, scope) VALUES ("supported", "supportedscope1 supportedscope2 supportedscope3 supportedscope4")'); + $pdo->exec('INSERT INTO oauth_scopes (type, scope) VALUES ("default", "defaultscope1 defaultscope2")'); + $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("supported", "clientscope1 clientscope2", "Test Client ID")'); + $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("default", "clientscope1 clientscope2", "Test Client ID")'); + $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("supported", "clientscope1 clientscope2 clientscope3", "Test Client ID 2")'); + $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("default", "clientscope1 clientscope2", "Test Client ID 2")'); + $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("supported", "clientscope1 clientscope2", "Test Default Scope Client ID")'); + $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("default", "clientscope1 clientscope2", "Test Default Scope Client ID")'); + $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("supported", "clientscope1 clientscope2 clientscope3", "Test Default Scope Client ID 2")'); + $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("default", "clientscope3", "Test Default Scope Client ID 2")'); + + // set up clients + $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret) VALUES ("Test Client ID", "TestSecret")'); + $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret) VALUES ("Test Client ID 2", "TestSecret")'); + $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret) VALUES ("Test Default Scope Client ID", "TestSecret")'); + $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret) VALUES ("Test Default Scope Client ID 2", "TestSecret")'); $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret, grant_types) VALUES ("oauth_test_client", "testpass", "implicit password")'); + + // set up misc $pdo->exec('INSERT INTO oauth_access_tokens (access_token, client_id) VALUES ("testtoken", "Some Client")'); $pdo->exec('INSERT INTO oauth_authorization_codes (authorization_code, client_id) VALUES ("testcode", "Some Client")'); $pdo->exec('INSERT INTO oauth_users (username, password) VALUES ("testuser", "password")'); + $pdo->exec('INSERT INTO oauth_public_keys (client_id, public_key, private_key, encryption_algorithm) VALUES ("ClientID_One", "client_1_public", "client_1_private", "RS256")'); + $pdo->exec('INSERT INTO oauth_public_keys (client_id, public_key, private_key, encryption_algorithm) VALUES ("ClientID_Two", "client_2_public", "client_2_private", "RS256")'); + $pdo->exec(sprintf('INSERT INTO oauth_public_keys (client_id, public_key, private_key, encryption_algorithm) VALUES (NULL, "%s", "%s", "RS256")', file_get_contents($this->configDir.'/keys/id_rsa.pub'), file_get_contents($this->configDir.'/keys/id_rsa'))); } public function removeMysqlDb(\PDO $pdo) @@ -113,9 +141,14 @@ class Bootstrap $pdo->exec('DROP DATABASE IF EXISTS oauth2_server_php'); } - private function getSqliteDir() + public function getSqliteDir() + { + return $this->configDir. '/test.sqlite'; + } + + public function getConfigDir() { - return dirname(__FILE__).'/../../../config/test.sqlite'; + return $this->configDir; } private function createMongoDb(\MongoDB $db) diff --git a/test/lib/OAuth2/Storage/MockRedisClient.php b/test/lib/OAuth2/Storage/MockRedisClient.php index 75dde33..dbfe264 100644 --- a/test/lib/OAuth2/Storage/MockRedisClient.php +++ b/test/lib/OAuth2/Storage/MockRedisClient.php @@ -13,6 +13,22 @@ class MockRedisClient 'redirect_uri' => '', 'grant_types' => 'implicit password', ), + 'oauth_clients:Test Client ID' => array( + 'client_id' => 'Test Client ID', + 'client_secret' => "TestSecret" + ), + 'oauth_clients:Test Client ID 2' => array( + 'client_id' => 'Test Client ID 2', + 'client_secret' => "TestSecret", + ), + 'oauth_clients:Test Default Scope Client ID' => array( + 'client_id' => 'Test Default Scope Client ID', + 'client_secret' => "TestSecret", + ), + 'oauth_clients:Test Default Scope Client ID 2' => array( + 'client_id' => 'Test Default Scope Client ID 2', + 'client_secret' => "TestSecret", + ), 'oauth_access_tokens:testtoken' => array( 'access_token' => 'testtoken', 'client_id' => "Some Client", @@ -33,35 +49,48 @@ class MockRedisClient 'password' => "password", 'first_name' => '', 'last_name' => '' - ) + ), + 'oauth_scopes:supported:global' => 'supportedscope1 supportedscope2 supportedscope3 supportedscope4', + 'oauth_scopes:default:global' => 'defaultscope1 defaultscope2', + 'oauth_scopes:supported:Test Client ID' => 'clientscope1 clientscope2', + 'oauth_scopes:default:Test Client ID' => 'clientscope1 clientscope2', + 'oauth_scopes:supported:Test Client ID 2' => 'clientscope3', + 'oauth_scopes:default:Test Client ID 2' => 'clientscope3', + 'oauth_scopes:supported:Test Default Scope Client ID' => 'clientscope1 clientscope2', + 'oauth_scopes:default:Test Default Scope Client ID' => 'clientscope1 clientscope2', + 'oauth_scopes:supported:Test Default Scope Client ID 2' => 'clientscope3', + 'oauth_scopes:default:Test Default Scope Client ID 2' => 'clientscope3', ); - foreach ( $data as $name => $val ) { + foreach ($data as $name => $val) { $data[$name] = json_encode($val); } $this->data = $data; } - function get($key) + public function get($key) { return isset($this->data[$key]) ? $this->data[$key] : null; } - function set ($key, $value) + public function set ($key, $value) { $this->data[$key] = $value; + return true; } - function setex($key, $expires, $value) + public function setex($key, $expires, $value) { $this->data[$key] = $value; + return true; } - function expire($key) + public function expire($key) { unset($this->data[$key]); + return true; } } |
