From b67704d9f73207aea9e4578a9fdfed0505602c27 Mon Sep 17 00:00:00 2001 From: Eric Schricker Date: Wed, 6 Oct 2021 05:55:00 +0200 Subject: [PATCH] Added dispatching of authentication events. The PR was originally created here https://github.com/tymondesigns/jwt-auth/pull/2137 by fkupper --- src/JWTGuard.php | 90 +++++++++++++++++++++-- src/Providers/AbstractServiceProvider.php | 3 +- tests/JWTGuardTest.php | 57 +++++++++++++- tests/ManagerTest.php | 6 +- 4 files changed, 145 insertions(+), 11 deletions(-) diff --git a/src/JWTGuard.php b/src/JWTGuard.php index a05c214b..9f6103ed 100644 --- a/src/JWTGuard.php +++ b/src/JWTGuard.php @@ -12,9 +12,13 @@ namespace PHPOpenSourceSaver\JWTAuth; use BadMethodCallException; +use Illuminate\Auth\Events\Attempting; +use Illuminate\Auth\Events\Failed; +use Illuminate\Auth\Events\Validated; use Illuminate\Auth\GuardHelpers; use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Auth\UserProvider; +use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Http\Request; use Illuminate\Support\Traits\Macroable; use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject; @@ -48,6 +52,20 @@ class JWTGuard implements Guard */ protected $request; + /** + * The event dispatcher instance. + * + * @var \Illuminate\Contracts\Events\Dispatcher + */ + protected $events; + + /** + * The name of the Guard. + * + * @var string + */ + protected $name = 'tymon.jwt'; + /** * Instantiate the class. * @@ -57,11 +75,12 @@ class JWTGuard implements Guard * * @return void */ - public function __construct(JWT $jwt, UserProvider $provider, Request $request) + public function __construct(JWT $jwt, UserProvider $provider, Request $request, Dispatcher $eventDispatcher) { $this->jwt = $jwt; $this->provider = $provider; $this->request = $request; + $this->events = $eventDispatcher; } /** @@ -75,7 +94,8 @@ public function user() return $this->user; } - if ($this->jwt->setRequest($this->request)->getToken() && + if ( + $this->jwt->setRequest($this->request)->getToken() && ($payload = $this->jwt->check(true)) && $this->validateSubject() ) { @@ -92,7 +112,7 @@ public function user() */ public function userOrFail() { - if (! $user = $this->user()) { + if (!$user = $this->user()) { throw new UserNotDefinedException; } @@ -123,10 +143,14 @@ public function attempt(array $credentials = [], $login = true) { $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + $this->fireAttemptEvent($credentials); + if ($this->hasValidCredentials($user, $credentials)) { return $login ? $this->login($user) : true; } + $this->fireFailedEvent($user, $credentials); + return false; } @@ -387,7 +411,13 @@ public function getLastAttempted() */ protected function hasValidCredentials($user, $credentials) { - return $user !== null && $this->provider->validateCredentials($user, $credentials); + $validated = $user !== null && $this->provider->validateCredentials($user, $credentials); + + if ($validated) { + $this->fireValidatedEvent($user); + } + + return $validated; } /** @@ -399,7 +429,7 @@ protected function validateSubject() { // If the provider doesn't have the necessary method // to get the underlying model name then allow. - if (! method_exists($this->provider, 'getModel')) { + if (!method_exists($this->provider, 'getModel')) { return true; } @@ -415,13 +445,61 @@ protected function validateSubject() */ protected function requireToken() { - if (! $this->jwt->setRequest($this->getRequest())->getToken()) { + if (!$this->jwt->setRequest($this->getRequest())->getToken()) { throw new JWTException('Token could not be parsed from the request.'); } return $this->jwt; } + /** + * Fire the attempt event. + * + * @param array $credentials + * + * @return void + */ + protected function fireAttemptEvent(array $credentials) + { + $this->events->dispatch(new Attempting( + $this->name, + $credentials, + false + )); + } + + /** + * Fires the validated event. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * + * @return void + */ + protected function fireValidatedEvent($user) + { + $this->events->dispatch(new Validated( + $this->name, + $user + )); + } + + /** + * Fire the failed authentication attempt event. + * + * @param \Illuminate\Contracts\Auth\Authenticatable|null $user + * @param array $credentials + * + * @return void + */ + protected function fireFailedEvent($user, array $credentials) + { + $this->events->dispatch(new Failed( + $this->name, + $user, + $credentials + )); + } + /** * Magically call the JWT instance. * diff --git a/src/Providers/AbstractServiceProvider.php b/src/Providers/AbstractServiceProvider.php index 71386ea1..099117f1 100644 --- a/src/Providers/AbstractServiceProvider.php +++ b/src/Providers/AbstractServiceProvider.php @@ -95,7 +95,8 @@ protected function extendAuthGuard() $guard = new JWTGuard( $app['tymon.jwt'], $app['auth']->createUserProvider($config['provider']), - $app['request'] + $app['request'], + $app['events'] ); $app->refresh('request', $guard, 'setRequest'); diff --git a/tests/JWTGuardTest.php b/tests/JWTGuardTest.php index f6e571cc..29a9e1cf 100644 --- a/tests/JWTGuardTest.php +++ b/tests/JWTGuardTest.php @@ -12,6 +12,10 @@ namespace PHPOpenSourceSaver\JWTAuth\Test; use Illuminate\Auth\EloquentUserProvider; +use Illuminate\Auth\Events\Attempting; +use Illuminate\Auth\Events\Failed; +use Illuminate\Auth\Events\Validated; +use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Http\Request; use Mockery; @@ -41,13 +45,24 @@ class JWTGuardTest extends AbstractTestCase */ protected $guard; + /** + * @var \lluminate\Contracts\Events\Dispatcher|\Mockery\MockInterface + */ + protected $eventDispatcher; + public function setUp(): void { parent::setUp(); $this->jwt = Mockery::mock(JWT::class); $this->provider = Mockery::mock(EloquentUserProvider::class); - $this->guard = new JWTGuard($this->jwt, $this->provider, Request::create('/foo', 'GET')); + $this->eventDispatcher = Mockery::mock(Dispatcher::class); + $this->guard = new JWTGuard( + $this->jwt, + $this->provider, + Request::create('/foo', 'GET'), + $this->eventDispatcher + ); } /** @test */ @@ -206,6 +221,14 @@ public function it_should_return_a_token_if_credentials_are_ok_and_user_is_found ->with(['foo' => 'bar']) ->andReturnSelf(); + $this->eventDispatcher->shouldReceive('dispatch') + ->once() + ->with(Mockery::type(Attempting::class)); + + $this->eventDispatcher->shouldReceive('dispatch') + ->once() + ->with(Mockery::type(Validated::class)); + $token = $this->guard->claims(['foo' => 'bar'])->attempt($credentials); $this->assertSame($this->guard->getLastAttempted(), $user); @@ -228,6 +251,14 @@ public function it_should_return_true_if_credentials_are_ok_and_user_is_found_wh ->with($user, $credentials) ->andReturn(true); + $this->eventDispatcher->shouldReceive('dispatch') + ->twice() + ->with(Mockery::type(Attempting::class)); + + $this->eventDispatcher->shouldReceive('dispatch') + ->twice() + ->with(Mockery::type(Validated::class)); + $this->assertTrue($this->guard->attempt($credentials, false)); // once $this->assertTrue($this->guard->validate($credentials)); // twice } @@ -248,6 +279,14 @@ public function it_should_return_false_if_credentials_are_invalid() ->with($user, $credentials) ->andReturn(false); + $this->eventDispatcher->shouldReceive('dispatch') + ->once() + ->with(Mockery::type(Attempting::class)); + + $this->eventDispatcher->shouldReceive('dispatch') + ->once() + ->with(Mockery::type(Failed::class)); + $this->assertFalse($this->guard->attempt($credentials)); } @@ -348,6 +387,14 @@ public function it_should_authenticate_the_user_by_credentials_and_return_true_i ->with($user, $credentials) ->andReturn(true); + $this->eventDispatcher->shouldReceive('dispatch') + ->once() + ->with(Mockery::type(Attempting::class)); + + $this->eventDispatcher->shouldReceive('dispatch') + ->once() + ->with(Mockery::type(Validated::class)); + $this->assertTrue($this->guard->once($credentials)); } @@ -367,6 +414,14 @@ public function it_should_attempt_to_authenticate_the_user_by_credentials_and_re ->with($user, $credentials) ->andReturn(false); + $this->eventDispatcher->shouldReceive('dispatch') + ->once() + ->with(Mockery::type(Attempting::class)); + + $this->eventDispatcher->shouldReceive('dispatch') + ->once() + ->with(Mockery::type(Failed::class)); + $this->assertFalse($this->guard->once($credentials)); } diff --git a/tests/ManagerTest.php b/tests/ManagerTest.php index a9efa692..49bd0ac1 100644 --- a/tests/ManagerTest.php +++ b/tests/ManagerTest.php @@ -283,7 +283,7 @@ public function test_if_show_blacklisted_exception_configuration_is_enabled() { $this->manager->setBlackListExceptionEnabled(true); - $this->assertIsBool($this->manager->gettBlackListExceptionEnabled()); + $this->assertIsBool($this->manager->getBlackListExceptionEnabled()); } /** @test */ @@ -291,7 +291,7 @@ public function test_if_black_listed_exception_is_set_to_true() { $this->manager->setBlackListExceptionEnabled(true); - $this->assertTrue($this->manager->gettBlackListExceptionEnabled()); + $this->assertTrue($this->manager->getBlackListExceptionEnabled()); } /** @test */ @@ -299,6 +299,6 @@ public function test_if_black_listed_exception_is_set_to_false() { $this->manager->setBlackListExceptionEnabled(false); - $this->assertFalse($this->manager->gettBlackListExceptionEnabled()); + $this->assertFalse($this->manager->getBlackListExceptionEnabled()); } }