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 82b5cd3 commit 46f67f9
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 12 deletions.
60 changes: 60 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2437,11 +2437,26 @@ parameters:
count: 1
path: src/webauthn/src/Denormalizer/AuthenticationExtensionsDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\AuthenticationExtensionsDenormalizer\\:\\:normalize\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/AuthenticationExtensionsDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\AuthenticationExtensionsDenormalizer\\:\\:normalize\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/AuthenticationExtensionsDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\AuthenticationExtensionsDenormalizer\\:\\:supportsDenormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/AuthenticationExtensionsDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\AuthenticationExtensionsDenormalizer\\:\\:supportsNormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/AuthenticationExtensionsDenormalizer.php

-
message: "#^Cannot access offset 'attestationObject' on mixed\\.$#"
count: 1
Expand Down Expand Up @@ -2752,11 +2767,26 @@ parameters:
count: 1
path: src/webauthn/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialOptionsDenormalizer\\:\\:normalize\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialOptionsDenormalizer\\:\\:normalize\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialOptionsDenormalizer\\:\\:supportsDenormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialOptionsDenormalizer\\:\\:supportsNormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php

-
message: "#^Parameter \\#1 \\$challenge of static method Webauthn\\\\PublicKeyCredentialRequestOptions\\:\\:create\\(\\) expects string, mixed given\\.$#"
count: 1
Expand Down Expand Up @@ -3012,11 +3042,26 @@ parameters:
count: 1
path: src/webauthn/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialUserEntityDenormalizer\\:\\:normalize\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialUserEntityDenormalizer\\:\\:normalize\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialUserEntityDenormalizer\\:\\:supportsDenormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialUserEntityDenormalizer\\:\\:supportsNormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php

-
message: "#^Parameter \\#2 \\$array of function array_key_exists expects array, mixed given\\.$#"
count: 1
Expand All @@ -3040,11 +3085,26 @@ parameters:
count: 1
path: src/webauthn/src/Denormalizer/TrustPathDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\TrustPathDenormalizer\\:\\:normalize\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/TrustPathDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\TrustPathDenormalizer\\:\\:normalize\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/TrustPathDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\TrustPathDenormalizer\\:\\:supportsDenormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/TrustPathDenormalizer.php

-
message: "#^Method Webauthn\\\\Denormalizer\\\\TrustPathDenormalizer\\:\\:supportsNormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/webauthn/src/Denormalizer/TrustPathDenormalizer.php

-
message: "#^Parameter \\#1 \\$ecdaaKeyId of class Webauthn\\\\TrustPath\\\\EcdaaKeyIdTrustPath constructor expects string, array given\\.$#"
count: 1
Expand Down
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;
}
}
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 !== []);
}
}
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/webauthn/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;
}
}
Loading

0 comments on commit 46f67f9

Please sign in to comment.