summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrent Shaffer <bshafs@gmail.com>2013-12-17 23:12:05 +0400
committerBrent Shaffer <bshafs@gmail.com>2013-12-17 23:12:05 +0400
commit31bf513f322e60684a699f67b79f7f1ae5de95ff (patch)
tree359158c38e4fbfd77cfde66f5e721fe7c455dfca
parent7abe692c57d8f11026556036ee59575f8b0d328e (diff)
parent753f1de6e96eb47ec312c4d4d5101254596e9002 (diff)
downloadoauth2-server-php-1.1.tar.xz
Merge pull request #276 from bshaffer/developv1.1
Create Tag v1.1
-rw-r--r--src/OAuth2/Autoloader.php2
-rw-r--r--src/OAuth2/ClientAssertionType/HttpBasic.php5
-rw-r--r--src/OAuth2/Controller/AuthorizeController.php19
-rw-r--r--src/OAuth2/Controller/ResourceController.php8
-rw-r--r--src/OAuth2/Controller/TokenController.php6
-rw-r--r--src/OAuth2/Encryption/EncryptionInterface.php9
-rw-r--r--src/OAuth2/Encryption/Jwt.php8
-rw-r--r--src/OAuth2/GrantType/AuthorizationCode.php4
-rw-r--r--src/OAuth2/GrantType/ClientCredentials.php18
-rw-r--r--src/OAuth2/GrantType/JwtBearer.php28
-rw-r--r--src/OAuth2/GrantType/RefreshToken.php3
-rw-r--r--src/OAuth2/GrantType/UserCredentials.php3
-rw-r--r--src/OAuth2/Response.php1
-rw-r--r--src/OAuth2/ResponseInterface.php2
-rw-r--r--src/OAuth2/ResponseType/AccessToken.php4
-rw-r--r--src/OAuth2/ResponseType/AuthorizationCode.php3
-rw-r--r--src/OAuth2/ResponseType/CryptoToken.php114
-rw-r--r--src/OAuth2/Scope.php1
-rw-r--r--src/OAuth2/Server.php17
-rw-r--r--src/OAuth2/Storage/ClientInterface.php1
-rw-r--r--src/OAuth2/Storage/CryptoToken.php77
-rw-r--r--src/OAuth2/Storage/JwtBearerInterface.php51
-rw-r--r--src/OAuth2/Storage/Memory.php88
-rw-r--r--src/OAuth2/Storage/Mongo.php52
-rw-r--r--src/OAuth2/Storage/Pdo.php115
-rw-r--r--src/OAuth2/Storage/PublicKeyInterface.php16
-rw-r--r--src/OAuth2/Storage/Redis.php78
-rw-r--r--src/OAuth2/Storage/RefreshTokenInterface.php3
-rw-r--r--src/OAuth2/TokenType/Bearer.php8
-rw-r--r--test/OAuth2/Encryption/JwtTest.php63
-rw-r--r--test/OAuth2/GrantType/ClientCredentialsTest.php21
-rw-r--r--test/OAuth2/GrantType/JWTBearerTest.php75
-rw-r--r--test/OAuth2/ResponseType/CryptoTokenTest.php88
-rw-r--r--test/OAuth2/ServerTest.php29
-rw-r--r--test/OAuth2/Storage/AccessTokenTest.php51
-rw-r--r--test/OAuth2/Storage/AuthorizationCodeTest.php72
-rw-r--r--test/OAuth2/Storage/ClientCredentialsTest.php27
-rw-r--r--test/OAuth2/Storage/ClientTest.php84
-rw-r--r--test/OAuth2/Storage/CryptoTokenTest.php45
-rw-r--r--test/OAuth2/Storage/PublicKeyTest.php22
-rw-r--r--test/OAuth2/Storage/RefreshTokenTest.php35
-rw-r--r--test/OAuth2/Storage/ScopeTest.php94
-rw-r--r--test/OAuth2/Storage/UserCredentialsTest.php39
-rw-r--r--test/OAuth2/StorageTest.php271
-rw-r--r--test/config/keys/id_rsa15
-rw-r--r--test/config/keys/id_rsa.pub16
-rw-r--r--test/config/storage.json57
-rw-r--r--test/lib/OAuth2/Request/TestRequest.php1
-rw-r--r--test/lib/OAuth2/Storage/BaseTest.php29
-rw-r--r--test/lib/OAuth2/Storage/Bootstrap.php67
-rw-r--r--test/lib/OAuth2/Storage/MockRedisClient.php41
51 files changed, 1598 insertions, 388 deletions
diff --git a/src/OAuth2/Autoloader.php b/src/OAuth2/Autoloader.php
index eff8461..24ac68b 100644
--- a/src/OAuth2/Autoloader.php
+++ b/src/OAuth2/Autoloader.php
@@ -31,7 +31,7 @@ class Autoloader
/**
* Handles autoloading of classes.
*
- * @param string $class A class name.
+ * @param string $class A class name.
*
* @return boolean Returns true if the class has been loaded
*/
diff --git a/src/OAuth2/ClientAssertionType/HttpBasic.php b/src/OAuth2/ClientAssertionType/HttpBasic.php
index a42dea0..2e8c89d 100644
--- a/src/OAuth2/ClientAssertionType/HttpBasic.php
+++ b/src/OAuth2/ClientAssertionType/HttpBasic.php
@@ -38,11 +38,13 @@ class HttpBasic implements ClientAssertionTypeInterface
if ($this->storage->checkClientCredentials($clientData['client_id'], $clientData['client_secret']) === false) {
$response->setError(400, 'invalid_client', 'The client credentials are invalid');
+
return false;
}
if (!$this->storage->checkRestrictedGrantType($clientData['client_id'], $request->request('grant_type'))) {
$response->setError(400, 'unauthorized_client', 'The grant type is unauthorized for this client_id');
+
return false;
}
@@ -89,6 +91,7 @@ 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', ''));
}
}
@@ -99,4 +102,4 @@ class HttpBasic implements ClientAssertionTypeInterface
return null;
}
-} \ No newline at end of file
+}
diff --git a/src/OAuth2/Controller/AuthorizeController.php b/src/OAuth2/Controller/AuthorizeController.php
index 7a66443..93ecf30 100644
--- a/src/OAuth2/Controller/AuthorizeController.php
+++ b/src/OAuth2/Controller/AuthorizeController.php
@@ -81,6 +81,7 @@ class AuthorizeController implements AuthorizeControllerInterface
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");
+
return;
}
@@ -113,12 +114,14 @@ class AuthorizeController implements AuthorizeControllerInterface
if (!$client_id = $request->query("client_id")) {
// We don't have a good URI to use
$response->setError(400, 'invalid_client', "No client id supplied");
+
return false;
}
// Get client details
if (!$clientData = $this->clientStorage->getClientDetails($client_id)) {
$response->setError(400, 'invalid_client', 'The client id supplied is invalid');
+
return false;
}
@@ -133,12 +136,14 @@ class AuthorizeController implements AuthorizeControllerInterface
$parts = parse_url($supplied_redirect_uri);
if (isset($parts['fragment']) && $parts['fragment']) {
$response->setError(400, 'invalid_uri', 'The redirect URI must not contain a fragment');
+
return false;
}
// validate against the registered redirect uri(s) if available
if ($registered_redirect_uri && !$this->validateRedirectUri($supplied_redirect_uri, $registered_redirect_uri)) {
$response->setError(400, 'redirect_uri_mismatch', 'The redirect URI provided is missing or does not match', '#section-3.1.2');
+
return false;
}
$redirect_uri = $supplied_redirect_uri;
@@ -146,11 +151,13 @@ class AuthorizeController implements AuthorizeControllerInterface
// use the registered redirect_uri if none has been supplied, if possible
if (!$registered_redirect_uri) {
$response->setError(400, 'invalid_uri', 'No redirect URI was supplied or stored');
+
return false;
}
if (count(explode(' ', $registered_redirect_uri)) > 1) {
$response->setError(400, 'invalid_uri', 'A redirect URI must be supplied when multiple redirect URIs are registered', '#section-3.1.2.3');
+
return false;
}
$redirect_uri = $registered_redirect_uri;
@@ -166,19 +173,23 @@ class AuthorizeController implements AuthorizeControllerInterface
// type and client_id are required
if (!$response_type || !in_array($response_type, array(self::RESPONSE_TYPE_AUTHORIZATION_CODE, self::RESPONSE_TYPE_ACCESS_TOKEN))) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_request', 'Invalid or missing response type', null);
+
return false;
}
if ($response_type == self::RESPONSE_TYPE_AUTHORIZATION_CODE) {
if (!isset($this->responseTypes['code'])) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'authorization code grant type not supported', null);
+
return false;
}
if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'authorization_code')) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null);
+
return false;
}
if ($this->responseTypes['code']->enforceRedirect() && !$redirect_uri) {
$response->setError(400, 'redirect_uri_mismatch', 'The redirect URI is mandatory and was not supplied');
+
return false;
}
}
@@ -186,10 +197,12 @@ class AuthorizeController implements AuthorizeControllerInterface
if ($response_type == self::RESPONSE_TYPE_ACCESS_TOKEN) {
if (!$this->config['allow_implicit']) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'implicit grant type not supported', null);
+
return false;
}
if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'implicit')) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null);
+
return false;
}
}
@@ -197,17 +210,20 @@ class AuthorizeController implements AuthorizeControllerInterface
// Validate that the requested scope is supported
if (false === $scope) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_client', 'This application requires you specify a scope parameter', null);
+
return false;
}
if (!is_null($scope) && !$this->scopeUtil->scopeExists($scope, $client_id)) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_scope', 'An unsupported scope was requested', null);
+
return false;
}
// Validate state parameter exists (if configured to enforce this)
if ($this->config['enforce_state'] && !$state) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, null, 'invalid_request', 'The state parameter is required');
+
return false;
}
@@ -240,7 +256,7 @@ class AuthorizeController implements AuthorizeControllerInterface
$parse_url = parse_url($uri);
// Add our params to the parsed uri
- foreach ( $params as $k => $v ) {
+ foreach ($params as $k => $v) {
if (isset($parse_url[$k])) {
$parse_url[$k] .= "&" . http_build_query($v);
} else {
@@ -293,6 +309,7 @@ class AuthorizeController implements AuthorizeControllerInterface
}
}
}
+
return false;
}
diff --git a/src/OAuth2/Controller/ResourceController.php b/src/OAuth2/Controller/ResourceController.php
index e407b54..862e364 100644
--- a/src/OAuth2/Controller/ResourceController.php
+++ b/src/OAuth2/Controller/ResourceController.php
@@ -38,7 +38,7 @@ class ResourceController implements ResourceControllerInterface
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null)
{
- $token = $this->getAccessTokenData($request, $response, $scope);
+ $token = $this->getAccessTokenData($request, $response);
// Check if we have token data
if (is_null($token)) {
@@ -61,6 +61,7 @@ class ResourceController implements ResourceControllerInterface
$response->getParameter('error_description')
)
));
+
return false;
}
@@ -79,9 +80,9 @@ class ResourceController implements ResourceControllerInterface
// Check token expiration (expires is a mandatory paramter)
if (!$token = $this->tokenStorage->getAccessToken($token_param)) {
$response->setError(401, 'invalid_token', 'The access token provided is invalid');
- } else if (!isset($token["expires"]) || !isset($token["client_id"])) {
+ } elseif (!isset($token["expires"]) || !isset($token["client_id"])) {
$response->setError(401, 'invalid_token', 'Malformed token (missing "expires" or "client_id")');
- } else if (time() > $token["expires"]) {
+ } elseif (time() > $token["expires"]) {
$response->setError(401, 'invalid_token', 'The access token provided has expired');
} else {
return $token;
@@ -98,6 +99,7 @@ class ResourceController implements ResourceControllerInterface
}
$response->addHttpHeaders(array('WWW-Authenticate' => $authHeader));
+
return null;
}
diff --git a/src/OAuth2/Controller/TokenController.php b/src/OAuth2/Controller/TokenController.php
index 3419f6f..a615354 100644
--- a/src/OAuth2/Controller/TokenController.php
+++ b/src/OAuth2/Controller/TokenController.php
@@ -74,6 +74,7 @@ class TokenController implements TokenControllerInterface
if (strtolower($request->server('REQUEST_METHOD')) != 'post') {
$response->setError(405, 'invalid_request', 'The request method must be POST when requesting an access token', '#section-3.2');
$response->addHttpHeaders(array('Allow' => 'POST'));
+
return null;
}
@@ -82,11 +83,13 @@ class TokenController implements TokenControllerInterface
*/
if (!$grantTypeIdentifier = $request->request('grant_type')) {
$response->setError(400, 'invalid_request', 'The grant type was not specified in the request');
+
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));
+
return null;
}
@@ -121,6 +124,7 @@ class TokenController implements TokenControllerInterface
// validate the Client ID (if applicable)
if (!is_null($storedClientId = $grantType->getClientId()) && $storedClientId != $clientId) {
$response->setError(400, 'invalid_grant', sprintf('%s doesn\'t exist or is invalid for the client', $grantTypeIdentifier));
+
return null;
}
}
@@ -136,6 +140,7 @@ class TokenController implements TokenControllerInterface
if (!$availableScope) {
if (false === $defaultScope = $this->scopeUtil->getDefaultScope($clientId)) {
$response->setError(400, 'invalid_scope', 'This application requires you specify a scope parameter');
+
return null;
}
}
@@ -145,6 +150,7 @@ class TokenController implements TokenControllerInterface
if (($requestedScope && !$this->scopeUtil->scopeExists($requestedScope, $clientId))
|| ($availableScope && !$this->scopeUtil->checkScope($requestedScope, $availableScope))) {
$response->setError(400, 'invalid_scope', 'An unsupported scope was requested');
+
return null;
}
diff --git a/src/OAuth2/Encryption/EncryptionInterface.php b/src/OAuth2/Encryption/EncryptionInterface.php
new file mode 100644
index 0000000..dc8e0d2
--- /dev/null
+++ b/src/OAuth2/Encryption/EncryptionInterface.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace OAuth2\Encryption;
+
+interface EncryptionInterface
+{
+ public function encode($payload, $key, $algorithm = null);
+ public function decode($payload, $key, $algorithm = null);
+}
diff --git a/src/OAuth2/Encryption/Jwt.php b/src/OAuth2/Encryption/Jwt.php
index 67a137e..641a2e9 100644
--- a/src/OAuth2/Encryption/Jwt.php
+++ b/src/OAuth2/Encryption/Jwt.php
@@ -6,7 +6,7 @@ namespace OAuth2\Encryption;
* @link https://github.com/F21/jwt
* @author F21
*/
-class Jwt
+class Jwt implements EncryptionInterface
{
public function encode($payload, $key, $algo = 'HS256')
{
@@ -60,7 +60,7 @@ class Jwt
private function verifySignature($signature, $input, $key, $algo = 'HS256')
{
- switch($algo){
+ switch ($algo) {
case'HS256':
case'HS384':
case'HS512':
@@ -82,7 +82,7 @@ class Jwt
private function sign($input, $key, $algo = 'HS256')
{
- switch($algo){
+ switch ($algo) {
case 'HS256':
return hash_hmac('sha256', $input, $key, true);
@@ -121,6 +121,7 @@ class Jwt
$b64 = str_replace(array('+', '/', '\r', '\n', '='),
array('-', '_'),
$b64);
+
return $b64;
}
@@ -129,6 +130,7 @@ class Jwt
$b64 = str_replace(array('-', '_'),
array('+', '/'),
$b64);
+
return base64_decode($b64);
}
}
diff --git a/src/OAuth2/GrantType/AuthorizationCode.php b/src/OAuth2/GrantType/AuthorizationCode.php
index b0c3d5f..60d80cb 100644
--- a/src/OAuth2/GrantType/AuthorizationCode.php
+++ b/src/OAuth2/GrantType/AuthorizationCode.php
@@ -30,12 +30,14 @@ class AuthorizationCode implements GrantTypeInterface
{
if (!$request->request('code')) {
$response->setError(400, 'invalid_request', 'Missing parameter: "code" is required');
+
return false;
}
$code = $request->request('code');
if (!$authCode = $this->storage->getAuthorizationCode($code)) {
$response->setError(400, 'invalid_grant', 'Authorization code doesn\'t exist or is invalid for the client');
+
return false;
}
@@ -46,6 +48,7 @@ class AuthorizationCode implements GrantTypeInterface
if (isset($authCode['redirect_uri']) && $authCode['redirect_uri']) {
if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != $authCode['redirect_uri']) {
$response->setError(400, 'redirect_uri_mismatch', "The redirect URI is missing or do not match", "#section-4.1.3");
+
return false;
}
}
@@ -56,6 +59,7 @@ class AuthorizationCode implements GrantTypeInterface
if ($authCode["expires"] < time()) {
$response->setError(400, 'invalid_grant', "The authorization code has expired");
+
return false;
}
diff --git a/src/OAuth2/GrantType/ClientCredentials.php b/src/OAuth2/GrantType/ClientCredentials.php
index ea0122d..0a73e53 100644
--- a/src/OAuth2/GrantType/ClientCredentials.php
+++ b/src/OAuth2/GrantType/ClientCredentials.php
@@ -12,6 +12,8 @@ use OAuth2\ResponseType\AccessTokenInterface;
*/
class ClientCredentials extends HttpBasic implements GrantTypeInterface
{
+ private $clientData;
+
public function getQuerystringIdentifier()
{
return 'client_credentials';
@@ -19,12 +21,16 @@ class ClientCredentials extends HttpBasic implements GrantTypeInterface
public function getScope()
{
- return null;
+ $this->loadClientData();
+
+ return isset($this->clientData['scope']) ? $this->clientData['scope'] : null;
}
public function getUserId()
{
- return null;
+ $this->loadClientData();
+
+ return isset($this->clientData['user_id']) ? $this->clientData['user_id'] : null;
}
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
@@ -34,6 +40,14 @@ class ClientCredentials extends HttpBasic implements GrantTypeInterface
* @see http://tools.ietf.org/html/rfc6749#section-4.4.3
*/
$includeRefreshToken = false;
+
return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
}
+
+ private function loadClientData()
+ {
+ if (!$this->clientData) {
+ $this->clientData = $this->storage->getClientDetails($this->getClientId());
+ }
+ }
}
diff --git a/src/OAuth2/GrantType/JwtBearer.php b/src/OAuth2/GrantType/JwtBearer.php
index 62823df..38fdc8b 100644
--- a/src/OAuth2/GrantType/JwtBearer.php
+++ b/src/OAuth2/GrantType/JwtBearer.php
@@ -72,6 +72,7 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
{
if (!$request->request("assertion")) {
$response->setError(400, 'invalid_request', 'Missing parameters: "assertion" required');
+
return null;
}
@@ -83,6 +84,7 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
if (!$jwt) {
$response->setError(400, 'invalid_request', "JWT is malformed");
+
return null;
}
@@ -102,16 +104,19 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
if (!isset($jwt['iss'])) {
$response->setError(400, 'invalid_grant', "Invalid issuer (iss) provided");
+
return null;
}
if (!isset($jwt['sub'])) {
$response->setError(400, 'invalid_grant', "Invalid subject (sub) provided");
+
return null;
}
if (!isset($jwt['exp'])) {
$response->setError(400, 'invalid_grant', "Expiration (exp) time must be present");
+
return null;
}
@@ -119,10 +124,12 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
if (ctype_digit($jwt['exp'])) {
if ($jwt['exp'] <= time()) {
$response->setError(400, 'invalid_grant', "JWT has expired");
+
return null;
}
} else {
$response->setError(400, 'invalid_grant', "Expiration (exp) time must be a unix time stamp");
+
return null;
}
@@ -131,10 +138,12 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
if (ctype_digit($notBefore)) {
if ($notBefore > time()) {
$response->setError(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time");
+
return null;
}
} else {
$response->setError(400, 'invalid_grant', "Not Before (nbf) time must be a unix time stamp");
+
return null;
}
}
@@ -142,19 +151,37 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
// Check the audience if required to match
if (!isset($jwt['aud']) || ($jwt['aud'] != $this->audience)) {
$response->setError(400, 'invalid_grant', "Invalid audience (aud)");
+
return null;
}
+ // Check the jti (nonce)
+ // @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-13#section-4.1.7
+ if (isset($jwt['jti'])) {
+ $jti = $this->storage->getJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']);
+
+ //Reject if jti is used and jwt is still valid (exp parameter has not expired).
+ if ($jti && $jti['expires'] > time()) {
+ $response->setError(400, 'invalid_grant', "JSON Token Identifier (jti) has already been used");
+
+ return null;
+ } else {
+ $this->storage->setJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']);
+ }
+ }
+
// Get the iss's public key
// @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-4.1.1
if (!$key = $this->storage->getClientKey($jwt['iss'], $jwt['sub'])) {
$response->setError(400, 'invalid_grant', "Invalid issuer (iss) or subject (sub) provided");
+
return null;
}
// Verify the JWT
if (!$this->jwtUtil->decode($undecodedJWT, $key, true)) {
$response->setError(400, 'invalid_grant', "JWT failed signature verification");
+
return null;
}
@@ -187,6 +214,7 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
$includeRefreshToken = false;
+
return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
}
}
diff --git a/src/OAuth2/GrantType/RefreshToken.php b/src/OAuth2/GrantType/RefreshToken.php
index e7de5ea..c866a80 100644
--- a/src/OAuth2/GrantType/RefreshToken.php
+++ b/src/OAuth2/GrantType/RefreshToken.php
@@ -35,16 +35,19 @@ class RefreshToken implements GrantTypeInterface
{
if (!$request->request("refresh_token")) {
$response->setError(400, 'invalid_request', 'Missing parameter: "refresh_token" is required');
+
return null;
}
if (!$refreshToken = $this->storage->getRefreshToken($request->request("refresh_token"))) {
$response->setError(400, 'invalid_grant', 'Invalid refresh token');
+
return null;
}
if ($refreshToken["expires"] < time()) {
$response->setError(400, 'invalid_grant', 'Refresh token has expired');
+
return null;
}
diff --git a/src/OAuth2/GrantType/UserCredentials.php b/src/OAuth2/GrantType/UserCredentials.php
index e2557c5..526fdbd 100644
--- a/src/OAuth2/GrantType/UserCredentials.php
+++ b/src/OAuth2/GrantType/UserCredentials.php
@@ -31,11 +31,13 @@ class UserCredentials implements GrantTypeInterface
{
if (!$request->request("password") || !$request->request("username")) {
$response->setError(400, 'invalid_request', 'Missing parameters: "username" and "password" required');
+
return null;
}
if (!$this->storage->checkUserCredentials($request->request("username"), $request->request("password"))) {
$response->setError(400, 'invalid_grant', 'Invalid username and password combination');
+
return null;
}
@@ -43,6 +45,7 @@ class UserCredentials implements GrantTypeInterface
if (empty($userInfo)) {
$response->setError(400, 'invalid_grant', 'Unable to retrieve user information');
+
return null;
}
diff --git a/src/OAuth2/Response.php b/src/OAuth2/Response.php
index 858446e..271bc4e 100644
--- a/src/OAuth2/Response.php
+++ b/src/OAuth2/Response.php
@@ -181,6 +181,7 @@ class Response implements ResponseInterface
// this only works for single-level arrays
$xml = new \SimpleXMLElement('<response/>');
array_walk($this->parameters, array($xml, 'addChild'));
+
return $xml->asXML();
}
diff --git a/src/OAuth2/ResponseInterface.php b/src/OAuth2/ResponseInterface.php
index 2548dc7..61da477 100644
--- a/src/OAuth2/ResponseInterface.php
+++ b/src/OAuth2/ResponseInterface.php
@@ -21,4 +21,4 @@ interface ResponseInterface
public function setRedirect($statusCode = 302, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null);
public function getParameter($name);
-} \ No newline at end of file
+}
diff --git a/src/OAuth2/ResponseType/AccessToken.php b/src/OAuth2/ResponseType/AccessToken.php
index 1c4fa2d..c82d9ed 100644
--- a/src/OAuth2/ResponseType/AccessToken.php
+++ b/src/OAuth2/ResponseType/AccessToken.php
@@ -13,6 +13,7 @@ class AccessToken implements AccessTokenInterface
{
protected $tokenStorage;
protected $refreshStorage;
+ protected $config;
public function __construct(AccessTokenStorageInterface $tokenStorage, RefreshTokenInterface $refreshStorage = null, array $config = array())
{
@@ -102,11 +103,12 @@ class AccessToken implements AccessTokenInterface
protected function generateAccessToken()
{
$tokenLen = 40;
- if (file_exists('/dev/urandom')) { // Get 100 bytes of random data
+ 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);
}
+
return substr(hash('sha512', $randomData), 0, $tokenLen);
}
diff --git a/src/OAuth2/ResponseType/AuthorizationCode.php b/src/OAuth2/ResponseType/AuthorizationCode.php
index b439637..c39d6a0 100644
--- a/src/OAuth2/ResponseType/AuthorizationCode.php
+++ b/src/OAuth2/ResponseType/AuthorizationCode.php
@@ -22,7 +22,6 @@ class AuthorizationCode implements AuthorizationCodeInterface
), $config);
}
-
public function getAuthorizeResponse($params, $user_id = null)
{
// build the URL to redirect to
@@ -59,6 +58,7 @@ class AuthorizationCode implements AuthorizationCodeInterface
{
$code = $this->generateAuthorizationCode();
$this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope);
+
return $code;
}
@@ -90,6 +90,7 @@ class AuthorizationCode implements AuthorizationCodeInterface
} else {
$randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
}
+
return substr(hash('sha512', $randomData), 0, $tokenLen);
}
}
diff --git a/src/OAuth2/ResponseType/CryptoToken.php b/src/OAuth2/ResponseType/CryptoToken.php
new file mode 100644
index 0000000..dd6896c
--- /dev/null
+++ b/src/OAuth2/ResponseType/CryptoToken.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace OAuth2\ResponseType;
+
+use OAuth2\Encryption\EncryptionInterface;
+use OAuth2\Encryption\Jwt;
+use OAuth2\Storage\AccessTokenInterface as AccessTokenStorageInterface;
+use OAuth2\Storage\RefreshTokenInterface;
+use OAuth2\Storage\PublicKeyInterface;
+use OAuth2\Storage\Memory;
+
+/**
+ *
+ * @author Brent Shaffer <bshafs at gmail dot com>
+ */
+class CryptoToken extends AccessToken
+{
+ protected $publicKeyStorage;
+ protected $encryptionUtil;
+
+ /**
+ * @param $config
+ * - store_encrypted_token_string (bool true)
+ * whether the entire encrypted string is stored,
+ * or just the token ID is stored
+ */
+ public function __construct(PublicKeyInterface $publicKeyStorage = null, AccessTokenStorageInterface $tokenStorage = null, RefreshTokenInterface $refreshStorage = null, array $config = array(), EncryptionInterface $encryptionUtil = null)
+ {
+ $this->publicKeyStorage = $publicKeyStorage;
+ $config = array_merge(array(
+ 'store_encrypted_token_string' => true,
+ ), $config);
+ if (is_null($tokenStorage)) {
+ // a pass-thru, so we can call the parent constructor
+ $tokenStorage = new Memory();
+ }
+ if (is_null($encryptionUtil)) {
+ $encryptionUtil = new Jwt();
+ }
+ $this->encryptionUtil = $encryptionUtil;
+ parent::__construct($tokenStorage, $refreshStorage, $config);
+ }
+
+ /**
+ * Handle the creation of access token, also issue refresh token if supported / desirable.
+ *
+ * @param $client_id
+ * Client identifier related to the access token.
+ * @param $user_id
+ * User ID associated with the access token
+ * @param $scope
+ * (optional) Scopes to be stored in space-separated string.
+ * @param bool $includeRefreshToken
+ * If true, a new refresh_token will be added to the response
+ *
+ * @see http://tools.ietf.org/html/rfc6749#section-5
+ * @ingroup oauth2_section_5
+ */
+ public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
+ {
+ // token to encrypt
+ $expires = time() + $this->config['access_lifetime'];
+ $cryptoToken = array(
+ 'id' => $this->generateAccessToken(),
+ 'client_id' => $client_id,
+ 'user_id' => $user_id,
+ 'expires' => $expires,
+ 'token_type' => $this->config['token_type'],
+ 'scope' => $scope
+ );
+
+ /*
+ * Issue a refresh token also, if we support them
+ *
+ * Refresh Tokens are considered supported if an instance of OAuth2_Storage_RefreshTokenInterface
+ * is supplied in the constructor
+ */
+ if ($includeRefreshToken && $this->refreshStorage) {
+ $cryptoToken["refresh_token"] = $this->generateRefreshToken();
+ $this->refreshStorage->setRefreshToken($cryptoToken['refresh_token'], $client_id, $user_id, time() + $this->config['refresh_token_lifetime'], $scope);
+ }
+
+ /*
+ * Encode the token data into a single access_token string
+ */
+ $access_token = $this->encodeToken($cryptoToken, $client_id);
+
+ /*
+ * Save the token to a secondary storage. This is implemented on the
+ * OAuth2\Storage\CryptoToken side, and will not actually store anything,
+ * if no secondary storage has been supplied
+ */
+ $token_to_store = $this->config['store_encrypted_token_string'] ? $access_token : $cryptoToken['id'];
+ $this->tokenStorage->setAccessToken($token_to_store, $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);
+
+ // token to return to the client
+ $token = array(
+ 'access_token' => $access_token,
+ 'expires_in' => $this->config['access_lifetime'],
+ 'token_type' => $this->config['token_type'],
+ 'scope' => $scope
+ );
+
+ return $token;
+ }
+
+ 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);
+ }
+}
diff --git a/src/OAuth2/Scope.php b/src/OAuth2/Scope.php
index a8119ba..6639346 100644
--- a/src/OAuth2/Scope.php
+++ b/src/OAuth2/Scope.php
@@ -47,6 +47,7 @@ class Scope implements ScopeInterface
{
$required_scope = explode(' ', trim($required_scope));
$available_scope = explode(' ', trim($available_scope));
+
return (count(array_diff($required_scope, $available_scope)) == 0);
}
diff --git a/src/OAuth2/Server.php b/src/OAuth2/Server.php
index 87e34e7..2d12c5b 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,
+ 'always_issue_new_refresh_token' => false,
), $config);
foreach ($grantTypes as $key => $grantType) {
@@ -121,6 +122,7 @@ class Server implements ResourceControllerInterface,
if (is_null($this->authorizeController)) {
$this->authorizeController = $this->createDefaultAuthorizeController();
}
+
return $this->authorizeController;
}
@@ -129,6 +131,7 @@ class Server implements ResourceControllerInterface,
if (is_null($this->tokenController)) {
$this->tokenController = $this->createDefaultTokenController();
}
+
return $this->tokenController;
}
@@ -137,6 +140,7 @@ class Server implements ResourceControllerInterface,
if (is_null($this->resourceController)) {
$this->resourceController = $this->createDefaultResourceController();
}
+
return $this->resourceController;
}
@@ -188,6 +192,7 @@ class Server implements ResourceControllerInterface,
{
$this->response = is_null($response) ? new Response() : $response;
$this->getTokenController()->handleTokenRequest($request, $this->response);
+
return $this->response;
}
@@ -195,6 +200,7 @@ class Server implements ResourceControllerInterface,
{
$this->response = is_null($response) ? new Response() : $response;
$value = $this->getTokenController()->grantAccessToken($request, $this->response);
+
return $value;
}
@@ -230,6 +236,7 @@ class Server implements ResourceControllerInterface,
{
$this->response = $response;
$this->getAuthorizeController()->handleAuthorizeRequest($request, $this->response, $is_authorized, $user_id);
+
return $this->response;
}
@@ -256,6 +263,7 @@ class Server implements ResourceControllerInterface,
{
$this->response = is_null($response) ? new Response() : $response;
$value = $this->getAuthorizeController()->validateAuthorizeRequest($request, $this->response);
+
return $value;
}
@@ -263,6 +271,7 @@ class Server implements ResourceControllerInterface,
{
$this->response = is_null($response) ? new Response() : $response;
$value = $this->getResourceController()->verifyResourceRequest($request, $this->response, $scope);
+
return $value;
}
@@ -270,6 +279,7 @@ class Server implements ResourceControllerInterface,
{
$this->response = is_null($response) ? new Response() : $response;
$value = $this->getResourceController()->getAccessTokenData($request, $this->response);
+
return $value;
}
@@ -352,6 +362,7 @@ class Server implements ResourceControllerInterface,
$storage = isset($this->storages['scope']) ? $this->storages['scope'] : null;
$this->scopeUtil = new Scope($storage);
}
+
return $this->scopeUtil;
}
@@ -372,6 +383,7 @@ class Server implements ResourceControllerInterface,
$this->responseTypes = $this->getDefaultResponseTypes();
}
$config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state require_exact_redirect_uri')));
+
return new AuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil());
}
@@ -407,12 +419,14 @@ class Server implements ResourceControllerInterface,
$this->tokenType = $this->getDefaultTokenType();
}
$config = array_intersect_key($this->config, array('www_realm' => ''));
+
return new ResourceController($this->tokenType, $this->storages['access_token'], $config, $this->getScopeUtil());
}
protected function getDefaultTokenType()
{
$config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name')));
+
return new Bearer($config);
}
@@ -449,7 +463,8 @@ class Server implements ResourceControllerInterface,
}
if (isset($this->storages['refresh_token'])) {
- $grantTypes['refresh_token'] = new RefreshToken($this->storages['refresh_token']);
+ $config = array_intersect_key($this->config, array('always_issue_new_refresh_token' => ''));
+ $grantTypes['refresh_token'] = new RefreshToken($this->storages['refresh_token'], $config);
}
if (isset($this->storages['authorization_code'])) {
diff --git a/src/OAuth2/Storage/ClientInterface.php b/src/OAuth2/Storage/ClientInterface.php
index 671025e..e9d0c72 100644
--- a/src/OAuth2/Storage/ClientInterface.php
+++ b/src/OAuth2/Storage/ClientInterface.php
@@ -28,6 +28,7 @@ interface ClientInterface
* "redirect_uri" => REDIRECT_URI, // REQUIRED redirect_uri registered for the client
* "client_id" => CLIENT_ID, // OPTIONAL the client id
* "grant_types" => GRANT_TYPES, // OPTIONAL an array of restricted grant types
+ * "user_id" => USER_ID, // OPTIONAL the user identifier associated with this client
* );
* @endcode
*
diff --git a/src/OAuth2/Storage/CryptoToken.php b/src/OAuth2/Storage/CryptoToken.php
new file mode 100644
index 0000000..3883c7b
--- /dev/null
+++ b/src/OAuth2/Storage/CryptoToken.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace OAuth2\Storage;
+
+use OAuth2\Encryption\EncryptionInterface;
+use OAuth2\Encryption\Jwt;
+
+/**
+ *
+ * @author Brent Shaffer <bshafs at gmail dot com>
+ */
+class CryptoToken implements AccessTokenInterface
+{
+ protected $publicKeyStorage;
+ protected $tokenStorage;
+ protected $encryptionUtil;
+
+ /**
+ * @param string $publicKey
+ * the public key encryption to use
+ *
+ * @param string $privateKey (optional)
+ * (optional) the private key to use to sign tokens
+ * this is only required for token granting, and can be omitted for resource servers,
+ * as only the publc key is required for crypto token verification
+ *
+ * @param array $config
+ * (optional) configuration array. Valid parameters are
+ * - encryption_algorithm
+ * the algorithm to use for encryption. This is passed to the
+ * EncryptionInterface object.
+ * @see OAuth2\Encryption\Jwt::verifySignature
+ *
+ * @param OAuth2\Storage\AccessTokenInterface $tokenStorage
+ * (optional) persist the access token to another storage. This is useful if
+ * you want to retain access token grant information somewhere, but
+ * is not necessary when using this grant type.
+ *
+ * @param OAuth2\Encryption\EncryptionInterface $encryptionUtil
+ * (optional) class to use for "encode" and "decode" functions.
+ */
+ public function __construct(PublicKeyInterface $publicKeyStorage, AccessTokenInterface $tokenStorage = null, EncryptionInterface $encryptionUtil = null)
+ {
+ $this->publicKeyStorage = $publicKeyStorage;
+ $this->tokenStorage = $tokenStorage;
+ if (is_null($encryptionUtil)) {
+ $encryptionUtil = new Jwt;
+ }
+ $this->encryptionUtil = $encryptionUtil;
+ }
+
+ public function getAccessToken($oauth_token)
+ {
+ // just decode the token, don't verify
+ if (!$tokenData = $this->encryptionUtil->decode($oauth_token, null, false)) {
+ return false;
+ }
+
+ $client_id = isset($tokenData['client_id']) ? $tokenData['client_id'] : null;
+ $public_key = $this->publicKeyStorage->getPublicKey($client_id);
+ $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
+
+ // now that we have the client_id, verify the token
+ if (false === $this->encryptionUtil->decode($oauth_token, $public_key, $algorithm)) {
+ return false;
+ }
+
+ return $tokenData;
+ }
+
+ public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null)
+ {
+ if ($this->tokenStorage) {
+ return $this->tokenStorage->setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope);
+ }
+ }
+}
diff --git a/src/OAuth2/Storage/JwtBearerInterface.php b/src/OAuth2/Storage/JwtBearerInterface.php
index 6b9dc3a..fcb1fe2 100644
--- a/src/OAuth2/Storage/JwtBearerInterface.php
+++ b/src/OAuth2/Storage/JwtBearerInterface.php
@@ -18,11 +18,58 @@ interface JwtBearerInterface
* Get the public key associated with a client_id
*
* @param $client_id
- * Client identifier to be check with.
+ * Client identifier to be checked with.
*
* @return
* STRING Return the public key for the client_id if it exists, and MUST return FALSE if it doesn't.
- * @endcode
*/
public function getClientKey($client_id, $subject);
+
+ /**
+ * Get a jti (JSON token identifier)by matching against the client_id, subject, audience and expiration.
+ *
+ * @param $client_id
+ * Client identifier to match.
+ *
+ * @param $subject
+ * The subject to match.
+ *
+ * @param $audience
+ * The audience to match.
+ *
+ * @param $expiration
+ * The expiration of the jti.
+ *
+ * @param $jti
+ * The jti to match.
+ *
+ * @return
+ * An associative array as below, and return NULL if the jti does not exist.
+ * - issuer: Stored client identifier.
+ * - subject: Stored subject.
+ * - audience: Stored audience.
+ * - expires: Stored expiration in unix timestamp.
+ * - jti: The stored jti.
+ */
+ public function getJti($client_id, $subject, $audience, $expiration, $jti);
+
+ /**
+ * Store a used jti so that we can check against it to prevent replay attacks.
+
+ * @param $client_id
+ * Client identifier to insert.
+ *
+ * @param $subject
+ * The subject to insert.
+ *
+ * @param $audience
+ * The audience to insert.
+ *
+ * @param $expiration
+ * The expiration of the jti.
+ *
+ * @param $jti
+ * The jti to insert.
+ */
+ public function setJti($client_id, $subject, $audience, $expiration, $jti);
}
diff --git a/src/OAuth2/Storage/Memory.php b/src/OAuth2/Storage/Memory.php
index 45df708..7f5a0c7 100644
--- a/src/OAuth2/Storage/Memory.php
+++ b/src/OAuth2/Storage/Memory.php
@@ -16,7 +16,8 @@ class Memory implements AuthorizationCodeInterface,
ClientCredentialsInterface,
RefreshTokenInterface,
JwtBearerInterface,
- ScopeInterface
+ ScopeInterface,
+ PublicKeyInterface
{
private $authorizationCodes;
private $userCredentials;
@@ -24,10 +25,12 @@ class Memory implements AuthorizationCodeInterface,
private $refreshTokens;
private $accessTokens;
private $jwt;
+ private $jti;
private $supportedScopes;
private $clientSupportedScopes;
private $clientDefaultScopes;
private $defaultScope;
+ private $keys;
public function __construct($params = array())
{
@@ -38,10 +41,12 @@ class Memory implements AuthorizationCodeInterface,
'refresh_tokens' => array(),
'access_tokens' => array(),
'jwt' => array(),
+ 'jti' => array(),
'default_scope' => null,
'client_supported_scopes' => array(),
'client_default_scopes' => array(),
'supported_scopes' => array(),
+ 'keys' => array(),
), $params);
$this->authorizationCodes = $params['authorization_codes'];
@@ -50,10 +55,12 @@ class Memory implements AuthorizationCodeInterface,
$this->refreshTokens = $params['refresh_tokens'];
$this->accessTokens = $params['access_tokens'];
$this->jwt = $params['jwt'];
+ $this->jti = $params['jti'];
$this->supportedScopes = $params['supported_scopes'];
$this->clientSupportedScopes = $params['client_supported_scopes'];
$this->clientDefaultScopes = $params['client_default_scopes'];
$this->defaultScope = $params['default_scope'];
+ $this->keys = $params['keys'];
}
/* AuthorizationCodeInterface */
@@ -89,6 +96,7 @@ class Memory implements AuthorizationCodeInterface,
public function checkUserCredentials($username, $password)
{
$userDetails = $this->getUserDetails($username);
+
return $userDetails && $userDetails['password'] && $userDetails['password'] === $password;
}
@@ -150,9 +158,17 @@ class Memory implements AuthorizationCodeInterface,
return true;
}
- public function setClientCredentials($client_credentials)
+ public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $user_id = null)
{
- $this->clientCredentials = $client_credentials;
+ $this->clientCredentials[$client_id] = array(
+ 'client_id' => $client_id,
+ 'client_secret' => $client_secret,
+ 'redirect_uri' => $redirect_uri,
+ 'grant_types' => $grant_types,
+ 'user_id' => $user_id,
+ );
+
+ return true;
}
/* RefreshTokenInterface */
@@ -208,7 +224,7 @@ class Memory implements AuthorizationCodeInterface,
{
if ($client_id && array_key_exists($client_id, $this->clientDefaultScopes)) {
return implode(' ', $this->clientDefaultScopes[$client_id]);
- }else{
+ } else {
return $this->defaultScope;
}
}
@@ -227,4 +243,68 @@ class Memory implements AuthorizationCodeInterface,
return false;
}
+
+ public function getJti($client_id, $subject, $audience, $expires, $jti)
+ {
+ foreach ($this->jti as $storedJti) {
+ if ($storedJti['issuer'] == $client_id && $storedJti['subject'] == $subject && $storedJti['audience'] == $audience && $storedJti['expires'] == $expires && $storedJti['jti'] == $jti) {
+ return array('issuer' => $storedJti['issuer'],
+ 'subject' => $storedJti['subject'],
+ 'audience' => $storedJti['audience'],
+ 'expires' => $storedJti['expires'],
+ 'jti' => $storedJti['jti']
+ );
+ }
+ }
+
+ return null;
+ }
+
+ public function setJti($client_id, $subject, $audience, $expires, $jti)
+ {
+ $this->jti[] = array('issuer' => $client_id, 'subject' => $subject, 'audience' => $audience, 'expires' => $expires, 'jti' => $jti);
+ }
+
+ /*PublicKeyInterface */
+ public function getPublicKey($client_id = null)
+ {
+ if (isset($this->keys[$client_id])) {
+ return $this->keys[$client_id]['public_key'];
+ }
+
+ // use a global encryption pair
+ if (isset($this->keys['public_key'])) {
+ return $this->keys['public_key'];
+ }
+
+ return false;
+ }
+
+ public function getPrivateKey($client_id = null)
+ {
+ if (isset($this->keys[$client_id])) {
+ return $this->keys[$client_id]['private_key'];
+ }
+
+ // use a global encryption pair
+ if (isset($this->keys['private_key'])) {
+ return $this->keys['private_key'];
+ }
+
+ return false;
+ }
+
+ public function getEncryptionAlgorithm($client_id = null)
+ {
+ if (isset($this->keys[$client_id]['encryption_algorithm'])) {
+ return $this->keys[$client_id]['encryption_algorithm'];
+ }
+
+ // use a global encryption algorithm
+ if (isset($this->keys['encryption_algorithm'])) {
+ return $this->keys['encryption_algorithm'];
+ }
+
+ return 'RS256';
+ }
}
diff --git a/src/OAuth2/Storage/Mongo.php b/src/OAuth2/Storage/Mongo.php
index 0ada22d..3cc70c6 100644
--- a/src/OAuth2/Storage/Mongo.php
+++ b/src/OAuth2/Storage/Mongo.php
@@ -5,8 +5,12 @@ namespace OAuth2\Storage;
/**
* Simple MongoDB storage for all storage types
*
- * NOTE: This class should never be used in production, and is
- * a stub class for example use only
+ * NOTE: This class is meant to get users started
+ * quickly. If your application requires further
+ * customization, extend this class or create your own.
+ *
+ * NOTE: Passwords are stored in plaintext, which is never
+ * a good idea. Be sure to override this for your application
*
* @author Julien Chaumond <chaumond@gmail.com>
*/
@@ -24,8 +28,7 @@ class Mongo implements AuthorizationCodeInterface,
{
if ($connection instanceof \MongoDB) {
$this->db = $connection;
- }
- else {
+ } else {
if (!is_array($connection)) {
throw new \InvalidArgumentException('First argument to OAuth2_Storage_Mongo must be an instance of MongoDB or a configuration array');
}
@@ -60,6 +63,7 @@ class Mongo implements AuthorizationCodeInterface,
if ($result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) {
return $result['client_secret'] == $client_secret;
}
+
return false;
}
@@ -70,6 +74,33 @@ class Mongo implements AuthorizationCodeInterface,
return is_null($result) ? false : $result;
}
+ public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $user_id = null)
+ {
+ if ($this->getClientDetails($client_id)) {
+ $this->collection('client_table')->update(
+ array('client_id' => $client_id),
+ array('$set' => array(
+ 'client_secret' => $client_secret,
+ 'redirect_uri' => $redirect_uri,
+ 'grant_types' => $grant_types,
+ 'user_id' => $user_id,
+ ))
+ );
+ } else {
+ $this->collection('client_table')->insert(
+ array(
+ 'client_id' => $client_id,
+ 'client_secret' => $client_secret,
+ 'redirect_uri' => $redirect_uri,
+ 'grant_types' => $grant_types,
+ 'user_id' => $user_id,
+ )
+ );
+ }
+
+ return true;
+ }
+
public function checkRestrictedGrantType($client_id, $grant_type)
{
$details = $this->getClientDetails($client_id);
@@ -172,6 +203,7 @@ class Mongo implements AuthorizationCodeInterface,
if ($user = $this->getUser($username)) {
return $this->checkPassword($user, $password);
}
+
return false;
}
@@ -262,4 +294,16 @@ class Mongo implements AuthorizationCodeInterface,
return $result;
}
+
+ public function getJti($client_id, $subject, $audience, $expiration, $jti)
+ {
+ //TODO: Needs mongodb implementation.
+ throw new \Exception('getJti() for the MongoDB driver is currently unimplemented.');
+ }
+
+ public function setJti($client_id, $subject, $audience, $expiration, $jti)
+ {
+ //TODO: Needs mongodb implementation.
+ 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 49f977a..cd19b4c 100644
--- a/src/OAuth2/Storage/Pdo.php
+++ b/src/OAuth2/Storage/Pdo.php
@@ -19,7 +19,9 @@ class Pdo implements AuthorizationCodeInterface,
ClientCredentialsInterface,
UserCredentialsInterface,
RefreshTokenInterface,
- JwtBearerInterface
+ JwtBearerInterface,
+ ScopeInterface,
+ PublicKeyInterface
{
protected $db;
protected $config;
@@ -51,7 +53,9 @@ class Pdo implements AuthorizationCodeInterface,
'refresh_token_table' => 'oauth_refresh_tokens',
'code_table' => 'oauth_authorization_codes',
'user_table' => 'oauth_users',
- 'jwt_table' => 'oauth_jwt',
+ 'jwt_table' => 'oauth_jwt',
+ 'scope_table' => 'oauth_scopes',
+ 'public_key_table' => 'oauth_public_keys',
), $config);
}
@@ -63,7 +67,7 @@ class Pdo implements AuthorizationCodeInterface,
$result = $stmt->fetch();
// make this extensible
- return $result['client_secret'] == $client_secret;
+ return $result && $result['client_secret'] == $client_secret;
}
public function getClientDetails($client_id)
@@ -74,6 +78,18 @@ class Pdo implements AuthorizationCodeInterface,
return $stmt->fetch();
}
+ public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $user_id = null)
+ {
+ // if it exists, update it.
+ if ($this->getClientDetails($client_id)) {
+ $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_secret=:client_secret, redirect_uri=:redirect_uri, grant_types=:grant_types, user_id=:user_id where client_id=:client_id', $this->config['client_table']));
+ } else {
+ $stmt = $this->db->prepare(sprintf('INSERT INTO %s (client_id, client_secret, redirect_uri, grant_types, user_id) VALUES (:client_id, :client_secret, :redirect_uri, :grant_types, :user_id)', $this->config['client_table']));
+ }
+
+ return $stmt->execute(compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'user_id'));
+ }
+
public function checkRestrictedGrantType($client_id, $grant_type)
{
$details = $this->getClientDetails($client_id);
@@ -112,6 +128,7 @@ class Pdo implements AuthorizationCodeInterface,
} else {
$stmt = $this->db->prepare(sprintf('INSERT INTO %s (access_token, client_id, expires, user_id, scope) VALUES (:access_token, :client_id, :expires, :user_id, :scope)', $this->config['access_token_table']));
}
+
return $stmt->execute(compact('access_token', 'client_id', 'user_id', 'expires', 'scope'));
}
@@ -140,6 +157,7 @@ class Pdo implements AuthorizationCodeInterface,
} else {
$stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope)', $this->config['code_table']));
}
+
return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope'));
}
@@ -156,6 +174,7 @@ class Pdo implements AuthorizationCodeInterface,
if ($user = $this->getUser($username)) {
return $this->checkPassword($user, $password);
}
+
return false;
}
@@ -227,15 +246,103 @@ class Pdo implements AuthorizationCodeInterface,
} else {
$stmt = $this->db->prepare(sprintf('INSERT INTO %s (username, password, first_name, last_name) VALUES (:username, :password, :firstName, :lastName)', $this->config['user_table']));
}
+
return $stmt->execute(compact('username', 'password', 'firstName', 'lastName'));
}
- /* OAuth2_Storage_JWTBearerInterface */
+ /* ScopeInterface */
+ 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'));
+
+ if ($result = $stmt->fetch()) {
+ $supportedScope = explode(' ', $result['scope']);
+ } else {
+ $supportedScope = array();
+ }
+
+ return (count(array_diff($scope, $supportedScope)) == 0);
+ }
+
+ 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'));
+
+ if ($result = $stmt->fetch()) {
+ return $result['scope'];
+ }
+
+ return null;
+ }
+
+ /* JWTBearerInterface */
public function getClientKey($client_id, $subject)
{
$stmt = $this->db->prepare($sql = sprintf('SELECT public_key from %s where client_id=:client_id AND subject=:subject', $this->config['jwt_table']));
$stmt->execute(array('client_id' => $client_id, 'subject' => $subject));
+
return $stmt->fetch();
}
+
+ public function getJti($client_id, $subject, $audience, $expires, $jti)
+ {
+ $stmt = $this->db->prepare($sql = sprintf('SELECT* FROM %s WHERE issuer=:client_id AND subject=:subject AND audience=:audience AND expires=:expires AND jti=:jti', $this->config['jti_table']));
+
+ $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti'));
+
+ if ($result = $stmt->fetch()) {
+ return array('issuer' => $result['issuer'],
+ 'subject' => $result['subject'],
+ 'audience' => $result['audience'],
+ 'expires' => $result['expires'],
+ 'jti' => $result['jti']
+ );
+ }
+
+ return null;
+ }
+
+ public function setJti($client_id, $subject, $audience, $expires, $jti)
+ {
+ $stmt = $this->db->prepare(sprintf('INSERT INTO %s (issuer, subject, audience, expires, jti) VALUES (:client_id, :subject, :audience, :expires, :jti)', $this->config['jti_table']));
+
+ return $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti'));
+ }
+
+ /* PublicKeyInterface */
+ public function getPublicKey($client_id = null)
+ {
+ $stmt = $this->db->prepare($sql = sprintf('SELECT public_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table']));
+
+ $stmt->execute(compact('client_id'));
+ if ($result = $stmt->fetch()) {
+ return $result['public_key'];
+ }
+ }
+
+ public function getPrivateKey($client_id = null)
+ {
+ $stmt = $this->db->prepare($sql = sprintf('SELECT private_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table']));
+
+ $stmt->execute(compact('client_id'));
+ if ($result = $stmt->fetch()) {
+ return $result['private_key'];
+ }
+ }
+
+ public function getEncryptionAlgorithm($client_id = null)
+ {
+ $stmt = $this->db->prepare($sql = sprintf('SELECT encryption_algorithm FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table']));
+
+ $stmt->execute(compact('client_id'));
+ if ($result = $stmt->fetch()) {
+ return $result['encryption_algorithm'];
+ }
+
+ return 'RS256';
+ }
}
diff --git a/src/OAuth2/Storage/PublicKeyInterface.php b/src/OAuth2/Storage/PublicKeyInterface.php
new file mode 100644
index 0000000..108418d
--- /dev/null
+++ b/src/OAuth2/Storage/PublicKeyInterface.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace OAuth2\Storage;
+
+/**
+ * Implement this interface to specify where the OAuth2 Server
+ * should get public/private key information
+ *
+ * @author Brent Shaffer <bshafs at gmail dot com>
+ */
+interface PublicKeyInterface
+{
+ public function getPublicKey($client_id = null);
+ public function getPrivateKey($client_id = null);
+ public function getEncryptionAlgorithm($client_id = null);
+}
diff --git a/src/OAuth2/Storage/Redis.php b/src/OAuth2/Storage/Redis.php
index 440c3a2..55d300c 100644
--- a/src/OAuth2/Storage/Redis.php
+++ b/src/OAuth2/Storage/Redis.php
@@ -8,7 +8,7 @@ namespace OAuth2\Storage;
* Register client:
* <code>
* $storage = new OAuth2_Storage_Redis($redis);
- * $storage->registerClient($client_id, $client_secret, $redirect_uri);
+ * $storage->setClientDetails($client_id, $client_secret, $redirect_uri);
* </code>
*/
class Redis implements AuthorizationCodeInterface,
@@ -16,17 +16,23 @@ class Redis implements AuthorizationCodeInterface,
ClientCredentialsInterface,
UserCredentialsInterface,
RefreshTokenInterface,
- JwtBearerInterface
+ JwtBearerInterface,
+ ScopeInterface
{
- private $redis;
- private $config;
+
private $cache;
+ /* The redis client */
+ protected $redis;
+
+ /* Configuration array */
+ protected $config;
+
/**
* Redis Storage!
*
* @param \Predis\Client $redis
- * @param array $config
+ * @param array $config
*/
public function __construct($redis, $config=array())
{
@@ -38,6 +44,7 @@ class Redis implements AuthorizationCodeInterface,
'code_key' => 'oauth_authorization_codes:',
'user_key' => 'oauth_users:',
'jwt_key' => 'oauth_jwt:',
+ 'scope_key' => 'oauth_scopes:',
), $config);
}
@@ -58,8 +65,9 @@ class Redis implements AuthorizationCodeInterface,
{
$this->cache[$key] = $value;
$str = json_encode($value);
- if ( $expire > 0 ) {
+ if ($expire > 0) {
$seconds = $expire - time();
+
return $this->redis->setex($key, $seconds, $str);
} else {
return $this->redis->set($key, $str);
@@ -69,6 +77,7 @@ class Redis implements AuthorizationCodeInterface,
protected function expireValue($key)
{
unset($this->cache[$key]);
+
return $this->redis->expire($key);
}
@@ -91,6 +100,7 @@ class Redis implements AuthorizationCodeInterface,
{
$key = $this->config['code_key'] . $code;
unset($this->cache[$key]);
+
return $this->expireValue($key);
}
@@ -98,6 +108,7 @@ class Redis implements AuthorizationCodeInterface,
public function checkUserCredentials($username, $password)
{
$user = $this->getUserDetails($username);
+
return $user && $user['password'] === $password;
}
@@ -129,7 +140,10 @@ class Redis implements AuthorizationCodeInterface,
/* ClientCredentialsInterface */
public function checkClientCredentials($client_id, $client_secret = null)
{
- $client = $this->getClientDetails($client_id);
+ if (!$client = $this->getClientDetails($client_id)) {
+ return false;
+ }
+
return isset($client['client_secret'])
&& $client['client_secret'] == $client_secret;
}
@@ -139,6 +153,14 @@ class Redis implements AuthorizationCodeInterface,
return $this->getValue($this->config['client_key'] . $client_id);
}
+ public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $user_id = null)
+ {
+ return $this->setValue(
+ $this->config['client_key'] . $client_id,
+ compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'user_id')
+ );
+ }
+
public function checkRestrictedGrantType($client_id, $grant_type)
{
$details = $this->getClientDetails($client_id);
@@ -152,14 +174,6 @@ class Redis implements AuthorizationCodeInterface,
return true;
}
- public function registerClient($client_id, $client_secret, $redirect_uri)
- {
- return $this->setValue(
- $this->config['client_key'] . $client_id,
- compact('client_id', 'client_secret', 'redirect_uri')
- );
- }
-
/* RefreshTokenInterface */
public function getRefreshToken($refresh_token)
{
@@ -195,6 +209,27 @@ class Redis implements AuthorizationCodeInterface,
);
}
+ /* ScopeInterface */
+ public function scopeExists($scope, $client_id = null)
+ {
+ $scope = explode(' ', $scope);
+ if (is_null($client_id) || !$result = $this->getValue($this->config['scope_key'].'supported:'.$client_id)) {
+ $result = $this->getValue($this->config['scope_key'].'supported:global');
+ }
+ $supportedScope = explode(' ', (string) $result);
+
+ return (count(array_diff($scope, $supportedScope)) == 0);
+ }
+
+ public function getDefaultScope($client_id = null)
+ {
+ if (is_null($client_id) || !$result = $this->getValue($this->config['scope_key'].'default:'.$client_id)) {
+ $result = $this->getValue($this->config['scope_key'].'default:global');
+ }
+
+ return $result;
+ }
+
/*JWTBearerInterface */
public function getClientKey($client_id, $subject)
{
@@ -202,6 +237,19 @@ class Redis implements AuthorizationCodeInterface,
if ( isset($jwt['subject']) && $jwt['subject'] == $subject ) {
return $jwt['key'];
}
+
return null;
}
+
+ public function getJti($client_id, $subject, $audience, $expiration, $jti)
+ {
+ //TODO: Needs redis implementation.
+ 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.');
+ }
}
diff --git a/src/OAuth2/Storage/RefreshTokenInterface.php b/src/OAuth2/Storage/RefreshTokenInterface.php
index 4b04e36..1f0c6c9 100644
--- a/src/OAuth2/Storage/RefreshTokenInterface.php
+++ b/src/OAuth2/Storage/RefreshTokenInterface.php
@@ -26,6 +26,7 @@ interface RefreshTokenInterface
* invalid:
* - refresh_token: Stored refresh token identifier.
* - client_id: Stored client identifier.
+ * - user_id: Stored user identifier.
* - expires: Stored expiration unix timestamp.
* - scope: (optional) Stored scope values in space-separated string.
*
@@ -50,6 +51,8 @@ interface RefreshTokenInterface
* Refresh token to be stored.
* @param $client_id
* Client identifier to be stored.
+ * @param $user_id
+ * User identifier to be stored.
* @param $expires
* expires to be stored.
* @param $scope
diff --git a/src/OAuth2/TokenType/Bearer.php b/src/OAuth2/TokenType/Bearer.php
index 919302f..4355275 100644
--- a/src/OAuth2/TokenType/Bearer.php
+++ b/src/OAuth2/TokenType/Bearer.php
@@ -22,7 +22,7 @@ class Bearer implements TokenTypeInterface
public function getTokenType()
{
- return 'bearer';
+ return 'Bearer';
}
/**
@@ -55,10 +55,12 @@ class Bearer implements TokenTypeInterface
$methodsUsed = !empty($headers) + !is_null($request->query($this->config['token_param_name'])) + !is_null($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 ($methodsUsed == 0) {
$response->setStatusCode(401);
+
return null;
}
@@ -66,8 +68,10 @@ class Bearer implements TokenTypeInterface
if (!empty($headers)) {
if (!preg_match('/' . $this->config['token_bearer_header_name'] . '\s(\S+)/', $headers, $matches)) {
$response->setError(400, 'invalid_request', 'Malformed auth header');
+
return null;
}
+
return $matches[1];
}
@@ -75,6 +79,7 @@ class Bearer implements TokenTypeInterface
// 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');
+
return null;
}
@@ -87,6 +92,7 @@ class Bearer implements TokenTypeInterface
// IETF specifies content-type. NB: Not all webservers populate this _SERVER variable
// @see http://tools.ietf.org/html/rfc6750#section-2.2
$response->setError(400, 'invalid_request', 'The content type for POST requests must be "application/x-www-form-urlencoded"');
+
return null;
}
diff --git a/test/OAuth2/Encryption/JwtTest.php b/test/OAuth2/Encryption/JwtTest.php
new file mode 100644
index 0000000..b10c61b
--- /dev/null
+++ b/test/OAuth2/Encryption/JwtTest.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace OAuth2\Encryption;
+
+use OAuth2\Storage\Bootstrap;
+
+class JwtTest extends \PHPUnit_Framework_TestCase
+{
+ private $privateKey;
+
+ public function setUp()
+ {
+ $this->privateKey = <<<EOD
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC5/SxVlE8gnpFqCxgl2wjhzY7ucEi00s0kUg3xp7lVEvgLgYcA
+nHiWp+gtSjOFfH2zsvpiWm6Lz5f743j/FEzHIO1owR0p4d9pOaJK07d01+RzoQLO
+IQAgXrr4T1CCWUesncwwPBVCyy2Mw3Nmhmr9MrF8UlvdRKBxriRnlP3qJQIDAQAB
+AoGAVgJJVU4fhYMu1e5JfYAcTGfF+Gf+h3iQm4JCpoUcxMXf5VpB9ztk3K7LRN5y
+kwFuFALpnUAarRcUPs0D8FoP4qBluKksbAtgHkO7bMSH9emN+mH4le4qpFlR7+P1
+3fLE2Y19IBwPwEfClC+TpJvuog6xqUYGPlg6XLq/MxQUB4ECQQDgovP1v+ONSeGS
+R+NgJTR47noTkQT3M2izlce/OG7a+O0yw6BOZjNXqH2wx3DshqMcPUFrTjibIClP
+l/tEQ3ShAkEA0/TdBYDtXpNNjqg0R9GVH2pw7Kh68ne6mZTuj0kCgFYpUF6L6iMm
+zXamIJ51rTDsTyKTAZ1JuAhAsK/M2BbDBQJAKQ5fXEkIA+i+64dsDUR/hKLBeRYG
+PFAPENONQGvGBwt7/s02XV3cgGbxIgAxqWkqIp0neb9AJUoJgtyaNe3GQQJANoL4
+QQ0af0NVJAZgg8QEHTNL3aGrFSbzx8IE5Lb7PLRsJa5bP5lQxnDoYuU+EI/Phr62
+niisp/b/ZDGidkTMXQJBALeRsH1I+LmICAvWXpLKa9Gv0zGCwkuIJLiUbV9c6CVh
+suocCAteQwL5iW2gA4AnYr5OGeHFsEl7NCQcwfPZpJ0=
+-----END RSA PRIVATE KEY-----
+EOD;
+ }
+
+ /** @dataProvider provideClientCredentials */
+ public function testJwtUtil($client_id, $client_key)
+ {
+ $jwtUtil = new Jwt();
+
+ $params = array(
+ 'iss' => $client_id,
+ 'exp' => time() + 1000,
+ 'iat' => time(),
+ 'sub' => 'testuser@ourdomain.com',
+ 'aud' => 'http://myapp.com/oauth/auth',
+ 'scope' => null,
+ );
+
+ $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256');
+
+ $payload = $jwtUtil->decode($encoded, $client_key);
+
+ $this->assertEquals($params, $payload);
+ }
+
+ public function provideClientCredentials()
+ {
+ $storage = Bootstrap::getInstance()->getMemoryStorage();
+ $client_id = 'Test Client ID';
+ $client_key = $storage->getClientKey($client_id, "testuser@ourdomain.com");
+
+ return array(
+ array($client_id, $client_key),
+ );
+ }
+}
diff --git a/test/OAuth2/GrantType/ClientCredentialsTest.php b/test/OAuth2/GrantType/ClientCredentialsTest.php
index 8077e61..f0d46cc 100644
--- a/test/OAuth2/GrantType/ClientCredentialsTest.php
+++ b/test/OAuth2/GrantType/ClientCredentialsTest.php
@@ -127,6 +127,27 @@ class ClientCredentialsTest extends \PHPUnit_Framework_TestCase
$this->assertNotNull($token['access_token']);
}
+ public function testClientUserIdIsSetInAccessToken()
+ {
+ $server = $this->getTestServer();
+ $request = TestRequest::createPost(array(
+ 'grant_type' => 'client_credentials', // valid grant type
+ 'client_id' => 'Client ID With User ID', // valid client id
+ 'client_secret' => 'TestSecret', // valid client secret
+ ));
+ $token = $server->grantAccessToken($request, new Response());
+
+ $this->assertNotNull($token);
+ $this->assertArrayHasKey('access_token', $token);
+
+ // verify the user_id was associated with the token
+ $storage = $server->getStorage('client');
+ $token = $storage->getAccessToken($token['access_token']);
+ $this->assertNotNull($token);
+ $this->assertArrayHasKey('user_id', $token);
+ $this->assertEquals($token['user_id'], 'brent@brentertainment.com');
+ }
+
private function getTestServer()
{
$storage = Bootstrap::getInstance()->getMemoryStorage();
diff --git a/test/OAuth2/GrantType/JWTBearerTest.php b/test/OAuth2/GrantType/JWTBearerTest.php
index 50fa77e..4b36ef2 100644
--- a/test/OAuth2/GrantType/JWTBearerTest.php
+++ b/test/OAuth2/GrantType/JWTBearerTest.php
@@ -261,24 +261,51 @@ EOD;
$this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested');
}
- public function testJwtUtil()
+ public function testValidJti()
{
- $storage = Bootstrap::getInstance()->getMemoryStorage();
- $jwtUtil = new Jwt();
- $client_id = 'Test Client ID';
- $params = $this->getJWTParams(null, null, null, $client_id);
+ $server = $this->getTestServer();
+ $request = TestRequest::createPost(array(
+ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type
+ 'assertion' => $this->getJWT(null, null, 'testuser@ourdomain.com', 'Test Client ID', null, 'unused_jti'), // valid assertion with invalid scope
+ ));
+ $token = $server->grantAccessToken($request, $response = new Response());
+ $this->assertNotNull($token);
+ $this->assertArrayHasKey('access_token', $token);
+ }
- if (version_compare(PHP_VERSION, '5.3.3') <= 0) {
- $encoded = $jwtUtil->encode($params, 'mysecretkey', 'HS256');
- $client_id .= ' PHP-5.2';
- } else {
- $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256');
- }
+ public function testInvalidJti()
+ {
+ $server = $this->getTestServer();
+ $request = TestRequest::createPost(array(
+ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type
+ 'assertion' => $this->getJWT(99999999900, null, 'testuser@ourdomain.com', 'Test Client ID', null, 'used_jti'), // valid assertion with invalid scope
+ ));
+ $token = $server->grantAccessToken($request, $response = new Response());
- $payload = $jwtUtil->decode($encoded, $storage->getClientKey($client_id, "testuser@ourdomain.com"));
+ $this->assertEquals($response->getStatusCode(), 400);
+ $this->assertEquals($response->getParameter('error'), 'invalid_grant');
+ $this->assertEquals($response->getParameter('error_description'), 'JSON Token Identifier (jti) has already been used');
+ }
- $this->assertEquals($params, $payload);
+ public function testJtiReplayAttack()
+ {
+ $server = $this->getTestServer();
+ $request = TestRequest::createPost(array(
+ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type
+ 'assertion' => $this->getJWT(99999999900, null, 'testuser@ourdomain.com', 'Test Client ID', null, 'totally_new_jti'), // valid assertion with invalid scope
+ ));
+ $token = $server->grantAccessToken($request, $response = new Response());
+
+ $this->assertNotNull($token);
+ $this->assertArrayHasKey('access_token', $token);
+
+ //Replay the same request
+ $token = $server->grantAccessToken($request, $response = new Response());
+
+ $this->assertEquals($response->getStatusCode(), 400);
+ $this->assertEquals($response->getParameter('error'), 'invalid_grant');
+ $this->assertEquals($response->getParameter('error_description'), 'JSON Token Identifier (jti) has already been used');
}
/**
@@ -289,15 +316,8 @@ EOD;
* @param $iss The issuer, usually the client_id.
* @return string
*/
- private function getJWTParams($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID', $scope = null)
+ private function getJWT($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID', $scope = null, $jti = null)
{
- //Since PHP 5.2 does not have OpenSSL support on Travis CI, we will test it using the HS256 algorithm
- //We also provided PHP 5.2 specific data for it in storage.json
- if (version_compare(PHP_VERSION, '5.3.3') <= 0) {
- // add "5.2" identifier onto the client name
- $iss .= ' PHP-5.2';
- }
-
if (!$exp) {
$exp = time() + 1000;
}
@@ -319,19 +339,12 @@ EOD;
$params['nbf'] = $nbf;
}
- return $params;
- }
-
- private function getJWT($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID', $scope = null)
- {
- $params = $this->getJWTParams($exp, $nbf, $sub, $iss, $scope);
+ if ($jti) {
+ $params['jti'] = $jti;
+ }
$jwtUtil = new Jwt();
- if (version_compare(PHP_VERSION, '5.3.3') <= 0) {
- return $jwtUtil->encode($params, 'mysecretkey', 'HS256');
- }
-
return $jwtUtil->encode($params, $this->privateKey, 'RS256');
}
diff --git a/test/OAuth2/ResponseType/CryptoTokenTest.php b/test/OAuth2/ResponseType/CryptoTokenTest.php
new file mode 100644
index 0000000..32f81a1
--- /dev/null
+++ b/test/OAuth2/ResponseType/CryptoTokenTest.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace OAuth2\ResponseType;
+
+use OAuth2\Server;
+use OAuth2\Response;
+use OAuth2\Request\TestRequest;
+use OAuth2\Storage\Bootstrap;
+use OAuth2\Storage\CryptoToken as CryptoTokenStorage;
+use OAuth2\GrantType\ClientCredentials;
+
+class CryptoTokenTest extends \PHPUnit_Framework_TestCase
+{
+ public function testGrantCryptoToken()
+ {
+ // add the test parameters in memory
+ $server = $this->getTestServer();
+ $request = TestRequest::createPost(array(
+ 'grant_type' => 'client_credentials', // valid grant type
+ 'client_id' => 'Test Client ID', // valid client id
+ 'client_secret' => 'TestSecret', // valid client secret
+ ));
+ $server->handleTokenRequest($request, $response = new Response());
+
+ $this->assertNotNull($response->getParameter('access_token'));
+ $this->assertEquals(2, substr_count($response->getParameter('access_token'), '.'));
+ }
+
+ public function testAccessResourceWithCryptoToken()
+ {
+ // add the test parameters in memory
+ $server = $this->getTestServer();
+ $request = TestRequest::createPost(array(
+ 'grant_type' => 'client_credentials', // valid grant type
+ 'client_id' => 'Test Client ID', // valid client id
+ 'client_secret' => 'TestSecret', // valid client secret
+ ));
+ $server->handleTokenRequest($request, $response = new Response());
+ $this->assertNotNull($cryptoToken = $response->getParameter('access_token'));
+
+ // make a call to the resource server using the crypto token
+ $request = TestRequest::createPost(array(
+ 'access_token' => $cryptoToken,
+ ));
+
+ $this->assertTrue($server->verifyResourceRequest($request));
+ }
+
+ public function testAccessResourceWithCryptoTokenUsingSecondaryStorage()
+ {
+ // add the test parameters in memory
+ $server = $this->getTestServer();
+ $request = TestRequest::createPost(array(
+ 'grant_type' => 'client_credentials', // valid grant type
+ 'client_id' => 'Test Client ID', // valid client id
+ 'client_secret' => 'TestSecret', // valid client secret
+ ));
+ $server->handleTokenRequest($request, $response = new Response());
+ $this->assertNotNull($cryptoToken = $response->getParameter('access_token'));
+
+ // make a call to the resource server using the crypto token
+ $request = TestRequest::createPost(array(
+ 'access_token' => $cryptoToken,
+ ));
+
+ // create a resource server with the "memory" storage from the grant server
+ $resourceServer = new Server($server->getStorage('client_credentials'));
+
+ $this->assertTrue($resourceServer->verifyResourceRequest($request));
+ }
+
+ private function getTestServer()
+ {
+ $memoryStorage = Bootstrap::getInstance()->getMemoryStorage();
+
+ $storage = array(
+ 'access_token' => new CryptoTokenStorage($memoryStorage),
+ 'client_credentials' => $memoryStorage,
+ );
+ $server = new Server($storage);
+ $server->addGrantType(new ClientCredentials($memoryStorage));
+
+ // make the "token" response type a CryptoToken
+ $server->addResponseType(new CryptoToken($memoryStorage, $memoryStorage));
+
+ return $server;
+ }
+}
diff --git a/test/OAuth2/ServerTest.php b/test/OAuth2/ServerTest.php
index 5960235..b61f851 100644
--- a/test/OAuth2/ServerTest.php
+++ b/test/OAuth2/ServerTest.php
@@ -265,6 +265,35 @@ class ServerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($config['allow_credentials_in_request_body'], false);
}
+ public function testRefreshTokenConfig()
+ {
+ // create mock storage
+ $storage = Bootstrap::getInstance()->getMemoryStorage();
+ $server1 = new Server(array($storage));
+ $server2 = new Server(array($storage), array('always_issue_new_refresh_token' => true));
+
+ $server1->getTokenController();
+ $refreshToken1 = $server1->getGrantType('refresh_token');
+
+ $server2->getTokenController();
+ $refreshToken2 = $server2->getGrantType('refresh_token');
+
+ $reflection1 = new \ReflectionClass($refreshToken1);
+ $prop1 = $reflection1->getProperty('config');
+ $prop1->setAccessible(true);
+
+ $reflection2 = new \ReflectionClass($refreshToken2);
+ $prop2 = $reflection2->getProperty('config');
+ $prop2->setAccessible(true);
+
+ // get the private "config" property
+ $config1 = $prop1->getValue($refreshToken1);
+ $config2 = $prop2->getValue($refreshToken2);
+
+ $this->assertEquals($config1['always_issue_new_refresh_token'], false);
+ $this->assertEquals($config2['always_issue_new_refresh_token'], true);
+ }
+
/**
* @expectedException InvalidArgumentException OAuth2\ResponseType\AuthorizationCodeInterface
**/
diff --git a/test/OAuth2/Storage/AccessTokenTest.php b/test/OAuth2/Storage/AccessTokenTest.php
new file mode 100644
index 0000000..17c9cd9
--- /dev/null
+++ b/test/OAuth2/Storage/AccessTokenTest.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace OAuth2\Storage;
+
+class AccessTokenTest extends BaseTest
+{
+ /** @dataProvider provideStorage */
+ public function testSetAccessToken(AccessTokenInterface $storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+ // assert token we are about to add does not exist
+ $token = $storage->getAccessToken('newtoken');
+ $this->assertFalse($token);
+
+ // add new token
+ $expires = time() + 20;
+ $success = $storage->setAccessToken('newtoken', 'client ID', 'SOMEUSERID', $expires);
+ $this->assertTrue($success);
+
+ $token = $storage->getAccessToken('newtoken');
+ $this->assertNotNull($token);
+ $this->assertArrayHasKey('access_token', $token);
+ $this->assertArrayHasKey('client_id', $token);
+ $this->assertArrayHasKey('user_id', $token);
+ $this->assertArrayHasKey('expires', $token);
+ $this->assertEquals($token['access_token'], 'newtoken');
+ $this->assertEquals($token['client_id'], 'client ID');
+ $this->assertEquals($token['user_id'], 'SOMEUSERID');
+ $this->assertEquals($token['expires'], $expires);
+
+ // change existing token
+ $expires = time() + 42;
+ $success = $storage->setAccessToken('newtoken', 'client ID2', 'SOMEOTHERID', $expires);
+ $this->assertTrue($success);
+
+ $token = $storage->getAccessToken('newtoken');
+ $this->assertNotNull($token);
+ $this->assertArrayHasKey('access_token', $token);
+ $this->assertArrayHasKey('client_id', $token);
+ $this->assertArrayHasKey('user_id', $token);
+ $this->assertArrayHasKey('expires', $token);
+ $this->assertEquals($token['access_token'], 'newtoken');
+ $this->assertEquals($token['client_id'], 'client ID2');
+ $this->assertEquals($token['user_id'], 'SOMEOTHERID');
+ $this->assertEquals($token['expires'], $expires);
+ }
+}
diff --git a/test/OAuth2/Storage/AuthorizationCodeTest.php b/test/OAuth2/Storage/AuthorizationCodeTest.php
new file mode 100644
index 0000000..9f57714
--- /dev/null
+++ b/test/OAuth2/Storage/AuthorizationCodeTest.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace OAuth2\Storage;
+
+class AuthorizationCodeTest extends BaseTest
+{
+ /** @dataProvider provideStorage */
+ public function testGetAuthorizationCode(AuthorizationCodeInterface $storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+ // nonexistant client_id
+ $details = $storage->getAuthorizationCode('faketoken');
+ $this->assertFalse($details);
+
+ // valid client_id
+ $details = $storage->getAuthorizationCode('testtoken');
+ $this->assertNotNull($details);
+ }
+
+ /** @dataProvider provideStorage */
+ public function testSetAuthorizationCode(AuthorizationCodeInterface $storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+ // assert code we are about to add does not exist
+ $code = $storage->getAuthorizationCode('newcode');
+ $this->assertFalse($code);
+
+ // add new code
+ $expires = time() + 20;
+ $success = $storage->setAuthorizationCode('newcode', 'client ID', 'SOMEUSERID', 'http://example.com', $expires);
+ $this->assertTrue($success);
+
+ $code = $storage->getAuthorizationCode('newcode');
+ $this->assertNotNull($code);
+ $this->assertArrayHasKey('authorization_code', $code);
+ $this->assertArrayHasKey('client_id', $code);
+ $this->assertArrayHasKey('user_id', $code);
+ $this->assertArrayHasKey('redirect_uri', $code);
+ $this->assertArrayHasKey('expires', $code);
+ $this->assertEquals($code['authorization_code'], 'newcode');
+ $this->assertEquals($code['client_id'], 'client ID');
+ $this->assertEquals($code['user_id'], 'SOMEUSERID');
+ $this->assertEquals($code['redirect_uri'], 'http://example.com');
+ $this->assertEquals($code['expires'], $expires);
+
+ // change existing code
+ $expires = time() + 42;
+ $success = $storage->setAuthorizationCode('newcode', 'client ID2', 'SOMEOTHERID', 'http://example.org', $expires);
+ $this->assertTrue($success);
+
+ $code = $storage->getAuthorizationCode('newcode');
+ $this->assertNotNull($code);
+ $this->assertArrayHasKey('authorization_code', $code);
+ $this->assertArrayHasKey('client_id', $code);
+ $this->assertArrayHasKey('user_id', $code);
+ $this->assertArrayHasKey('redirect_uri', $code);
+ $this->assertArrayHasKey('expires', $code);
+ $this->assertEquals($code['authorization_code'], 'newcode');
+ $this->assertEquals($code['client_id'], 'client ID2');
+ $this->assertEquals($code['user_id'], 'SOMEOTHERID');
+ $this->assertEquals($code['redirect_uri'], 'http://example.org');
+ $this->assertEquals($code['expires'], $expires);
+ }
+}
diff --git a/test/OAuth2/Storage/ClientCredentialsTest.php b/test/OAuth2/Storage/ClientCredentialsTest.php
new file mode 100644
index 0000000..ecaf6f6
--- /dev/null
+++ b/test/OAuth2/Storage/ClientCredentialsTest.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace OAuth2\Storage;
+
+class ClientCredentialsTest extends BaseTest
+{
+ /** @dataProvider provideStorage */
+ public function testCheckClientCredentials(ClientCredentialsInterface $storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+ // nonexistant client_id
+ $pass = $storage->checkClientCredentials('fakeclient', 'testpass');
+ $this->assertFalse($pass);
+
+ // invalid password
+ $pass = $storage->checkClientCredentials('oauth_test_client', 'invalidcredentials');
+ $this->assertFalse($pass);
+
+ // valid credentials
+ $pass = $storage->checkClientCredentials('oauth_test_client', 'testpass');
+ $this->assertTrue($pass);
+ }
+}
diff --git a/test/OAuth2/Storage/ClientTest.php b/test/OAuth2/Storage/ClientTest.php
new file mode 100644
index 0000000..4d95c6c
--- /dev/null
+++ b/test/OAuth2/Storage/ClientTest.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace OAuth2\Storage;
+
+class ClientTest extends BaseTest
+{
+ /** @dataProvider provideStorage */
+ public function testGetClientDetails(ClientInterface $storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+ // nonexistant client_id
+ $details = $storage->getClientDetails('fakeclient');
+ $this->assertFalse($details);
+
+ // valid client_id
+ $details = $storage->getClientDetails('oauth_test_client');
+ $this->assertNotNull($details);
+ $this->assertArrayHasKey('client_id', $details);
+ $this->assertArrayHasKey('client_secret', $details);
+ $this->assertArrayHasKey('redirect_uri', $details);
+ }
+
+ /** @dataProvider provideStorage */
+ public function testCheckRestrictedGrantType(ClientInterface $storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+
+ // Check invalid
+ $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'authorization_code');
+ $this->assertFalse($pass);
+
+ // Check valid
+ $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'implicit');
+ $this->assertTrue($pass);
+ }
+
+ /** @dataProvider provideStorage */
+ public function testGetAccessToken(ClientInterface $storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+ // nonexistant client_id
+ $details = $storage->getAccessToken('faketoken');
+ $this->assertFalse($details);
+
+ // valid client_id
+ $details = $storage->getAccessToken('testtoken');
+ $this->assertNotNull($details);
+ }
+
+ /** @dataProvider provideStorage */
+ public function testSaveClient(ClientInterface $storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+
+ $clientId = 'some-client-'.rand();
+
+ // create a new client
+ $success = $storage->setClientDetails($clientId, 'somesecret', 'http://test.com', 'client_credentials', 'brent@brentertainment.com');
+ $this->assertTrue($success);
+
+ // valid client_id
+ $details = $storage->getClientDetails($clientId);
+ $this->assertEquals($details['client_secret'], 'somesecret');
+ $this->assertEquals($details['redirect_uri'], 'http://test.com');
+ $this->assertEquals($details['grant_types'], 'client_credentials');
+ $this->assertEquals($details['user_id'], 'brent@brentertainment.com');
+ }
+}
diff --git a/test/OAuth2/Storage/CryptoTokenTest.php b/test/OAuth2/Storage/CryptoTokenTest.php
new file mode 100644
index 0000000..4a63b7e
--- /dev/null
+++ b/test/OAuth2/Storage/CryptoTokenTest.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace OAuth2\Storage;
+
+use OAuth2\Encryption\Jwt;
+
+class CryptoTokenTest extends BaseTest
+{
+ /** @dataProvider provideStorage */
+ public function testSetAccessToken($storage)
+ {
+ $publicKeyStorage = Bootstrap::getInstance()->getMemoryStorage();
+ $encryptionUtil = new Jwt();
+
+ $cryptoToken = array(
+ 'access_token' => rand(),
+ 'expires' => time() + 100,
+ 'scope' => 'foo',
+ );
+
+ $token = $encryptionUtil->encode($cryptoToken, $publicKeyStorage->getPrivateKey(), $publicKeyStorage->getEncryptionAlgorithm());
+
+ $this->assertNotNull($token);
+
+ $tokenData = $storage->getAccessToken($token);
+
+ $this->assertTrue(is_array($tokenData));
+
+ /* assert the decoded token is the same */
+ $this->assertEquals($tokenData['access_token'], $cryptoToken['access_token']);
+ $this->assertEquals($tokenData['expires'], $cryptoToken['expires']);
+ $this->assertEquals($tokenData['scope'], $cryptoToken['scope']);
+ }
+
+ // @TODO - use the BaseTest provideStorage, and add support for storages which omit certain interfaces
+ public function provideStorage()
+ {
+ $memory = Bootstrap::getInstance()->getMemoryStorage();
+ $storage = new CryptoToken($memory);
+
+ return array(
+ array($storage)
+ );
+ }
+}
diff --git a/test/OAuth2/Storage/PublicKeyTest.php b/test/OAuth2/Storage/PublicKeyTest.php
new file mode 100644
index 0000000..d08c9c0
--- /dev/null
+++ b/test/OAuth2/Storage/PublicKeyTest.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace OAuth2\Storage;
+
+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');
+ }
+
+ $configDir = Bootstrap::getInstance()->getConfigDir();
+ $globalPublicKey = file_get_contents($configDir.'/keys/id_rsa.pub');
+ $globalPrivateKey = file_get_contents($configDir.'/keys/id_rsa');
+
+ /* assert values from storage */
+ $this->assertEquals($storage->getPublicKey(), $globalPublicKey);
+ $this->assertEquals($storage->getPrivateKey(), $globalPrivateKey);
+ }
+}
diff --git a/test/OAuth2/Storage/RefreshTokenTest.php b/test/OAuth2/Storage/RefreshTokenTest.php
new file mode 100644
index 0000000..7777bb9
--- /dev/null
+++ b/test/OAuth2/Storage/RefreshTokenTest.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace OAuth2\Storage;
+
+class RefreshTokenTest extends BaseTest
+{
+ /** @dataProvider provideStorage */
+ public function testSetRefreshToken(RefreshTokenInterface $storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+ // assert token we are about to add does not exist
+ $token = $storage->getRefreshToken('refreshtoken');
+ $this->assertFalse($token);
+
+ // add new token
+ $expires = time() + 20;
+ $success = $storage->setRefreshToken('refreshtoken', 'client ID', 'SOMEUSERID', $expires);
+ $this->assertTrue($success);
+
+ $token = $storage->getRefreshToken('refreshtoken');
+ $this->assertNotNull($token);
+ $this->assertArrayHasKey('refresh_token', $token);
+ $this->assertArrayHasKey('client_id', $token);
+ $this->assertArrayHasKey('user_id', $token);
+ $this->assertArrayHasKey('expires', $token);
+ $this->assertEquals($token['refresh_token'], 'refreshtoken');
+ $this->assertEquals($token['client_id'], 'client ID');
+ $this->assertEquals($token['user_id'], 'SOMEUSERID');
+ $this->assertEquals($token['expires'], $expires);
+ }
+}
diff --git a/test/OAuth2/Storage/ScopeTest.php b/test/OAuth2/Storage/ScopeTest.php
new file mode 100644
index 0000000..a34ce54
--- /dev/null
+++ b/test/OAuth2/Storage/ScopeTest.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace OAuth2\Storage;
+
+use OAuth2\Scope;
+
+class ScopeTest extends BaseTest
+{
+ /** @dataProvider provideStorage */
+ public function testScopeExists($storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+
+ if (!$storage instanceof ScopeInterface) {
+ $this->markTestSkipped('Skipping incompatible storage');
+
+ return;
+ }
+
+ //Test getting scopes with a client_id
+ $scopeUtil = new Scope($storage);
+ $this->assertTrue($scopeUtil->scopeExists('clientscope1 clientscope2', 'Test Client ID'));
+ $this->assertFalse($scopeUtil->scopeExists('clientscope1 clientscope2 clientscope3', 'Test Client ID'));
+ $this->assertTrue($scopeUtil->scopeExists('clientscope3', 'Test Client ID 2'));
+ }
+
+ /** @dataProvider provideStorage */
+ public function testGetDefaultScope($storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+
+ if (!$storage instanceof ScopeInterface) {
+ $this->markTestSkipped('Skipping incompatible storage');
+
+ return;
+ }
+
+ // test getting default scope
+ $scopeUtil = new Scope($storage);
+ $this->assertEquals($scopeUtil->getDefaultScope(), 'defaultscope1 defaultscope2');
+ }
+
+ /** @dataProvider provideStorage */
+ public function testClientScopeExists($storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+
+ if (!$storage instanceof ScopeInterface) {
+ $this->markTestSkipped('Skipping incompatible storage');
+
+ return;
+ }
+
+ // test getting scopes with a client_id
+ $scopeUtil = new Scope($storage);
+ $this->assertTrue($scopeUtil->scopeExists('supportedscope1'));
+ $this->assertTrue($scopeUtil->scopeExists('supportedscope2'));
+ $this->assertTrue($scopeUtil->scopeExists('supportedscope1 supportedscope2'));
+ $this->assertFalse($scopeUtil->scopeExists('bogusscope'));
+ }
+
+ /** @dataProvider provideStorage */
+ public function testGetDefaultClientScope($storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+
+ if (!$storage instanceof ScopeInterface) {
+ $this->markTestSkipped('Skipping incompatible storage');
+
+ return;
+ }
+
+ // test getting scopes with a client_id
+ $scopeUtil = new Scope($storage);
+ $this->assertEquals($scopeUtil->getDefaultScope('Test Default Scope Client ID'), 'clientscope1 clientscope2');
+ $this->assertEquals($scopeUtil->getDefaultScope('Test Default Scope Client ID 2'), 'clientscope3');
+ }
+}
diff --git a/test/OAuth2/Storage/UserCredentialsTest.php b/test/OAuth2/Storage/UserCredentialsTest.php
new file mode 100644
index 0000000..ee1c721
--- /dev/null
+++ b/test/OAuth2/Storage/UserCredentialsTest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace OAuth2\Storage;
+
+class UserCredentialsTest extends BaseTest
+{
+ /** @dataProvider provideStorage */
+ public function testCheckUserCredentials(UserCredentialsInterface $storage = null)
+ {
+ if (is_null($storage)) {
+ $this->markTestSkipped('Unable to load class Mongo_Client');
+
+ return;
+ }
+ // create a new user for testing
+ $success = $storage->setUser('testusername', 'testpass', 'Test', 'User');
+ $this->assertTrue($success);
+
+ // correct credentials
+ $this->assertTrue($storage->checkUserCredentials('testusername', 'testpass'));
+ // invalid password
+ $this->assertFalse($storage->checkUserCredentials('testusername', 'fakepass'));
+ // invalid username
+ $this->assertFalse($storage->checkUserCredentials('fakeusername', 'testpass'));
+
+ // invalid username
+ $this->assertFalse($storage->getUserDetails('fakeusername'));
+
+ // ensure all properties are set
+ $user = $storage->getUserDetails('testusername');
+ $this->assertTrue($user !== false);
+ $this->assertArrayHasKey('user_id', $user);
+ $this->assertArrayHasKey('first_name', $user);
+ $this->assertArrayHasKey('last_name', $user);
+ $this->assertEquals($user['user_id'], 'testusername');
+ $this->assertEquals($user['first_name'], 'Test');
+ $this->assertEquals($user['last_name'], 'User');
+ }
+}
diff --git a/test/OAuth2/StorageTest.php b/test/OAuth2/StorageTest.php
deleted file mode 100644
index 9a36cb8..0000000
--- a/test/OAuth2/StorageTest.php
+++ /dev/null
@@ -1,271 +0,0 @@
-<?php
-
-namespace OAuth2;
-
-use OAuth2\Storage\ClientCredentialsInterface;
-use OAuth2\Storage\ClientInterface;
-use OAuth2\Storage\AccessTokenInterface;
-use OAuth2\Storage\RefreshTokenInterface;
-use OAuth2\Storage\AuthorizationCodeInterface;
-use OAuth2\Storage\Bootstrap;
-
-class StorageTest extends \PHPUnit_Framework_TestCase
-{
- /** @dataProvider provideStorage */
- public function testCheckClientCredentials(ClientCredentialsInterface $storage = null)
- {
- if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
- return;
- }
- // nonexistant client_id
- $pass = $storage->checkClientCredentials('fakeclient', 'testpass');
- $this->assertFalse($pass);
-
- // invalid password
- $pass = $storage->checkClientCredentials('oauth_test_client', 'invalidcredentials');
- $this->assertFalse($pass);
-
- // valid credentials
- $pass = $storage->checkClientCredentials('oauth_test_client', 'testpass');
- $this->assertTrue($pass);
- }
-
- /** @dataProvider provideStorage */
- public function testGetClientDetails(ClientInterface $storage = null)
- {
- if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
- return;
- }
- // nonexistant client_id
- $details = $storage->getClientDetails('fakeclient');
- $this->assertFalse($details);
-
- // valid client_id
- $details = $storage->getClientDetails('oauth_test_client');
- $this->assertNotNull($details);
- $this->assertArrayHasKey('client_id', $details);
- $this->assertArrayHasKey('client_secret', $details);
- $this->assertArrayHasKey('redirect_uri', $details);
- }
-
- /** @dataProvider provideStorage */
- public function testCheckRestrictedGrantType(ClientInterface $storage = null)
- {
- if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
- return;
- }
-
- // Check invalid
- $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'authorization_code');
- $this->assertFalse($pass);
-
- // Check valid
- $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'implicit');
- $this->assertTrue($pass);
- }
-
- /** @dataProvider provideStorage */
- public function testGetAccessToken(ClientInterface $storage = null)
- {
- if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
- return;
- }
- // nonexistant client_id
- $details = $storage->getAccessToken('faketoken');
- $this->assertFalse($details);
-
- // valid client_id
- $details = $storage->getAccessToken('testtoken');
- $this->assertNotNull($details);
- }
-
- /** @dataProvider provideStorage */
- public function testSetAccessToken(AccessTokenInterface $storage = null)
- {
- if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
- return;
- }
- // assert token we are about to add does not exist
- $token = $storage->getAccessToken('newtoken');
- $this->assertFalse($token);
-
- // add new token
- $expires = time() + 20;
- $success = $storage->setAccessToken('newtoken', 'client ID', 'SOMEUSERID', $expires);
- $this->assertTrue($success);
-
- $token = $storage->getAccessToken('newtoken');
- $this->assertNotNull($token);
- $this->assertArrayHasKey('access_token', $token);
- $this->assertArrayHasKey('client_id', $token);
- $this->assertArrayHasKey('user_id', $token);
- $this->assertArrayHasKey('expires', $token);
- $this->assertEquals($token['access_token'], 'newtoken');
- $this->assertEquals($token['client_id'], 'client ID');
- $this->assertEquals($token['user_id'], 'SOMEUSERID');
- $this->assertEquals($token['expires'], $expires);
-
- // change existing token
- $expires = time() + 42;
- $success = $storage->setAccessToken('newtoken', 'client ID2', 'SOMEOTHERID', $expires);
- $this->assertTrue($success);
-
- $token = $storage->getAccessToken('newtoken');
- $this->assertNotNull($token);
- $this->assertArrayHasKey('access_token', $token);
- $this->assertArrayHasKey('client_id', $token);
- $this->assertArrayHasKey('user_id', $token);
- $this->assertArrayHasKey('expires', $token);
- $this->assertEquals($token['access_token'], 'newtoken');
- $this->assertEquals($token['client_id'], 'client ID2');
- $this->assertEquals($token['user_id'], 'SOMEOTHERID');
- $this->assertEquals($token['expires'], $expires);
- }
-
- /** @dataProvider provideStorage */
- public function testSetRefreshToken(RefreshTokenInterface $storage = null)
- {
- if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
- return;
- }
- // assert token we are about to add does not exist
- $token = $storage->getRefreshToken('refreshtoken');
- $this->assertFalse($token);
-
- // add new token
- $expires = time() + 20;
- $success = $storage->setRefreshToken('refreshtoken', 'client ID', 'SOMEUSERID', $expires);
- $this->assertTrue($success);
-
- $token = $storage->getRefreshToken('refreshtoken');
- $this->assertNotNull($token);
- $this->assertArrayHasKey('refresh_token', $token);
- $this->assertArrayHasKey('client_id', $token);
- $this->assertArrayHasKey('user_id', $token);
- $this->assertArrayHasKey('expires', $token);
- $this->assertEquals($token['refresh_token'], 'refreshtoken');
- $this->assertEquals($token['client_id'], 'client ID');
- $this->assertEquals($token['user_id'], 'SOMEUSERID');
- $this->assertEquals($token['expires'], $expires);
- }
-
- /** @dataProvider provideStorage */
- public function testGetAuthorizationCode(AuthorizationCodeInterface $storage = null)
- {
- if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
- return;
- }
- // nonexistant client_id
- $details = $storage->getAuthorizationCode('faketoken');
- $this->assertFalse($details);
-
- // valid client_id
- $details = $storage->getAuthorizationCode('testtoken');
- $this->assertNotNull($details);
- }
-
- /** @dataProvider provideStorage */
- public function testSetAuthorizationCode(AuthorizationCodeInterface $storage = null)
- {
- if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
- return;
- }
- // assert code we are about to add does not exist
- $code = $storage->getAuthorizationCode('newcode');
- $this->assertFalse($code);
-
- // add new code
- $expires = time() + 20;
- $success = $storage->setAuthorizationCode('newcode', 'client ID', 'SOMEUSERID', 'http://example.com', $expires);
- $this->assertTrue($success);
-
- $code = $storage->getAuthorizationCode('newcode');
- $this->assertNotNull($code);
- $this->assertArrayHasKey('authorization_code', $code);
- $this->assertArrayHasKey('client_id', $code);
- $this->assertArrayHasKey('user_id', $code);
- $this->assertArrayHasKey('redirect_uri', $code);
- $this->assertArrayHasKey('expires', $code);
- $this->assertEquals($code['authorization_code'], 'newcode');
- $this->assertEquals($code['client_id'], 'client ID');
- $this->assertEquals($code['user_id'], 'SOMEUSERID');
- $this->assertEquals($code['redirect_uri'], 'http://example.com');
- $this->assertEquals($code['expires'], $expires);
-
- // change existing code
- $expires = time() + 42;
- $success = $storage->setAuthorizationCode('newcode', 'client ID2', 'SOMEOTHERID', 'http://example.org', $expires);
- $this->assertTrue($success);
-
- $code = $storage->getAuthorizationCode('newcode');
- $this->assertNotNull($code);
- $this->assertArrayHasKey('authorization_code', $code);
- $this->assertArrayHasKey('client_id', $code);
- $this->assertArrayHasKey('user_id', $code);
- $this->assertArrayHasKey('redirect_uri', $code);
- $this->assertArrayHasKey('expires', $code);
- $this->assertEquals($code['authorization_code'], 'newcode');
- $this->assertEquals($code['client_id'], 'client ID2');
- $this->assertEquals($code['user_id'], 'SOMEOTHERID');
- $this->assertEquals($code['redirect_uri'], 'http://example.org');
- $this->assertEquals($code['expires'], $expires);
- }
-
- /** @dataProvider provideStorage */
- public function testCheckUserCredentials($storage)
- {
- if (is_null($storage)) {
- $this->markTestSkipped('Unable to load class Mongo_Client');
- return;
- }
- // create a new user for testing
- $success = $storage->setUser('testusername', 'testpass', 'Test', 'User');
- $this->assertTrue($success);
-
- // correct credentials
- $this->assertTrue($storage->checkUserCredentials('testusername', 'testpass'));
- // invalid password
- $this->assertFalse($storage->checkUserCredentials('testusername', 'fakepass'));
- // invalid username
- $this->assertFalse($storage->checkUserCredentials('fakeusername', 'testpass'));
-
- // invalid username
- $this->assertFalse($storage->getUserDetails('fakeusername'));
-
- // ensure all properties are set
- $user = $storage->getUserDetails('testusername');
- $this->assertTrue($user !== false);
- $this->assertArrayHasKey('user_id', $user);
- $this->assertArrayHasKey('first_name', $user);
- $this->assertArrayHasKey('last_name', $user);
- $this->assertEquals($user['user_id'], 'testusername');
- $this->assertEquals($user['first_name'], 'Test');
- $this->assertEquals($user['last_name'], 'User');
- }
-
- public function provideStorage()
- {
- $memory = Bootstrap::getInstance()->getMemoryStorage();
- $mysql = Bootstrap::getInstance()->getMysqlPdo();
- $sqlite = Bootstrap::getInstance()->getSqlitePdo();
- $mongo = Bootstrap::getInstance()->getMongo();
- $redis = Bootstrap::getInstance()->getRedisStorage();
-
- // will add multiple storage types later
- return array(
- array($memory),
- array($sqlite),
- array($mysql),
- array($mongo),
- array($redis)
- );
- }
-}
diff --git a/test/config/keys/id_rsa b/test/config/keys/id_rsa
new file mode 100644
index 0000000..e8b9eff
--- /dev/null
+++ b/test/config/keys/id_rsa
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLsNjP+uAt2eO0cc5J9H5XV
+8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdwizIum8j0KzpsGYH5qReN
+QDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJuBe+FQpZTs8DewwIDAQAB
+AoGARfNxNknmtx/n1bskZ/01iZRzAge6BLEE0LV6Q4gS7mkRZu/Oyiv39Sl5vUlA
++WdGxLjkBwKNjxGN8Vxw9/ASd8rSsqeAUYIwAeifXrHhj5DBPQT/pDPkeFnp9B1w
+C6jo+3AbBQ4/b0ONSIEnCL2xGGglSIAxO17T1ViXp7lzXPECQQDe63nkRdWM0OCb
+oaHQPT3E26224maIstrGFUdt9yw3yJf4bOF7TtiPLlLuHsTTge3z+fG6ntC0xG56
+1cl37C3ZAkEA2HdVcRGugNp/qmVz4LJTpD+WZKi73PLAO47wDOrYh9Pn2I6fcEH0
+CPnggt1ko4ujvGzFTvRH64HXa6aPCv1j+wJBAMQMah3VQPNf/DlDVFEUmw9XeBZg
+VHaifX851aEjgXLp6qVj9IYCmLiLsAmVa9rr6P7p8asD418nZlaHUHE0eDkCQQCr
+uxis6GMx1Ka971jcJX2X696LoxXPd0KsvXySMupv79yagKPa8mgBiwPjrnK+EPVo
+cj6iochA/bSCshP/mwFrAkBHEKPi6V6gb94JinCT7x3weahbdp6bJ6/nzBH/p9VA
+HoT1JtwNFhGv9BCjmDydshQHfSWpY9NxlccBKL7ITm8R
+-----END RSA PRIVATE KEY----- \ No newline at end of file
diff --git a/test/config/keys/id_rsa.pub b/test/config/keys/id_rsa.pub
new file mode 100644
index 0000000..1ac15f5
--- /dev/null
+++ b/test/config/keys/id_rsa.pub
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICiDCCAfGgAwIBAgIBADANBgkqhkiG9w0BAQQFADA9MQswCQYDVQQGEwJVUzEL
+MAkGA1UECBMCVVQxITAfBgNVBAoTGFZpZ25ldHRlIENvcnBvcmF0aW9uIFNCWDAe
+Fw0xMTEwMTUwMzE4MjdaFw0zMTEwMTAwMzE4MjdaMD0xCzAJBgNVBAYTAlVTMQsw
+CQYDVQQIEwJVVDEhMB8GA1UEChMYVmlnbmV0dGUgQ29ycG9yYXRpb24gU0JYMIGf
+MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLs
+NjP+uAt2eO0cc5J9H5XV8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdw
+izIum8j0KzpsGYH5qReNQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJu
+Be+FQpZTs8DewwIDAQABo4GXMIGUMB0GA1UdDgQWBBRe8hrEXm+Yim4YlD5Nx+1K
+vCYs9DBlBgNVHSMEXjBcgBRe8hrEXm+Yim4YlD5Nx+1KvCYs9KFBpD8wPTELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgTAlVUMSEwHwYDVQQKExhWaWduZXR0ZSBDb3Jwb3Jh
+dGlvbiBTQliCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBjhyRD
+lM7vnLn6drgQVftW5V9nDFAyPAuiGvMIKFSbiAf1PxXCRn5sfJquwWKsJUi4ZGNl
+aViXdFmN6/F13PSM+yg63tpKy0fYqMbTM+Oe5WuSHkSW1VuYNHV+24adgNk/FRDL
+FRrlM1f6s9VTLWvwGItjfrof0Ba8Uq7ZDSb9Xg==
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/test/config/storage.json b/test/config/storage.json
index db6f840..f2db151 100644
--- a/test/config/storage.json
+++ b/test/config/storage.json
@@ -48,6 +48,10 @@
"Test Client ID Empty Secret": {
"client_secret": ""
},
+ "Client ID With User ID": {
+ "client_secret": "TestSecret",
+ "user_id": "brent@brentertainment.com"
+ },
"oauth_test_client": {
"client_secret": "testpass",
"grant_types": "implicit password"
@@ -97,23 +101,32 @@
}
},
"jwt": {
- "Test Client ID": {
- "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5/SxVlE8gnpFqCxgl2wjhzY7u\ncEi00s0kUg3xp7lVEvgLgYcAnHiWp+gtSjOFfH2zsvpiWm6Lz5f743j/FEzHIO1o\nwR0p4d9pOaJK07d01+RzoQLOIQAgXrr4T1CCWUesncwwPBVCyy2Mw3Nmhmr9MrF8\nUlvdRKBxriRnlP3qJQIDAQAB\n-----END PUBLIC KEY-----",
- "subject": "testuser@ourdomain.com"
- },
- "Test Client ID PHP-5.2": {
- "key": "mysecretkey",
- "subject": "testuser@ourdomain.com"
- },
- "Missing Key Client": {
- "key": null,
- "subject": "testuser@ourdomain.com"
- },
- "Missing Key Client PHP-5.2": {
- "key": null,
- "subject": "testuser@ourdomain.com"
- }
+ "Test Client ID": {
+ "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5/SxVlE8gnpFqCxgl2wjhzY7u\ncEi00s0kUg3xp7lVEvgLgYcAnHiWp+gtSjOFfH2zsvpiWm6Lz5f743j/FEzHIO1o\nwR0p4d9pOaJK07d01+RzoQLOIQAgXrr4T1CCWUesncwwPBVCyy2Mw3Nmhmr9MrF8\nUlvdRKBxriRnlP3qJQIDAQAB\n-----END PUBLIC KEY-----",
+ "subject": "testuser@ourdomain.com"
+ },
+ "Test Client ID PHP-5.2": {
+ "key": "mysecretkey",
+ "subject": "testuser@ourdomain.com"
+ },
+ "Missing Key Client": {
+ "key": null,
+ "subject": "testuser@ourdomain.com"
+ },
+ "Missing Key Client PHP-5.2": {
+ "key": null,
+ "subject": "testuser@ourdomain.com"
+ }
},
+ "jti": [
+ {
+ "issuer": "Test Client ID",
+ "subject": "testuser@ourdomain.com",
+ "audience": "http://myapp.com/oauth/auth",
+ "expires": 99999999900,
+ "jti": "used_jti"
+ }
+ ],
"client_supported_scopes" : {
"Test Client ID" : ["clientscope1", "clientscope2"],
"Test Client ID 2" : ["clientscope3"]
@@ -125,6 +138,14 @@
"supported_scopes" : [
"scope1",
"scope2",
- "scope3"
- ]
+ "scope3",
+ "supportedscope1",
+ "supportedscope2",
+ "supportedscope3",
+ "supportedscope4"
+ ],
+ "keys": {
+ "public_key": "-----BEGIN CERTIFICATE-----\nMIICiDCCAfGgAwIBAgIBADANBgkqhkiG9w0BAQQFADA9MQswCQYDVQQGEwJVUzEL\nMAkGA1UECBMCVVQxITAfBgNVBAoTGFZpZ25ldHRlIENvcnBvcmF0aW9uIFNCWDAe\nFw0xMTEwMTUwMzE4MjdaFw0zMTEwMTAwMzE4MjdaMD0xCzAJBgNVBAYTAlVTMQsw\nCQYDVQQIEwJVVDEhMB8GA1UEChMYVmlnbmV0dGUgQ29ycG9yYXRpb24gU0JYMIGf\nMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLs\nNjP+uAt2eO0cc5J9H5XV8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdw\nizIum8j0KzpsGYH5qReNQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJu\nBe+FQpZTs8DewwIDAQABo4GXMIGUMB0GA1UdDgQWBBRe8hrEXm+Yim4YlD5Nx+1K\nvCYs9DBlBgNVHSMEXjBcgBRe8hrEXm+Yim4YlD5Nx+1KvCYs9KFBpD8wPTELMAkG\nA1UEBhMCVVMxCzAJBgNVBAgTAlVUMSEwHwYDVQQKExhWaWduZXR0ZSBDb3Jwb3Jh\ndGlvbiBTQliCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBjhyRD\nlM7vnLn6drgQVftW5V9nDFAyPAuiGvMIKFSbiAf1PxXCRn5sfJquwWKsJUi4ZGNl\naViXdFmN6/F13PSM+yg63tpKy0fYqMbTM+Oe5WuSHkSW1VuYNHV+24adgNk/FRDL\nFRrlM1f6s9VTLWvwGItjfrof0Ba8Uq7ZDSb9Xg==\n-----END CERTIFICATE-----",
+ "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLsNjP+uAt2eO0cc5J9H5XV\n8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdwizIum8j0KzpsGYH5qReN\nQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJuBe+FQpZTs8DewwIDAQAB\nAoGARfNxNknmtx/n1bskZ/01iZRzAge6BLEE0LV6Q4gS7mkRZu/Oyiv39Sl5vUlA\n+WdGxLjkBwKNjxGN8Vxw9/ASd8rSsqeAUYIwAeifXrHhj5DBPQT/pDPkeFnp9B1w\nC6jo+3AbBQ4/b0ONSIEnCL2xGGglSIAxO17T1ViXp7lzXPECQQDe63nkRdWM0OCb\noaHQPT3E26224maIstrGFUdt9yw3yJf4bOF7TtiPLlLuHsTTge3z+fG6ntC0xG56\n1cl37C3ZAkEA2HdVcRGugNp/qmVz4LJTpD+WZKi73PLAO47wDOrYh9Pn2I6fcEH0\nCPnggt1ko4ujvGzFTvRH64HXa6aPCv1j+wJBAMQMah3VQPNf/DlDVFEUmw9XeBZg\nVHaifX851aEjgXLp6qVj9IYCmLiLsAmVa9rr6P7p8asD418nZlaHUHE0eDkCQQCr\nuxis6GMx1Ka971jcJX2X696LoxXPd0KsvXySMupv79yagKPa8mgBiwPjrnK+EPVo\ncj6iochA/bSCshP/mwFrAkBHEKPi6V6gb94JinCT7x3weahbdp6bJ6/nzBH/p9VA\nHoT1JtwNFhGv9BCjmDydshQHfSWpY9NxlccBKL7ITm8R\n-----END RSA PRIVATE KEY-----"
+ }
} \ No newline at end of file
diff --git a/test/lib/OAuth2/Request/TestRequest.php b/test/lib/OAuth2/Request/TestRequest.php
index aa471f2..5e0d081 100644
--- a/test/lib/OAuth2/Request/TestRequest.php
+++ b/test/lib/OAuth2/Request/TestRequest.php
@@ -58,6 +58,7 @@ class TestRequest implements RequestInterface
{
$request = new self();
$request->setPost($params);
+
return $request;
}
}
diff --git a/test/lib/OAuth2/Storage/BaseTest.php b/test/lib/OAuth2/Storage/BaseTest.php
new file mode 100644
index 0000000..2b09265
--- /dev/null
+++ b/test/lib/OAuth2/Storage/BaseTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace OAuth2\Storage;
+
+abstract class BaseTest extends \PHPUnit_Framework_TestCase
+{
+ public function provideStorage()
+ {
+ $mysql = Bootstrap::getInstance()->getMysqlPdo();
+ $sqlite = Bootstrap::getInstance()->getSqlitePdo();
+ $mongo = Bootstrap::getInstance()->getMongo();
+ $redis = Bootstrap::getInstance()->getRedisStorage();
+
+ /* hack until we can fix "default_scope" dependencies in other tests */
+ // $memory = Bootstrap::getInstance()->getMemoryStorage();
+ $memoryConfig = json_decode(file_get_contents(__DIR__.'/../../../config/storage.json'), true);
+ $memoryConfig['default_scope'] = 'defaultscope1 defaultscope2';
+ $memory = new Memory($memoryConfig);
+
+ // will add multiple storage types later
+ return array(
+ array($memory),
+ array($sqlite),
+ array($mysql),
+ array($mongo),
+ array($redis)
+ );
+ }
+}
diff --git a/test/lib/OAuth2/Storage/Bootstrap.php b/test/lib/OAuth2/Storage/Bootstrap.php
index 9b0839f..673a01a 100644
--- a/test/lib/OAuth2/Storage/Bootstrap.php
+++ b/test/lib/OAuth2/Storage/Bootstrap.php
@@ -8,12 +8,19 @@ class Bootstrap
private $mysql;
private $sqlite;
private $mongo;
+ private $configDir;
+
+ public function __construct()
+ {
+ $this->configDir = __DIR__.'/../../../config';
+ }
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new self();
}
+
return self::$instance;
}
@@ -27,12 +34,13 @@ class Bootstrap
$this->sqlite = new Pdo($pdo);
}
+
return $this->sqlite;
}
public function getMemoryStorage()
{
- return new Memory(json_decode(file_get_contents(dirname(__FILE__).'/../../../config/storage.json'), true));
+ return new Memory(json_decode(file_get_contents($this->configDir. '/storage.json'), true));
}
public function getRedisStorage()
@@ -50,6 +58,7 @@ class Bootstrap
$this->mysql = new Pdo($pdo);
}
+
return $this->mysql;
}
@@ -66,22 +75,13 @@ class Bootstrap
$this->mongo = new Mongo($db);
}
}
+
return $this->mongo;
}
private function createSqliteDb(\PDO $pdo)
{
- $pdo->exec('CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT, grant_types TEXT)');
- $pdo->exec('CREATE TABLE oauth_access_tokens (access_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT)');
- $pdo->exec('CREATE TABLE oauth_authorization_codes (authorization_code TEXT, client_id TEXT, user_id TEXT, redirect_uri TEXT, expires TIMESTAMP, scope TEXT)');
- $pdo->exec('CREATE TABLE oauth_users (username TEXT, password TEXT, first_name TEXT, last_name TEXT)');
- $pdo->exec('CREATE TABLE oauth_refresh_tokens (refresh_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT)');
-
- // test data
- $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret, grant_types) VALUES ("oauth_test_client", "testpass", "implicit password")');
- $pdo->exec('INSERT INTO oauth_access_tokens (access_token, client_id) VALUES ("testtoken", "Some Client")');
- $pdo->exec('INSERT INTO oauth_authorization_codes (authorization_code, client_id) VALUES ("testcode", "Some Client")');
- $pdo->exec('INSERT INTO oauth_users (username, password) VALUES ("testuser", "password")');
+ $this->runPdoSql($pdo);
}
private function removeSqliteDb()
@@ -95,17 +95,45 @@ class Bootstrap
{
$pdo->exec('CREATE DATABASE oauth2_server_php');
$pdo->exec('USE oauth2_server_php');
- $pdo->exec('CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT, grant_types TEXT)');
+ $this->runPdoSql($pdo);
+ }
+
+ public function runPdoSql(\PDO $pdo)
+ {
+ $pdo->exec('CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT, grant_types TEXT, user_id TEXT)');
$pdo->exec('CREATE TABLE oauth_access_tokens (access_token TEXT, client_id TEXT, user_id TEXT, expires DATETIME, scope TEXT)');
$pdo->exec('CREATE TABLE oauth_authorization_codes (authorization_code TEXT, client_id TEXT, user_id TEXT, redirect_uri TEXT, expires DATETIME, scope TEXT)');
$pdo->exec('CREATE TABLE oauth_users (username TEXT, password TEXT, first_name TEXT, last_name TEXT)');
$pdo->exec('CREATE TABLE oauth_refresh_tokens (refresh_token TEXT, client_id TEXT, user_id TEXT, expires DATETIME, scope TEXT)');
-
- // test data
+ $pdo->exec('CREATE TABLE oauth_scopes (type TEXT, scope TEXT, client_id TEXT)');
+ $pdo->exec('CREATE TABLE oauth_public_keys (client_id TEXT, public_key TEXT, private_key TEXT, encryption_algorithm VARCHAR(100) DEFAULT "RS256")');
+
+ // set up scopes
+ $pdo->exec('INSERT INTO oauth_scopes (type, scope) VALUES ("supported", "supportedscope1 supportedscope2 supportedscope3 supportedscope4")');
+ $pdo->exec('INSERT INTO oauth_scopes (type, scope) VALUES ("default", "defaultscope1 defaultscope2")');
+ $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("supported", "clientscope1 clientscope2", "Test Client ID")');
+ $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("default", "clientscope1 clientscope2", "Test Client ID")');
+ $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("supported", "clientscope1 clientscope2 clientscope3", "Test Client ID 2")');
+ $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("default", "clientscope1 clientscope2", "Test Client ID 2")');
+ $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("supported", "clientscope1 clientscope2", "Test Default Scope Client ID")');
+ $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("default", "clientscope1 clientscope2", "Test Default Scope Client ID")');
+ $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("supported", "clientscope1 clientscope2 clientscope3", "Test Default Scope Client ID 2")');
+ $pdo->exec('INSERT INTO oauth_scopes (type, scope, client_id) VALUES ("default", "clientscope3", "Test Default Scope Client ID 2")');
+
+ // set up clients
+ $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret) VALUES ("Test Client ID", "TestSecret")');
+ $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret) VALUES ("Test Client ID 2", "TestSecret")');
+ $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret) VALUES ("Test Default Scope Client ID", "TestSecret")');
+ $pdo->exec('INSERT INTO oauth_clients (client_id, client_secret) VALUES ("Test Default Scope Client ID 2", "TestSecret")');
$pdo->exec('INSERT INTO oauth_clients (client_id, client_secret, grant_types) VALUES ("oauth_test_client", "testpass", "implicit password")');
+
+ // set up misc
$pdo->exec('INSERT INTO oauth_access_tokens (access_token, client_id) VALUES ("testtoken", "Some Client")');
$pdo->exec('INSERT INTO oauth_authorization_codes (authorization_code, client_id) VALUES ("testcode", "Some Client")');
$pdo->exec('INSERT INTO oauth_users (username, password) VALUES ("testuser", "password")');
+ $pdo->exec('INSERT INTO oauth_public_keys (client_id, public_key, private_key, encryption_algorithm) VALUES ("ClientID_One", "client_1_public", "client_1_private", "RS256")');
+ $pdo->exec('INSERT INTO oauth_public_keys (client_id, public_key, private_key, encryption_algorithm) VALUES ("ClientID_Two", "client_2_public", "client_2_private", "RS256")');
+ $pdo->exec(sprintf('INSERT INTO oauth_public_keys (client_id, public_key, private_key, encryption_algorithm) VALUES (NULL, "%s", "%s", "RS256")', file_get_contents($this->configDir.'/keys/id_rsa.pub'), file_get_contents($this->configDir.'/keys/id_rsa')));
}
public function removeMysqlDb(\PDO $pdo)
@@ -113,9 +141,14 @@ class Bootstrap
$pdo->exec('DROP DATABASE IF EXISTS oauth2_server_php');
}
- private function getSqliteDir()
+ public function getSqliteDir()
+ {
+ return $this->configDir. '/test.sqlite';
+ }
+
+ public function getConfigDir()
{
- return dirname(__FILE__).'/../../../config/test.sqlite';
+ return $this->configDir;
}
private function createMongoDb(\MongoDB $db)
diff --git a/test/lib/OAuth2/Storage/MockRedisClient.php b/test/lib/OAuth2/Storage/MockRedisClient.php
index 75dde33..dbfe264 100644
--- a/test/lib/OAuth2/Storage/MockRedisClient.php
+++ b/test/lib/OAuth2/Storage/MockRedisClient.php
@@ -13,6 +13,22 @@ class MockRedisClient
'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",
@@ -33,35 +49,48 @@ class MockRedisClient
'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 ) {
+ foreach ($data as $name => $val) {
$data[$name] = json_encode($val);
}
$this->data = $data;
}
- function get($key)
+ public function get($key)
{
return isset($this->data[$key]) ? $this->data[$key] : null;
}
- function set ($key, $value)
+ public function set ($key, $value)
{
$this->data[$key] = $value;
+
return true;
}
- function setex($key, $expires, $value)
+ public function setex($key, $expires, $value)
{
$this->data[$key] = $value;
+
return true;
}
- function expire($key)
+ public function expire($key)
{
unset($this->data[$key]);
+
return true;
}
}