From 89f15373bc5fcc68ae7abdc30c7d5c3c01aeffe9 Mon Sep 17 00:00:00 2001 From: Italo Israel Baeza Cabrera Date: Fri, 11 Nov 2022 18:26:07 -0300 Subject: [PATCH] Adds tests for trait, fixing some methods. --- src/WebAuthnAuthentication.php | 25 ++- tests/WebAuthnAuthenticationTest.php | 260 +++++++++++++++++++++++++++ 2 files changed, 271 insertions(+), 14 deletions(-) create mode 100644 tests/WebAuthnAuthenticationTest.php diff --git a/src/WebAuthnAuthentication.php b/src/WebAuthnAuthentication.php index 1623f92..ee38f8c 100644 --- a/src/WebAuthnAuthentication.php +++ b/src/WebAuthnAuthentication.php @@ -7,7 +7,6 @@ use Illuminate\Support\Facades\Date; use JetBrains\PhpStorm\ArrayShape; use Laragear\WebAuthn\Models\WebAuthnCredential; -use function in_array; /** * @property-read \Illuminate\Database\Eloquent\Collection $webAuthnCredentials @@ -39,20 +38,17 @@ public function webAuthnData(): array */ public function flushCredentials(string ...$except): void { - if ($this->relationLoaded('webAuthnCredentials') && $this->webAuthnCredentials instanceof Collection) { - $partitioned = $this->webAuthnCredentials - ->partition(static function (WebAuthnCredential $credential) use ($except): bool { - return in_array($credential->getKey(), $except, true); - }); - - $partitioned->first()->each->delete(); - - $this->setRelation('webAuthnCredentials', $partitioned->last()); + if (! $this->relationLoaded('webAuthnCredentials')) { + $this->webAuthnCredentials()->whereKeyNot($except)->delete(); return; } - $this->webAuthnCredentials()->whereKeyNot($except)->delete(); + if ($this->webAuthnCredentials instanceof Collection && $this->webAuthnCredentials->isNotEmpty()) { + $this->webAuthnCredentials->whereNotIn('id', $except)->each->delete(); + + $this->setRelation('webAuthnCredentials', $this->webAuthnCredentials->whereIn('id', $except)); + } } /** @@ -65,13 +61,14 @@ public function disableAllCredentials(string ...$except): void { if ($this->relationLoaded('webAuthnCredentials') && $this->webAuthnCredentials instanceof Collection) { $this->webAuthnCredentials - ->each(static function (WebAuthnCredential $credential) use ($except): bool { - if ($credential->isEnabled() && in_array($credential->getKey(), $except, true)) { + ->when($except)->whereNotIn('id', $except) + ->each(static function (WebAuthnCredential $credential): void { + if ($credential->isEnabled()) { $credential->disable(); } }); } else { - $this->webAuthnCredentials()->whereKeyNot($except)->update(['disabled_at' => Date::now()]); + $this->webAuthnCredentials()->whereKeyNot($except)->whereEnabled()->update(['disabled_at' => Date::now()]); } } diff --git a/tests/WebAuthnAuthenticationTest.php b/tests/WebAuthnAuthenticationTest.php new file mode 100644 index 0000000..fc688ab --- /dev/null +++ b/tests/WebAuthnAuthenticationTest.php @@ -0,0 +1,260 @@ +user = Stubs\WebAuthnAuthenticatableUser::forceCreate([ + 'name' => FakeAuthenticator::ATTESTATION_USER['displayName'], + 'email' => FakeAuthenticator::ATTESTATION_USER['name'], + 'password' => 'test_password', + ]); + + $this->user->webAuthnCredentials()->make()->forceFill([ + 'id' => 'test_id', + 'user_id' => Uuid::NIL, + 'counter' => 0, + 'rp_id' => 'http://localhost', + 'origin' => 'http://localhost:8000', + 'aaguid' => Uuid::NIL, + 'public_key' => 'test_key', + 'attestation_format' => 'none', + ])->save(); + } + + public function test_shows_webauthn_data(): void + { + static::assertSame([ + 'name' => FakeAuthenticator::ATTESTATION_USER['name'], + 'displayName' => FakeAuthenticator::ATTESTATION_USER['displayName'], + ], $this->user->webAuthnData()); + } + + public function test_flushes_all_credentials(): void + { + $this->user->webAuthnCredentials()->make()->forceFill([ + 'id' => 'test_id_2', + 'user_id' => Uuid::NIL, + 'counter' => 10, + 'rp_id' => 'http://localhost', + 'origin' => 'http://localhost:8000', + 'aaguid' => Uuid::NIL, + 'public_key' => 'test_key', + 'attestation_format' => 'none', + 'disabled_at' => now() + ])->save(); + + $this->user->flushCredentials(); + + $this->assertDatabaseCount(WebAuthnCredential::class, 0); + } + + public function test_flushes_all_credentials_using_loaded_relation(): void + { + $this->user->webAuthnCredentials()->make()->forceFill([ + 'id' => 'test_id_2', + 'user_id' => Uuid::NIL, + 'counter' => 10, + 'rp_id' => 'http://localhost', + 'origin' => 'http://localhost:8000', + 'aaguid' => Uuid::NIL, + 'public_key' => 'test_key', + 'attestation_format' => 'none', + 'disabled_at' => now() + ])->save(); + + $this->user->load('webAuthnCredentials'); + + static::assertCount(2, $this->user->webAuthnCredentials); + + $this->user->flushCredentials(); + + static::assertEmpty($this->user->webAuthnCredentials); + + $this->assertDatabaseCount(WebAuthnCredential::class, 0); + } + + public function test_flushes_all_credentials_except_given_id(): void + { + $this->user->webAuthnCredentials()->make()->forceFill([ + 'id' => 'test_id_2', + 'user_id' => Uuid::NIL, + 'counter' => 10, + 'rp_id' => 'http://localhost', + 'origin' => 'http://localhost:8000', + 'aaguid' => Uuid::NIL, + 'public_key' => 'test_key', + 'attestation_format' => 'none', + 'disabled_at' => now() + ])->save(); + + $this->user->flushCredentials('test_id_2'); + + $this->assertDatabaseCount(WebAuthnCredential::class, 1); + $this->assertDatabaseMissing(WebAuthnCredential::class, [ + 'id' => 'test_id' + ]); + } + + public function test_flushes_all_credentials_using_loaded_relation_except_given_id(): void + { + $this->user->webAuthnCredentials()->make()->forceFill([ + 'id' => 'test_id_2', + 'user_id' => Uuid::NIL, + 'counter' => 10, + 'rp_id' => 'http://localhost', + 'origin' => 'http://localhost:8000', + 'aaguid' => Uuid::NIL, + 'public_key' => 'test_key', + 'attestation_format' => 'none', + 'disabled_at' => now() + ])->save(); + + $this->user->load('webAuthnCredentials'); + + static::assertCount(2, $this->user->webAuthnCredentials); + + $this->user->flushCredentials('test_id_2'); + + static::assertCount(1, $this->user->webAuthnCredentials); + static::assertTrue($this->user->webAuthnCredentials->contains('id', 'test_id_2')); + + $this->assertDatabaseCount(WebAuthnCredential::class, 1); + $this->assertDatabaseMissing(WebAuthnCredential::class, [ + 'id' => 'test_id' + ]); + } + + public function test_disables_all_credentials(): void + { + $this->travelTo(Carbon::now()->startOfSecond()); + + $this->user->webAuthnCredentials()->make()->forceFill([ + 'id' => 'test_id_2', + 'user_id' => Uuid::NIL, + 'counter' => 10, + 'rp_id' => 'http://localhost', + 'origin' => 'http://localhost:8000', + 'aaguid' => Uuid::NIL, + 'public_key' => 'test_key', + 'attestation_format' => 'none', + 'disabled_at' => now()->subMinute() + ])->save(); + + $this->user->disableAllCredentials(); + + $this->assertDatabaseCount(WebAuthnCredential::class, 2); + $this->assertDatabaseHas(WebAuthnCredential::class, [ + 'id' => 'test_id', + 'disabled_at' => now()->toDateTimeString(), + ]); + $this->assertDatabaseHas(WebAuthnCredential::class, [ + 'id' => 'test_id_2', + 'disabled_at' => now()->subMinute()->toDateTimeString(), + ]); + } + + public function test_disables_all_credentials_with_loaded_relation(): void + { + $this->travelTo(Carbon::now()->startOfSecond()); + + $this->user->webAuthnCredentials()->make()->forceFill([ + 'id' => 'test_id_2', + 'user_id' => Uuid::NIL, + 'counter' => 10, + 'rp_id' => 'http://localhost', + 'origin' => 'http://localhost:8000', + 'aaguid' => Uuid::NIL, + 'public_key' => 'test_key', + 'attestation_format' => 'none', + 'disabled_at' => now()->subMinute() + ])->save(); + + $this->user->load('webAuthnCredentials'); + + $this->user->disableAllCredentials(); + + static::assertTrue($this->user->webAuthnCredentials->firstWhere('id', 'test_id')->isDisabled()); + static::assertTrue($this->user->webAuthnCredentials->firstWhere('id', 'test_id_2')->isDisabled()); + + $this->assertDatabaseCount(WebAuthnCredential::class, 2); + $this->assertDatabaseHas(WebAuthnCredential::class, [ + 'id' => 'test_id', + 'disabled_at' => now()->toDateTimeString(), + ]); + $this->assertDatabaseHas(WebAuthnCredential::class, [ + 'id' => 'test_id_2', + 'disabled_at' => now()->subMinute()->toDateTimeString(), + ]); + } + + public function test_disables_all_credentials_except_one(): void + { + $this->travelTo(Carbon::now()->startOfSecond()); + + $this->user->webAuthnCredentials()->make()->forceFill([ + 'id' => 'test_id_2', + 'user_id' => Uuid::NIL, + 'counter' => 10, + 'rp_id' => 'http://localhost', + 'origin' => 'http://localhost:8000', + 'aaguid' => Uuid::NIL, + 'public_key' => 'test_key', + 'attestation_format' => 'none', + ])->save(); + + $this->user->disableAllCredentials('test_id'); + + $this->assertDatabaseCount(WebAuthnCredential::class, 2); + $this->assertDatabaseHas(WebAuthnCredential::class, [ + 'id' => 'test_id', + 'disabled_at' => null, + ]); + $this->assertDatabaseHas(WebAuthnCredential::class, [ + 'id' => 'test_id_2', + 'disabled_at' => now()->toDateTimeString(), + ]); + } + + public function test_disables_all_credentials_with_loaded_relation_except_one(): void + { + $this->travelTo(Carbon::now()->startOfSecond()); + + $this->user->webAuthnCredentials()->make()->forceFill([ + 'id' => 'test_id_2', + 'user_id' => Uuid::NIL, + 'counter' => 10, + 'rp_id' => 'http://localhost', + 'origin' => 'http://localhost:8000', + 'aaguid' => Uuid::NIL, + 'public_key' => 'test_key', + 'attestation_format' => 'none', + ])->save(); + + $this->user->load('webAuthnCredentials'); + + $this->user->disableAllCredentials('test_id_2'); + + static::assertTrue($this->user->webAuthnCredentials->firstWhere('id', 'test_id')->isDisabled()); + static::assertFalse($this->user->webAuthnCredentials->firstWhere('id', 'test_id_2')->isDisabled()); + + $this->assertDatabaseCount(WebAuthnCredential::class, 2); + $this->assertDatabaseHas(WebAuthnCredential::class, [ + 'id' => 'test_id', + 'disabled_at' => now()->toDateTimeString(), + ]); + $this->assertDatabaseHas(WebAuthnCredential::class, [ + 'id' => 'test_id_2', + 'disabled_at' => null, + ]); + } +}