Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IBX-9337: Fixed failure to serialize field data post symfony/serializer 5.4.40 #466

Merged
merged 5 commits into from
Jan 9, 2025

Conversation

Steveb-p
Copy link
Contributor

@Steveb-p Steveb-p commented Dec 23, 2024

🎫 Issue IBX-9337

Related issues:

Description:

Due to change introduced in Symfony 5.4.40'ish, our ValueObjects stopped being immediately serializable through ObjectNormalizer default configuration/implementation.

Note

We never encouraged user-developers to use ObjectNormalizer against our built-in objects.

While that should work, it is also very susceptible to any changes in that generic implementation (which is what happened). It also can be source of security issues - as this PR shows in it's tests, where password hash is exposed.

In lieu of us providing normalizers for every possible object in the system, developers should write their own - especially when dealing with sensitive content fields.

This PR adds missing metadata to relevant properties/methods, which in turn causes said ObjectNormalizer to properly skip these when collecting data to normalize.

Warning

Note that in this PR Symfony's Ignore annotation is aliased.

It is extremely important that this import/alias should NOT match any of our existing phpdoc annotations, such as @ignore and similar.

Due to how PhpParser works (from Doctrine Annotations) it will start treating those as if they were that imported statement ⚠️.
This would mean that certain fields would be skipped by ObjectNormalizer even if they should have not. In this case non-aliased import would cause @ignore to be treated as if it was @Ignore.
Alternative is to use serializer.yaml file in bundle resources (it is picked up automatically by the framework, if exists).

Ibexa\Contracts\Core\Repository\Values\ValueObject:
    attributes:
        attribute:
            ignore: true
        properties:
            ignore: true

Known issues

Content objects on their own are technically possible to serialize, but due to our usage of VirtualProxy objects these need to either remain unresolved, or load themselves on demand.

However, this poses an issue when some objects are loaded outside of their normal permission state - specifically during testing, Section objects failed to load due to missing permissions (whether it really was a permission issue I cannot tell, I have not debugged it further other than looking at the exception).

Add following normalizer to application code to force proxies to resolve themselves, or skip them - depending on the code below.

<?php

namespace App\Normalizer;

use Ibexa\Contracts\Core\FieldType\Value;
use Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException;
use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType;
use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentTypeGroup;
use ProxyManager\Proxy\VirtualProxyInterface;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

final class VirtualProxyNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface, NormalizerAwareInterface
{
    use NormalizerAwareTrait;

    private bool $initializeProxies = true;

    /**
     * @param VirtualProxyInterface $object
     */
    public function normalize($object, ?string $format = null, array $context = [])
    {

        try {
            if ($object->isProxyInitialized() === false) {
                if (!$this->initializeProxies) {
                    return null;
                }

                $object->initializeProxy();

            }
            return $this->normalizer->normalize($object->getWrappedValueHolderValue(), $format, $context);
        } catch (UnauthorizedException $e) {
            return null;
        }
    }

    private function getAllowedClasses(): array
    {
        return [
            ContentTypeGroup::class,
            ContentType::class,
            Value::class,
        ];
    }

    public function supportsNormalization($data, ?string $format = null): bool
    {
        if (!$data instanceof VirtualProxyInterface) {
            return false;
        }

        foreach ($this->getAllowedClasses() as $class) {
            if ($data instanceof $class) {
                return true;
            }
        }

        return false;
    }

    public function hasCacheableSupportsMethod(): bool
    {
        return true;
    }
}

For QA:

Add a following controller to your application:

<?php

namespace App\Controller;

use Ibexa\Contracts\Core\Repository\ContentService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

final class TestController extends AbstractController
{
    private ContentService $contentService;

    public function __construct(ContentService $contentService)
    {
        $this->contentService = $contentService;
    }

    /**
     * @Route("/vtestcontroller/cs-12273b/{contentId}")
     */
    public function cs12273b(int $contentId): JsonResponse
    {
        $content = $this->contentService->loadContent($contentId);
        $name = $content->getField('name');

        return $this->json($name);
    }
}

Visiting address https://<YOUR_LOCAL_INSTANCE>/vtestcontroller/cs-12273b/52 will present you with an error without the change provided in this PR.

You can check that on versions prior to 5.4.40 this was working correctly.

Add to your composer.json file:

    "conflict": {
        "symfony/symfony": "*",
        "symfony/serializer": ">=5.4.40"
    },

and run composer update.

Documentation:

@Steveb-p Steveb-p changed the base branch from main to 4.6 December 23, 2024 11:51
@Steveb-p Steveb-p force-pushed the ibx-9337/fix-field-serialization branch from 658783c to bb2634f Compare December 23, 2024 12:01
@Steveb-p Steveb-p requested a review from a team December 23, 2024 12:06
@Steveb-p Steveb-p changed the title IBX-9316: Fixed failure to serialize field data post symfony/serializer 5.4.40 IBX-9337: Fixed failure to serialize field data post symfony/serializer 5.4.40 Jan 2, 2025
Co-authored-by: Konrad Oboza <[email protected]>
Co-authored-by: Konrad Oboza <[email protected]>
Copy link

sonarqubecloud bot commented Jan 7, 2025

@Steveb-p Steveb-p merged commit cd57d58 into 4.6 Jan 9, 2025
22 checks passed
@Steveb-p Steveb-p deleted the ibx-9337/fix-field-serialization branch January 9, 2025 13:09
@Steveb-p
Copy link
Contributor Author

Steveb-p commented Jan 9, 2025

Merged & merged up into main in e8a4b95.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants