diff --git a/app/database/seeds/TestSeeder.php b/app/database/seeds/TestSeeder.php index b7a6e941..ac1752e5 100644 --- a/app/database/seeds/TestSeeder.php +++ b/app/database/seeds/TestSeeder.php @@ -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 ) ); diff --git a/app/filters/OAuth2RequestAccessTokenValidator.php b/app/filters/OAuth2RequestAccessTokenValidator.php index 49b8109a..921717e7 100644 --- a/app/filters/OAuth2RequestAccessTokenValidator.php +++ b/app/filters/OAuth2RequestAccessTokenValidator.php @@ -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() ); diff --git a/app/libs/oauth2/grant_types/ValidateBearerTokenGrantType.php b/app/libs/oauth2/grant_types/ValidateBearerTokenGrantType.php index 1032c932..170a75fd 100644 --- a/app/libs/oauth2/grant_types/ValidateBearerTokenGrantType.php +++ b/app/libs/oauth2/grant_types/ValidateBearerTokenGrantType.php @@ -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); diff --git a/app/libs/oauth2/responses/OAuth2AccessTokenValidationResponse.php b/app/libs/oauth2/responses/OAuth2AccessTokenValidationResponse.php index a0c01322..c2475383 100644 --- a/app/libs/oauth2/responses/OAuth2AccessTokenValidationResponse.php +++ b/app/libs/oauth2/responses/OAuth2AccessTokenValidationResponse.php @@ -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); + } } } \ No newline at end of file diff --git a/app/models/oauth2/Client.php b/app/models/oauth2/Client.php index f3befe1b..11ca1147 100644 --- a/app/models/oauth2/Client.php +++ b/app/models/oauth2/Client.php @@ -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; } diff --git a/app/services/oauth2/CORS/CORSMiddleware.php b/app/services/oauth2/CORS/CORSMiddleware.php index 9d42070f..f67f4439 100644 --- a/app/services/oauth2/CORS/CORSMiddleware.php +++ b/app/services/oauth2/CORS/CORSMiddleware.php @@ -84,6 +84,7 @@ class CORSMiddleware { if (!$this->checkOrigin($request)) { $response->headers->set('Access-Control-Allow-Origin', 'null'); + $response->setStatusCode(403); return $response; }