implements oauth2.0 v9.0 Resource Owner Basic Credentials

This commit is contained in:
Nicolas Le Goff
2012-04-13 15:44:39 +02:00
parent e513a75d86
commit 527b3639b5
3 changed files with 493 additions and 442 deletions

View File

@@ -14,6 +14,8 @@ namespace Alchemy\Phrasea\Application;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpFoundation\Request;
/** /**
* *
@@ -102,10 +104,20 @@ return call_user_func(function()
$template = "api/auth/end_user_authorization.twig"; $template = "api/auth/end_user_authorization.twig";
$custom_template = $app['appbox']->get_registry()->get('GV_RootPath') . 'config/templates/web/api/auth/end_user_authorization/' . $client->get_id() . '.twig';
$custom_template = sprintf(
"%sconfig/templates/web/api/auth/end_user_authorization/%s.twig"
, $app['appbox']->get_registry()->get('GV_RootPath')
, $client->get_id()
);
if (file_exists($custom_template)) if (file_exists($custom_template))
{ {
$template = 'api/auth/end_user_authorization/' . $client->get_id() . '.twig'; $template = sprintf(
'api/auth/end_user_authorization/%s.twig'
, $client->get_id()
);
} }
if ( ! $authenticated) if ( ! $authenticated)
@@ -146,8 +158,11 @@ return call_user_func(function()
} }
} }
//check if current client is alreadu authorized by current user //check if current client is already authorized by current user
$user_auth_clients = \API_OAuth2_Application::load_authorized_app_by_user($app['appbox'], $app['Core']->getAuthenticatedUser()); $user_auth_clients = \API_OAuth2_Application::load_authorized_app_by_user(
$app['appbox']
, $app['Core']->getAuthenticatedUser()
);
foreach ($user_auth_clients as $auth_client) foreach ($user_auth_clients as $auth_client)
{ {
@@ -156,6 +171,7 @@ return call_user_func(function()
} }
$account = $oauth2_adapter->updateAccount($session->get_usr_id()); $account = $oauth2_adapter->updateAccount($session->get_usr_id());
$params['account_id'] = $account->get_id(); $params['account_id'] = $account->get_id();
if ( ! $app_authorized && $action_accept === null) if ( ! $app_authorized && $action_accept === null)
@@ -203,8 +219,13 @@ return call_user_func(function()
* Token endpoint - used to exchange an authorization grant for an access token. * Token endpoint - used to exchange an authorization grant for an access token.
*/ */
$route = '/token'; $route = '/token';
$app->post($route, function() use ($app) $app->post($route, function(\Silex\Application $app, Request $request)
{ {
if(!$request->isSecure())
{
throw new HttpException(400, 'require the use of the https', null, array('content-type'=> 'application/json'));
}
$app['oauth']->grantAccessToken(); $app['oauth']->grantAccessToken();
ob_flush(); ob_flush();
flush(); flush();
@@ -431,9 +452,26 @@ return call_user_func(function()
return new Response('The requested page could not be found.', 404); return new Response('The requested page could not be found.', 404);
} }
$code = $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500; $code = 500;
$msg = 'We are sorry, but something went wrong';
$headers = array();
return new Response('We are sorry, but something went wrong.<br />' . $e->getMessage(), $code); if($e instanceof HttpExceptionInterface)
{
$headers = $e->getHeaders();
$msg = $e->getMessage();
$code = $e->getStatusCode();
if(isset($headers['content-type']) && $headers['content-type'] == 'application/json')
{
$obj = new \stdClass();
$obj->msg = $msg;
$obj->code = $code;
$msg = json_encode($obj);
}
}
return new Response($msg, $code, $headers);
}); });

View File

@@ -195,7 +195,6 @@ class API_OAuth2_Adapter extends OAuth2
$application = API_OAuth2_Application::load_from_client_id($this->appbox, $client_id); $application = API_OAuth2_Application::load_from_client_id($this->appbox, $client_id);
if ($client_secret === NULL) if ($client_secret === NULL)
return true; return true;
$crypted = $this->crypt_secret($client_secret, $application->get_nonce()); $crypted = $this->crypt_secret($client_secret, $application->get_nonce());
@@ -285,7 +284,8 @@ class API_OAuth2_Adapter extends OAuth2
protected function getSupportedGrantTypes() protected function getSupportedGrantTypes()
{ {
return array( return array(
OAUTH2_GRANT_TYPE_AUTH_CODE OAUTH2_GRANT_TYPE_AUTH_CODE,
OAUTH2_GRANT_TYPE_USER_CREDENTIALS
); );
} }
@@ -410,12 +410,12 @@ class API_OAuth2_Adapter extends OAuth2
$scope = $request->get('scope', false); $scope = $request->get('scope', false);
$state = $request->get('state', false); $state = $request->get('state', false);
if($state) if ($state)
{ {
$datas["state"] = $state; $datas["state"] = $state;
} }
if($scope) if ($scope)
{ {
$datas["scope"] = $scope; $datas["scope"] = $scope;
} }
@@ -443,7 +443,7 @@ class API_OAuth2_Adapter extends OAuth2
* check for valid client_id * check for valid client_id
* check for valid redirect_uri * check for valid redirect_uri
*/ */
if (!$input["client_id"]) if ( ! $input["client_id"])
{ {
if ($input["redirect_uri"]) if ($input["redirect_uri"])
$this->errorDoRedirectUriCallback( $this->errorDoRedirectUriCallback(
@@ -462,7 +462,7 @@ class API_OAuth2_Adapter extends OAuth2
/** /**
* At least one of: existing redirect URI or input redirect URI must be specified * At least one of: existing redirect URI or input redirect URI must be specified
*/ */
if (!$redirect_uri && !$input["redirect_uri"]) if ( ! $redirect_uri && ! $input["redirect_uri"])
$this->errorJsonResponse( $this->errorJsonResponse(
OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST); OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST);
@@ -502,7 +502,7 @@ class API_OAuth2_Adapter extends OAuth2
/** /**
* Check response_type * Check response_type
*/ */
if (!$input["response_type"]) if ( ! $input["response_type"])
{ {
$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', NULL, $input["state"]); $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', NULL, $input["state"]);
} }
@@ -525,7 +525,7 @@ class API_OAuth2_Adapter extends OAuth2
/** /**
* Validate that the requested scope is supported * Validate that the requested scope is supported
*/ */
if ($input["scope"] && !$this->checkScope($input["scope"], $this->getSupportedScopes())) if ($input["scope"] && ! $this->checkScope($input["scope"], $this->getSupportedScopes()))
$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, NULL, NULL, $input["state"]); $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, NULL, NULL, $input["state"]);
/** /**
@@ -640,13 +640,11 @@ class API_OAuth2_Adapter extends OAuth2
if ($token_param === FALSE) // Access token was not provided if ($token_param === FALSE) // Access token was not provided
return $exit_not_present ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_BAD_REQUEST, $realm, OAUTH2_ERROR_INVALID_REQUEST, 'The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.', NULL, $scope) : FALSE; return $exit_not_present ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_BAD_REQUEST, $realm, OAUTH2_ERROR_INVALID_REQUEST, 'The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.', NULL, $scope) : FALSE;
// Get the stored token data (from the implementing subclass) // Get the stored token data (from the implementing subclass)
$token = $this->getAccessToken($token_param); $token = $this->getAccessToken($token_param);
if ($token === NULL) if ($token === NULL)
return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', NULL, $scope) : FALSE; return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', NULL, $scope) : FALSE;
if (isset($token['revoked']) && $token['revoked']) if (isset($token['revoked']) && $token['revoked'])
@@ -658,13 +656,11 @@ class API_OAuth2_Adapter extends OAuth2
{ {
// Check token expiration (I'm leaving this check separated, later we'll fill in better error messages) // Check token expiration (I'm leaving this check separated, later we'll fill in better error messages)
if (isset($token["expires"]) && time() > $token["expires"]) if (isset($token["expires"]) && time() > $token["expires"])
return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', NULL, $scope) : FALSE; return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', NULL, $scope) : FALSE;
} }
// Check scope, if provided // Check scope, if provided
// If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error // If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error
if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->checkScope($scope, $token["scope"]))) if ($scope && ( ! isset($token["scope"]) || ! $token["scope"] || ! $this->checkScope($scope, $token["scope"])))
return $exit_scope ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_FORBIDDEN, $realm, OAUTH2_ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', NULL, $scope) : FALSE; return $exit_scope ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_FORBIDDEN, $realm, OAUTH2_ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', NULL, $scope) : FALSE;
//save token's linked ses_id //save token's linked ses_id
@@ -718,11 +714,11 @@ class API_OAuth2_Adapter extends OAuth2
$input = filter_input_array(INPUT_POST, $filters); $input = filter_input_array(INPUT_POST, $filters);
// Grant Type must be specified. // Grant Type must be specified.
if (!$input["grant_type"]) if ( ! $input["grant_type"])
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing');
// Make sure we've implemented the requested grant type // Make sure we've implemented the requested grant type
if (!in_array($input["grant_type"], $this->getSupportedGrantTypes())) if ( ! in_array($input["grant_type"], $this->getSupportedGrantTypes()))
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE);
// Authorize the client // Authorize the client
@@ -731,39 +727,36 @@ class API_OAuth2_Adapter extends OAuth2
if ($this->checkClientCredentials($client[0], $client[1]) === FALSE) if ($this->checkClientCredentials($client[0], $client[1]) === FALSE)
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT);
if (!$this->checkRestrictedGrantType($client[0], $input["grant_type"])) if ( ! $this->checkRestrictedGrantType($client[0], $input["grant_type"]))
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT);
// Do the granting // Do the granting
switch ($input["grant_type"]) switch ($input["grant_type"])
{ {
case OAUTH2_GRANT_TYPE_AUTH_CODE: case OAUTH2_GRANT_TYPE_AUTH_CODE:
if (!$input["code"] || !$input["redirect_uri"]) if ( ! $input["code"] || ! $input["redirect_uri"])
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);
$stored = $this->getAuthCode($input["code"]); $stored = $this->getAuthCode($input["code"]);
// Ensure that the input uri starts with the stored uri // Ensure that the input uri starts with the stored uri
if ($stored === NULL || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"]) if ($stored === NULL || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"])
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);
if ($stored["expires"] < time()) if ($stored["expires"] < time())
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN);
break; break;
case OAUTH2_GRANT_TYPE_USER_CREDENTIALS: case OAUTH2_GRANT_TYPE_USER_CREDENTIALS:
if (!$input["username"] || !$input["password"]) if ( ! $input["username"] || ! $input["password"])
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required'); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required');
$stored = $this->checkUserCredentials($client[0], $input["username"], $input["password"]); $stored = $this->checkUserCredentials($client[0], $input["username"], $input["password"]);
if ($stored === FALSE) if ($stored === FALSE)
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT, 'Unknow user');
break; break;
case OAUTH2_GRANT_TYPE_ASSERTION: case OAUTH2_GRANT_TYPE_ASSERTION:
if (!$input["assertion_type"] || !$input["assertion"]) if ( ! $input["assertion_type"] || ! $input["assertion"])
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);
$stored = $this->checkAssertion($client[0], $input["assertion_type"], $input["assertion"]); $stored = $this->checkAssertion($client[0], $input["assertion_type"], $input["assertion"]);
@@ -773,7 +766,7 @@ class API_OAuth2_Adapter extends OAuth2
break; break;
case OAUTH2_GRANT_TYPE_REFRESH_TOKEN: case OAUTH2_GRANT_TYPE_REFRESH_TOKEN:
if (!$input["refresh_token"]) if ( ! $input["refresh_token"])
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found'); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found');
$stored = $this->getRefreshToken($input["refresh_token"]); $stored = $this->getRefreshToken($input["refresh_token"]);
@@ -796,10 +789,10 @@ class API_OAuth2_Adapter extends OAuth2
} }
// Check scope, if provided // Check scope, if provided
if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"]))) if ($input["scope"] && ( ! is_array($stored) || ! isset($stored["scope"]) || ! $this->checkScope($input["scope"], $stored["scope"])))
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_SCOPE); $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_SCOPE);
if (!$input["scope"]) if ( ! $input["scope"])
$input["scope"] = NULL; $input["scope"] = NULL;
$token = $this->createAccessToken($stored['account_id'], $input["scope"]); $token = $this->createAccessToken($stored['account_id'], $input["scope"]);
@@ -835,4 +828,30 @@ class API_OAuth2_Adapter extends OAuth2
return $token; return $token;
} }
protected function checkUserCredentials($client_id, $username, $password)
{
try
{
$appbox = appbox::get_instance(\bootstrap::getCore());
$application = API_OAuth2_Application::load_from_client_id($appbox, $client_id);
$auth = new \Session_Authentication_Native($appbox, $username, $password);
$auth->challenge_password();
$account = API_OAuth2_Account::load_with_user($appbox, $application, $auth->get_user());
return array(
'redirect_uri' => $application->get_redirect_uri()
, 'client_id' => $application->get_client_id()
, 'account_id' => $account->get_id()
);
}
catch (\Exception $e)
{
return false;
}
}
} }

View File

@@ -15,14 +15,8 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3 * @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link www.phraseanet.com * @link www.phraseanet.com
*/ */
use Symfony\Component\HttpFoundation\Response; require_once __DIR__ . "/../../../lib/bootstrap.php";
try $app = require __DIR__ . '/../../../lib/Alchemy/Phrasea/Application/OAuth2.php';
{
$app = require __DIR__ . '/../../../lib/Alchemy/Phrasea/Application/OAuth2.php'; $app->run();
$app->run();
}
catch (Exception $e)
{
return new Response('Internal Server Error', 500);
}