Skip to content

Commit 6cb08b5

Browse files
committed
Add signal classes and denormalizers for WebAuthn events
Introduced `Signal` interface along with three implementations: `AllAcceptedCredentials`, `CurrentUserDetails`, and `UnknownCredential`. Added corresponding denormalizers to handle serialization and deserialization of these events, updating `WebauthnSerializerFactory` to register them. Extended `SerializerTest` to validate proper functionality with new test cases.
1 parent a218bc9 commit 6cb08b5

File tree

9 files changed

+286
-0
lines changed

9 files changed

+286
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webauthn\Denormalizer;
6+
7+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
8+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
9+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
10+
use Webauthn\PublicKeyCredentialDescriptor;
11+
use Webauthn\Signal\AllAcceptedCredentials;
12+
use function assert;
13+
14+
class SignalAllAcceptedCredentialsDenormalizer implements NormalizerInterface, NormalizerAwareInterface
15+
{
16+
use NormalizerAwareTrait;
17+
18+
/**
19+
* @return array<class-string, bool>
20+
*/
21+
public function getSupportedTypes(?string $format): array
22+
{
23+
return [
24+
AllAcceptedCredentials::class => true,
25+
];
26+
}
27+
28+
/**
29+
* @return array<string, mixed>
30+
*/
31+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
32+
{
33+
assert($data instanceof AllAcceptedCredentials);
34+
35+
$normalized_rp = $this->normalizer->normalize($data->rp, $format, $context);
36+
37+
$normalized_user = $this->normalizer->normalize($data->user, $format, $context);
38+
39+
$normalized_credentials = array_map(function (PublicKeyCredentialDescriptor $credential) use (
40+
$format,
41+
$context
42+
) {
43+
return $this->normalizer->normalize($credential, $format, $context);
44+
}, $data->allAcceptedCredentials);
45+
46+
return [
47+
'rpId' => $normalized_rp['id'],
48+
'userId' => $normalized_user['id'],
49+
'allAcceptedCredentialIds' => array_map(
50+
fn (array $credential): string => $credential['id'],
51+
$normalized_credentials
52+
),
53+
];
54+
}
55+
56+
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
57+
{
58+
return $data instanceof AllAcceptedCredentials;
59+
}
60+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webauthn\Denormalizer;
6+
7+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
8+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
9+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
10+
use Webauthn\Signal\CurrentUserDetails;
11+
use function assert;
12+
13+
class SignalCurrentUserDetailsDenormalizer implements NormalizerInterface, NormalizerAwareInterface
14+
{
15+
use NormalizerAwareTrait;
16+
17+
/**
18+
* @return array<class-string, bool>
19+
*/
20+
public function getSupportedTypes(?string $format): array
21+
{
22+
return [
23+
CurrentUserDetails::class => true,
24+
];
25+
}
26+
27+
/**
28+
* @return array<string, mixed>
29+
*/
30+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
31+
{
32+
assert($data instanceof CurrentUserDetails);
33+
34+
$normalized_rp = $this->normalizer->normalize($data->rp, $format, $context);
35+
36+
$normalized_user = $this->normalizer->normalize($data->user, $format, $context);
37+
38+
return [
39+
'rpId' => $normalized_rp['id'],
40+
'userId' => $normalized_user['id'],
41+
'name' => $normalized_user['name'],
42+
'displayName' => $normalized_user['displayName'],
43+
];
44+
}
45+
46+
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
47+
{
48+
return $data instanceof CurrentUserDetails;
49+
}
50+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webauthn\Denormalizer;
6+
7+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
8+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
9+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
10+
use Webauthn\Signal\UnknownCredential;
11+
use function assert;
12+
13+
class SignalUnknownCredentialDenormalizer implements NormalizerInterface, NormalizerAwareInterface
14+
{
15+
use NormalizerAwareTrait;
16+
17+
/**
18+
* @return array<class-string, bool>
19+
*/
20+
public function getSupportedTypes(?string $format): array
21+
{
22+
return [
23+
UnknownCredential::class => true,
24+
];
25+
}
26+
27+
/**
28+
* @return array<string, mixed>
29+
*/
30+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
31+
{
32+
assert($data instanceof UnknownCredential);
33+
34+
$normalized_rp = $this->normalizer->normalize($data->rp, $format, $context);
35+
36+
$normalized_credential = $this->normalizer->normalize($data->credential, $format, $context);
37+
38+
return [
39+
'rpId' => $normalized_rp['id'],
40+
'credentialId' => $normalized_credential['id'],
41+
];
42+
}
43+
44+
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
45+
{
46+
return $data instanceof UnknownCredential;
47+
}
48+
}

src/webauthn/src/Denormalizer/WebauthnSerializerFactory.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ public function create(): SerializerInterface
6060
new PublicKeyCredentialOptionsDenormalizer(),
6161
new PublicKeyCredentialSourceDenormalizer(),
6262
new PublicKeyCredentialUserEntityDenormalizer(),
63+
new SignalAllAcceptedCredentialsDenormalizer(),
64+
new SignalCurrentUserDetailsDenormalizer(),
65+
new SignalUnknownCredentialDenormalizer(),
6366
new TrustPathDenormalizer(),
6467
new UidNormalizer(),
6568
new ArrayDenormalizer(),
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webauthn\Signal;
6+
7+
use Webauthn\PublicKeyCredentialDescriptor;
8+
use Webauthn\PublicKeyCredentialRpEntity;
9+
use Webauthn\PublicKeyCredentialUserEntity;
10+
11+
readonly class AllAcceptedCredentials implements Signal
12+
{
13+
/**
14+
* @param PublicKeyCredentialDescriptor[] $allAcceptedCredentials
15+
*/
16+
public function __construct(
17+
public PublicKeyCredentialRpEntity $rp,
18+
public PublicKeyCredentialUserEntity $user,
19+
public array $allAcceptedCredentials,
20+
) {
21+
}
22+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webauthn\Signal;
6+
7+
use Webauthn\PublicKeyCredentialRpEntity;
8+
use Webauthn\PublicKeyCredentialUserEntity;
9+
10+
readonly class CurrentUserDetails implements Signal
11+
{
12+
public function __construct(
13+
public PublicKeyCredentialRpEntity $rp,
14+
public PublicKeyCredentialUserEntity $user,
15+
) {
16+
}
17+
}

src/webauthn/src/Signal/Signal.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webauthn\Signal;
6+
7+
interface Signal
8+
{
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webauthn\Signal;
6+
7+
use Webauthn\PublicKeyCredentialDescriptor;
8+
use Webauthn\PublicKeyCredentialRpEntity;
9+
10+
readonly class UnknownCredential implements Signal
11+
{
12+
public function __construct(
13+
public PublicKeyCredentialRpEntity $rp,
14+
public PublicKeyCredentialDescriptor $credential,
15+
) {
16+
}
17+
}

tests/library/Unit/SerializerTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
1111
use Webauthn\AuthenticatorSelectionCriteria;
1212
use Webauthn\PublicKeyCredentialCreationOptions;
13+
use Webauthn\PublicKeyCredentialDescriptor;
1314
use Webauthn\PublicKeyCredentialParameters;
1415
use Webauthn\PublicKeyCredentialRpEntity;
1516
use Webauthn\PublicKeyCredentialUserEntity;
17+
use Webauthn\Signal\AllAcceptedCredentials;
18+
use Webauthn\Signal\CurrentUserDetails;
19+
use Webauthn\Signal\UnknownCredential;
1620
use Webauthn\Tests\AbstractTestCase;
1721
use Webauthn\TrustPath\CertificateTrustPath;
1822
use Webauthn\TrustPath\EmptyTrustPath;
@@ -134,4 +138,60 @@ public function theCredentialCanBeDeserialized(): void
134138
$json,
135139
);
136140
}
141+
142+
#[Test]
143+
public function itSerializesSignalUnknownCredential(): void
144+
{
145+
$rp = new PublicKeyCredentialRpEntity('Example.com', 'rp.example.com');
146+
$credential = new PublicKeyCredentialDescriptor(
147+
PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
148+
'cred-123',
149+
[]
150+
);
151+
$signal = new UnknownCredential($rp, $credential);
152+
153+
$serializer = $this->getSerializer();
154+
$json = $serializer->serialize($signal, 'json');
155+
static::assertJsonStringEqualsJsonString('{"rpId":"rp.example.com","credentialId":"Y3JlZC0xMjM"}', $json);
156+
}
157+
158+
#[Test]
159+
public function itSerializesSignalAllAcceptedCredentials(): void
160+
{
161+
$rp = new PublicKeyCredentialRpEntity('Example.com', 'rp.example.com');
162+
$user = new PublicKeyCredentialUserEntity('john.doe', 'user-1', 'John Doe');
163+
$cred1 = new PublicKeyCredentialDescriptor(
164+
PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
165+
'cred-1',
166+
[]
167+
);
168+
$cred2 = new PublicKeyCredentialDescriptor(
169+
PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
170+
'cred-2',
171+
[]
172+
);
173+
$signal = new AllAcceptedCredentials($rp, $user, [$cred1, $cred2]);
174+
175+
$serializer = $this->getSerializer();
176+
$json = $serializer->serialize($signal, 'json');
177+
static::assertJsonStringEqualsJsonString(
178+
'{"rpId":"rp.example.com","userId":"dXNlci0x","allAcceptedCredentialIds":["Y3JlZC0x","Y3JlZC0y"]}',
179+
$json
180+
);
181+
}
182+
183+
#[Test]
184+
public function itSerializesSignalCurrentUserDetails(): void
185+
{
186+
$rp = new PublicKeyCredentialRpEntity('Example.com', 'rp.example.com');
187+
$user = new PublicKeyCredentialUserEntity('john.doe', 'user-1', 'John Doe');
188+
$signal = new CurrentUserDetails($rp, $user);
189+
190+
$serializer = $this->getSerializer();
191+
$json = $serializer->serialize($signal, 'json');
192+
static::assertJsonStringEqualsJsonString(
193+
'{"rpId":"rp.example.com","userId":"dXNlci0x","name":"john.doe","displayName":"John Doe"}',
194+
$json
195+
);
196+
}
137197
}

0 commit comments

Comments
 (0)