summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBrent Shaffer <bshafs@gmail.com>2014-06-12 21:32:10 +0400
committerBrent Shaffer <bshafs@gmail.com>2014-06-12 21:32:10 +0400
commita7f02c9b7b4a18e54a71f93436aa4a6091f399a7 (patch)
treeb75564eaec9a5456c717af283df6003017e69a0e /src
parent95e731e0855b8156a161b93dc3f1909a8dd98abe (diff)
parent15132d13735b40b96c929dee5fa79863e3cd01c7 (diff)
downloadoauth2-server-php-1.4.tar.xz
Merge pull request #392 from bshaffer/developv1.4
Tag 1.4
Diffstat (limited to 'src')
-rw-r--r--src/OAuth2/Controller/AuthorizeController.php45
-rw-r--r--src/OAuth2/Controller/ResourceController.php2
-rw-r--r--src/OAuth2/Encryption/EncryptionInterface.php2
-rw-r--r--src/OAuth2/Encryption/Jwt.php42
-rw-r--r--src/OAuth2/GrantType/GrantTypeInterface.php1
-rw-r--r--src/OAuth2/GrantType/JwtBearer.php3
-rw-r--r--src/OAuth2/OpenID/Controller/AuthorizeController.php86
-rw-r--r--src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php12
-rw-r--r--src/OAuth2/OpenID/Controller/UserInfoController.php57
-rw-r--r--src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php23
-rw-r--r--src/OAuth2/OpenID/GrantType/AuthorizationCode.php36
-rw-r--r--src/OAuth2/OpenID/ResponseType/AuthorizationCode.php60
-rw-r--r--src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php32
-rw-r--r--src/OAuth2/OpenID/ResponseType/IdToken.php125
-rw-r--r--src/OAuth2/OpenID/ResponseType/IdTokenInterface.php29
-rw-r--r--src/OAuth2/OpenID/ResponseType/TokenIdToken.php28
-rw-r--r--src/OAuth2/OpenID/ResponseType/TokenIdTokenInterface.php9
-rw-r--r--src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php44
-rw-r--r--src/OAuth2/OpenID/Storage/UserClaimsInterface.php38
-rw-r--r--src/OAuth2/Request.php6
-rw-r--r--src/OAuth2/Response.php4
-rw-r--r--src/OAuth2/ResponseType/AccessToken.php6
-rw-r--r--src/OAuth2/ResponseType/AuthorizationCode.php10
-rw-r--r--src/OAuth2/Scope.php27
-rw-r--r--src/OAuth2/Server.php170
-rw-r--r--src/OAuth2/Storage/AccessTokenInterface.php3
-rw-r--r--src/OAuth2/Storage/Cassandra.php9
-rw-r--r--src/OAuth2/Storage/CryptoToken.php2
-rw-r--r--src/OAuth2/Storage/Memory.php54
-rw-r--r--src/OAuth2/Storage/Mongo.php13
-rw-r--r--src/OAuth2/Storage/Pdo.php82
-rw-r--r--src/OAuth2/Storage/Redis.php9
-rw-r--r--src/OAuth2/TokenType/Bearer.php19
33 files changed, 1006 insertions, 82 deletions
diff --git a/src/OAuth2/Controller/AuthorizeController.php b/src/OAuth2/Controller/AuthorizeController.php
index 74d2423..e669824 100644
--- a/src/OAuth2/Controller/AuthorizeController.php
+++ b/src/OAuth2/Controller/AuthorizeController.php
@@ -78,6 +78,7 @@ class AuthorizeController implements AuthorizeControllerInterface
$registered_redirect_uri = $clientData['redirect_uri'];
}
+ // the user declined access to the client's application
if ($is_authorized === false) {
$redirect_uri = $this->redirect_uri ?: $registered_redirect_uri;
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->state, 'access_denied', "The user denied access to your application");
@@ -85,14 +86,10 @@ class AuthorizeController implements AuthorizeControllerInterface
return;
}
- // @TODO: we should be explicit with this in the future
- $params = array(
- 'scope' => $this->scope,
- 'state' => $this->state,
- 'client_id' => $this->client_id,
- 'redirect_uri' => $this->redirect_uri,
- 'response_type' => $this->response_type,
- );
+ // build the parameters to set in the redirect URI
+ if (!$params = $this->buildAuthorizeParameters($request, $response, $user_id)) {
+ return;
+ }
$authResult = $this->responseTypes[$this->response_type]->getAuthorizeResponse($params, $user_id);
@@ -108,6 +105,24 @@ class AuthorizeController implements AuthorizeControllerInterface
$response->setRedirect($this->config['redirect_status_code'], $uri);
}
+ /*
+ * We have made this protected so this class can be extended to add/modify
+ * these parameters
+ */
+ protected function buildAuthorizeParameters($request, $response, $user_id)
+ {
+ // @TODO: we should be explicit with this in the future
+ $params = array(
+ 'scope' => $this->scope,
+ 'state' => $this->state,
+ 'client_id' => $this->client_id,
+ 'redirect_uri' => $this->redirect_uri,
+ 'response_type' => $this->response_type,
+ );
+
+ return $params;
+ }
+
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)
@@ -168,7 +183,7 @@ class AuthorizeController implements AuthorizeControllerInterface
$state = $request->query('state');
// type and client_id are required
- if (!$response_type || !in_array($response_type, array(self::RESPONSE_TYPE_AUTHORIZATION_CODE, self::RESPONSE_TYPE_ACCESS_TOKEN))) {
+ if (!$response_type || !in_array($response_type, $this->getValidResponseTypes())) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_request', 'Invalid or missing response type', null);
return false;
@@ -189,9 +204,7 @@ class AuthorizeController implements AuthorizeControllerInterface
return false;
}
- }
-
- if ($response_type == self::RESPONSE_TYPE_ACCESS_TOKEN) {
+ } else {
if (!$this->config['allow_implicit']) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'implicit grant type not supported', null);
@@ -287,6 +300,14 @@ class AuthorizeController implements AuthorizeControllerInterface
;
}
+ protected function getValidResponseTypes()
+ {
+ return array(
+ self::RESPONSE_TYPE_ACCESS_TOKEN,
+ self::RESPONSE_TYPE_AUTHORIZATION_CODE,
+ );
+ }
+
/**
* Internal method for validating redirect URI supplied
*
diff --git a/src/OAuth2/Controller/ResourceController.php b/src/OAuth2/Controller/ResourceController.php
index 317d1d5..9623b9f 100644
--- a/src/OAuth2/Controller/ResourceController.php
+++ b/src/OAuth2/Controller/ResourceController.php
@@ -81,7 +81,7 @@ class ResourceController implements ResourceControllerInterface
if (!$token = $this->tokenStorage->getAccessToken($token_param)) {
$response->setError(401, 'invalid_token', 'The access token provided is invalid');
} elseif (!isset($token["expires"]) || !isset($token["client_id"])) {
- $response->setError(401, 'invalid_token', 'Malformed token (missing "expires" or "client_id")');
+ $response->setError(401, 'invalid_token', 'Malformed token (missing "expires")');
} elseif (time() > $token["expires"]) {
$response->setError(401, 'invalid_token', 'The access token provided has expired');
} else {
diff --git a/src/OAuth2/Encryption/EncryptionInterface.php b/src/OAuth2/Encryption/EncryptionInterface.php
index dc8e0d2..2d336c6 100644
--- a/src/OAuth2/Encryption/EncryptionInterface.php
+++ b/src/OAuth2/Encryption/EncryptionInterface.php
@@ -6,4 +6,6 @@ interface EncryptionInterface
{
public function encode($payload, $key, $algorithm = null);
public function decode($payload, $key, $algorithm = null);
+ public function urlSafeB64Encode($data);
+ public function urlSafeB64Decode($b64);
}
diff --git a/src/OAuth2/Encryption/Jwt.php b/src/OAuth2/Encryption/Jwt.php
index 6bde68e..824ac13 100644
--- a/src/OAuth2/Encryption/Jwt.php
+++ b/src/OAuth2/Encryption/Jwt.php
@@ -10,11 +10,11 @@ class Jwt implements EncryptionInterface
{
public function encode($payload, $key, $algo = 'HS256')
{
- $header = array('typ' => 'JWT', 'alg' => $algo);
+ $header = $this->generateJwtHeader($payload, $algo);
$segments = array(
- $this->urlsafeB64Encode(json_encode($header)),
- $this->urlsafeB64Encode(json_encode($payload))
+ $this->urlSafeB64Encode(json_encode($header)),
+ $this->urlSafeB64Encode(json_encode($payload))
);
$signing_input = implode('.', $segments);
@@ -39,15 +39,15 @@ class Jwt implements EncryptionInterface
list($headb64, $payloadb64, $cryptob64) = $tks;
- if (null === ($header = json_decode($this->urlsafeB64Decode($headb64), true))) {
+ if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) {
return false;
}
- if (null === $payload = json_decode($this->urlsafeB64Decode($payloadb64), true)) {
+ if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) {
return false;
}
- $sig = $this->urlsafeB64Decode($cryptob64);
+ $sig = $this->urlSafeB64Decode($cryptob64);
if ($verify) {
if (!isset($header['alg'])) {
@@ -64,6 +64,7 @@ class Jwt implements EncryptionInterface
private function verifySignature($signature, $input, $key, $algo = 'HS256')
{
+ // use constants when possible, for HipHop support
switch ($algo) {
case'HS256':
case'HS384':
@@ -71,13 +72,13 @@ class Jwt implements EncryptionInterface
return $this->sign($input, $key, $algo) === $signature;
case 'RS256':
- return openssl_verify($input, $signature, $key, 'sha256') === 1;
+ return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256') === 1;
case 'RS384':
- return @openssl_verify($input, $signature, $key, 'sha384') === 1;
+ return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1;
case 'RS512':
- return @openssl_verify($input, $signature, $key, 'sha512') === 1;
+ return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1;
default:
throw new \InvalidArgumentException("Unsupported or invalid signing algorithm.");
@@ -97,13 +98,13 @@ class Jwt implements EncryptionInterface
return hash_hmac('sha512', $input, $key, true);
case 'RS256':
- return $this->generateRSASignature($input, $key, 'sha256');
+ return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256');
case 'RS384':
- return $this->generateRSASignature($input, $key, 'sha384');
+ return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384');
case 'RS512':
- return $this->generateRSASignature($input, $key, 'sha512');
+ return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512');
default:
throw new \Exception("Unsupported or invalid signing algorithm.");
@@ -119,17 +120,17 @@ class Jwt implements EncryptionInterface
return $signature;
}
- private function urlSafeB64Encode($data)
+ public function urlSafeB64Encode($data)
{
$b64 = base64_encode($data);
- $b64 = str_replace(array('+', '/', '\r', '\n', '='),
+ $b64 = str_replace(array('+', '/', "\r", "\n", '='),
array('-', '_'),
$b64);
return $b64;
}
- private function urlSafeB64Decode($b64)
+ public function urlSafeB64Decode($b64)
{
$b64 = str_replace(array('-', '_'),
array('+', '/'),
@@ -137,4 +138,15 @@ class Jwt implements EncryptionInterface
return base64_decode($b64);
}
+
+ /**
+ * Override to create a custom header
+ */
+ protected function generateJwtHeader($payload, $algorithm)
+ {
+ return array(
+ 'typ' => 'JWT',
+ 'alg' => $algorithm,
+ );
+ }
}
diff --git a/src/OAuth2/GrantType/GrantTypeInterface.php b/src/OAuth2/GrantType/GrantTypeInterface.php
index e70bb23..98489e9 100644
--- a/src/OAuth2/GrantType/GrantTypeInterface.php
+++ b/src/OAuth2/GrantType/GrantTypeInterface.php
@@ -11,6 +11,7 @@ use OAuth2\ResponseInterface;
*/
interface GrantTypeInterface
{
+ public function getQuerystringIdentifier();
public function validateRequest(RequestInterface $request, ResponseInterface $response);
public function getClientId();
public function getUserId();
diff --git a/src/OAuth2/GrantType/JwtBearer.php b/src/OAuth2/GrantType/JwtBearer.php
index 04ac144..534feae 100644
--- a/src/OAuth2/GrantType/JwtBearer.php
+++ b/src/OAuth2/GrantType/JwtBearer.php
@@ -5,6 +5,7 @@ namespace OAuth2\GrantType;
use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
use OAuth2\Storage\JwtBearerInterface;
use OAuth2\Encryption\Jwt;
+use OAuth2\Encryption\EncryptionInterface;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
@@ -35,7 +36,7 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
* @param OAuth2\Encryption\JWT OPTIONAL $jwtUtil
* The class used to decode, encode and verify JWTs.
*/
- public function __construct(JwtBearerInterface $storage, $audience, Jwt $jwtUtil = null)
+ public function __construct(JwtBearerInterface $storage, $audience, EncryptionInterface $jwtUtil = null)
{
$this->storage = $storage;
$this->audience = $audience;
diff --git a/src/OAuth2/OpenID/Controller/AuthorizeController.php b/src/OAuth2/OpenID/Controller/AuthorizeController.php
new file mode 100644
index 0000000..4850ebe
--- /dev/null
+++ b/src/OAuth2/OpenID/Controller/AuthorizeController.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace OAuth2\OpenID\Controller;
+
+use OAuth2\Controller\AuthorizeController as BaseAuthorizeController;
+use OAuth2\RequestInterface;
+use OAuth2\ResponseInterface;
+
+/**
+ * @see OAuth2\Controller\AuthorizeControllerInterface
+ */
+class AuthorizeController extends BaseAuthorizeController implements AuthorizeControllerInterface
+{
+ private $nonce;
+
+ protected function buildAuthorizeParameters($request, $response, $user_id)
+ {
+ if (!$params = parent::buildAuthorizeParameters($request, $response, $user_id)) {
+ return;
+ }
+
+ // Generate an id token if needed.
+ if ($this->needsIdToken($this->getScope()) && $this->getResponseType() == self::RESPONSE_TYPE_AUTHORIZATION_CODE) {
+ $params['id_token'] = $this->responseTypes['id_token']->createIdToken($this->getClientId(), $user_id, $this->nonce);
+ }
+
+ // add the nonce to return with the redirect URI
+ $params['nonce'] = $this->nonce;
+
+ return $params;
+ }
+
+ public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response)
+ {
+ if (!parent::validateAuthorizeRequest($request, $response)) {
+ return false;
+ }
+
+ $nonce = $request->query('nonce');
+
+ // Validate required nonce for "id_token" and "token id_token"
+ if (!$nonce && in_array($this->getResponseType(), array(self::RESPONSE_TYPE_ID_TOKEN, self::RESPONSE_TYPE_TOKEN_ID_TOKEN))) {
+ $response->setError(400, 'invalid_nonce', 'This application requires you specify a nonce parameter');
+
+ return false;
+ }
+
+ $this->nonce = $nonce;
+
+ return true;
+ }
+
+ protected function getValidResponseTypes()
+ {
+ return array(
+ self::RESPONSE_TYPE_ACCESS_TOKEN,
+ self::RESPONSE_TYPE_AUTHORIZATION_CODE,
+ self::RESPONSE_TYPE_ID_TOKEN,
+ self::RESPONSE_TYPE_TOKEN_ID_TOKEN,
+ );
+ }
+
+ /**
+ * Returns whether the current request needs to generate an id token.
+ *
+ * ID Tokens are a part of the OpenID Connect specification, so this
+ * method checks whether OpenID Connect is enabled in the server settings
+ * and whether the openid scope was requested.
+ *
+ * @param $request_scope
+ * A space-separated string of scopes.
+ *
+ * @return
+ * TRUE if an id token is needed, FALSE otherwise.
+ */
+ public function needsIdToken($request_scope)
+ {
+ // see if the "openid" scope exists in the requested scope
+ return $this->scopeUtil->checkScope('openid', $request_scope);
+ }
+
+ public function getNonce()
+ {
+ return $this->nonce;
+ }
+}
diff --git a/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php b/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php
new file mode 100644
index 0000000..af47cd0
--- /dev/null
+++ b/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace OAuth2\OpenID\Controller;
+
+use OAuth2\RequestInterface;
+use OAuth2\ResponseInterface;
+
+interface AuthorizeControllerInterface
+{
+ const RESPONSE_TYPE_ID_TOKEN = 'id_token';
+ const RESPONSE_TYPE_TOKEN_ID_TOKEN = 'token id_token';
+}
diff --git a/src/OAuth2/OpenID/Controller/UserInfoController.php b/src/OAuth2/OpenID/Controller/UserInfoController.php
new file mode 100644
index 0000000..28e429e
--- /dev/null
+++ b/src/OAuth2/OpenID/Controller/UserInfoController.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace OAuth2\OpenID\Controller;
+
+use OAuth2\TokenType\TokenTypeInterface;
+use OAuth2\Storage\AccessTokenInterface;
+use OAuth2\OpenID\Storage\UserClaimsInterface;
+use OAuth2\Controller\ResourceController;
+use OAuth2\ScopeInterface;
+use OAuth2\RequestInterface;
+use OAuth2\ResponseInterface;
+
+/**
+ * @see OAuth2\Controller\UserInfoControllerInterface
+ */
+class UserInfoController extends ResourceController implements UserInfoControllerInterface
+{
+ private $token;
+
+ protected $tokenType;
+ protected $tokenStorage;
+ protected $userClaimsStorage;
+ protected $config;
+ protected $scopeUtil;
+
+ public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, UserClaimsInterface $userClaimsStorage, $config = array(), ScopeInterface $scopeUtil = null)
+ {
+ $this->tokenType = $tokenType;
+ $this->tokenStorage = $tokenStorage;
+ $this->userClaimsStorage = $userClaimsStorage;
+
+ $this->config = array_merge(array(
+ 'www_realm' => 'Service',
+ ), $config);
+
+ if (is_null($scopeUtil)) {
+ $scopeUtil = new Scope();
+ }
+ $this->scopeUtil = $scopeUtil;
+ }
+
+ public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response)
+ {
+ if (!$this->verifyResourceRequest($request, $response, 'openid')) {
+ return;
+ }
+
+ $token = $this->getToken();
+ $claims = $this->userClaimsStorage->getUserClaims($token['user_id'], $token['scope']);
+ // The sub Claim MUST always be returned in the UserInfo Response.
+ // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
+ $claims += array(
+ 'sub' => $token['user_id'],
+ );
+ $response->setParameters($claims);
+ }
+}
diff --git a/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php b/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php
new file mode 100644
index 0000000..a89049d
--- /dev/null
+++ b/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace OAuth2\OpenID\Controller;
+
+use OAuth2\RequestInterface;
+use OAuth2\ResponseInterface;
+
+/**
+ * This controller is called when the user claims for OpenID Connect's
+ * UserInfo endpoint should be returned.
+ *
+ * ex:
+ * > $response = new OAuth2\Response();
+ * > $userInfoController->handleUserInfoRequest(
+ * > OAuth2\Request::createFromGlobals(),
+ * > $response;
+ * > $response->send();
+ *
+ */
+interface UserInfoControllerInterface
+{
+ public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response);
+}
diff --git a/src/OAuth2/OpenID/GrantType/AuthorizationCode.php b/src/OAuth2/OpenID/GrantType/AuthorizationCode.php
new file mode 100644
index 0000000..4a5da01
--- /dev/null
+++ b/src/OAuth2/OpenID/GrantType/AuthorizationCode.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace OAuth2\OpenID\GrantType;
+
+use OAuth2\GrantType\AuthorizationCode as BaseAuthorizationCode;
+use OAuth2\Storage\AuthorizationCodeInterface;
+use OAuth2\ResponseType\AccessTokenInterface;
+use OAuth2\RequestInterface;
+use OAuth2\ResponseInterface;
+
+/**
+ *
+ * @author Brent Shaffer <bshafs at gmail dot com>
+ */
+class AuthorizationCode extends BaseAuthorizationCode
+{
+ public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
+ {
+ $includeRefreshToken = true;
+ if (isset($this->authCode['id_token'])) {
+ // OpenID Connect requests include the refresh token only if the
+ // offline_access scope has been requested and granted.
+ $scopes = explode(' ', trim($scope));
+ $includeRefreshToken = in_array('offline_access', $scopes);
+ }
+
+ $token = $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
+ if (isset($this->authCode['id_token'])) {
+ $token['id_token'] = $this->authCode['id_token'];
+ }
+
+ $this->storage->expireAuthorizationCode($this->authCode['code']);
+
+ return $token;
+ }
+}
diff --git a/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php b/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php
new file mode 100644
index 0000000..8971954
--- /dev/null
+++ b/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace OAuth2\OpenID\ResponseType;
+
+use OAuth2\ResponseType\AuthorizationCode as BaseAuthorizationCode;
+use OAuth2\OpenID\Storage\AuthorizationCodeInterface as AuthorizationCodeStorageInterface;
+
+/**
+ *
+ * @author Brent Shaffer <bshafs at gmail dot com>
+ */
+class AuthorizationCode extends BaseAuthorizationCode implements AuthorizationCodeInterface
+{
+ public function __construct(AuthorizationCodeStorageInterface $storage, array $config = array())
+ {
+ parent::__construct($storage, $config);
+ }
+
+ public function getAuthorizeResponse($params, $user_id = null)
+ {
+ // build the URL to redirect to
+ $result = array('query' => array());
+
+ $params += array('scope' => null, 'state' => null, 'id_token' => null);
+
+ $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope'], $params['id_token']);
+
+ if (isset($params['state'])) {
+ $result['query']['state'] = $params['state'];
+ }
+
+ return array($params['redirect_uri'], $result);
+ }
+
+ /**
+ * Handle the creation of the authorization code.
+ *
+ * @param $client_id
+ * Client identifier related to the authorization code
+ * @param $user_id
+ * User ID associated with the authorization code
+ * @param $redirect_uri
+ * An absolute URI to which the authorization server will redirect the
+ * user-agent to when the end-user authorization step is completed.
+ * @param $scope
+ * (optional) Scopes to be stored in space-separated string.
+ * @param $id_token
+ * (optional) The OpenID Connect id_token.
+ *
+ * @see http://tools.ietf.org/html/rfc6749#section-4
+ * @ingroup oauth2_section_4
+ */
+ public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null)
+ {
+ $code = $this->generateAuthorizationCode();
+ $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope, $id_token);
+
+ return $code;
+ }
+}
diff --git a/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php b/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php
new file mode 100644
index 0000000..7b8de7c
--- /dev/null
+++ b/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace OAuth2\OpenID\ResponseType;
+
+use OAuth2\ResponseType\AuthorizationCodeInterface as BaseAuthorizationCodeInterface;
+
+/**
+ *
+ * @author Brent Shaffer <bshafs at gmail dot com>
+ */
+interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface
+{
+ /**
+ * Handle the creation of the authorization code.
+ *
+ * @param $client_id
+ * Client identifier related to the authorization code
+ * @param $user_id
+ * User ID associated with the authorization code
+ * @param $redirect_uri
+ * An absolute URI to which the authorization server will redirect the
+ * user-agent to when the end-user authorization step is completed.
+ * @param $scope
+ * (optional) Scopes to be stored in space-separated string.
+ * @param $id_token
+ * (optional) The OpenID Connect id_token.
+ *
+ * @see http://tools.ietf.org/html/rfc6749#section-4
+ * @ingroup oauth2_section_4
+ */
+ public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null);
+}
diff --git a/src/OAuth2/OpenID/ResponseType/IdToken.php b/src/OAuth2/OpenID/ResponseType/IdToken.php
new file mode 100644
index 0000000..8d907bc
--- /dev/null
+++ b/src/OAuth2/OpenID/ResponseType/IdToken.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace OAuth2\OpenID\ResponseType;
+
+use OAuth2\Encryption\EncryptionInterface;
+use OAuth2\Encryption\Jwt;
+use OAuth2\Storage\RefreshTokenInterface;
+use OAuth2\Storage\PublicKeyInterface;
+use OAuth2\OpenID\Storage\UserClaimsInterface;
+
+class IdToken implements IdTokenInterface
+{
+ protected $userClaimsStorage;
+ protected $publicKeyStorage;
+ protected $config;
+ protected $encryptionUtil;
+
+ public function __construct(UserClaimsInterface $userClaimsStorage, PublicKeyInterface $publicKeyStorage, array $config = array(), EncryptionInterface $encryptionUtil = null)
+ {
+ $this->userClaimsStorage = $userClaimsStorage;
+ $this->publicKeyStorage = $publicKeyStorage;
+ if (is_null($encryptionUtil)) {
+ $encryptionUtil = new Jwt();
+ }
+ $this->encryptionUtil = $encryptionUtil;
+
+ if (!isset($config['issuer'])) {
+ throw new \LogicException('config parameter "issuer" must be set');
+ }
+ $this->config = array_merge(array(
+ 'id_lifetime' => 3600,
+ ), $config);
+ }
+
+ public function getAuthorizeResponse($params, $userInfo = null)
+ {
+ // build the URL to redirect to
+ $result = array('query' => array());
+ $params += array('scope' => null, 'state' => null, 'nonce' => null);
+
+ // create the id token.
+ list($user_id, $auth_time) = $this->getUserIdAndAuthTime($userInfo);
+ $userClaims = $this->userClaimsStorage->getUserClaims($user_id, $params['scope']);
+
+ $id_token = $this->createIdToken($params['client_id'], $userInfo, $params['nonce'], $userClaims, null);
+ $result["fragment"] = array('id_token' => $id_token);
+ if (isset($params['state'])) {
+ $result["fragment"]["state"] = $params['state'];
+ }
+
+ return array($params['redirect_uri'], $result);
+ }
+
+ public function createIdToken($client_id, $userInfo, $nonce = null, $userClaims = null, $access_token = null)
+ {
+ // pull auth_time from user info if supplied
+ list($user_id, $auth_time) = $this->getUserIdAndAuthTime($userInfo);
+
+ $token = array(
+ 'iss' => $this->config['issuer'],
+ 'sub' => $user_id,
+ 'aud' => $client_id,
+ 'iat' => time(),
+ 'exp' => time() + $this->config['id_lifetime'],
+ 'auth_time' => $auth_time,
+ );
+
+ if ($nonce) {
+ $token['nonce'] = $nonce;
+ }
+
+ if ($userClaims) {
+ $token += $userClaims;
+ }
+
+ if ($access_token) {
+ $token['at_hash'] = $this->createAtHash($access_token, $client_id);
+ }
+
+ return $this->encodeToken($token, $client_id);
+ }
+
+ protected function createAtHash($access_token, $client_id = null)
+ {
+ // maps HS256 and RS256 to sha256, etc.
+ $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
+ $hash_algorithm = 'sha' . substr($algorithm, 2);
+ $hash = hash($hash_algorithm, $access_token);
+ $at_hash = substr($hash, 0, strlen($hash) / 2);
+
+ return $this->encryptionUtil->urlSafeB64Encode($at_hash);
+ }
+
+ protected function encodeToken(array $token, $client_id = null)
+ {
+ $private_key = $this->publicKeyStorage->getPrivateKey($client_id);
+ $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
+
+ return $this->encryptionUtil->encode($token, $private_key, $algorithm);
+ }
+
+ private function getUserIdAndAuthTime($userInfo)
+ {
+ $auth_time = null;
+
+ // support an array for user_id / auth_time
+ if (is_array($userInfo)) {
+ if (!isset($userInfo['user_id'])) {
+ throw new \LogicException('if $user_id argument is an array, user_id index must be set');
+ }
+
+ $auth_time = isset($userInfo['auth_time']) ? $userInfo['auth_time'] : null;
+ $user_id = $userInfo['user_id'];
+ } else {
+ $user_id = $userInfo;
+ }
+
+ if (is_null($auth_time)) {
+ $auth_time = time();
+ }
+
+ // userInfo is a scalar, and so this is the $user_id. Auth Time is null
+ return array($user_id, $auth_time);
+ }
+}
diff --git a/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php b/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php
new file mode 100644
index 0000000..3dfb57e
--- /dev/null
+++ b/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace OAuth2\OpenID\ResponseType;
+
+use OAuth2\ResponseType\ResponseTypeInterface;
+
+interface IdTokenInterface extends ResponseTypeInterface
+{
+ /**
+ * Create the id token.
+ *
+ * If Authorization Code Flow is used, the id_token is generated when the
+ * authorization code is issued, and later returned from the token endpoint
+ * together with the access_token.
+ * If the Implicit Flow is used, the token and id_token are generated and
+ * returned together.
+ *
+ * @param string $client_id The client id.
+ * @param string $user_id The user id.
+ * @param string $nonce OPTIONAL The nonce.
+ * @param string $userClaims OPTIONAL Claims about the user.
+ * @param string $access_token OPTIONAL The access token, if known.
+ *
+ * @return string The ID Token represented as a JSON Web Token (JWT).
+ *
+ * @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken
+ */
+ public function createIdToken($client_id, $userInfo, $nonce = null, $userClaims = null, $access_token = null);
+}
diff --git a/src/OAuth2/OpenID/ResponseType/TokenIdToken.php b/src/OAuth2/OpenID/ResponseType/TokenIdToken.php
new file mode 100644
index 0000000..67f2a4a
--- /dev/null
+++ b/src/OAuth2/OpenID/ResponseType/TokenIdToken.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace OAuth2\OpenID\ResponseType;
+
+use OAuth2\ResponseType\AccessTokenInterface;
+use OAuth2\ResponseType\ResponseTypeInterface;
+
+class TokenIdToken implements TokenIdTokenInterface
+{
+ protected $accessToken;
+ protected $idToken;
+
+ public function __construct(AccessTokenInterface $accessToken, IdToken $idToken)
+ {
+ $this->accessToken = $accessToken;
+ $this->idToken = $idToken;
+ }
+
+ public function getAuthorizeResponse($params, $user_id = null)
+ {
+ $result = $this->accessToken->getAuthorizeResponse($params, $user_id);
+ $access_token = $result[1]['fragment']['access_token'];
+ $id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce'], null, $access_token);
+ $result[1]['fragment']['id_token'] = $id_token;
+
+ return $result;
+ }
+}
diff --git a/src/OAuth2/OpenID/ResponseType/TokenIdTokenInterface.php b/src/OAuth2/OpenID/ResponseType/TokenIdTokenInterface.php
new file mode 100644
index 0000000..1b4c513
--- /dev/null
+++ b/src/OAuth2/OpenID/ResponseType/TokenIdTokenInterface.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace OAuth2\OpenID\ResponseType;
+
+use OAuth2\ResponseType\ResponseTypeInterface;
+
+interface TokenIdTokenInterface extends ResponseTypeInterface
+{
+}
diff --git a/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php b/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php
new file mode 100644
index 0000000..3c6d00b
--- /dev/null
+++ b/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace OAuth2\OpenID\Storage;
+
+use OAuth2\Storage\AuthorizationCodeInterface as BaseAuthorizationCodeInterface;
+/**
+ * Implement this interface to specify where the OAuth2 Server
+ * should get/save authorization codes for the "Authorization Code"
+ * grant type
+ *
+ * @author Brent Shaffer <bshafs at gmail dot com>
+ */
+interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface
+{
+ /**
+ * Take the provided authorization code values and store them somewhere.
+ *
+ * This function should be the storage counterpart to getAuthCode().
+ *
+ * If storage fails for some reason, we're not currently checking for
+ * any sort of success/failure, so you should bail out of the script
+ * and provide a descriptive fail message.
+ *
+ * Required for OAuth2::GRANT_TYPE_AUTH_CODE.
+ *
+ * @param $code
+ * Authorization code to be stored.
+ * @param $client_id
+ * Client identifier to be stored.
+ * @param $user_id
+ * User identifier to be stored.
+ * @param string $redirect_uri
+ * Redirect URI(s) to be stored in a space-separated string.
+ * @param int $expires
+ * Expiration to be stored as a Unix timestamp.
+ * @param string $scope
+ * (optional) Scopes to be stored in space-separated string.
+ * @param string $id_token
+ * (optional) The OpenID Connect id_token.
+ *
+ * @ingroup oauth2_section_4
+ */
+ public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null);
+}
diff --git a/src/OAuth2/OpenID/Storage/UserClaimsInterface.php b/src/OAuth2/OpenID/Storage/UserClaimsInterface.php
new file mode 100644
index 0000000..f230bef
--- /dev/null
+++ b/src/OAuth2/OpenID/Storage/UserClaimsInterface.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace OAuth2\OpenID\Storage;
+
+/**
+ * Implement this interface to specify where the OAuth2 Server
+ * should retrieve user claims for the OpenID Connect id_token.
+ */
+interface UserClaimsInterface
+{
+ // valid scope values to pass into the user claims API call
+ const VALID_CLAIMS = 'profile email address phone';
+
+ // fields returned for the claims above
+ const PROFILE_CLAIM_VALUES = 'name family_name given_name middle_name nickname preferred_username profile picture website gender birthdate zoneinfo locale updated_at';
+ const EMAIL_CLAIM_VALUES = 'email email_verified';
+ const ADDRESS_CLAIM_VALUES = 'formatted street_address locality region postal_code country';
+ const PHONE_CLAIM_VALUES = 'phone_number phone_number_verified';
+
+ /**
+ * Return claims about the provided user id.
+ *
+ * Groups of claims are returned based on the requested scopes. No group
+ * is required, and no claim is required.
+ *
+ * @param $user_id
+ * The id of the user for which claims should be returned.
+ * @param $scope
+ * The requested scope.
+ * Scopes with matching claims: profile, email, address, phone.
+ *
+ * @return
+ * An array in the claim => value format.
+ *
+ * @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
+ */
+ public function getUserClaims($user_id, $scope);
+}
diff --git a/src/OAuth2/Request.php b/src/OAuth2/Request.php
index b49a42a..b37c591 100644
--- a/src/OAuth2/Request.php
+++ b/src/OAuth2/Request.php
@@ -80,7 +80,9 @@ class Request implements RequestInterface
public function headers($name, $default = null)
{
- return isset($this->headers[$name]) ? $this->headers[$name] : $default;
+ $headers = array_change_key_case($this->headers);
+ $name = strtolower($name);
+ return isset($headers[$name]) ? $headers[$name] : $default;
}
public function getAllQueryParameters()
@@ -149,7 +151,7 @@ class Request implements RequestInterface
} elseif (isset($server['REDIRECT_HTTP_AUTHORIZATION'])) {
$authorizationHeader = $server['REDIRECT_HTTP_AUTHORIZATION'];
} elseif (function_exists('apache_request_headers')) {
- $requestHeaders = apache_request_headers();
+ $requestHeaders = (array) apache_request_headers();
// Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
diff --git a/src/OAuth2/Response.php b/src/OAuth2/Response.php
index c16b476..d746789 100644
--- a/src/OAuth2/Response.php
+++ b/src/OAuth2/Response.php
@@ -180,7 +180,9 @@ class Response implements ResponseInterface
case 'xml':
// this only works for single-level arrays
$xml = new \SimpleXMLElement('<response/>');
- array_walk($this->parameters, array($xml, 'addChild'));
+ foreach ($this->parameters as $key => $param) {
+ $xml->addChild($param, $key);
+ }
return $xml->asXML();
}
diff --git a/src/OAuth2/ResponseType/AccessToken.php b/src/OAuth2/ResponseType/AccessToken.php
index 078a457..0fb1121 100644
--- a/src/OAuth2/ResponseType/AccessToken.php
+++ b/src/OAuth2/ResponseType/AccessToken.php
@@ -122,7 +122,11 @@ class AccessToken implements AccessTokenInterface
protected function generateAccessToken()
{
$tokenLen = 40;
- if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
+ if (function_exists('mcrypt_create_iv')) {
+ $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM);
+ } else if (function_exists('openssl_random_pseudo_bytes')) {
+ $randomData = openssl_random_pseudo_bytes(100);
+ } else if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
$randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true);
} else {
$randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
diff --git a/src/OAuth2/ResponseType/AuthorizationCode.php b/src/OAuth2/ResponseType/AuthorizationCode.php
index c39d6a0..51bd437 100644
--- a/src/OAuth2/ResponseType/AuthorizationCode.php
+++ b/src/OAuth2/ResponseType/AuthorizationCode.php
@@ -29,10 +29,10 @@ class AuthorizationCode implements AuthorizationCodeInterface
$params += array('scope' => null, 'state' => null);
- $result["query"]["code"] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope']);
+ $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope']);
if (isset($params['state'])) {
- $result["query"]["state"] = $params['state'];
+ $result['query']['state'] = $params['state'];
}
return array($params['redirect_uri'], $result);
@@ -85,7 +85,11 @@ class AuthorizationCode implements AuthorizationCodeInterface
protected function generateAuthorizationCode()
{
$tokenLen = 40;
- if (file_exists('/dev/urandom')) { // Get 100 bytes of random data
+ if (function_exists('mcrypt_create_iv')) {
+ $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM);
+ } else if (function_exists('openssl_random_pseudo_bytes')) {
+ $randomData = openssl_random_pseudo_bytes(100);
+ } else if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
$randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true);
} else {
$randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
diff --git a/src/OAuth2/Scope.php b/src/OAuth2/Scope.php
index 1e2e339..5c792bb 100644
--- a/src/OAuth2/Scope.php
+++ b/src/OAuth2/Scope.php
@@ -62,7 +62,18 @@ class Scope implements ScopeInterface
*/
public function scopeExists($scope)
{
- return $this->storage->scopeExists($scope);
+ // Check reserved scopes first.
+ $scope = explode(' ', trim($scope));
+ $reservedScope = $this->getReservedScopes();
+ $nonReservedScopes = array_diff($scope, $reservedScope);
+ if (count($nonReservedScopes) == 0) {
+ return true;
+ }
+ else {
+ // Check the storage for non-reserved scopes.
+ $nonReservedScopes = implode(' ', $nonReservedScopes);
+ return $this->storage->scopeExists($nonReservedScopes);
+ }
}
public function getScopeFromRequest(RequestInterface $request)
@@ -75,4 +86,18 @@ class Scope implements ScopeInterface
{
return $this->storage->getDefaultScope($client_id);
}
+
+ /**
+ * Get reserved scopes needed by the server.
+ *
+ * In case OpenID Connect is used, these scopes must include:
+ * 'openid', offline_access'.
+ *
+ * @return
+ * An array of reserved scopes.
+ */
+ public function getReservedScopes()
+ {
+ return array('openid', 'offline_access');
+ }
}
diff --git a/src/OAuth2/Server.php b/src/OAuth2/Server.php
index f07b5fb..b138a55 100644
--- a/src/OAuth2/Server.php
+++ b/src/OAuth2/Server.php
@@ -4,6 +4,12 @@ namespace OAuth2;
use OAuth2\Controller\ResourceControllerInterface;
use OAuth2\Controller\ResourceController;
+use OAuth2\OpenID\Controller\UserInfoControllerInterface;
+use OAuth2\OpenID\Controller\UserInfoController;
+use OAuth2\OpenID\Controller\AuthorizeController as OpenIDAuthorizeController;
+use OAuth2\OpenID\ResponseType\AuthorizationCode as OpenIDAuthorizationCodeResponseType;
+use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
+use OAuth2\OpenID\GrantType\AuthorizationCode as OpenIDAuthorizationCodeGrantType;
use OAuth2\Controller\AuthorizeControllerInterface;
use OAuth2\Controller\AuthorizeController;
use OAuth2\Controller\TokenControllerInterface;
@@ -14,6 +20,8 @@ use OAuth2\ResponseType\ResponseTypeInterface;
use OAuth2\ResponseType\AuthorizationCode as AuthorizationCodeResponseType;
use OAuth2\ResponseType\AccessToken;
use OAuth2\ResponseType\CryptoToken;
+use OAuth2\OpenID\ResponseType\IdToken;
+use OAuth2\OpenID\ResponseType\TokenIdToken;
use OAuth2\TokenType\TokenTypeInterface;
use OAuth2\TokenType\Bearer;
use OAuth2\GrantType\GrantTypeInterface;
@@ -34,7 +42,8 @@ use OAuth2\Storage\CryptoTokenInterface;
*/
class Server implements ResourceControllerInterface,
AuthorizeControllerInterface,
- TokenControllerInterface
+ TokenControllerInterface,
+ UserInfoControllerInterface
{
// misc properties
protected $response;
@@ -45,6 +54,7 @@ class Server implements ResourceControllerInterface,
protected $authorizeController;
protected $tokenController;
protected $resourceController;
+ protected $userInfoController;
// config classes
protected $grantTypes;
@@ -60,6 +70,7 @@ class Server implements ResourceControllerInterface,
'client' => 'OAuth2\Storage\ClientInterface',
'refresh_token' => 'OAuth2\Storage\RefreshTokenInterface',
'user_credentials' => 'OAuth2\Storage\UserCredentialsInterface',
+ 'user_claims' => 'OAuth2\OpenID\Storage\UserClaimsInterface',
'public_key' => 'OAuth2\Storage\PublicKeyInterface',
'jwt_bearer' => 'OAuth2\Storage\JWTBearerInterface',
'scope' => 'OAuth2\Storage\ScopeInterface',
@@ -67,6 +78,8 @@ class Server implements ResourceControllerInterface,
protected $responseTypeMap = array(
'token' => 'OAuth2\ResponseType\AccessTokenInterface',
'code' => 'OAuth2\ResponseType\AuthorizationCodeInterface',
+ 'id_token' => 'OAuth2\OpenID\ResponseType\IdTokenInterface',
+ 'token id_token' => 'OAuth2\OpenID\ResponseType\TokenIdTokenInterface',
);
/**
@@ -101,6 +114,8 @@ class Server implements ResourceControllerInterface,
$this->config = array_merge(array(
'use_crypto_tokens' => false,
'store_encrypted_token_string' => true,
+ 'use_openid_connect' => false,
+ 'id_lifetime' => 3600,
'access_lifetime' => 3600,
'www_realm' => 'Service',
'token_param_name' => 'access_token',
@@ -151,6 +166,15 @@ class Server implements ResourceControllerInterface,
return $this->resourceController;
}
+ public function getUserInfoController()
+ {
+ if (is_null($this->userInfoController)) {
+ $this->userInfoController = $this->createDefaultUserInfoController();
+ }
+
+ return $this->userInfoController;
+ }
+
/**
* every getter deserves a setter
*/
@@ -176,6 +200,37 @@ class Server implements ResourceControllerInterface,
}
/**
+ * every getter deserves a setter
+ */
+ public function setUserInfoController(UserInfoControllerInterface $userInfoController)
+ {
+ $this->userInfoController = $userInfoController;
+ }
+
+ /**
+ * 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 $response - OAuth2\ResponseInterface
+ * Response object containing error messages (failure) or user claims (success)
+ *
+ * @throws InvalidArgumentException
+ * @throws LogicException
+ *
+ * @see http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
+ */
+ public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response = null)
+ {
+ $this->response = is_null($response) ? new Response() : $response;
+ $this->getUserInfoController()->handleUserInfoRequest($request, $this->response);
+
+ return $this->response;
+ }
+
+ /**
* Grant or deny a requested access token.
* This would be called from the "/token" endpoint as defined in the spec.
* Obviously, you can call your endpoint whatever you want.
@@ -295,7 +350,7 @@ class Server implements ResourceControllerInterface,
if (is_string($key)) {
$this->grantTypes[$key] = $grantType;
} else {
- $this->grantTypes[] = $grantType;
+ $this->grantTypes[$grantType->getQuerystringIdentifier()] = $grantType;
}
// persist added grant type down to TokenController
@@ -318,7 +373,7 @@ class Server implements ResourceControllerInterface,
{
// if explicitly set to a valid key, do not "magically" set below
if (isset($this->storageMap[$key])) {
- if (!$storage instanceof $this->storageMap[$key]) {
+ if (!is_null($storage) && !$storage instanceof $this->storageMap[$key]) {
throw new \InvalidArgumentException(sprintf('storage of type "%s" must implement interface "%s"', $key, $this->storageMap[$key]));
}
$this->storages[$key] = $storage;
@@ -400,8 +455,19 @@ class Server implements ResourceControllerInterface,
if (0 == count($this->responseTypes)) {
$this->responseTypes = $this->getDefaultResponseTypes();
}
+ 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();
+ }
+ }
+
$config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state require_exact_redirect_uri')));
+ if ($this->config['use_openid_connect']) {
+ return new OpenIDAuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil());
+ }
+
return new AuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil());
}
@@ -429,11 +495,7 @@ class Server implements ResourceControllerInterface,
throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\ClientInterface to use the token server");
}
- if ($this->config['use_crypto_tokens']) {
- $accessTokenResponseType = $this->getCryptoTokenResponseType();
- } else {
- $accessTokenResponseType = $this->getAccessTokenResponseType();
- }
+ $accessTokenResponseType = $this->getAccessTokenResponseType();
return new TokenController($accessTokenResponseType, $this->storages['client'], $this->grantTypes, $this->clientAssertionType, $this->getScopeUtil());
}
@@ -458,6 +520,30 @@ class Server implements ResourceControllerInterface,
return new ResourceController($this->tokenType, $this->storages['access_token'], $config, $this->getScopeUtil());
}
+ 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();
+ }
+ } 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");
+ }
+
+ 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");
+ }
+
+ if (!$this->tokenType) {
+ $this->tokenType = $this->getDefaultTokenType();
+ }
+
+ $config = array_intersect_key($this->config, array('www_realm' => ''));
+
+ return new UserInfoController($this->tokenType, $this->storages['access_token'], $this->storages['user_claims'], $config, $this->getScopeUtil());
+ }
+
protected function getDefaultTokenType()
{
$config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name')));
@@ -470,16 +556,26 @@ class Server implements ResourceControllerInterface,
$responseTypes = array();
if ($this->config['allow_implicit']) {
- if ($this->config['use_crypto_tokens']) {
- $responseTypes['token'] = $this->getCryptoTokenResponseType();
- } elseif (isset($this->storages['access_token'])) {
- $responseTypes['token'] = $this->getAccessTokenResponseType();
+ $responseTypes['token'] = $this->getAccessTokenResponseType();
+ }
+
+ if ($this->config['use_openid_connect']) {
+ $responseTypes['id_token'] = $this->getIdTokenResponseType();
+ if ($this->config['allow_implicit']) {
+ $responseTypes['token id_token'] = $this->getTokenIdTokenResponseType();
}
}
if (isset($this->storages['authorization_code'])) {
$config = array_intersect_key($this->config, array_flip(explode(' ', 'enforce_redirect auth_code_lifetime')));
- $responseTypes['code'] = new AuthorizationCodeResponseType($this->storages['authorization_code'], $config);
+ 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");
+ }
+ $responseTypes['code'] = new OpenIDAuthorizationCodeResponseType($this->storages['authorization_code'], $config);
+ } else {
+ $responseTypes['code'] = new AuthorizationCodeResponseType($this->storages['authorization_code'], $config);
+ }
}
if (count($responseTypes) == 0) {
@@ -508,7 +604,14 @@ class Server implements ResourceControllerInterface,
}
if (isset($this->storages['authorization_code'])) {
- $grantTypes['authorization_code'] = new AuthorizationCode($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");
+ }
+ $grantTypes['authorization_code'] = new OpenIDAuthorizationCodeGrantType($this->storages['authorization_code']);
+ } else {
+ $grantTypes['authorization_code'] = new AuthorizationCode($this->storages['authorization_code']);
+ }
}
if (count($grantTypes) == 0) {
@@ -524,16 +627,29 @@ class Server implements ResourceControllerInterface,
return $this->responseTypes['token'];
}
+ if ($this->config['use_crypto_tokens']) {
+ return $this->createDefaultCryptoTokenResponseType();
+ }
+
return $this->createDefaultAccessTokenResponseType();
}
- protected function getCryptoTokenResponseType()
+ protected function getIdTokenResponseType()
{
- if (isset($this->responseTypes['token'])) {
- return $this->responseTypes['token'];
+ if (isset($this->responseTypes['id_token'])) {
+ return $this->responseTypes['id_token'];
+ }
+
+ return $this->createDefaultIdTokenResponseType();
+ }
+
+ protected function getTokenIdTokenResponseType()
+ {
+ if (isset($this->responseTypes['token id_token'])) {
+ return $this->responseTypes['token id_token'];
}
- return $this->createDefaultCryptoTokenResponseType();
+ return $this->createDefaultTokenIdTokenResponseType();
}
/**
@@ -593,6 +709,24 @@ class Server implements ResourceControllerInterface,
return new AccessToken($this->storages['access_token'], $refreshStorage, $config);
}
+ 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");
+ }
+ if (!isset($this->storages['public_key'])) {
+ 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')));
+ return new IdToken($this->storages['user_claims'], $this->storages['public_key'], $config);
+ }
+
+ protected function createDefaultTokenIdTokenResponseType()
+ {
+ return new TokenIdToken($this->getAccessTokenResponseType(), $this->getIdTokenResponseType());
+ }
+
public function getResponse()
{
return $this->response;
diff --git a/src/OAuth2/Storage/AccessTokenInterface.php b/src/OAuth2/Storage/AccessTokenInterface.php
index dd687e4..b55c3d6 100644
--- a/src/OAuth2/Storage/AccessTokenInterface.php
+++ b/src/OAuth2/Storage/AccessTokenInterface.php
@@ -21,10 +21,11 @@ interface AccessTokenInterface
* @return
* An associative array as below, and return NULL if the supplied oauth_token
* is invalid:
- * - client_id: Stored client identifier.
* - 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).
*
* @ingroup oauth2_section_7
*/
diff --git a/src/OAuth2/Storage/Cassandra.php b/src/OAuth2/Storage/Cassandra.php
index b63a752..f6d1e05 100644
--- a/src/OAuth2/Storage/Cassandra.php
+++ b/src/OAuth2/Storage/Cassandra.php
@@ -1,9 +1,11 @@
<?php
namespace OAuth2\Storage;
+
use phpcassa\ColumnFamily;
use phpcassa\ColumnSlice;
use phpcassa\Connection\ConnectionPool;
+use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
/**
* Cassandra storage for all storage types
@@ -32,7 +34,8 @@ class Cassandra implements AuthorizationCodeInterface,
UserCredentialsInterface,
RefreshTokenInterface,
JwtBearerInterface,
- ScopeInterface
+ ScopeInterface,
+ OpenIDAuthorizationCodeInterface
{
private $cache;
@@ -145,11 +148,11 @@ class Cassandra implements AuthorizationCodeInterface,
return $this->getValue($this->config['code_key'] . $code);
}
- public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null)
+ public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
{
return $this->setValue(
$this->config['code_key'] . $authorization_code,
- compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope'),
+ compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'),
$expires
);
}
diff --git a/src/OAuth2/Storage/CryptoToken.php b/src/OAuth2/Storage/CryptoToken.php
index 6cad23b..f107e45 100644
--- a/src/OAuth2/Storage/CryptoToken.php
+++ b/src/OAuth2/Storage/CryptoToken.php
@@ -61,7 +61,7 @@ class CryptoToken implements CryptoTokenInterface
$algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
// now that we have the client_id, verify the token
- if (false === $this->encryptionUtil->decode($oauth_token, $public_key, $algorithm)) {
+ if (false === $this->encryptionUtil->decode($oauth_token, $public_key, true)) {
return false;
}
diff --git a/src/OAuth2/Storage/Memory.php b/src/OAuth2/Storage/Memory.php
index 2c9dc3d..35e6cba 100644
--- a/src/OAuth2/Storage/Memory.php
+++ b/src/OAuth2/Storage/Memory.php
@@ -2,6 +2,9 @@
namespace OAuth2\Storage;
+use OAuth2\OpenID\Storage\UserClaimsInterface;
+use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
+
/**
* Simple in-memory storage for all storage types
*
@@ -12,12 +15,14 @@ namespace OAuth2\Storage;
*/
class Memory implements AuthorizationCodeInterface,
UserCredentialsInterface,
+ UserClaimsInterface,
AccessTokenInterface,
ClientCredentialsInterface,
RefreshTokenInterface,
JwtBearerInterface,
ScopeInterface,
- PublicKeyInterface
+ PublicKeyInterface,
+ OpenIDAuthorizationCodeInterface
{
public $authorizationCodes;
public $userCredentials;
@@ -69,9 +74,9 @@ class Memory implements AuthorizationCodeInterface,
), $this->authorizationCodes[$code]);
}
- public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null)
+ public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
{
- $this->authorizationCodes[$code] = compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope');
+ $this->authorizationCodes[$code] = compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token');
return true;
}
@@ -119,6 +124,45 @@ class Memory implements AuthorizationCodeInterface,
), $this->userCredentials[$username]);
}
+ /* UserClaimsInterface */
+ public function getUserClaims($user_id, $claims)
+ {
+ if (!$userDetails = $this->getUserDetails($user_id)) {
+ return false;
+ }
+
+ $claims = explode(' ', trim($claims));
+ $userClaims = array();
+
+ // for each requested claim, if the user has the claim, set it in the response
+ $validClaims = explode(' ', self::VALID_CLAIMS);
+ foreach ($validClaims as $validClaim) {
+ if (in_array($validClaim, $claims)) {
+ if ($validClaim == 'address') {
+ // address is an object with subfields
+ $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails);
+ } else {
+ $userClaims = array_merge($this->getUserClaim($validClaim, $userDetails));
+ }
+ }
+ }
+
+ return $userClaims;
+ }
+
+ protected function getUserClaim($claim, $userDetails)
+ {
+ $userClaims = array();
+ $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim)));
+ $claimValues = explode(' ', $claimValuesString);
+
+ foreach ($claimValues as $value) {
+ $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null;
+ }
+
+ return $userClaims;
+ }
+
/* ClientCredentialsInterface */
public function checkClientCredentials($client_id, $client_secret = null)
{
@@ -206,9 +250,9 @@ class Memory implements AuthorizationCodeInterface,
return isset($this->accessTokens[$access_token]) ? $this->accessTokens[$access_token] : false;
}
- public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null)
+ public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null, $id_token = null)
{
- $this->accessTokens[$access_token] = compact('access_token', 'client_id', 'user_id', 'expires', 'scope');
+ $this->accessTokens[$access_token] = compact('access_token', 'client_id', 'user_id', 'expires', 'scope', 'id_token');
return true;
}
diff --git a/src/OAuth2/Storage/Mongo.php b/src/OAuth2/Storage/Mongo.php
index 60db771..87935fe 100644
--- a/src/OAuth2/Storage/Mongo.php
+++ b/src/OAuth2/Storage/Mongo.php
@@ -2,6 +2,8 @@
namespace OAuth2\Storage;
+use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
+
/**
* Simple MongoDB storage for all storage types
*
@@ -19,7 +21,8 @@ class Mongo implements AuthorizationCodeInterface,
ClientCredentialsInterface,
UserCredentialsInterface,
RefreshTokenInterface,
- JwtBearerInterface
+ JwtBearerInterface,
+ OpenIDAuthorizationCodeInterface
{
protected $db;
protected $config;
@@ -171,7 +174,7 @@ class Mongo implements AuthorizationCodeInterface,
return is_null($code) ? false : $code;
}
- public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null)
+ 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)) {
@@ -182,7 +185,8 @@ class Mongo implements AuthorizationCodeInterface,
'user_id' => $user_id,
'redirect_uri' => $redirect_uri,
'expires' => $expires,
- 'scope' => $scope
+ 'scope' => $scope,
+ 'id_token' => $id_token,
))
);
} else {
@@ -193,7 +197,8 @@ class Mongo implements AuthorizationCodeInterface,
'user_id' => $user_id,
'redirect_uri' => $redirect_uri,
'expires' => $expires,
- 'scope' => $scope
+ 'scope' => $scope,
+ 'id_token' => $id_token,
)
);
}
diff --git a/src/OAuth2/Storage/Pdo.php b/src/OAuth2/Storage/Pdo.php
index 6f0eac3..0cdd52e 100644
--- a/src/OAuth2/Storage/Pdo.php
+++ b/src/OAuth2/Storage/Pdo.php
@@ -2,6 +2,8 @@
namespace OAuth2\Storage;
+use OAuth2\OpenID\Storage\UserClaimsInterface;
+use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
/**
* Simple PDO storage for all storage types
*
@@ -21,7 +23,9 @@ class Pdo implements AuthorizationCodeInterface,
RefreshTokenInterface,
JwtBearerInterface,
ScopeInterface,
- PublicKeyInterface
+ PublicKeyInterface,
+ UserClaimsInterface,
+ OpenIDAuthorizationCodeInterface
{
protected $db;
protected $config;
@@ -29,8 +33,11 @@ class Pdo implements AuthorizationCodeInterface,
public function __construct($connection, $config = array())
{
if (!$connection instanceof \PDO) {
+ if (is_string($connection)) {
+ $connection = array('dsn' => $connection);
+ }
if (!is_array($connection)) {
- throw new \InvalidArgumentException('First argument to OAuth2\Storage\Pdo must be an instance of PDO or a configuration array');
+ throw new \InvalidArgumentException('First argument to OAuth2\Storage\Pdo must be an instance of PDO, a DSN string, or a configuration array');
}
if (!isset($connection['dsn'])) {
throw new \InvalidArgumentException('configuration array must contain "dsn"');
@@ -39,8 +46,9 @@ class Pdo implements AuthorizationCodeInterface,
$connection = array_merge(array(
'username' => null,
'password' => null,
+ 'options' => array(),
), $connection);
- $connection = new \PDO($connection['dsn'], $connection['username'], $connection['password']);
+ $connection = new \PDO($connection['dsn'], $connection['username'], $connection['password'], $connection['options']);
}
$this->db = $connection;
@@ -159,8 +167,13 @@ class Pdo implements AuthorizationCodeInterface,
return $code;
}
- public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null)
+ public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
{
+ if (func_num_args() > 6) {
+ // we are calling with an id token
+ return call_user_func_array(array($this, 'setAuthorizationCodeWithIdToken'), func_get_args());
+ }
+
// convert expires to datestring
$expires = date('Y-m-d H:i:s', $expires);
@@ -174,6 +187,21 @@ class Pdo implements AuthorizationCodeInterface,
return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope'));
}
+ private function setAuthorizationCodeWithIdToken($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
+ {
+ // convert expires to datestring
+ $expires = date('Y-m-d H:i:s', $expires);
+
+ // if it exists, update it.
+ if ($this->getAuthorizationCode($code)) {
+ $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope, id_token =:id_token where authorization_code=:code', $this->config['code_table']));
+ } else {
+ $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope, id_token) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope, :id_token)', $this->config['code_table']));
+ }
+
+ return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'));
+ }
+
public function expireAuthorizationCode($code)
{
$stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE authorization_code = :code', $this->config['code_table']));
@@ -196,6 +224,45 @@ class Pdo implements AuthorizationCodeInterface,
return $this->getUser($username);
}
+ /* UserClaimsInterface */
+ public function getUserClaims($user_id, $claims)
+ {
+ if (!$userDetails = $this->getUserDetails($user_id)) {
+ return false;
+ }
+
+ $claims = explode(' ', trim($claims));
+ $userClaims = array();
+
+ // for each requested claim, if the user has the claim, set it in the response
+ $validClaims = explode(' ', self::VALID_CLAIMS);
+ foreach ($validClaims as $validClaim) {
+ if (in_array($validClaim, $claims)) {
+ if ($validClaim == 'address') {
+ // address is an object with subfields
+ $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails);
+ } else {
+ $userClaims = array_merge($this->getUserClaim($validClaim, $userDetails));
+ }
+ }
+ }
+
+ return $userClaims;
+ }
+
+ protected function getUserClaim($claim, $userDetails)
+ {
+ $userClaims = array();
+ $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim)));
+ $claimValues = explode(' ', $claimValuesString);
+
+ foreach ($claimValues as $value) {
+ $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null;
+ }
+
+ return $userClaims;
+ }
+
/* OAuth2\Storage\RefreshTokenInterface */
public function getRefreshToken($refresh_token)
{
@@ -267,8 +334,9 @@ class Pdo implements AuthorizationCodeInterface,
public function scopeExists($scope)
{
$scope = explode(' ', $scope);
- $stmt = $this->db->prepare(sprintf('SELECT count(scope) as count FROM %s WHERE scope IN ("%s")', $this->config['scope_table'], implode('","', $scope)));
- $stmt->execute();
+ $whereIn = implode(',', array_fill(0, count($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()) {
return $result['count'] == count($scope);
@@ -318,7 +386,7 @@ class Pdo implements AuthorizationCodeInterface,
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 = $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'));
diff --git a/src/OAuth2/Storage/Redis.php b/src/OAuth2/Storage/Redis.php
index ee6a4ca..fd94f47 100644
--- a/src/OAuth2/Storage/Redis.php
+++ b/src/OAuth2/Storage/Redis.php
@@ -2,6 +2,8 @@
namespace OAuth2\Storage;
+use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
+
/**
* redis storage for all storage types
*
@@ -19,7 +21,8 @@ class Redis implements AuthorizationCodeInterface,
UserCredentialsInterface,
RefreshTokenInterface,
JwtBearerInterface,
- ScopeInterface
+ ScopeInterface,
+ OpenIDAuthorizationCodeInterface
{
private $cache;
@@ -92,11 +95,11 @@ class Redis implements AuthorizationCodeInterface,
return $this->getValue($this->config['code_key'] . $code);
}
- public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null)
+ public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
{
return $this->setValue(
$this->config['code_key'] . $authorization_code,
- compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope'),
+ compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'),
$expires
);
}
diff --git a/src/OAuth2/TokenType/Bearer.php b/src/OAuth2/TokenType/Bearer.php
index f437a0b..0672572 100644
--- a/src/OAuth2/TokenType/Bearer.php
+++ b/src/OAuth2/TokenType/Bearer.php
@@ -26,6 +26,19 @@ class Bearer implements TokenTypeInterface
}
/**
+ * Check if the request has supplied token
+ *
+ * @see https://github.com/bshaffer/oauth2-server-php/issues/349#issuecomment-37993588
+ */
+ public function requestHasToken(RequestInterface $request)
+ {
+ $headers = $request->headers('AUTHORIZATION');
+
+ // check the header, then the querystring, then the request body
+ return !empty($headers) || (bool)($request->request($this->config['token_param_name'])) || (bool)($request->query($this->config['token_param_name']));
+ }
+
+ /**
* This is a convenience function that can be used to get the token, which can then
* be passed to getAccessTokenData(). The constraints specified by the draft are
* attempted to be adheared to in this method.
@@ -88,9 +101,9 @@ class Bearer implements TokenTypeInterface
}
if ($request->request($this->config['token_param_name'])) {
- // POST: Get the token from POST data
- if (strtolower($request->server('REQUEST_METHOD')) != 'post') {
- $response->setError(400, 'invalid_request', 'When putting the token in the body, the method must be POST');
+ // // POST: Get the token from POST data
+ if (!in_array(strtolower($request->server('REQUEST_METHOD')), array('post', 'put'))) {
+ $response->setError(400, 'invalid_request', 'When putting the token in the body, the method must be POST or PUT', '#section-2.2');
return null;
}