summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml5
-rw-r--r--CHANGELOG.md66
-rw-r--r--composer.json2
-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
-rw-r--r--test/OAuth2/Controller/TokenControllerTest.php35
-rw-r--r--test/OAuth2/Encryption/JwtTest.php8
-rw-r--r--test/OAuth2/ServerTest.php9
-rw-r--r--test/OAuth2/Storage/AccessTokenTest.php2
-rw-r--r--test/OAuth2/Storage/AuthorizationCodeTest.php4
-rw-r--r--test/OAuth2/Storage/ClientCredentialsTest.php2
-rw-r--r--test/OAuth2/Storage/ClientTest.php8
-rw-r--r--test/OAuth2/Storage/PublicKeyTest.php9
-rw-r--r--test/OAuth2/Storage/RefreshTokenTest.php2
-rw-r--r--test/OAuth2/Storage/ScopeTest.php28
-rw-r--r--test/OAuth2/Storage/UserCredentialsTest.php2
-rw-r--r--test/bootstrap.php5
-rw-r--r--test/config/storage.json7
-rw-r--r--test/lib/OAuth2/Storage/Bootstrap.php35
-rw-r--r--test/lib/OAuth2/Storage/MockRedisClient.php96
31 files changed, 344 insertions, 168 deletions
diff --git a/.gitignore b/.gitignore
index a175f81..fad4cdf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
# Test Files #
-test/config/test.sqlite \ No newline at end of file
+test/config/test.sqlite
+vendor
diff --git a/.travis.yml b/.travis.yml
index 1d1e6d1..8abddff 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,10 +8,13 @@ php:
env:
- SKIP_MONGO_TESTS=1
-services: mongodb
+services:
+ - mongodb
+ - redis-server
before_script:
- echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
+ - composer require predis/predis:dev-master
after_failure:
- ps ax | grep mongo
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..3270fe1
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,66 @@
+CHANGELOG for 1.x
+=================
+
+This changelog references the relevant changes (bug and security fixes) done
+in 1.x minor versions.
+
+To get the diff for a specific change, go to https://github.com/bshaffer/oauth2-server-php/commit/XXX where XXX is the change hash
+To get the diff between two versions, go to https://github.com/bshaffer/oauth2-server-php/compare/v1.0...v1.1
+
+* 1.2 (2014-01-03)
+
+ PR: https://github.com/bshaffer/oauth2-server-php/pull/288
+
+ * bug #285 changed response header from 200 to 401 when empty token received
+ * bug #286 adds documentation and links to spec for not including error messages when no token is supplied
+ * bug #280 ensures PHP warnings do not get thrown as a result of an invalid argument to $jwt->decode()
+ * bug #279 predis wrong number of arguments
+ * bug #277 Securing JS WebApp client secret w/ password grant type
+
+* 1.1 (2013-12-17)
+
+ PR: https://github.com/bshaffer/oauth2-server-php/pull/276
+
+ * bug #278 adds refresh token configuration to Server class
+ * bug #274 Supplying a null client_id and client_secret grants API access
+ * bug #244 [MongoStorage] More detailed implementation info
+ * bug #268 Implement jti for JWT Bearer tokens to prevent replay attacks.
+ * bug #266 Removing unused argument to getAccessTokenData
+ * bug #247 Make Bearer token type consistent
+ * bug #253 Fixing CryptoToken refresh token lifetime
+ * bug #246 refactors public key logic to be more intuitive
+ * bug #245 adds support for JSON crypto tokens
+ * bug #230 Remove unused columns in oauth_clients
+ * bug #215 makes Redis Scope Storage obey the same paradigm as PDO
+ * bug #228 removes scope group
+ * bug #227 squelches open basedir restriction error
+ * bug #223 Updated docblocks for RefreshTokenInterface.php
+ * bug #224 Adds protected properties
+ * bug #217 Implement ScopeInterface for PDO, Redis
+
+* 1.0 (2013-08-12)
+
+ * bug #203 Add redirect\_status_code config param for AuthorizeController
+ * bug #205 ensures unnecessary ? is not set when ** bug
+ * bug #204 Fixed call to LogicException
+ * bug #202 Add explode to checkRestrictedGrant in PDO Storage
+ * bug #197 adds support for 'false' default scope ** bug
+ * bug #192 reference errors and adds tests
+ * bug #194 makes some appropriate properties ** bug
+ * bug #191 passes config to HttpBasic
+ * bug #190 validates client credentials before ** bug
+ * bug #171 Fix wrong redirect following authorization step
+ * bug #187 client_id is now passed to getDefaultScope().
+ * bug #176 Require refresh_token in getRefreshToken response
+ * bug #174 make user\_id not required for refresh_token grant
+ * bug #173 Duplication in JwtBearer Grant
+ * bug #168 user\_id not required for authorization_code grant
+ * bug #133 hardens default security for user object
+ * bug #163 allows redirect\_uri on authorization_code to be NULL in docs example
+ * bug #162 adds getToken on ResourceController for convenience
+ * bug #161 fixes fatal error
+ * bug #163 Invalid redirect_uri handling
+ * bug #156 user\_id in OAuth2\_Storage_AuthorizationCodeInterface::getAuthorizationCode() response
+ * bug #157 Fix for extending access and refresh tokens
+ * bug #154 ResponseInterface: getParameter method is used in the library but not defined in the interface
+ * bug #148 Add more detail to examples in Readme.md
diff --git a/composer.json b/composer.json
index a8e5b46..2503439 100644
--- a/composer.json
+++ b/composer.json
@@ -13,7 +13,7 @@
],
"homepage": "http://github.com/bshaffer/oauth2-server-php",
"require":{
- "php":">=5.2.0"
+ "php":">=5.3.9"
},
"autoload": {
"psr-0": { "OAuth2": "src/" }
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);
diff --git a/test/OAuth2/Controller/TokenControllerTest.php b/test/OAuth2/Controller/TokenControllerTest.php
index 6cd7416..183e392 100644
--- a/test/OAuth2/Controller/TokenControllerTest.php
+++ b/test/OAuth2/Controller/TokenControllerTest.php
@@ -6,6 +6,7 @@ use OAuth2\Storage\Bootstrap;
use OAuth2\Server;
use OAuth2\GrantType\AuthorizationCode;
use OAuth2\GrantType\ClientCredentials;
+use OAuth2\GrantType\UserCredentials;
use OAuth2\Scope;
use OAuth2\Request\TestRequest;
use OAuth2\Response;
@@ -52,7 +53,7 @@ class Controller_TokenControllerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($response->getParameter('error_description'), 'Client credentials were not found in the headers or body');
}
- public function testNoClientSecret()
+ public function testNoClientSecretWithConfidentialClient()
{
// add the test parameters in memory
$server = $this->getTestServer();
@@ -65,7 +66,7 @@ class Controller_TokenControllerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($response->getStatusCode(), 400);
$this->assertEquals($response->getParameter('error'), 'invalid_client');
- $this->assertEquals($response->getParameter('error_description'), 'The client credentials are invalid');
+ $this->assertEquals($response->getParameter('error_description'), 'This client is invalid or must authenticate using a client secret');
}
public function testNoClientSecretWithEmptySecret()
@@ -132,9 +133,9 @@ class Controller_TokenControllerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($response->getStatusCode(), 200);
$this->assertNull($response->getParameter('error'));
$this->assertNull($response->getParameter('error_description'));
- $this->assertNotNUll($response->getParameter('access_token'));
- $this->assertNotNUll($response->getParameter('expires_in'));
- $this->assertNotNUll($response->getParameter('token_type'));
+ $this->assertNotNull($response->getParameter('access_token'));
+ $this->assertNotNull($response->getParameter('expires_in'));
+ $this->assertNotNull($response->getParameter('token_type'));
}
public function testValidClientIdScope()
@@ -198,6 +199,30 @@ class Controller_TokenControllerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($response->getParameter('error_description'), 'This application requires you specify a scope parameter');
}
+ public function testCanReceiveAccessTokenUsingPasswordGrantTypeWithoutClientSecret()
+ {
+ // add the test parameters in memory
+ $storage = Bootstrap::getInstance()->getMemoryStorage();
+ $server = new Server($storage);
+ $server->addGrantType(new UserCredentials($storage));
+
+ $request = TestRequest::createPost(array(
+ 'grant_type' => 'password', // valid grant type
+ 'client_id' => 'Test Client ID For Password Grant', // valid client id
+ 'username' => 'johndoe', // valid username
+ 'password' => 'password', // valid password for username
+ ));
+ $server->handleTokenRequest($request, $response = new Response());
+
+ $this->assertTrue($response instanceof Response);
+ $this->assertEquals(200, $response->getStatusCode(), var_export($response, 1));
+ $this->assertNull($response->getParameter('error'));
+ $this->assertNull($response->getParameter('error_description'));
+ $this->assertNotNull($response->getParameter('access_token'));
+ $this->assertNotNull($response->getParameter('expires_in'));
+ $this->assertNotNull($response->getParameter('token_type'));
+ }
+
public function testCreateController()
{
$storage = Bootstrap::getInstance()->getMemoryStorage();
diff --git a/test/OAuth2/Encryption/JwtTest.php b/test/OAuth2/Encryption/JwtTest.php
index b10c61b..20d3128 100644
--- a/test/OAuth2/Encryption/JwtTest.php
+++ b/test/OAuth2/Encryption/JwtTest.php
@@ -50,6 +50,14 @@ EOD;
$this->assertEquals($params, $payload);
}
+ public function testInvalidJwt()
+ {
+ $jwtUtil = new Jwt();
+
+ $this->assertFalse($jwtUtil->decode('goob'));
+ $this->assertFalse($jwtUtil->decode('go.o.b'));
+ }
+
public function provideClientCredentials()
{
$storage = Bootstrap::getInstance()->getMemoryStorage();
diff --git a/test/OAuth2/ServerTest.php b/test/OAuth2/ServerTest.php
index b61f851..8794b42 100644
--- a/test/OAuth2/ServerTest.php
+++ b/test/OAuth2/ServerTest.php
@@ -253,7 +253,10 @@ class ServerTest extends \PHPUnit_Framework_TestCase
{
// create mock storage
$storage = Bootstrap::getInstance()->getMemoryStorage();
- $server = new Server(array($storage), array('allow_credentials_in_request_body' => false));
+ $server = new Server(array($storage), array(
+ 'allow_credentials_in_request_body' => false,
+ 'allow_public_clients' => false
+ ));
$server->getTokenController();
$httpBasic = $server->getClientAssertionType();
@@ -261,8 +264,10 @@ class ServerTest extends \PHPUnit_Framework_TestCase
$prop = $reflection->getProperty('config');
$prop->setAccessible(true);
- $config = $prop->getValue($httpBasic); // get the private "storages" property
+ $config = $prop->getValue($httpBasic); // get the private "config" property
+
$this->assertEquals($config['allow_credentials_in_request_body'], false);
+ $this->assertEquals($config['allow_public_clients'], false);
}
public function testRefreshTokenConfig()
diff --git a/test/OAuth2/Storage/AccessTokenTest.php b/test/OAuth2/Storage/AccessTokenTest.php
index 17c9cd9..ba54b34 100644
--- a/test/OAuth2/Storage/AccessTokenTest.php
+++ b/test/OAuth2/Storage/AccessTokenTest.php
@@ -8,7 +8,7 @@ class AccessTokenTest extends BaseTest
public function testSetAccessToken(AccessTokenInterface $storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
+ $this->markTestSkipped('Skipped Storage');
return;
}
diff --git a/test/OAuth2/Storage/AuthorizationCodeTest.php b/test/OAuth2/Storage/AuthorizationCodeTest.php
index 9f57714..7dacbf4 100644
--- a/test/OAuth2/Storage/AuthorizationCodeTest.php
+++ b/test/OAuth2/Storage/AuthorizationCodeTest.php
@@ -8,7 +8,7 @@ class AuthorizationCodeTest extends BaseTest
public function testGetAuthorizationCode(AuthorizationCodeInterface $storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
+ $this->markTestSkipped('Skipped Storage');
return;
}
@@ -25,7 +25,7 @@ class AuthorizationCodeTest extends BaseTest
public function testSetAuthorizationCode(AuthorizationCodeInterface $storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
+ $this->markTestSkipped('Skipped Storage');
return;
}
diff --git a/test/OAuth2/Storage/ClientCredentialsTest.php b/test/OAuth2/Storage/ClientCredentialsTest.php
index ecaf6f6..21d6c14 100644
--- a/test/OAuth2/Storage/ClientCredentialsTest.php
+++ b/test/OAuth2/Storage/ClientCredentialsTest.php
@@ -8,7 +8,7 @@ class ClientCredentialsTest extends BaseTest
public function testCheckClientCredentials(ClientCredentialsInterface $storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
+ $this->markTestSkipped('Skipped Storage');
return;
}
diff --git a/test/OAuth2/Storage/ClientTest.php b/test/OAuth2/Storage/ClientTest.php
index 4d95c6c..e370e38 100644
--- a/test/OAuth2/Storage/ClientTest.php
+++ b/test/OAuth2/Storage/ClientTest.php
@@ -8,7 +8,7 @@ class ClientTest extends BaseTest
public function testGetClientDetails(ClientInterface $storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
+ $this->markTestSkipped('Skipped Storage');
return;
}
@@ -28,7 +28,7 @@ class ClientTest extends BaseTest
public function testCheckRestrictedGrantType(ClientInterface $storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
+ $this->markTestSkipped('Skipped Storage');
return;
}
@@ -46,7 +46,7 @@ class ClientTest extends BaseTest
public function testGetAccessToken(ClientInterface $storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
+ $this->markTestSkipped('Skipped Storage');
return;
}
@@ -63,7 +63,7 @@ class ClientTest extends BaseTest
public function testSaveClient(ClientInterface $storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
+ $this->markTestSkipped('Skipped Storage');
return;
}
diff --git a/test/OAuth2/Storage/PublicKeyTest.php b/test/OAuth2/Storage/PublicKeyTest.php
index d08c9c0..0e2bfd6 100644
--- a/test/OAuth2/Storage/PublicKeyTest.php
+++ b/test/OAuth2/Storage/PublicKeyTest.php
@@ -7,8 +7,13 @@ class PublicKeyTest extends BaseTest
/** @dataProvider provideStorage */
public function testSetAccessToken($storage)
{
- if (is_null($storage) || !$storage instanceof PublicKeyInterface) {
- return $this->markTestSkipped('Invalid storage for public key test');
+ if (is_null($storage)) {
+ return $this->markTestSkipped('Skipped Storage');
+ }
+
+ if (!$storage instanceof PublicKeyInterface) {
+ // incompatible storage
+ return;
}
$configDir = Bootstrap::getInstance()->getConfigDir();
diff --git a/test/OAuth2/Storage/RefreshTokenTest.php b/test/OAuth2/Storage/RefreshTokenTest.php
index 7777bb9..a356cc5 100644
--- a/test/OAuth2/Storage/RefreshTokenTest.php
+++ b/test/OAuth2/Storage/RefreshTokenTest.php
@@ -8,7 +8,7 @@ class RefreshTokenTest extends BaseTest
public function testSetRefreshToken(RefreshTokenInterface $storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
+ $this->markTestSkipped('Skipped Storage');
return;
}
diff --git a/test/OAuth2/Storage/ScopeTest.php b/test/OAuth2/Storage/ScopeTest.php
index a34ce54..83d9b6f 100644
--- a/test/OAuth2/Storage/ScopeTest.php
+++ b/test/OAuth2/Storage/ScopeTest.php
@@ -10,14 +10,11 @@ class ScopeTest extends BaseTest
public function testScopeExists($storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
-
- return;
+ return $this->markTestSkipped('Skipped Storage');
}
if (!$storage instanceof ScopeInterface) {
- $this->markTestSkipped('Skipping incompatible storage');
-
+ // incompatible storage
return;
}
@@ -32,14 +29,11 @@ class ScopeTest extends BaseTest
public function testGetDefaultScope($storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
-
- return;
+ return $this->markTestSkipped('Skipped Storage');
}
if (!$storage instanceof ScopeInterface) {
- $this->markTestSkipped('Skipping incompatible storage');
-
+ // incompatible storage
return;
}
@@ -52,14 +46,11 @@ class ScopeTest extends BaseTest
public function testClientScopeExists($storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
-
- return;
+ return $this->markTestSkipped('Skipped Storage');
}
if (!$storage instanceof ScopeInterface) {
- $this->markTestSkipped('Skipping incompatible storage');
-
+ // incompatible storage
return;
}
@@ -75,14 +66,11 @@ class ScopeTest extends BaseTest
public function testGetDefaultClientScope($storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
-
- return;
+ return $this->markTestSkipped('Skipped Storage');
}
if (!$storage instanceof ScopeInterface) {
- $this->markTestSkipped('Skipping incompatible storage');
-
+ // incompatible storage
return;
}
diff --git a/test/OAuth2/Storage/UserCredentialsTest.php b/test/OAuth2/Storage/UserCredentialsTest.php
index ee1c721..78fa804 100644
--- a/test/OAuth2/Storage/UserCredentialsTest.php
+++ b/test/OAuth2/Storage/UserCredentialsTest.php
@@ -8,7 +8,7 @@ class UserCredentialsTest extends BaseTest
public function testCheckUserCredentials(UserCredentialsInterface $storage = null)
{
if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
+ $this->markTestSkipped('Skipped Storage');
return;
}
diff --git a/test/bootstrap.php b/test/bootstrap.php
index a967dea..0a4af07 100644
--- a/test/bootstrap.php
+++ b/test/bootstrap.php
@@ -5,3 +5,8 @@ OAuth2\Autoloader::register();
// register test classes
OAuth2\Autoloader::register(dirname(__FILE__).'/lib');
+
+// register vendors if possible
+if (file_exists(__DIR__.'/../vendor/autoload.php')) {
+ require_once(__DIR__.'/../vendor/autoload.php');
+}
diff --git a/test/config/storage.json b/test/config/storage.json
index f2db151..23a3988 100644
--- a/test/config/storage.json
+++ b/test/config/storage.json
@@ -48,6 +48,10 @@
"Test Client ID Empty Secret": {
"client_secret": ""
},
+ "Test Client ID For Password Grant": {
+ "grant_types": "password",
+ "client_secret": ""
+ },
"Client ID With User ID": {
"client_secret": "TestSecret",
"user_id": "brent@brentertainment.com"
@@ -63,6 +67,9 @@
},
"testusername": {
"password": "testpass"
+ },
+ "johndoe": {
+ "password": "password"
}
},
"refresh_tokens" : {
diff --git a/test/lib/OAuth2/Storage/Bootstrap.php b/test/lib/OAuth2/Storage/Bootstrap.php
index 673a01a..d1e7818 100644
--- a/test/lib/OAuth2/Storage/Bootstrap.php
+++ b/test/lib/OAuth2/Storage/Bootstrap.php
@@ -8,6 +8,7 @@ class Bootstrap
private $mysql;
private $sqlite;
private $mongo;
+ private $redis;
private $configDir;
public function __construct()
@@ -45,7 +46,16 @@ class Bootstrap
public function getRedisStorage()
{
- return new Redis(new MockRedisClient());
+ if (!$this->redis) {
+ if (class_exists('Predis\Client')) {
+ $redis = new \Predis\Client();
+ $redis->flushdb();
+ $this->redis = new Redis($redis);
+ $this->createRedisDb($this->redis);
+ }
+ }
+
+ return $this->redis;
}
public function getMysqlPdo()
@@ -159,6 +169,29 @@ class Bootstrap
$db->oauth_users->insert(array('username' => "testuser", 'password' => "password"));
}
+ private function createRedisDb(Redis $storage)
+ {
+ $storage->setClientDetails("oauth_test_client", "testpass", "http://example.com", 'implicit password');
+ $storage->setAccessToken("testtoken", "Some Client", '', time() + 1000);
+ $storage->setAuthorizationCode("testcode", "Some Client", '', '', time() + 1000);
+ $storage->setUser("testuser", "password");
+
+ $storage->setScope('supportedscope1 supportedscope2 supportedscope3 supportedscope4');
+ $storage->setScope('defaultscope1 defaultscope2', null, 'default');
+
+ $storage->setScope('clientscope1 clientscope2', 'Test Client ID');
+ $storage->setScope('clientscope1 clientscope2', 'Test Client ID', 'default');
+
+ $storage->setScope('clientscope1 clientscope2 clientscope3', 'Test Client ID 2');
+ $storage->setScope('clientscope1 clientscope2', 'Test Client ID 2', 'default');
+
+ $storage->setScope('clientscope1 clientscope2', 'Test Default Scope Client ID');
+ $storage->setScope('clientscope1 clientscope2', 'Test Default Scope Client ID', 'default');
+
+ $storage->setScope('clientscope1 clientscope2 clientscope3', 'Test Default Scope Client ID 2');
+ $storage->setScope('clientscope3', 'Test Default Scope Client ID 2', 'default');
+ }
+
public function removeMongoDb(\MongoDB $db)
{
$db->drop();
diff --git a/test/lib/OAuth2/Storage/MockRedisClient.php b/test/lib/OAuth2/Storage/MockRedisClient.php
deleted file mode 100644
index dbfe264..0000000
--- a/test/lib/OAuth2/Storage/MockRedisClient.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-
-namespace OAuth2\Storage;
-
-class MockRedisClient
-{
- public function __construct()
- {
- $data = array(
- 'oauth_clients:oauth_test_client' => array(
- 'client_id' => 'oauth_test_client',
- 'client_secret' => "testpass",
- 'redirect_uri' => '',
- 'grant_types' => 'implicit password',
- ),
- 'oauth_clients:Test Client ID' => array(
- 'client_id' => 'Test Client ID',
- 'client_secret' => "TestSecret"
- ),
- 'oauth_clients:Test Client ID 2' => array(
- 'client_id' => 'Test Client ID 2',
- 'client_secret' => "TestSecret",
- ),
- 'oauth_clients:Test Default Scope Client ID' => array(
- 'client_id' => 'Test Default Scope Client ID',
- 'client_secret' => "TestSecret",
- ),
- 'oauth_clients:Test Default Scope Client ID 2' => array(
- 'client_id' => 'Test Default Scope Client ID 2',
- 'client_secret' => "TestSecret",
- ),
- 'oauth_access_tokens:testtoken' => array(
- 'access_token' => 'testtoken',
- 'client_id' => "Some Client",
- 'user_id' => '',
- 'expires' => 0,
- 'scope' => ''
- ),
- 'oauth_authorization_codes:testcode' => array(
- 'client_id' => "Some Client",
- 'authorization_code' => 'testcode',
- 'user_id' => '',
- 'redirect_uri' => '',
- 'expires' => 0,
- 'scope' => ''
- ),
- 'oauth_users:testuser' => array(
- 'user_id' => 'testuser',
- 'password' => "password",
- 'first_name' => '',
- 'last_name' => ''
- ),
- 'oauth_scopes:supported:global' => 'supportedscope1 supportedscope2 supportedscope3 supportedscope4',
- 'oauth_scopes:default:global' => 'defaultscope1 defaultscope2',
- 'oauth_scopes:supported:Test Client ID' => 'clientscope1 clientscope2',
- 'oauth_scopes:default:Test Client ID' => 'clientscope1 clientscope2',
- 'oauth_scopes:supported:Test Client ID 2' => 'clientscope3',
- 'oauth_scopes:default:Test Client ID 2' => 'clientscope3',
- 'oauth_scopes:supported:Test Default Scope Client ID' => 'clientscope1 clientscope2',
- 'oauth_scopes:default:Test Default Scope Client ID' => 'clientscope1 clientscope2',
- 'oauth_scopes:supported:Test Default Scope Client ID 2' => 'clientscope3',
- 'oauth_scopes:default:Test Default Scope Client ID 2' => 'clientscope3',
- );
- foreach ($data as $name => $val) {
- $data[$name] = json_encode($val);
- }
- $this->data = $data;
- }
-
- public function get($key)
- {
- return isset($this->data[$key]) ? $this->data[$key] : null;
- }
-
- public function set ($key, $value)
- {
- $this->data[$key] = $value;
-
- return true;
- }
-
- public function setex($key, $expires, $value)
- {
-
- $this->data[$key] = $value;
-
- return true;
- }
-
- public function expire($key)
- {
- unset($this->data[$key]);
-
- return true;
- }
-}