Skip to content

Commit 80db8a0

Browse files
committed
Merge branch 'feat/attempt-callbacks' into 8.x
2 parents 58a6d2d + 696cbc7 commit 80db8a0

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed

src/Illuminate/Auth/SessionGuard.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Illuminate\Contracts\Cookie\QueueingFactory as CookieJar;
1818
use Illuminate\Contracts\Events\Dispatcher;
1919
use Illuminate\Contracts\Session\Session;
20+
use Illuminate\Support\Arr;
2021
use Illuminate\Support\Facades\Hash;
2122
use Illuminate\Support\Str;
2223
use Illuminate\Support\Traits\Macroable;
@@ -374,6 +375,34 @@ public function attempt(array $credentials = [], $remember = false)
374375
return false;
375376
}
376377

378+
/**
379+
* Attempt to authenticate a user with credentials and additional callbacks.
380+
*
381+
* @param array $credentials
382+
* @param false $remember
383+
* @param array|callable $callbacks
384+
* @return bool
385+
*/
386+
public function attemptWith(array $credentials = [], $remember = false, $callbacks = null)
387+
{
388+
$this->fireAttemptEvent($credentials, $remember);
389+
390+
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
391+
392+
// This method does the exact same thing as attempt, but also executes callbacks after
393+
// the user is retrieved and validated. If one of the callbacks returns falsy we do
394+
// not login the user. Instead, we will fail the specific authentication attempt.
395+
if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) {
396+
$this->login($user, $remember);
397+
398+
return true;
399+
}
400+
401+
$this->fireFailedEvent($user, $credentials);
402+
403+
return false;
404+
}
405+
377406
/**
378407
* Determine if the user matches the credentials.
379408
*
@@ -392,6 +421,24 @@ protected function hasValidCredentials($user, $credentials)
392421
return $validated;
393422
}
394423

424+
/**
425+
* Determine if the user should login by executing the given callbacks.
426+
*
427+
* @param array|callable|null $callbacks
428+
* @param \Illuminate\Contracts\Auth\Authenticatable $user
429+
* @return bool
430+
*/
431+
protected function shouldLogin($callbacks, AuthenticatableContract $user)
432+
{
433+
foreach (Arr::wrap($callbacks) as $callback) {
434+
if (! $callback($user, $this)) {
435+
return false;
436+
}
437+
}
438+
439+
return true;
440+
}
441+
395442
/**
396443
* Log the given user ID into the application.
397444
*

tests/Auth/AuthGuardTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,48 @@ public function testAttemptReturnsFalseIfUserNotGiven()
126126
$this->assertFalse($mock->attempt(['foo']));
127127
}
128128

129+
public function testAttemptAndWithCallbacks()
130+
{
131+
[$session, $provider, $request, $cookie] = $this->getMocks();
132+
$mock = $this->getMockBuilder(SessionGuard::class)->onlyMethods(['getName'])->setConstructorArgs(['default', $provider, $session, $request])->getMock();
133+
$mock->setDispatcher($events = m::mock(Dispatcher::class));
134+
$user = m::mock(Authenticatable::class);
135+
$events->shouldReceive('dispatch')->times(3)->with(m::type(Attempting::class));
136+
$events->shouldReceive('dispatch')->once()->with(m::type(Login::class));
137+
$events->shouldReceive('dispatch')->once()->with(m::type(Authenticated::class));
138+
$events->shouldReceive('dispatch')->twice()->with(m::type(Validated::class));
139+
$events->shouldReceive('dispatch')->twice()->with(m::type(Failed::class));
140+
$mock->expects($this->once())->method('getName')->willReturn('foo');
141+
$user->shouldReceive('getAuthIdentifier')->once()->andReturn('bar');
142+
$mock->getSession()->shouldReceive('put')->with('foo', 'bar')->once();
143+
$session->shouldReceive('migrate')->once();
144+
$mock->getProvider()->shouldReceive('retrieveByCredentials')->times(3)->with(['foo'])->andReturn($user);
145+
$mock->getProvider()->shouldReceive('validateCredentials')->twice()->andReturnTrue();
146+
$mock->getProvider()->shouldReceive('validateCredentials')->once()->andReturnFalse();
147+
148+
$this->assertTrue($mock->attemptWith(['foo'], false, function ($user, $guard) {
149+
static::assertInstanceOf(Authenticatable::class, $user);
150+
static::assertInstanceOf(SessionGuard::class, $guard);
151+
152+
return true;
153+
}));
154+
155+
$this->assertFalse($mock->attemptWith(['foo'], false, function ($user, $guard) {
156+
static::assertInstanceOf(Authenticatable::class, $user);
157+
static::assertInstanceOf(SessionGuard::class, $guard);
158+
159+
return false;
160+
}));
161+
162+
$executed = false;
163+
164+
$this->assertFalse($mock->attemptWith(['foo'], false, function () use (&$executed) {
165+
return $executed = true;
166+
}));
167+
168+
$this->assertFalse($executed);
169+
}
170+
129171
public function testLoginStoresIdentifierInSession()
130172
{
131173
[$session, $provider, $request, $cookie] = $this->getMocks();

0 commit comments

Comments
 (0)