From 409647db612e81880696f4605f8832bc7e33f8db Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 1 Dec 2023 10:20:51 +0800 Subject: [PATCH] Fixes `$request->user()` or `Auth::user()` run query each time if token doesn't exists fixes laravel/framework#49201 Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Auth/RequestGuard.php | 11 +++++++++- src/Illuminate/Auth/TokenGuard.php | 11 +++++++++- tests/Auth/AuthRequestGuardTest.php | 33 ++++++++++++++++++++++++++++ tests/Auth/AuthTokenGuardTest.php | 14 ++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 tests/Auth/AuthRequestGuardTest.php diff --git a/src/Illuminate/Auth/RequestGuard.php b/src/Illuminate/Auth/RequestGuard.php index d0af83cb4f4f..1d0c35c55aa9 100644 --- a/src/Illuminate/Auth/RequestGuard.php +++ b/src/Illuminate/Auth/RequestGuard.php @@ -25,6 +25,13 @@ class RequestGuard implements Guard */ protected $request; + /** + * Indicates if a token user retrieval has been attempted. + * + * @var bool + */ + protected $recallAttempted = false; + /** * Create a new authentication guard. * @@ -50,10 +57,12 @@ public function user() // If we've already retrieved the user for the current request we can just // return it back immediately. We do not want to fetch the user data on // every call to this method because that would be tremendously slow. - if (! is_null($this->user)) { + if (! is_null($this->user) || $this->recallAttempted) { return $this->user; } + $this->recallAttempted = true; + return $this->user = call_user_func( $this->callback, $this->request, $this->getProvider() ); diff --git a/src/Illuminate/Auth/TokenGuard.php b/src/Illuminate/Auth/TokenGuard.php index b1aa7a7e5162..d02d3b241727 100644 --- a/src/Illuminate/Auth/TokenGuard.php +++ b/src/Illuminate/Auth/TokenGuard.php @@ -38,6 +38,13 @@ class TokenGuard implements Guard */ protected $hash = false; + /** + * Indicates if a token user retrieval has been attempted. + * + * @var bool + */ + protected $recallAttempted = false; + /** * Create a new authentication guard. * @@ -72,10 +79,12 @@ public function user() // If we've already retrieved the user for the current request we can just // return it back immediately. We do not want to fetch the user data on // every call to this method because that would be tremendously slow. - if (! is_null($this->user)) { + if (! is_null($this->user) || $this->recallAttempted) { return $this->user; } + $this->recallAttempted = true; + $user = null; $token = $this->getTokenForRequest(); diff --git a/tests/Auth/AuthRequestGuardTest.php b/tests/Auth/AuthRequestGuardTest.php new file mode 100644 index 000000000000..c909957e0b8b --- /dev/null +++ b/tests/Auth/AuthRequestGuardTest.php @@ -0,0 +1,33 @@ +shouldReceive('retrieveByCredentials')->once()->with(['api_token' => 'foo'])->andReturnNull(); + $request = Request::create('/', 'GET', ['api_token' => 'foo']); + + $guard = new RequestGuard(function ($request, $provider) { + return $provider->retrieveByCredentials($request->query()); + }, $request, $provider); + + $this->assertNull($guard->user()); + + // Ensure mocked provider.retrieveByCredential expectation only called once. + $this->assertNull($guard->user()); + } +} diff --git a/tests/Auth/AuthTokenGuardTest.php b/tests/Auth/AuthTokenGuardTest.php index b79c079b8885..761295cbee6a 100644 --- a/tests/Auth/AuthTokenGuardTest.php +++ b/tests/Auth/AuthTokenGuardTest.php @@ -64,6 +64,20 @@ public function testUserCanBeRetrievedByAuthHeaders() $this->assertSame(1, $user->id); } + public function testUserDoesntCallRetrieveByCredentialMoreThanOnceWhenGivenAuthentication() + { + $provider = m::mock(UserProvider::class); + $provider->shouldReceive('retrieveByCredentials')->once()->with(['api_token' => 'foo'])->andReturnNull(); + $request = Request::create('/', 'GET', ['api_token' => 'foo']); + + $guard = new TokenGuard($provider, $request); + + $this->assertNull($guard->user()); + + // Ensure mocked provider.retrieveByCredential expectation only called once. + $this->assertNull($guard->user()); + } + public function testUserCanBeRetrievedByBearerToken() { $provider = m::mock(UserProvider::class);