Skip to content

Commit 16f14c8

Browse files
committed
fix: backport for legacy event system as well
1 parent 76e035b commit 16f14c8

File tree

4 files changed

+152
-8
lines changed

4 files changed

+152
-8
lines changed

src/State/Provider/ReadProvider.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
106106
->create($relationClass)
107107
->getOperation($operation->getExtraProperties()['parent_uri_template'] ?? null);
108108
try {
109-
$relation = $this->provider->provide($parentOperation, [$uriVariable->getIdentifiers()[0] => $context['request']->attributes->all()[$key]], $context);
109+
$relation = $this->provider->provide($parentOperation, [$uriVariable->getIdentifiers()[0] => $request?->attributes->all()[$key]], $context);
110110
} catch (ProviderNotFoundException) {
111111
$relation = null;
112112
}
@@ -124,8 +124,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
124124
if (!$securityObjectName) {
125125
continue;
126126
}
127-
var_dump($securityObjectName);
128-
$request->attributes->set($securityObjectName, $relation);
127+
$request?->attributes->set($securityObjectName, $relation);
129128
} catch (InvalidIdentifierException|InvalidUriVariableException $e) {
130129
throw new NotFoundHttpException('Invalid identifier value or configuration.', $e);
131130
}

src/Symfony/EventListener/DenyAccessListener.php

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Symfony\EventListener;
1515

16+
use ApiPlatform\Metadata\Link;
1617
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
1718
use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
1819
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
@@ -89,17 +90,31 @@ private function checkSecurity(Request $request, string $attribute, array $extra
8990
$message = $operation->getSecurityMessage();
9091
}
9192

92-
if (null === $isGranted) {
93-
return;
94-
}
95-
9693
$extraVariables += $request->attributes->all();
9794
$extraVariables['object'] = $request->attributes->get('data');
9895
$extraVariables['previous_object'] = $request->attributes->get('previous_data');
9996
$extraVariables['request'] = $request;
10097

101-
if (!$this->resourceAccessChecker->isGranted($attributes['resource_class'], $isGranted, $extraVariables)) {
98+
if ($isGranted && !$this->resourceAccessChecker->isGranted($attributes['resource_class'], $isGranted, $extraVariables)) {
10299
throw new AccessDeniedException($message ?? 'Access Denied.');
103100
}
101+
102+
if ($operation->getUriVariables()) {
103+
foreach ($operation->getUriVariables() as $key => $uriVariable) {
104+
if (!$uriVariable instanceof Link || !$uriVariable->getSecurity()) {
105+
continue;
106+
}
107+
108+
$targetResource = $uriVariable->getFromClass() ?? $uriVariable->getToClass();
109+
110+
if (!$targetResource) {
111+
continue;
112+
}
113+
114+
if (!$this->resourceAccessChecker->isGranted($targetResource, $uriVariable->getSecurity(), $extraVariables)) {
115+
throw new AccessDeniedException($uriVariable->getSecurityMessage() ?? 'Access Denied.');
116+
}
117+
}
118+
}
104119
}
105120
}

src/Symfony/EventListener/ReadListener.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ApiPlatform\Api\UriVariablesConverterInterface;
1717
use ApiPlatform\Exception\InvalidIdentifierException;
1818
use ApiPlatform\Exception\InvalidUriVariableException;
19+
use ApiPlatform\Metadata\Link;
1920
use ApiPlatform\Metadata\Put;
2021
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2122
use ApiPlatform\Metadata\Util\CloneTrait;
@@ -116,6 +117,47 @@ public function onKernelRequest(RequestEvent $event): void
116117
throw new NotFoundHttpException('Not Found');
117118
}
118119

120+
if ($operation->getUriVariables()) {
121+
foreach ($operation->getUriVariables() as $key => $uriVariable) {
122+
if (!$uriVariable instanceof Link || !$uriVariable->getSecurity()) {
123+
continue;
124+
}
125+
126+
$relationClass = $uriVariable->getFromClass() ?? $uriVariable->getToClass();
127+
128+
if (!$relationClass) {
129+
continue;
130+
}
131+
132+
$parentOperation = $this->resourceMetadataCollectionFactory
133+
->create($relationClass)
134+
->getOperation($operation->getExtraProperties()['parent_uri_template'] ?? null);
135+
try {
136+
$relation = $this->provider->provide($parentOperation, [$uriVariable->getIdentifiers()[0] => $request->attributes->all()[$key]], $context);
137+
} catch (ProviderNotFoundException) {
138+
$relation = null;
139+
}
140+
if (!$relation) {
141+
throw new NotFoundHttpException('Not Found');
142+
}
143+
144+
try {
145+
$securityObjectName = $uriVariable->getSecurityObjectName();
146+
147+
if (!$securityObjectName) {
148+
$securityObjectName = $uriVariable->getToProperty() ?? $uriVariable->getFromProperty();
149+
}
150+
151+
if (!$securityObjectName) {
152+
continue;
153+
}
154+
$request->attributes->set($securityObjectName, $relation);
155+
} catch (InvalidIdentifierException|InvalidUriVariableException $e) {
156+
throw new NotFoundHttpException('Invalid identifier value or configuration.', $e);
157+
}
158+
}
159+
}
160+
119161
$request->attributes->set('data', $data);
120162
$request->attributes->set('previous_data', $this->clone($data));
121163
}

tests/Symfony/EventListener/DenyAccessListenerTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
use ApiPlatform\Metadata\ApiResource;
1717
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\GetCollection;
19+
use ApiPlatform\Metadata\Link;
1820
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
1921
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
2022
use ApiPlatform\Symfony\EventListener\DenyAccessListener;
@@ -155,6 +157,92 @@ public function testSecurityComponentNotAvailable(): void
155157
$listener->onSecurity($event);
156158
}
157159

160+
public function testIsGrantedLink(): void
161+
{
162+
$request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_operation_name' => 'get_collection']);
163+
164+
$eventProphecy = $this->prophesize(RequestEvent::class);
165+
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
166+
$event = $eventProphecy->reveal();
167+
168+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
169+
$resourceMetadataFactoryProphecy->create('Foo')->shouldBeCalled()->willReturn(new ResourceMetadataCollection('Foo', [
170+
new ApiResource(
171+
uriTemplate: '/bars/{barId}/foos',
172+
operations: [
173+
'get_collection' => new GetCollection(uriVariables: [
174+
'barId' => new Link(toProperty: 'bar', fromClass: 'Bar', security: 'is_granted("some_voter", "bar")'),
175+
], ),
176+
],
177+
),
178+
]));
179+
180+
$resourceAccessCheckerProphecy = $this->prophesize(ResourceAccessCheckerInterface::class);
181+
$resourceAccessCheckerProphecy->isGranted('Bar', 'is_granted("some_voter", "bar")', Argument::type('array'))->willReturn(true)->shouldBeCalled();
182+
183+
$listener = $this->getListener($resourceMetadataFactoryProphecy->reveal(), $resourceAccessCheckerProphecy->reveal());
184+
$listener->onSecurity($event);
185+
}
186+
187+
public function testIsNotGrantedLink(): void
188+
{
189+
$this->expectException(AccessDeniedException::class);
190+
191+
$request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_operation_name' => 'get_collection']);
192+
193+
$eventProphecy = $this->prophesize(RequestEvent::class);
194+
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
195+
$event = $eventProphecy->reveal();
196+
197+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
198+
$resourceMetadataFactoryProphecy->create('Foo')->shouldBeCalled()->willReturn(new ResourceMetadataCollection('Foo', [
199+
new ApiResource(
200+
uriTemplate: '/bars/{barId}/foos',
201+
operations: [
202+
'get_collection' => new GetCollection(uriVariables: [
203+
'barId' => new Link(toProperty: 'bar', fromClass: 'Bar', security: 'is_granted("some_voter", "bar")'),
204+
], ),
205+
],
206+
),
207+
]));
208+
209+
$resourceAccessCheckerProphecy = $this->prophesize(ResourceAccessCheckerInterface::class);
210+
$resourceAccessCheckerProphecy->isGranted('Bar', 'is_granted("some_voter", "bar")', Argument::type('array'))->willReturn(false)->shouldBeCalled();
211+
212+
$listener = $this->getListener($resourceMetadataFactoryProphecy->reveal(), $resourceAccessCheckerProphecy->reveal());
213+
$listener->onSecurity($event);
214+
}
215+
216+
public function testSecurityMessageLink(): void
217+
{
218+
$this->expectException(AccessDeniedException::class);
219+
$this->expectExceptionMessage('You are not admin.');
220+
221+
$request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_operation_name' => 'get_collection']);
222+
223+
$eventProphecy = $this->prophesize(RequestEvent::class);
224+
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
225+
$event = $eventProphecy->reveal();
226+
227+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
228+
$resourceMetadataFactoryProphecy->create('Foo')->shouldBeCalled()->willReturn(new ResourceMetadataCollection('Foo', [
229+
new ApiResource(
230+
uriTemplate: '/bars/{barId}/foos',
231+
operations: [
232+
'get_collection' => new GetCollection(uriVariables: [
233+
'barId' => new Link(toProperty: 'bar', fromClass: 'Bar', security: 'is_granted("some_voter", "bar")', securityMessage: 'You are not admin.'),
234+
], ),
235+
],
236+
),
237+
]));
238+
239+
$resourceAccessCheckerProphecy = $this->prophesize(ResourceAccessCheckerInterface::class);
240+
$resourceAccessCheckerProphecy->isGranted('Bar', 'is_granted("some_voter", "bar")', Argument::type('array'))->willReturn(false)->shouldBeCalled();
241+
242+
$listener = $this->getListener($resourceMetadataFactoryProphecy->reveal(), $resourceAccessCheckerProphecy->reveal());
243+
$listener->onSecurity($event);
244+
}
245+
158246
private function getListener(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ResourceAccessCheckerInterface $resourceAccessChecker = null): DenyAccessListener
159247
{
160248
if (null === $resourceAccessChecker) {

0 commit comments

Comments
 (0)