From 67fd7ad2ff78d2204451043c278c5c42c3e23074 Mon Sep 17 00:00:00 2001 From: DarkGhosthunter Date: Thu, 22 Apr 2021 18:25:13 -0400 Subject: [PATCH] Adds `attemptWith()` with tests. --- src/Illuminate/Auth/SessionGuard.php | 47 ++++++++++++++++++++++++++++ tests/Auth/AuthGuardTest.php | 42 +++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index 4bb3fd4b6f73..ae86cc5c1f89 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -17,6 +17,7 @@ use Illuminate\Contracts\Cookie\QueueingFactory as CookieJar; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Session\Session; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; @@ -374,6 +375,34 @@ public function attempt(array $credentials = [], $remember = false) return false; } + /** + * Attempt to authenticate a user with credentials and additional callbacks. + * + * @param array $credentials + * @param false $remember + * @param array|callable $callbacks + * @return bool + */ + public function attemptWith(array $credentials = [], $remember = false, $callbacks = null) + { + $this->fireAttemptEvent($credentials, $remember); + + $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + + // This method does the exact same thing as Attempt, but also executes callbacks after + // the user is retrieved and validated. If one of the callbacks returns falsy, we + // won't login this user. Instead, we will fail this authentication attempt. + if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) { + $this->login($user, $remember); + + return true; + } + + $this->fireFailedEvent($user, $credentials); + + return false; + } + /** * Determine if the user matches the credentials. * @@ -392,6 +421,24 @@ protected function hasValidCredentials($user, $credentials) return $validated; } + /** + * Checks if the user should login by executing the given callbacks. + * + * @param array|callable|null $callbacks + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @return bool + */ + protected function shouldLogin($callbacks, AuthenticatableContract $user) + { + foreach (Arr::wrap($callbacks) as $callback) { + if (! $callback($user, $this)) { + return false; + } + } + + return true; + } + /** * Log the given user ID into the application. * diff --git a/tests/Auth/AuthGuardTest.php b/tests/Auth/AuthGuardTest.php index 3872bcb0a148..dea34e366a88 100755 --- a/tests/Auth/AuthGuardTest.php +++ b/tests/Auth/AuthGuardTest.php @@ -126,6 +126,48 @@ public function testAttemptReturnsFalseIfUserNotGiven() $this->assertFalse($mock->attempt(['foo'])); } + public function testAttemptAndWithCallbacks() + { + [$session, $provider, $request, $cookie] = $this->getMocks(); + $mock = $this->getMockBuilder(SessionGuard::class)->onlyMethods(['getName'])->setConstructorArgs(['default', $provider, $session, $request])->getMock(); + $mock->setDispatcher($events = m::mock(Dispatcher::class)); + $user = m::mock(Authenticatable::class); + $events->shouldReceive('dispatch')->times(3)->with(m::type(Attempting::class)); + $events->shouldReceive('dispatch')->once()->with(m::type(Login::class)); + $events->shouldReceive('dispatch')->once()->with(m::type(Authenticated::class)); + $events->shouldReceive('dispatch')->twice()->with(m::type(Validated::class)); + $events->shouldReceive('dispatch')->twice()->with(m::type(Failed::class)); + $mock->expects($this->once())->method('getName')->willReturn('foo'); + $user->shouldReceive('getAuthIdentifier')->once()->andReturn('bar'); + $mock->getSession()->shouldReceive('put')->with('foo', 'bar')->once(); + $session->shouldReceive('migrate')->once(); + $mock->getProvider()->shouldReceive('retrieveByCredentials')->times(3)->with(['foo'])->andReturn($user); + $mock->getProvider()->shouldReceive('validateCredentials')->twice()->andReturnTrue(); + $mock->getProvider()->shouldReceive('validateCredentials')->once()->andReturnFalse(); + + $this->assertTrue($mock->attemptWith(['foo'], false, function ($user, $guard) { + static::assertInstanceOf(Authenticatable::class, $user); + static::assertInstanceOf(SessionGuard::class, $guard); + + return true; + })); + + $this->assertFalse($mock->attemptWith(['foo'], false, function ($user, $guard) { + static::assertInstanceOf(Authenticatable::class, $user); + static::assertInstanceOf(SessionGuard::class, $guard); + + return false; + })); + + $executed = false; + + $this->assertFalse($mock->attemptWith(['foo'], false, function () use (&$executed) { + return $executed = true; + })); + + $this->assertFalse($executed); + } + public function testLoginStoresIdentifierInSession() { [$session, $provider, $request, $cookie] = $this->getMocks();