Skip to content

Commit d188135

Browse files
authored
fix: securityPostDenormalize not working because clone is made after denormalization (#5182)
* fix: securityPostDenormalize not working because clone is made after denormalization * test: add non-regression tests
1 parent 62af874 commit d188135

File tree

3 files changed

+110
-1
lines changed

3 files changed

+110
-1
lines changed

features/authorization/deny.feature

+27
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,33 @@ Feature: Authorization checking
8787
And the JSON node "ownerOnlyProperty" should exist
8888
And the JSON node "ownerOnlyProperty" should not be null
8989

90+
Scenario: An admin can create a secured resource with properties depending on themselves
91+
When I add "Accept" header equal to "application/ld+json"
92+
And I add "Content-Type" header equal to "application/ld+json"
93+
And I add "Authorization" header equal to "Basic YWRtaW46a2l0dGVu"
94+
And I send a "POST" request to "/secured_dummy_with_properties_depending_on_themselves" with body:
95+
"""
96+
{
97+
"canUpdateProperty": false,
98+
"property": false
99+
}
100+
"""
101+
Then the response status code should be 201
102+
103+
Scenario: A user cannot patch a secured property if not granted
104+
When I add "Content-Type" header equal to "application/merge-patch+json"
105+
And I add "Authorization" header equal to "Basic YWRtaW46a2l0dGVu"
106+
And I send a "PATCH" request to "/secured_dummy_with_properties_depending_on_themselves/1" with body:
107+
"""
108+
{
109+
"canUpdateProperty": true,
110+
"property": true
111+
}
112+
"""
113+
Then the response status code should be 200
114+
And the JSON node "canUpdateProperty" should be true
115+
And the JSON node "property" should be false
116+
90117
Scenario: An admin can't see a secured owner-only property on objects they don't own
91118
When I add "Accept" header equal to "application/ld+json"
92119
And I add "Authorization" header equal to "Basic YWRtaW46a2l0dGVu"

src/Serializer/AbstractItemNormalizer.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,14 @@ public function denormalize(mixed $data, string $class, string $format = null, a
207207
throw new UnexpectedValueException(sprintf('Expected IRI or document for resource "%s", "%s" given.', $resourceClass, \gettype($data)));
208208
}
209209

210+
$previousObject = isset($objectToPopulate) ? clone $objectToPopulate : null;
211+
210212
$object = parent::denormalize($data, $class, $format, $context);
211213

212214
if (!$this->resourceClassResolver->isResourceClass($class)) {
213215
return $object;
214216
}
215217

216-
$previousObject = isset($objectToPopulate) ? clone $objectToPopulate : null;
217218
// Revert attributes that aren't allowed to be changed after a post-denormalize check
218219
foreach (array_keys($data) as $attribute) {
219220
if (!$this->canAccessAttributePostDenormalize($object, $previousObject, $attribute, $context)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\ApiResource;
18+
use ApiPlatform\Metadata\Get;
19+
use ApiPlatform\Metadata\Patch;
20+
use ApiPlatform\Metadata\Post;
21+
use Doctrine\ORM\Mapping as ORM;
22+
23+
/**
24+
* Secured resource with properties depending on themselves.
25+
*
26+
* @author Loïc Boursin <[email protected]>
27+
*/
28+
#[ApiResource(
29+
operations: [
30+
new Get(),
31+
new Patch(inputFormats: ['json' => ['application/merge-patch+json'], 'jsonapi']),
32+
new Post(security: 'is_granted("ROLE_ADMIN")'),
33+
]
34+
)]
35+
#[ORM\Entity]
36+
class SecuredDummyWithPropertiesDependingOnThemselves
37+
{
38+
#[ORM\Column(type: 'integer')]
39+
#[ORM\Id]
40+
#[ORM\GeneratedValue(strategy: 'AUTO')]
41+
private ?int $id = null;
42+
43+
#[ORM\Column]
44+
private bool $canUpdateProperty = false;
45+
46+
/**
47+
* @var bool Special property, only writable when granted rights by another property
48+
*/
49+
#[ApiProperty(securityPostDenormalize: 'previous_object and previous_object.getCanUpdateProperty()')]
50+
#[ORM\Column]
51+
private bool $property = false;
52+
53+
public function __construct()
54+
{
55+
}
56+
57+
public function getId(): ?int
58+
{
59+
return $this->id;
60+
}
61+
62+
public function getCanUpdateProperty(): bool
63+
{
64+
return $this->canUpdateProperty;
65+
}
66+
67+
public function setCanUpdateProperty(bool $canUpdateProperty): void
68+
{
69+
$this->canUpdateProperty = $canUpdateProperty;
70+
}
71+
72+
public function getProperty(): bool
73+
{
74+
return $this->property;
75+
}
76+
77+
public function setProperty(bool $property): void
78+
{
79+
$this->property = $property;
80+
}
81+
}

0 commit comments

Comments
 (0)