Skip to content

Commit

Permalink
Add normalization support to denormalizers (#604)
Browse files Browse the repository at this point in the history
This update enhances several denormalizer classes in the WebAuthn package (AuthenticationExtensionsDenormalizer, PublicKeyCredentialOptionsDenormalizer, PublicKeyCredentialUserEntityDenormalizer, TrustPathDenormalizer) to also support normalization. This allows backward conversion from entities back to arrays. A new Serializer unit test has been introduced for validation.
  • Loading branch information
Spomky authored Jun 30, 2024
1 parent 614d5a6 commit 1430166
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 12 deletions.
23 changes: 18 additions & 5 deletions src/Denormalizer/AuthenticationExtensionsDenormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

namespace Webauthn\Denormalizer;

use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Webauthn\AuthenticationExtensions\AuthenticationExtension;
use Webauthn\AuthenticationExtensions\AuthenticationExtensions;
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
Expand All @@ -16,10 +15,8 @@
use function is_array;
use function is_string;

final class AuthenticationExtensionsDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface
final class AuthenticationExtensionsDenormalizer implements DenormalizerInterface, NormalizerInterface
{
use DenormalizerAwareTrait;

public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed
{
if ($data instanceof AuthenticationExtensions) {
Expand Down Expand Up @@ -60,4 +57,20 @@ public function getSupportedTypes(?string $format): array
AuthenticationExtensionsClientOutputs::class => true,
];
}

public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
assert($data instanceof AuthenticationExtensions);
$extensions = [];
foreach ($data->extensions as $extension) {
$extensions[$extension->name] = $extension->value;
}

return $extensions;
}

public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
{
return $data instanceof AuthenticationExtensions;
}
}
62 changes: 61 additions & 1 deletion src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Webauthn\AuthenticationExtensions\AuthenticationExtensions;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialCreationOptions;
Expand All @@ -18,11 +21,13 @@
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialUserEntity;
use function array_key_exists;
use function assert;
use function in_array;

final class PublicKeyCredentialOptionsDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface
final class PublicKeyCredentialOptionsDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface, NormalizerInterface, NormalizerAwareInterface
{
use DenormalizerAwareTrait;
use NormalizerAwareTrait;

public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed
{
Expand Down Expand Up @@ -107,6 +112,11 @@ public function supportsDenormalization(mixed $data, string $type, string $forma
);
}

public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
{
return $data instanceof PublicKeyCredentialCreationOptions || $data instanceof PublicKeyCredentialRequestOptions;
}

/**
* @return array<class-string, bool>
*/
Expand All @@ -117,4 +127,54 @@ public function getSupportedTypes(?string $format): array
PublicKeyCredentialRequestOptions::class => true,
];
}

public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
assert(
$data instanceof PublicKeyCredentialCreationOptions || $data instanceof PublicKeyCredentialRequestOptions
);
$json = [
'challenge' => Base64UrlSafe::encodeUnpadded($data->challenge),
'timeout' => $data->timeout,
'extensions' => $this->normalizer->normalize($data->extensions, $format, $context),
];

if ($data instanceof PublicKeyCredentialCreationOptions) {
$json = [
...$json,
'rp' => $this->normalizer->normalize($data->rp, PublicKeyCredentialRpEntity::class, $context),
'user' => $this->normalizer->normalize($data->user, PublicKeyCredentialUserEntity::class, $context),
'pubKeyCredParams' => $this->normalizer->normalize(
$data->pubKeyCredParams,
PublicKeyCredentialParameters::class . '[]',
$context
),
'authenticatorSelection' => $data->authenticatorSelection === null ? null : $this->normalizer->normalize(
$data->authenticatorSelection,
AuthenticatorSelectionCriteria::class,
$context
),
'attestation' => $data->attestation,
'excludeCredentials' => $this->normalizer->normalize(
$data->excludeCredentials,
PublicKeyCredentialDescriptor::class . '[]',
$context
),
];
}
if ($data instanceof PublicKeyCredentialRequestOptions) {
$json = [
...$json,
'rpId' => $data->rpId,
'allowCredentials' => $this->normalizer->normalize(
$data->allowCredentials,
PublicKeyCredentialDescriptor::class . '[]',
$context
),
'userVerification' => $data->userVerification,
];
}

return array_filter($json, static fn ($value) => $value !== null && $value !== []);
}
}
27 changes: 22 additions & 5 deletions src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@

namespace Webauthn\Denormalizer;

use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use ParagonIE\ConstantTime\Base64UrlSafe;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Webauthn\PublicKeyCredentialUserEntity;
use Webauthn\Util\Base64;
use function array_key_exists;
use function assert;

final class PublicKeyCredentialUserEntityDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface
final class PublicKeyCredentialUserEntityDenormalizer implements DenormalizerInterface, NormalizerInterface
{
use DenormalizerAwareTrait;

public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed
{
if (! array_key_exists('id', $data)) {
Expand Down Expand Up @@ -44,4 +43,22 @@ public function getSupportedTypes(?string $format): array
PublicKeyCredentialUserEntity::class => true,
];
}

public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
assert($data instanceof PublicKeyCredentialUserEntity);
$normalized = [
'id' => Base64UrlSafe::encodeUnpadded($data->id),
'name' => $data->name,
'displayName' => $data->displayName,
'icon' => $data->icon,
];

return array_filter($normalized, fn ($value) => $value !== null);
}

public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
{
return $data instanceof PublicKeyCredentialUserEntity;
}
}
24 changes: 23 additions & 1 deletion src/Denormalizer/TrustPathDenormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
namespace Webauthn\Denormalizer;

use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Webauthn\Exception\InvalidTrustPathException;
use Webauthn\TrustPath\CertificateTrustPath;
use Webauthn\TrustPath\EcdaaKeyIdTrustPath;
use Webauthn\TrustPath\EmptyTrustPath;
use Webauthn\TrustPath\TrustPath;
use function array_key_exists;
use function assert;

final class TrustPathDenormalizer implements DenormalizerInterface
final class TrustPathDenormalizer implements DenormalizerInterface, NormalizerInterface
{
public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed
{
Expand All @@ -38,4 +40,24 @@ public function getSupportedTypes(?string $format): array
TrustPath::class => true,
];
}

public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
assert($data instanceof TrustPath);
return match (true) {
$data instanceof EcdaaKeyIdTrustPath => [
'ecdaaKeyId' => $data->getEcdaaKeyId(),
],
$data instanceof CertificateTrustPath => [
'x5c' => $data->certificates,
],
$data instanceof EmptyTrustPath => [],
default => throw new InvalidTrustPathException('Unsupported trust path type'),
};
}

public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
{
return $data instanceof TrustPath;
}
}

0 comments on commit 1430166

Please sign in to comment.