diff --git a/src/Http/Middleware/CheckClientCredentials.php b/src/Http/Middleware/CheckClientCredentials.php index 802e5294c..9a648157a 100644 --- a/src/Http/Middleware/CheckClientCredentials.php +++ b/src/Http/Middleware/CheckClientCredentials.php @@ -5,6 +5,7 @@ use Closure; use Zend\Diactoros\StreamFactory; use Zend\Diactoros\ResponseFactory; +use Laravel\Passport\TokenRepository; use Zend\Diactoros\UploadedFileFactory; use League\OAuth2\Server\ResourceServer; use Zend\Diactoros\ServerRequestFactory; @@ -22,15 +23,24 @@ class CheckClientCredentials */ protected $server; + /** + * Token Repository. + * + * @var \Laravel\Passport\TokenRepository + */ + protected $repository; + /** * Create a new middleware instance. * * @param \League\OAuth2\Server\ResourceServer $server + * @param \Laravel\Passport\TokenRepository $repository * @return void */ - public function __construct(ResourceServer $server) + public function __construct(ResourceServer $server, TokenRepository $repository) { $this->server = $server; + $this->repository = $repository; } /** @@ -57,27 +67,33 @@ public function handle($request, Closure $next, ...$scopes) throw new AuthenticationException; } - $this->validateScopes($psr, $scopes); + $this->validate($psr, $scopes); return $next($request); } /** - * Validate the scopes on the incoming request. + * Validate the scopes and token on the incoming request. * * @param \Psr\Http\Message\ServerRequestInterface $psr * @param array $scopes * @return void - * @throws \Laravel\Passport\Exceptions\MissingScopeException + * @throws \Laravel\Passport\Exceptions\MissingScopeException|\Illuminate\Auth\AuthenticationException */ - protected function validateScopes($psr, $scopes) + protected function validate($psr, $scopes) { - if (in_array('*', $tokenScopes = $psr->getAttribute('oauth_scopes'))) { + $token = $this->repository->find($psr->getAttribute('oauth_access_token_id')); + + if (! $token || $token->client->firstParty()) { + throw new AuthenticationException; + } + + if (in_array('*', $token->scopes)) { return; } foreach ($scopes as $scope) { - if (! in_array($scope, $tokenScopes)) { + if ($token->cant($scope)) { throw new MissingScopeException($scope); } } diff --git a/src/Http/Middleware/CheckClientCredentialsForAnyScope.php b/src/Http/Middleware/CheckClientCredentialsForAnyScope.php index cbda4a6bf..df618fe71 100644 --- a/src/Http/Middleware/CheckClientCredentialsForAnyScope.php +++ b/src/Http/Middleware/CheckClientCredentialsForAnyScope.php @@ -5,6 +5,7 @@ use Closure; use Zend\Diactoros\StreamFactory; use Zend\Diactoros\ResponseFactory; +use Laravel\Passport\TokenRepository; use Zend\Diactoros\UploadedFileFactory; use League\OAuth2\Server\ResourceServer; use Zend\Diactoros\ServerRequestFactory; @@ -22,15 +23,24 @@ class CheckClientCredentialsForAnyScope */ private $server; + /** + * Token Repository. + * + * @var \Laravel\Passport\TokenRepository + */ + protected $repository; + /** * Create a new middleware instance. * * @param \League\OAuth2\Server\ResourceServer $server + * @param \Laravel\Passport\TokenRepository $repository * @return void */ - public function __construct(ResourceServer $server) + public function __construct(ResourceServer $server, TokenRepository $repository) { $this->server = $server; + $this->repository = $repository; } /** @@ -57,7 +67,7 @@ public function handle($request, Closure $next, ...$scopes) throw new AuthenticationException; } - if ($this->validateScopes($psr, $scopes)) { + if ($this->validate($psr, $scopes)) { return $next($request); } @@ -65,20 +75,27 @@ public function handle($request, Closure $next, ...$scopes) } /** - * Validate the scopes on the incoming request. + * Validate the scopes and token on the incoming request. * * @param \Psr\Http\Message\ServerRequestInterface $psr * @param array $scopes * @return bool + * @throws \Illuminate\Auth\AuthenticationException */ - protected function validateScopes($psr, $scopes) + protected function validate($psr, $scopes) { - if (in_array('*', $tokenScopes = $psr->getAttribute('oauth_scopes'))) { + $token = $this->repository->find($psr->getAttribute('oauth_access_token_id')); + + if (! $token || $token->client->firstParty()) { + throw new AuthenticationException; + } + + if (in_array('*', $token->scopes)) { return true; } foreach ($scopes as $scope) { - if (in_array($scope, $tokenScopes)) { + if ($token->can($scope)) { return true; } } diff --git a/tests/CheckClientCredentialsForAnyScopeTest.php b/tests/CheckClientCredentialsForAnyScopeTest.php index 887c6d882..135dc0a86 100644 --- a/tests/CheckClientCredentialsForAnyScopeTest.php +++ b/tests/CheckClientCredentialsForAnyScopeTest.php @@ -3,8 +3,11 @@ namespace Laravel\Passport\Tests; use Mockery as m; +use Laravel\Passport\Token; use Illuminate\Http\Request; +use Laravel\Passport\Client; use PHPUnit\Framework\TestCase; +use Laravel\Passport\TokenRepository; use League\OAuth2\Server\ResourceServer; use League\OAuth2\Server\Exception\OAuthServerException; use Laravel\Passport\Http\Middleware\CheckClientCredentialsForAnyScope; @@ -25,7 +28,17 @@ public function test_request_is_passed_along_if_token_is_valid() $psr->shouldReceive('getAttribute')->with('oauth_access_token_id')->andReturn('token'); $psr->shouldReceive('getAttribute')->with('oauth_scopes')->andReturn(['*']); - $middleware = new CheckClientCredentialsForAnyScope($resourceServer); + $client = m::mock(Client::class); + $client->shouldReceive('firstParty')->andReturnFalse(); + + $token = m::mock(Token::class); + $token->shouldReceive('getAttribute')->with('client')->andReturn($client); + $token->shouldReceive('getAttribute')->with('scopes')->andReturn(['*']); + + $tokenRepository = m::mock(TokenRepository::class); + $tokenRepository->shouldReceive('find')->with('token')->andReturn($token); + + $middleware = new CheckClientCredentialsForAnyScope($resourceServer, $tokenRepository); $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); @@ -46,7 +59,19 @@ public function test_request_is_passed_along_if_token_has_any_required_scope() $psr->shouldReceive('getAttribute')->with('oauth_access_token_id')->andReturn('token'); $psr->shouldReceive('getAttribute')->with('oauth_scopes')->andReturn(['foo', 'bar', 'baz']); - $middleware = new CheckClientCredentialsForAnyScope($resourceServer); + $client = m::mock(Client::class); + $client->shouldReceive('firstParty')->andReturnFalse(); + + $token = m::mock(Token::class); + $token->shouldReceive('getAttribute')->with('client')->andReturn($client); + $token->shouldReceive('getAttribute')->with('scopes')->andReturn(['foo', 'bar', 'baz']); + $token->shouldReceive('can')->with('notfoo')->andReturnFalse(); + $token->shouldReceive('can')->with('bar')->andReturnTrue(); + + $tokenRepository = m::mock(TokenRepository::class); + $tokenRepository->shouldReceive('find')->with('token')->andReturn($token); + + $middleware = new CheckClientCredentialsForAnyScope($resourceServer, $tokenRepository); $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); @@ -63,12 +88,13 @@ public function test_request_is_passed_along_if_token_has_any_required_scope() */ public function test_exception_is_thrown_when_oauth_throws_exception() { + $tokenRepository = m::mock(TokenRepository::class); $resourceServer = m::mock(ResourceServer::class); $resourceServer->shouldReceive('validateAuthenticatedRequest')->andThrow( new OAuthServerException('message', 500, 'error type') ); - $middleware = new CheckClientCredentialsForAnyScope($resourceServer); + $middleware = new CheckClientCredentialsForAnyScope($resourceServer, $tokenRepository); $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); @@ -90,7 +116,19 @@ public function test_exception_is_thrown_if_token_does_not_have_required_scope() $psr->shouldReceive('getAttribute')->with('oauth_access_token_id')->andReturn('token'); $psr->shouldReceive('getAttribute')->with('oauth_scopes')->andReturn(['foo', 'bar']); - $middleware = new CheckClientCredentialsForAnyScope($resourceServer); + $client = m::mock(Client::class); + $client->shouldReceive('firstParty')->andReturnFalse(); + + $token = m::mock(Token::class); + $token->shouldReceive('getAttribute')->with('client')->andReturn($client); + $token->shouldReceive('getAttribute')->with('scopes')->andReturn(['foo', 'bar']); + $token->shouldReceive('can')->with('baz')->andReturnFalse(); + $token->shouldReceive('can')->with('notbar')->andReturnFalse(); + + $tokenRepository = m::mock(TokenRepository::class); + $tokenRepository->shouldReceive('find')->with('token')->andReturn($token); + + $middleware = new CheckClientCredentialsForAnyScope($resourceServer, $tokenRepository); $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); @@ -99,4 +137,35 @@ public function test_exception_is_thrown_if_token_does_not_have_required_scope() return 'response'; }, 'baz', 'notbar'); } + + /** + * @expectedException \Illuminate\Auth\AuthenticationException + */ + public function test_exception_is_thrown_if_token_belongs_to_first_party_client() + { + $resourceServer = m::mock(ResourceServer::class); + $resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock()); + $psr->shouldReceive('getAttribute')->with('oauth_user_id')->andReturn(1); + $psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1); + $psr->shouldReceive('getAttribute')->with('oauth_access_token_id')->andReturn('token'); + $psr->shouldReceive('getAttribute')->with('oauth_scopes')->andReturn(['*']); + + $client = m::mock(Client::class); + $client->shouldReceive('firstParty')->andReturnTrue(); + + $token = m::mock(Token::class); + $token->shouldReceive('getAttribute')->with('client')->andReturn($client); + + $tokenRepository = m::mock(TokenRepository::class); + $tokenRepository->shouldReceive('find')->with('token')->andReturn($token); + + $middleware = new CheckClientCredentialsForAnyScope($resourceServer, $tokenRepository); + + $request = Request::create('/'); + $request->headers->set('Authorization', 'Bearer token'); + + $response = $middleware->handle($request, function () { + return 'response'; + }); + } } diff --git a/tests/CheckClientCredentialsTest.php b/tests/CheckClientCredentialsTest.php index 3e2470f3b..1f1b790e1 100644 --- a/tests/CheckClientCredentialsTest.php +++ b/tests/CheckClientCredentialsTest.php @@ -3,8 +3,11 @@ namespace Laravel\Passport\Tests; use Mockery as m; +use Laravel\Passport\Token; use Illuminate\Http\Request; +use Laravel\Passport\Client; use PHPUnit\Framework\TestCase; +use Laravel\Passport\TokenRepository; use League\OAuth2\Server\ResourceServer; use League\OAuth2\Server\Exception\OAuthServerException; use Laravel\Passport\Http\Middleware\CheckClientCredentials; @@ -25,7 +28,17 @@ public function test_request_is_passed_along_if_token_is_valid() $psr->shouldReceive('getAttribute')->with('oauth_access_token_id')->andReturn('token'); $psr->shouldReceive('getAttribute')->with('oauth_scopes')->andReturn(['*']); - $middleware = new CheckClientCredentials($resourceServer); + $client = m::mock(Client::class); + $client->shouldReceive('firstParty')->andReturnFalse(); + + $token = m::mock(Token::class); + $token->shouldReceive('getAttribute')->with('client')->andReturn($client); + $token->shouldReceive('getAttribute')->with('scopes')->andReturn(['*']); + + $tokenRepository = m::mock(TokenRepository::class); + $tokenRepository->shouldReceive('find')->with('token')->andReturn($token); + + $middleware = new CheckClientCredentials($resourceServer, $tokenRepository); $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); @@ -46,14 +59,25 @@ public function test_request_is_passed_along_if_token_and_scope_are_valid() $psr->shouldReceive('getAttribute')->with('oauth_access_token_id')->andReturn('token'); $psr->shouldReceive('getAttribute')->with('oauth_scopes')->andReturn(['see-profile']); - $middleware = new CheckClientCredentials($resourceServer); + $client = m::mock(Client::class); + $client->shouldReceive('firstParty')->andReturnFalse(); + + $token = m::mock(Token::class); + $token->shouldReceive('getAttribute')->with('client')->andReturn($client); + $token->shouldReceive('getAttribute')->with('scopes')->andReturn(['see-profile']); + $token->shouldReceive('cant')->with('see-profile')->andReturnFalse(); + + $tokenRepository = m::mock(TokenRepository::class); + $tokenRepository->shouldReceive('find')->with('token')->andReturn($token); + + $middleware = new CheckClientCredentials($resourceServer, $tokenRepository); $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); $response = $middleware->handle($request, function () { return 'response'; - }); + }, 'see-profile'); $this->assertEquals('response', $response); } @@ -63,12 +87,13 @@ public function test_request_is_passed_along_if_token_and_scope_are_valid() */ public function test_exception_is_thrown_when_oauth_throws_exception() { + $tokenRepository = m::mock(TokenRepository::class); $resourceServer = m::mock(ResourceServer::class); $resourceServer->shouldReceive('validateAuthenticatedRequest')->andThrow( new OAuthServerException('message', 500, 'error type') ); - $middleware = new CheckClientCredentials($resourceServer); + $middleware = new CheckClientCredentials($resourceServer, $tokenRepository); $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); @@ -90,7 +115,19 @@ public function test_exception_is_thrown_if_token_does_not_have_required_scopes( $psr->shouldReceive('getAttribute')->with('oauth_access_token_id')->andReturn('token'); $psr->shouldReceive('getAttribute')->with('oauth_scopes')->andReturn(['foo', 'notbar']); - $middleware = new CheckClientCredentials($resourceServer); + $client = m::mock(Client::class); + $client->shouldReceive('firstParty')->andReturnFalse(); + + $token = m::mock(Token::class); + $token->shouldReceive('getAttribute')->with('client')->andReturn($client); + $token->shouldReceive('getAttribute')->with('scopes')->andReturn(['foo', 'notbar']); + $token->shouldReceive('cant')->with('foo')->andReturnFalse(); + $token->shouldReceive('cant')->with('bar')->andReturnTrue(); + + $tokenRepository = m::mock(TokenRepository::class); + $tokenRepository->shouldReceive('find')->with('token')->andReturn($token); + + $middleware = new CheckClientCredentials($resourceServer, $tokenRepository); $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); @@ -99,4 +136,35 @@ public function test_exception_is_thrown_if_token_does_not_have_required_scopes( return 'response'; }, 'foo', 'bar'); } + + /** + * @expectedException \Illuminate\Auth\AuthenticationException + */ + public function test_exception_is_thrown_if_token_belongs_to_first_party_client() + { + $resourceServer = m::mock(ResourceServer::class); + $resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock()); + $psr->shouldReceive('getAttribute')->with('oauth_user_id')->andReturn(1); + $psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1); + $psr->shouldReceive('getAttribute')->with('oauth_access_token_id')->andReturn('token'); + $psr->shouldReceive('getAttribute')->with('oauth_scopes')->andReturn(['*']); + + $client = m::mock(Client::class); + $client->shouldReceive('firstParty')->andReturnTrue(); + + $token = m::mock(Token::class); + $token->shouldReceive('getAttribute')->with('client')->andReturn($client); + + $tokenRepository = m::mock(TokenRepository::class); + $tokenRepository->shouldReceive('find')->with('token')->andReturn($token); + + $middleware = new CheckClientCredentials($resourceServer, $tokenRepository); + + $request = Request::create('/'); + $request->headers->set('Authorization', 'Bearer token'); + + $response = $middleware->handle($request, function () { + return 'response'; + }); + } }