From abd4ca4d0bfeb0ebd70b18971907481c667ef718 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Sat, 6 Jul 2024 21:20:03 +0200 Subject: [PATCH] Add new denormalizers and deprecate old JSON methods (#618) * Add new denormalizers and deprecate old JSON methods Added new Denormalizer classes for AttestedCredentialData, AuthenticationExtensions, and others. Deprecated JSON serialization methods in several classes, scheduled to be removed in version 5.0. Now serialization relies fully on the Symfony Serializer component. Various minor amendments in other classes to support these changes. --- phpstan-baseline.neon | 87 +++++++++++++++++-- .../src/DataCollector/WebauthnCollector.php | 57 +++++++++--- .../src/Resources/config/dev_services.php | 2 + src/symfony/src/Resources/config/security.php | 6 +- src/symfony/src/Resources/config/services.php | 18 ++++ .../Handler/DefaultCreationOptionsHandler.php | 18 ++-- .../Handler/DefaultRequestOptionsHandler.php | 18 ++-- .../AttestationStatement.php | 6 ++ src/webauthn/src/AttestedCredentialData.php | 6 ++ .../AuthenticationExtension.php | 6 ++ .../AuthenticationExtensions.php | 6 ++ .../src/AuthenticatorSelectionCriteria.php | 6 ++ .../AttestedCredentialDataNormalizer.php | 42 +++++++++ .../AuthenticationExtensionNormalizer.php | 34 ++++++++ ...enticatorAssertionResponseDenormalizer.php | 4 +- ...ublicKeyCredentialDescriptorNormalizer.php | 44 ++++++++++ ...PublicKeyCredentialOptionsDenormalizer.php | 24 +++-- .../PublicKeyCredentialSourceDenormalizer.php | 35 +++++++- .../WebauthnSerializerFactory.php | 3 + src/webauthn/src/PublicKeyCredential.php | 2 +- .../PublicKeyCredentialCreationOptions.php | 6 ++ .../src/PublicKeyCredentialDescriptor.php | 6 ++ .../src/PublicKeyCredentialParameters.php | 6 ++ .../src/PublicKeyCredentialRequestOptions.php | 6 ++ .../src/PublicKeyCredentialRpEntity.php | 6 ++ .../src/PublicKeyCredentialSource.php | 10 ++- .../src/PublicKeyCredentialUserEntity.php | 6 ++ .../src/TrustPath/CertificateTrustPath.php | 6 ++ src/webauthn/src/TrustPath/EmptyTrustPath.php | 6 ++ .../src/TrustPath/TrustPathLoader.php | 27 +++--- tests/library/AbstractTestCase.php | 9 ++ tests/library/Functional/AssertionTest.php | 17 ++++ .../Unit/AttestedCredentialDataTest.php | 12 ++- ...ationExtensionsClientOutputsLoaderTest.php | 10 ++- .../AuthenticationExtensionsClientTest.php | 18 ++-- .../AuthenticatorSelectionCriteriaTest.php | 32 +++++-- tests/library/Unit/EntityTest.php | 17 ++-- ...PublicKeyCredentialCreationOptionsTest.php | 28 ++++-- .../PublicKeyCredentialDescriptorTest.php | 11 ++- .../PublicKeyCredentialParametersTest.php | 10 ++- .../PublicKeyCredentialRequestOptionsTest.php | 25 ++++-- .../Unit/PublicKeyCredentialSourceTest.php | 52 +++++++++-- .../library/Unit/TrustPath/TrustPathTest.php | 6 +- .../AdditionalAuthenticatorTest.php | 1 - .../Firewall/RegistrationAreaTest.php | 4 +- 45 files changed, 626 insertions(+), 135 deletions(-) create mode 100644 src/webauthn/src/Denormalizer/AttestedCredentialDataNormalizer.php create mode 100644 src/webauthn/src/Denormalizer/AuthenticationExtensionNormalizer.php create mode 100644 src/webauthn/src/Denormalizer/PublicKeyCredentialDescriptorNormalizer.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index bc26eb02..ac93686a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2421,6 +2421,41 @@ parameters: count: 1 path: src/webauthn/src/Denormalizer/AttestationStatementDenormalizer.php + - + message: "#^Method Webauthn\\\\Denormalizer\\\\AttestedCredentialDataNormalizer\\:\\:normalize\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/AttestedCredentialDataNormalizer.php + + - + message: "#^Method Webauthn\\\\Denormalizer\\\\AttestedCredentialDataNormalizer\\:\\:normalize\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/AttestedCredentialDataNormalizer.php + + - + message: "#^Method Webauthn\\\\Denormalizer\\\\AttestedCredentialDataNormalizer\\:\\:supportsNormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/AttestedCredentialDataNormalizer.php + + - + message: "#^Method Webauthn\\\\Denormalizer\\\\AuthenticationExtensionNormalizer\\:\\:normalize\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/AuthenticationExtensionNormalizer.php + + - + message: "#^Method Webauthn\\\\Denormalizer\\\\AuthenticationExtensionNormalizer\\:\\:normalize\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/AuthenticationExtensionNormalizer.php + + - + message: "#^Method Webauthn\\\\Denormalizer\\\\AuthenticationExtensionNormalizer\\:\\:normalize\\(\\) should return array but returns mixed\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/AuthenticationExtensionNormalizer.php + + - + message: "#^Method Webauthn\\\\Denormalizer\\\\AuthenticationExtensionNormalizer\\:\\:supportsNormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/AuthenticationExtensionNormalizer.php + - message: """ #^Fetching class constant class of deprecated class Webauthn\\\\AuthenticationExtensions\\\\AuthenticationExtensionsClientInputs\\: @@ -2474,7 +2509,7 @@ parameters: - message: "#^Cannot access offset 'clientDataJSON' on mixed\\.$#" - count: 3 + count: 2 path: src/webauthn/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php - @@ -2484,7 +2519,7 @@ parameters: - message: "#^Cannot access offset 'userHandle' on mixed\\.$#" - count: 3 + count: 1 path: src/webauthn/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php - @@ -2522,11 +2557,6 @@ parameters: count: 1 path: src/webauthn/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php - - - message: "#^Parameter \\#4 \\$userHandle of static method Webauthn\\\\AuthenticatorAssertionResponse\\:\\:create\\(\\) expects string\\|null, mixed given\\.$#" - count: 1 - path: src/webauthn/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php - - message: "#^Parameter \\#5 \\$attestationObject of static method Webauthn\\\\AuthenticatorAssertionResponse\\:\\:create\\(\\) expects Webauthn\\\\AttestationStatement\\\\AttestationObject\\|null, mixed given\\.$#" count: 1 @@ -2687,6 +2717,21 @@ parameters: count: 1 path: src/webauthn/src/Denormalizer/PublicKeyCredentialDenormalizer.php + - + message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialDescriptorNormalizer\\:\\:normalize\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/PublicKeyCredentialDescriptorNormalizer.php + + - + message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialDescriptorNormalizer\\:\\:normalize\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/PublicKeyCredentialDescriptorNormalizer.php + + - + message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialDescriptorNormalizer\\:\\:supportsNormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/PublicKeyCredentialDescriptorNormalizer.php + - message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" count: 1 @@ -2962,11 +3007,26 @@ parameters: count: 1 path: src/webauthn/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php + - + message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialSourceDenormalizer\\:\\:normalize\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php + + - + message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialSourceDenormalizer\\:\\:normalize\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php + - message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialSourceDenormalizer\\:\\:supportsDenormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" count: 1 path: src/webauthn/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php + - + message: "#^Method Webauthn\\\\Denormalizer\\\\PublicKeyCredentialSourceDenormalizer\\:\\:supportsNormalization\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/webauthn/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php + - message: "#^Parameter \\#1 \\$data of static method Webauthn\\\\Util\\\\Base64\\:\\:decode\\(\\) expects string, mixed given\\.$#" count: 1 @@ -3292,3 +3352,16 @@ parameters: message: "#^Parameter \\#1 \\$ecdaaKeyId of class Webauthn\\\\TrustPath\\\\EcdaaKeyIdTrustPath constructor expects string, mixed given\\.$#" count: 1 path: src/webauthn/src/TrustPath/EcdaaKeyIdTrustPath.php + + - + message: """ + #^Instantiation of deprecated class Webauthn\\\\TrustPath\\\\EcdaaKeyIdTrustPath\\: + since 4\\.2\\.0 and will be removed in 5\\.0\\.0\\. The ECDAA Trust Anchor does no longer exist in Webauthn specification\\.$# + """ + count: 1 + path: src/webauthn/src/TrustPath/TrustPathLoader.php + + - + message: "#^Parameter \\#1 \\$certificates of static method Webauthn\\\\TrustPath\\\\CertificateTrustPath\\:\\:create\\(\\) expects array\\, array given\\.$#" + count: 1 + path: src/webauthn/src/TrustPath/TrustPathLoader.php diff --git a/src/symfony/src/DataCollector/WebauthnCollector.php b/src/symfony/src/DataCollector/WebauthnCollector.php index 3f4d3d73..2f7c4366 100644 --- a/src/symfony/src/DataCollector/WebauthnCollector.php +++ b/src/symfony/src/DataCollector/WebauthnCollector.php @@ -8,6 +8,10 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\Serializer\Encoder\JsonEncode; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; +use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Cloner\VarCloner; use Throwable; @@ -52,6 +56,11 @@ class WebauthnCollector extends DataCollector implements EventSubscriberInterfac */ private array $authenticatorAssertionResponseValidationFailed = []; + public function __construct( + private readonly SerializerInterface $serializer + ) { + } + public function collect(Request $request, Response $response, ?Throwable $exception = null): void { $this->data = [ @@ -110,9 +119,13 @@ public function addPublicKeyCredentialCreationOptions(PublicKeyCredentialCreatio $cloner = new VarCloner(); $this->publicKeyCredentialCreationOptions[] = [ 'options' => $cloner->cloneVar($event->publicKeyCredentialCreationOptions), - 'json' => json_encode( + 'json' => $this->serializer->serialize( $event->publicKeyCredentialCreationOptions, - JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT + JsonEncoder::FORMAT, + [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + JsonEncode::OPTIONS => JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT, + ] ), ]; } @@ -124,9 +137,13 @@ public function addAuthenticatorAttestationResponseValidationSucceeded( $this->authenticatorAttestationResponseValidationSucceeded[] = [ 'attestation_response' => $cloner->cloneVar($event->authenticatorAttestationResponse), 'options' => $cloner->cloneVar($event->publicKeyCredentialCreationOptions), - 'options_json' => json_encode( + 'options_json' => $this->serializer->serialize( $event->publicKeyCredentialCreationOptions, - JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT + JsonEncoder::FORMAT, + [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + JsonEncode::OPTIONS => JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT, + ] ), 'credential_source' => $cloner->cloneVar($event->publicKeyCredentialSource), ]; @@ -139,9 +156,13 @@ public function addAuthenticatorAttestationResponseValidationFailed( $this->authenticatorAttestationResponseValidationFailed[] = [ 'attestation_response' => $cloner->cloneVar($event->authenticatorAttestationResponse), 'options' => $cloner->cloneVar($event->publicKeyCredentialCreationOptions), - 'options_json' => json_encode( + 'options_json' => $this->serializer->serialize( $event->publicKeyCredentialCreationOptions, - JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT + JsonEncoder::FORMAT, + [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + JsonEncode::OPTIONS => JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT, + ] ), 'exception' => $cloner->cloneVar($event->throwable), ]; @@ -152,9 +173,13 @@ public function addPublicKeyCredentialRequestOptions(PublicKeyCredentialRequestO $cloner = new VarCloner(); $this->publicKeyCredentialRequestOptions[] = [ 'options' => $cloner->cloneVar($event->publicKeyCredentialRequestOptions), - 'json' => json_encode( + 'json' => $this->serializer->serialize( $event->publicKeyCredentialRequestOptions, - JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT + JsonEncoder::FORMAT, + [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + JsonEncode::OPTIONS => JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT, + ] ), ]; } @@ -168,9 +193,13 @@ public function addAuthenticatorAssertionResponseValidationSucceeded( 'credential_id' => $cloner->cloneVar($event->credentialId), 'assertion_response' => $cloner->cloneVar($event->authenticatorAssertionResponse), 'options' => $cloner->cloneVar($event->publicKeyCredentialRequestOptions), - 'options_json' => json_encode( + 'options_json' => $this->serializer->serialize( $event->publicKeyCredentialRequestOptions, - JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT + JsonEncoder::FORMAT, + [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + JsonEncode::OPTIONS => JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT, + ] ), 'credential_source' => $cloner->cloneVar($event->publicKeyCredentialSource), ]; @@ -185,9 +214,13 @@ public function addAuthenticatorAssertionResponseValidationFailed( 'credential_id' => $cloner->cloneVar($event->getCredential()?->publicKeyCredentialId), 'assertion_response' => $cloner->cloneVar($event->authenticatorAssertionResponse), 'options' => $cloner->cloneVar($event->publicKeyCredentialRequestOptions), - 'options_json' => json_encode( + 'options_json' => $this->serializer->serialize( $event->publicKeyCredentialRequestOptions, - JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT + JsonEncoder::FORMAT, + [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + JsonEncode::OPTIONS => JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT, + ] ), 'exception' => $cloner->cloneVar($event->throwable), ]; diff --git a/src/symfony/src/Resources/config/dev_services.php b/src/symfony/src/Resources/config/dev_services.php index a472ffcd..2833b492 100644 --- a/src/symfony/src/Resources/config/dev_services.php +++ b/src/symfony/src/Resources/config/dev_services.php @@ -5,6 +5,7 @@ namespace Webauthn\Bundle\DataCollector; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; return static function (ContainerConfigurator $container): void { $container = $container->services() @@ -13,6 +14,7 @@ ->autoconfigure(); $container->set(WebauthnCollector::class) + ->args([service('serializer')]) ->tag('data_collector', [ 'id' => 'webauthn_collector', 'template' => '@Webauthn/data_collector/template.html.twig', diff --git a/src/symfony/src/Resources/config/security.php b/src/symfony/src/Resources/config/security.php index 3e7c2ce1..2f664f75 100644 --- a/src/symfony/src/Resources/config/security.php +++ b/src/symfony/src/Resources/config/security.php @@ -36,8 +36,10 @@ $container->set(DefaultFailureHandler::class); $container->set(SessionStorage::class)->args([service('request_stack')]); $container->set(CacheStorage::class)->args([service(CacheItemPoolInterface::class)]); - $container->set(DefaultCreationOptionsHandler::class); - $container->set(DefaultRequestOptionsHandler::class); + $container->set(DefaultCreationOptionsHandler::class) + ->args([service('serializer')]); + $container->set(DefaultRequestOptionsHandler::class) + ->args([service('serializer')]); $container ->set(WebauthnFactory::AUTHENTICATOR_DEFINITION_ID, WebauthnAuthenticator::class) ->abstract() diff --git a/src/symfony/src/Resources/config/services.php b/src/symfony/src/Resources/config/services.php index 45e93941..7f337d61 100644 --- a/src/symfony/src/Resources/config/services.php +++ b/src/symfony/src/Resources/config/services.php @@ -32,6 +32,8 @@ use Webauthn\Counter\ThrowExceptionIfInvalid; use Webauthn\Denormalizer\AttestationObjectDenormalizer; use Webauthn\Denormalizer\AttestationStatementDenormalizer; +use Webauthn\Denormalizer\AttestedCredentialDataNormalizer; +use Webauthn\Denormalizer\AuthenticationExtensionNormalizer; use Webauthn\Denormalizer\AuthenticationExtensionsDenormalizer; use Webauthn\Denormalizer\AuthenticatorAssertionResponseDenormalizer; use Webauthn\Denormalizer\AuthenticatorAttestationResponseDenormalizer; @@ -39,6 +41,7 @@ use Webauthn\Denormalizer\AuthenticatorResponseDenormalizer; use Webauthn\Denormalizer\CollectedClientDataDenormalizer; use Webauthn\Denormalizer\PublicKeyCredentialDenormalizer; +use Webauthn\Denormalizer\PublicKeyCredentialDescriptorNormalizer; use Webauthn\Denormalizer\PublicKeyCredentialOptionsDenormalizer; use Webauthn\Denormalizer\PublicKeyCredentialSourceDenormalizer; use Webauthn\Denormalizer\PublicKeyCredentialUserEntityDenormalizer; @@ -209,6 +212,21 @@ ->tag('serializer.normalizer', [ 'priority' => 1024, ]); + $container + ->set(AuthenticationExtensionNormalizer::class) + ->tag('serializer.normalizer', [ + 'priority' => 1024, + ]); + $container + ->set(PublicKeyCredentialDescriptorNormalizer::class) + ->tag('serializer.normalizer', [ + 'priority' => 1024, + ]); + $container + ->set(AttestedCredentialDataNormalizer::class) + ->tag('serializer.normalizer', [ + 'priority' => 1024, + ]); $container ->set(AuthenticationExtensionsDenormalizer::class) ->tag('serializer.normalizer', [ diff --git a/src/symfony/src/Security/Handler/DefaultCreationOptionsHandler.php b/src/symfony/src/Security/Handler/DefaultCreationOptionsHandler.php index b4e5a0c9..6a8fdb93 100644 --- a/src/symfony/src/Security/Handler/DefaultCreationOptionsHandler.php +++ b/src/symfony/src/Security/Handler/DefaultCreationOptionsHandler.php @@ -7,23 +7,27 @@ use RuntimeException; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Webauthn\PublicKeyCredentialCreationOptions; use Webauthn\PublicKeyCredentialUserEntity; use function is_array; -use const JSON_THROW_ON_ERROR; final class DefaultCreationOptionsHandler implements CreationOptionsHandler { + public function __construct( + private readonly NormalizerInterface $normalizer + ) { + } + public function onCreationOptions( PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, PublicKeyCredentialUserEntity $userEntity ): Response { - $data = json_decode( - json_encode($publicKeyCredentialCreationOptions, JSON_THROW_ON_ERROR), - true, - 512, - JSON_THROW_ON_ERROR - ); + $data = $this->normalizer->normalize($publicKeyCredentialCreationOptions, JsonEncoder::FORMAT, [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]); is_array($data) || throw new RuntimeException('Unable to encode the response to JSON.'); $data['status'] = 'ok'; $data['errorMessage'] = ''; diff --git a/src/symfony/src/Security/Handler/DefaultRequestOptionsHandler.php b/src/symfony/src/Security/Handler/DefaultRequestOptionsHandler.php index 3fdbad46..3c790e8e 100644 --- a/src/symfony/src/Security/Handler/DefaultRequestOptionsHandler.php +++ b/src/symfony/src/Security/Handler/DefaultRequestOptionsHandler.php @@ -7,23 +7,27 @@ use RuntimeException; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Webauthn\PublicKeyCredentialRequestOptions; use Webauthn\PublicKeyCredentialUserEntity; use function is_array; -use const JSON_THROW_ON_ERROR; final class DefaultRequestOptionsHandler implements RequestOptionsHandler { + public function __construct( + private readonly NormalizerInterface $normalizer + ) { + } + public function onRequestOptions( PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ?PublicKeyCredentialUserEntity $userEntity ): Response { - $data = json_decode( - json_encode($publicKeyCredentialRequestOptions, JSON_THROW_ON_ERROR), - true, - 512, - JSON_THROW_ON_ERROR - ); + $data = $this->normalizer->normalize($publicKeyCredentialRequestOptions, JsonEncoder::FORMAT, [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]); is_array($data) || throw new RuntimeException('Unable to encode the response to JSON.'); $data['status'] = 'ok'; $data['errorMessage'] = ''; diff --git a/src/webauthn/src/AttestationStatement/AttestationStatement.php b/src/webauthn/src/AttestationStatement/AttestationStatement.php index 6f216783..4f300447 100644 --- a/src/webauthn/src/AttestationStatement/AttestationStatement.php +++ b/src/webauthn/src/AttestationStatement/AttestationStatement.php @@ -174,6 +174,12 @@ public static function createFromArray(array $data): self */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); return [ 'fmt' => $this->fmt, 'attStmt' => $this->attStmt, diff --git a/src/webauthn/src/AttestedCredentialData.php b/src/webauthn/src/AttestedCredentialData.php index a0d589d2..2f5d74f4 100644 --- a/src/webauthn/src/AttestedCredentialData.php +++ b/src/webauthn/src/AttestedCredentialData.php @@ -108,6 +108,12 @@ public static function createFromArray(array $json): self */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); $result = [ 'aaguid' => $this->aaguid->__toString(), 'credentialId' => base64_encode($this->credentialId), diff --git a/src/webauthn/src/AuthenticationExtensions/AuthenticationExtension.php b/src/webauthn/src/AuthenticationExtensions/AuthenticationExtension.php index 3f9ff6a2..3ed9dc0c 100644 --- a/src/webauthn/src/AuthenticationExtensions/AuthenticationExtension.php +++ b/src/webauthn/src/AuthenticationExtensions/AuthenticationExtension.php @@ -39,6 +39,12 @@ public function value(): mixed public function jsonSerialize(): mixed { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); return $this->value; } } diff --git a/src/webauthn/src/AuthenticationExtensions/AuthenticationExtensions.php b/src/webauthn/src/AuthenticationExtensions/AuthenticationExtensions.php index bedabbb5..9d7b2bed 100644 --- a/src/webauthn/src/AuthenticationExtensions/AuthenticationExtensions.php +++ b/src/webauthn/src/AuthenticationExtensions/AuthenticationExtensions.php @@ -109,6 +109,12 @@ public function get(string $key): AuthenticationExtension */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); return $this->extensions; } diff --git a/src/webauthn/src/AuthenticatorSelectionCriteria.php b/src/webauthn/src/AuthenticatorSelectionCriteria.php index 0bbad86f..8e47e92e 100644 --- a/src/webauthn/src/AuthenticatorSelectionCriteria.php +++ b/src/webauthn/src/AuthenticatorSelectionCriteria.php @@ -229,6 +229,12 @@ public static function createFromArray(array $json): self */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); $json = [ 'requireResidentKey' => $this->requireResidentKey, 'userVerification' => $this->userVerification, diff --git a/src/webauthn/src/Denormalizer/AttestedCredentialDataNormalizer.php b/src/webauthn/src/Denormalizer/AttestedCredentialDataNormalizer.php new file mode 100644 index 00000000..b46c57c3 --- /dev/null +++ b/src/webauthn/src/Denormalizer/AttestedCredentialDataNormalizer.php @@ -0,0 +1,42 @@ + $this->normalizer->normalize($data->aaguid, $format, $context), + 'credentialId' => base64_encode($data->credentialId), + ]; + if ($data->credentialPublicKey !== null) { + $result['credentialPublicKey'] = base64_encode($data->credentialPublicKey); + } + + return $result; + } + + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool + { + return $data instanceof AttestedCredentialData; + } + + public function getSupportedTypes(?string $format): array + { + return [ + AttestedCredentialData::class => true, + ]; + } +} diff --git a/src/webauthn/src/Denormalizer/AuthenticationExtensionNormalizer.php b/src/webauthn/src/Denormalizer/AuthenticationExtensionNormalizer.php new file mode 100644 index 00000000..26102f55 --- /dev/null +++ b/src/webauthn/src/Denormalizer/AuthenticationExtensionNormalizer.php @@ -0,0 +1,34 @@ + + */ + public function getSupportedTypes(?string $format): array + { + return [ + AuthenticationExtension::class => true, + ]; + } + + public function normalize(mixed $data, ?string $format = null, array $context = []): array + { + assert($data instanceof AuthenticationExtension); + + return $data->value; + } + + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool + { + return $data instanceof AuthenticationExtension; + } +} diff --git a/src/webauthn/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php b/src/webauthn/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php index 1f017ca2..613331c7 100644 --- a/src/webauthn/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php +++ b/src/webauthn/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php @@ -25,14 +25,14 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $data['clientDataJSON'] = Base64UrlSafe::decodeNoPadding($data['clientDataJSON']); $userHandle = $data['userHandle'] ?? null; if ($userHandle !== '' && $userHandle !== null) { - $data['userHandle'] = Base64::decode($userHandle); + $userHandle = Base64::decode($userHandle); } return AuthenticatorAssertionResponse::create( $this->denormalizer->denormalize($data['clientDataJSON'], CollectedClientData::class, $format, $context), $this->denormalizer->denormalize($data['authenticatorData'], AuthenticatorData::class, $format, $context), $data['signature'], - $data['userHandle'] ?? null, + $userHandle ?? null, ! isset($data['attestationObject']) ? null : $this->denormalizer->denormalize( $data['attestationObject'], AttestationObject::class, diff --git a/src/webauthn/src/Denormalizer/PublicKeyCredentialDescriptorNormalizer.php b/src/webauthn/src/Denormalizer/PublicKeyCredentialDescriptorNormalizer.php new file mode 100644 index 00000000..2accc307 --- /dev/null +++ b/src/webauthn/src/Denormalizer/PublicKeyCredentialDescriptorNormalizer.php @@ -0,0 +1,44 @@ + $data->type, + 'id' => Base64UrlSafe::encodeUnpadded($data->id), + ]; + if (count($data->transports) !== 0) { + $result['transports'] = $data->transports; + } + + return $result; + } + + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool + { + return $data instanceof PublicKeyCredentialDescriptor; + } + + public function getSupportedTypes(?string $format): array + { + return [ + PublicKeyCredentialDescriptor::class => true, + ]; + } +} diff --git a/src/webauthn/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php b/src/webauthn/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php index fc35aff6..06657613 100644 --- a/src/webauthn/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php +++ b/src/webauthn/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php @@ -136,14 +136,18 @@ public function normalize(mixed $data, ?string $format = null, array $context = $json = [ 'challenge' => Base64UrlSafe::encodeUnpadded($data->challenge), 'timeout' => $data->timeout, - 'extensions' => $this->normalizer->normalize($data->extensions, $format, $context), + 'extensions' => $data->extensions->count() === 0 ? null : $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), + 'rp' => $this->normalizer->normalize($data->rp, $format, $context), + 'user' => $this->normalizer->normalize($data->user, $format, $context), 'pubKeyCredParams' => $this->normalizer->normalize( $data->pubKeyCredParams, PublicKeyCredentialParameters::class . '[]', @@ -151,26 +155,18 @@ public function normalize(mixed $data, ?string $format = null, array $context = ), 'authenticatorSelection' => $data->authenticatorSelection === null ? null : $this->normalizer->normalize( $data->authenticatorSelection, - AuthenticatorSelectionCriteria::class, + $format, $context ), 'attestation' => $data->attestation, - 'excludeCredentials' => $this->normalizer->normalize( - $data->excludeCredentials, - PublicKeyCredentialDescriptor::class . '[]', - $context - ), + 'excludeCredentials' => $this->normalizer->normalize($data->excludeCredentials, $format, $context), ]; } if ($data instanceof PublicKeyCredentialRequestOptions) { $json = [ ...$json, 'rpId' => $data->rpId, - 'allowCredentials' => $this->normalizer->normalize( - $data->allowCredentials, - PublicKeyCredentialDescriptor::class . '[]', - $context - ), + 'allowCredentials' => $this->normalizer->normalize($data->allowCredentials, $format, $context), 'userVerification' => $data->userVerification, ]; } diff --git a/src/webauthn/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php b/src/webauthn/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php index df599248..51018d8f 100644 --- a/src/webauthn/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php +++ b/src/webauthn/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php @@ -4,18 +4,24 @@ namespace Webauthn\Denormalizer; +use ParagonIE\ConstantTime\Base64UrlSafe; 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 Symfony\Component\Uid\Uuid; use Webauthn\Exception\InvalidDataException; use Webauthn\PublicKeyCredentialSource; use Webauthn\TrustPath\TrustPath; use Webauthn\Util\Base64; use function array_key_exists; +use function assert; -final class PublicKeyCredentialSourceDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface +final class PublicKeyCredentialSourceDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface, NormalizerInterface, NormalizerAwareInterface { + use NormalizerAwareTrait; use DenormalizerAwareTrait; public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed @@ -57,4 +63,31 @@ public function getSupportedTypes(?string $format): array PublicKeyCredentialSource::class => true, ]; } + + public function normalize(mixed $data, ?string $format = null, array $context = []): array + { + assert($data instanceof PublicKeyCredentialSource); + $result = [ + 'publicKeyCredentialId' => Base64UrlSafe::encodeUnpadded($data->publicKeyCredentialId), + 'type' => $data->type, + 'transports' => $data->transports, + 'attestationType' => $data->attestationType, + 'trustPath' => $this->normalizer->normalize($data->trustPath, $format, $context), + 'aaguid' => $this->normalizer->normalize($data->aaguid, $format, $context), + 'credentialPublicKey' => Base64UrlSafe::encodeUnpadded($data->credentialPublicKey), + 'userHandle' => Base64UrlSafe::encodeUnpadded($data->userHandle), + 'counter' => $data->counter, + 'otherUI' => $data->otherUI, + 'backupEligible' => $data->backupEligible, + 'backupStatus' => $data->backupStatus, + 'uvInitialized' => $data->uvInitialized, + ]; + + return array_filter($result, static fn ($value): bool => $value !== null); + } + + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool + { + return $data instanceof PublicKeyCredentialSource; + } } diff --git a/src/webauthn/src/Denormalizer/WebauthnSerializerFactory.php b/src/webauthn/src/Denormalizer/WebauthnSerializerFactory.php index 304cd035..694039a0 100644 --- a/src/webauthn/src/Denormalizer/WebauthnSerializerFactory.php +++ b/src/webauthn/src/Denormalizer/WebauthnSerializerFactory.php @@ -42,6 +42,9 @@ public function create(): SerializerInterface } $denormalizers = [ + new AuthenticationExtensionNormalizer(), + new PublicKeyCredentialDescriptorNormalizer(), + new AttestedCredentialDataNormalizer(), new AttestationObjectDenormalizer(), new AttestationStatementDenormalizer($this->attestationStatementSupportManager), new AuthenticationExtensionsDenormalizer(), diff --git a/src/webauthn/src/PublicKeyCredential.php b/src/webauthn/src/PublicKeyCredential.php index 778d138b..8e09ad65 100644 --- a/src/webauthn/src/PublicKeyCredential.php +++ b/src/webauthn/src/PublicKeyCredential.php @@ -23,7 +23,7 @@ public function __construct( } /** - * @deprecated since 4.8.0. Please use the PublicKeyCredentialDescriptor ({self::getPublicKeyCredentialDescriptor}) instead. + * @deprecated since 4.8.0. * @infection-ignore-all */ public function __toString(): string diff --git a/src/webauthn/src/PublicKeyCredentialCreationOptions.php b/src/webauthn/src/PublicKeyCredentialCreationOptions.php index 1a20ee5e..d6088e97 100644 --- a/src/webauthn/src/PublicKeyCredentialCreationOptions.php +++ b/src/webauthn/src/PublicKeyCredentialCreationOptions.php @@ -313,6 +313,12 @@ public static function createFromArray(array $json): static */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); $json = [ 'rp' => $this->rp, 'user' => $this->user, diff --git a/src/webauthn/src/PublicKeyCredentialDescriptor.php b/src/webauthn/src/PublicKeyCredentialDescriptor.php index ba38446e..eac25690 100644 --- a/src/webauthn/src/PublicKeyCredentialDescriptor.php +++ b/src/webauthn/src/PublicKeyCredentialDescriptor.php @@ -107,6 +107,12 @@ public static function createFromArray(array $json): self */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); $json = [ 'type' => $this->type, 'id' => Base64UrlSafe::encodeUnpadded($this->id), diff --git a/src/webauthn/src/PublicKeyCredentialParameters.php b/src/webauthn/src/PublicKeyCredentialParameters.php index 62cfa053..1eab10d5 100644 --- a/src/webauthn/src/PublicKeyCredentialParameters.php +++ b/src/webauthn/src/PublicKeyCredentialParameters.php @@ -83,6 +83,12 @@ public static function createFromArray(array $json): self */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); return [ 'type' => $this->type, 'alg' => $this->alg, diff --git a/src/webauthn/src/PublicKeyCredentialRequestOptions.php b/src/webauthn/src/PublicKeyCredentialRequestOptions.php index 4016f34f..a74d97b3 100644 --- a/src/webauthn/src/PublicKeyCredentialRequestOptions.php +++ b/src/webauthn/src/PublicKeyCredentialRequestOptions.php @@ -204,6 +204,12 @@ public static function createFromArray(array $json): static */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); $json = [ 'challenge' => Base64UrlSafe::encodeUnpadded($this->challenge), ]; diff --git a/src/webauthn/src/PublicKeyCredentialRpEntity.php b/src/webauthn/src/PublicKeyCredentialRpEntity.php index 1720462b..bb40b539 100644 --- a/src/webauthn/src/PublicKeyCredentialRpEntity.php +++ b/src/webauthn/src/PublicKeyCredentialRpEntity.php @@ -51,6 +51,12 @@ public static function createFromArray(array $json): self */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); $json = parent::jsonSerialize(); if ($this->id !== null) { $json['id'] = $this->id; diff --git a/src/webauthn/src/PublicKeyCredentialSource.php b/src/webauthn/src/PublicKeyCredentialSource.php index de55bb10..ca94d8a5 100644 --- a/src/webauthn/src/PublicKeyCredentialSource.php +++ b/src/webauthn/src/PublicKeyCredentialSource.php @@ -248,7 +248,13 @@ public static function createFromArray(array $data): self */ public function jsonSerialize(): array { - return [ + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); + $result = [ 'publicKeyCredentialId' => Base64UrlSafe::encodeUnpadded($this->publicKeyCredentialId), 'type' => $this->type, 'transports' => $this->transports, @@ -263,5 +269,7 @@ public function jsonSerialize(): array 'backupStatus' => $this->backupStatus, 'uvInitialized' => $this->uvInitialized, ]; + + return array_filter($result, static fn ($value): bool => $value !== null); } } diff --git a/src/webauthn/src/PublicKeyCredentialUserEntity.php b/src/webauthn/src/PublicKeyCredentialUserEntity.php index 53f147d8..b5c75ee3 100644 --- a/src/webauthn/src/PublicKeyCredentialUserEntity.php +++ b/src/webauthn/src/PublicKeyCredentialUserEntity.php @@ -87,6 +87,12 @@ public static function createFromArray(array $json): self */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); $json = parent::jsonSerialize(); $json['id'] = Base64UrlSafe::encodeUnpadded($this->id); $json['displayName'] = $this->displayName; diff --git a/src/webauthn/src/TrustPath/CertificateTrustPath.php b/src/webauthn/src/TrustPath/CertificateTrustPath.php index 796511c5..5bc39fbf 100644 --- a/src/webauthn/src/TrustPath/CertificateTrustPath.php +++ b/src/webauthn/src/TrustPath/CertificateTrustPath.php @@ -56,6 +56,12 @@ public static function createFromArray(array $data): static */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); return [ 'type' => self::class, 'x5c' => $this->certificates, diff --git a/src/webauthn/src/TrustPath/EmptyTrustPath.php b/src/webauthn/src/TrustPath/EmptyTrustPath.php index 74410336..a91a6121 100644 --- a/src/webauthn/src/TrustPath/EmptyTrustPath.php +++ b/src/webauthn/src/TrustPath/EmptyTrustPath.php @@ -16,6 +16,12 @@ public static function create(): self */ public function jsonSerialize(): array { + trigger_deprecation( + 'web-auth/webauthn-bundle', + '4.9.0', + 'The "%s" method is deprecated and will be removed in 5.0. The serializer instead.', + __METHOD__ + ); return [ 'type' => self::class, ]; diff --git a/src/webauthn/src/TrustPath/TrustPathLoader.php b/src/webauthn/src/TrustPath/TrustPathLoader.php index e06b1551..b79675bc 100644 --- a/src/webauthn/src/TrustPath/TrustPathLoader.php +++ b/src/webauthn/src/TrustPath/TrustPathLoader.php @@ -6,8 +6,8 @@ use Webauthn\Exception\InvalidTrustPathException; use function array_key_exists; -use function class_implements; -use function in_array; +use function is_array; +use function is_string; final class TrustPathLoader { @@ -16,18 +16,15 @@ final class TrustPathLoader */ public static function loadTrustPath(array $data): TrustPath { - array_key_exists('type', $data) || throw InvalidTrustPathException::create('The trust path type is missing'); - $type = $data['type']; - if (class_exists($type) !== true) { - throw InvalidTrustPathException::create( - sprintf('The trust path type "%s" is not supported', $data['type']) - ); - } - - $implements = class_implements($type); - if (in_array(TrustPath::class, $implements, true)) { - return $type::createFromArray($data); - } - throw InvalidTrustPathException::create(sprintf('The trust path type "%s" is not supported', $data['type'])); + return match (true) { + $data === [] || $data === [ + 'type' => EmptyTrustPath::class, + ] => EmptyTrustPath::create(), + array_key_exists('x5c', $data) && is_array($data['x5c']) => CertificateTrustPath::create($data['x5c']), + array_key_exists('ecdaaKeyId', $data) && is_string($data['ecdaaKeyId']) => new EcdaaKeyIdTrustPath( + $data['ecdaaKeyId'] + ), + default => throw InvalidTrustPathException::create('Unsupported trust path'), + }; } } diff --git a/tests/library/AbstractTestCase.php b/tests/library/AbstractTestCase.php index f90b2928..48fc750e 100644 --- a/tests/library/AbstractTestCase.php +++ b/tests/library/AbstractTestCase.php @@ -20,6 +20,7 @@ use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport; use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport; use Webauthn\AttestationStatement\AppleAttestationStatementSupport; +use Webauthn\AttestationStatement\AttestationObjectLoader; use Webauthn\AttestationStatement\AttestationStatementSupportManager; use Webauthn\AttestationStatement\FidoU2FAttestationStatementSupport; use Webauthn\AttestationStatement\NoneAttestationStatementSupport; @@ -36,6 +37,7 @@ use Webauthn\MetadataService\Service\ChainedMetadataServices; use Webauthn\MetadataService\Service\JsonMetadataService; use Webauthn\MetadataService\Service\LocalResourceMetadataService; +use Webauthn\PublicKeyCredentialLoader; use Webauthn\Tests\Bundle\Functional\MockClock; use Webauthn\Tests\Functional\MetadataStatementRepository; use Webauthn\Tests\Functional\StatusReportRepository; @@ -152,6 +154,13 @@ protected function getSerializer(): SerializerInterface return $this->webauthnSerializer; } + protected function getLegacyLoader(): PublicKeyCredentialLoader + { + return new PublicKeyCredentialLoader(new AttestationObjectLoader( + $this->getAttestationStatementSupportManager() + )); + } + private function getAttestationStatementSupportManager(): AttestationStatementSupportManager { $attestationStatementSupportManager = new AttestationStatementSupportManager(); diff --git a/tests/library/Functional/AssertionTest.php b/tests/library/Functional/AssertionTest.php index 605ffc74..62e392b4 100644 --- a/tests/library/Functional/AssertionTest.php +++ b/tests/library/Functional/AssertionTest.php @@ -69,6 +69,23 @@ public function anAssertionCanBeVerified(): void static::assertSame(123, $publicKeyCredentialSource->counter); } + #[Test] + public function serializerAndLegacyMethodGiveTheSameResult(): void + { + // Given + $input = '{"id":"ADqYfFWXiscOCOPCd9OLiBtSGhletNPKlSOELS0Nuwj_uCzf9s3trLUK9ockO8xa8jBAYdKixLZYOAezy0FJiV1bnTCty_LiInWWJlov","type":"public-key","rawId":"ADqYfFWXiscOCOPCd9OLiBtSGhletNPKlSOELS0Nuwj/uCzf9s3trLUK9ockO8xa8jBAYdKixLZYOAezy0FJiV1bnTCty/LiInWWJlov","response":{"authenticatorData":"tIXbbgSILsWHHbR0Fjkl96X4ROZYLvVtOopBWCQoAqpFXFBJyQAAAAAAAAAAAAAAAAAAAAAATgA6mHxVl4rHDgjjwnfTi4gbUhoZXrTTypUjhC0tDbsI_7gs3_bN7ay1CvaHJDvMWvIwQGHSosS2WDgHs8tBSYldW50wrcvy4iJ1liZaL6UBAgMmIAEhWCAIpUDJSoLScguLRDKBEc32v682i6RPjy6SFZnFTBj2QSJYIG8DS0CpphjyFyZB9xyCTrKDsr_S5iX5hhidWLRdP_7B","clientDataJSON":"eyJjaGFsbGVuZ2UiOiJ3S2xXN1MzRUVOSGxjRjJOZ1loZFVKZlJKZUN2QXZsYmstTWxsdnhvMEhBIiwib3JpZ2luIjoiaHR0cHM6Ly9zcG9ta3ktd2ViYXV0aG4uaGVyb2t1YXBwLmNvbSIsInR5cGUiOiJ3ZWJhdXRobi5nZXQifQ","signature":"MEQCIBnVPX8inAXIxXAsMdF6nW6nZJa36G1O+G9JXiauenxBAiBU4MQoRWxiXGn0TcKTkRJafZ58KLqeCJiB2VFAplwPJA==","userHandle":"YWJmYzhmZGYtMDdmNi00NWE5LWFiZWMtZmExOTIyNzViMjc2"}}'; + + //When + $publicKeyCredential1 = $this->getSerializer() + ->deserialize($input, PublicKeyCredential::class, 'json'); + $publicKeyCredential2 = $this->getLegacyLoader() + ->load($input); + + //Then + static::assertEquals($publicKeyCredential1, $publicKeyCredential2); + + } + #[Test] public function anAssertionWithTokenBindingCanBeVerified(): void { diff --git a/tests/library/Unit/AttestedCredentialDataTest.php b/tests/library/Unit/AttestedCredentialDataTest.php index 6a863a65..be8a13ef 100644 --- a/tests/library/Unit/AttestedCredentialDataTest.php +++ b/tests/library/Unit/AttestedCredentialDataTest.php @@ -6,20 +6,21 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Uid\Uuid; use Webauthn\AttestedCredentialData; -use const JSON_UNESCAPED_SLASHES; +use Webauthn\Tests\AbstractTestCase; /** * @internal */ -final class AttestedCredentialDataTest extends TestCase +final class AttestedCredentialDataTest extends AbstractTestCase { #[Test] #[DataProvider('dataAAGUID')] public function anAttestedCredentialDataCanBeCreatedAndValueAccessed(string $uuid): void { + // Given $attestedCredentialData = AttestedCredentialData::create(Uuid::fromString( $uuid ), 'credential_id', 'credential_public_key'); @@ -29,7 +30,10 @@ public function anAttestedCredentialDataCanBeCreatedAndValueAccessed(string $uui static::assertSame('credential_public_key', $attestedCredentialData->credentialPublicKey); static::assertSame( '{"aaguid":"' . $uuid . '","credentialId":"Y3JlZGVudGlhbF9pZA==","credentialPublicKey":"Y3JlZGVudGlhbF9wdWJsaWNfa2V5"}', - json_encode($attestedCredentialData, JSON_UNESCAPED_SLASHES) + $this->getSerializer() + ->serialize($attestedCredentialData, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]) ); json_decode( diff --git a/tests/library/Unit/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoaderTest.php b/tests/library/Unit/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoaderTest.php index 1fb97af2..1ea8e2cd 100644 --- a/tests/library/Unit/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoaderTest.php +++ b/tests/library/Unit/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoaderTest.php @@ -9,16 +9,16 @@ use CBOR\MapObject; use CBOR\OtherObject\TrueObject; use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader; use Webauthn\Exception\AuthenticationExtensionException; -use const JSON_THROW_ON_ERROR; +use Webauthn\Tests\AbstractTestCase; /** * @internal */ -final class AuthenticationExtensionsClientOutputsLoaderTest extends TestCase +final class AuthenticationExtensionsClientOutputsLoaderTest extends AbstractTestCase { #[Test] public function theExtensionsCanBeLoaded(): void @@ -29,7 +29,9 @@ public function theExtensionsCanBeLoaded(): void static::assertInstanceOf(AuthenticationExtensionsClientOutputs::class, $extensions); static::assertCount(1, $extensions); - static::assertSame('{"loc":true}', json_encode($extensions, JSON_THROW_ON_ERROR)); + static::assertSame('{"loc":true}', $this->getSerializer()->serialize($extensions, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); } #[Test] diff --git a/tests/library/Unit/AuthenticationExtensions/AuthenticationExtensionsClientTest.php b/tests/library/Unit/AuthenticationExtensions/AuthenticationExtensionsClientTest.php index 18ad55d6..dc8118f7 100644 --- a/tests/library/Unit/AuthenticationExtensions/AuthenticationExtensionsClientTest.php +++ b/tests/library/Unit/AuthenticationExtensions/AuthenticationExtensionsClientTest.php @@ -5,16 +5,16 @@ namespace Webauthn\Tests\Unit\AuthenticationExtensions; use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Webauthn\AuthenticationExtensions\AuthenticationExtension; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs; -use const JSON_THROW_ON_ERROR; +use Webauthn\Tests\AbstractTestCase; /** * @internal */ -final class AuthenticationExtensionsClientTest extends TestCase +final class AuthenticationExtensionsClientTest extends AbstractTestCase { #[Test] public function anAuthenticationExtensionsClientCanBeCreatedAndValueAccessed(): void @@ -23,7 +23,9 @@ public function anAuthenticationExtensionsClientCanBeCreatedAndValueAccessed(): static::assertSame('name', $extension->name); static::assertSame(['value'], $extension->value); - static::assertSame('["value"]', json_encode($extension, JSON_THROW_ON_ERROR)); + static::assertSame('["value"]', $this->getSerializer()->serialize($extension, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); } #[Test] @@ -34,7 +36,9 @@ public function theAuthenticationExtensionsClientInputsCanManageExtensions(): vo ]); static::assertSame(1, $inputs->count()); - static::assertSame('{"name":["value"]}', json_encode($inputs, JSON_THROW_ON_ERROR)); + static::assertSame('{"name":["value"]}', $this->getSerializer()->serialize($inputs, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); static::assertContainsOnlyInstancesOf(AuthenticationExtension::class, $inputs); } @@ -46,7 +50,9 @@ public function theAuthenticationExtensionsClientOutputsCanManageExtensions(): v ]); static::assertSame(1, $inputs->count()); - static::assertSame('{"name":["value"]}', json_encode($inputs, JSON_THROW_ON_ERROR)); + static::assertSame('{"name":["value"]}', $this->getSerializer()->serialize($inputs, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); static::assertContainsOnlyInstancesOf(AuthenticationExtension::class, $inputs); } } diff --git a/tests/library/Unit/AuthenticatorSelectionCriteriaTest.php b/tests/library/Unit/AuthenticatorSelectionCriteriaTest.php index f03aae89..4ea84046 100644 --- a/tests/library/Unit/AuthenticatorSelectionCriteriaTest.php +++ b/tests/library/Unit/AuthenticatorSelectionCriteriaTest.php @@ -5,9 +5,9 @@ namespace Webauthn\Tests\Unit; use PHPUnit\Framework\Attributes\Test; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Webauthn\AuthenticatorSelectionCriteria; use Webauthn\Tests\AbstractTestCase; -use const JSON_THROW_ON_ERROR; /** * @internal @@ -27,7 +27,9 @@ public function anAuthenticatorSelectionCriteriaCanBeCreatedAndValueAccessed(): //When $data = $this->getSerializer() - ->deserialize($expectedJson, AuthenticatorSelectionCriteria::class, 'json'); + ->deserialize($expectedJson, AuthenticatorSelectionCriteria::class, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]); //Then static::assertSame( @@ -40,15 +42,23 @@ public function anAuthenticatorSelectionCriteriaCanBeCreatedAndValueAccessed(): ); static::assertNull($data->requireResidentKey); static::assertSame(AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_NO_PREFERENCE, $data->residentKey); - static::assertSame($expectedJson, json_encode($data, JSON_THROW_ON_ERROR)); - static::assertSame($expectedJson, json_encode($authenticatorSelectionCriteria, JSON_THROW_ON_ERROR)); + static::assertJsonStringEqualsJsonString($expectedJson, $this->getSerializer()->serialize($data, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); + static::assertJsonStringEqualsJsonString( + $expectedJson, + $this->getSerializer() + ->serialize($authenticatorSelectionCriteria, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]) + ); } #[Test] public function anAuthenticatorSelectionCriteriaWithResidentKeyCanBeCreatedAndValueAccessed(): void { // Given - $expectedJson = '{"requireResidentKey":true,"userVerification":"required","residentKey":"required","authenticatorAttachment":"platform"}'; + $expectedJson = '{"authenticatorAttachment":"platform","requireResidentKey":true,"userVerification":"required","residentKey":"required"}'; $authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create( AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM, AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED, @@ -58,7 +68,9 @@ public function anAuthenticatorSelectionCriteriaWithResidentKeyCanBeCreatedAndVa //When $data = $this->getSerializer() - ->deserialize($expectedJson, AuthenticatorSelectionCriteria::class, 'json'); + ->deserialize($expectedJson, AuthenticatorSelectionCriteria::class, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]); //Then static::assertSame( @@ -71,7 +83,11 @@ public function anAuthenticatorSelectionCriteriaWithResidentKeyCanBeCreatedAndVa ); static::assertTrue($data->requireResidentKey); static::assertSame(AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED, $data->residentKey); - static::assertSame($expectedJson, json_encode($data, JSON_THROW_ON_ERROR)); - static::assertSame($expectedJson, json_encode($authenticatorSelectionCriteria, JSON_THROW_ON_ERROR)); + static::assertSame($expectedJson, $this->getSerializer()->serialize($data, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); + static::assertSame($expectedJson, $this->getSerializer()->serialize($authenticatorSelectionCriteria, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); } } diff --git a/tests/library/Unit/EntityTest.php b/tests/library/Unit/EntityTest.php index 4363f96f..9b5661f6 100644 --- a/tests/library/Unit/EntityTest.php +++ b/tests/library/Unit/EntityTest.php @@ -5,15 +5,15 @@ namespace Webauthn\Tests\Unit; use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Webauthn\PublicKeyCredentialRpEntity; use Webauthn\PublicKeyCredentialUserEntity; -use const JSON_THROW_ON_ERROR; +use Webauthn\Tests\AbstractTestCase; /** * @internal */ -final class EntityTest extends TestCase +final class EntityTest extends AbstractTestCase { #[Test] public function anPublicKeyCredentialUserEntityCanBeCreatedAndValueAccessed(): void @@ -25,8 +25,11 @@ public function anPublicKeyCredentialUserEntityCanBeCreatedAndValueAccessed(): v static::assertSame('icon', $user->icon); static::assertSame('id', $user->id); static::assertSame( - '{"name":"name","icon":"icon","id":"aWQ","displayName":"display_name"}', - json_encode($user, JSON_THROW_ON_ERROR) + '{"id":"aWQ","name":"name","displayName":"display_name","icon":"icon"}', + $this->getSerializer() + ->serialize($user, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]) ); } @@ -38,6 +41,8 @@ public function anPublicKeyCredentialRpEntityCanBeCreatedAndValueAccessed(): voi static::assertSame('name', $rp->name); static::assertSame('icon', $rp->icon); static::assertSame('id', $rp->id); - static::assertSame('{"name":"name","icon":"icon","id":"id"}', json_encode($rp, JSON_THROW_ON_ERROR)); + static::assertSame('{"id":"id","name":"name","icon":"icon"}', $this->getSerializer()->serialize($rp, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); } } diff --git a/tests/library/Unit/PublicKeyCredentialCreationOptionsTest.php b/tests/library/Unit/PublicKeyCredentialCreationOptionsTest.php index 0f60119f..2164f342 100644 --- a/tests/library/Unit/PublicKeyCredentialCreationOptionsTest.php +++ b/tests/library/Unit/PublicKeyCredentialCreationOptionsTest.php @@ -5,13 +5,13 @@ namespace Webauthn\Tests\Unit; use PHPUnit\Framework\Attributes\Test; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Webauthn\PublicKeyCredentialCreationOptions; use Webauthn\PublicKeyCredentialDescriptor; use Webauthn\PublicKeyCredentialParameters; use Webauthn\PublicKeyCredentialRpEntity; use Webauthn\PublicKeyCredentialUserEntity; use Webauthn\Tests\AbstractTestCase; -use const JSON_THROW_ON_ERROR; /** * @internal @@ -42,9 +42,12 @@ public function anPublicKeyCredentialCreationOptionsCanBeCreatedAndValueAccessed static::assertSame([$credentialParameters], $options->pubKeyCredParams); static::assertSame('direct', $options->attestation); static::assertSame(1000, $options->timeout); - static::assertSame( + static::assertJsonStringEqualsJsonString( '{"rp":{"name":"RP"},"user":{"name":"USER","id":"aWQ","displayName":"FOO BAR"},"challenge":"Y2hhbGxlbmdl","pubKeyCredParams":[{"type":"type","alg":-100}],"timeout":1000,"excludeCredentials":[{"type":"type","id":"aWQ","transports":["transport"]}],"attestation":"direct"}', - json_encode($options, JSON_THROW_ON_ERROR) + $this->getSerializer() + ->serialize($options, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]) ); $data = $this->getSerializer() @@ -56,9 +59,12 @@ public function anPublicKeyCredentialCreationOptionsCanBeCreatedAndValueAccessed static::assertSame('challenge', $data->challenge); static::assertSame('direct', $data->attestation); static::assertSame(1000, $data->timeout); - static::assertSame( + static::assertJsonStringEqualsJsonString( '{"rp":{"name":"RP"},"user":{"name":"USER","id":"aWQ","displayName":"FOO BAR"},"challenge":"Y2hhbGxlbmdl","pubKeyCredParams":[{"type":"type","alg":-100}],"timeout":1000,"excludeCredentials":[{"type":"type","id":"aWQ","transports":["transport"]}],"authenticatorSelection":{"requireResidentKey":false,"userVerification":"preferred","residentKey":"preferred"},"attestation":"direct"}', - json_encode($data, JSON_THROW_ON_ERROR) + $this->getSerializer() + ->serialize($data, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]) ); } @@ -79,15 +85,19 @@ public function anPublicKeyCredentialCreationOptionsWithoutExcludeCredentialsCan timeout: 1000 ); - $json = json_encode($options, JSON_THROW_ON_ERROR); - static::assertSame( - // '{"rp":{"name":"RP"},"pubKeyCredParams":[{"type":"type","alg":-100}],"challenge":"Y2hhbGxlbmdl","attestation":"indirect","user":{"name":"USER","id":"aWQ","displayName":"FOO BAR"},"authenticatorSelection":{"requireResidentKey":false,"userVerification":"preferred","residentKey":"preferred"},"excludeCredentials":[],"timeout":1000}', // TODO: On hold. Waiting for issue clarification. See https://github.com/fido-alliance/conformance-test-tools-resources/issues/676 + $json = $this->getSerializer() + ->serialize($options, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]); + static::assertJsonStringEqualsJsonString( '{"rp":{"name":"RP"},"user":{"name":"USER","id":"aWQ","displayName":"FOO BAR"},"challenge":"Y2hhbGxlbmdl","pubKeyCredParams":[{"type":"type","alg":-100}],"timeout":1000,"attestation":"indirect"}', $json ); $data = $this->getSerializer() - ->deserialize($json, PublicKeyCredentialCreationOptions::class, 'json'); + ->deserialize($json, PublicKeyCredentialCreationOptions::class, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]); static::assertSame([], $data->excludeCredentials); } } diff --git a/tests/library/Unit/PublicKeyCredentialDescriptorTest.php b/tests/library/Unit/PublicKeyCredentialDescriptorTest.php index 77d6c294..99ab9b7e 100644 --- a/tests/library/Unit/PublicKeyCredentialDescriptorTest.php +++ b/tests/library/Unit/PublicKeyCredentialDescriptorTest.php @@ -5,14 +5,14 @@ namespace Webauthn\Tests\Unit; use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Webauthn\PublicKeyCredentialDescriptor; -use const JSON_THROW_ON_ERROR; +use Webauthn\Tests\AbstractTestCase; /** * @internal */ -final class PublicKeyCredentialDescriptorTest extends TestCase +final class PublicKeyCredentialDescriptorTest extends AbstractTestCase { #[Test] public function anPublicKeyCredentialDescriptorCanBeCreatedAndValueAccessed(): void @@ -24,7 +24,10 @@ public function anPublicKeyCredentialDescriptorCanBeCreatedAndValueAccessed(): v static::assertSame(['transport'], $descriptor->transports); static::assertSame( '{"type":"type","id":"aWQ","transports":["transport"]}', - json_encode($descriptor, JSON_THROW_ON_ERROR) + $this->getSerializer() + ->serialize($descriptor, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]) ); } } diff --git a/tests/library/Unit/PublicKeyCredentialParametersTest.php b/tests/library/Unit/PublicKeyCredentialParametersTest.php index 01632248..7ba1789f 100644 --- a/tests/library/Unit/PublicKeyCredentialParametersTest.php +++ b/tests/library/Unit/PublicKeyCredentialParametersTest.php @@ -5,9 +5,9 @@ namespace Webauthn\Tests\Unit; use PHPUnit\Framework\Attributes\Test; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Webauthn\PublicKeyCredentialParameters; use Webauthn\Tests\AbstractTestCase; -use const JSON_THROW_ON_ERROR; /** * @internal @@ -21,12 +21,16 @@ public function aPublicKeyCredentialParametersCanBeCreatedAndValueAccessed(): vo static::assertSame('type', $parameters->type); static::assertSame(100, $parameters->alg); - static::assertSame('{"type":"type","alg":100}', json_encode($parameters, JSON_THROW_ON_ERROR)); + static::assertSame('{"type":"type","alg":100}', $this->getSerializer()->serialize($parameters, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); $data = $this->getSerializer() ->deserialize('{"type":"type","alg":100}', PublicKeyCredentialParameters::class, 'json'); static::assertSame('type', $data->type); static::assertSame(100, $data->alg); - static::assertSame('{"type":"type","alg":100}', json_encode($data, JSON_THROW_ON_ERROR)); + static::assertSame('{"type":"type","alg":100}', $this->getSerializer()->serialize($data, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); } } diff --git a/tests/library/Unit/PublicKeyCredentialRequestOptionsTest.php b/tests/library/Unit/PublicKeyCredentialRequestOptionsTest.php index 808a5d0c..0ed8a85d 100644 --- a/tests/library/Unit/PublicKeyCredentialRequestOptionsTest.php +++ b/tests/library/Unit/PublicKeyCredentialRequestOptionsTest.php @@ -5,13 +5,13 @@ namespace Webauthn\Tests\Unit; use PHPUnit\Framework\Attributes\Test; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Webauthn\AuthenticationExtensions\AuthenticationExtension; use Webauthn\AuthenticationExtensions\AuthenticationExtensions; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; use Webauthn\PublicKeyCredentialDescriptor; use Webauthn\PublicKeyCredentialRequestOptions; use Webauthn\Tests\AbstractTestCase; -use const JSON_THROW_ON_ERROR; /** * @internal @@ -24,7 +24,10 @@ public function authenticatorExtensionSerialization(): void // Given $extensions = AuthenticationExtensions::create([AuthenticationExtension::create('foo', 'bar')]); $extensions['baz'] = 'New era'; - $json = json_encode($extensions, JSON_THROW_ON_ERROR); + $json = $this->getSerializer() + ->serialize($extensions, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]); // When $data = $this->getSerializer() @@ -35,7 +38,9 @@ public function authenticatorExtensionSerialization(): void static::assertSame('bar', $data->get('foo')->value); static::assertSame('bar', $data['foo']->value); static::assertSame('New era', $data['baz']->value); - static::assertSame($json, json_encode($data, JSON_THROW_ON_ERROR)); + static::assertSame($json, $this->getSerializer()->serialize($data, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); } #[Test] @@ -59,9 +64,12 @@ public function aPublicKeyCredentialRequestOptionsCanBeCreatedAndValueAccessed() static::assertSame([$credential], $publicKeyCredentialRequestOptions->allowCredentials); static::assertSame('preferred', $publicKeyCredentialRequestOptions->userVerification); static::assertInstanceOf(AuthenticationExtensions::class, $publicKeyCredentialRequestOptions->extensions); - static::assertSame( + static::assertJsonStringEqualsJsonString( '{"challenge":"Y2hhbGxlbmdl","rpId":"rp_id","userVerification":"preferred","allowCredentials":[{"type":"type","id":"aWQ","transports":["transport"]}],"extensions":{"foo":"bar"},"timeout":1000}', - json_encode($publicKeyCredentialRequestOptions, JSON_THROW_ON_ERROR) + $this->getSerializer() + ->serialize($publicKeyCredentialRequestOptions, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]) ); $data = $this->getSerializer() @@ -75,9 +83,12 @@ public function aPublicKeyCredentialRequestOptionsCanBeCreatedAndValueAccessed() static::assertSame('rp_id', $data->rpId); static::assertSame('preferred', $data->userVerification); static::assertInstanceOf(AuthenticationExtensions::class, $data->extensions); - static::assertSame( + static::assertJsonStringEqualsJsonString( '{"challenge":"Y2hhbGxlbmdl","rpId":"rp_id","userVerification":"preferred","allowCredentials":[{"type":"type","id":"aWQ","transports":["transport"]}],"extensions":{"foo":"bar"},"timeout":1000}', - json_encode($data, JSON_THROW_ON_ERROR) + $this->getSerializer() + ->serialize($data, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]) ); } } diff --git a/tests/library/Unit/PublicKeyCredentialSourceTest.php b/tests/library/Unit/PublicKeyCredentialSourceTest.php index d8955aca..cde05124 100644 --- a/tests/library/Unit/PublicKeyCredentialSourceTest.php +++ b/tests/library/Unit/PublicKeyCredentialSourceTest.php @@ -5,12 +5,11 @@ namespace Webauthn\Tests\Unit; use PHPUnit\Framework\Attributes\Test; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Uid\Uuid; use Webauthn\PublicKeyCredentialSource; use Webauthn\Tests\AbstractTestCase; use Webauthn\TrustPath\EmptyTrustPath; -use const JSON_UNESCAPED_SLASHES; -use const JSON_UNESCAPED_UNICODE; /** * @internal @@ -20,11 +19,45 @@ final class PublicKeyCredentialSourceTest extends AbstractTestCase #[Test] public function backwardCompatibilityIsEnsured(): void { - $data = '{"publicKeyCredentialId":"cHVibGljS2V5Q3JlZGVudGlhbElk","type":"type","transports":["transport1","transport2"],"attestationType":"attestationType","trustPath":{"type":"Webauthn\\\\TrustPath\\\\EmptyTrustPath"},"aaguid":"014c0f17-f86f-4586-9914-2779922ba877","credentialPublicKey":"cHVibGljS2V5","userHandle":"dXNlckhhbmRsZQ","counter":123456789}'; - $source = $this->getSerializer() + // Given + $data = '{"publicKeyCredentialId":"cHVibGljS2V5Q3JlZGVudGlhbElk","type":"type","transports":["transport1","transport2"],"attestationType":"attestationType","trustPath":[],"aaguid":"014c0f17-f86f-4586-9914-2779922ba877","credentialPublicKey":"cHVibGljS2V5","userHandle":"dXNlckhhbmRsZQ","counter":123456789}'; + + //When + $source1 = $this->getSerializer() ->deserialize($data, PublicKeyCredentialSource::class, 'json'); + $source2 = PublicKeyCredentialSource::createFromArray(json_decode($data, true)); + + static::assertSame('publicKeyCredentialId', $source1->publicKeyCredentialId); + static::assertEquals($source1, $source2); + static::assertJsonStringEqualsJsonString($data, $this->getSerializer()->serialize($source2, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ])); + } + + #[Test] + public function jsonObject(): void + { + $serializer = $this->getSerializer(); + $source = publicKeyCredentialSource::create( + 'publicKeyCredentialId', + 'type', + ['transport1', 'transport2'], + 'attestationType', + EmptyTrustPath::create(), + Uuid::fromString('02ffd35d-7f0c-46b5-9eae-851ee4807b25'), + 'publicKey', + 'userHandle', + 123_456_789 + ); + $asJson = $this->getSerializer() + ->serialize($source, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]); + + $object1 = $serializer->deserialize($asJson, PublicKeyCredentialSource::class, 'json'); + $object2 = PublicKeyCredentialSource::createFromArray(json_decode($asJson, true)); - static::assertSame('publicKeyCredentialId', $source->publicKeyCredentialId); + static::assertEquals($object1, $object2); } #[Test] @@ -46,9 +79,12 @@ public function objectSerialization(): void false ); - static::assertSame( - '{"publicKeyCredentialId":"cHVibGljS2V5Q3JlZGVudGlhbElk","type":"type","transports":["transport1","transport2"],"attestationType":"attestationType","trustPath":{"type":"Webauthn\\\\TrustPath\\\\EmptyTrustPath"},"aaguid":"02ffd35d-7f0c-46b5-9eae-851ee4807b25","credentialPublicKey":"cHVibGljS2V5","userHandle":"dXNlckhhbmRsZQ","counter":123456789,"otherUI":null,"backupEligible":true,"backupStatus":true,"uvInitialized":false}', - json_encode($source, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) + static::assertJsonStringEqualsJsonString( + '{"publicKeyCredentialId":"cHVibGljS2V5Q3JlZGVudGlhbElk","type":"type","transports":["transport1","transport2"],"attestationType":"attestationType","trustPath":[],"aaguid":"02ffd35d-7f0c-46b5-9eae-851ee4807b25","credentialPublicKey":"cHVibGljS2V5","userHandle":"dXNlckhhbmRsZQ","counter":123456789,"backupEligible":true,"backupStatus":true,"uvInitialized":false}', + $this->getSerializer() + ->serialize($source, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]) ); } } diff --git a/tests/library/Unit/TrustPath/TrustPathTest.php b/tests/library/Unit/TrustPath/TrustPathTest.php index 76914994..5a797d06 100644 --- a/tests/library/Unit/TrustPath/TrustPathTest.php +++ b/tests/library/Unit/TrustPath/TrustPathTest.php @@ -46,7 +46,7 @@ public function anEcdaaKeyIdTrustPathCanBeCreated(): void public function theLoaderCannotLoadUnsupportedTypeName(): void { $this->expectException(InvalidTrustPathException::class); - $this->expectExceptionMessage('The trust path type "foo" is not supported'); + $this->expectExceptionMessage('Unsupported trust path'); TrustPathLoader::loadTrustPath([ 'type' => 'foo', ]); @@ -59,9 +59,7 @@ public function theLoaderCannotLoadUnsupportedTypeName(): void public function theLoaderCannotLoadUnsupportedTypeNameBasedOnClass(): void { $this->expectException(InvalidTrustPathException::class); - $this->expectExceptionMessage( - 'The trust path type "Webauthn\Tests\Unit\TrustPath\NotAValidTrustPath" is not supported' - ); + $this->expectExceptionMessage('Unsupported trust path'); TrustPathLoader::loadTrustPath([ 'type' => NotAValidTrustPath::class, ]); diff --git a/tests/symfony/functional/Attestation/AdditionalAuthenticatorTest.php b/tests/symfony/functional/Attestation/AdditionalAuthenticatorTest.php index ef91ea58..afa63586 100644 --- a/tests/symfony/functional/Attestation/AdditionalAuthenticatorTest.php +++ b/tests/symfony/functional/Attestation/AdditionalAuthenticatorTest.php @@ -54,7 +54,6 @@ public function anExistingUserCanAskForOptionsUsingTheDedicatedController(): voi 'status', 'errorMessage', 'rp', - 'pubKeyCredParams', 'challenge', 'attestation', 'user', diff --git a/tests/symfony/functional/Firewall/RegistrationAreaTest.php b/tests/symfony/functional/Firewall/RegistrationAreaTest.php index 473830a2..9bd96006 100644 --- a/tests/symfony/functional/Firewall/RegistrationAreaTest.php +++ b/tests/symfony/functional/Firewall/RegistrationAreaTest.php @@ -103,10 +103,10 @@ public function aValidRequestProcessed(): void static::assertArrayHasKey('authenticatorSelection', $data); static::assertSame([ + 'authenticatorAttachment' => 'cross-platform', 'requireResidentKey' => true, 'userVerification' => 'required', 'residentKey' => 'required', - 'authenticatorAttachment' => 'cross-platform', ], $data['authenticatorSelection']); } @@ -188,10 +188,10 @@ public function aValidRequestProcessedWithExtensions(): void static::assertArrayHasKey('authenticatorSelection', $data); static::assertSame([ + 'authenticatorAttachment' => 'platform', 'requireResidentKey' => true, 'userVerification' => 'required', 'residentKey' => 'required', - 'authenticatorAttachment' => 'platform', ], $data['authenticatorSelection']); }