Explorar el Código

#542 OAuth2 (bshaffer) from 1.8.0 to 1.9.0

Alex Trofimov hace 9 años
padre
commit
05b7266ca4

+ 13 - 3
plugins/OAuth2/Controller/AuthorizeController.php

@@ -126,6 +126,11 @@ class AuthorizeController implements AuthorizeControllerInterface
         return $params;
     }
 
+    /**
+     * @param RequestInterface $request
+     * @param ResponseInterface $response
+     * @return bool
+     */
     public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response)
     {
         // Make sure a valid client id was supplied (we can not redirect because we were unable to verify the URI)
@@ -236,8 +241,8 @@ class AuthorizeController implements AuthorizeControllerInterface
             // restrict scope by client specific scope if applicable,
             // otherwise verify the scope exists
             $clientScope = $this->clientStorage->getClientScope($client_id);
-            if ((is_null($clientScope) && !$this->scopeUtil->scopeExists($requestedScope))
-                || ($clientScope && !$this->scopeUtil->checkScope($requestedScope, $clientScope))) {
+            if ((empty($clientScope) && !$this->scopeUtil->scopeExists($requestedScope))
+                || (!empty($clientScope) && !$this->scopeUtil->checkScope($requestedScope, $clientScope))) {
                 $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_scope', 'An unsupported scope was requested', null);
 
                 return false;
@@ -341,9 +346,14 @@ class AuthorizeController implements AuthorizeControllerInterface
                     return true;
                 }
             } else {
+                $registered_uri_length = strlen($registered_uri);
+                if ($registered_uri_length === 0) {
+                    return false;
+                }
+
                 // the input uri is validated against the registered uri using case-insensitive match of the initial string
                 // i.e. additional query parameters may be applied
-                if (strcasecmp(substr($inputUri, 0, strlen($registered_uri)), $registered_uri) === 0) {
+                if (strcasecmp(substr($inputUri, 0, $registered_uri_length), $registered_uri) === 0) {
                     return true;
                 }
             }

+ 1 - 1
plugins/OAuth2/Controller/ResourceController.php

@@ -83,7 +83,7 @@ class ResourceController implements ResourceControllerInterface
             } elseif (!isset($token["expires"]) || !isset($token["client_id"])) {
                 $response->setError(401, 'malformed_token', 'Malformed token (missing "expires")');
             } elseif (time() > $token["expires"]) {
-                $response->setError(401, 'expired_token', 'The access token provided has expired');
+                $response->setError(401, 'invalid_token', 'The access token provided has expired');
             } else {
                 return $token;
             }

+ 31 - 10
plugins/OAuth2/Controller/TokenController.php

@@ -12,14 +12,33 @@ use OAuth2\RequestInterface;
 use OAuth2\ResponseInterface;
 
 /**
- * @see OAuth2\Controller\TokenControllerInterface
+ * @see \OAuth2\Controller\TokenControllerInterface
  */
 class TokenController implements TokenControllerInterface
 {
+    /**
+     * @var AccessTokenInterface
+     */
     protected $accessToken;
+
+    /**
+     * @var array
+     */
     protected $grantTypes;
+
+    /**
+     * @var ClientAssertionTypeInterface
+     */
     protected $clientAssertionType;
+
+    /**
+     * @var Scope|ScopeInterface
+     */
     protected $scopeUtil;
+
+    /**
+     * @var ClientInterface
+     */
     protected $clientStorage;
 
     public function __construct(AccessTokenInterface $accessToken, ClientInterface $clientStorage, array $grantTypes = array(), ClientAssertionTypeInterface $clientAssertionType = null, ScopeInterface $scopeUtil = null)
@@ -51,7 +70,11 @@ class TokenController implements TokenControllerInterface
             // server MUST disable caching in headers when tokens are involved
             $response->setStatusCode(200);
             $response->addParameters($token);
-            $response->addHttpHeaders(array('Cache-Control' => 'no-store', 'Pragma' => 'no-cache'));
+            $response->addHttpHeaders(array(
+                'Cache-Control' => 'no-store',
+                'Pragma' => 'no-cache',
+                'Content-Type' => 'application/json'
+            ));
         }
     }
 
@@ -60,11 +83,11 @@ class TokenController implements TokenControllerInterface
      * This would be called from the "/token" endpoint as defined in the spec.
      * You can call your endpoint whatever you want.
      *
-     * @param $request - RequestInterface
-     * Request object to grant access token
+     * @param RequestInterface $request Request object to grant access token
+     * @param ResponseInterface $response
      *
-     * @throws InvalidArgumentException
-     * @throws LogicException
+     * @throws \InvalidArgumentException
+     * @throws \LogicException
      *
      * @see http://tools.ietf.org/html/rfc6749#section-4
      * @see http://tools.ietf.org/html/rfc6749#section-10.6
@@ -204,10 +227,8 @@ class TokenController implements TokenControllerInterface
     /**
      * addGrantType
      *
-     * @param grantType - OAuth2\GrantTypeInterface
-     * the grant type to add for the specified identifier
-     * @param identifier - string
-     * a string passed in as "grant_type" in the response that will call this grantType
+     * @param GrantTypeInterface $grantType the grant type to add for the specified identifier
+     * @param string $identifier a string passed in as "grant_type" in the response that will call this grantType
      */
     public function addGrantType(GrantTypeInterface $grantType, $identifier = null)
     {

+ 2 - 2
plugins/OAuth2/GrantType/AuthorizationCode.php

@@ -17,7 +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)
     {
@@ -49,7 +49,7 @@ class AuthorizationCode implements GrantTypeInterface
          * @uri - http://tools.ietf.org/html/rfc6749#section-4.1.3
          */
         if (isset($authCode['redirect_uri']) && $authCode['redirect_uri']) {
-            if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != $authCode['redirect_uri']) {
+            if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != urldecode($authCode['redirect_uri'])) {
                 $response->setError(400, 'redirect_uri_mismatch', "The redirect URI is missing or do not match", "#section-4.1.3");
 
                 return false;

+ 2 - 2
plugins/OAuth2/OpenID/ResponseType/CodeIdToken.php

@@ -16,8 +16,8 @@ class CodeIdToken implements CodeIdTokenInterface
     public function getAuthorizeResponse($params, $user_id = null)
     {
         $result = $this->authCode->getAuthorizeResponse($params, $user_id);
-        $id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce']);
-        $result[1]['query']['id_token'] = $id_token;
+        $resultIdToken = $this->idToken->getAuthorizeResponse($params, $user_id);
+        $result[1]['query']['id_token'] = $resultIdToken[1]['fragment']['id_token'];
 
         return $result;
     }

+ 1 - 1
plugins/OAuth2/OpenID/ResponseType/IdToken.php

@@ -84,7 +84,7 @@ class IdToken implements IdTokenInterface
         // maps HS256 and RS256 to sha256, etc.
         $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
         $hash_algorithm = 'sha' . substr($algorithm, 2);
-        $hash = hash($hash_algorithm, $access_token);
+        $hash = hash($hash_algorithm, $access_token, true);
         $at_hash = substr($hash, 0, strlen($hash) / 2);
 
         return $this->encryptionUtil->urlSafeB64Encode($at_hash);

+ 1 - 1
plugins/OAuth2/Response.php

@@ -176,7 +176,7 @@ class Response implements ResponseInterface
     {
         switch ($format) {
             case 'json':
-                return json_encode($this->parameters);
+                return $this->parameters ? json_encode($this->parameters) : '';
             case 'xml':
                 // this only works for single-level arrays
                 $xml = new \SimpleXMLElement('<response/>');

+ 4 - 4
plugins/OAuth2/ResponseType/AccessToken.php

@@ -114,14 +114,14 @@ class AccessToken implements AccessTokenInterface
      */
     protected function generateAccessToken()
     {
-        if (function_exists('mcrypt_create_iv')) {
-            $randomData = mcrypt_create_iv(20, MCRYPT_DEV_URANDOM);
+        if (function_exists('openssl_random_pseudo_bytes')) {
+            $randomData = openssl_random_pseudo_bytes(20);
             if ($randomData !== false && strlen($randomData) === 20) {
                 return bin2hex($randomData);
             }
         }
-        if (function_exists('openssl_random_pseudo_bytes')) {
-            $randomData = openssl_random_pseudo_bytes(20);
+        if (function_exists('mcrypt_create_iv')) {
+            $randomData = mcrypt_create_iv(20, MCRYPT_DEV_URANDOM);
             if ($randomData !== false && strlen($randomData) === 20) {
                 return bin2hex($randomData);
             }

+ 3 - 3
plugins/OAuth2/ResponseType/AuthorizationCode.php

@@ -85,10 +85,10 @@ class AuthorizationCode implements AuthorizationCodeInterface
     protected function generateAuthorizationCode()
     {
         $tokenLen = 40;
-        if (function_exists('mcrypt_create_iv')) {
-            $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM);
-        } elseif (function_exists('openssl_random_pseudo_bytes')) {
+        if (function_exists('openssl_random_pseudo_bytes')) {
             $randomData = openssl_random_pseudo_bytes(100);
+        } elseif (function_exists('mcrypt_create_iv')) {
+            $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM);
         } 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 {

+ 73 - 26
plugins/OAuth2/Server.php

@@ -47,20 +47,50 @@ class Server implements ResourceControllerInterface,
     UserInfoControllerInterface
 {
     // misc properties
+    /**
+     * @var Response
+     */
     protected $response;
+
+    /**
+     * @var array
+     */
     protected $config;
+
+    /**
+     * @var array
+     */
     protected $storages;
 
     // servers
+    /**
+     * @var AuthorizeControllerInterface
+     */
     protected $authorizeController;
+
+    /**
+     * @var TokenControllerInterface
+     */
     protected $tokenController;
+
+    /**
+     * @var ResourceControllerInterface
+     */
     protected $resourceController;
+
+    /**
+     * @var UserInfoControllerInterface
+     */
     protected $userInfoController;
 
     // config classes
     protected $grantTypes;
     protected $responseTypes;
     protected $tokenType;
+
+    /**
+     * @var ScopeInterface
+     */
     protected $scopeUtil;
     protected $clientAssertionType;
 
@@ -92,9 +122,9 @@ class Server implements ResourceControllerInterface,
      * @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 \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
      */
@@ -180,6 +210,8 @@ class Server implements ResourceControllerInterface,
 
     /**
      * every getter deserves a setter
+     *
+     * @param AuthorizeControllerInterface $authorizeController
      */
     public function setAuthorizeController(AuthorizeControllerInterface $authorizeController)
     {
@@ -188,6 +220,8 @@ class Server implements ResourceControllerInterface,
 
     /**
      * every getter deserves a setter
+     *
+     * @param TokenControllerInterface $tokenController
      */
     public function setTokenController(TokenControllerInterface $tokenController)
     {
@@ -196,6 +230,8 @@ class Server implements ResourceControllerInterface,
 
     /**
      * every getter deserves a setter
+     *
+     * @param ResourceControllerInterface $resourceController
      */
     public function setResourceController(ResourceControllerInterface $resourceController)
     {
@@ -204,6 +240,8 @@ class Server implements ResourceControllerInterface,
 
     /**
      * every getter deserves a setter
+     *
+     * @param UserInfoControllerInterface $userInfoController
      */
     public function setUserInfoController(UserInfoControllerInterface $userInfoController)
     {
@@ -214,14 +252,16 @@ class Server implements ResourceControllerInterface,
      * Return claims about the authenticated end-user.
      * This would be called from the "/UserInfo" endpoint as defined in the spec.
      *
-     * @param $request - OAuth2\RequestInterface
+     * @param $request - \OAuth2\RequestInterface
      * Request object to grant access token
      *
-     * @param $response - OAuth2\ResponseInterface
+     * @param $response - \OAuth2\ResponseInterface
      * Response object containing error messages (failure) or user claims (success)
      *
-     * @throws InvalidArgumentException
-     * @throws LogicException
+     * @return ResponseInterface
+     *
+     * @throws \InvalidArgumentException
+     * @throws \LogicException
      *
      * @see http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
      */
@@ -238,14 +278,16 @@ class Server implements ResourceControllerInterface,
      * This would be called from the "/token" endpoint as defined in the spec.
      * Obviously, you can call your endpoint whatever you want.
      *
-     * @param $request - OAuth2\RequestInterface
+     * @param $request - \OAuth2\RequestInterface
      * Request object to grant access token
      *
-     * @param $response - OAuth2\ResponseInterface
+     * @param $response - \OAuth2\ResponseInterface
      * Response object containing error messages (failure) or access token (success)
      *
-     * @throws InvalidArgumentException
-     * @throws LogicException
+     * @return ResponseInterface
+     *
+     * @throws \InvalidArgumentException
+     * @throws \LogicException
      *
      * @see http://tools.ietf.org/html/rfc6749#section-4
      * @see http://tools.ietf.org/html/rfc6749#section-10.6
@@ -306,11 +348,14 @@ class Server implements ResourceControllerInterface,
      * list of space-delimited strings.
      * - state: (optional) An opaque value used by the client to maintain
      * state between the request and callback.
+     * @param ResponseInterface $response
      * @param $is_authorized
      * TRUE or FALSE depending on whether the user authorized the access.
      * @param $user_id
      * Identifier of user who authorized the client
      *
+     * @return Response
+     *
      * @see http://tools.ietf.org/html/rfc6749#section-4
      *
      * @ingroup oauth2_section_4
@@ -464,6 +509,8 @@ class Server implements ResourceControllerInterface,
 
     /**
      * every getter deserves a setter
+     *
+     * @param ScopeInterface $scopeUtil
      */
     public function setScopeUtil($scopeUtil)
     {
@@ -473,7 +520,7 @@ class Server implements ResourceControllerInterface,
     protected function createDefaultAuthorizeController()
     {
         if (!isset($this->storages['client'])) {
-            throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\ClientInterface to use the authorize server");
+            throw new \LogicException('You must supply a storage object implementing \OAuth2\Storage\ClientInterface to use the authorize server');
         }
         if (0 == count($this->responseTypes)) {
             $this->responseTypes = $this->getDefaultResponseTypes();
@@ -505,7 +552,7 @@ class Server implements ResourceControllerInterface,
             foreach ($this->grantTypes as $grantType) {
                 if (!$grantType instanceof ClientAssertionTypeInterface) {
                     if (!isset($this->storages['client_credentials'])) {
-                        throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\ClientCredentialsInterface to use the token server");
+                        throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\ClientCredentialsInterface to use the token server');
                     }
                     $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_credentials_in_request_body allow_public_clients')));
                     $this->clientAssertionType = new HttpBasic($this->storages['client_credentials'], $config);
@@ -515,7 +562,7 @@ class Server implements ResourceControllerInterface,
         }
 
         if (!isset($this->storages['client'])) {
-            throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\ClientInterface to use the token server");
+            throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\ClientInterface to use the token server');
         }
 
         $accessTokenResponseType = $this->getAccessTokenResponseType();
@@ -531,7 +578,7 @@ class Server implements ResourceControllerInterface,
                 $this->storages['access_token'] = $this->createDefaultJwtAccessTokenStorage();
             }
         } elseif (!isset($this->storages['access_token'])) {
-            throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface or use JwtAccessTokens to use the resource server");
+            throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface or use JwtAccessTokens to use the resource server');
         }
 
         if (!$this->tokenType) {
@@ -551,11 +598,11 @@ class Server implements ResourceControllerInterface,
                 $this->storages['access_token'] = $this->createDefaultJwtAccessTokenStorage();
             }
         } elseif (!isset($this->storages['access_token'])) {
-            throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface or use JwtAccessTokens to use the UserInfo server");
+            throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface or use JwtAccessTokens to use the UserInfo server');
         }
 
         if (!isset($this->storages['user_claims'])) {
-            throw new \LogicException("You must supply a storage object implementing OAuth2\OpenID\Storage\UserClaimsInterface to use the UserInfo server");
+            throw new \LogicException('You must supply a storage object implementing OAuth2\OpenID\Storage\UserClaimsInterface to use the UserInfo server');
         }
 
         if (!$this->tokenType) {
@@ -593,7 +640,7 @@ class Server implements ResourceControllerInterface,
             $config = array_intersect_key($this->config, array_flip(explode(' ', 'enforce_redirect auth_code_lifetime')));
             if ($this->config['use_openid_connect']) {
                 if (!$this->storages['authorization_code'] instanceof OpenIDAuthorizationCodeInterface) {
-                    throw new \LogicException("Your authorization_code storage must implement OAuth2\OpenID\Storage\AuthorizationCodeInterface to work when 'use_openid_connect' is true");
+                    throw new \LogicException('Your authorization_code storage must implement OAuth2\OpenID\Storage\AuthorizationCodeInterface to work when "use_openid_connect" is true');
                 }
                 $responseTypes['code'] = new OpenIDAuthorizationCodeResponseType($this->storages['authorization_code'], $config);
                 $responseTypes['code id_token'] = new CodeIdToken($responseTypes['code'], $responseTypes['id_token']);
@@ -603,7 +650,7 @@ class Server implements ResourceControllerInterface,
         }
 
         if (count($responseTypes) == 0) {
-            throw new \LogicException("You must supply an array of response_types in the constructor or implement a OAuth2\Storage\AuthorizationCodeInterface storage object or set 'allow_implicit' to true and implement a OAuth2\Storage\AccessTokenInterface storage object");
+            throw new \LogicException('You must supply an array of response_types in the constructor or implement a OAuth2\Storage\AuthorizationCodeInterface storage object or set "allow_implicit" to true and implement a OAuth2\Storage\AccessTokenInterface storage object');
         }
 
         return $responseTypes;
@@ -630,7 +677,7 @@ class Server implements ResourceControllerInterface,
         if (isset($this->storages['authorization_code'])) {
             if ($this->config['use_openid_connect']) {
                 if (!$this->storages['authorization_code'] instanceof OpenIDAuthorizationCodeInterface) {
-                    throw new \LogicException("Your authorization_code storage must implement OAuth2\OpenID\Storage\AuthorizationCodeInterface to work when 'use_openid_connect' is true");
+                    throw new \LogicException('Your authorization_code storage must implement OAuth2\OpenID\Storage\AuthorizationCodeInterface to work when "use_openid_connect" is true');
                 }
                 $grantTypes['authorization_code'] = new OpenIDAuthorizationCodeGrantType($this->storages['authorization_code']);
             } else {
@@ -639,7 +686,7 @@ class Server implements ResourceControllerInterface,
         }
 
         if (count($grantTypes) == 0) {
-            throw new \LogicException("Unable to build default grant types - You must supply an array of grant_types in the constructor");
+            throw new \LogicException('Unable to build default grant types - You must supply an array of grant_types in the constructor');
         }
 
         return $grantTypes;
@@ -682,7 +729,7 @@ class Server implements ResourceControllerInterface,
     protected function createDefaultJwtAccessTokenStorage()
     {
         if (!isset($this->storages['public_key'])) {
-            throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use crypto tokens");
+            throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use crypto tokens');
         }
         $tokenStorage = null;
         if (!empty($this->config['store_encrypted_token_string']) && isset($this->storages['access_token'])) {
@@ -698,7 +745,7 @@ class Server implements ResourceControllerInterface,
     protected function createDefaultJwtAccessTokenResponseType()
     {
         if (!isset($this->storages['public_key'])) {
-            throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use crypto tokens");
+            throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use crypto tokens');
         }
 
         $tokenStorage = null;
@@ -719,7 +766,7 @@ class Server implements ResourceControllerInterface,
     protected function createDefaultAccessTokenResponseType()
     {
         if (!isset($this->storages['access_token'])) {
-            throw new \LogicException("You must supply a response type implementing OAuth2\ResponseType\AccessTokenInterface, or a storage object implementing OAuth2\Storage\AccessTokenInterface to use the token server");
+            throw new \LogicException('You must supply a response type implementing OAuth2\ResponseType\AccessTokenInterface, or a storage object implementing OAuth2\Storage\AccessTokenInterface to use the token server');
         }
 
         $refreshStorage = null;
@@ -736,10 +783,10 @@ class Server implements ResourceControllerInterface,
     protected function createDefaultIdTokenResponseType()
     {
         if (!isset($this->storages['user_claims'])) {
-            throw new \LogicException("You must supply a storage object implementing OAuth2\OpenID\Storage\UserClaimsInterface to use openid connect");
+            throw new \LogicException('You must supply a storage object implementing OAuth2\OpenID\Storage\UserClaimsInterface to use openid connect');
         }
         if (!isset($this->storages['public_key'])) {
-            throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use openid connect");
+            throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use openid connect');
         }
 
         $config = array_intersect_key($this->config, array_flip(explode(' ', 'issuer id_lifetime')));

+ 1 - 0
plugins/OAuth2/Storage/AccessTokenInterface.php

@@ -55,6 +55,7 @@ interface AccessTokenInterface
      * @param $access_token
      * Access token to be expired.
      *
+     * @return BOOL true if an access token was unset, false if not
      * @ingroup oauth2_section_6
      *
      * @todo v2.0 include this method in interface. Omitted to maintain BC in v1.x

+ 1 - 1
plugins/OAuth2/Storage/AuthorizationCodeInterface.php

@@ -71,7 +71,7 @@ interface AuthorizationCodeInterface
     public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null);
 
     /**
-     * once an Authorization Code is used, it must be exipired
+     * once an Authorization Code is used, it must be expired
      *
      * @see http://tools.ietf.org/html/rfc6749#section-4.1.2
      *

+ 20 - 9
plugins/OAuth2/Storage/Cassandra.php

@@ -136,14 +136,19 @@ class Cassandra implements AuthorizationCodeInterface,
         unset($this->cache[$key]);
 
         $cf = new ColumnFamily($this->cassandra, $this->config['column_family']);
-        try {
-            // __data key set as C* requires a field
-            $cf->remove($key, array('__data'));
-        } catch (\Exception $e) {
-            return false;
+
+        if ($cf->get_count($key) > 0) {
+            try {
+                // __data key set as C* requires a field
+                $cf->remove($key, array('__data'));
+            } catch (\Exception $e) {
+                return false;
+            }
+
+            return true;
         }
 
-        return true;
+        return false;
     }
 
     /* AuthorizationCodeInterface */
@@ -182,7 +187,13 @@ class Cassandra implements AuthorizationCodeInterface,
     // plaintext passwords are bad!  Override this for your application
     protected function checkPassword($user, $password)
     {
-        return $user['password'] == sha1($password);
+        return $user['password'] == $this->hashPassword($password);
+    }
+
+    // use a secure hashing algorithm when storing passwords. Override this for your application
+    protected function hashPassword($password)
+    {
+        return sha1($password);
     }
 
     public function getUserDetails($username)
@@ -204,7 +215,7 @@ class Cassandra implements AuthorizationCodeInterface,
 
     public function setUser($username, $password, $first_name = null, $last_name = null)
     {
-        $password = sha1($password);
+        $password = $this->hashPassword($password);
 
         return $this->setValue(
             $this->config['user_key'] . $username,
@@ -229,7 +240,7 @@ class Cassandra implements AuthorizationCodeInterface,
             return false;
         }
 
-        return empty($result['client_secret']);;
+        return empty($client['client_secret']);
     }
 
     /* ClientInterface */

+ 21 - 9
plugins/OAuth2/Storage/DynamoDB.php

@@ -124,7 +124,7 @@ class DynamoDB implements
     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); });
+        $clientData = array_filter($clientData, 'self::isNotEmpty');
 
         $result = $this->client->putItem(array(
             'TableName' =>  $this->config['client_table'],
@@ -171,7 +171,7 @@ class DynamoDB implements
         $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 !empty($value); });
+        $clientData = array_filter($clientData, 'self::isNotEmpty');
 
         $result = $this->client->putItem(array(
             'TableName' =>  $this->config['access_token_table'],
@@ -186,10 +186,11 @@ class DynamoDB implements
     {
         $result = $this->client->deleteItem(array(
             'TableName' =>  $this->config['access_token_table'],
-            'Key' => $this->client->formatAttributes(array("access_token" => $access_token))
+            'Key' => $this->client->formatAttributes(array("access_token" => $access_token)),
+            'ReturnValues' => 'ALL_OLD',
         ));
 
-        return true;
+        return null !== $result->get('Attributes');
     }
 
     /* OAuth2\Storage\AuthorizationCodeInterface */
@@ -218,7 +219,7 @@ class DynamoDB implements
         $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 !empty($value); });
+        $clientData = array_filter($clientData, 'self::isNotEmpty');
 
         $result = $this->client->putItem(array(
             'TableName' =>  $this->config['code_table'],
@@ -319,7 +320,7 @@ class DynamoDB implements
         $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 !empty($value); });
+        $clientData = array_filter($clientData, 'self::isNotEmpty');
 
         $result = $this->client->putItem(array(
             'TableName' =>  $this->config['refresh_token_table'],
@@ -342,7 +343,13 @@ class DynamoDB implements
     // plaintext passwords are bad!  Override this for your application
     protected function checkPassword($user, $password)
     {
-        return $user['password'] == sha1($password);
+        return $user['password'] == $this->hashPassword($password);
+    }
+
+    // use a secure hashing algorithm when storing passwords. Override this for your application
+    protected function hashPassword($password)
+    {
+        return sha1($password);
     }
 
     public function getUser($username)
@@ -363,10 +370,10 @@ class DynamoDB implements
     public function setUser($username, $password, $first_name = null, $last_name = null)
     {
         // do not store in plaintext
-        $password = sha1($password);
+        $password = $this->hashPassword($password);
 
         $clientData = compact('username', 'password', 'first_name', 'last_name');
-        $clientData = array_filter($clientData, function ($value) { return !is_null($value); });
+        $clientData = array_filter($clientData, 'self::isNotEmpty');
 
         $result = $this->client->putItem(array(
             'TableName' =>  $this->config['user_table'],
@@ -525,4 +532,9 @@ class DynamoDB implements
 
         return $result;
     }
+
+    private static function isNotEmpty($value)
+    {
+        return null !== $value && '' !== $value;
+    }
 }

+ 15 - 3
plugins/OAuth2/Storage/Memory.php

@@ -142,7 +142,7 @@ class Memory 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));
                 }
             }
         }
@@ -236,7 +236,13 @@ class Memory implements AuthorizationCodeInterface,
 
     public function unsetRefreshToken($refresh_token)
     {
-        unset($this->refreshTokens[$refresh_token]);
+        if (isset($this->refreshTokens[$refresh_token])) {
+            unset($this->refreshTokens[$refresh_token]);
+
+            return true;
+        }
+
+        return false;
     }
 
     public function setRefreshTokens($refresh_tokens)
@@ -259,7 +265,13 @@ class Memory implements AuthorizationCodeInterface,
 
     public function unsetAccessToken($access_token)
     {
-        unset($this->accessTokens[$access_token]);
+        if (isset($this->accessTokens[$access_token])) {
+            unset($this->accessTokens[$access_token]);
+
+            return true;
+        }
+
+        return false;
     }
 
     public function scopeExists($scope)

+ 62 - 3
plugins/OAuth2/Storage/Mongo.php

@@ -22,6 +22,7 @@ class Mongo implements AuthorizationCodeInterface,
     UserCredentialsInterface,
     RefreshTokenInterface,
     JwtBearerInterface,
+    PublicKeyInterface,
     OpenIDAuthorizationCodeInterface
 {
     protected $db;
@@ -46,6 +47,7 @@ class Mongo implements AuthorizationCodeInterface,
             'refresh_token_table' => 'oauth_refresh_tokens',
             'code_table' => 'oauth_authorization_codes',
             'user_table' => 'oauth_users',
+            'key_table' => 'oauth_keys',
             'jwt_table' => 'oauth_jwt',
         ), $config);
     }
@@ -161,7 +163,11 @@ class Mongo implements AuthorizationCodeInterface,
 
     public function unsetAccessToken($access_token)
     {
-        $this->collection('access_token_table')->remove(array('access_token' => $access_token));
+        $result = $this->collection('access_token_table')->remove(array(
+            'access_token' => $access_token
+        ), array('w' => 1));
+
+        return $result['n'] > 0;
     }
 
 
@@ -254,9 +260,11 @@ class Mongo implements AuthorizationCodeInterface,
 
     public function unsetRefreshToken($refresh_token)
     {
-        $this->collection('refresh_token_table')->remove(array('refresh_token' => $refresh_token));
+        $result = $this->collection('refresh_token_table')->remove(array(
+            'refresh_token' => $refresh_token
+        ), array('w' => 1));
 
-        return true;
+        return $result['n'] > 0;
     }
 
     // plaintext passwords are bad!  Override this for your application
@@ -330,4 +338,55 @@ class Mongo implements AuthorizationCodeInterface,
         //TODO: Needs mongodb implementation.
         throw new \Exception('setJti() for the MongoDB driver is currently unimplemented.');
     }
+
+    public function getPublicKey($client_id = null)
+    {
+        if ($client_id) {
+            $result = $this->collection('key_table')->findOne(array(
+                'client_id' => $client_id
+            ));
+            if ($result) {
+                return $result['public_key'];
+            }
+        }
+
+        $result = $this->collection('key_table')->findOne(array(
+            'client_id' => null
+        ));
+        return is_null($result) ? false : $result['public_key'];
+    }
+
+    public function getPrivateKey($client_id = null)
+    {
+        if ($client_id) {
+            $result = $this->collection('key_table')->findOne(array(
+                'client_id' => $client_id
+            ));
+            if ($result) {
+                return $result['private_key'];
+            }
+        }
+
+        $result = $this->collection('key_table')->findOne(array(
+            'client_id' => null
+        ));
+        return is_null($result) ? false : $result['private_key'];
+    }
+
+    public function getEncryptionAlgorithm($client_id = null)
+    {
+        if ($client_id) {
+            $result = $this->collection('key_table')->findOne(array(
+                'client_id' => $client_id
+            ));
+            if ($result) {
+                return $result['encryption_algorithm'];
+            }
+        }
+
+        $result = $this->collection('key_table')->findOne(array(
+            'client_id' => null
+        ));
+        return is_null($result) ? 'RS256' : $result['encryption_algorithm'];
+    }
 }

+ 380 - 0
plugins/OAuth2/Storage/MongoDB.php

@@ -0,0 +1,380 @@
+<?php
+
+namespace OAuth2\Storage;
+
+use MongoDB\Client;
+use MongoDB\Database;
+use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
+
+/**
+ * Simple MongoDB storage for all storage types
+ *
+ * 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>
+ */
+class MongoDB implements AuthorizationCodeInterface,
+    UserCredentialsInterface,
+    AccessTokenInterface,
+    ClientCredentialsInterface,
+    RefreshTokenInterface,
+    JwtBearerInterface,
+    PublicKeyInterface,
+    OpenIDAuthorizationCodeInterface
+{
+    protected $db;
+    protected $config;
+
+    public function __construct($connection, $config = array())
+    {
+        if ($connection instanceof Database) {
+            $this->db = $connection;
+        } else {
+            if (!is_array($connection)) {
+                throw new \InvalidArgumentException('First argument to OAuth2\Storage\Mongo must be an instance of MongoDB\Database or a configuration array');
+            }
+            $server = sprintf('mongodb://%s:%d', $connection['host'], $connection['port']);
+            $m = new Client($server);
+            $this->db = $m->selectDatabase($connection['database']);
+        }
+        $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',
+            'jti_table' => 'oauth_jti',
+            'scope_table'  => 'oauth_scopes',
+            'key_table'  => 'oauth_keys',
+        ), $config);
+    }
+
+    /* ClientCredentialsInterface */
+    public function checkClientCredentials($client_id, $client_secret = null)
+    {
+        if ($result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) {
+            return $result['client_secret'] == $client_secret;
+        }
+        return false;
+    }
+
+    public function isPublicClient($client_id)
+    {
+        if (!$result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) {
+            return false;
+        }
+        return empty($result['client_secret']);
+    }
+
+    /* ClientInterface */
+    public function getClientDetails($client_id)
+    {
+        $result = $this->collection('client_table')->findOne(array('client_id' => $client_id));
+        return is_null($result) ? false : $result;
+    }
+
+    public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null)
+    {
+        if ($this->getClientDetails($client_id)) {
+            $result = $this->collection('client_table')->updateOne(
+                array('client_id' => $client_id),
+                array('$set' => array(
+                    'client_secret' => $client_secret,
+                    'redirect_uri'  => $redirect_uri,
+                    'grant_types'   => $grant_types,
+                    'scope'         => $scope,
+                    'user_id'       => $user_id,
+                ))
+            );
+            return $result->getMatchedCount() > 0;
+        }
+        $client = array(
+            'client_id'     => $client_id,
+            'client_secret' => $client_secret,
+            'redirect_uri'  => $redirect_uri,
+            'grant_types'   => $grant_types,
+            'scope'         => $scope,
+            'user_id'       => $user_id,
+        );
+        $result = $this->collection('client_table')->insertOne($client);
+        return $result->getInsertedCount() > 0;
+    }
+
+    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, $grant_types);
+        }
+        // if grant_types are not defined, then none are restricted
+        return true;
+    }
+
+    /* AccessTokenInterface */
+    public function getAccessToken($access_token)
+    {
+        $token = $this->collection('access_token_table')->findOne(array('access_token' => $access_token));
+        return is_null($token) ? false : $token;
+    }
+
+    public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null)
+    {
+        // if it exists, update it.
+        if ($this->getAccessToken($access_token)) {
+            $result = $this->collection('access_token_table')->updateOne(
+                array('access_token' => $access_token),
+                array('$set' => array(
+                    'client_id' => $client_id,
+                    'expires' => $expires,
+                    'user_id' => $user_id,
+                    'scope' => $scope
+                ))
+            );
+            return $result->getMatchedCount() > 0;
+        }
+        $token = array(
+            'access_token' => $access_token,
+            'client_id' => $client_id,
+            'expires' => $expires,
+            'user_id' => $user_id,
+            'scope' => $scope
+        );
+        $result = $this->collection('access_token_table')->insertOne($token);
+        return $result->getInsertedCount() > 0;
+    }
+
+    public function unsetAccessToken($access_token)
+    {
+        $result = $this->collection('access_token_table')->deleteOne(array(
+            'access_token' => $access_token
+        ));
+        return $result->getDeletedCount() > 0;
+    }
+
+    /* AuthorizationCodeInterface */
+    public function getAuthorizationCode($code)
+    {
+        $code = $this->collection('code_table')->findOne(array(
+            'authorization_code' => $code
+        ));
+        return is_null($code) ? false : $code;
+    }
+
+    public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
+    {
+        // if it exists, update it.
+        if ($this->getAuthorizationCode($code)) {
+            $result = $this->collection('code_table')->updateOne(
+                array('authorization_code' => $code),
+                array('$set' => array(
+                    'client_id' => $client_id,
+                    'user_id' => $user_id,
+                    'redirect_uri' => $redirect_uri,
+                    'expires' => $expires,
+                    'scope' => $scope,
+                    'id_token' => $id_token,
+                ))
+            );
+            return $result->getMatchedCount() > 0;
+        }
+        $token = array(
+            'authorization_code' => $code,
+            'client_id' => $client_id,
+            'user_id' => $user_id,
+            'redirect_uri' => $redirect_uri,
+            'expires' => $expires,
+            'scope' => $scope,
+            'id_token' => $id_token,
+        );
+        $result = $this->collection('code_table')->insertOne($token);
+        return $result->getInsertedCount() > 0;
+    }
+
+    public function expireAuthorizationCode($code)
+    {
+        $result = $this->collection('code_table')->deleteOne(array(
+            'authorization_code' => $code
+        ));
+        return $result->getDeletedCount() > 0;
+    }
+
+    /* UserCredentialsInterface */
+    public function checkUserCredentials($username, $password)
+    {
+        if ($user = $this->getUser($username)) {
+            return $this->checkPassword($user, $password);
+        }
+        return false;
+    }
+
+    public function getUserDetails($username)
+    {
+        if ($user = $this->getUser($username)) {
+            $user['user_id'] = $user['username'];
+        }
+        return $user;
+    }
+
+    /* RefreshTokenInterface */
+    public function getRefreshToken($refresh_token)
+    {
+        $token = $this->collection('refresh_token_table')->findOne(array(
+            'refresh_token' => $refresh_token
+        ));
+        return is_null($token) ? false : $token;
+    }
+
+    public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null)
+    {
+        $token = array(
+            'refresh_token' => $refresh_token,
+            'client_id' => $client_id,
+            'user_id' => $user_id,
+            'expires' => $expires,
+            'scope' => $scope
+        );
+        $result = $this->collection('refresh_token_table')->insertOne($token);
+        return $result->getInsertedCount() > 0;
+    }
+
+    public function unsetRefreshToken($refresh_token)
+    {
+        $result = $this->collection('refresh_token_table')->deleteOne(array(
+            'refresh_token' => $refresh_token
+        ));
+        return $result->getDeletedCount() > 0;
+    }
+
+    // plaintext passwords are bad!  Override this for your application
+    protected function checkPassword($user, $password)
+    {
+        return $user['password'] == $password;
+    }
+
+    public function getUser($username)
+    {
+        $result = $this->collection('user_table')->findOne(array('username' => $username));
+        return is_null($result) ? false : $result;
+    }
+
+    public function setUser($username, $password, $firstName = null, $lastName = null)
+    {
+        if ($this->getUser($username)) {
+            $result = $this->collection('user_table')->updateOne(
+                array('username' => $username),
+                array('$set' => array(
+                    'password' => $password,
+                    'first_name' => $firstName,
+                    'last_name' => $lastName
+                ))
+            );
+
+            return $result->getMatchedCount() > 0;
+        }
+
+        $user = array(
+            'username' => $username,
+            'password' => $password,
+            'first_name' => $firstName,
+            'last_name' => $lastName
+        );
+        $result = $this->collection('user_table')->insertOne($user);
+        return $result->getInsertedCount() > 0;
+    }
+
+    public function getClientKey($client_id, $subject)
+    {
+        $result = $this->collection('jwt_table')->findOne(array(
+            'client_id' => $client_id,
+            'subject' => $subject
+        ));
+        return is_null($result) ? false : $result['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: Needs mongodb implementation.
+        throw new \Exception('getJti() for the MongoDB driver is currently unimplemented.');
+    }
+
+    public function setJti($client_id, $subject, $audience, $expires, $jti)
+    {
+        //TODO: Needs mongodb implementation.
+        throw new \Exception('setJti() for the MongoDB driver is currently unimplemented.');
+    }
+
+    public function getPublicKey($client_id = null)
+    {
+        if ($client_id) {
+            $result = $this->collection('key_table')->findOne(array(
+                'client_id' => $client_id
+            ));
+            if ($result) {
+                return $result['public_key'];
+            }
+        }
+
+        $result = $this->collection('key_table')->findOne(array(
+            'client_id' => null
+        ));
+        return is_null($result) ? false : $result['public_key'];
+    }
+
+    public function getPrivateKey($client_id = null)
+    {
+        if ($client_id) {
+            $result = $this->collection('key_table')->findOne(array(
+                'client_id' => $client_id
+            ));
+            if ($result) {
+                return $result['private_key'];
+            }
+        }
+
+        $result = $this->collection('key_table')->findOne(array(
+            'client_id' => null
+        ));
+        return is_null($result) ? false : $result['private_key'];
+    }
+
+    public function getEncryptionAlgorithm($client_id = null)
+    {
+        if ($client_id) {
+            $result = $this->collection('key_table')->findOne(array(
+                'client_id' => $client_id
+            ));
+            if ($result) {
+                return $result['encryption_algorithm'];
+            }
+        }
+
+        $result = $this->collection('key_table')->findOne(array(
+            'client_id' => null
+        ));
+        return is_null($result) ? 'RS256' : $result['encryption_algorithm'];
+    }
+
+    // Helper function to access a MongoDB collection by `type`:
+    protected function collection($name)
+    {
+        return $this->db->{$this->config[$name]};
+    }
+}

+ 16 - 6
plugins/OAuth2/Storage/Pdo.php

@@ -160,7 +160,9 @@ class Pdo implements
     {
         $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE access_token = :access_token', $this->config['access_token_table']));
 
-        return $stmt->execute(compact('access_token'));
+        $stmt->execute(compact('access_token'));
+
+        return $stmt->rowCount() > 0;
     }
 
     /* OAuth2\Storage\AuthorizationCodeInterface */
@@ -301,13 +303,21 @@ class Pdo implements
     {
         $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table']));
 
-        return $stmt->execute(compact('refresh_token'));
+        $stmt->execute(compact('refresh_token'));
+
+        return $stmt->rowCount() > 0;
     }
 
     // plaintext passwords are bad!  Override this for your application
     protected function checkPassword($user, $password)
     {
-        return $user['password'] == sha1($password);
+        return $user['password'] == $this->hashPassword($password);
+    }
+
+    // use a secure hashing algorithm when storing passwords. Override this for your application
+    protected function hashPassword($password)
+    {
+        return sha1($password);
     }
 
     public function getUser($username)
@@ -328,7 +338,7 @@ class Pdo implements
     public function setUser($username, $password, $firstName = null, $lastName = null)
     {
         // do not store in plaintext
-        $password = sha1($password);
+        $password = $this->hashPassword($password);
 
         // if it exists, update it.
         if ($this->getUser($username)) {
@@ -463,7 +473,7 @@ class Pdo implements
         $sql = "
         CREATE TABLE {$this->config['client_table']} (
           client_id             VARCHAR(80)   NOT NULL,
-          client_secret         VARCHAR(80)   NOT NULL,
+          client_secret         VARCHAR(80),
           redirect_uri          VARCHAR(2000),
           grant_types           VARCHAR(80),
           scope                 VARCHAR(4000),
@@ -525,7 +535,7 @@ class Pdo implements
         CREATE TABLE {$this->config['jti_table']} (
           issuer              VARCHAR(80)   NOT NULL,
           subject             VARCHAR(80),
-          audiance            VARCHAR(80),
+          audience            VARCHAR(80),
           expires             TIMESTAMP     NOT NULL,
           jti                 VARCHAR(2000) NOT NULL
         );

+ 7 - 3
plugins/OAuth2/Storage/Redis.php

@@ -162,7 +162,7 @@ class Redis implements AuthorizationCodeInterface,
             return false;
         }
 
-        return empty($result['client_secret']);
+        return empty($client['client_secret']);
     }
 
     /* ClientInterface */
@@ -209,7 +209,9 @@ class Redis implements AuthorizationCodeInterface,
 
     public function unsetRefreshToken($refresh_token)
     {
-        return $this->expireValue($this->config['refresh_token_key'] . $refresh_token);
+        $result = $this->expireValue($this->config['refresh_token_key'] . $refresh_token);
+
+        return $result > 0;
     }
 
     /* AccessTokenInterface */
@@ -229,7 +231,9 @@ class Redis implements AuthorizationCodeInterface,
 
     public function unsetAccessToken($access_token)
     {
-        return $this->expireValue($this->config['access_token_key'] . $access_token);
+        $result = $this->expireValue($this->config['access_token_key'] . $access_token);
+
+        return $result > 0;
     }
 
     /* ScopeInterface */

+ 1 - 1
plugins/OAuth2/Storage/RefreshTokenInterface.php

@@ -74,7 +74,7 @@ interface RefreshTokenInterface
      * and provide a descriptive fail message.
      *
      * @param $refresh_token
-     * Refresh token to be expirse.
+     * Refresh token to be expired.
      *
      * @ingroup oauth2_section_6
      */