From 598433f66466bc6dab6b0e0bb452ad02e3bbc92f Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Wed, 10 Jul 2024 17:38:06 +0200 Subject: [PATCH] Implement serialization/deserialization in Doctrine Types Introduce the `SerializerTrait` to handle serialization and deserialization for Doctrine Types. This refactoring deprecated and replaced the previous ways in these types: `PublicKeyCredentialDescriptorType`, `AttestedCredentialDataType`, `TrustPathDataType`. Meanwhile, test entities were removed, some deprecated methods related to serialization were marked, and the database configuration was updated in the test environment. --- phpstan-baseline.neon | 46 +++++--- phpunit.xml.dist | 1 - .../Type/AttestedCredentialDataType.php | 14 +-- ...ublicKeyCredentialDescriptorCollection.php | 1 + ...cKeyCredentialDescriptorCollectionType.php | 7 +- .../PublicKeyCredentialDescriptorType.php | 13 ++- .../src/Doctrine/Type/SerializerTrait.php | 32 ++++++ .../src/Doctrine/Type/TrustPathDataType.php | 15 ++- src/webauthn/src/AttestedCredentialData.php | 1 + .../WebauthnSerializerFactory.php | 4 + .../src/PublicKeyCredentialDescriptor.php | 4 + ...ublicKeyCredentialDescriptorCollection.php | 6 + .../src/SimpleFakeCredentialGenerator.php | 2 +- .../src/TrustPath/TrustPathLoader.php | 3 + tests/symfony/config/config.yml | 10 ++ .../symfony/functional/Entity/Credential.php | 19 ---- .../symfony/functional/Entity/EntityTest.php | 105 ------------------ 17 files changed, 121 insertions(+), 162 deletions(-) create mode 100644 src/symfony/src/Doctrine/Type/SerializerTrait.php delete mode 100644 tests/symfony/functional/Entity/Credential.php delete mode 100644 tests/symfony/functional/Entity/EntityTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fbc9057a4..1b98fd289 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1176,6 +1176,14 @@ parameters: count: 1 path: src/symfony/src/DependencyInjection/WebauthnExtension.php + - + message: """ + #^Fetching class constant class of deprecated class Webauthn\\\\Bundle\\\\Doctrine\\\\Type\\\\PublicKeyCredentialDescriptorCollectionType\\: + since 4\\.9\\.0 and will be removed in 5\\.0\\.0\\.$# + """ + count: 1 + path: src/symfony/src/DependencyInjection/WebauthnExtension.php + - message: """ #^Fetching class constant class of deprecated interface Webauthn\\\\Bundle\\\\Repository\\\\PublicKeyCredentialUserEntityRepository\\: @@ -1248,12 +1256,17 @@ parameters: path: src/symfony/src/Doctrine/Type/AAGUIDDataType.php - - message: "#^Cannot cast mixed to string\\.$#" + message: "#^Method Webauthn\\\\Bundle\\\\Doctrine\\\\Type\\\\AttestedCredentialDataType\\:\\:convertToDatabaseValue\\(\\) should return string\\|null but returns mixed\\.$#" count: 1 path: src/symfony/src/Doctrine/Type/AttestedCredentialDataType.php - - message: "#^Parameter \\#1 \\$json of static method Webauthn\\\\AttestedCredentialData\\:\\:createFromArray\\(\\) expects array, mixed given\\.$#" + message: "#^Method Webauthn\\\\Bundle\\\\Doctrine\\\\Type\\\\AttestedCredentialDataType\\:\\:convertToPHPValue\\(\\) should return Webauthn\\\\AttestedCredentialData\\|null but returns mixed\\.$#" + count: 1 + path: src/symfony/src/Doctrine/Type/AttestedCredentialDataType.php + + - + message: "#^Parameter \\#1 \\$data of method Webauthn\\\\Bundle\\\\Doctrine\\\\Type\\\\AttestedCredentialDataType\\:\\:deserialize\\(\\) expects string, mixed given\\.$#" count: 1 path: src/symfony/src/Doctrine/Type/AttestedCredentialDataType.php @@ -1298,17 +1311,32 @@ parameters: path: src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorCollection.php - - message: "#^Parameter \\#1 \\$data of static method Webauthn\\\\PublicKeyCredentialDescriptor\\:\\:createFromString\\(\\) expects string, mixed given\\.$#" + message: "#^Method Webauthn\\\\Bundle\\\\Doctrine\\\\Type\\\\PublicKeyCredentialDescriptorType\\:\\:convertToDatabaseValue\\(\\) should return string\\|null but returns mixed\\.$#" count: 1 path: src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorType.php - - message: "#^Cannot cast mixed to string\\.$#" + message: "#^Method Webauthn\\\\Bundle\\\\Doctrine\\\\Type\\\\PublicKeyCredentialDescriptorType\\:\\:convertToPHPValue\\(\\) should return Webauthn\\\\PublicKeyCredentialDescriptor\\|null but returns mixed\\.$#" + count: 1 + path: src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorType.php + + - + message: "#^Parameter \\#1 \\$data of method Webauthn\\\\Bundle\\\\Doctrine\\\\Type\\\\PublicKeyCredentialDescriptorType\\:\\:deserialize\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorType.php + + - + message: "#^Method Webauthn\\\\Bundle\\\\Doctrine\\\\Type\\\\TrustPathDataType\\:\\:convertToDatabaseValue\\(\\) should return string\\|null but returns mixed\\.$#" count: 1 path: src/symfony/src/Doctrine/Type/TrustPathDataType.php - - message: "#^Parameter \\#1 \\$data of static method Webauthn\\\\TrustPath\\\\TrustPathLoader\\:\\:loadTrustPath\\(\\) expects array, mixed given\\.$#" + message: "#^Method Webauthn\\\\Bundle\\\\Doctrine\\\\Type\\\\TrustPathDataType\\:\\:convertToPHPValue\\(\\) should return Webauthn\\\\TrustPath\\\\TrustPath\\|null but returns mixed\\.$#" + count: 1 + path: src/symfony/src/Doctrine/Type/TrustPathDataType.php + + - + message: "#^Parameter \\#1 \\$data of method Webauthn\\\\Bundle\\\\Doctrine\\\\Type\\\\TrustPathDataType\\:\\:deserialize\\(\\) expects string, mixed given\\.$#" count: 1 path: src/symfony/src/Doctrine/Type/TrustPathDataType.php @@ -3403,14 +3431,6 @@ parameters: 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 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ae583fc43..ddc6030b1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -22,7 +22,6 @@ - diff --git a/src/symfony/src/Doctrine/Type/AttestedCredentialDataType.php b/src/symfony/src/Doctrine/Type/AttestedCredentialDataType.php index 4130b04f5..6ab7814a6 100644 --- a/src/symfony/src/Doctrine/Type/AttestedCredentialDataType.php +++ b/src/symfony/src/Doctrine/Type/AttestedCredentialDataType.php @@ -7,17 +7,18 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use Webauthn\AttestedCredentialData; -use const JSON_THROW_ON_ERROR; final class AttestedCredentialDataType extends Type { + use SerializerTrait; + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { - if ($value === null) { + if (! $value instanceof AttestedCredentialData) { return $value; } - return json_encode($value, JSON_THROW_ON_ERROR); + return $this->serialize($value); } public function convertToPHPValue($value, AbstractPlatform $platform): ?AttestedCredentialData @@ -25,14 +26,13 @@ public function convertToPHPValue($value, AbstractPlatform $platform): ?Attested if ($value === null || $value instanceof AttestedCredentialData) { return $value; } - $json = json_decode((string) $value, true, flags: JSON_THROW_ON_ERROR); - return AttestedCredentialData::createFromArray($json); + return $this->deserialize($value, AttestedCredentialData::class); } - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return $platform->getJsonTypeDeclarationSQL($fieldDeclaration); + return $platform->getJsonTypeDeclarationSQL($column); } public function getName(): string diff --git a/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorCollection.php b/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorCollection.php index 70b796e88..74979c5b5 100644 --- a/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorCollection.php +++ b/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorCollection.php @@ -19,6 +19,7 @@ /** * @implements IteratorAggregate * @internal + * @deprecated since 4.9.0 and will be removed in 5.0.0. */ final class PublicKeyCredentialDescriptorCollection implements JsonSerializable, Countable, IteratorAggregate { diff --git a/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorCollectionType.php b/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorCollectionType.php index 726780b00..eb5c6ae5f 100644 --- a/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorCollectionType.php +++ b/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorCollectionType.php @@ -10,6 +10,9 @@ use function is_string; use const JSON_THROW_ON_ERROR; +/** + * @deprecated since 4.9.0 and will be removed in 5.0.0. + */ final class PublicKeyCredentialDescriptorCollectionType extends Type { public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string @@ -37,9 +40,9 @@ public function convertToPHPValue( return PublicKeyCredentialDescriptorCollection::createFromString($value); } - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return $platform->getJsonTypeDeclarationSQL($fieldDeclaration); + return $platform->getJsonTypeDeclarationSQL($column); } public function getName(): string diff --git a/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorType.php b/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorType.php index 85ef449fa..a49d9c19e 100644 --- a/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorType.php +++ b/src/symfony/src/Doctrine/Type/PublicKeyCredentialDescriptorType.php @@ -7,17 +7,18 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use Webauthn\PublicKeyCredentialDescriptor; -use const JSON_THROW_ON_ERROR; final class PublicKeyCredentialDescriptorType extends Type { + use SerializerTrait; + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { - if ($value === null) { + if (! $value instanceof PublicKeyCredentialDescriptor) { return $value; } - return json_encode($value, JSON_THROW_ON_ERROR); + return $this->serialize($value); } public function convertToPHPValue($value, AbstractPlatform $platform): ?PublicKeyCredentialDescriptor @@ -26,12 +27,12 @@ public function convertToPHPValue($value, AbstractPlatform $platform): ?PublicKe return $value; } - return PublicKeyCredentialDescriptor::createFromString($value); + return $this->deserialize($value, PublicKeyCredentialDescriptor::class); } - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return $platform->getJsonTypeDeclarationSQL($fieldDeclaration); + return $platform->getJsonTypeDeclarationSQL($column); } public function getName(): string diff --git a/src/symfony/src/Doctrine/Type/SerializerTrait.php b/src/symfony/src/Doctrine/Type/SerializerTrait.php new file mode 100644 index 000000000..b9b652885 --- /dev/null +++ b/src/symfony/src/Doctrine/Type/SerializerTrait.php @@ -0,0 +1,32 @@ +create(); + + return $serializer->serialize($data, JsonEncoder::FORMAT, [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + JsonEncode::OPTIONS => JSON_THROW_ON_ERROR, + ]); + } + + protected function deserialize(string $data, string $class): mixed + { + $serializer = (new WebauthnSerializerFactory(AttestationStatementSupportManager::create()))->create(); + + return $serializer->deserialize($data, $class, JsonEncoder::FORMAT); + } +} diff --git a/src/symfony/src/Doctrine/Type/TrustPathDataType.php b/src/symfony/src/Doctrine/Type/TrustPathDataType.php index 677dfc6ce..76ba2ecc2 100644 --- a/src/symfony/src/Doctrine/Type/TrustPathDataType.php +++ b/src/symfony/src/Doctrine/Type/TrustPathDataType.php @@ -7,18 +7,18 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use Webauthn\TrustPath\TrustPath; -use Webauthn\TrustPath\TrustPathLoader; -use const JSON_THROW_ON_ERROR; final class TrustPathDataType extends Type { + use SerializerTrait; + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { - if ($value === null) { + if (! $value instanceof TrustPath) { return $value; } - return json_encode($value, JSON_THROW_ON_ERROR); + return $this->serialize($value); } public function convertToPHPValue($value, AbstractPlatform $platform): ?TrustPath @@ -26,14 +26,13 @@ public function convertToPHPValue($value, AbstractPlatform $platform): ?TrustPat if ($value === null || $value instanceof TrustPath) { return $value; } - $json = json_decode((string) $value, true, flags: JSON_THROW_ON_ERROR); - return TrustPathLoader::loadTrustPath($json); + return $this->deserialize($value, TrustPath::class); } - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return $platform->getJsonTypeDeclarationSQL($fieldDeclaration); + return $platform->getJsonTypeDeclarationSQL($column); } public function getName(): string diff --git a/src/webauthn/src/AttestedCredentialData.php b/src/webauthn/src/AttestedCredentialData.php index cb226a3f5..a97f3fa49 100644 --- a/src/webauthn/src/AttestedCredentialData.php +++ b/src/webauthn/src/AttestedCredentialData.php @@ -66,6 +66,7 @@ public function getCredentialPublicKey(): ?string /** * @param mixed[] $json + * @deprecated since 4.9.0 and will be removed in 5.0.0. Please use the serializer instead. */ public static function createFromArray(array $json): self { diff --git a/src/webauthn/src/Denormalizer/WebauthnSerializerFactory.php b/src/webauthn/src/Denormalizer/WebauthnSerializerFactory.php index 694039a01..258a4bfb4 100644 --- a/src/webauthn/src/Denormalizer/WebauthnSerializerFactory.php +++ b/src/webauthn/src/Denormalizer/WebauthnSerializerFactory.php @@ -15,6 +15,8 @@ use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; use Webauthn\AttestationStatement\AttestationStatementSupportManager; +use Webauthn\MetadataService\Denormalizer\ExtensionDescriptorDenormalizer; +use Webauthn\MetadataService\Denormalizer\VerificationMethodANDCombinationsDenormalizer; final class WebauthnSerializerFactory { @@ -42,6 +44,8 @@ public function create(): SerializerInterface } $denormalizers = [ + new ExtensionDescriptorDenormalizer(), + new VerificationMethodANDCombinationsDenormalizer(), new AuthenticationExtensionNormalizer(), new PublicKeyCredentialDescriptorNormalizer(), new AttestedCredentialDataNormalizer(), diff --git a/src/webauthn/src/PublicKeyCredentialDescriptor.php b/src/webauthn/src/PublicKeyCredentialDescriptor.php index eedddf500..3a351be27 100644 --- a/src/webauthn/src/PublicKeyCredentialDescriptor.php +++ b/src/webauthn/src/PublicKeyCredentialDescriptor.php @@ -79,6 +79,9 @@ public function getTransports(): array return $this->transports; } + /** + * @deprecated since 4.9.0 and will be removed in 5.0.0. Please use the serializer instead. + */ public static function createFromString(string $data): self { $data = json_decode($data, true, flags: JSON_THROW_ON_ERROR); @@ -88,6 +91,7 @@ public static function createFromString(string $data): self /** * @param mixed[] $json + * @deprecated since 4.9.0 and will be removed in 5.0.0. Please use the serializer instead. */ public static function createFromArray(array $json): self { diff --git a/src/webauthn/src/PublicKeyCredentialDescriptorCollection.php b/src/webauthn/src/PublicKeyCredentialDescriptorCollection.php index bbc3a8c1b..d5f1481c9 100644 --- a/src/webauthn/src/PublicKeyCredentialDescriptorCollection.php +++ b/src/webauthn/src/PublicKeyCredentialDescriptorCollection.php @@ -100,6 +100,12 @@ public function count(int $mode = COUNT_NORMAL): int */ 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. Please use the serializer instead.', + __METHOD__ + ); return $this->publicKeyCredentialDescriptors; } diff --git a/src/webauthn/src/SimpleFakeCredentialGenerator.php b/src/webauthn/src/SimpleFakeCredentialGenerator.php index 9a0f16ac9..a0c4fb90d 100644 --- a/src/webauthn/src/SimpleFakeCredentialGenerator.php +++ b/src/webauthn/src/SimpleFakeCredentialGenerator.php @@ -46,7 +46,7 @@ private function generateCredentials(string $username): array $transports = [ PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_USB, PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_NFC, - PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_INTERNAL, + PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_BLE, ]; $credentials = []; for ($i = 0; $i < random_int(1, 3); $i++) { diff --git a/src/webauthn/src/TrustPath/TrustPathLoader.php b/src/webauthn/src/TrustPath/TrustPathLoader.php index b79675bc0..017f55252 100644 --- a/src/webauthn/src/TrustPath/TrustPathLoader.php +++ b/src/webauthn/src/TrustPath/TrustPathLoader.php @@ -9,6 +9,9 @@ use function is_array; use function is_string; +/** + * @deprecated since 4.9.0 and will be removed in 5.0.0. Use the serializer instead + */ final class TrustPathLoader { /** diff --git a/tests/symfony/config/config.yml b/tests/symfony/config/config.yml index 75f6c750a..d04ba5a3d 100644 --- a/tests/symfony/config/config.yml +++ b/tests/symfony/config/config.yml @@ -1,3 +1,6 @@ +parameters: + env(DATABASE_URL): '' + framework: test: true secret: 'test' @@ -95,6 +98,13 @@ services: doctrine: dbal: + driver: 'pdo_mysql' + server_version: '5.7' + charset: utf8mb4 + default_table_options: + charset: utf8mb4 + collate: utf8mb4_unicode_ci + url: '%env(resolve:DATABASE_URL)%' orm: enable_lazy_ghost_objects: true diff --git a/tests/symfony/functional/Entity/Credential.php b/tests/symfony/functional/Entity/Credential.php deleted file mode 100644 index 595dff21c..000000000 --- a/tests/symfony/functional/Entity/Credential.php +++ /dev/null @@ -1,19 +0,0 @@ -get(EntityManagerInterface::class); - - //When - $classMetadata = $entityManager->getClassMetadata(Credential::class); - $fields = $classMetadata->fieldMappings; - - //Then - static::assertArrayHasKey($name, $fields); - $field = $fields[$name]; - static::assertSame($type, $field['type']); - static::assertSame($nullable, $field['nullable']); - - } - - public static function expectedFields(): iterable - { - yield [ - 'name' => 'publicKeyCredentialId', - 'type' => 'base64', - 'nullable' => null, - ]; - yield [ - 'name' => 'type', - 'type' => 'string', - 'nullable' => null, - ]; - yield [ - 'name' => 'transports', - 'type' => 'array', - 'nullable' => null, - ]; - yield [ - 'name' => 'attestationType', - 'type' => 'string', - 'nullable' => null, - ]; - yield [ - 'name' => 'trustPath', - 'type' => 'trust_path', - 'nullable' => null, - ]; - yield [ - 'name' => 'aaguid', - 'type' => 'aaguid', - 'nullable' => null, - ]; - yield [ - 'name' => 'credentialPublicKey', - 'type' => 'base64', - 'nullable' => null, - ]; - yield [ - 'name' => 'userHandle', - 'type' => 'string', - 'nullable' => null, - ]; - yield [ - 'name' => 'counter', - 'type' => 'integer', - 'nullable' => null, - ]; - yield [ - 'name' => 'otherUI', - 'type' => 'array', - 'nullable' => true, - ]; - yield [ - 'name' => 'backupEligible', - 'type' => 'boolean', - 'nullable' => true, - ]; - yield [ - 'name' => 'backupStatus', - 'type' => 'boolean', - 'nullable' => true, - ]; - yield [ - 'name' => 'uvInitialized', - 'type' => 'boolean', - 'nullable' => true, - ]; - } -}