From 1b50727404609f6c0cfc0cebe29344959fb1de33 Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Tue, 23 Nov 2021 22:36:34 +0100 Subject: [PATCH 1/4] Allow authenticated client to be retrieved from the guard --- src/Guards/TokenGuard.php | 86 ++++++++++++++++++++++++------ src/PassportServiceProvider.php | 20 ++++--- tests/Unit/TokenGuardTest.php | 94 ++++++++++++++++----------------- 3 files changed, 126 insertions(+), 74 deletions(-) diff --git a/src/Guards/TokenGuard.php b/src/Guards/TokenGuard.php index d60265208..568b43f7d 100644 --- a/src/Guards/TokenGuard.php +++ b/src/Guards/TokenGuard.php @@ -4,7 +4,9 @@ use Exception; use Firebase\JWT\JWT; +use Illuminate\Auth\GuardHelpers; use Illuminate\Container\Container; +use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Cookie\CookieValuePrefix; @@ -20,8 +22,10 @@ use Nyholm\Psr7\Factory\Psr17Factory; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; -class TokenGuard +class TokenGuard implements Guard { + use GuardHelpers; + /** * The resource server instance. * @@ -57,6 +61,20 @@ class TokenGuard */ protected $encrypter; + /** + * The request instance. + * + * @var \Illuminate\Http\Request + */ + protected $request; + + /** + * The currently authenticated client. + * + * @var \Laravel\Passport\Client|null + */ + protected $client; + /** * Create a new token guard instance. * @@ -65,6 +83,7 @@ class TokenGuard * @param \Laravel\Passport\TokenRepository $tokens * @param \Laravel\Passport\ClientRepository $clients * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Illuminate\Http\Request $request * @return void */ public function __construct( @@ -72,49 +91,71 @@ public function __construct( PassportUserProvider $provider, TokenRepository $tokens, ClientRepository $clients, - Encrypter $encrypter + Encrypter $encrypter, + Request $request ) { $this->server = $server; $this->tokens = $tokens; $this->clients = $clients; $this->provider = $provider; $this->encrypter = $encrypter; + $this->request = $request; } /** * Get the user for the incoming request. * - * @param \Illuminate\Http\Request $request * @return mixed */ - public function user(Request $request) + public function user() { - if ($request->bearerToken()) { - return $this->authenticateViaBearerToken($request); - } elseif ($request->cookie(Passport::cookie())) { - return $this->authenticateViaCookie($request); + if (! is_null($this->user)) { + return $this->user; + } + + if ($this->request->bearerToken()) { + return $this->user = $this->authenticateViaBearerToken($this->request); + } elseif ($this->request->cookie(Passport::cookie())) { + return $this->user = $this->authenticateViaCookie($this->request); } } + /** + * Validate a user's credentials. + * + * @param array $credentials + * @return bool + */ + public function validate(array $credentials = []) + { + return ! is_null((new static( + $this->server, + $this->provider, + $this->tokens, + $this->clients, + $this->encrypter, + $credentials['request'], + ))->user()); + } + /** * Get the client for the incoming request. * - * @param \Illuminate\Http\Request $request * @return mixed */ - public function client(Request $request) + public function client() { - if ($request->bearerToken()) { - if (! $psr = $this->getPsrRequestViaBearerToken($request)) { + if ($this->request->bearerToken()) { + if (! $psr = $this->getPsrRequestViaBearerToken($this->request)) { return; } - return $this->clients->findActive( + return $this->client = $this->clients->findActive( $psr->getAttribute('oauth_client_id') ); - } elseif ($request->cookie(Passport::cookie())) { - if ($token = $this->getTokenViaCookie($request)) { - return $this->clients->findActive($token['aud']); + } elseif ($this->request->cookie(Passport::cookie())) { + if ($token = $this->getTokenViaCookie($this->request)) { + return $this->client = $this->clients->findActive($token['aud']); } } } @@ -285,6 +326,19 @@ protected function getTokenFromRequest($request) return $token; } + /** + * Set the current request instance. + * + * @param \Illuminate\Http\Request $request + * @return $this + */ + public function setRequest(Request $request) + { + $this->request = $request; + + return $this; + } + /** * Determine if the cookie contents should be serialized. * diff --git a/src/PassportServiceProvider.php b/src/PassportServiceProvider.php index de02ca4ee..0dcc65663 100644 --- a/src/PassportServiceProvider.php +++ b/src/PassportServiceProvider.php @@ -4,7 +4,6 @@ use DateInterval; use Illuminate\Auth\Events\Logout; -use Illuminate\Auth\RequestGuard; use Illuminate\Config\Repository as Config; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cookie; @@ -341,19 +340,18 @@ protected function registerGuard() * Make an instance of the token guard. * * @param array $config - * @return \Illuminate\Auth\RequestGuard + * @return \Laravel\Passport\Guards\TokenGuard */ protected function makeGuard(array $config) { - return new RequestGuard(function ($request) use ($config) { - return (new TokenGuard( - $this->app->make(ResourceServer::class), - new PassportUserProvider(Auth::createUserProvider($config['provider']), $config['provider']), - $this->app->make(TokenRepository::class), - $this->app->make(ClientRepository::class), - $this->app->make('encrypter') - ))->user($request); - }, $this->app['request']); + return new TokenGuard( + $this->app->make(ResourceServer::class), + new PassportUserProvider(Auth::createUserProvider($config['provider']), $config['provider']), + $this->app->make(TokenRepository::class), + $this->app->make(ClientRepository::class), + $this->app->make('encrypter'), + $this->app->make('request') + ); } /** diff --git a/tests/Unit/TokenGuardTest.php b/tests/Unit/TokenGuardTest.php index b586eff99..e804ade65 100644 --- a/tests/Unit/TokenGuardTest.php +++ b/tests/Unit/TokenGuardTest.php @@ -37,11 +37,11 @@ public function test_user_can_be_pulled_via_bearer_token() $clients = m::mock(ClientRepository::class); $encrypter = m::mock(Encrypter::class); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $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); @@ -52,7 +52,7 @@ public function test_user_can_be_pulled_via_bearer_token() $clients->shouldReceive('revoked')->with(1)->andReturn(false); $clients->shouldReceive('findActive')->with(1)->andReturn(new TokenGuardTestClient); - $user = $guard->user($request); + $user = $guard->user(); $this->assertInstanceOf(TokenGuardTestUser::class, $user); $this->assertEquals($token, $user->token()); @@ -71,19 +71,19 @@ public function test_no_user_is_returned_when_oauth_throws_exception() $clients = m::mock(ClientRepository::class); $encrypter = m::mock(Encrypter::class); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $resourceServer->shouldReceive('validateAuthenticatedRequest')->andThrow( new OAuthServerException('message', 500, 'error type') ); - $this->assertNull($guard->user($request)); + $this->assertNull($guard->user()); // Assert that `validateAuthenticatedRequest` isn't called twice on failure. - $this->assertNull($guard->user($request)); + $this->assertNull($guard->user()); } public function test_null_is_returned_if_no_user_is_found() @@ -98,18 +98,18 @@ public function test_null_is_returned_if_no_user_is_found() ->with(1) ->andReturn(new TokenGuardTestClient); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $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); $userProvider->shouldReceive('retrieveById')->with(1)->andReturn(null); $userProvider->shouldReceive('getProviderName')->andReturn(null); - $this->assertNull($guard->user($request)); + $this->assertNull($guard->user()); } public function test_users_may_be_retrieved_from_cookies_with_csrf_token_header() @@ -124,8 +124,6 @@ public function test_users_may_be_retrieved_from_cookies_with_csrf_token_header( ->with(1) ->andReturn(new TokenGuardTestClient); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('X-CSRF-TOKEN', 'token'); $request->cookies->set('laravel_token', @@ -137,10 +135,12 @@ public function test_users_may_be_retrieved_from_cookies_with_csrf_token_header( ], str_repeat('a', 16)), false) ); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser); $userProvider->shouldReceive('getProviderName')->andReturn(null); - $user = $guard->user($request); + $user = $guard->user(); $this->assertEquals($expectedUser, $user); } @@ -157,8 +157,6 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header( ->with(1) ->andReturn(new TokenGuardTestClient); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('X-XSRF-TOKEN', $encrypter->encrypt(CookieValuePrefix::create('X-XSRF-TOKEN', $encrypter->getKey()).'token', false)); $request->cookies->set('laravel_token', @@ -170,10 +168,12 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header( ], str_repeat('a', 16)), false) ); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser); $userProvider->shouldReceive('getProviderName')->andReturn(null); - $user = $guard->user($request); + $user = $guard->user(); $this->assertEquals($expectedUser, $user); } @@ -186,8 +186,6 @@ public function test_cookie_xsrf_is_verified_against_csrf_token_header() $clients = m::mock(ClientRepository::class); $encrypter = new Encrypter(str_repeat('a', 16)); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('X-CSRF-TOKEN', 'wrong_token'); $request->cookies->set('laravel_token', @@ -199,9 +197,11 @@ public function test_cookie_xsrf_is_verified_against_csrf_token_header() ], str_repeat('a', 16))) ); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $userProvider->shouldReceive('retrieveById')->never(); - $this->assertNull($guard->user($request)); + $this->assertNull($guard->user()); } public function test_cookie_xsrf_is_verified_against_xsrf_token_header() @@ -212,8 +212,6 @@ public function test_cookie_xsrf_is_verified_against_xsrf_token_header() $clients = m::mock(ClientRepository::class); $encrypter = new Encrypter(str_repeat('a', 16)); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('X-XSRF-TOKEN', $encrypter->encrypt('wrong_token', false)); $request->cookies->set('laravel_token', @@ -225,9 +223,11 @@ public function test_cookie_xsrf_is_verified_against_xsrf_token_header() ], str_repeat('a', 16))) ); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $userProvider->shouldReceive('retrieveById')->never(); - $this->assertNull($guard->user($request)); + $this->assertNull($guard->user()); } public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_when_using_a_custom_encryption_key() @@ -246,8 +246,6 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_ ->with(1) ->andReturn(new TokenGuardTestClient); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('X-XSRF-TOKEN', $encrypter->encrypt(CookieValuePrefix::create('X-XSRF-TOKEN', $encrypter->getKey()).'token', false)); $request->cookies->set('laravel_token', @@ -259,10 +257,12 @@ public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_ ], Passport::tokenEncryptionKey($encrypter)), false) ); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser); $userProvider->shouldReceive('getProviderName')->andReturn(null); - $user = $guard->user($request); + $user = $guard->user(); $this->assertEquals($expectedUser, $user); @@ -278,8 +278,6 @@ public function test_xsrf_token_cookie_without_a_token_header_is_not_accepted() $clients = m::mock(ClientRepository::class); $encrypter = new Encrypter(str_repeat('a', 16)); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->cookies->set('XSRF-TOKEN', $encrypter->encrypt('token', false)); $request->cookies->set('laravel_token', @@ -291,9 +289,11 @@ public function test_xsrf_token_cookie_without_a_token_header_is_not_accepted() ], str_repeat('a', 16))) ); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $userProvider->shouldReceive('retrieveById')->never(); - $this->assertNull($guard->user($request)); + $this->assertNull($guard->user()); } public function test_expired_cookies_may_not_be_used() @@ -304,8 +304,6 @@ public function test_expired_cookies_may_not_be_used() $clients = m::mock(ClientRepository::class); $encrypter = new Encrypter(str_repeat('a', 16)); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('X-CSRF-TOKEN', 'token'); $request->cookies->set('laravel_token', @@ -317,9 +315,11 @@ public function test_expired_cookies_may_not_be_used() ], str_repeat('a', 16))) ); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $userProvider->shouldReceive('retrieveById')->never(); - $this->assertNull($guard->user($request)); + $this->assertNull($guard->user()); } public function test_csrf_check_can_be_disabled() @@ -334,8 +334,6 @@ public function test_csrf_check_can_be_disabled() ->with(1) ->andReturn(new TokenGuardTestClient); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - Passport::ignoreCsrfToken(); $request = Request::create('/'); @@ -347,10 +345,12 @@ public function test_csrf_check_can_be_disabled() ], str_repeat('a', 16)), false) ); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser); $userProvider->shouldReceive('getProviderName')->andReturn(null); - $user = $guard->user($request); + $user = $guard->user(); $this->assertEquals($expectedUser, $user); } @@ -363,16 +363,16 @@ public function test_client_can_be_pulled_via_bearer_token() $clients = m::mock(ClientRepository::class); $encrypter = m::mock(Encrypter::class); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock()); $psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1); $clients->shouldReceive('findActive')->with(1)->andReturn(new TokenGuardTestClient); - $client = $guard->client($request); + $client = $guard->client(); $this->assertInstanceOf(TokenGuardTestClient::class, $client); } @@ -390,19 +390,19 @@ public function test_no_client_is_returned_when_oauth_throws_exception() $clients = m::mock(ClientRepository::class); $encrypter = m::mock(Encrypter::class); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $resourceServer->shouldReceive('validateAuthenticatedRequest')->andThrow( new OAuthServerException('message', 500, 'error type') ); - $this->assertNull($guard->client($request)); + $this->assertNull($guard->client()); // Assert that `validateAuthenticatedRequest` isn't called twice on failure. - $this->assertNull($guard->client($request)); + $this->assertNull($guard->client()); } public function test_null_is_returned_if_no_client_is_found() @@ -413,16 +413,16 @@ public function test_null_is_returned_if_no_client_is_found() $clients = m::mock(ClientRepository::class); $encrypter = m::mock(Encrypter::class); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('Authorization', 'Bearer token'); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock()); $psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1); $clients->shouldReceive('findActive')->with(1)->andReturn(null); - $this->assertNull($guard->client($request)); + $this->assertNull($guard->client()); } public function test_clients_may_be_retrieved_from_cookies() @@ -433,8 +433,6 @@ public function test_clients_may_be_retrieved_from_cookies() $clients = m::mock(ClientRepository::class); $encrypter = new Encrypter(str_repeat('a', 16)); - $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter); - $request = Request::create('/'); $request->headers->set('X-CSRF-TOKEN', 'token'); $request->cookies->set('laravel_token', @@ -446,9 +444,11 @@ public function test_clients_may_be_retrieved_from_cookies() ], str_repeat('a', 16)), false) ); + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + $clients->shouldReceive('findActive')->with(1)->andReturn($expectedClient = new TokenGuardTestClient); - $client = $guard->client($request); + $client = $guard->client(); $this->assertEquals($expectedClient, $client); } From afee9039fff3ba459871cab4e4fd0c613c41c1f3 Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Thu, 25 Nov 2021 15:06:51 +0100 Subject: [PATCH 2/4] Make token guard macroable --- src/Guards/TokenGuard.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Guards/TokenGuard.php b/src/Guards/TokenGuard.php index 568b43f7d..e8a587ce8 100644 --- a/src/Guards/TokenGuard.php +++ b/src/Guards/TokenGuard.php @@ -12,6 +12,7 @@ use Illuminate\Cookie\CookieValuePrefix; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Http\Request; +use Illuminate\Support\Traits\Macroable; use Laravel\Passport\ClientRepository; use Laravel\Passport\Passport; use Laravel\Passport\PassportUserProvider; @@ -24,7 +25,7 @@ class TokenGuard implements Guard { - use GuardHelpers; + use GuardHelpers, Macroable; /** * The resource server instance. From 0355ed94dda679ec5e647836cbb38ca610d2ead1 Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Thu, 25 Nov 2021 15:14:15 +0100 Subject: [PATCH 3/4] Use previously resolved client when available --- src/Guards/TokenGuard.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Guards/TokenGuard.php b/src/Guards/TokenGuard.php index e8a587ce8..640c6961e 100644 --- a/src/Guards/TokenGuard.php +++ b/src/Guards/TokenGuard.php @@ -146,6 +146,10 @@ public function validate(array $credentials = []) */ public function client() { + if (! is_null($this->client)) { + return $this->client; + } + if ($this->request->bearerToken()) { if (! $psr = $this->getPsrRequestViaBearerToken($this->request)) { return; From 53f06fd1d31ad73d16d085ff0a5018e9197727a1 Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Thu, 25 Nov 2021 15:21:01 +0100 Subject: [PATCH 4/4] Add tests ensuring client and user are only pulled from the database once --- tests/Unit/TokenGuardTest.php | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/Unit/TokenGuardTest.php b/tests/Unit/TokenGuardTest.php index e804ade65..0528f3839 100644 --- a/tests/Unit/TokenGuardTest.php +++ b/tests/Unit/TokenGuardTest.php @@ -58,6 +58,40 @@ public function test_user_can_be_pulled_via_bearer_token() $this->assertEquals($token, $user->token()); } + public function test_user_is_resolved_only_once() + { + $resourceServer = m::mock(ResourceServer::class); + $userProvider = m::mock(PassportUserProvider::class); + $tokens = m::mock(TokenRepository::class); + $clients = m::mock(ClientRepository::class); + $encrypter = m::mock(Encrypter::class); + + $request = Request::create('/'); + $request->headers->set('Authorization', 'Bearer token'); + + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + + $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'); + $userProvider->shouldReceive('retrieveById')->with(1)->andReturn(new TokenGuardTestUser); + $userProvider->shouldReceive('getProviderName')->andReturn(null); + $tokens->shouldReceive('find')->once()->with('token')->andReturn($token = m::mock()); + $clients->shouldReceive('revoked')->with(1)->andReturn(false); + $clients->shouldReceive('findActive')->with(1)->andReturn(new TokenGuardTestClient); + + $user = $guard->user(); + + $userProvider->shouldReceive('retrieveById')->never(); + + $user2 = $guard->user(); + + $this->assertInstanceOf(TokenGuardTestUser::class, $user); + $this->assertEquals($token, $user->token()); + $this->assertSame($user, $user2); + } + public function test_no_user_is_returned_when_oauth_throws_exception() { $container = new Container; @@ -377,6 +411,33 @@ public function test_client_can_be_pulled_via_bearer_token() $this->assertInstanceOf(TokenGuardTestClient::class, $client); } + public function test_client_is_resolved_only_once() + { + $resourceServer = m::mock(ResourceServer::class); + $userProvider = m::mock(PassportUserProvider::class); + $tokens = m::mock(TokenRepository::class); + $clients = m::mock(ClientRepository::class); + $encrypter = m::mock(Encrypter::class); + + $request = Request::create('/'); + $request->headers->set('Authorization', 'Bearer token'); + + $guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter, $request); + + $resourceServer->shouldReceive('validateAuthenticatedRequest')->andReturn($psr = m::mock()); + $psr->shouldReceive('getAttribute')->with('oauth_client_id')->andReturn(1); + $clients->shouldReceive('findActive')->with(1)->andReturn(new TokenGuardTestClient); + + $client = $guard->client(); + + $clients->shouldReceive('findActive')->never(); + + $client2 = $guard->client(); + + $this->assertInstanceOf(TokenGuardTestClient::class, $client); + $this->assertSame($client, $client2); + } + public function test_no_client_is_returned_when_oauth_throws_exception() { $container = new Container;