summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/OAuth2/ClientAssertionType/HttpBasic.php29
-rw-r--r--src/OAuth2/Controller/TokenController.php1
-rw-r--r--src/OAuth2/Encryption/Jwt.php4
-rw-r--r--src/OAuth2/GrantType/ClientCredentials.php16
-rw-r--r--src/OAuth2/Server.php6
-rw-r--r--src/OAuth2/Storage/AccessTokenInterface.php1
-rw-r--r--src/OAuth2/Storage/ClientCredentialsInterface.php18
-rw-r--r--src/OAuth2/Storage/Memory.php10
-rw-r--r--src/OAuth2/Storage/Mongo.php12
-rw-r--r--src/OAuth2/Storage/Pdo.php31
-rw-r--r--src/OAuth2/Storage/Redis.php40
-rw-r--r--src/OAuth2/TokenType/Bearer.php16
12 files changed, 155 insertions, 29 deletions
diff --git a/src/OAuth2/ClientAssertionType/HttpBasic.php b/src/OAuth2/ClientAssertionType/HttpBasic.php
index 2e8c89d..d755e1b 100644
--- a/src/OAuth2/ClientAssertionType/HttpBasic.php
+++ b/src/OAuth2/ClientAssertionType/HttpBasic.php
@@ -22,7 +22,8 @@ class HttpBasic implements ClientAssertionTypeInterface
{
$this->storage = $storage;
$this->config = array_merge(array(
- 'allow_credentials_in_request_body' => true
+ 'allow_credentials_in_request_body' => true,
+ 'allow_public_clients' => true,
), $config);
}
@@ -32,11 +33,23 @@ class HttpBasic implements ClientAssertionTypeInterface
return false;
}
- if (!isset($clientData['client_id']) || !isset($clientData['client_secret'])) {
- throw new \LogicException('the clientData array must have "client_id" and "client_secret" values set.');
+ if (!isset($clientData['client_id'])) {
+ throw new \LogicException('the clientData array must have "client_id" set');
}
- if ($this->storage->checkClientCredentials($clientData['client_id'], $clientData['client_secret']) === false) {
+ if (!isset($clientData['client_secret']) || $clientData['client_secret'] == '') {
+ if (!$this->config['allow_public_clients']) {
+ $response->setError(400, 'invalid_client', 'client credentials are required');
+
+ return false;
+ }
+
+ if (!$this->storage->isPublicClient($clientData['client_id'])) {
+ $response->setError(400, 'invalid_client', 'This client is invalid or must authenticate using a client secret');
+
+ return false;
+ }
+ } elseif ($this->storage->checkClientCredentials($clientData['client_id'], $clientData['client_secret']) === false) {
$response->setError(400, 'invalid_client', 'The client credentials are invalid');
return false;
@@ -70,7 +83,7 @@ class HttpBasic implements ClientAssertionTypeInterface
* @code
* return array(
* "client_id" => CLIENT_ID, // REQUIRED the client id
- * "client_secret" => CLIENT_SECRET, // REQUIRED the client secret
+ * "client_secret" => CLIENT_SECRET, // OPTIONAL the client secret (may be omitted for public clients)
* );
* @endcode
*
@@ -91,13 +104,13 @@ class HttpBasic implements ClientAssertionTypeInterface
* client_secret can be null if the client's password is an empty string
* @see http://tools.ietf.org/html/rfc6749#section-2.3.1
*/
-
- return array('client_id' => $request->request('client_id'), 'client_secret' => $request->request('client_secret', ''));
+ return array('client_id' => $request->request('client_id'), 'client_secret' => $request->request('client_secret'));
}
}
if ($response) {
- $response->setError(400, 'invalid_client', 'Client credentials were not found in the headers or body');
+ $message = $this->config['allow_credentials_in_request_body'] ? ' or body' : '';
+ $response->setError(400, 'invalid_client', 'Client credentials were not found in the headers'.$message);
}
return null;
diff --git a/src/OAuth2/Controller/TokenController.php b/src/OAuth2/Controller/TokenController.php
index a615354..277b477 100644
--- a/src/OAuth2/Controller/TokenController.php
+++ b/src/OAuth2/Controller/TokenController.php
@@ -86,6 +86,7 @@ class TokenController implements TokenControllerInterface
return null;
}
+
if (!isset($this->grantTypes[$grantTypeIdentifier])) {
/* TODO: If this is an OAuth2 supported grant type that we have chosen not to implement, throw a 501 Not Implemented instead */
$response->setError(400, 'unsupported_grant_type', sprintf('Grant type "%s" not supported', $grantTypeIdentifier));
diff --git a/src/OAuth2/Encryption/Jwt.php b/src/OAuth2/Encryption/Jwt.php
index 641a2e9..6bde68e 100644
--- a/src/OAuth2/Encryption/Jwt.php
+++ b/src/OAuth2/Encryption/Jwt.php
@@ -27,6 +27,10 @@ class Jwt implements EncryptionInterface
public function decode($jwt, $key = null, $verify = true)
{
+ if (!strpos($jwt, '.')) {
+ return false;
+ }
+
$tks = explode('.', $jwt);
if (count($tks) != 3) {
diff --git a/src/OAuth2/GrantType/ClientCredentials.php b/src/OAuth2/GrantType/ClientCredentials.php
index 0a73e53..d18bb8c 100644
--- a/src/OAuth2/GrantType/ClientCredentials.php
+++ b/src/OAuth2/GrantType/ClientCredentials.php
@@ -4,6 +4,7 @@ namespace OAuth2\GrantType;
use OAuth2\ClientAssertionType\HttpBasic;
use OAuth2\ResponseType\AccessTokenInterface;
+use OAuth2\Storage\ClientCredentialsInterface;
/**
* @author Brent Shaffer <bshafs at gmail dot com>
@@ -14,6 +15,18 @@ class ClientCredentials extends HttpBasic implements GrantTypeInterface
{
private $clientData;
+ public function __construct(ClientCredentialsInterface $storage, array $config = array())
+ {
+ /**
+ * The client credentials grant type MUST only be used by confidential clients
+ *
+ * @see http://tools.ietf.org/html/rfc6749#section-4.4
+ */
+ $config['allow_public_clients'] = false;
+
+ parent::__construct($storage, $config);
+ }
+
public function getQuerystringIdentifier()
{
return 'client_credentials';
@@ -35,8 +48,9 @@ class ClientCredentials extends HttpBasic implements GrantTypeInterface
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
- /*
+ /**
* Client Credentials Grant does NOT include a refresh token
+ *
* @see http://tools.ietf.org/html/rfc6749#section-4.4.3
*/
$includeRefreshToken = false;
diff --git a/src/OAuth2/Server.php b/src/OAuth2/Server.php
index 2d12c5b..12dfd3d 100644
--- a/src/OAuth2/Server.php
+++ b/src/OAuth2/Server.php
@@ -103,6 +103,7 @@ class Server implements ResourceControllerInterface,
'require_exact_redirect_uri' => true,
'allow_implicit' => false,
'allow_credentials_in_request_body' => true,
+ 'allow_public_clients' => true,
'always_issue_new_refresh_token' => false,
), $config);
@@ -400,7 +401,7 @@ class Server implements ResourceControllerInterface,
if (!isset($this->storages['client_credentials'])) {
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('allow_credentials_in_request_body' => ''));
+ $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);
break;
}
@@ -459,7 +460,8 @@ class Server implements ResourceControllerInterface,
}
if (isset($this->storages['client_credentials'])) {
- $grantTypes['client_credentials'] = new ClientCredentials($this->storages['client_credentials']);
+ $config = array_intersect_key($this->config, array('allow_credentials_in_request_body' => ''));
+ $grantTypes['client_credentials'] = new ClientCredentials($this->storages['client_credentials'], $config);
}
if (isset($this->storages['refresh_token'])) {
diff --git a/src/OAuth2/Storage/AccessTokenInterface.php b/src/OAuth2/Storage/AccessTokenInterface.php
index b538ad2..dd687e4 100644
--- a/src/OAuth2/Storage/AccessTokenInterface.php
+++ b/src/OAuth2/Storage/AccessTokenInterface.php
@@ -23,6 +23,7 @@ interface AccessTokenInterface
* is invalid:
* - client_id: Stored client identifier.
* - expires: Stored expiration in unix timestamp.
+ * - user_id: (optional) Stored user identifier.
* - scope: (optional) Stored scope values in space-separated string.
*
* @ingroup oauth2_section_7
diff --git a/src/OAuth2/Storage/ClientCredentialsInterface.php b/src/OAuth2/Storage/ClientCredentialsInterface.php
index 530157c..3318c69 100644
--- a/src/OAuth2/Storage/ClientCredentialsInterface.php
+++ b/src/OAuth2/Storage/ClientCredentialsInterface.php
@@ -28,4 +28,22 @@ interface ClientCredentialsInterface extends ClientInterface
* @ingroup oauth2_section_3
*/
public function checkClientCredentials($client_id, $client_secret = null);
+
+ /**
+ * Determine if the client is a "public" client, and therefore
+ * does not require passing credentials for certain grant types
+ *
+ * @param $client_id
+ * Client identifier to be check with.
+ *
+ * @return
+ * TRUE if the client is public, and FALSE if it isn't.
+ * @endcode
+ *
+ * @see http://tools.ietf.org/html/rfc6749#section-2.3
+ * @see https://github.com/bshaffer/oauth2-server-php/issues/257
+ *
+ * @ingroup oauth2_section_2
+ */
+ public function isPublicClient($client_id);
}
diff --git a/src/OAuth2/Storage/Memory.php b/src/OAuth2/Storage/Memory.php
index 7f5a0c7..ee59703 100644
--- a/src/OAuth2/Storage/Memory.php
+++ b/src/OAuth2/Storage/Memory.php
@@ -131,6 +131,16 @@ class Memory implements AuthorizationCodeInterface,
return isset($this->clientCredentials[$client_id]['client_secret']) && $this->clientCredentials[$client_id]['client_secret'] === $client_secret;
}
+ public function isPublicClient($client_id)
+ {
+ if (!isset($this->clientCredentials[$client_id])) {
+ return false;
+ }
+
+ return empty($this->clientCredentials[$client_id]['client_secret']);
+ }
+
+ /* ClientInterface */
public function getClientDetails($client_id)
{
if (!isset($this->clientCredentials[$client_id])) {
diff --git a/src/OAuth2/Storage/Mongo.php b/src/OAuth2/Storage/Mongo.php
index 3cc70c6..e198dfc 100644
--- a/src/OAuth2/Storage/Mongo.php
+++ b/src/OAuth2/Storage/Mongo.php
@@ -67,6 +67,16 @@ class Mongo implements AuthorizationCodeInterface,
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));
@@ -304,6 +314,6 @@ class Mongo implements AuthorizationCodeInterface,
public function setJti($client_id, $subject, $audience, $expiration, $jti)
{
//TODO: Needs mongodb implementation.
- throw new \Exception('setJti() for the MongoDB driver is currently unimplemented.');
+ throw new \Exception('setJti() for the MongoDB driver is currently unimplemented.');
}
}
diff --git a/src/OAuth2/Storage/Pdo.php b/src/OAuth2/Storage/Pdo.php
index cd19b4c..1c2b522 100644
--- a/src/OAuth2/Storage/Pdo.php
+++ b/src/OAuth2/Storage/Pdo.php
@@ -59,7 +59,7 @@ class Pdo implements AuthorizationCodeInterface,
), $config);
}
- /* OAuth2_Storage_ClientCredentialsInterface */
+ /* OAuth2\Storage\ClientCredentialsInterface */
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']));
@@ -70,6 +70,19 @@ class Pdo implements AuthorizationCodeInterface,
return $result && $result['client_secret'] == $client_secret;
}
+ 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()) {
+ return false;
+ }
+
+ return empty($result['client_secret']);;
+ }
+
+ /* OAuth2\Storage\ClientInterface */
public function getClientDetails($client_id)
{
$stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table']));
@@ -103,7 +116,7 @@ class Pdo implements AuthorizationCodeInterface,
return true;
}
- /* OAuth2_Storage_AccessTokenInterface */
+ /* OAuth2\Storage\AccessTokenInterface */
public function getAccessToken($access_token)
{
$stmt = $this->db->prepare(sprintf('SELECT * from %s where access_token = :access_token', $this->config['access_token_table']));
@@ -132,7 +145,7 @@ class Pdo implements AuthorizationCodeInterface,
return $stmt->execute(compact('access_token', 'client_id', 'user_id', 'expires', 'scope'));
}
- /* OAuth2_Storage_AuthorizationCodeInterface */
+ /* OAuth2\Storage\AuthorizationCodeInterface */
public function getAuthorizationCode($code)
{
$stmt = $this->db->prepare(sprintf('SELECT * from %s where authorization_code = :code', $this->config['code_table']));
@@ -168,7 +181,7 @@ class Pdo implements AuthorizationCodeInterface,
return $stmt->execute(compact('code'));
}
- /* OAuth2_Storage_UserCredentialsInterface */
+ /* OAuth2\Storage\UserCredentialsInterface */
public function checkUserCredentials($username, $password)
{
if ($user = $this->getUser($username)) {
@@ -183,7 +196,7 @@ class Pdo implements AuthorizationCodeInterface,
return $this->getUser($username);
}
- /* OAuth2_Storage_RefreshTokenInterface */
+ /* OAuth2\Storage\RefreshTokenInterface */
public function getRefreshToken($refresh_token)
{
$stmt = $this->db->prepare(sprintf('SELECT * FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table']));
@@ -254,8 +267,8 @@ class Pdo implements AuthorizationCodeInterface,
public function scopeExists($scope, $client_id = null)
{
$scope = explode(' ', $scope);
- $stmt = $this->db->prepare($sql = sprintf('SELECT scope FROM %s WHERE type="supported" AND (client_id=:client_id OR client_id IS NULL) ORDER BY client_id IS NOT NULL DESC', $this->config['scope_table']));
- $stmt->execute(compact('client_id'));
+ $stmt = $this->db->prepare($sql = sprintf('SELECT scope FROM %s WHERE type=:type AND (client_id=:client_id OR client_id IS NULL) ORDER BY client_id IS NOT NULL DESC', $this->config['scope_table']));
+ $stmt->execute(array('client_id' => $client_id, 'type'=> 'supported'));
if ($result = $stmt->fetch()) {
$supportedScope = explode(' ', $result['scope']);
@@ -268,8 +281,8 @@ class Pdo implements AuthorizationCodeInterface,
public function getDefaultScope($client_id = null)
{
- $stmt = $this->db->prepare($sql = sprintf('SELECT scope FROM %s WHERE type="default" AND (client_id=:client_id OR client_id IS NULL) ORDER BY client_id IS NOT NULL DESC', $this->config['scope_table']));
- $stmt->execute(compact('client_id'));
+ $stmt = $this->db->prepare($sql = sprintf('SELECT scope FROM %s WHERE type=:type AND (client_id=:client_id OR client_id IS NULL) ORDER BY client_id IS NOT NULL DESC', $this->config['scope_table']));
+ $stmt->execute(array('client_id' => $client_id, 'type' => 'default'));
if ($result = $stmt->fetch()) {
return $result['scope'];
diff --git a/src/OAuth2/Storage/Redis.php b/src/OAuth2/Storage/Redis.php
index 55d300c..0b2badc 100644
--- a/src/OAuth2/Storage/Redis.php
+++ b/src/OAuth2/Storage/Redis.php
@@ -67,18 +67,21 @@ class Redis implements AuthorizationCodeInterface,
$str = json_encode($value);
if ($expire > 0) {
$seconds = $expire - time();
-
- return $this->redis->setex($key, $seconds, $str);
+ $ret = $this->redis->setex($key, $seconds, $str);
} else {
- return $this->redis->set($key, $str);
+ $ret = $this->redis->set($key, $str);
}
+
+ // check that the key was set properly
+ // if this fails, an exception will usually thrown, so this step isn't strictly necessary
+ return $ret->getPayload() == 'OK';
}
protected function expireValue($key)
{
unset($this->cache[$key]);
- return $this->redis->expire($key);
+ return $this->redis->delete($key);
}
/* AuthorizationCodeInterface */
@@ -148,6 +151,16 @@ class Redis implements AuthorizationCodeInterface,
&& $client['client_secret'] == $client_secret;
}
+ public function isPublicClient($client_id)
+ {
+ if (!$client = $this->getClientDetails($client_id)) {
+ return false;
+ }
+
+ return empty($result['client_secret']);;
+ }
+
+ /* ClientInterface */
public function getClientDetails($client_id)
{
return $this->getValue($this->config['client_key'] . $client_id);
@@ -230,6 +243,21 @@ class Redis implements AuthorizationCodeInterface,
return $result;
}
+ public function setScope($scope, $client_id = null, $type = 'supported')
+ {
+ if (!in_array($type, array('default', 'supported'))) {
+ throw new \InvalidArgumentException('"$type" must be one of "default", "supported"');
+ }
+
+ if (is_null($client_id)) {
+ $key = $this->config['scope_key'].$type.':global';
+ } else {
+ $key = $this->config['scope_key'].$type.':'.$client_id;
+ }
+
+ return $this->setValue($key, $scope);
+ }
+
/*JWTBearerInterface */
public function getClientKey($client_id, $subject)
{
@@ -244,12 +272,12 @@ class Redis implements AuthorizationCodeInterface,
public function getJti($client_id, $subject, $audience, $expiration, $jti)
{
//TODO: Needs redis implementation.
- throw new \Exception('getJti() for the Redis driver is currently unimplemented.');
+ throw new \Exception('getJti() for the Redis driver is currently unimplemented.');
}
public function setJti($client_id, $subject, $audience, $expiration, $jti)
{
//TODO: Needs redis implementation.
- throw new \Exception('setJti() for the Redis driver is currently unimplemented.');
+ throw new \Exception('setJti() for the Redis driver is currently unimplemented.');
}
}
diff --git a/src/OAuth2/TokenType/Bearer.php b/src/OAuth2/TokenType/Bearer.php
index 4355275..f437a0b 100644
--- a/src/OAuth2/TokenType/Bearer.php
+++ b/src/OAuth2/TokenType/Bearer.php
@@ -51,13 +51,25 @@ class Bearer implements TokenTypeInterface
{
$headers = $request->headers('AUTHORIZATION');
- // Check that exactly one method was used
- $methodsUsed = !empty($headers) + !is_null($request->query($this->config['token_param_name'])) + !is_null($request->request($this->config['token_param_name']));
+ /**
+ * Ensure more than one method is not used for including an
+ * access token
+ *
+ * @see http://tools.ietf.org/html/rfc6750#section-3.1
+ */
+ $methodsUsed = !empty($headers) + (bool)($request->query($this->config['token_param_name'])) + (bool)($request->request($this->config['token_param_name']));
if ($methodsUsed > 1) {
$response->setError(400, 'invalid_request', 'Only one method may be used to authenticate at a time (Auth header, GET or POST)');
return null;
}
+
+ /**
+ * If no authentication is provided, set the status code
+ * to 401 and return no other error information
+ *
+ * @see http://tools.ietf.org/html/rfc6750#section-3.1
+ */
if ($methodsUsed == 0) {
$response->setStatusCode(401);