Added Origin checking for implicit clients (JS)

* Added origin checking at resource server
* Added to instrospection endpoint allowed origin/return uris info

Change-Id: I85086408a981c4f003503cf03f9e32542a8698ab
This commit is contained in:
Sebastian Marcet 2015-03-24 17:01:03 -03:00
parent cf9a093ed2
commit 8c15d9888d
6 changed files with 76 additions and 31 deletions

View File

@ -409,8 +409,16 @@ class TestSeeder extends Seeder {
//add uris
ClientAuthorizedUri::create(
array(
'uri'=>'https://www.test.com/oauth2',
'client_id'=>$client_confidential->id
'uri' => 'https://www.test.com/oauth2',
'client_id' => $client_confidential->id
)
);
//add uris
ClientAllowedOrigin::create(
array(
'allowed_origin' => 'https://www.test.com/oauth2',
'client_id' => $client_confidential->id
)
);

View File

@ -10,6 +10,8 @@ use oauth2\exceptions\OAuth2ResourceServerException;
use oauth2\exceptions\InvalidGrantTypeException;
use utils\services\ICheckPointService;
use oauth2\IResourceServerContext;
use oauth2\services\IClientService;
use oauth2\models\IClient;
/**
* Class OAuth2BearerAccessTokenRequestValidator
* this class implements the logic to Accessing to Protected Resources
@ -21,13 +23,13 @@ class OAuth2BearerAccessTokenRequestValidator {
protected function getHeaders()
{
$headers = array();
$headers = array();
if (function_exists('getallheaders')) {
// @codeCoverageIgnoreStart
foreach(getallheaders() as $name => $value){
$headers[strtolower($name)] = $value;
}
foreach(getallheaders() as $name => $value){
$headers[strtolower($name)] = $value;
}
} else {
// @codeCoverageIgnoreEnd
foreach ($_SERVER as $name => $value) {
@ -37,10 +39,10 @@ class OAuth2BearerAccessTokenRequestValidator {
}
}
foreach(Request::header() as $name => $value){
if(!array_key_exists($name,$headers))
$headers[strtolower($name)] = $value[0];
}
foreach(Request::header() as $name => $value){
if(!array_key_exists($name,$headers))
$headers[strtolower($name)] = $value[0];
}
}
return $headers;
}
@ -51,14 +53,16 @@ class OAuth2BearerAccessTokenRequestValidator {
private $checkpoint_service;
private $resource_server_context;
private $headers;
private $client_service;
public function __construct(IResourceServerContext $resource_server_context,IApiEndpointService $api_endpoint_service, ITokenService $token_service, ILogService $log_service, ICheckPointService $checkpoint_service){
public function __construct(IResourceServerContext $resource_server_context, IApiEndpointService $api_endpoint_service, ITokenService $token_service, ILogService $log_service, ICheckPointService $checkpoint_service, IClientService $client_service){
$this->api_endpoint_service = $api_endpoint_service;
$this->token_service = $token_service;
$this->log_service = $log_service;
$this->checkpoint_service = $checkpoint_service;
$this->resource_server_context = $resource_server_context;
$this->headers = $this->getHeaders();
$this->client_service = $client_service;
}
/**
@ -74,6 +78,8 @@ class OAuth2BearerAccessTokenRequestValidator {
}
$method = $request->getMethod();
$realm = $request->getHost();
// http://tools.ietf.org/id/draft-abarth-origin-03.html
$origin = $request->headers->has('Origin') ? $request->headers->get('Origin') : null;
try{
$endpoint = $this->api_endpoint_service->getApiEndpointByUrlAndMethod($url, $method);
@ -107,6 +113,18 @@ class OAuth2BearerAccessTokenRequestValidator {
if((!in_array($realm,$audience)))
throw new OAuth2ResourceServerException(401,OAuth2Protocol::OAuth2Protocol_Error_InvalidToken,'access token audience does not match');
//check client existence
$client_id = $access_token->getClientId();
$client = $this->client_service->getClientById($client_id);
if(is_null($client))
throw new OAuth2ResourceServerException(400,OAuth2Protocol::OAuth2Protocol_Error_InvalidRequest, 'invalid client');
//if js client , then check if the origin is allowed ....
if($client->getApplicationType() == IClient::ApplicationType_JS_Client){
if(!$client->isOriginAllowed($origin))
throw new OAuth2ResourceServerException(403,OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient, 'invalid origin');
}
//check scopes
$endpoint_scopes = explode(' ',$endpoint->getScope());
$token_scopes = explode(' ',$access_token->getScope());
@ -123,7 +141,7 @@ class OAuth2BearerAccessTokenRequestValidator {
$context = array(
'access_token' => $access_token_value,
'expires_in' => $access_token->getRemainingLifetime(),
'client_id' => $access_token->getClientId(),
'client_id' => $client_id,
'scope' => $access_token->getScope()
);

View File

@ -126,7 +126,17 @@ class ValidateBearerTokenGrantType extends AbstractGrantType
throw new BearerTokenDisclosureAttemptException($this->current_client_id,sprintf('access token current audience does not match with current request ip %s', $current_ip));
}
return new OAuth2AccessTokenValidationResponse( $token_value, $access_token->getScope(), $access_token->getAudience(), $access_token->getClientId(), $access_token->getRemainingLifetime(), $access_token->getUserId());
$allowed_origins = array();
foreach($this->current_client->getClientAllowedOrigins() as $origin){
array_push($allowed_origins, $origin->allowed_origin);
}
$allowed_urls = array();
foreach($this->current_client->getClientRegisteredUris() as $url){
array_push($allowed_urls, $url->uri);
}
return new OAuth2AccessTokenValidationResponse($token_value, $access_token->getScope(), $access_token->getAudience(), $access_token->getClientId(), $access_token->getRemainingLifetime(), $access_token->getUserId(), $allowed_urls, $allowed_origins);
}
catch(InvalidAccessTokenException $ex1){
$this->log_service->error($ex1);

View File

@ -6,7 +6,7 @@ use oauth2\OAuth2Protocol;
class OAuth2AccessTokenValidationResponse extends OAuth2DirectResponse {
public function __construct($access_token,$scope, $audience,$client_id,$expires_in, $user_id = null)
public function __construct($access_token,$scope, $audience,$client_id,$expires_in, $user_id = null, $allowed_urls = array(), $allowed_origins = array())
{
// Successful Responses: A server receiving a valid request MUST send a
// response with an HTTP status code of 200.
@ -21,5 +21,13 @@ class OAuth2AccessTokenValidationResponse extends OAuth2DirectResponse {
if(!is_null($user_id)){
$this[OAuth2Protocol::OAuth2Protocol_UserId] = $user_id;
}
if(count($allowed_urls)){
$this['allowed_return_uris'] = implode(' ', $allowed_urls);
}
if(count($allowed_origins)){
$this['allowed_origins'] = implode(' ', $allowed_origins);
}
}
}

View File

@ -7,19 +7,19 @@ class Client extends BaseModelEloquent implements IClient {
protected $table = 'oauth2_client';
public function getActiveAttribute(){
return (bool) $this->attributes['active'];
}
public function getActiveAttribute(){
return (bool) $this->attributes['active'];
}
public function getIdAttribute(){
return (int) $this->attributes['id'];
}
public function getIdAttribute(){
return (int) $this->attributes['id'];
}
public function getLockedAttribute(){
return (int) $this->attributes['locked'];
}
public function getLockedAttribute(){
return (int) $this->attributes['locked'];
}
public function access_tokens()
public function access_tokens()
{
return $this->hasMany('AccessToken');
}
@ -202,13 +202,13 @@ class Client extends BaseModelEloquent implements IClient {
switch($this->application_type){
case IClient::ApplicationType_JS_Client:
return 'Client Side (JS)';
break;
break;
case IClient::ApplicationType_Service:
return 'Service Account';
break;
break;
case IClient::ApplicationType_Web_App:
return 'Web Server Application';
break;
break;
}
throw new Exception('Invalid Application Type');
}
@ -232,13 +232,13 @@ class Client extends BaseModelEloquent implements IClient {
}
if($parts['scheme']!=='https')
return false;
$origin_without_port = sprinf("%sː//%s",$parts['scheme'],$parts['host']);
$origin_without_port = $parts['scheme'].'://'.$parts['host'];
$client_allowed_origin = $this->allowed_origins()->where('allowed_origin','=',$origin_without_port)->first();
if(!is_null($client_allowed_origin)) return true;
if(isset($parts['port'])){
$origin_with_port = sprinf("%sː//%s:%s",$parts['scheme'],$parts['host'],$parts['port']);
$client_authorized_uri = $this->allowed_origins()->where('allowed_origin','=',$origin_with_port)->first();;
return !is_null($client_authorized_uri);
$origin_with_port = $parts['scheme'].'://'.$parts['host'].':'.$parts['port'];
$client_authorized_uri = $this->allowed_origins()->where('allowed_origin','=',$origin_with_port)->first();;
return !is_null($client_authorized_uri);
}
return false;
}

View File

@ -84,6 +84,7 @@ class CORSMiddleware {
if (!$this->checkOrigin($request)) {
$response->headers->set('Access-Control-Allow-Origin', 'null');
$response->setStatusCode(403);
return $response;
}