api-platform / core

The server component of API Platform: hypermedia and GraphQL APIs in minutes
https://api-platform.com
MIT License
2.39k stars 846 forks source link

[3.3] Object to update failed to load into security context #6341

Closed latenzio closed 1 month ago

latenzio commented 2 months ago

API Platform version(s) affected: 3.3.0

Description
After update from 3.2.22 to 3.3.0 my PATCH and PUT endpoints do not behave expected. The object is not loaded anymore, so my security context definitions fail.

How to reproduce


use ApiPlatform\Metadata as APM;

#[APM\ApiResource(
    operations: [
        new APM\Patch(
            uriTemplate: '/users/{username}',
            uriVariables: [
                'clientId' => new APM\Link(toProperty: 'client', fromClass: Client::class),
                'username' => new APM\Link(fromClass: User::class),
            ],
            requirements: ['username' => '.+'],
            status: Response::HTTP_ACCEPTED,
            security: "is_granted('".Roles::FRONTEND_DEFAULT."')".
            " and is_granted('".Roles::FRONTEND_CLIENT_READ."', object.getClient())".
            " and is_granted('".Roles::FRONTEND_USER_UPDATE."', object)",
            input: Update::class,
            output: false,
            processor: UpdateProcessor::class,
        ),
    ],
    routePrefix: '/{clientId}',
)]

Possible Solution
I have no idea.

Additional Context
Occurs in dev mode on docker container and also in prod on live server

soyuka commented 2 months ago

interesting what is the use_symfony_listener value on your configuration? can we also get the backward compatibility flags you use (usually inside defaults.extra_properties

latenzio commented 2 months ago

@soyuka thx for your quick reply. this is my configuration in packages/api_platform.yaml:

api_platform:
    title: 'Hello API Platform'
    version: 1.0.0
    formats:
        jsonld: ['application/ld+json']
    docs_formats:
        jsonld: ['application/ld+json']
        jsonopenapi: ['application/vnd.openapi+json']
        html: ['text/html']
    defaults:
        stateless: true
        cache_headers:
            vary: ['Content-Type', 'Authorization', 'Origin']
        extra_properties:
            standard_put: true
            rfc_7807_compliant_errors: true
        pagination_client_items_per_page: true
    event_listeners_backward_compatibility_layer: false
    keep_legacy_inflector: false
    swagger:
        api_keys:
            JWT:
                name: Authorization
                type: header

I was not aware of this new use_symfony_listeners option. Just tested setting this to true brings no success.

latenzio commented 2 months ago

Here is the event trace before and after update. Seems that no APIPlatform listener is called anymore.

Before.pdf After.pdf

soyuka commented 2 months ago

Mhh it's weird use_symfony_listeners should load the proper services for events to work, I'll investigate that, it looks related to #6340 can you try to remove event_listeners_backward_compatibility_layer ? Btw according to your configuration you can remove event_listeners_backward_compatibility_layer: false and even don't care about use_symfony_listeners, API Platform 3.3 doesn't use listeners at all by default.

horsai commented 2 months ago

I have exactly the same problem with 3.3.0, on my PATCH and PUT endpoint. I try to remove the event_listeners_backward_compatibility_layer but nothing change.

paullallier commented 2 months ago

I'm not sure if this is related, but I ran into an issue with some of my operations with 3.2.x --> 3.3.

In 3.2.x, it uses MainController (for me) and that sets context['data'] and context['previous_data'] to the existing values from the database.

In 3.3.0, it sets the controller to Action/PlaceholderAction, which seems to cause it to take a different route which only sets context['previous_data']

I was using context['data'] in my Processor (probably wrongly) - which started to fail.

(Note, this is with use_symfony_listeners: true)

soyuka commented 2 months ago

Interesting thanks @paullallier can I see your processor @latenzio ? @horsai I think you've my email could you reach me back tomorrow if you have half an hour free so that I take a look?

latenzio commented 2 months ago

@soyuka after-with-use_symfony_listeners.pdf this is the event trace with use_symfony_listeners: true.

Here is my update Processor. There's nothing special I think.

<?php

declare(strict_types=1);

namespace App\ApiResource\State\Frontend\User;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\Validator\ValidatorInterface;
use App\ApiResource\Dto\Frontend\User\Update;
use App\Model\User\Command\UpdateUser;
use stdClass;
use Symfony\Component\Messenger\MessageBusInterface;

final readonly class UpdateProcessor implements ProcessorInterface
{
    public function __construct(
        private MessageBusInterface $commandBus,
        private ValidatorInterface  $validator
    )
    {
    }

    /** @param Update $data */
    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
    {
        $command = UpdateUser::create(
            username: (string)$uriVariables['username'],
            email: $data->email,
            enabledInBackend: new stdClass,
            enabledInFrontend: $data->enabledInFrontend,
            rolesInBackend: new stdClass,
            rolesInFrontend: $data->rolesInFrontend,
            firstname: $data->firstname,
            lastname: $data->lastname,
            department: $data->department,
            position: $data->position,
            faxNumber: $data->faxNumber,
            mobileNumber: $data->mobileNumber,
            phoneNumber: $data->phoneNumber,
        );

        $this->validator->validate($command, $operation->getValidationContext() ?? []);
        $this->commandBus->dispatch($command);
    }
}

BTW… I have 3 database connections - no one is called default. Could this be a reason?

soyuka commented 2 months ago

No, I don't understand what the issue is in your case, note that by default API Platform won't be using any listeners anymore, you can still use them using use_symfony_listeners: true. But that doesn't impact your case, the object is set at

https://github.com/api-platform/core/blob/e867d07f59b82d5f1bdca69e096ddf452dd7efc8/src/Symfony/Security/State/AccessCheckerProvider.php#L63-L70

If it's null, the ReadProvider did not call the Doctrine provider to retrieve your entity.

soyuka commented 1 month ago

okay I've a fix https://github.com/api-platform/core/issues/6347 thanks @horsai !

latenzio commented 1 month ago

@soyuka i reproduced the behavior in https://github.com/latenzio/a-p-3.3-debug. When i set output: false to the operations in App\Entity\Foo, an Exception is thrown Unable to call method "isEditable" of non-object "object".

soyuka commented 1 month ago

should be fixed now let me now if that's not the case