diff options
Diffstat (limited to 'src')
35 files changed, 786 insertions, 243 deletions
diff --git a/src/OAuth2/ClientAssertionType/HttpBasic.php b/src/OAuth2/ClientAssertionType/HttpBasic.php index 0e76154..0ecb7e1 100644 --- a/src/OAuth2/ClientAssertionType/HttpBasic.php +++ b/src/OAuth2/ClientAssertionType/HttpBasic.php @@ -19,16 +19,14 @@ class HttpBasic implements ClientAssertionTypeInterface protected $config; /** - * @param OAuth2\Storage\ClientCredentialsInterface $clientStorage - * REQUIRED Storage class for retrieving client credentials information - * @param array $config - * OPTIONAL Configuration options for the server - * @code - * $config = array( - * 'allow_credentials_in_request_body' => true, // whether to look for credentials in the POST body in addition to the Authorize HTTP Header - * 'allow_public_clients' => true // if true, "public clients" (clients without a secret) may be authenticated - * ); - * @endcode + * @param OAuth2\Storage\ClientCredentialsInterface $clientStorage REQUIRED Storage class for retrieving client credentials information + * @param array $config OPTIONAL Configuration options for the server + * <code> + * $config = array( + * 'allow_credentials_in_request_body' => true, // whether to look for credentials in the POST body in addition to the Authorize HTTP Header + * 'allow_public_clients' => true // if true, "public clients" (clients without a secret) may be authenticated + * ); + * </code> */ public function __construct(ClientCredentialsInterface $storage, array $config = array()) { @@ -110,6 +108,7 @@ class HttpBasic implements ClientAssertionTypeInterface * client_secret can be null if the client's password is an empty string * @see http://tools.ietf.org/html/rfc6749#section-2.3.1 */ + return array('client_id' => $request->request('client_id'), 'client_secret' => $request->request('client_secret')); } } diff --git a/src/OAuth2/Controller/AuthorizeController.php b/src/OAuth2/Controller/AuthorizeController.php index e669824..d57d38e 100644 --- a/src/OAuth2/Controller/AuthorizeController.php +++ b/src/OAuth2/Controller/AuthorizeController.php @@ -25,23 +25,19 @@ class AuthorizeController implements AuthorizeControllerInterface protected $scopeUtil; /** - * @param OAuth2\Storage\ClientInterface $clientStorage - * REQUIRED Instance of OAuth2\Storage\ClientInterface to retrieve client information - * @param array $responseTypes - * OPTIONAL Array of OAuth2\ResponseType\ResponseTypeInterface objects. Valid array - * keys are "code" and "token" - * @param array $config - * OPTIONAL Configuration options for the server - * @code - * $config = array( - * 'allow_implicit' => false, // if the controller should allow the "implicit" grant type - * 'enforce_state' => true // if the controller should require the "state" parameter - * 'require_exact_redirect_uri' => true, // if the controller should require an exact match on the "redirect_uri" parameter - * 'redirect_status_code' => 302, // HTTP status code to use for redirect responses - * ); - * @endcode - * @param OAuth2\ScopeInterface $scopeUtil - * OPTIONAL Instance of OAuth2\ScopeInterface to validate the requested scope + * @param OAuth2\Storage\ClientInterface $clientStorage REQUIRED Instance of OAuth2\Storage\ClientInterface to retrieve client information + * @param array $responseTypes OPTIONAL Array of OAuth2\ResponseType\ResponseTypeInterface objects. Valid array + * keys are "code" and "token" + * @param array $config OPTIONAL Configuration options for the server + * <code> + * $config = array( + * 'allow_implicit' => false, // if the controller should allow the "implicit" grant type + * 'enforce_state' => true // if the controller should require the "state" parameter + * 'require_exact_redirect_uri' => true, // if the controller should require an exact match on the "redirect_uri" parameter + * 'redirect_status_code' => 302, // HTTP status code to use for redirect responses + * ); + * </code> + * @param OAuth2\ScopeInterface $scopeUtil OPTIONAL Instance of OAuth2\ScopeInterface to validate the requested scope */ public function __construct(ClientInterface $clientStorage, array $responseTypes = array(), array $config = array(), ScopeInterface $scopeUtil = null) { @@ -81,7 +77,7 @@ class AuthorizeController implements AuthorizeControllerInterface // the user declined access to the client's application if ($is_authorized === false) { $redirect_uri = $this->redirect_uri ?: $registered_redirect_uri; - $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->state, 'access_denied', "The user denied access to your application"); + $this->setNotAuthorizedResponse($request, $response, $redirect_uri, $user_id); return; } @@ -105,6 +101,13 @@ class AuthorizeController implements AuthorizeControllerInterface $response->setRedirect($this->config['redirect_status_code'], $uri); } + protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null) + { + $error = 'access_denied'; + $error_message = 'The user denied access to your application'; + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->state, $error, $error_message); + } + /* * We have made this protected so this class can be extended to add/modify * these parameters @@ -264,10 +267,8 @@ class AuthorizeController implements AuthorizeControllerInterface /** * Build the absolute URI based on supplied URI and parameters. * - * @param $uri - * An absolute URI. - * @param $params - * Parameters to be append as GET. + * @param $uri An absolute URI. + * @param $params Parameters to be append as GET. * * @return * An absolute URI with supplied parameters. @@ -311,11 +312,9 @@ class AuthorizeController implements AuthorizeControllerInterface /** * Internal method for validating redirect URI supplied * - * @param string $inputUri - * The submitted URI to be validated - * @param string $registeredUriString - * The allowed URI(s) to validate against. Can be a space-delimited string of URIs to - * allow for multiple URIs + * @param string $inputUri The submitted URI to be validated + * @param string $registeredUriString The allowed URI(s) to validate against. Can be a space-delimited string of URIs to + * allow for multiple URIs * * @see http://tools.ietf.org/html/rfc6749#section-3.1.2 */ diff --git a/src/OAuth2/Controller/ResourceController.php b/src/OAuth2/Controller/ResourceController.php index 9623b9f..e858818 100644 --- a/src/OAuth2/Controller/ResourceController.php +++ b/src/OAuth2/Controller/ResourceController.php @@ -81,9 +81,9 @@ class ResourceController implements ResourceControllerInterface if (!$token = $this->tokenStorage->getAccessToken($token_param)) { $response->setError(401, 'invalid_token', 'The access token provided is invalid'); } elseif (!isset($token["expires"]) || !isset($token["client_id"])) { - $response->setError(401, 'invalid_token', 'Malformed token (missing "expires")'); + $response->setError(401, 'malformed_token', 'Malformed token (missing "expires")'); } elseif (time() > $token["expires"]) { - $response->setError(401, 'invalid_token', 'The access token provided has expired'); + $response->setError(401, 'expired_token', 'The access token provided has expired'); } else { return $token; } diff --git a/src/OAuth2/GrantType/AuthorizationCode.php b/src/OAuth2/GrantType/AuthorizationCode.php index 69f291e..e899520 100644 --- a/src/OAuth2/GrantType/AuthorizationCode.php +++ b/src/OAuth2/GrantType/AuthorizationCode.php @@ -17,8 +17,7 @@ class AuthorizationCode implements GrantTypeInterface protected $authCode; /** - * @param OAuth2\Storage\AuthorizationCodeInterface $storage - * REQUIRED Storage class for retrieving authorization code information + * @param OAuth2\Storage\AuthorizationCodeInterface $storage REQUIRED Storage class for retrieving authorization code information */ public function __construct(AuthorizationCodeInterface $storage) { diff --git a/src/OAuth2/GrantType/JwtBearer.php b/src/OAuth2/GrantType/JwtBearer.php index 534feae..ebc5d5a 100644 --- a/src/OAuth2/GrantType/JwtBearer.php +++ b/src/OAuth2/GrantType/JwtBearer.php @@ -29,12 +29,9 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface /** * Creates an instance of the JWT bearer grant type. * - * @param OAuth2\Storage\JWTBearerInterface $storage - * A valid storage interface that implements storage hooks for the JWT bearer grant type. - * @param string $audience - * The audience to validate the token against. This is usually the full URI of the OAuth token requests endpoint. - * @param OAuth2\Encryption\JWT OPTIONAL $jwtUtil - * The class used to decode, encode and verify JWTs. + * @param OAuth2\Storage\JWTBearerInterface $storage A valid storage interface that implements storage hooks for the JWT bearer grant type. + * @param string $audience The audience to validate the token against. This is usually the full URI of the OAuth token requests endpoint. + * @param OAuth2\Encryption\JWT $jwtUtil OPTONAL The class used to decode, encode and verify JWTs. */ public function __construct(JwtBearerInterface $storage, $audience, EncryptionInterface $jwtUtil = null) { diff --git a/src/OAuth2/GrantType/RefreshToken.php b/src/OAuth2/GrantType/RefreshToken.php index 71b1bdb..d6691df 100644 --- a/src/OAuth2/GrantType/RefreshToken.php +++ b/src/OAuth2/GrantType/RefreshToken.php @@ -19,15 +19,13 @@ class RefreshToken implements GrantTypeInterface protected $config; /** - * @param OAuth2\Storage\RefreshTokenInterface $storage - * REQUIRED Storage class for retrieving refresh token information - * @param array $config - * OPTIONAL Configuration options for the server - * @code - * $config = array( - * 'always_issue_new_refresh_token' => true, // whether to issue a new refresh token upon successful token request - * ); - * @endcode + * @param OAuth2\Storage\RefreshTokenInterface $storage REQUIRED Storage class for retrieving refresh token information + * @param array $config OPTIONAL Configuration options for the server + * <code> + * $config = array( + * 'always_issue_new_refresh_token' => true, // whether to issue a new refresh token upon successful token request + * ); + * </code> */ public function __construct(RefreshTokenInterface $storage, $config = array()) { @@ -80,7 +78,7 @@ class RefreshToken implements GrantTypeInterface public function getScope() { - return $this->refreshToken['scope']; + return isset($this->refreshToken['scope']) ? $this->refreshToken['scope'] : null; } public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) diff --git a/src/OAuth2/GrantType/UserCredentials.php b/src/OAuth2/GrantType/UserCredentials.php index 5d72aaf..f165538 100644 --- a/src/OAuth2/GrantType/UserCredentials.php +++ b/src/OAuth2/GrantType/UserCredentials.php @@ -18,8 +18,7 @@ class UserCredentials implements GrantTypeInterface protected $storage; /** - * @param OAuth2\Storage\UserCredentialsInterface $storage - * REQUIRED Storage class for retrieving user credentials information + * @param OAuth2\Storage\UserCredentialsInterface $storage REQUIRED Storage class for retrieving user credentials information */ public function __construct(UserCredentialsInterface $storage) { diff --git a/src/OAuth2/OpenID/Controller/AuthorizeController.php b/src/OAuth2/OpenID/Controller/AuthorizeController.php index 4850ebe..c6fe4f9 100644 --- a/src/OAuth2/OpenID/Controller/AuthorizeController.php +++ b/src/OAuth2/OpenID/Controller/AuthorizeController.php @@ -13,6 +13,25 @@ class AuthorizeController extends BaseAuthorizeController implements AuthorizeCo { private $nonce; + protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null) + { + $prompt = $request->query('prompt', 'consent'); + if ($prompt == 'none') { + if (is_null($user_id)) { + $error = 'login_required'; + $error_message = 'The user must log in'; + } else { + $error = 'interaction_required'; + $error_message = 'The user must grant access to your application'; + } + } else { + $error = 'consent_required'; + $error_message = 'The user denied access to your application'; + } + + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->getState(), $error, $error_message); + } + protected function buildAuthorizeParameters($request, $response, $user_id) { if (!$params = parent::buildAuthorizeParameters($request, $response, $user_id)) { diff --git a/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php b/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php index af47cd0..62704e7 100644 --- a/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php +++ b/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php @@ -2,9 +2,6 @@ namespace OAuth2\OpenID\Controller; -use OAuth2\RequestInterface; -use OAuth2\ResponseInterface; - interface AuthorizeControllerInterface { const RESPONSE_TYPE_ID_TOKEN = 'id_token'; diff --git a/src/OAuth2/OpenID/Controller/UserInfoController.php b/src/OAuth2/OpenID/Controller/UserInfoController.php index 28e429e..30cb942 100644 --- a/src/OAuth2/OpenID/Controller/UserInfoController.php +++ b/src/OAuth2/OpenID/Controller/UserInfoController.php @@ -2,6 +2,7 @@ namespace OAuth2\OpenID\Controller; +use OAuth2\Scope; use OAuth2\TokenType\TokenTypeInterface; use OAuth2\Storage\AccessTokenInterface; use OAuth2\OpenID\Storage\UserClaimsInterface; @@ -52,6 +53,6 @@ class UserInfoController extends ResourceController implements UserInfoControlle $claims += array( 'sub' => $token['user_id'], ); - $response->setParameters($claims); + $response->addParameters($claims); } } diff --git a/src/OAuth2/OpenID/GrantType/AuthorizationCode.php b/src/OAuth2/OpenID/GrantType/AuthorizationCode.php index 4a5da01..8ed1edc 100644 --- a/src/OAuth2/OpenID/GrantType/AuthorizationCode.php +++ b/src/OAuth2/OpenID/GrantType/AuthorizationCode.php @@ -3,10 +3,7 @@ namespace OAuth2\OpenID\GrantType; use OAuth2\GrantType\AuthorizationCode as BaseAuthorizationCode; -use OAuth2\Storage\AuthorizationCodeInterface; use OAuth2\ResponseType\AccessTokenInterface; -use OAuth2\RequestInterface; -use OAuth2\ResponseInterface; /** * diff --git a/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php b/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php index 7b8de7c..ea47792 100644 --- a/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php +++ b/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php @@ -13,17 +13,12 @@ interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface /** * Handle the creation of the authorization code. * - * @param $client_id - * Client identifier related to the authorization code - * @param $user_id - * User ID associated with the authorization code - * @param $redirect_uri - * An absolute URI to which the authorization server will redirect the - * user-agent to when the end-user authorization step is completed. - * @param $scope - * (optional) Scopes to be stored in space-separated string. - * @param $id_token - * (optional) The OpenID Connect id_token. + * @param $client_id Client identifier related to the authorization code + * @param $user_id User ID associated with the authorization code + * @param $redirect_uri An absolute URI to which the authorization server will redirect the + * user-agent to when the end-user authorization step is completed. + * @param $scope OPTIONAL Scopes to be stored in space-separated string. + * @param $id_token OPTIONAL The OpenID Connect id_token. * * @see http://tools.ietf.org/html/rfc6749#section-4 * @ingroup oauth2_section_4 diff --git a/src/OAuth2/OpenID/ResponseType/IdToken.php b/src/OAuth2/OpenID/ResponseType/IdToken.php index 8d907bc..4696155 100644 --- a/src/OAuth2/OpenID/ResponseType/IdToken.php +++ b/src/OAuth2/OpenID/ResponseType/IdToken.php @@ -4,7 +4,6 @@ namespace OAuth2\OpenID\ResponseType; use OAuth2\Encryption\EncryptionInterface; use OAuth2\Encryption\Jwt; -use OAuth2\Storage\RefreshTokenInterface; use OAuth2\Storage\PublicKeyInterface; use OAuth2\OpenID\Storage\UserClaimsInterface; diff --git a/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php b/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php index 3dfb57e..0bd2f83 100644 --- a/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php +++ b/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php @@ -15,10 +15,10 @@ interface IdTokenInterface extends ResponseTypeInterface * If the Implicit Flow is used, the token and id_token are generated and * returned together. * - * @param string $client_id The client id. - * @param string $user_id The user id. - * @param string $nonce OPTIONAL The nonce. - * @param string $userClaims OPTIONAL Claims about the user. + * @param string $client_id The client id. + * @param string $user_id The user id. + * @param string $nonce OPTIONAL The nonce. + * @param string $userClaims OPTIONAL Claims about the user. * @param string $access_token OPTIONAL The access token, if known. * * @return string The ID Token represented as a JSON Web Token (JWT). diff --git a/src/OAuth2/OpenID/ResponseType/TokenIdToken.php b/src/OAuth2/OpenID/ResponseType/TokenIdToken.php index 67f2a4a..5567569 100644 --- a/src/OAuth2/OpenID/ResponseType/TokenIdToken.php +++ b/src/OAuth2/OpenID/ResponseType/TokenIdToken.php @@ -3,7 +3,6 @@ namespace OAuth2\OpenID\ResponseType; use OAuth2\ResponseType\AccessTokenInterface; -use OAuth2\ResponseType\ResponseTypeInterface; class TokenIdToken implements TokenIdTokenInterface { diff --git a/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php b/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php index 3c6d00b..51dd867 100644 --- a/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php +++ b/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php @@ -23,20 +23,13 @@ interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface * * Required for OAuth2::GRANT_TYPE_AUTH_CODE. * - * @param $code - * Authorization code to be stored. - * @param $client_id - * Client identifier to be stored. - * @param $user_id - * User identifier to be stored. - * @param string $redirect_uri - * Redirect URI(s) to be stored in a space-separated string. - * @param int $expires - * Expiration to be stored as a Unix timestamp. - * @param string $scope - * (optional) Scopes to be stored in space-separated string. - * @param string $id_token - * (optional) The OpenID Connect id_token. + * @param $code authorization code to be stored. + * @param $client_id client identifier to be stored. + * @param $user_id user identifier to be stored. + * @param string $redirect_uri redirect URI(s) to be stored in a space-separated string. + * @param int $expires expiration to be stored as a Unix timestamp. + * @param string $scope OPTIONAL scopes to be stored in space-separated string. + * @param string $id_token OPTIONAL the OpenID Connect id_token. * * @ingroup oauth2_section_4 */ diff --git a/src/OAuth2/Request.php b/src/OAuth2/Request.php index b37c591..faec0ce 100644 --- a/src/OAuth2/Request.php +++ b/src/OAuth2/Request.php @@ -82,6 +82,7 @@ class Request implements RequestInterface { $headers = array_change_key_case($this->headers); $name = strtolower($name); + return isset($headers[$name]) ? $headers[$name] : $default; } diff --git a/src/OAuth2/Response.php b/src/OAuth2/Response.php index d746789..d8eabe7 100644 --- a/src/OAuth2/Response.php +++ b/src/OAuth2/Response.php @@ -181,13 +181,13 @@ class Response implements ResponseInterface // this only works for single-level arrays $xml = new \SimpleXMLElement('<response/>'); foreach ($this->parameters as $key => $param) { - $xml->addChild($param, $key); + $xml->addChild($key, $param); } return $xml->asXML(); } - throw new \InvalidArgumentException(sprintf('The format %s is not supported')); + throw new \InvalidArgumentException(sprintf('The format %s is not supported', $format)); } @@ -243,7 +243,7 @@ class Response implements ResponseInterface } } - public function setRedirect($statusCode = 302, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null) + public function setRedirect($statusCode, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null) { if (empty($url)) { throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); diff --git a/src/OAuth2/ResponseInterface.php b/src/OAuth2/ResponseInterface.php index 842002b..c99b5f7 100644 --- a/src/OAuth2/ResponseInterface.php +++ b/src/OAuth2/ResponseInterface.php @@ -18,7 +18,7 @@ interface ResponseInterface public function setError($statusCode, $name, $description = null, $uri = null); - public function setRedirect($statusCode = 302, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null); + public function setRedirect($statusCode, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null); public function getParameter($name); } diff --git a/src/OAuth2/ResponseType/AccessToken.php b/src/OAuth2/ResponseType/AccessToken.php index 0fb1121..b05bf87 100644 --- a/src/OAuth2/ResponseType/AccessToken.php +++ b/src/OAuth2/ResponseType/AccessToken.php @@ -16,19 +16,16 @@ class AccessToken implements AccessTokenInterface protected $config; /** - * @param OAuth2\Storage\AccessTokenInterface $tokenStorage - * REQUIRED Storage class for saving access token information - * @param OAuth2\Storage\RefreshTokenInterface $refreshStorage - * OPTIONAL Storage class for saving refresh token information - * @param array $config - * OPTIONAL Configuration options for the server - * @code - * $config = array( - * 'token_type' => 'bearer', // token type identifier - * 'access_lifetime' => 3600, // time before access token expires - * 'refresh_token_lifetime' => 1209600, // time before refresh token expires - * ); - * @endcode + * @param OAuth2\Storage\AccessTokenInterface $tokenStorage REQUIRED Storage class for saving access token information + * @param OAuth2\Storage\RefreshTokenInterface $refreshStorage OPTIONAL Storage class for saving refresh token information + * @param array $config OPTIONAL Configuration options for the server + * <code> + * $config = array( + * 'token_type' => 'bearer', // token type identifier + * 'access_lifetime' => 3600, // time before access token expires + * 'refresh_token_lifetime' => 1209600, // time before refresh token expires + * ); + * </endcode> */ public function __construct(AccessTokenStorageInterface $tokenStorage, RefreshTokenInterface $refreshStorage = null, array $config = array()) { @@ -67,14 +64,10 @@ class AccessToken implements AccessTokenInterface /** * Handle the creation of access token, also issue refresh token if supported / desirable. * - * @param $client_id - * Client identifier related to the access token. - * @param $user_id - * User ID associated with the access token - * @param $scope - * (optional) Scopes to be stored in space-separated string. - * @param bool $includeRefreshToken - * If true, a new refresh_token will be added to the response + * @param $client_id client identifier related to the access token. + * @param $user_id user ID associated with the access token + * @param $scope OPTIONAL scopes to be stored in space-separated string. + * @param bool $includeRefreshToken if true, a new refresh_token will be added to the response * * @see http://tools.ietf.org/html/rfc6749#section-5 * @ingroup oauth2_section_5 @@ -124,9 +117,9 @@ class AccessToken implements AccessTokenInterface $tokenLen = 40; if (function_exists('mcrypt_create_iv')) { $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM); - } else if (function_exists('openssl_random_pseudo_bytes')) { + } elseif (function_exists('openssl_random_pseudo_bytes')) { $randomData = openssl_random_pseudo_bytes(100); - } else if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data + } elseif (@file_exists('/dev/urandom')) { // Get 100 bytes of random data $randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true); } else { $randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true); diff --git a/src/OAuth2/ResponseType/AccessTokenInterface.php b/src/OAuth2/ResponseType/AccessTokenInterface.php index 9190327..2ba4a4f 100644 --- a/src/OAuth2/ResponseType/AccessTokenInterface.php +++ b/src/OAuth2/ResponseType/AccessTokenInterface.php @@ -11,14 +11,10 @@ interface AccessTokenInterface extends ResponseTypeInterface /** * Handle the creation of access token, also issue refresh token if supported / desirable. * - * @param $client_id - * Client identifier related to the access token. - * @param $user_id - * User ID associated with the access token - * @param $scope - * (optional) Scopes to be stored in space-separated string. - * @param bool $includeRefreshToken - * If true, a new refresh_token will be added to the response + * @param $client_id client identifier related to the access token. + * @param $user_id user ID associated with the access token + * @param $scope OPTONAL scopes to be stored in space-separated string. + * @param bool $includeRefreshToken if true, a new refresh_token will be added to the response * * @see http://tools.ietf.org/html/rfc6749#section-5 * @ingroup oauth2_section_5 diff --git a/src/OAuth2/ResponseType/AuthorizationCode.php b/src/OAuth2/ResponseType/AuthorizationCode.php index 51bd437..6a305fd 100644 --- a/src/OAuth2/ResponseType/AuthorizationCode.php +++ b/src/OAuth2/ResponseType/AuthorizationCode.php @@ -87,9 +87,9 @@ class AuthorizationCode implements AuthorizationCodeInterface $tokenLen = 40; if (function_exists('mcrypt_create_iv')) { $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM); - } else if (function_exists('openssl_random_pseudo_bytes')) { + } elseif (function_exists('openssl_random_pseudo_bytes')) { $randomData = openssl_random_pseudo_bytes(100); - } else if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data + } elseif (@file_exists('/dev/urandom')) { // Get 100 bytes of random data $randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true); } else { $randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true); diff --git a/src/OAuth2/ResponseType/AuthorizationCodeInterface.php b/src/OAuth2/ResponseType/AuthorizationCodeInterface.php index 9d5b90f..df777e2 100644 --- a/src/OAuth2/ResponseType/AuthorizationCodeInterface.php +++ b/src/OAuth2/ResponseType/AuthorizationCodeInterface.php @@ -17,15 +17,11 @@ interface AuthorizationCodeInterface extends ResponseTypeInterface /** * Handle the creation of the authorization code. * - * @param $client_id - * Client identifier related to the authorization code - * @param $user_id - * User ID associated with the authorization code - * @param $redirect_uri - * An absolute URI to which the authorization server will redirect the - * user-agent to when the end-user authorization step is completed. - * @param $scope - * (optional) Scopes to be stored in space-separated string. + * @param $client_id client identifier related to the authorization code + * @param $user_id user id associated with the authorization code + * @param $redirect_uri an absolute URI to which the authorization server will redirect the + * user-agent to when the end-user authorization step is completed. + * @param $scope OPTIONAL scopes to be stored in space-separated string. * * @see http://tools.ietf.org/html/rfc6749#section-4 * @ingroup oauth2_section_4 diff --git a/src/OAuth2/ResponseType/CryptoToken.php b/src/OAuth2/ResponseType/CryptoToken.php index 3f5ca37..08ff853 100644 --- a/src/OAuth2/ResponseType/CryptoToken.php +++ b/src/OAuth2/ResponseType/CryptoToken.php @@ -51,7 +51,7 @@ class CryptoToken extends AccessToken * @param $scope * (optional) Scopes to be stored in space-separated string. * @param bool $includeRefreshToken - * If true, a new refresh_token will be added to the response + * If true, a new refresh_token will be added to the response * * @see http://tools.ietf.org/html/rfc6749#section-5 * @ingroup oauth2_section_5 @@ -70,21 +70,6 @@ class CryptoToken extends AccessToken ); /* - * Issue a refresh token also, if we support them - * - * Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface - * is supplied in the constructor - */ - if ($includeRefreshToken && $this->refreshStorage) { - $cryptoToken["refresh_token"] = $this->generateRefreshToken(); - $expires = 0; - if ($this->config['refresh_token_lifetime'] > 0) { - $expires = time() + $this->config['refresh_token_lifetime']; - } - $this->refreshStorage->setRefreshToken($cryptoToken['refresh_token'], $client_id, $user_id, $expires, $scope); - } - - /* * Encode the token data into a single access_token string */ $access_token = $this->encodeToken($cryptoToken, $client_id); @@ -105,6 +90,22 @@ class CryptoToken extends AccessToken 'scope' => $scope ); + /* + * Issue a refresh token also, if we support them + * + * Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface + * is supplied in the constructor + */ + if ($includeRefreshToken && $this->refreshStorage) { + $refresh_token = $this->generateRefreshToken(); + $expires = 0; + if ($this->config['refresh_token_lifetime'] > 0) { + $expires = time() + $this->config['refresh_token_lifetime']; + } + $this->refreshStorage->setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope); + $token['refresh_token'] = $refresh_token; + } + return $token; } diff --git a/src/OAuth2/Scope.php b/src/OAuth2/Scope.php index 5c792bb..c44350b 100644 --- a/src/OAuth2/Scope.php +++ b/src/OAuth2/Scope.php @@ -68,10 +68,10 @@ class Scope implements ScopeInterface $nonReservedScopes = array_diff($scope, $reservedScope); if (count($nonReservedScopes) == 0) { return true; - } - else { + } else { // Check the storage for non-reserved scopes. $nonReservedScopes = implode(' ', $nonReservedScopes); + return $this->storage->scopeExists($nonReservedScopes); } } diff --git a/src/OAuth2/Server.php b/src/OAuth2/Server.php index b138a55..3434a7b 100644 --- a/src/OAuth2/Server.php +++ b/src/OAuth2/Server.php @@ -83,22 +83,15 @@ class Server implements ResourceControllerInterface, ); /** - * @param mixed $storage - * array - array of Objects to implement storage - * OAuth2\Storage object implementing all required storage types (ClientCredentialsInterface and AccessTokenInterface as a minimum) - * @param array $config - * specify a different token lifetime, token header name, etc - * @param array $grantTypes - * An array of OAuth2\GrantType\GrantTypeInterface to use for granting access tokens - * @param array $responseTypes - * Response types to use. array keys should be "code" and and "token" for - * Access Token and Authorization Code response types - * @param OAuth2\TokenType\TokenTypeInterface $tokenType - * The token type object to use. Valid token types are "bearer" and "mac" - * @param OAuth2\ScopeInterface $scopeUtil - * The scope utility class to use to validate scope - * @param OAuth2\ClientAssertionType\ClientAssertionTypeInterface $clientAssertionType - * The method in which to verify the client identity. Default is HttpBasic + * @param mixed $storage (array or OAuth2\Storage) - single object or array of objects implementing the + * required storage types (ClientCredentialsInterface and AccessTokenInterface as a minimum) + * @param array $config specify a different token lifetime, token header name, etc + * @param array $grantTypes An array of OAuth2\GrantType\GrantTypeInterface to use for granting access tokens + * @param array $responseTypes Response types to use. array keys should be "code" and and "token" for + * Access Token and Authorization Code response types + * @param OAuth2\TokenType\TokenTypeInterface $tokenType The token type object to use. Valid token types are "bearer" and "mac" + * @param OAuth2\ScopeInterface $scopeUtil The scope utility class to use to validate scope + * @param OAuth2\ClientAssertionType\ClientAssertionTypeInterface $clientAssertionType The method in which to verify the client identity. Default is HttpBasic * * @ingroup oauth2_section_7 */ @@ -719,6 +712,7 @@ class Server implements ResourceControllerInterface, } $config = array_intersect_key($this->config, array_flip(explode(' ', 'issuer id_lifetime'))); + return new IdToken($this->storages['user_claims'], $this->storages['public_key'], $config); } diff --git a/src/OAuth2/Storage/AccessTokenInterface.php b/src/OAuth2/Storage/AccessTokenInterface.php index b55c3d6..d382f89 100644 --- a/src/OAuth2/Storage/AccessTokenInterface.php +++ b/src/OAuth2/Storage/AccessTokenInterface.php @@ -36,16 +36,11 @@ interface AccessTokenInterface * * We need to store access token data as we create and verify tokens. * - * @param $oauth_token - * oauth_token to be stored. - * @param $client_id - * Client identifier to be stored. - * @param $user_id - * User identifier to be stored. - * @param int $expires - * Expiration to be stored as a Unix timestamp. - * @param string $scope - * (optional) Scopes to be stored in space-separated string. + * @param $oauth_token oauth_token to be stored. + * @param $client_id client identifier to be stored. + * @param $user_id user identifier to be stored. + * @param int $expires expiration to be stored as a Unix timestamp. + * @param string $scope OPTIONAL Scopes to be stored in space-separated string. * * @ingroup oauth2_section_4 */ diff --git a/src/OAuth2/Storage/AuthorizationCodeInterface.php b/src/OAuth2/Storage/AuthorizationCodeInterface.php index d59a61f..3beb0e4 100644 --- a/src/OAuth2/Storage/AuthorizationCodeInterface.php +++ b/src/OAuth2/Storage/AuthorizationCodeInterface.php @@ -59,18 +59,12 @@ interface AuthorizationCodeInterface * * Required for OAuth2::GRANT_TYPE_AUTH_CODE. * - * @param $code - * Authorization code to be stored. - * @param $client_id - * Client identifier to be stored. - * @param $user_id - * User identifier to be stored. - * @param string $redirect_uri - * Redirect URI(s) to be stored in a space-separated string. - * @param int $expires - * Expiration to be stored as a Unix timestamp. - * @param string $scope - * (optional) Scopes to be stored in space-separated string. + * @param string $code Authorization code to be stored. + * @param mixed $client_id Client identifier to be stored. + * @param mixed $user_id User identifier to be stored. + * @param string $redirect_uri Redirect URI(s) to be stored in a space-separated string. + * @param int $expires Expiration to be stored as a Unix timestamp. + * @param string $scope OPTIONAL Scopes to be stored in space-separated string. * * @ingroup oauth2_section_4 */ diff --git a/src/OAuth2/Storage/Cassandra.php b/src/OAuth2/Storage/Cassandra.php index f6d1e05..9587b4c 100644 --- a/src/OAuth2/Storage/Cassandra.php +++ b/src/OAuth2/Storage/Cassandra.php @@ -50,7 +50,7 @@ class Cassandra implements AuthorizationCodeInterface, * Cassandra Storage! uses phpCassa * * @param \phpcassa\ConnectionPool $cassandra - * @param array $config + * @param array $config */ public function __construct($connection = array(), array $config = array()) { @@ -112,14 +112,14 @@ class Cassandra implements AuthorizationCodeInterface, $seconds = $expire - time(); // __data key set as C* requires a field, note: max TTL can only be 630720000 seconds $cf->insert($key, array('__data' => $str), null, $seconds); - } catch(\Exception $e) { + } catch (\Exception $e) { return false; } } else { try { // __data key set as C* requires a field $cf->insert($key, array('__data' => $str)); - } catch(\Exception $e) { + } catch (\Exception $e) { return false; } } @@ -135,7 +135,7 @@ class Cassandra implements AuthorizationCodeInterface, try { // __data key set as C* requires a field $cf->remove($key, array('__data')); - } catch(\Exception $e) { + } catch (\Exception $e) { return false; } @@ -364,4 +364,3 @@ class Cassandra implements AuthorizationCodeInterface, throw new \Exception('setJti() for the Cassandra driver is currently unimplemented.'); } } - diff --git a/src/OAuth2/Storage/ClientInterface.php b/src/OAuth2/Storage/ClientInterface.php index ec8d7fd..09a5bff 100644 --- a/src/OAuth2/Storage/ClientInterface.php +++ b/src/OAuth2/Storage/ClientInterface.php @@ -20,18 +20,18 @@ interface ClientInterface * Client identifier to be check with. * * @return array - * Client details. The only mandatory key in the array is "redirect_uri". - * This function MUST return FALSE if the given client does not exist or is - * invalid. "redirect_uri" can be space-delimited to allow for multiple valid uris. - * @code - * return array( - * "redirect_uri" => REDIRECT_URI, // REQUIRED redirect_uri registered for the client - * "client_id" => CLIENT_ID, // OPTIONAL the client id - * "grant_types" => GRANT_TYPES, // OPTIONAL an array of restricted grant types - * "user_id" => USER_ID, // OPTIONAL the user identifier associated with this client - * "scope" => SCOPE, // OPTIONAL the scopes allowed for this client - * ); - * @endcode + * Client details. The only mandatory key in the array is "redirect_uri". + * This function MUST return FALSE if the given client does not exist or is + * invalid. "redirect_uri" can be space-delimited to allow for multiple valid uris. + * <code> + * return array( + * "redirect_uri" => REDIRECT_URI, // REQUIRED redirect_uri registered for the client + * "client_id" => CLIENT_ID, // OPTIONAL the client id + * "grant_types" => GRANT_TYPES, // OPTIONAL an array of restricted grant types + * "user_id" => USER_ID, // OPTIONAL the user identifier associated with this client + * "scope" => SCOPE, // OPTIONAL the scopes allowed for this client + * ); + * </code> * * @ingroup oauth2_section_4 */ diff --git a/src/OAuth2/Storage/CryptoToken.php b/src/OAuth2/Storage/CryptoToken.php index f107e45..31ebb65 100644 --- a/src/OAuth2/Storage/CryptoToken.php +++ b/src/OAuth2/Storage/CryptoToken.php @@ -16,28 +16,11 @@ class CryptoToken implements CryptoTokenInterface protected $encryptionUtil; /** - * @param string $publicKey - * the public key encryption to use - * - * @param string $privateKey (optional) - * (optional) the private key to use to sign tokens - * this is only required for token granting, and can be omitted for resource servers, - * as only the publc key is required for crypto token verification - * - * @param array $config - * (optional) configuration array. Valid parameters are - * - encryption_algorithm - * the algorithm to use for encryption. This is passed to the - * EncryptionInterface object. - * @see OAuth2\Encryption\Jwt::verifySignature - * - * @param OAuth2\Storage\AccessTokenInterface $tokenStorage - * (optional) persist the access token to another storage. This is useful if - * you want to retain access token grant information somewhere, but - * is not necessary when using this grant type. - * - * @param OAuth2\Encryption\EncryptionInterface $encryptionUtil - * (optional) class to use for "encode" and "decode" functions. + * @param OAuth2\Encryption\PublicKeyInterface $publicKeyStorage the public key encryption to use + * @param OAuth2\Storage\AccessTokenInterface $tokenStorage OPTIONAL persist the access token to another storage. This is useful if + * you want to retain access token grant information somewhere, but + * is not necessary when using this grant type. + * @param OAuth2\Encryption\EncryptionInterface $encryptionUtil OPTIONAL class to use for "encode" and "decode" functions. */ public function __construct(PublicKeyInterface $publicKeyStorage, AccessTokenInterface $tokenStorage = null, EncryptionInterface $encryptionUtil = null) { diff --git a/src/OAuth2/Storage/DynamoDB.php b/src/OAuth2/Storage/DynamoDB.php new file mode 100644 index 0000000..fe06428 --- /dev/null +++ b/src/OAuth2/Storage/DynamoDB.php @@ -0,0 +1,518 @@ +<?php + +namespace OAuth2\Storage; + +use Aws\DynamoDb\DynamoDbClient; + +use OAuth2\OpenID\Storage\UserClaimsInterface; +use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface; +/** + * DynamoDB storage for all storage types + * + * To use, install "aws/aws-sdk-php" via composer + * <code> + * composer require aws/aws-sdk-php:dev-master + * </code> + * + * Once this is done, instantiate the DynamoDB client + * <code> + * $storage = new OAuth2\Storage\Dynamodb(array("key" => "YOURKEY", "secret" => "YOURSECRET", "region" => "YOURREGION")); + * </code> + * + * Table : + * - oauth_access_tokens (primary hash key : access_token) + * - oauth_authorization_codes (primary hash key : authorization_code) + * - oauth_clients (primary hash key : client_id) + * - oauth_jwt (primary hash key : client_id, primary range key : subject) + * - oauth_public_keys (primary hash key : client_id) + * - oauth_refresh_tokens (primary hash key : refresh_token) + * - oauth_scopes (primary hash key : scope, secondary index : is_default-index hash key is_default) + * - oauth_users (primary hash key : username) + * + * @author Frederic AUGUSTE <frederic.auguste at gmail dot com> + */ +class DynamoDB implements + AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + PublicKeyInterface, + UserClaimsInterface, + OpenIDAuthorizationCodeInterface +{ + protected $client; + protected $config; + + public function __construct($connection, $config = array()) + { + if (!($connection instanceof DynamoDbClient)) { + if (!is_array($connection)) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Dynamodb must be an instance a configuration array containt key, secret, region'); + } + if (!array_key_exists("key",$connection) || !array_key_exists("secret",$connection) || !array_key_exists("region",$connection) ) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Dynamodb must be an instance a configuration array containt key, secret, region'); + } + $this->client = DynamoDbClient::factory(array( + 'key' => $connection["key"], + 'secret' => $connection["secret"], + 'region' =>$connection["region"] + )); + } else { + $this->client = $connection; + } + + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'jwt_table' => 'oauth_jwt', + 'scope_table' => 'oauth_scopes', + 'public_key_table' => 'oauth_public_keys', + ), $config); + } + + /* OAuth2\Storage\ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['client_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + + return $result->count()==1 && $result["Item"]["client_secret"]["S"] == $client_secret; + } + + public function isPublicClient($client_id) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['client_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + + if ($result->count()==0) { + return false ; + } + + return empty($result["Item"]["client_secret"]); + } + + /* OAuth2\Storage\ClientInterface */ + public function getClientDetails($client_id) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['client_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return false ; + } + $result = $this->dynamo2array($result); + foreach (array('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id') as $key => $val) { + if (!array_key_exists ($val, $result)) { + $result[$val] = null; + } + } + + return $result; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + $clientData = compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id'); + $clientData = array_filter($clientData, function ($value) { return !is_null($value); }); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['client_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, (array) $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /* OAuth2\Storage\AccessTokenInterface */ + public function getAccessToken($access_token) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['access_token_table'], + "Key" => array('access_token' => array('S' => $access_token)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + if (array_key_exists ('expires', $token)) { + $token['expires'] = strtotime($token['expires']); + } + + return $token; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $clientData = compact('access_token', 'client_id', 'user_id', 'expires', 'scope'); + $clientData = array_filter($clientData, function ($value) { return !is_null($value); }); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['access_token_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + + } + + /* OAuth2\Storage\AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['code_table'], + "Key" => array('authorization_code' => array('S' => $code)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + if (!array_key_exists("id_token", $token )) { + $token['id_token'] = null; + } + $token['expires'] = strtotime($token['expires']); + + return $token; + + } + + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $clientData = compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'id_token', 'scope'); + $clientData = array_filter($clientData, function ($value) { return !is_null($value); }); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['code_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + } + + public function expireAuthorizationCode($code) + { + + $result = $this->client->deleteItem(array( + 'TableName' => $this->config['code_table'], + 'Key' => $this->client->formatAttributes(array("authorization_code" => $code)) + )); + + return true; + } + + /* OAuth2\Storage\UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + public function getUserDetails($username) + { + return $this->getUser($username); + } + + /* UserClaimsInterface */ + public function getUserClaims($user_id, $claims) + { + if (!$userDetails = $this->getUserDetails($user_id)) { + return false; + } + + $claims = explode(' ', trim($claims)); + $userClaims = array(); + + // for each requested claim, if the user has the claim, set it in the response + $validClaims = explode(' ', self::VALID_CLAIMS); + foreach ($validClaims as $validClaim) { + if (in_array($validClaim, $claims)) { + if ($validClaim == 'address') { + // address is an object with subfields + $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); + } else { + $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); + } + } + } + + return $userClaims; + } + + protected function getUserClaim($claim, $userDetails) + { + $userClaims = array(); + $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim))); + $claimValues = explode(' ', $claimValuesString); + + foreach ($claimValues as $value) { + if ($value == 'email_verified') { + $userClaims[$value] = $userDetails[$value]=='true' ? true : false; + } else { + $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null; + } + } + + return $userClaims; + } + + /* OAuth2\Storage\RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['refresh_token_table'], + "Key" => array('refresh_token' => array('S' => $refresh_token)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + $token['expires'] = strtotime($token['expires']); + + return $token; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $clientData = compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'); + $clientData = array_filter($clientData, function ($value) { return !is_null($value); }); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['refresh_token_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + } + + public function unsetRefreshToken($refresh_token) + { + $result = $this->client->deleteItem(array( + 'TableName' => $this->config['refresh_token_table'], + 'Key' => $this->client->formatAttributes(array("refresh_token" => $refresh_token)) + )); + + return true; + } + + // plaintext passwords are bad! Override this for your application + protected function checkPassword($user, $password) + { + return $user['password'] == sha1($password); + } + + public function getUser($username) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['user_table'], + "Key" => array('username' => array('S' => $username)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + $token['user_id'] = $username; + + return $token; + } + + public function setUser($username, $password, $first_name = null, $last_name = null) + { + // do not store in plaintext + $password = sha1($password); + + $clientData = compact('username', 'password', 'first_name', 'last_name'); + $clientData = array_filter($clientData, function ($value) { return !is_null($value); }); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['user_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + + } + + /* ScopeInterface */ + public function scopeExists($scope) + { + $scope = explode(' ', $scope); + $scope_query = array(); + $count = 0; + foreach ($scope as $key => $val) { + $result = $this->client->query(array( + 'TableName' => $this->config['scope_table'], + 'Select' => 'COUNT', + 'KeyConditions' => array( + 'scope' => array( + 'AttributeValueList' => array(array('S' => $val)), + 'ComparisonOperator' => 'EQ' + ) + ) + )); + $count += $result['Count']; + } + + return $count == count($scope); + } + + public function getDefaultScope($client_id = null) + { + + $result = $this->client->query(array( + 'TableName' => $this->config['scope_table'], + 'IndexName' => 'is_default-index', + 'Select' => 'ALL_ATTRIBUTES', + 'KeyConditions' => array( + 'is_default' => array( + 'AttributeValueList' => array(array('S' => 'true')), + 'ComparisonOperator' => 'EQ', + ), + ) + )); + $defaultScope = array(); + if ($result->count() > 0) { + $array = $result->toArray(); + foreach ($array["Items"] as $item) { + $defaultScope[] = $item['scope']['S']; + } + + return implode(' ', $defaultScope); + } + + return null; + } + + /* JWTBearerInterface */ + public function getClientKey($client_id, $subject) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['jwt_table'], + "Key" => array('client_id' => array('S' => $client_id), 'subject' => array('S' => $subject)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + + return $token['public_key']; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + //TODO not use. + } + + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + //TODO not use. + } + + /* PublicKeyInterface */ + public function getPublicKey($client_id = '0') + { + + $result = $this->client->getItem(array( + "TableName"=> $this->config['public_key_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + + return $token['public_key']; + + } + + public function getPrivateKey($client_id = '0') + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['public_key_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + + return $token['private_key']; + } + + public function getEncryptionAlgorithm($client_id = null) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['public_key_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return 'RS256' ; + } + $token = $this->dynamo2array($result); + + return $token['encryption_algorithm']; + } + + /** + * Transform dynamodb resultset to an array. + * @param $dynamodbResult + * @return $array + */ + private function dynamo2array($dynamodbResult) + { + $result = array(); + foreach ($dynamodbResult["Item"] as $key => $val) { + $result[$key] = $val["S"]; + $result[] = $val["S"]; + } + + return $result; + } +} diff --git a/src/OAuth2/Storage/Pdo.php b/src/OAuth2/Storage/Pdo.php index 0cdd52e..17016e7 100644 --- a/src/OAuth2/Storage/Pdo.php +++ b/src/OAuth2/Storage/Pdo.php @@ -4,6 +4,7 @@ namespace OAuth2\Storage; use OAuth2\OpenID\Storage\UserClaimsInterface; use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface; + /** * Simple PDO storage for all storage types * @@ -16,7 +17,8 @@ use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeI * * @author Brent Shaffer <bshafs at gmail dot com> */ -class Pdo implements AuthorizationCodeInterface, +class Pdo implements + AuthorizationCodeInterface, AccessTokenInterface, ClientCredentialsInterface, UserCredentialsInterface, @@ -242,7 +244,7 @@ class Pdo implements AuthorizationCodeInterface, // address is an object with subfields $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); } else { - $userClaims = array_merge($this->getUserClaim($validClaim, $userDetails)); + $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); } } } @@ -442,4 +444,84 @@ class Pdo implements AuthorizationCodeInterface, return 'RS256'; } + + /** + * DDL to create OAuth2 database and tables for PDO storage + * + * @see https://github.com/dsquier/oauth2-server-php-mysql + */ + public function getBuildSql($dbName = 'oauth2_server_php') + { + $sql = " + CREATE TABLE {$this->config['client_table']} ( + client_id VARCHAR(80) NOT NULL, + client_secret VARCHAR(80) NOT NULL, + redirect_uri VARCHAR(2000), + grant_types VARCHAR(80), + scope VARCHAR(4000), + user_id VARCHAR(80), + PRIMARY KEY (client_id) + ); + + CREATE TABLE {$this->config['access_token_table']} ( + access_token VARCHAR(40) NOT NULL, + client_id VARCHAR(80) NOT NULL, + user_id VARCHAR(80), + expires TIMESTAMP NOT NULL, + scope VARCHAR(4000), + PRIMARY KEY (access_token) + ); + + CREATE TABLE {$this->config['code_table']} ( + authorization_code VARCHAR(40) NOT NULL, + client_id VARCHAR(80) NOT NULL, + user_id VARCHAR(80), + redirect_uri VARCHAR(2000), + expires TIMESTAMP NOT NULL, + scope VARCHAR(4000), + id_token VARCHAR(1000), + PRIMARY KEY (authorization_code) + ); + + CREATE TABLE {$this->config['refresh_token_table']} ( + refresh_token VARCHAR(40) NOT NULL, + client_id VARCHAR(80) NOT NULL, + user_id VARCHAR(80), + expires TIMESTAMP NOT NULL, + scope VARCHAR(4000), + PRIMARY KEY (refresh_token) + ); + + CREATE TABLE {$this->config['user_table']} ( + username VARCHAR(80), + password VARCHAR(80), + first_name VARCHAR(80), + last_name VARCHAR(80), + email VARCHAR(80), + email_verified BOOLEAN, + scope VARCHAR(4000) + ); + + CREATE TABLE {$this->config['scope_table']} ( + scope VARCHAR(80) NOT NULL, + is_default BOOLEAN, + PRIMARY KEY (scope) + ); + + CREATE TABLE {$this->config['jwt_table']} ( + client_id VARCHAR(80) NOT NULL, + subject VARCHAR(80), + public_key VARCHAR(2000) NOT NULL + ); + + CREATE TABLE {$this->config['public_key_table']} ( + client_id VARCHAR(80), + public_key VARCHAR(2000), + private_key VARCHAR(2000), + encryption_algorithm VARCHAR(100) DEFAULT 'RS256' + ) +"; + + return $sql; + } } diff --git a/src/OAuth2/Storage/ScopeInterface.php b/src/OAuth2/Storage/ScopeInterface.php index e88ff7f..a829226 100644 --- a/src/OAuth2/Storage/ScopeInterface.php +++ b/src/OAuth2/Storage/ScopeInterface.php @@ -28,7 +28,7 @@ interface ScopeInterface * request_error is returned by the server to force a * scope request by the client. By returning "null", * opt out of requiring scopes - * + * * @param $client_id * An optional client id that can be used to return customized default scopes. * diff --git a/src/OAuth2/TokenType/Bearer.php b/src/OAuth2/TokenType/Bearer.php index 0672572..0ec9fec 100644 --- a/src/OAuth2/TokenType/Bearer.php +++ b/src/OAuth2/TokenType/Bearer.php @@ -35,7 +35,7 @@ class Bearer implements TokenTypeInterface $headers = $request->headers('AUTHORIZATION'); // check the header, then the querystring, then the request body - return !empty($headers) || (bool)($request->request($this->config['token_param_name'])) || (bool)($request->query($this->config['token_param_name'])); + return !empty($headers) || (bool) ($request->request($this->config['token_param_name'])) || (bool) ($request->query($this->config['token_param_name'])); } /** @@ -70,7 +70,7 @@ class Bearer implements TokenTypeInterface * * @see http://tools.ietf.org/html/rfc6750#section-3.1 */ - $methodsUsed = !empty($headers) + (bool)($request->query($this->config['token_param_name'])) + (bool)($request->request($this->config['token_param_name'])); + $methodsUsed = !empty($headers) + (bool) ($request->query($this->config['token_param_name'])) + (bool) ($request->request($this->config['token_param_name'])); if ($methodsUsed > 1) { $response->setError(400, 'invalid_request', 'Only one method may be used to authenticate at a time (Auth header, GET or POST)'); |
