- * $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
- * );
- *
+ * Config array $config should look as follows:
+ * @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 ClientCredentialsInterface $storage Storage
+ * @param array $config Configuration options for the server
*/
public function __construct(ClientCredentialsInterface $storage, array $config = array())
{
@@ -37,6 +40,14 @@ public function __construct(ClientCredentialsInterface $storage, array $config =
), $config);
}
+ /**
+ * Validate the OAuth request
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return bool|mixed
+ * @throws LogicException
+ */
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$clientData = $this->getClientCredentials($request, $response)) {
@@ -44,7 +55,7 @@ public function validateRequest(RequestInterface $request, ResponseInterface $re
}
if (!isset($clientData['client_id'])) {
- throw new \LogicException('the clientData array must have "client_id" set');
+ throw new LogicException('the clientData array must have "client_id" set');
}
if (!isset($clientData['client_secret']) || $clientData['client_secret'] == '') {
@@ -70,6 +81,11 @@ public function validateRequest(RequestInterface $request, ResponseInterface $re
return true;
}
+ /**
+ * Get the client id
+ *
+ * @return mixed
+ */
public function getClientId()
{
return $this->clientData['client_id'];
@@ -82,13 +98,14 @@ public function getClientId()
* According to the spec (draft 20), the client_id can be provided in
* the Basic Authorization header (recommended) or via GET/POST.
*
- * @return
- * A list containing the client identifier and password, for example
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return array|null A list containing the client identifier and password, for example:
* @code
- * return array(
- * "client_id" => CLIENT_ID, // REQUIRED the client id
- * "client_secret" => CLIENT_SECRET, // OPTIONAL the client secret (may be omitted for public clients)
- * );
+ * return array(
+ * "client_id" => CLIENT_ID, // REQUIRED the client id
+ * "client_secret" => CLIENT_SECRET, // OPTIONAL the client secret (may be omitted for public clients)
+ * );
* @endcode
*
* @see http://tools.ietf.org/html/rfc6749#section-2.3.1
@@ -108,7 +125,6 @@ public function getClientCredentials(RequestInterface $request, ResponseInterfac
* 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/OAuth2/Controller/AuthorizeController.php b/OAuth2/Controller/AuthorizeController.php
index 6d6481e..4bafb1d 100644
--- a/OAuth2/Controller/AuthorizeController.php
+++ b/OAuth2/Controller/AuthorizeController.php
@@ -7,37 +7,76 @@
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
use OAuth2\Scope;
+use InvalidArgumentException;
/**
- * @see OAuth2\Controller\AuthorizeControllerInterface
+ * @see AuthorizeControllerInterface
*/
class AuthorizeController implements AuthorizeControllerInterface
{
+ /**
+ * @var string
+ */
private $scope;
+
+ /**
+ * @var int
+ */
private $state;
+
+ /**
+ * @var mixed
+ */
private $client_id;
+
+ /**
+ * @var string
+ */
private $redirect_uri;
+
+ /**
+ * The response type
+ *
+ * @var string
+ */
private $response_type;
+ /**
+ * @var ClientInterface
+ */
protected $clientStorage;
+
+ /**
+ * @var array
+ */
protected $responseTypes;
+
+ /**
+ * @var array
+ */
protected $config;
+
+ /**
+ * @var ScopeInterface
+ */
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
- *
- * $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
- * );
- *
- * @param OAuth2\ScopeInterface $scopeUtil OPTIONAL Instance of OAuth2\ScopeInterface to validate the requested scope
+ * Constructor
+ *
+ * @param 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:
+ * @param ScopeInterface $scopeUtil OPTIONAL Instance of OAuth2\ScopeInterface to validate the requested scope
+ * @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
*/
public function __construct(ClientInterface $clientStorage, array $responseTypes = array(), array $config = array(), ScopeInterface $scopeUtil = null)
{
@@ -56,10 +95,20 @@ public function __construct(ClientInterface $clientStorage, array $responseTypes
$this->scopeUtil = $scopeUtil;
}
+ /**
+ * Handle the authorization request
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param boolean $is_authorized
+ * @param mixed $user_id
+ * @return mixed|void
+ * @throws InvalidArgumentException
+ */
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null)
{
if (!is_bool($is_authorized)) {
- throw new \InvalidArgumentException('Argument "is_authorized" must be a boolean. This method must know if the user has granted access to the client.');
+ throw new InvalidArgumentException('Argument "is_authorized" must be a boolean. This method must know if the user has granted access to the client.');
}
// We repeat this, because we need to re-validate. The request could be POSTed
@@ -101,6 +150,14 @@ public function handleAuthorizeRequest(RequestInterface $request, ResponseInterf
$response->setRedirect($this->config['redirect_status_code'], $uri);
}
+ /**
+ * Set not authorized response
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param string $redirect_uri
+ * @param mixed $user_id
+ */
protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null)
{
$error = 'access_denied';
@@ -108,9 +165,16 @@ protected function setNotAuthorizedResponse(RequestInterface $request, ResponseI
$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
+ *
+ * @TODO: add dependency injection for the parameters in this method
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param mixed $user_id
+ * @return array
*/
protected function buildAuthorizeParameters($request, $response, $user_id)
{
@@ -126,10 +190,17 @@ protected function buildAuthorizeParameters($request, $response, $user_id)
return $params;
}
+ /**
+ * Validate the OAuth request
+ *
+ * @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)
- if (!$client_id = $request->query("client_id")) {
+ if (!$client_id = $request->query('client_id', $request->request('client_id'))) {
// We don't have a good URI to use
$response->setError(400, 'invalid_client', "No client id supplied");
@@ -149,7 +220,7 @@ public function validateAuthorizeRequest(RequestInterface $request, ResponseInte
// @see http://tools.ietf.org/html/rfc6749#section-3.1.2
// @see http://tools.ietf.org/html/rfc6749#section-4.1.2.1
// @see http://tools.ietf.org/html/rfc6749#section-4.2.2.1
- if ($supplied_redirect_uri = $request->query('redirect_uri')) {
+ if ($supplied_redirect_uri = $request->query('redirect_uri', $request->request('redirect_uri'))) {
// validate there is no fragment supplied
$parts = parse_url($supplied_redirect_uri);
if (isset($parts['fragment']) && $parts['fragment']) {
@@ -181,9 +252,17 @@ public function validateAuthorizeRequest(RequestInterface $request, ResponseInte
$redirect_uri = $registered_redirect_uri;
}
- // Select the redirect URI
- $response_type = $request->query('response_type');
- $state = $request->query('state');
+ // Select the response type
+ $response_type = $request->query('response_type', $request->request('response_type'));
+
+ // for multiple-valued response types - make them alphabetical
+ if (false !== strpos($response_type, ' ')) {
+ $types = explode(' ', $response_type);
+ sort($types);
+ $response_type = ltrim(implode(' ', $types));
+ }
+
+ $state = $request->query('state', $request->request('state'));
// type and client_id are required
if (!$response_type || !in_array($response_type, $this->getValidResponseTypes())) {
@@ -191,6 +270,7 @@ public function validateAuthorizeRequest(RequestInterface $request, ResponseInte
return false;
}
+
if ($response_type == self::RESPONSE_TYPE_AUTHORIZATION_CODE) {
if (!isset($this->responseTypes['code'])) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'authorization code grant type not supported', null);
@@ -227,8 +307,8 @@ public function validateAuthorizeRequest(RequestInterface $request, ResponseInte
// 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;
@@ -267,10 +347,10 @@ public function validateAuthorizeRequest(RequestInterface $request, ResponseInte
/**
* Build the absolute URI based on supplied URI and parameters.
*
- * @param $uri An absolute URI.
- * @param $params Parameters to be append as GET.
+ * @param string $uri An absolute URI.
+ * @param array $params Parameters to be append as GET.
*
- * @return
+ * @return string
* An absolute URI with supplied parameters.
*
* @ingroup oauth2_section_4
@@ -282,15 +362,15 @@ private function buildUri($uri, $params)
// Add our params to the parsed uri
foreach ($params as $k => $v) {
if (isset($parse_url[$k])) {
- $parse_url[$k] .= "&" . http_build_query($v);
+ $parse_url[$k] .= "&" . http_build_query($v, '', '&');
} else {
- $parse_url[$k] = http_build_query($v);
+ $parse_url[$k] = http_build_query($v, '', '&');
}
}
- // Put humpty dumpty back together
+ // Put the uri back together
return
- ((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "")
+ ((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "")
. ((isset($parse_url["user"])) ? $parse_url["user"]
. ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "")
. ((isset($parse_url["host"])) ? $parse_url["host"] : "")
@@ -312,19 +392,19 @@ protected function getValidResponseTypes()
/**
* Internal method for validating redirect URI supplied
*
- * @param string $inputUri The submitted URI to be validated
+ * @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
- *
+ * @return bool
* @see http://tools.ietf.org/html/rfc6749#section-3.1.2
*/
- private function validateRedirectUri($inputUri, $registeredUriString)
+ protected function validateRedirectUri($inputUri, $registeredUriString)
{
if (!$inputUri || !$registeredUriString) {
return false; // if either one is missing, assume INVALID
}
- $registered_uris = explode(' ', $registeredUriString);
+ $registered_uris = preg_split('/\s+/', $registeredUriString);
foreach ($registered_uris as $registered_uri) {
if ($this->config['require_exact_redirect_uri']) {
// the input uri is validated against the registered uri using exact match
@@ -332,48 +412,67 @@ private function validateRedirectUri($inputUri, $registeredUriString)
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;
}
}
}
- //XTEC *********** AFEGIT Check also http
- //@pferre22 20.02.2015
- $nossl = preg_replace("/^https:/i", "http:", $inputUri);
- if ($inputUri != $nossl) {
- return $this->validateRedirectUri($nossl, $registeredUriString);
- }
- //FI PATCH
return false;
}
/**
- * Convenience methods to access the parameters derived from the validated request
+ * Convenience method to access the scope
+ *
+ * @return string
*/
-
public function getScope()
{
return $this->scope;
}
+ /**
+ * Convenience method to access the state
+ *
+ * @return int
+ */
public function getState()
{
return $this->state;
}
+ /**
+ * Convenience method to access the client id
+ *
+ * @return mixed
+ */
public function getClientId()
{
return $this->client_id;
}
+ /**
+ * Convenience method to access the redirect url
+ *
+ * @return string
+ */
public function getRedirectUri()
{
return $this->redirect_uri;
}
+ /**
+ * Convenience method to access the response type
+ *
+ * @return string
+ */
public function getResponseType()
{
return $this->response_type;
diff --git a/OAuth2/Controller/AuthorizeControllerInterface.php b/OAuth2/Controller/AuthorizeControllerInterface.php
index fa07ae8..f758f97 100644
--- a/OAuth2/Controller/AuthorizeControllerInterface.php
+++ b/OAuth2/Controller/AuthorizeControllerInterface.php
@@ -11,17 +11,18 @@
* authorization directly, this controller ensures the request is valid, but
* requires the application to determine the value of $is_authorized
*
- * ex:
- * > $user_id = $this->somehowDetermineUserId();
- * > $is_authorized = $this->somehowDetermineUserAuthorization();
- * > $response = new OAuth2\Response();
- * > $authorizeController->handleAuthorizeRequest(
- * > OAuth2\Request::createFromGlobals(),
- * > $response,
- * > $is_authorized,
- * > $user_id);
- * > $response->send();
- *
+ * @code
+ * $user_id = $this->somehowDetermineUserId();
+ * $is_authorized = $this->somehowDetermineUserAuthorization();
+ * $response = new OAuth2\Response();
+ * $authorizeController->handleAuthorizeRequest(
+ * OAuth2\Request::createFromGlobals(),
+ * $response,
+ * $is_authorized,
+ * $user_id
+ * );
+ * $response->send();
+ * @endcode
*/
interface AuthorizeControllerInterface
{
@@ -37,7 +38,21 @@ interface AuthorizeControllerInterface
const RESPONSE_TYPE_AUTHORIZATION_CODE = 'code';
const RESPONSE_TYPE_ACCESS_TOKEN = 'token';
+ /**
+ * Handle the OAuth request
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param $is_authorized
+ * @param null $user_id
+ * @return mixed
+ */
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null);
+ /**
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return bool
+ */
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response);
}
diff --git a/OAuth2/Controller/ResourceController.php b/OAuth2/Controller/ResourceController.php
index e858818..926f90f 100644
--- a/OAuth2/Controller/ResourceController.php
+++ b/OAuth2/Controller/ResourceController.php
@@ -10,17 +10,43 @@
use OAuth2\Scope;
/**
- * @see OAuth2\Controller\ResourceControllerInterface
+ * @see ResourceControllerInterface
*/
class ResourceController implements ResourceControllerInterface
{
+ /**
+ * @var array
+ */
private $token;
+ /**
+ * @var TokenTypeInterface
+ */
protected $tokenType;
+
+ /**
+ * @var AccessTokenInterface
+ */
protected $tokenStorage;
+
+ /**
+ * @var array
+ */
protected $config;
+
+ /**
+ * @var ScopeInterface
+ */
protected $scopeUtil;
+ /**
+ * Constructor
+ *
+ * @param TokenTypeInterface $tokenType
+ * @param AccessTokenInterface $tokenStorage
+ * @param array $config
+ * @param ScopeInterface $scopeUtil
+ */
public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, $config = array(), ScopeInterface $scopeUtil = null)
{
$this->tokenType = $tokenType;
@@ -36,6 +62,14 @@ public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface
$this->scopeUtil = $scopeUtil;
}
+ /**
+ * Verify the resource request
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param null $scope
+ * @return bool
+ */
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null)
{
$token = $this->getAccessTokenData($request, $response);
@@ -71,6 +105,13 @@ public function verifyResourceRequest(RequestInterface $request, ResponseInterfa
return (bool) $token;
}
+ /**
+ * Get access token data.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return array|null
+ */
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response)
{
// Get the token parameter
@@ -83,7 +124,7 @@ public function getAccessTokenData(RequestInterface $request, ResponseInterface
} 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;
}
@@ -103,7 +144,11 @@ public function getAccessTokenData(RequestInterface $request, ResponseInterface
return null;
}
- // convenience method to allow retrieval of the token
+ /**
+ * convenience method to allow retrieval of the token.
+ *
+ * @return array
+ */
public function getToken()
{
return $this->token;
diff --git a/OAuth2/Controller/ResourceControllerInterface.php b/OAuth2/Controller/ResourceControllerInterface.php
index 6114219..0e847ca 100644
--- a/OAuth2/Controller/ResourceControllerInterface.php
+++ b/OAuth2/Controller/ResourceControllerInterface.php
@@ -10,17 +10,32 @@
* call verifyResourceRequest in order to determine if the request
* contains a valid token.
*
- * ex:
- * > if (!$resourceController->verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) {
- * > $response->send(); // authorization failed
- * > die();
- * > }
- * > return json_encode($resource); // valid token! Send the stuff!
- *
+ * @code
+ * if (!$resourceController->verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) {
+ * $response->send(); // authorization failed
+ * die();
+ * }
+ * return json_encode($resource); // valid token! Send the stuff!
+ * @endcode
*/
interface ResourceControllerInterface
{
+ /**
+ * Verify the resource request
+ *
+ * @param RequestInterface $request - Request object
+ * @param ResponseInterface $response - Response object
+ * @param string $scope
+ * @return mixed
+ */
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null);
+ /**
+ * Get access token data.
+ *
+ * @param RequestInterface $request - Request object
+ * @param ResponseInterface $response - Response object
+ * @return mixed
+ */
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response);
}
diff --git a/OAuth2/Controller/TokenController.php b/OAuth2/Controller/TokenController.php
index 46cfb17..7fdaf85 100644
--- a/OAuth2/Controller/TokenController.php
+++ b/OAuth2/Controller/TokenController.php
@@ -10,24 +10,56 @@
use OAuth2\Storage\ClientInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
+use InvalidArgumentException;
+use LogicException;
+use RuntimeException;
/**
- * @see OAuth2\Controller\TokenControllerInterface
+ * @see TokenControllerInterface
*/
class TokenController implements TokenControllerInterface
{
+ /**
+ * @var AccessTokenInterface
+ */
protected $accessToken;
+
+ /**
+ * @var array
- * $config = array(
- * 'always_issue_new_refresh_token' => true, // whether to issue a new refresh token upon successful token request
- * );
- *
+ * @param 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
+ * 'unset_refresh_token_after_use' => true // whether to unset the refresh token after after using
+ * );
+ * @endcode
*/
public function __construct(RefreshTokenInterface $storage, $config = array())
{
$this->config = array_merge(array(
- 'always_issue_new_refresh_token' => false
+ 'always_issue_new_refresh_token' => false,
+ 'unset_refresh_token_after_use' => true
), $config);
+
+ // to preserve B.C. with v1.6
+ // @see https://github.com/bshaffer/oauth2-server-php/pull/580
+ // @todo - remove in v2.0
+ if (isset($config['always_issue_new_refresh_token']) && !isset($config['unset_refresh_token_after_use'])) {
+ $this->config['unset_refresh_token_after_use'] = $config['always_issue_new_refresh_token'];
+ }
+
$this->storage = $storage;
}
- public function getQuerystringIdentifier()
+ /**
+ * @return string
+ */
+ public function getQueryStringIdentifier()
{
return 'refresh_token';
}
+ /**
+ * Validate the OAuth request
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return bool|mixed|null
+ */
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$request->request("refresh_token")) {
@@ -66,21 +95,45 @@ public function validateRequest(RequestInterface $request, ResponseInterface $re
return true;
}
+ /**
+ * Get client id
+ *
+ * @return mixed
+ */
public function getClientId()
{
return $this->refreshToken['client_id'];
}
+ /**
+ * Get user id
+ *
+ * @return mixed|null
+ */
public function getUserId()
{
return isset($this->refreshToken['user_id']) ? $this->refreshToken['user_id'] : null;
}
+ /**
+ * Get scope
+ *
+ * @return null|string
+ */
public function getScope()
{
return isset($this->refreshToken['scope']) ? $this->refreshToken['scope'] : null;
}
+ /**
+ * Create access token
+ *
+ * @param AccessTokenInterface $accessToken
+ * @param mixed $client_id - client identifier related to the access token.
+ * @param mixed $user_id - user id associated with the access token
+ * @param string $scope - scopes to be stored in space-separated string.
+ * @return array
+ */
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
/*
@@ -89,9 +142,10 @@ public function createAccessToken(AccessTokenInterface $accessToken, $client_id,
* @see http://tools.ietf.org/html/rfc6749#section-6
*/
$issueNewRefreshToken = $this->config['always_issue_new_refresh_token'];
+ $unsetRefreshToken = $this->config['unset_refresh_token_after_use'];
$token = $accessToken->createAccessToken($client_id, $user_id, $scope, $issueNewRefreshToken);
- if ($issueNewRefreshToken) {
+ if ($unsetRefreshToken) {
$this->storage->unsetRefreshToken($this->refreshToken['refresh_token']);
}
diff --git a/OAuth2/GrantType/UserCredentials.php b/OAuth2/GrantType/UserCredentials.php
index f165538..b10c2dd 100644
--- a/OAuth2/GrantType/UserCredentials.php
+++ b/OAuth2/GrantType/UserCredentials.php
@@ -6,30 +6,46 @@
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
+use LogicException;
/**
- *
* @author Brent Shaffer
- * $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
- * );
- *
+ * @param AccessTokenStorageInterface $tokenStorage - REQUIRED Storage class for saving access token information
+ * @param 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())
{
@@ -39,6 +50,13 @@ public function __construct(AccessTokenStorageInterface $tokenStorage, RefreshTo
), $config);
}
+ /**
+ * Get authorize response
+ *
+ * @param array $params
+ * @param mixed $user_id
+ * @return array
+ */
public function getAuthorizeResponse($params, $user_id = null)
{
// build the URL to redirect to
@@ -64,10 +82,11 @@ public function getAuthorizeResponse($params, $user_id = null)
/**
* 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 mixed $client_id - client identifier related to the access token.
+ * @param mixed $user_id - user ID associated with the access token
+ * @param string $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
+ * @return array
*
* @see http://tools.ietf.org/html/rfc6749#section-5
* @ingroup oauth2_section_5
@@ -107,25 +126,40 @@ public function createAccessToken($client_id, $user_id, $scope = null, $includeR
* Implementing classes may want to override this function to implement
* other access token generation schemes.
*
- * @return
- * An unique access token.
+ * @return string - A unique access token.
*
* @ingroup oauth2_section_4
*/
protected function generateAccessToken()
{
- $tokenLen = 40;
+ if (function_exists('random_bytes')) {
+ $randomData = random_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 ($randomData !== false && strlen($randomData) === 20) {
+ return bin2hex($randomData);
+ }
+ }
if (function_exists('mcrypt_create_iv')) {
- $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM);
- } elseif (function_exists('openssl_random_pseudo_bytes')) {
- $randomData = openssl_random_pseudo_bytes(100);
- } 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);
+ $randomData = mcrypt_create_iv(20, MCRYPT_DEV_URANDOM);
+ if ($randomData !== false && strlen($randomData) === 20) {
+ return bin2hex($randomData);
+ }
}
+ if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
+ $randomData = file_get_contents('/dev/urandom', false, null, 0, 20);
+ if ($randomData !== false && strlen($randomData) === 20) {
+ return bin2hex($randomData);
+ }
+ }
+ // Last resort which you probably should just get rid of:
+ $randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
- return substr(hash('sha512', $randomData), 0, $tokenLen);
+ return substr(hash('sha512', $randomData), 0, 40);
}
/**
@@ -134,8 +168,7 @@ protected function generateAccessToken()
* Implementing classes may want to override this function to implement
* other refresh token generation schemes.
*
- * @return
- * An unique refresh.
+ * @return string - A unique refresh token.
*
* @ingroup oauth2_section_4
* @see OAuth2::generateAccessToken()
@@ -144,4 +177,42 @@ protected function generateRefreshToken()
{
return $this->generateAccessToken(); // let's reuse the same scheme for token generation
}
+
+ /**
+ * Handle the revoking of refresh tokens, and access tokens if supported / desirable
+ * RFC7009 specifies that "If the server is unable to locate the token using
+ * the given hint, it MUST extend its search across all of its supported token types"
+ *
+ * @param $token
+ * @param null $tokenTypeHint
+ * @throws RuntimeException
+ * @return boolean
+ */
+ public function revokeToken($token, $tokenTypeHint = null)
+ {
+ if ($tokenTypeHint == 'refresh_token') {
+ if ($this->refreshStorage && $revoked = $this->refreshStorage->unsetRefreshToken($token)) {
+ return true;
+ }
+ }
+
+ /** @TODO remove in v2 */
+ if (!method_exists($this->tokenStorage, 'unsetAccessToken')) {
+ throw new RuntimeException(
+ sprintf('Token storage %s must implement unsetAccessToken method', get_class($this->tokenStorage)
+ ));
+ }
+
+ $revoked = $this->tokenStorage->unsetAccessToken($token);
+
+ // if a typehint is supplied and fails, try other storages
+ // @see https://tools.ietf.org/html/rfc7009#section-2.1
+ if (!$revoked && $tokenTypeHint != 'refresh_token') {
+ if ($this->refreshStorage) {
+ $revoked = $this->refreshStorage->unsetRefreshToken($token);
+ }
+ }
+
+ return $revoked;
+ }
}
diff --git a/OAuth2/ResponseType/AccessTokenInterface.php b/OAuth2/ResponseType/AccessTokenInterface.php
index 2ba4a4f..0e576df 100644
--- a/OAuth2/ResponseType/AccessTokenInterface.php
+++ b/OAuth2/ResponseType/AccessTokenInterface.php
@@ -3,7 +3,6 @@
namespace OAuth2\ResponseType;
/**
- *
* @author Brent Shaffer
*/
interface AccessTokenInterface extends ResponseTypeInterface
@@ -11,13 +10,24 @@ 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 OPTONAL scopes to be stored in space-separated string.
- * @param bool $includeRefreshToken if true, a new refresh_token will be added to the response
+ * @param mixed $client_id - client identifier related to the access token.
+ * @param mixed $user_id - user ID associated with the access token
+ * @param string $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
*/
public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true);
-}
+
+ /**
+ * Handle the revoking of refresh tokens, and access tokens if supported / desirable
+ *
+ * @param $token
+ * @param $tokenTypeHint
+ * @return mixed
+ *
+ * @todo v2.0 include this method in interface. Omitted to maintain BC in v1.x
+ */
+ //public function revokeToken($token, $tokenTypeHint);
+}
\ No newline at end of file
diff --git a/OAuth2/ResponseType/AuthorizationCode.php b/OAuth2/ResponseType/AuthorizationCode.php
index 6a305fd..b92c73c 100644
--- a/OAuth2/ResponseType/AuthorizationCode.php
+++ b/OAuth2/ResponseType/AuthorizationCode.php
@@ -5,7 +5,6 @@
use OAuth2\Storage\AuthorizationCodeInterface as AuthorizationCodeStorageInterface;
/**
- *
* @author Brent Shaffer
*/
class AuthorizationCode implements AuthorizationCodeInterface
@@ -85,10 +84,12 @@ public function enforceRedirect()
protected function generateAuthorizationCode()
{
$tokenLen = 40;
- if (function_exists('mcrypt_create_iv')) {
- $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM);
+ if (function_exists('random_bytes')) {
+ $randomData = random_bytes(100);
} elseif (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 {
diff --git a/OAuth2/ResponseType/AuthorizationCodeInterface.php b/OAuth2/ResponseType/AuthorizationCodeInterface.php
index df777e2..4f0a29d 100644
--- a/OAuth2/ResponseType/AuthorizationCodeInterface.php
+++ b/OAuth2/ResponseType/AuthorizationCodeInterface.php
@@ -3,7 +3,6 @@
namespace OAuth2\ResponseType;
/**
- *
* @author Brent Shaffer
*/
interface AuthorizationCodeInterface extends ResponseTypeInterface
@@ -17,11 +16,12 @@ public function enforceRedirect();
/**
* 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 mixed $client_id - Client identifier related to the authorization code
+ * @param mixed $user_id - User ID associated with the authorization code
+ * @param string $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 string $scope - OPTIONAL Scopes to be stored in space-separated string.
+ * @return string
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @ingroup oauth2_section_4
diff --git a/OAuth2/ResponseType/CryptoToken.php b/OAuth2/ResponseType/JwtAccessToken.php
similarity index 53%
rename from OAuth2/ResponseType/CryptoToken.php
rename to OAuth2/ResponseType/JwtAccessToken.php
index 08ff853..0ee3708 100644
--- a/OAuth2/ResponseType/CryptoToken.php
+++ b/OAuth2/ResponseType/JwtAccessToken.php
@@ -10,25 +10,28 @@
use OAuth2\Storage\Memory;
/**
- *
* @author Brent Shaffer
*/
-class CryptoToken extends AccessToken
+class JwtAccessToken extends AccessToken
{
protected $publicKeyStorage;
protected $encryptionUtil;
/**
- * @param $config
- * - store_encrypted_token_string (bool true)
- * whether the entire encrypted string is stored,
- * or just the token ID is stored
+ * @param PublicKeyInterface $publicKeyStorage -
+ * @param AccessTokenStorageInterface $tokenStorage -
+ * @param RefreshTokenInterface $refreshStorage -
+ * @param array $config - array with key store_encrypted_token_string (bool true)
+ * whether the entire encrypted string is stored,
+ * or just the token ID is stored
+ * @param EncryptionInterface $encryptionUtil -
*/
public function __construct(PublicKeyInterface $publicKeyStorage = null, AccessTokenStorageInterface $tokenStorage = null, RefreshTokenInterface $refreshStorage = null, array $config = array(), EncryptionInterface $encryptionUtil = null)
{
$this->publicKeyStorage = $publicKeyStorage;
$config = array_merge(array(
'store_encrypted_token_string' => true,
+ 'issuer' => ''
), $config);
if (is_null($tokenStorage)) {
// a pass-thru, so we can call the parent constructor
@@ -44,42 +47,31 @@ public function __construct(PublicKeyInterface $publicKeyStorage = null, AccessT
/**
* 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 mixed $client_id - Client identifier related to the access token.
+ * @param mixed $user_id - User ID associated with the access token
+ * @param string $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
+ * @return array - The access token
*
* @see http://tools.ietf.org/html/rfc6749#section-5
* @ingroup oauth2_section_5
*/
public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
{
- // token to encrypt
- $expires = time() + $this->config['access_lifetime'];
- $cryptoToken = array(
- 'id' => $this->generateAccessToken(),
- 'client_id' => $client_id,
- 'user_id' => $user_id,
- 'expires' => $expires,
- 'token_type' => $this->config['token_type'],
- 'scope' => $scope
- );
+ // payload to encrypt
+ $payload = $this->createPayload($client_id, $user_id, $scope);
/*
- * Encode the token data into a single access_token string
+ * Encode the payload data into a single JWT access_token string
*/
- $access_token = $this->encodeToken($cryptoToken, $client_id);
+ $access_token = $this->encodeToken($payload, $client_id);
/*
* Save the token to a secondary storage. This is implemented on the
- * OAuth2\Storage\CryptoToken side, and will not actually store anything,
+ * OAuth2\Storage\JwtAccessToken side, and will not actually store anything,
* if no secondary storage has been supplied
*/
- $token_to_store = $this->config['store_encrypted_token_string'] ? $access_token : $cryptoToken['id'];
+ $token_to_store = $this->config['store_encrypted_token_string'] ? $access_token : $payload['id'];
$this->tokenStorage->setAccessToken($token_to_store, $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);
// token to return to the client
@@ -109,6 +101,11 @@ public function createAccessToken($client_id, $user_id, $scope = null, $includeR
return $token;
}
+ /**
+ * @param array $token
+ * @param mixed $client_id
+ * @return mixed
+ */
protected function encodeToken(array $token, $client_id = null)
{
$private_key = $this->publicKeyStorage->getPrivateKey($client_id);
@@ -116,4 +113,47 @@ protected function encodeToken(array $token, $client_id = null)
return $this->encryptionUtil->encode($token, $private_key, $algorithm);
}
+
+ /**
+ * This function can be used to create custom JWT payloads
+ *
+ * @param mixed $client_id - Client identifier related to the access token.
+ * @param mixed $user_id - User ID associated with the access token
+ * @param string $scope - (optional) Scopes to be stored in space-separated string.
+ * @return array - The access token
+ */
+ protected function createPayload($client_id, $user_id, $scope = null)
+ {
+ // token to encrypt
+ $expires = time() + $this->config['access_lifetime'];
+ $id = $this->generateAccessToken();
+
+ $payload = array(
+ 'id' => $id, // for BC (see #591)
+ 'jti' => $id,
+ 'iss' => $this->config['issuer'],
+ 'aud' => $client_id,
+ 'sub' => $user_id,
+ 'exp' => $expires,
+ 'iat' => time(),
+ 'token_type' => $this->config['token_type'],
+ 'scope' => $scope
+ );
+
+ if (isset($this->config['jwt_extra_payload_callable'])) {
+ if (!is_callable($this->config['jwt_extra_payload_callable'])) {
+ throw new \InvalidArgumentException('jwt_extra_payload_callable is not callable');
+ }
+
+ $extra = call_user_func($this->config['jwt_extra_payload_callable'], $client_id, $user_id, $scope);
+
+ if (!is_array($extra)) {
+ throw new \InvalidArgumentException('jwt_extra_payload_callable must return array');
+ }
+
+ $payload = array_merge($extra, $payload);
+ }
+
+ return $payload;
+ }
}
diff --git a/OAuth2/ResponseType/ResponseTypeInterface.php b/OAuth2/ResponseType/ResponseTypeInterface.php
index f8e26a5..a271565 100644
--- a/OAuth2/ResponseType/ResponseTypeInterface.php
+++ b/OAuth2/ResponseType/ResponseTypeInterface.php
@@ -4,5 +4,10 @@
interface ResponseTypeInterface
{
+ /**
+ * @param array $params
+ * @param mixed $user_id
+ * @return mixed
+ */
public function getAuthorizeResponse($params, $user_id = null);
}
diff --git a/OAuth2/Scope.php b/OAuth2/Scope.php
index c44350b..3ba6e53 100644
--- a/OAuth2/Scope.php
+++ b/OAuth2/Scope.php
@@ -2,19 +2,23 @@
namespace OAuth2;
+use InvalidArgumentException;
use OAuth2\Storage\Memory;
use OAuth2\Storage\ScopeInterface as ScopeStorageInterface;
/**
-* @see OAuth2\ScopeInterface
+* @see ScopeInterface
*/
class Scope implements ScopeInterface
{
protected $storage;
/**
- * @param mixed @storage
- * Either an array of supported scopes, or an instance of OAuth2\Storage\ScopeInterface
+ * Constructor
+ *
+ * @param mixed $storage - Either an array of supported scopes, or an instance of OAuth2\Storage\ScopeInterface
+ *
+ * @throws InvalidArgumentException
*/
public function __construct($storage = null)
{
@@ -23,7 +27,7 @@ public function __construct($storage = null)
}
if (!$storage instanceof ScopeStorageInterface) {
- throw new \InvalidArgumentException("Argument 1 to OAuth2\Scope must be null, an array, or instance of OAuth2\Storage\ScopeInterface");
+ throw new InvalidArgumentException("Argument 1 to OAuth2\Scope must be null, an array, or instance of OAuth2\Storage\ScopeInterface");
}
$this->storage = $storage;
@@ -32,12 +36,10 @@ public function __construct($storage = null)
/**
* Check if everything in required scope is contained in available scope.
*
- * @param $required_scope
- * A space-separated string of scopes.
- *
- * @return
- * TRUE if everything in required scope is contained in available scope,
- * and FALSE if it isn't.
+ * @param string $required_scope - A space-separated string of scopes.
+ * @param string $available_scope - A space-separated string of scopes.
+ * @return bool - TRUE if everything in required scope is contained in available scope and FALSE
+ * if it isn't.
*
* @see http://tools.ietf.org/html/rfc6749#section-7
*
@@ -54,11 +56,8 @@ public function checkScope($required_scope, $available_scope)
/**
* Check if the provided scope exists in storage.
*
- * @param $scope
- * A space-separated string of scopes.
- *
- * @return
- * TRUE if it exists, FALSE otherwise.
+ * @param string $scope - A space-separated string of scopes.
+ * @return bool - TRUE if it exists, FALSE otherwise.
*/
public function scopeExists($scope)
{
@@ -76,12 +75,20 @@ public function scopeExists($scope)
}
}
+ /**
+ * @param RequestInterface $request
+ * @return string
+ */
public function getScopeFromRequest(RequestInterface $request)
{
// "scope" is valid if passed in either POST or QUERY
return $request->request('scope', $request->query('scope'));
}
+ /**
+ * @param null $client_id
+ * @return mixed
+ */
public function getDefaultScope($client_id = null)
{
return $this->storage->getDefaultScope($client_id);
@@ -93,8 +100,7 @@ public function getDefaultScope($client_id = null)
* In case OpenID Connect is used, these scopes must include:
* 'openid', offline_access'.
*
- * @return
- * An array of reserved scopes.
+ * @return array - An array of reserved scopes.
*/
public function getReservedScopes()
{
diff --git a/OAuth2/ScopeInterface.php b/OAuth2/ScopeInterface.php
index 5b60f9a..f65cfa7 100644
--- a/OAuth2/ScopeInterface.php
+++ b/OAuth2/ScopeInterface.php
@@ -7,19 +7,17 @@
/**
* Class to handle scope implementation logic
*
- * @see OAuth2\Storage\ScopeInterface
+ * @see \OAuth2\Storage\ScopeInterface
*/
interface ScopeInterface extends ScopeStorageInterface
{
/**
* Check if everything in required scope is contained in available scope.
*
- * @param $required_scope
- * A space-separated string of scopes.
- *
- * @return
- * TRUE if everything in required scope is contained in available scope,
- * and FALSE if it isn't.
+ * @param string $required_scope - A space-separated string of scopes.
+ * @param string $available_scope - A space-separated string of scopes.
+ * @return boolean - TRUE if everything in required scope is contained in available scope and FALSE
+ * if it isn't.
*
* @see http://tools.ietf.org/html/rfc6749#section-7
*
@@ -30,11 +28,8 @@ public function checkScope($required_scope, $available_scope);
/**
* Return scope info from request
*
- * @param OAuth2\RequestInterface
- * Request object to check
- *
- * @return
- * string representation of requested scope
+ * @param RequestInterface $request - Request object to check
+ * @return string - representation of requested scope
*/
public function getScopeFromRequest(RequestInterface $request);
}
diff --git a/OAuth2/Server.php b/OAuth2/Server.php
index 3434a7b..cf040c2 100644
--- a/OAuth2/Server.php
+++ b/OAuth2/Server.php
@@ -19,9 +19,10 @@
use OAuth2\ResponseType\ResponseTypeInterface;
use OAuth2\ResponseType\AuthorizationCode as AuthorizationCodeResponseType;
use OAuth2\ResponseType\AccessToken;
-use OAuth2\ResponseType\CryptoToken;
+use OAuth2\ResponseType\JwtAccessToken;
+use OAuth2\OpenID\ResponseType\CodeIdToken;
use OAuth2\OpenID\ResponseType\IdToken;
-use OAuth2\OpenID\ResponseType\TokenIdToken;
+use OAuth2\OpenID\ResponseType\IdTokenToken;
use OAuth2\TokenType\TokenTypeInterface;
use OAuth2\TokenType\Bearer;
use OAuth2\GrantType\GrantTypeInterface;
@@ -29,40 +30,89 @@
use OAuth2\GrantType\ClientCredentials;
use OAuth2\GrantType\RefreshToken;
use OAuth2\GrantType\AuthorizationCode;
-use OAuth2\Storage\CryptoToken as CryptoTokenStorage;
-use OAuth2\Storage\CryptoTokenInterface;
+use OAuth2\Storage\ClientCredentialsInterface;
+use OAuth2\Storage\ClientInterface;
+use OAuth2\Storage\JwtAccessToken as JwtAccessTokenStorage;
+use OAuth2\Storage\JwtAccessTokenInterface;
+use InvalidArgumentException;
+use LogicException;
/**
* Server class for OAuth2
* This class serves as a convience class which wraps the other Controller classes
*
-* @see OAuth2\Controller\ResourceController
-* @see OAuth2\Controller\AuthorizeController
-* @see OAuth2\Controller\TokenController
+* @see \OAuth2\Controller\ResourceController
+* @see \OAuth2\Controller\AuthorizeController
+* @see \OAuth2\Controller\TokenController
*/
class Server implements ResourceControllerInterface,
AuthorizeControllerInterface,
TokenControllerInterface,
UserInfoControllerInterface
{
- // misc properties
+ /**
+ * @var ResponseInterface
+ */
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;
+ /**
+ * @var array
+ */
+ protected $grantTypes = array();
+
+ /**
+ * @var array
+ */
+ protected $responseTypes = array();
+
+ /**
+ * @var TokenTypeInterface
+ */
protected $tokenType;
+
+ /**
+ * @var ScopeInterface
+ */
protected $scopeUtil;
+
+ /**
+ * @var ClientAssertionTypeInterface
+ */
protected $clientAssertionType;
+ /**
+ * @var array
+ */
protected $storageMap = array(
'access_token' => 'OAuth2\Storage\AccessTokenInterface',
'authorization_code' => 'OAuth2\Storage\AuthorizationCodeInterface',
@@ -75,23 +125,28 @@ class Server implements ResourceControllerInterface,
'jwt_bearer' => 'OAuth2\Storage\JWTBearerInterface',
'scope' => 'OAuth2\Storage\ScopeInterface',
);
+
+ /**
+ * @var array
+ */
protected $responseTypeMap = array(
'token' => 'OAuth2\ResponseType\AccessTokenInterface',
'code' => 'OAuth2\ResponseType\AuthorizationCodeInterface',
'id_token' => 'OAuth2\OpenID\ResponseType\IdTokenInterface',
- 'token id_token' => 'OAuth2\OpenID\ResponseType\TokenIdTokenInterface',
+ 'id_token token' => 'OAuth2\OpenID\ResponseType\IdTokenTokenInterface',
+ 'code id_token' => 'OAuth2\OpenID\ResponseType\CodeIdTokenInterface',
);
/**
- * @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
+ * @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 "token" for
+ * Access Token and Authorization Code response types
+ * @param TokenTypeInterface $tokenType The token type object to use. Valid token types are "bearer" and "mac"
+ * @param ScopeInterface $scopeUtil The scope utility class to use to validate scope
+ * @param ClientAssertionTypeInterface $clientAssertionType The method in which to verify the client identity. Default is HttpBasic
*
* @ingroup oauth2_section_7
*/
@@ -105,7 +160,8 @@ public function __construct($storage = array(), array $config = array(), array $
// merge all config values. These get passed to our controller objects
$this->config = array_merge(array(
- 'use_crypto_tokens' => false,
+ 'use_jwt_access_tokens' => false,
+ 'jwt_extra_payload_callable' => null,
'store_encrypted_token_string' => true,
'use_openid_connect' => false,
'id_lifetime' => 3600,
@@ -119,19 +175,29 @@ public function __construct($storage = array(), array $config = array(), array $
'allow_credentials_in_request_body' => true,
'allow_public_clients' => true,
'always_issue_new_refresh_token' => false,
+ 'unset_refresh_token_after_use' => true,
), $config);
foreach ($grantTypes as $key => $grantType) {
$this->addGrantType($grantType, $key);
}
+
foreach ($responseTypes as $key => $responseType) {
$this->addResponseType($responseType, $key);
}
+
$this->tokenType = $tokenType;
$this->scopeUtil = $scopeUtil;
$this->clientAssertionType = $clientAssertionType;
+
+ if ($this->config['use_openid_connect']) {
+ $this->validateOpenIdConnect();
+ }
}
+ /**
+ * @return AuthorizeControllerInterface
+ */
public function getAuthorizeController()
{
if (is_null($this->authorizeController)) {
@@ -141,6 +207,9 @@ public function getAuthorizeController()
return $this->authorizeController;
}
+ /**
+ * @return TokenController
+ */
public function getTokenController()
{
if (is_null($this->tokenController)) {
@@ -150,6 +219,9 @@ public function getTokenController()
return $this->tokenController;
}
+ /**
+ * @return ResourceControllerInterface
+ */
public function getResourceController()
{
if (is_null($this->resourceController)) {
@@ -159,6 +231,9 @@ public function getResourceController()
return $this->resourceController;
}
+ /**
+ * @return UserInfoControllerInterface
+ */
public function getUserInfoController()
{
if (is_null($this->userInfoController)) {
@@ -169,7 +244,7 @@ public function getUserInfoController()
}
/**
- * every getter deserves a setter
+ * @param AuthorizeControllerInterface $authorizeController
*/
public function setAuthorizeController(AuthorizeControllerInterface $authorizeController)
{
@@ -177,7 +252,7 @@ public function setAuthorizeController(AuthorizeControllerInterface $authorizeCo
}
/**
- * every getter deserves a setter
+ * @param TokenControllerInterface $tokenController
*/
public function setTokenController(TokenControllerInterface $tokenController)
{
@@ -185,7 +260,7 @@ public function setTokenController(TokenControllerInterface $tokenController)
}
/**
- * every getter deserves a setter
+ * @param ResourceControllerInterface $resourceController
*/
public function setResourceController(ResourceControllerInterface $resourceController)
{
@@ -193,7 +268,7 @@ public function setResourceController(ResourceControllerInterface $resourceContr
}
/**
- * every getter deserves a setter
+ * @param UserInfoControllerInterface $userInfoController
*/
public function setUserInfoController(UserInfoControllerInterface $userInfoController)
{
@@ -204,14 +279,12 @@ public function setUserInfoController(UserInfoControllerInterface $userInfoContr
* Return claims about the authenticated end-user.
* This would be called from the "/UserInfo" endpoint as defined in the spec.
*
- * @param $request - OAuth2\RequestInterface
- * Request object to grant access token
+ * @param RequestInterface $request - Request object to grant access token
+ * @param ResponseInterface $response - Response object containing error messages (failure) or user claims (success)
+ * @return ResponseInterface
*
- * @param $response - OAuth2\ResponseInterface
- * Response object containing error messages (failure) or user claims (success)
- *
- * @throws InvalidArgumentException
- * @throws LogicException
+ * @throws \InvalidArgumentException
+ * @throws \LogicException
*
* @see http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
*/
@@ -228,14 +301,12 @@ public function handleUserInfoRequest(RequestInterface $request, ResponseInterfa
* 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
- * Request object to grant access token
+ * @param RequestInterface $request - Request object to grant access token
+ * @param ResponseInterface $response - Response object containing error messages (failure) or access token (success)
+ * @return ResponseInterface
*
- * @param $response - OAuth2\ResponseInterface
- * Response object containing error messages (failure) or access token (success)
- *
- * @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
@@ -251,6 +322,11 @@ public function handleTokenRequest(RequestInterface $request, ResponseInterface
return $this->response;
}
+ /**
+ * @param RequestInterface $request - Request object to grant access token
+ * @param ResponseInterface $response - Response object
+ * @return mixed
+ */
public function grantAccessToken(RequestInterface $request, ResponseInterface $response = null)
{
$this->response = is_null($response) ? new Response() : $response;
@@ -259,6 +335,24 @@ public function grantAccessToken(RequestInterface $request, ResponseInterface $r
return $value;
}
+ /**
+ * Handle a revoke token request
+ * This would be called from the "/revoke" endpoint as defined in the draft Token Revocation spec
+ *
+ * @see https://tools.ietf.org/html/rfc7009#section-2
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return Response|ResponseInterface
+ */
+ public function handleRevokeRequest(RequestInterface $request, ResponseInterface $response = null)
+ {
+ $this->response = is_null($response) ? new Response() : $response;
+ $this->getTokenController()->handleRevokeRequest($request, $this->response);
+
+ return $this->response;
+ }
+
/**
* Redirect the user appropriately after approval.
*
@@ -266,22 +360,18 @@ public function grantAccessToken(RequestInterface $request, ResponseInterface $r
* authorization server should call this function to redirect the user
* appropriately.
*
- * @param $request
- * The request should have the follow parameters set in the querystring:
- * - response_type: The requested response: an access token, an
- * authorization code, or both.
+ * @param RequestInterface $request - The request should have the follow parameters set in the querystring:
+ * - response_type: The requested response: an access token, an authorization code, or both.
* - client_id: The client identifier as described in Section 2.
- * - redirect_uri: An absolute URI to which the authorization server
- * will redirect the user-agent to when the end-user authorization
- * step is completed.
- * - scope: (optional) The scope of the resource request expressed as a
- * list of space-delimited strings.
- * - state: (optional) An opaque value used by the client to maintain
- * state between the request and callback.
- * @param $is_authorized
- * TRUE or FALSE depending on whether the user authorized the access.
- * @param $user_id
- * Identifier of user who authorized the client
+ * - redirect_uri: An absolute URI to which the authorization server will redirect the user-agent to when the
+ * end-user authorization step is completed.
+ * - scope: (optional) The scope of the resource request expressed as a 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 - Response object
+ * @param bool $is_authorized - TRUE or FALSE depending on whether the user authorized the access.
+ * @param mixed $user_id - Identifier of user who authorized the client
+ * @return ResponseInterface
*
* @see http://tools.ietf.org/html/rfc6749#section-4
*
@@ -298,14 +388,17 @@ public function handleAuthorizeRequest(RequestInterface $request, ResponseInterf
/**
* Pull the authorization request data out of the HTTP request.
* - The redirect_uri is OPTIONAL as per draft 20. But your implementation can enforce it
- * by setting $config['enforce_redirect'] to true.
+ * by setting $config['enforce_redirect'] to true.
* - The state is OPTIONAL but recommended to enforce CSRF. Draft 21 states, however, that
- * CSRF protection is MANDATORY. You can enforce this by setting the $config['enforce_state'] to true.
+ * CSRF protection is MANDATORY. You can enforce this by setting the $config['enforce_state'] to true.
*
* The draft specifies that the parameters should be retrieved from GET, override the Response
* object to change this
*
- * @return
+ * @param RequestInterface $request - Request object
+ * @param ResponseInterface $response - Response object
+ * @return bool
+ *
* The authorization parameters so the authorization server can prompt
* the user for approval if valid.
*
@@ -322,6 +415,12 @@ public function validateAuthorizeRequest(RequestInterface $request, ResponseInte
return $value;
}
+ /**
+ * @param RequestInterface $request - Request object
+ * @param ResponseInterface $response - Response object
+ * @param string $scope - Scope
+ * @return mixed
+ */
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response = null, $scope = null)
{
$this->response = is_null($response) ? new Response() : $response;
@@ -330,6 +429,11 @@ public function verifyResourceRequest(RequestInterface $request, ResponseInterfa
return $value;
}
+ /**
+ * @param RequestInterface $request - Request object
+ * @param ResponseInterface $response - Response object
+ * @return mixed
+ */
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response = null)
{
$this->response = is_null($response) ? new Response() : $response;
@@ -338,28 +442,31 @@ public function getAccessTokenData(RequestInterface $request, ResponseInterface
return $value;
}
- public function addGrantType(GrantTypeInterface $grantType, $key = null)
+ /**
+ * @param GrantTypeInterface $grantType
+ * @param mixed $identifier
+ */
+ public function addGrantType(GrantTypeInterface $grantType, $identifier = null)
{
- if (is_string($key)) {
- $this->grantTypes[$key] = $grantType;
- } else {
- $this->grantTypes[$grantType->getQuerystringIdentifier()] = $grantType;
+ if (!is_string($identifier)) {
+ $identifier = $grantType->getQueryStringIdentifier();
}
+ $this->grantTypes[$identifier] = $grantType;
+
// persist added grant type down to TokenController
if (!is_null($this->tokenController)) {
- $this->getTokenController()->addGrantType($grantType);
+ $this->getTokenController()->addGrantType($grantType, $identifier);
}
}
/**
* Set a storage object for the server
*
- * @param $storage
- * An object implementing one of the Storage interfaces
- * @param $key
- * If null, the storage is set to the key of each storage interface it implements
+ * @param object $storage - An object implementing one of the Storage interfaces
+ * @param mixed $key - If null, the storage is set to the key of each storage interface it implements
*
+ * @throws InvalidArgumentException
* @see storageMap
*/
public function addStorage($storage, $key = null)
@@ -373,11 +480,11 @@ public function addStorage($storage, $key = null)
// special logic to handle "client" and "client_credentials" strangeness
if ($key === 'client' && !isset($this->storages['client_credentials'])) {
- if ($storage instanceof \OAuth2\Storage\ClientCredentialsInterface) {
+ if ($storage instanceof ClientCredentialsInterface) {
$this->storages['client_credentials'] = $storage;
}
} elseif ($key === 'client_credentials' && !isset($this->storages['client'])) {
- if ($storage instanceof \OAuth2\Storage\ClientInterface) {
+ if ($storage instanceof ClientInterface) {
$this->storages['client'] = $storage;
}
}
@@ -398,8 +505,16 @@ public function addStorage($storage, $key = null)
}
}
+ /**
+ * @param ResponseTypeInterface $responseType
+ * @param mixed $key
+ *
+ * @throws InvalidArgumentException
+ */
public function addResponseType(ResponseTypeInterface $responseType, $key = null)
{
+ $key = $this->normalizeResponseType($key);
+
if (isset($this->responseTypeMap[$key])) {
if (!$responseType instanceof $this->responseTypeMap[$key]) {
throw new \InvalidArgumentException(sprintf('responseType of type "%s" must implement interface "%s"', $key, $this->responseTypeMap[$key]));
@@ -422,6 +537,9 @@ public function addResponseType(ResponseTypeInterface $responseType, $key = null
}
}
+ /**
+ * @return ScopeInterface
+ */
public function getScopeUtil()
{
if (!$this->scopeUtil) {
@@ -433,17 +551,21 @@ public function getScopeUtil()
}
/**
- * every getter deserves a setter
+ * @param ScopeInterface $scopeUtil
*/
public function setScopeUtil($scopeUtil)
{
$this->scopeUtil = $scopeUtil;
}
+ /**
+ * @return AuthorizeControllerInterface
+ * @throws LogicException
+ */
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();
@@ -451,7 +573,7 @@ protected function createDefaultAuthorizeController()
if ($this->config['use_openid_connect'] && !isset($this->responseTypes['id_token'])) {
$this->responseTypes['id_token'] = $this->createDefaultIdTokenResponseType();
if ($this->config['allow_implicit']) {
- $this->responseTypes['token id_token'] = $this->createDefaultTokenIdTokenResponseType();
+ $this->responseTypes['id_token token'] = $this->createDefaultIdTokenTokenResponseType();
}
}
@@ -464,6 +586,10 @@ protected function createDefaultAuthorizeController()
return new AuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil());
}
+ /**
+ * @return TokenControllerInterface
+ * @throws LogicException
+ */
protected function createDefaultTokenController()
{
if (0 == count($this->grantTypes)) {
@@ -475,7 +601,7 @@ protected function createDefaultTokenController()
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);
@@ -485,7 +611,7 @@ protected function createDefaultTokenController()
}
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();
@@ -493,15 +619,19 @@ protected function createDefaultTokenController()
return new TokenController($accessTokenResponseType, $this->storages['client'], $this->grantTypes, $this->clientAssertionType, $this->getScopeUtil());
}
+ /**
+ * @return ResourceControllerInterface
+ * @throws LogicException
+ */
protected function createDefaultResourceController()
{
- if ($this->config['use_crypto_tokens']) {
- // overwrites access token storage with crypto token storage if "use_crypto_tokens" is set
- if (!isset($this->storages['access_token']) || !$this->storages['access_token'] instanceof CryptoTokenInterface) {
- $this->storages['access_token'] = $this->createDefaultCryptoTokenStorage();
+ if ($this->config['use_jwt_access_tokens']) {
+ // overwrites access token storage with crypto token storage if "use_jwt_access_tokens" is set
+ if (!isset($this->storages['access_token']) || !$this->storages['access_token'] instanceof JwtAccessTokenInterface) {
+ $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 CryptoTokens 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) {
@@ -513,19 +643,23 @@ protected function createDefaultResourceController()
return new ResourceController($this->tokenType, $this->storages['access_token'], $config, $this->getScopeUtil());
}
+ /**
+ * @return UserInfoControllerInterface
+ * @throws LogicException
+ */
protected function createDefaultUserInfoController()
{
- if ($this->config['use_crypto_tokens']) {
- // overwrites access token storage with crypto token storage if "use_crypto_tokens" is set
- if (!isset($this->storages['access_token']) || !$this->storages['access_token'] instanceof CryptoTokenInterface) {
- $this->storages['access_token'] = $this->createDefaultCryptoTokenStorage();
+ if ($this->config['use_jwt_access_tokens']) {
+ // overwrites access token storage with crypto token storage if "use_jwt_access_tokens" is set
+ if (!isset($this->storages['access_token']) || !$this->storages['access_token'] instanceof JwtAccessTokenInterface) {
+ $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 CryptoTokens 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) {
@@ -537,6 +671,9 @@ protected function createDefaultUserInfoController()
return new UserInfoController($this->tokenType, $this->storages['access_token'], $this->storages['user_claims'], $config, $this->getScopeUtil());
}
+ /**
+ * @return Bearer
+ */
protected function getDefaultTokenType()
{
$config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name')));
@@ -544,6 +681,10 @@ protected function getDefaultTokenType()
return new Bearer($config);
}
+ /**
+ * @return array
+ * @throws LogicException
+ */
protected function getDefaultResponseTypes()
{
$responseTypes = array();
@@ -555,7 +696,7 @@ protected function getDefaultResponseTypes()
if ($this->config['use_openid_connect']) {
$responseTypes['id_token'] = $this->getIdTokenResponseType();
if ($this->config['allow_implicit']) {
- $responseTypes['token id_token'] = $this->getTokenIdTokenResponseType();
+ $responseTypes['id_token token'] = $this->getIdTokenTokenResponseType();
}
}
@@ -563,21 +704,26 @@ protected function getDefaultResponseTypes()
$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']);
} else {
$responseTypes['code'] = new AuthorizationCodeResponseType($this->storages['authorization_code'], $config);
}
}
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;
}
+ /**
+ * @return array
+ * @throws LogicException
+ */
protected function getDefaultGrantTypes()
{
$grantTypes = array();
@@ -592,14 +738,14 @@ protected function getDefaultGrantTypes()
}
if (isset($this->storages['refresh_token'])) {
- $config = array_intersect_key($this->config, array('always_issue_new_refresh_token' => ''));
+ $config = array_intersect_key($this->config, array_flip(explode(' ', 'always_issue_new_refresh_token unset_refresh_token_after_use')));
$grantTypes['refresh_token'] = new RefreshToken($this->storages['refresh_token'], $config);
}
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 {
@@ -608,25 +754,31 @@ protected function getDefaultGrantTypes()
}
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;
}
+ /**
+ * @return AccessToken
+ */
protected function getAccessTokenResponseType()
{
if (isset($this->responseTypes['token'])) {
return $this->responseTypes['token'];
}
- if ($this->config['use_crypto_tokens']) {
- return $this->createDefaultCryptoTokenResponseType();
+ if ($this->config['use_jwt_access_tokens']) {
+ return $this->createDefaultJwtAccessTokenResponseType();
}
return $this->createDefaultAccessTokenResponseType();
}
+ /**
+ * @return IdToken
+ */
protected function getIdTokenResponseType()
{
if (isset($this->responseTypes['id_token'])) {
@@ -636,38 +788,47 @@ protected function getIdTokenResponseType()
return $this->createDefaultIdTokenResponseType();
}
- protected function getTokenIdTokenResponseType()
+ /**
+ * @return IdTokenToken
+ */
+ protected function getIdTokenTokenResponseType()
{
- if (isset($this->responseTypes['token id_token'])) {
- return $this->responseTypes['token id_token'];
+ if (isset($this->responseTypes['id_token token'])) {
+ return $this->responseTypes['id_token token'];
}
- return $this->createDefaultTokenIdTokenResponseType();
+ return $this->createDefaultIdTokenTokenResponseType();
}
/**
* For Resource Controller
+ *
+ * @return JwtAccessTokenStorage
+ * @throws LogicException
*/
- protected function createDefaultCryptoTokenStorage()
+ 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'])) {
$tokenStorage = $this->storages['access_token'];
}
// wrap the access token storage as required.
- return new CryptoTokenStorage($this->storages['public_key'], $tokenStorage);
+ return new JwtAccessTokenStorage($this->storages['public_key'], $tokenStorage);
}
/**
* For Authorize and Token Controllers
+ *
+ * @return JwtAccessToken
+ * @throws LogicException
*/
- protected function createDefaultCryptoTokenResponseType()
+ 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;
@@ -680,15 +841,19 @@ protected function createDefaultCryptoTokenResponseType()
$refreshStorage = $this->storages['refresh_token'];
}
- $config = array_intersect_key($this->config, array_flip(explode(' ', 'store_encrypted_token_string')));
+ $config = array_intersect_key($this->config, array_flip(explode(' ', 'store_encrypted_token_string issuer access_lifetime refresh_token_lifetime jwt_extra_payload_callable')));
- return new CryptoToken($this->storages['public_key'], $tokenStorage, $refreshStorage, $config);
+ return new JwtAccessToken($this->storages['public_key'], $tokenStorage, $refreshStorage, $config);
}
+ /**
+ * @return AccessToken
+ * @throws LogicException
+ */
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;
@@ -702,13 +867,17 @@ protected function createDefaultAccessTokenResponseType()
return new AccessToken($this->storages['access_token'], $refreshStorage, $config);
}
+ /**
+ * @return IdToken
+ * @throws LogicException
+ */
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')));
@@ -716,61 +885,133 @@ protected function createDefaultIdTokenResponseType()
return new IdToken($this->storages['user_claims'], $this->storages['public_key'], $config);
}
- protected function createDefaultTokenIdTokenResponseType()
+ /**
+ * @return IdTokenToken
+ */
+ protected function createDefaultIdTokenTokenResponseType()
+ {
+ return new IdTokenToken($this->getAccessTokenResponseType(), $this->getIdTokenResponseType());
+ }
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ protected function validateOpenIdConnect()
+ {
+ $authCodeGrant = $this->getGrantType('authorization_code');
+ if (!empty($authCodeGrant) && !$authCodeGrant instanceof OpenIDAuthorizationCodeGrantType) {
+ throw new \InvalidArgumentException('You have enabled OpenID Connect, but supplied a grant type that does not support it.');
+ }
+ }
+
+ /**
+ * @param string $name
+ * @return string
+ */
+ protected function normalizeResponseType($name)
{
- return new TokenIdToken($this->getAccessTokenResponseType(), $this->getIdTokenResponseType());
+ // for multiple-valued response types - make them alphabetical
+ if (!empty($name) && false !== strpos($name, ' ')) {
+ $types = explode(' ', $name);
+ sort($types);
+ $name = implode(' ', $types);
+ }
+
+ return $name;
}
+ /**
+ * @return mixed
+ */
public function getResponse()
{
return $this->response;
}
+ /**
+ * @return array
+ */
public function getStorages()
{
return $this->storages;
}
+ /**
+ * @param string $name
+ * @return object|null
+ */
public function getStorage($name)
{
return isset($this->storages[$name]) ? $this->storages[$name] : null;
}
+ /**
+ * @return array
+ */
public function getGrantTypes()
{
return $this->grantTypes;
}
+ /**
+ * @param string $name
+ * @return object|null
+ */
public function getGrantType($name)
{
return isset($this->grantTypes[$name]) ? $this->grantTypes[$name] : null;
}
+ /**
+ * @return array
+ */
public function getResponseTypes()
{
return $this->responseTypes;
}
+ /**
+ * @param string $name
+ * @return object|null
+ */
public function getResponseType($name)
{
+ // for multiple-valued response types - make them alphabetical
+ $name = $this->normalizeResponseType($name);
+
return isset($this->responseTypes[$name]) ? $this->responseTypes[$name] : null;
}
+ /**
+ * @return TokenTypeInterface
+ */
public function getTokenType()
{
return $this->tokenType;
}
+ /**
+ * @return ClientAssertionTypeInterface
+ */
public function getClientAssertionType()
{
return $this->clientAssertionType;
}
+ /**
+ * @param string $name
+ * @param mixed $value
+ */
public function setConfig($name, $value)
{
$this->config[$name] = $value;
}
+ /**
+ * @param string $name
+ * @param mixed $default
+ * @return mixed
+ */
public function getConfig($name, $default = null)
{
return isset($this->config[$name]) ? $this->config[$name] : $default;
diff --git a/OAuth2/Storage/AccessTokenInterface.php b/OAuth2/Storage/AccessTokenInterface.php
index d382f89..22428f2 100644
--- a/OAuth2/Storage/AccessTokenInterface.php
+++ b/OAuth2/Storage/AccessTokenInterface.php
@@ -15,17 +15,18 @@ interface AccessTokenInterface
*
* We need to retrieve access token data as we create and verify tokens.
*
- * @param $oauth_token
- * oauth_token to be check with.
- *
- * @return
- * An associative array as below, and return NULL if the supplied oauth_token
- * is invalid:
- * - expires: Stored expiration in unix timestamp.
- * - client_id: (optional) Stored client identifier.
- * - user_id: (optional) Stored user identifier.
- * - scope: (optional) Stored scope values in space-separated string.
- * - id_token: (optional) Stored id_token (if "use_openid_connect" is true).
+ * @param string $oauth_token - oauth_token to be check with.
+ *
+ * @return array|null - An associative array as below, and return NULL if the supplied oauth_token is invalid:
+ * @code
+ * array(
+ * 'expires' => $expires, // Stored expiration in unix timestamp.
+ * 'client_id' => $client_id, // (optional) Stored client identifier.
+ * 'user_id' => $user_id, // (optional) Stored user identifier.
+ * 'scope' => $scope, // (optional) Stored scope values in space-separated string.
+ * 'id_token' => $id_token // (optional) Stored id_token (if "use_openid_connect" is true).
+ * );
+ * @endcode
*
* @ingroup oauth2_section_7
*/
@@ -36,13 +37,29 @@ public function getAccessToken($oauth_token);
*
* 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 string $oauth_token - oauth_token to be stored.
+ * @param mixed $client_id - client identifier to be stored.
+ * @param mixed $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
*/
public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null);
-}
+
+ /**
+ * Expire an access token.
+ *
+ * This is not explicitly required in the spec, but if defined in a draft RFC for token
+ * revoking (RFC 7009) https://tools.ietf.org/html/rfc7009
+ *
+ * @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
+ */
+ //public function unsetAccessToken($access_token);
+}
\ No newline at end of file
diff --git a/OAuth2/Storage/AuthorizationCodeInterface.php b/OAuth2/Storage/AuthorizationCodeInterface.php
index 3beb0e4..2dbc138 100644
--- a/OAuth2/Storage/AuthorizationCodeInterface.php
+++ b/OAuth2/Storage/AuthorizationCodeInterface.php
@@ -59,19 +59,19 @@ public function getAuthorizationCode($code);
*
* Required for OAuth2::GRANT_TYPE_AUTH_CODE.
*
- * @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.
+ * @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
*/
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
*
diff --git a/OAuth2/Storage/Cassandra.php b/OAuth2/Storage/Cassandra.php
index 9587b4c..e60e9d3 100644
--- a/OAuth2/Storage/Cassandra.php
+++ b/OAuth2/Storage/Cassandra.php
@@ -5,25 +5,27 @@
use phpcassa\ColumnFamily;
use phpcassa\ColumnSlice;
use phpcassa\Connection\ConnectionPool;
+use OAuth2\OpenID\Storage\UserClaimsInterface;
use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
+use InvalidArgumentException;
/**
* Cassandra storage for all storage types
*
- * To use, install "thobbs/phpcassa" via composer
+ * To use, install "thobbs/phpcassa" via composer:
*
- * composer require thobbs/phpcassa:dev-master
+ * composer require thobbs/phpcassa:dev-master
*
*
- * Once this is done, instantiate the
+ * Once this is done, instantiate the connection:
*
- * $cassandra = new \phpcassa\Connection\ConnectionPool('oauth2_server', array('127.0.0.1:9160'));
+ * $cassandra = new \phpcassa\Connection\ConnectionPool('oauth2_server', array('127.0.0.1:9160'));
*
*
* Then, register the storage client:
*
- * $storage = new OAuth2\Storage\Cassandra($cassandra);
- * $storage->setClientDetails($client_id, $client_secret, $redirect_uri);
+ * $storage = new OAuth2\Storage\Cassandra($cassandra);
+ * $storage->setClientDetails($client_id, $client_secret, $redirect_uri);
*
*
* @see test/lib/OAuth2/Storage/Bootstrap::getCassandraStorage
@@ -35,22 +37,30 @@ class Cassandra implements AuthorizationCodeInterface,
RefreshTokenInterface,
JwtBearerInterface,
ScopeInterface,
+ PublicKeyInterface,
+ UserClaimsInterface,
OpenIDAuthorizationCodeInterface
{
private $cache;
- /* The cassandra client */
+ /**
+ * @var ConnectionPool
+ */
protected $cassandra;
- /* Configuration array */
+ /**
+ * @var array
+ */
protected $config;
/**
* Cassandra Storage! uses phpCassa
*
- * @param \phpcassa\ConnectionPool $cassandra
- * @param array $config
+ * @param ConnectionPool|array $connection
+ * @param array $config
+ *
+ * @throws InvalidArgumentException
*/
public function __construct($connection = array(), array $config = array())
{
@@ -58,7 +68,7 @@ public function __construct($connection = array(), array $config = array())
$this->cassandra = $connection;
} else {
if (!is_array($connection)) {
- throw new \InvalidArgumentException('First argument to OAuth2\Storage\Mongo must be an instance of MongoDB or a configuration array');
+ throw new InvalidArgumentException('First argument to OAuth2\Storage\Cassandra must be an instance of phpcassa\Connection\ConnectionPool or a configuration array');
}
$connection = array_merge(array(
'keyspace' => 'oauth2',
@@ -80,19 +90,24 @@ public function __construct($connection = array(), array $config = array())
'user_key' => 'oauth_users:',
'jwt_key' => 'oauth_jwt:',
'scope_key' => 'oauth_scopes:',
+ 'public_key_key' => 'oauth_public_keys:',
), $config);
}
+ /**
+ * @param $key
+ * @return bool|mixed
+ */
protected function getValue($key)
{
if (isset($this->cache[$key])) {
return $this->cache[$key];
}
-
$cf = new ColumnFamily($this->cassandra, $this->config['column_family']);
try {
- $value = array_shift($cf->get($key, new ColumnSlice("", "")));
+ $value = $cf->get($key, new ColumnSlice("", ""));
+ $value = array_shift($value);
} catch (\cassandra\NotFoundException $e) {
return false;
}
@@ -100,6 +115,12 @@ protected function getValue($key)
return json_decode($value, true);
}
+ /**
+ * @param $key
+ * @param $value
+ * @param int $expire
+ * @return bool
+ */
protected function setValue($key, $value, $expire = 0)
{
$this->cache[$key] = $value;
@@ -127,27 +148,49 @@ protected function setValue($key, $value, $expire = 0)
return true;
}
+ /**
+ * @param $key
+ * @return bool
+ */
protected function expireValue($key)
{
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 */
+ /**
+ * @param string $code
+ * @return bool|mixed
+ */
public function getAuthorizationCode($code)
{
return $this->getValue($this->config['code_key'] . $code);
}
+ /**
+ * @param string $authorization_code
+ * @param mixed $client_id
+ * @param mixed $user_id
+ * @param string $redirect_uri
+ * @param int $expires
+ * @param string $scope
+ * @param string $id_token
+ * @return bool
+ */
public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
{
return $this->setValue(
@@ -157,6 +200,10 @@ public function setAuthorizationCode($authorization_code, $client_id, $user_id,
);
}
+ /**
+ * @param string $code
+ * @return bool
+ */
public function expireAuthorizationCode($code)
{
$key = $this->config['code_key'] . $code;
@@ -165,19 +212,51 @@ public function expireAuthorizationCode($code)
return $this->expireValue($key);
}
- /* UserCredentialsInterface */
+ /**
+ * @param string $username
+ * @param string $password
+ * @return bool
+ */
public function checkUserCredentials($username, $password)
{
- $user = $this->getUserDetails($username);
+ if ($user = $this->getUser($username)) {
+ return $this->checkPassword($user, $password);
+ }
- return $user && $user['password'] === $password;
+ return false;
}
+ /**
+ * plaintext passwords are bad! Override this for your application
+ *
+ * @param array $user
+ * @param string $password
+ * @return bool
+ */
+ protected function checkPassword($user, $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);
+ }
+
+ /**
+ * @param string $username
+ * @return array|bool|false
+ */
public function getUserDetails($username)
{
return $this->getUser($username);
}
+ /**
+ * @param string $username
+ * @return array|bool
+ */
public function getUser($username)
{
if (!$userInfo = $this->getValue($this->config['user_key'] . $username)) {
@@ -190,15 +269,28 @@ public function getUser($username)
), $userInfo);
}
+ /**
+ * @param string $username
+ * @param string $password
+ * @param string $first_name
+ * @param string $last_name
+ * @return bool
+ */
public function setUser($username, $password, $first_name = null, $last_name = null)
{
+ $password = $this->hashPassword($password);
+
return $this->setValue(
$this->config['user_key'] . $username,
compact('username', 'password', 'first_name', 'last_name')
);
}
- /* ClientCredentialsInterface */
+ /**
+ * @param mixed $client_id
+ * @param string $client_secret
+ * @return bool
+ */
public function checkClientCredentials($client_id, $client_secret = null)
{
if (!$client = $this->getClientDetails($client_id)) {
@@ -209,21 +301,37 @@ public function checkClientCredentials($client_id, $client_secret = null)
&& $client['client_secret'] == $client_secret;
}
+ /**
+ * @param $client_id
+ * @return bool
+ */
public function isPublicClient($client_id)
{
if (!$client = $this->getClientDetails($client_id)) {
return false;
}
- return empty($result['client_secret']);;
+ return empty($client['client_secret']);
}
- /* ClientInterface */
+ /**
+ * @param $client_id
+ * @return array|bool|mixed
+ */
public function getClientDetails($client_id)
{
return $this->getValue($this->config['client_key'] . $client_id);
}
+ /**
+ * @param $client_id
+ * @param null $client_secret
+ * @param null $redirect_uri
+ * @param null $grant_types
+ * @param null $scope
+ * @param null $user_id
+ * @return bool
+ */
public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null)
{
return $this->setValue(
@@ -232,6 +340,11 @@ public function setClientDetails($client_id, $client_secret = null, $redirect_ur
);
}
+ /**
+ * @param $client_id
+ * @param $grant_type
+ * @return bool
+ */
public function checkRestrictedGrantType($client_id, $grant_type)
{
$details = $this->getClientDetails($client_id);
@@ -245,12 +358,23 @@ public function checkRestrictedGrantType($client_id, $grant_type)
return true;
}
- /* RefreshTokenInterface */
+ /**
+ * @param $refresh_token
+ * @return bool|mixed
+ */
public function getRefreshToken($refresh_token)
{
return $this->getValue($this->config['refresh_token_key'] . $refresh_token);
}
+ /**
+ * @param $refresh_token
+ * @param $client_id
+ * @param $user_id
+ * @param $expires
+ * @param null $scope
+ * @return bool
+ */
public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null)
{
return $this->setValue(
@@ -260,17 +384,32 @@ public function setRefreshToken($refresh_token, $client_id, $user_id, $expires,
);
}
+ /**
+ * @param $refresh_token
+ * @return bool
+ */
public function unsetRefreshToken($refresh_token)
{
return $this->expireValue($this->config['refresh_token_key'] . $refresh_token);
}
- /* AccessTokenInterface */
+ /**
+ * @param string $access_token
+ * @return array|bool|mixed|null
+ */
public function getAccessToken($access_token)
{
return $this->getValue($this->config['access_token_key'].$access_token);
}
+ /**
+ * @param string $access_token
+ * @param mixed $client_id
+ * @param mixed $user_id
+ * @param int $expires
+ * @param null $scope
+ * @return bool
+ */
public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null)
{
return $this->setValue(
@@ -280,7 +419,19 @@ public function setAccessToken($access_token, $client_id, $user_id, $expires, $s
);
}
- /* ScopeInterface */
+ /**
+ * @param $access_token
+ * @return bool
+ */
+ public function unsetAccessToken($access_token)
+ {
+ return $this->expireValue($this->config['access_token_key'] . $access_token);
+ }
+
+ /**
+ * @param $scope
+ * @return bool
+ */
public function scopeExists($scope)
{
$scope = explode(' ', $scope);
@@ -292,6 +443,10 @@ public function scopeExists($scope)
return (count(array_diff($scope, $supportedScope)) == 0);
}
+ /**
+ * @param null $client_id
+ * @return bool|mixed
+ */
public function getDefaultScope($client_id = null)
{
if (is_null($client_id) || !$result = $this->getValue($this->config['scope_key'].'default:'.$client_id)) {
@@ -301,6 +456,13 @@ public function getDefaultScope($client_id = null)
return $result;
}
+ /**
+ * @param $scope
+ * @param null $client_id
+ * @param string $type
+ * @return bool
+ * @throws \InvalidArgumentException
+ */
public function setScope($scope, $client_id = null, $type = 'supported')
{
if (!in_array($type, array('default', 'supported'))) {
@@ -316,7 +478,11 @@ public function setScope($scope, $client_id = null, $type = 'supported')
return $this->setValue($key, $scope);
}
- /*JWTBearerInterface */
+ /**
+ * @param $client_id
+ * @param $subject
+ * @return bool|null
+ */
public function getClientKey($client_id, $subject)
{
if (!$jwt = $this->getValue($this->config['jwt_key'] . $client_id)) {
@@ -330,6 +496,12 @@ public function getClientKey($client_id, $subject)
return null;
}
+ /**
+ * @param $client_id
+ * @param $key
+ * @param null $subject
+ * @return bool
+ */
public function setClientKey($client_id, $key, $subject = null)
{
return $this->setValue($this->config['jwt_key'] . $client_id, array(
@@ -338,7 +510,10 @@ public function setClientKey($client_id, $key, $subject = null)
));
}
- /*ScopeInterface */
+ /**
+ * @param $client_id
+ * @return bool|null
+ */
public function getClientScope($client_id)
{
if (!$clientDetails = $this->getClientDetails($client_id)) {
@@ -352,15 +527,134 @@ public function getClientScope($client_id)
return null;
}
+ /**
+ * @param $client_id
+ * @param $subject
+ * @param $audience
+ * @param $expiration
+ * @param $jti
+ * @throws \Exception
+ */
public function getJti($client_id, $subject, $audience, $expiration, $jti)
{
//TODO: Needs cassandra implementation.
throw new \Exception('getJti() for the Cassandra driver is currently unimplemented.');
}
+ /**
+ * @param $client_id
+ * @param $subject
+ * @param $audience
+ * @param $expiration
+ * @param $jti
+ * @throws \Exception
+ */
public function setJti($client_id, $subject, $audience, $expiration, $jti)
{
//TODO: Needs cassandra implementation.
throw new \Exception('setJti() for the Cassandra driver is currently unimplemented.');
}
-}
+
+ /**
+ * @param string $client_id
+ * @return mixed
+ */
+ public function getPublicKey($client_id = '')
+ {
+ $public_key = $this->getValue($this->config['public_key_key'] . $client_id);
+ if (is_array($public_key)) {
+ return $public_key['public_key'];
+ }
+ $public_key = $this->getValue($this->config['public_key_key']);
+ if (is_array($public_key)) {
+ return $public_key['public_key'];
+ }
+ }
+
+ /**
+ * @param string $client_id
+ * @return mixed
+ */
+ public function getPrivateKey($client_id = '')
+ {
+ $public_key = $this->getValue($this->config['public_key_key'] . $client_id);
+ if (is_array($public_key)) {
+ return $public_key['private_key'];
+ }
+ $public_key = $this->getValue($this->config['public_key_key']);
+ if (is_array($public_key)) {
+ return $public_key['private_key'];
+ }
+ }
+
+ /**
+ * @param null $client_id
+ * @return mixed|string
+ */
+ public function getEncryptionAlgorithm($client_id = null)
+ {
+ $public_key = $this->getValue($this->config['public_key_key'] . $client_id);
+ if (is_array($public_key)) {
+ return $public_key['encryption_algorithm'];
+ }
+ $public_key = $this->getValue($this->config['public_key_key']);
+ if (is_array($public_key)) {
+ return $public_key['encryption_algorithm'];
+ }
+
+ return 'RS256';
+ }
+
+ /**
+ * @param mixed $user_id
+ * @param string $claims
+ * @return array|bool
+ */
+ public function getUserClaims($user_id, $claims)
+ {
+ $userDetails = $this->getUserDetails($user_id);
+ if (!is_array($userDetails)) {
+ 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;
+ }
+
+ /**
+ * @param $claim
+ * @param $userDetails
+ * @return array
+ */
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/OAuth2/Storage/CouchbaseDB.php b/OAuth2/Storage/CouchbaseDB.php
new file mode 100644
index 0000000..9e8148b
--- /dev/null
+++ b/OAuth2/Storage/CouchbaseDB.php
@@ -0,0 +1,331 @@
+
+ */
+class CouchbaseDB implements AuthorizationCodeInterface,
+ AccessTokenInterface,
+ ClientCredentialsInterface,
+ UserCredentialsInterface,
+ RefreshTokenInterface,
+ JwtBearerInterface,
+ OpenIDAuthorizationCodeInterface
+{
+ protected $db;
+ protected $config;
+
+ public function __construct($connection, $config = array())
+ {
+ if ($connection instanceof \Couchbase) {
+ $this->db = $connection;
+ } else {
+ if (!is_array($connection) || !is_array($connection['servers'])) {
+ throw new \InvalidArgumentException('First argument to OAuth2\Storage\CouchbaseDB must be an instance of Couchbase or a configuration array containing a server array');
+ }
+
+ $this->db = new \Couchbase($connection['servers'], (!isset($connection['username'])) ? '' : $connection['username'], (!isset($connection['password'])) ? '' : $connection['password'], $connection['bucket'], false);
+ }
+
+ $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',
+ ), $config);
+ }
+
+ // Helper function to access couchbase item by type:
+ protected function getObjectByType($name,$id)
+ {
+ return json_decode($this->db->get($this->config[$name].'-'.$id),true);
+ }
+
+ // Helper function to set couchbase item by type:
+ protected function setObjectByType($name,$id,$array)
+ {
+ $array['type'] = $name;
+
+ return $this->db->set($this->config[$name].'-'.$id,json_encode($array));
+ }
+
+ // Helper function to delete couchbase item by type, wait for persist to at least 1 node
+ protected function deleteObjectByType($name,$id)
+ {
+ $this->db->delete($this->config[$name].'-'.$id,"",1);
+ }
+
+ /* ClientCredentialsInterface */
+ public function checkClientCredentials($client_id, $client_secret = null)
+ {
+ if ($result = $this->getObjectByType('client_table',$client_id)) {
+ return $result['client_secret'] == $client_secret;
+ }
+
+ return false;
+ }
+
+ public function isPublicClient($client_id)
+ {
+ if (!$result = $this->getObjectByType('client_table',$client_id)) {
+ return false;
+ }
+
+ return empty($result['client_secret']);
+ }
+
+ /* ClientInterface */
+ public function getClientDetails($client_id)
+ {
+ $result = $this->getObjectByType('client_table',$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)) {
+
+ $this->setObjectByType('client_table',$client_id, array(
+ 'client_id' => $client_id,
+ 'client_secret' => $client_secret,
+ 'redirect_uri' => $redirect_uri,
+ 'grant_types' => $grant_types,
+ 'scope' => $scope,
+ 'user_id' => $user_id,
+ ));
+ } else {
+ $this->setObjectByType('client_table',$client_id, array(
+ 'client_id' => $client_id,
+ 'client_secret' => $client_secret,
+ 'redirect_uri' => $redirect_uri,
+ 'grant_types' => $grant_types,
+ 'scope' => $scope,
+ 'user_id' => $user_id,
+ ));
+ }
+
+ 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, $grant_types);
+ }
+
+ // if grant_types are not defined, then none are restricted
+ return true;
+ }
+
+ /* AccessTokenInterface */
+ public function getAccessToken($access_token)
+ {
+ $token = $this->getObjectByType('access_token_table',$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)) {
+ $this->setObjectByType('access_token_table',$access_token, array(
+ 'access_token' => $access_token,
+ 'client_id' => $client_id,
+ 'expires' => $expires,
+ 'user_id' => $user_id,
+ 'scope' => $scope
+ ));
+ } else {
+ $this->setObjectByType('access_token_table',$access_token, array(
+ 'access_token' => $access_token,
+ 'client_id' => $client_id,
+ 'expires' => $expires,
+ 'user_id' => $user_id,
+ 'scope' => $scope
+ ));
+ }
+
+ return true;
+ }
+
+ /* AuthorizationCodeInterface */
+ public function getAuthorizationCode($code)
+ {
+ $code = $this->getObjectByType('code_table',$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)) {
+ $this->setObjectByType('code_table',$code, array(
+ 'authorization_code' => $code,
+ 'client_id' => $client_id,
+ 'user_id' => $user_id,
+ 'redirect_uri' => $redirect_uri,
+ 'expires' => $expires,
+ 'scope' => $scope,
+ 'id_token' => $id_token,
+ ));
+ } else {
+ $this->setObjectByType('code_table',$code,array(
+ 'authorization_code' => $code,
+ 'client_id' => $client_id,
+ 'user_id' => $user_id,
+ 'redirect_uri' => $redirect_uri,
+ 'expires' => $expires,
+ 'scope' => $scope,
+ 'id_token' => $id_token,
+ ));
+ }
+
+ return true;
+ }
+
+ public function expireAuthorizationCode($code)
+ {
+ $this->deleteObjectByType('code_table',$code);
+
+ return true;
+ }
+
+ /* 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->getObjectByType('refresh_token_table',$refresh_token);
+
+ return is_null($token) ? false : $token;
+ }
+
+ public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null)
+ {
+ $this->setObjectByType('refresh_token_table',$refresh_token, array(
+ 'refresh_token' => $refresh_token,
+ 'client_id' => $client_id,
+ 'user_id' => $user_id,
+ 'expires' => $expires,
+ 'scope' => $scope
+ ));
+
+ return true;
+ }
+
+ public function unsetRefreshToken($refresh_token)
+ {
+ $this->deleteObjectByType('refresh_token_table',$refresh_token);
+
+ return true;
+ }
+
+ // plaintext passwords are bad! Override this for your application
+ protected function checkPassword($user, $password)
+ {
+ return $user['password'] == $password;
+ }
+
+ public function getUser($username)
+ {
+ $result = $this->getObjectByType('user_table',$username);
+
+ return is_null($result) ? false : $result;
+ }
+
+ public function setUser($username, $password, $firstName = null, $lastName = null)
+ {
+ if ($this->getUser($username)) {
+ $this->setObjectByType('user_table',$username, array(
+ 'username' => $username,
+ 'password' => $password,
+ 'first_name' => $firstName,
+ 'last_name' => $lastName
+ ));
+
+ } else {
+ $this->setObjectByType('user_table',$username, array(
+ 'username' => $username,
+ 'password' => $password,
+ 'first_name' => $firstName,
+ 'last_name' => $lastName
+ ));
+
+ }
+
+ return true;
+ }
+
+ public function getClientKey($client_id, $subject)
+ {
+ if (!$jwt = $this->getObjectByType('jwt_table',$client_id)) {
+ return false;
+ }
+
+ if (isset($jwt['subject']) && $jwt['subject'] == $subject) {
+ return $jwt['key'];
+ }
+
+ return false;
+ }
+
+ 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, $expiration, $jti)
+ {
+ //TODO: Needs couchbase implementation.
+ throw new \Exception('getJti() for the Couchbase driver is currently unimplemented.');
+ }
+
+ public function setJti($client_id, $subject, $audience, $expiration, $jti)
+ {
+ //TODO: Needs couchbase implementation.
+ throw new \Exception('setJti() for the Couchbase driver is currently unimplemented.');
+ }
+}
\ No newline at end of file
diff --git a/OAuth2/Storage/DynamoDB.php b/OAuth2/Storage/DynamoDB.php
index fe06428..a54cb37 100644
--- a/OAuth2/Storage/DynamoDB.php
+++ b/OAuth2/Storage/DynamoDB.php
@@ -124,7 +124,7 @@ public function getClientDetails($client_id)
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 @@ public function setAccessToken($access_token, $client_id, $user_id, $expires, $s
$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); });
+ $clientData = array_filter($clientData, 'self::isNotEmpty');
$result = $this->client->putItem(array(
'TableName' => $this->config['access_token_table'],
@@ -182,6 +182,17 @@ public function setAccessToken($access_token, $client_id, $user_id, $expires, $s
}
+ public function unsetAccessToken($access_token)
+ {
+ $result = $this->client->deleteItem(array(
+ 'TableName' => $this->config['access_token_table'],
+ 'Key' => $this->client->formatAttributes(array("access_token" => $access_token)),
+ 'ReturnValues' => 'ALL_OLD',
+ ));
+
+ return null !== $result->get('Attributes');
+ }
+
/* OAuth2\Storage\AuthorizationCodeInterface */
public function getAuthorizationCode($code)
{
@@ -208,7 +219,7 @@ public function setAuthorizationCode($authorization_code, $client_id, $user_id,
$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); });
+ $clientData = array_filter($clientData, 'self::isNotEmpty');
$result = $this->client->putItem(array(
'TableName' => $this->config['code_table'],
@@ -309,7 +320,7 @@ public function setRefreshToken($refresh_token, $client_id, $user_id, $expires,
$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); });
+ $clientData = array_filter($clientData, 'self::isNotEmpty');
$result = $this->client->putItem(array(
'TableName' => $this->config['refresh_token_table'],
@@ -332,7 +343,13 @@ public function unsetRefreshToken($refresh_token)
// 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)
@@ -353,10 +370,10 @@ public function getUser($username)
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'],
@@ -411,7 +428,7 @@ public function getDefaultScope($client_id = null)
$defaultScope[] = $item['scope']['S'];
}
- return implode(' ', $defaultScope);
+ return empty($defaultScope) ? null : implode(' ', $defaultScope);
}
return null;
@@ -515,4 +532,9 @@ private function dynamo2array($dynamodbResult)
return $result;
}
-}
+
+ private static function isNotEmpty($value)
+ {
+ return null !== $value && '' !== $value;
+ }
+}
\ No newline at end of file
diff --git a/OAuth2/Storage/CryptoToken.php b/OAuth2/Storage/JwtAccessToken.php
similarity index 68%
rename from OAuth2/Storage/CryptoToken.php
rename to OAuth2/Storage/JwtAccessToken.php
index 31ebb65..6ccacd6 100644
--- a/OAuth2/Storage/CryptoToken.php
+++ b/OAuth2/Storage/JwtAccessToken.php
@@ -6,10 +6,9 @@
use OAuth2\Encryption\Jwt;
/**
- *
* @author Brent Shaffer
*/
-class CryptoToken implements CryptoTokenInterface
+class JwtAccessToken implements JwtAccessTokenInterface
{
protected $publicKeyStorage;
protected $tokenStorage;
@@ -39,16 +38,17 @@ public function getAccessToken($oauth_token)
return false;
}
- $client_id = isset($tokenData['client_id']) ? $tokenData['client_id'] : null;
+ $client_id = isset($tokenData['aud']) ? $tokenData['aud'] : null;
$public_key = $this->publicKeyStorage->getPublicKey($client_id);
$algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
// now that we have the client_id, verify the token
- if (false === $this->encryptionUtil->decode($oauth_token, $public_key, true)) {
+ if (false === $this->encryptionUtil->decode($oauth_token, $public_key, array($algorithm))) {
return false;
}
- return $tokenData;
+ // normalize the JWT claims to the format expected by other components in this library
+ return $this->convertJwtToOAuth2($tokenData);
}
public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null)
@@ -57,4 +57,31 @@ public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $sc
return $this->tokenStorage->setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope);
}
}
-}
+
+ public function unsetAccessToken($access_token)
+ {
+ if ($this->tokenStorage) {
+ return $this->tokenStorage->unsetAccessToken($access_token);
+ }
+ }
+
+
+ // converts a JWT access token into an OAuth2-friendly format
+ protected function convertJwtToOAuth2($tokenData)
+ {
+ $keyMapping = array(
+ 'aud' => 'client_id',
+ 'exp' => 'expires',
+ 'sub' => 'user_id'
+ );
+
+ foreach ($keyMapping as $jwtKey => $oauth2Key) {
+ if (isset($tokenData[$jwtKey])) {
+ $tokenData[$oauth2Key] = $tokenData[$jwtKey];
+ unset($tokenData[$jwtKey]);
+ }
+ }
+
+ return $tokenData;
+ }
+}
\ No newline at end of file
diff --git a/OAuth2/Storage/CryptoTokenInterface.php b/OAuth2/Storage/JwtAccessTokenInterface.php
similarity index 76%
rename from OAuth2/Storage/CryptoTokenInterface.php
rename to OAuth2/Storage/JwtAccessTokenInterface.php
index 867710c..3abb2aa 100644
--- a/OAuth2/Storage/CryptoTokenInterface.php
+++ b/OAuth2/Storage/JwtAccessTokenInterface.php
@@ -8,7 +8,7 @@
*
* @author Brent Shaffer
*/
-interface CryptoTokenInterface extends AccessTokenInterface
+interface JwtAccessTokenInterface extends AccessTokenInterface
{
}
diff --git a/OAuth2/Storage/Memory.php b/OAuth2/Storage/Memory.php
index 35e6cba..2c60b71 100644
--- a/OAuth2/Storage/Memory.php
+++ b/OAuth2/Storage/Memory.php
@@ -142,7 +142,7 @@ public function getUserClaims($user_id, $claims)
// 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 @@ public function setRefreshToken($refresh_token, $client_id, $user_id, $expires,
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)
@@ -257,6 +263,17 @@ public function setAccessToken($access_token, $client_id, $user_id, $expires, $s
return true;
}
+ public function unsetAccessToken($access_token)
+ {
+ if (isset($this->accessTokens[$access_token])) {
+ unset($this->accessTokens[$access_token]);
+
+ return true;
+ }
+
+ return false;
+ }
+
public function scopeExists($scope)
{
$scope = explode(' ', trim($scope));
@@ -361,4 +378,4 @@ public function getEncryptionAlgorithm($client_id = null)
return 'RS256';
}
-}
+}
\ No newline at end of file
diff --git a/OAuth2/Storage/Mongo.php b/OAuth2/Storage/Mongo.php
index 87935fe..eea06e3 100644
--- a/OAuth2/Storage/Mongo.php
+++ b/OAuth2/Storage/Mongo.php
@@ -22,6 +22,7 @@ class Mongo implements AuthorizationCodeInterface,
UserCredentialsInterface,
RefreshTokenInterface,
JwtBearerInterface,
+ PublicKeyInterface,
OpenIDAuthorizationCodeInterface
{
protected $db;
@@ -40,16 +41,13 @@ public function __construct($connection, $config = array())
$this->db = $m->{$connection['database']};
}
- // Unix timestamps might get larger than 32 bits,
- // so let's add native support for 64 bit ints.
- ini_set('mongo.native_long', 1);
-
$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',
+ 'key_table' => 'oauth_keys',
'jwt_table' => 'oauth_jwt',
), $config);
}
@@ -101,16 +99,15 @@ public function setClientDetails($client_id, $client_secret = null, $redirect_ur
))
);
} else {
- $this->collection('client_table')->insert(
- array(
- 'client_id' => $client_id,
- 'client_secret' => $client_secret,
- 'redirect_uri' => $redirect_uri,
- 'grant_types' => $grant_types,
- 'scope' => $scope,
- 'user_id' => $user_id,
- )
+ $client = array(
+ 'client_id' => $client_id,
+ 'client_secret' => $client_secret,
+ 'redirect_uri' => $redirect_uri,
+ 'grant_types' => $grant_types,
+ 'scope' => $scope,
+ 'user_id' => $user_id,
);
+ $this->collection('client_table')->insert($client);
}
return true;
@@ -151,20 +148,28 @@ public function setAccessToken($access_token, $client_id, $user_id, $expires, $s
))
);
} else {
- $this->collection('access_token_table')->insert(
- array(
- 'access_token' => $access_token,
- 'client_id' => $client_id,
- 'expires' => $expires,
- 'user_id' => $user_id,
- 'scope' => $scope
- )
+ $token = array(
+ 'access_token' => $access_token,
+ 'client_id' => $client_id,
+ 'expires' => $expires,
+ 'user_id' => $user_id,
+ 'scope' => $scope
);
+ $this->collection('access_token_table')->insert($token);
}
return true;
}
+ public function unsetAccessToken($access_token)
+ {
+ $result = $this->collection('access_token_table')->remove(array(
+ 'access_token' => $access_token
+ ), array('w' => 1));
+
+ return $result['n'] > 0;
+ }
+
/* AuthorizationCodeInterface */
public function getAuthorizationCode($code)
@@ -190,17 +195,16 @@ public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri,
))
);
} else {
- $this->collection('code_table')->insert(
- array(
- 'authorization_code' => $code,
- 'client_id' => $client_id,
- 'user_id' => $user_id,
- 'redirect_uri' => $redirect_uri,
- 'expires' => $expires,
- 'scope' => $scope,
- 'id_token' => $id_token,
- )
+ $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,
);
+ $this->collection('code_table')->insert($token);
}
return true;
@@ -213,7 +217,6 @@ public function expireAuthorizationCode($code)
return true;
}
-
/* UserCredentialsInterface */
public function checkUserCredentials($username, $password)
{
@@ -243,27 +246,27 @@ public function getRefreshToken($refresh_token)
public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null)
{
- $this->collection('refresh_token_table')->insert(
- array(
- 'refresh_token' => $refresh_token,
- 'client_id' => $client_id,
- 'user_id' => $user_id,
- 'expires' => $expires,
- 'scope' => $scope
- )
+ $token = array(
+ 'refresh_token' => $refresh_token,
+ 'client_id' => $client_id,
+ 'user_id' => $user_id,
+ 'expires' => $expires,
+ 'scope' => $scope
);
+ $this->collection('refresh_token_table')->insert($token);
return true;
}
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
protected function checkPassword($user, $password)
{
@@ -289,14 +292,13 @@ public function setUser($username, $password, $firstName = null, $lastName = nul
))
);
} else {
- $this->collection('user_table')->insert(
- array(
- 'username' => $username,
- 'password' => $password,
- 'first_name' => $firstName,
- 'last_name' => $lastName
- )
+ $user = array(
+ 'username' => $username,
+ 'password' => $password,
+ 'first_name' => $firstName,
+ 'last_name' => $lastName
);
+ $this->collection('user_table')->insert($user);
}
return true;
@@ -336,4 +338,55 @@ public function setJti($client_id, $subject, $audience, $expiration, $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'];
+ }
}
diff --git a/OAuth2/Storage/MongoDB.php b/OAuth2/Storage/MongoDB.php
new file mode 100644
index 0000000..64f740f
--- /dev/null
+++ b/OAuth2/Storage/MongoDB.php
@@ -0,0 +1,380 @@
+
+ */
+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]};
+ }
+}
\ No newline at end of file
diff --git a/OAuth2/Storage/Pdo.php b/OAuth2/Storage/Pdo.php
index 17016e7..074cee4 100644
--- a/OAuth2/Storage/Pdo.php
+++ b/OAuth2/Storage/Pdo.php
@@ -4,6 +4,7 @@
use OAuth2\OpenID\Storage\UserClaimsInterface;
use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
+use InvalidArgumentException;
/**
* Simple PDO storage for all storage types
@@ -29,9 +30,22 @@ class Pdo implements
UserClaimsInterface,
OpenIDAuthorizationCodeInterface
{
+ /**
+ * @var \PDO
+ */
protected $db;
+
+ /**
+ * @var array
+ */
protected $config;
+ /**
+ * @param mixed $connection
+ * @param array $config
+ *
+ * @throws InvalidArgumentException
+ */
public function __construct($connection, $config = array())
{
if (!$connection instanceof \PDO) {
@@ -64,43 +78,64 @@ public function __construct($connection, $config = array())
'code_table' => 'oauth_authorization_codes',
'user_table' => 'oauth_users',
'jwt_table' => 'oauth_jwt',
+ 'jti_table' => 'oauth_jti',
'scope_table' => 'oauth_scopes',
'public_key_table' => 'oauth_public_keys',
), $config);
}
- /* OAuth2\Storage\ClientCredentialsInterface */
+ /**
+ * @param string $client_id
+ * @param null|string $client_secret
+ * @return bool
+ */
public function checkClientCredentials($client_id, $client_secret = null)
{
$stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table']));
$stmt->execute(compact('client_id'));
- $result = $stmt->fetch();
+ $result = $stmt->fetch(\PDO::FETCH_ASSOC);
// make this extensible
return $result && $result['client_secret'] == $client_secret;
}
+ /**
+ * @param string $client_id
+ * @return bool
+ */
public function isPublicClient($client_id)
{
$stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table']));
$stmt->execute(compact('client_id'));
- if (!$result = $stmt->fetch()) {
+ if (!$result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return false;
}
- return empty($result['client_secret']);;
+ return empty($result['client_secret']);
}
- /* OAuth2\Storage\ClientInterface */
+ /**
+ * @param string $client_id
+ * @return array|mixed
+ */
public function getClientDetails($client_id)
{
$stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table']));
$stmt->execute(compact('client_id'));
- return $stmt->fetch();
+ return $stmt->fetch(\PDO::FETCH_ASSOC);
}
+ /**
+ * @param string $client_id
+ * @param null|string $client_secret
+ * @param null|string $redirect_uri
+ * @param null|array $grant_types
+ * @param null|string $scope
+ * @param null|string $user_id
+ * @return bool
+ */
public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null)
{
// if it exists, update it.
@@ -113,6 +148,11 @@ public function setClientDetails($client_id, $client_secret = null, $redirect_ur
return $stmt->execute(compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id'));
}
+ /**
+ * @param $client_id
+ * @param $grant_type
+ * @return bool
+ */
public function checkRestrictedGrantType($client_id, $grant_type)
{
$details = $this->getClientDetails($client_id);
@@ -126,13 +166,16 @@ public function checkRestrictedGrantType($client_id, $grant_type)
return true;
}
- /* OAuth2\Storage\AccessTokenInterface */
+ /**
+ * @param string $access_token
+ * @return array|bool|mixed|null
+ */
public function getAccessToken($access_token)
{
$stmt = $this->db->prepare(sprintf('SELECT * from %s where access_token = :access_token', $this->config['access_token_table']));
$token = $stmt->execute(compact('access_token'));
- if ($token = $stmt->fetch()) {
+ if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) {
// convert date string back to timestamp
$token['expires'] = strtotime($token['expires']);
}
@@ -140,6 +183,14 @@ public function getAccessToken($access_token)
return $token;
}
+ /**
+ * @param string $access_token
+ * @param mixed $client_id
+ * @param mixed $user_id
+ * @param int $expires
+ * @param string $scope
+ * @return bool
+ */
public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null)
{
// convert expires to datestring
@@ -155,13 +206,30 @@ public function setAccessToken($access_token, $client_id, $user_id, $expires, $s
return $stmt->execute(compact('access_token', 'client_id', 'user_id', 'expires', 'scope'));
}
+ /**
+ * @param $access_token
+ * @return bool
+ */
+ public function unsetAccessToken($access_token)
+ {
+ $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE access_token = :access_token', $this->config['access_token_table']));
+
+ $stmt->execute(compact('access_token'));
+
+ return $stmt->rowCount() > 0;
+ }
+
/* OAuth2\Storage\AuthorizationCodeInterface */
+ /**
+ * @param string $code
+ * @return mixed
+ */
public function getAuthorizationCode($code)
{
$stmt = $this->db->prepare(sprintf('SELECT * from %s where authorization_code = :code', $this->config['code_table']));
$stmt->execute(compact('code'));
- if ($code = $stmt->fetch()) {
+ if ($code = $stmt->fetch(\PDO::FETCH_ASSOC)) {
// convert date string back to timestamp
$code['expires'] = strtotime($code['expires']);
}
@@ -169,6 +237,16 @@ public function getAuthorizationCode($code)
return $code;
}
+ /**
+ * @param string $code
+ * @param mixed $client_id
+ * @param mixed $user_id
+ * @param string $redirect_uri
+ * @param int $expires
+ * @param string $scope
+ * @param string $id_token
+ * @return bool|mixed
+ */
public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
{
if (func_num_args() > 6) {
@@ -189,6 +267,16 @@ public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri,
return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope'));
}
+ /**
+ * @param string $code
+ * @param mixed $client_id
+ * @param mixed $user_id
+ * @param string $redirect_uri
+ * @param string $expires
+ * @param string $scope
+ * @param string $id_token
+ * @return bool
+ */
private function setAuthorizationCodeWithIdToken($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
{
// convert expires to datestring
@@ -204,6 +292,10 @@ private function setAuthorizationCodeWithIdToken($code, $client_id, $user_id, $r
return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'));
}
+ /**
+ * @param string $code
+ * @return bool
+ */
public function expireAuthorizationCode($code)
{
$stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE authorization_code = :code', $this->config['code_table']));
@@ -211,7 +303,11 @@ public function expireAuthorizationCode($code)
return $stmt->execute(compact('code'));
}
- /* OAuth2\Storage\UserCredentialsInterface */
+ /**
+ * @param string $username
+ * @param string $password
+ * @return bool
+ */
public function checkUserCredentials($username, $password)
{
if ($user = $this->getUser($username)) {
@@ -221,12 +317,20 @@ public function checkUserCredentials($username, $password)
return false;
}
+ /**
+ * @param string $username
+ * @return array|bool
+ */
public function getUserDetails($username)
{
return $this->getUser($username);
}
- /* UserClaimsInterface */
+ /**
+ * @param mixed $user_id
+ * @param string $claims
+ * @return array|bool
+ */
public function getUserClaims($user_id, $claims)
{
if (!$userDetails = $this->getUserDetails($user_id)) {
@@ -252,6 +356,11 @@ public function getUserClaims($user_id, $claims)
return $userClaims;
}
+ /**
+ * @param string $claim
+ * @param array $userDetails
+ * @return array
+ */
protected function getUserClaim($claim, $userDetails)
{
$userClaims = array();
@@ -265,13 +374,16 @@ protected function getUserClaim($claim, $userDetails)
return $userClaims;
}
- /* OAuth2\Storage\RefreshTokenInterface */
+ /**
+ * @param string $refresh_token
+ * @return bool|mixed
+ */
public function getRefreshToken($refresh_token)
{
$stmt = $this->db->prepare(sprintf('SELECT * FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table']));
$token = $stmt->execute(compact('refresh_token'));
- if ($token = $stmt->fetch()) {
+ if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) {
// convert expires to epoch time
$token['expires'] = strtotime($token['expires']);
}
@@ -279,6 +391,14 @@ public function getRefreshToken($refresh_token)
return $token;
}
+ /**
+ * @param string $refresh_token
+ * @param mixed $client_id
+ * @param mixed $user_id
+ * @param string $expires
+ * @param string $scope
+ * @return bool
+ */
public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null)
{
// convert expires to datestring
@@ -289,25 +409,47 @@ public function setRefreshToken($refresh_token, $client_id, $user_id, $expires,
return $stmt->execute(compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'));
}
+ /**
+ * @param string $refresh_token
+ * @return bool
+ */
public function unsetRefreshToken($refresh_token)
{
$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
+ /**
+ * plaintext passwords are bad! Override this for your application
+ *
+ * @param array $user
+ * @param string $password
+ * @return bool
+ */
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);
}
+ /**
+ * @param string $username
+ * @return array|bool
+ */
public function getUser($username)
{
$stmt = $this->db->prepare($sql = sprintf('SELECT * from %s where username=:username', $this->config['user_table']));
$stmt->execute(array('username' => $username));
- if (!$userInfo = $stmt->fetch()) {
+ if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return false;
}
@@ -317,10 +459,19 @@ public function getUser($username)
), $userInfo);
}
+ /**
+ * plaintext passwords are bad! Override this for your application
+ *
+ * @param string $username
+ * @param string $password
+ * @param string $firstName
+ * @param string $lastName
+ * @return bool
+ */
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)) {
@@ -332,7 +483,10 @@ public function setUser($username, $password, $firstName = null, $lastName = nul
return $stmt->execute(compact('username', 'password', 'firstName', 'lastName'));
}
- /* ScopeInterface */
+ /**
+ * @param string $scope
+ * @return bool
+ */
public function scopeExists($scope)
{
$scope = explode(' ', $scope);
@@ -340,19 +494,23 @@ public function scopeExists($scope)
$stmt = $this->db->prepare(sprintf('SELECT count(scope) as count FROM %s WHERE scope IN (%s)', $this->config['scope_table'], $whereIn));
$stmt->execute($scope);
- if ($result = $stmt->fetch()) {
+ if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return $result['count'] == count($scope);
}
return false;
}
+ /**
+ * @param mixed $client_id
+ * @return null|string
+ */
public function getDefaultScope($client_id = null)
{
$stmt = $this->db->prepare(sprintf('SELECT scope FROM %s WHERE is_default=:is_default', $this->config['scope_table']));
$stmt->execute(array('is_default' => true));
- if ($result = $stmt->fetchAll()) {
+ if ($result = $stmt->fetchAll(\PDO::FETCH_ASSOC)) {
$defaultScope = array_map(function ($row) {
return $row['scope'];
}, $result);
@@ -363,7 +521,11 @@ public function getDefaultScope($client_id = null)
return null;
}
- /* JWTBearerInterface */
+ /**
+ * @param mixed $client_id
+ * @param $subject
+ * @return string
+ */
public function getClientKey($client_id, $subject)
{
$stmt = $this->db->prepare($sql = sprintf('SELECT public_key from %s where client_id=:client_id AND subject=:subject', $this->config['jwt_table']));
@@ -373,6 +535,10 @@ public function getClientKey($client_id, $subject)
return $stmt->fetchColumn();
}
+ /**
+ * @param mixed $client_id
+ * @return bool|null
+ */
public function getClientScope($client_id)
{
if (!$clientDetails = $this->getClientDetails($client_id)) {
@@ -386,13 +552,21 @@ public function getClientScope($client_id)
return null;
}
+ /**
+ * @param mixed $client_id
+ * @param $subject
+ * @param $audience
+ * @param $expires
+ * @param $jti
+ * @return array|null
+ */
public function getJti($client_id, $subject, $audience, $expires, $jti)
{
$stmt = $this->db->prepare($sql = sprintf('SELECT * FROM %s WHERE issuer=:client_id AND subject=:subject AND audience=:audience AND expires=:expires AND jti=:jti', $this->config['jti_table']));
$stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti'));
- if ($result = $stmt->fetch()) {
+ if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return array(
'issuer' => $result['issuer'],
'subject' => $result['subject'],
@@ -405,6 +579,14 @@ public function getJti($client_id, $subject, $audience, $expires, $jti)
return null;
}
+ /**
+ * @param mixed $client_id
+ * @param $subject
+ * @param $audience
+ * @param $expires
+ * @param $jti
+ * @return bool
+ */
public function setJti($client_id, $subject, $audience, $expires, $jti)
{
$stmt = $this->db->prepare(sprintf('INSERT INTO %s (issuer, subject, audience, expires, jti) VALUES (:client_id, :subject, :audience, :expires, :jti)', $this->config['jti_table']));
@@ -412,33 +594,44 @@ public function setJti($client_id, $subject, $audience, $expires, $jti)
return $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti'));
}
- /* PublicKeyInterface */
+ /**
+ * @param mixed $client_id
+ * @return mixed
+ */
public function getPublicKey($client_id = null)
{
$stmt = $this->db->prepare($sql = sprintf('SELECT public_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table']));
$stmt->execute(compact('client_id'));
- if ($result = $stmt->fetch()) {
+ if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return $result['public_key'];
}
}
+ /**
+ * @param mixed $client_id
+ * @return mixed
+ */
public function getPrivateKey($client_id = null)
{
$stmt = $this->db->prepare($sql = sprintf('SELECT private_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table']));
$stmt->execute(compact('client_id'));
- if ($result = $stmt->fetch()) {
+ if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return $result['private_key'];
}
}
+ /**
+ * @param mixed $client_id
+ * @return string
+ */
public function getEncryptionAlgorithm($client_id = null)
{
$stmt = $this->db->prepare($sql = sprintf('SELECT encryption_algorithm FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table']));
$stmt->execute(compact('client_id'));
- if ($result = $stmt->fetch()) {
+ if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return $result['encryption_algorithm'];
}
@@ -449,13 +642,16 @@ public function getEncryptionAlgorithm($client_id = null)
* DDL to create OAuth2 database and tables for PDO storage
*
* @see https://github.com/dsquier/oauth2-server-php-mysql
+ *
+ * @param string $dbName
+ * @return string
*/
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,
+ client_secret VARCHAR(80),
redirect_uri VARCHAR(2000),
grant_types VARCHAR(80),
scope VARCHAR(4000),
@@ -463,65 +659,73 @@ public function getBuildSql($dbName = 'oauth2_server_php')
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['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['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['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['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['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['jwt_table']} (
+ client_id VARCHAR(80) NOT NULL,
+ subject VARCHAR(80),
+ public_key VARCHAR(2000) NOT NULL
+ );
+
+ CREATE TABLE {$this->config['jti_table']} (
+ issuer VARCHAR(80) NOT NULL,
+ subject VARCHAR(80),
+ audiance VARCHAR(80),
+ expires TIMESTAMP NOT NULL,
+ jti 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'
- )
-";
+ 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;
}
-}
+}
\ No newline at end of file
diff --git a/OAuth2/Storage/PublicKeyInterface.php b/OAuth2/Storage/PublicKeyInterface.php
index 108418d..a6dc49f 100644
--- a/OAuth2/Storage/PublicKeyInterface.php
+++ b/OAuth2/Storage/PublicKeyInterface.php
@@ -10,7 +10,21 @@
*/
interface PublicKeyInterface
{
+ /**
+ * @param mixed $client_id
+ * @return mixed
+ */
public function getPublicKey($client_id = null);
+
+ /**
+ * @param mixed $client_id
+ * @return mixed
+ */
public function getPrivateKey($client_id = null);
+
+ /**
+ * @param mixed $client_id
+ * @return mixed
+ */
public function getEncryptionAlgorithm($client_id = null);
-}
+}
\ No newline at end of file
diff --git a/OAuth2/Storage/Redis.php b/OAuth2/Storage/Redis.php
index fd94f47..e6294e2 100644
--- a/OAuth2/Storage/Redis.php
+++ b/OAuth2/Storage/Redis.php
@@ -162,7 +162,7 @@ public function isPublicClient($client_id)
return false;
}
- return empty($result['client_secret']);;
+ return empty($client['client_secret']);
}
/* ClientInterface */
@@ -209,7 +209,9 @@ public function setRefreshToken($refresh_token, $client_id, $user_id, $expires,
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 */
@@ -227,6 +229,13 @@ public function setAccessToken($access_token, $client_id, $user_id, $expires, $s
);
}
+ public function unsetAccessToken($access_token)
+ {
+ $result = $this->expireValue($this->config['access_token_key'] . $access_token);
+
+ return $result > 0;
+ }
+
/* ScopeInterface */
public function scopeExists($scope)
{
diff --git a/OAuth2/Storage/RefreshTokenInterface.php b/OAuth2/Storage/RefreshTokenInterface.php
index 0273f21..e6407e4 100644
--- a/OAuth2/Storage/RefreshTokenInterface.php
+++ b/OAuth2/Storage/RefreshTokenInterface.php
@@ -74,7 +74,7 @@ public function setRefreshToken($refresh_token, $client_id, $user_id, $expires,
* and provide a descriptive fail message.
*
* @param $refresh_token
- * Refresh token to be expirse.
+ * Refresh token to be expired.
*
* @ingroup oauth2_section_6
*/
diff --git a/OAuth2/Storage/UserCredentialsInterface.php b/OAuth2/Storage/UserCredentialsInterface.php
index 6e0fd7b..f550579 100644
--- a/OAuth2/Storage/UserCredentialsInterface.php
+++ b/OAuth2/Storage/UserCredentialsInterface.php
@@ -37,15 +37,15 @@ interface UserCredentialsInterface
public function checkUserCredentials($username, $password);
/**
- * @return
- * ARRAY the associated "user_id" and optional "scope" values
- * This function MUST return FALSE if the requested user does not exist or is
- * invalid. "scope" is a space-separated list of restricted scopes.
+ * @param string $username - username to get details for
+ * @return array|false - the associated "user_id" and optional "scope" values
+ * This function MUST return FALSE if the requested user does not exist or is
+ * invalid. "scope" is a space-separated list of restricted scopes.
* @code
- * return array(
- * "user_id" => USER_ID, // REQUIRED user_id to be stored with the authorization code or access token
- * "scope" => SCOPE // OPTIONAL space-separated list of restricted scopes
- * );
+ * return array(
+ * "user_id" => USER_ID, // REQUIRED user_id to be stored with the authorization code or access token
+ * "scope" => SCOPE // OPTIONAL space-separated list of restricted scopes
+ * );
* @endcode
*/
public function getUserDetails($username);
diff --git a/OAuth2/TokenType/Bearer.php b/OAuth2/TokenType/Bearer.php
index 0ec9fec..8ac8596 100644
--- a/OAuth2/TokenType/Bearer.php
+++ b/OAuth2/TokenType/Bearer.php
@@ -91,7 +91,7 @@ public function getAccessTokenParameter(RequestInterface $request, ResponseInter
// HEADER: Get the access token from the header
if (!empty($headers)) {
- if (!preg_match('/' . $this->config['token_bearer_header_name'] . '\s(\S+)/', $headers, $matches)) {
+ if (!preg_match('/' . $this->config['token_bearer_header_name'] . '\s(\S+)/i', $headers, $matches)) {
$response->setError(400, 'invalid_request', 'Malformed auth header');
return null;
diff --git a/db/install.xml b/db/install.xml
old mode 100755
new mode 100644
diff --git a/index.php b/index.php
index f4be545..7805103 100644
--- a/index.php
+++ b/index.php
@@ -67,12 +67,12 @@
$form->user_id = $client_edit->user_id;
$form->action = 'edit';
} else {
- $form->client_id = "";
- $form->redirect_uri = "";
- $form->grant_types = "authorization_code";
- $form->scope = "user_info";
- $form->user_id = "0";
- $form->action = 'add';
+ $form->client_id = "";
+ $form->redirect_uri = "";
+ $form->grant_types = "authorization_code";
+ $form->scope = "user_info";
+ $form->user_id = "0";
+ $form->action = 'add';
}
$bform->set_data($form);
$bform->display();
@@ -135,6 +135,7 @@
if ($view_table) {
echo '';
+
if (function_exists('is_agora') && is_agora()) {
if (is_service_enabled('nodes') && !$DB->record_exists('oauth_clients', array('client_id' => 'nodes'))) {
echo ''.get_string('addnodesclient', 'local_oauth').'';
@@ -145,7 +146,6 @@
echo ''.get_string('addclient', 'local_oauth').'';
}
-
echo '
';
$clients = $DB->get_records('oauth_clients');
diff --git a/lang/ca/local_oauth.php b/lang/ca/local_oauth.php
index 7a4e2a9..ebd29e9 100644
--- a/lang/ca/local_oauth.php
+++ b/lang/ca/local_oauth.php
@@ -39,4 +39,4 @@
$string['event_user_info_request_failed'] = 'Error en la petició de dades d\'usuari/ària';
$string['client_id_help'] = 'ID de l\'aplicació per fer servir al formulari del client (XTECBlocs o Nodes) per tal de referenciar el proveïdor. Ha de ser únic. Per exemple, un ID de l\'aplicació vàlid pot ser "blog1" o "nodes".';
-$string['redirect_uri_help'] = 'URI a on redirigir després d\'autenticar-se. Per exemple, per a XTECBlocs o Nodes, l\'URL de redirecció ha de ser semblant a: - XTECBlocs: http://blocs.xtec.cat/nomdelbloc/wp-content/plugins/wordpress-social-login/hybridauth/?hauth.done=Moodle
- NODES: http://agora.xtec.cat/nomdelcentre/wp-content/plugins/wordpress-social-login/hybridauth/?hauth.done=Moodle
';
+$string['redirect_uri_help'] = 'URI a on redirigir després d\'autenticar-se. Per exemple, per a XTECBlocs o Nodes, l\'URL de redirecció ha de ser semblant a: - XTECBlocs: https://blocs.xtec.cat/nomdelbloc/wp-content/plugins/wordpress-social-login/hybridauth/callbacks/moodle.php
- NODES: https://agora.xtec.cat/nomdelcentre/wp-content/plugins/wordpress-social-login/hybridauth/callbacks/moodle.php
';
diff --git a/lang/en/local_oauth.php b/lang/en/local_oauth.php
index 2d50385..188147f 100644
--- a/lang/en/local_oauth.php
+++ b/lang/en/local_oauth.php
@@ -39,4 +39,4 @@
$string['event_user_info_request_failed'] = 'User info request failed';
$string['client_id_help'] = 'Identifier to be used from the client form in order to reference this provider. It has to be unique. For instance, a valid identifier could be "blog1" or "nodes".';
-$string['redirect_uri_help'] = 'URI where to redirect after login. For instance, for XTECBlocs or Nodes, the redirect URI are like: - XTECBlocs: http://blocs.xtec.cat/nomdelbloc/wp-content/plugins/wordpress-social-login/hybridauth/?hauth.done=Moodle
- NODES: http://agora.xtec.cat/nomdelcentre/wp-content/plugins/wordpress-social-login/hybridauth/?hauth.done=Moodle
';
+$string['redirect_uri_help'] = 'URI where to redirect after login. For instance, for XTECBlocs or Nodes, the redirect URI are like: - XTECBlocs: https://blocs.xtec.cat/nomdelbloc/wp-content/plugins/wordpress-social-login/hybridauth/callbacks/moodle.php
- NODES: https://agora.xtec.cat/nomdelcentre/wp-content/plugins/wordpress-social-login/hybridauth/callbacks/moodle.php
';
diff --git a/lang/es/local_oauth.php b/lang/es/local_oauth.php
index 4a11d50..3f04bbd 100644
--- a/lang/es/local_oauth.php
+++ b/lang/es/local_oauth.php
@@ -24,7 +24,7 @@
$string['client_not_exists'] = 'El cliente no existe';
$string['saveok'] = 'El cliente se ha guardado correctamente';
-$string['confirmdeletestr'] = 'Está seguro que quiere eleminar el cliente {$a}?';
+$string['confirmdeletestr'] = 'Está seguro que quiere eliminar el cliente {$a}?';
$string['delok'] = 'El cliente se ha eliminado correctamente';
$string['client_id_existing_error'] = 'El identificador de cliente especificado ya existe, seleccione otro';
$string['insert_error'] = 'Se ha producido un error creando el cliente';
@@ -39,4 +39,4 @@
$string['event_user_info_request_failed'] = 'Error en la petición de datos de usuario';
$string['client_id_help'] = 'ID de la aplicación para utilizar en el formulario del cliente (XTECBlocs o Nodes) para referenciar el proveedor. Debe ser único. Por ejemplo, un ID de la aplicación válido puede ser "blog1" o "nodes".';
-$string['redirect_uri_help'] = 'URI donde redirigir después de la autenticación. Por ejemplo, para XTECBlocs o Nodes, la URL de redirección debe ser parecida a: - XTECBlocs: http://blocs.xtec.cat/nomdelbloc/wp-content/plugins/wordpress-social-login/hybridauth/?hauth.done=Moodle
- NODES: http://agora.xtec.cat/nomdelcentre/wp-content/plugins/wordpress-social-login/hybridauth/?hauth.done=Moodle
';
+$string['redirect_uri_help'] = 'URI donde redirigir después de la autenticación. Por ejemplo, para XTECBlocs o Nodes, la URL de redirección debe ser parecida a: - XTECBlocs: https://blocs.xtec.cat/nomdelbloc/wp-content/plugins/wordpress-social-login/hybridauth/callbacks/moodle.php
- NODES: https://agora.xtec.cat/nomdelcentre/wp-content/plugins/wordpress-social-login/hybridauth/callbacks/moodle.php
';
diff --git a/locallib.php b/locallib.php
index 3041db7..aa25116 100644
--- a/locallib.php
+++ b/locallib.php
@@ -8,7 +8,7 @@ function oauth_add_wordpress_client($client_id, $url) {
$url .= '/';
}
$record = new stdClass();
- $record->redirect_uri = $url.'wp-content/plugins/wordpress-social-login/hybridauth/?hauth.done=Moodle';
+ $record->redirect_uri = $url . 'wp-content/plugins/wordpress-social-login/hybridauth/callbacks/moodle.php';
$record->grant_types = 'authorization_code';
$record->scope = 'user_info ';
$record->user_id = '';
diff --git a/settings.php b/settings.php
old mode 100755
new mode 100644
diff --git a/version.php b/version.php
index d12fdc5..07b7606 100644
--- a/version.php
+++ b/version.php
@@ -2,6 +2,6 @@
defined('MOODLE_INTERNAL') || die;
-$plugin->version = 2016021601; // The (date) version of this plugin
+$plugin->version = 2021071400; // The (date) version of this plugin
$plugin->requires = 2011021900; // Requires this Moodle version
-$plugin->component = 'local_oauth';
\ No newline at end of file
+$plugin->component = 'local_oauth';