Skip to content

Commit f7f2d19

Browse files
committed
test: mitigates #7194 with link constraints
1 parent 6e482c0 commit f7f2d19

File tree

7 files changed

+125
-25
lines changed

7 files changed

+125
-25
lines changed

src/Metadata/Link.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Metadata;
1515

1616
use ApiPlatform\OpenApi;
17+
use Symfony\Component\TypeInfo\Type;
1718

1819
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_PARAMETER)]
1920
final class Link extends Parameter
@@ -27,7 +28,8 @@ public function __construct(
2728
private ?array $identifiers = null,
2829
private ?bool $compositeIdentifier = null,
2930
private ?string $expandedValue = null,
30-
?string $security = null,
31+
32+
string|\Stringable|null $security = null,
3133
?string $securityMessage = null,
3234
private ?string $securityObjectName = null,
3335

@@ -37,9 +39,15 @@ public function __construct(
3739
mixed $provider = null,
3840
mixed $filter = null,
3941
?string $property = null,
42+
?array $properties = null,
4043
?string $description = null,
4144
?bool $required = null,
4245
array $extraProperties = [],
46+
47+
mixed $constraints = null,
48+
array|string|null $filterContext = null,
49+
?Type $nativeType = null,
50+
?bool $castToArray = null,
4351
) {
4452
// For the inverse property shortcut
4553
if ($this->parameterName && class_exists($this->parameterName)) {
@@ -53,11 +61,16 @@ public function __construct(
5361
provider: $provider,
5462
filter: $filter,
5563
property: $property,
64+
properties: $properties,
5665
description: $description,
5766
required: $required,
67+
constraints: $constraints,
5868
security: $security,
5969
securityMessage: $securityMessage,
60-
extraProperties: $extraProperties
70+
extraProperties: $extraProperties,
71+
filterContext: $filterContext,
72+
nativeType: $nativeType,
73+
castToArray: $castToArray,
6174
);
6275
}
6376

src/Metadata/Tests/Resource/Factory/UriTemplateResourceMetadataCollectionFactoryTest.php

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -135,27 +135,60 @@ class: AttributeResource::class,
135135
$this->assertEquals(
136136
new ResourceMetadataCollection(AttributeResource::class, [
137137
new ApiResource(
138-
uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id')],
138+
uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id', key: 'id')],
139139
shortName: 'AttributeResource',
140140
class: AttributeResource::class,
141141
operations: [
142-
'_api_/attribute_resources/{id}{._format}_get' => new Get(uriTemplate: '/attribute_resources/{id}{._format}', shortName: 'AttributeResource', class: AttributeResource::class, controller: 'api_platform.action.placeholder', uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id')], name: '_api_/attribute_resources/{id}{._format}_get'),
143-
'_api_/attribute_resources/{id}{._format}_put' => new Put(uriTemplate: '/attribute_resources/{id}{._format}', shortName: 'AttributeResource', class: AttributeResource::class, controller: 'api_platform.action.placeholder', uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id')], name: '_api_/attribute_resources/{id}{._format}_put'),
144-
'_api_/attribute_resources/{id}{._format}_delete' => new Delete(uriTemplate: '/attribute_resources/{id}{._format}', shortName: 'AttributeResource', class: AttributeResource::class, controller: 'api_platform.action.placeholder', uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id')], name: '_api_/attribute_resources/{id}{._format}_delete'),
145-
'_api_/attribute_resources{._format}_get_collection' => new GetCollection(uriTemplate: '/attribute_resources{._format}', shortName: 'AttributeResource', class: AttributeResource::class, controller: 'api_platform.action.placeholder', name: '_api_/attribute_resources{._format}_get_collection'),
142+
'_api_/attribute_resources/{id}{._format}_get' => new Get(
143+
uriTemplate: '/attribute_resources/{id}{._format}',
144+
shortName: 'AttributeResource',
145+
class: AttributeResource::class,
146+
controller: 'api_platform.action.placeholder',
147+
uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id', key: 'id')],
148+
name: '_api_/attribute_resources/{id}{._format}_get',
149+
),
150+
'_api_/attribute_resources/{id}{._format}_put' => new Put(
151+
uriTemplate: '/attribute_resources/{id}{._format}',
152+
shortName: 'AttributeResource',
153+
class: AttributeResource::class,
154+
controller: 'api_platform.action.placeholder',
155+
uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id', key: 'id')],
156+
name: '_api_/attribute_resources/{id}{._format}_put',
157+
),
158+
'_api_/attribute_resources/{id}{._format}_delete' => new Delete(
159+
uriTemplate: '/attribute_resources/{id}{._format}',
160+
shortName: 'AttributeResource',
161+
class: AttributeResource::class,
162+
controller: 'api_platform.action.placeholder',
163+
uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id', key: 'id')],
164+
name: '_api_/attribute_resources/{id}{._format}_delete',
165+
),
166+
'_api_/attribute_resources{._format}_get_collection' => new GetCollection(
167+
uriTemplate: '/attribute_resources{._format}',
168+
shortName: 'AttributeResource',
169+
class: AttributeResource::class,
170+
controller: 'api_platform.action.placeholder',
171+
name: '_api_/attribute_resources{._format}_get_collection',
172+
),
146173
]
147174
),
148175
new ApiResource(
149176
shortName: 'AttributeResource',
150177
class: AttributeResource::class,
151178
uriTemplate: '/dummy/{dummyId}/attribute_resources/{id}',
152-
uriVariables: ['dummyId' => new Link(fromClass: Dummy::class, identifiers: ['id'], parameterName: 'dummyId'), 'id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id')],
179+
uriVariables: [
180+
'dummyId' => new Link(fromClass: Dummy::class, identifiers: ['id'], parameterName: 'dummyId', key: 'dummyId'),
181+
'id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id', key: 'id'),
182+
],
153183
operations: [
154184
'_api_/dummy/{dummyId}/attribute_resources/{id}_get' => new Get(
155185
class: AttributeResource::class,
156186
uriTemplate: '/dummy/{dummyId}/attribute_resources/{id}',
157187
shortName: 'AttributeResource',
158-
uriVariables: ['dummyId' => new Link(fromClass: Dummy::class, identifiers: ['id'], parameterName: 'dummyId'), 'id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id')],
188+
uriVariables: [
189+
'dummyId' => new Link(fromClass: Dummy::class, identifiers: ['id'], parameterName: 'dummyId', key: 'dummyId'),
190+
'id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id', key: 'id'),
191+
],
159192
extraProperties: ['user_defined_uri_template' => true],
160193
name: '_api_/dummy/{dummyId}/attribute_resources/{id}_get'
161194
),
@@ -165,32 +198,38 @@ class: AttributeResource::class,
165198
shortName: 'AttributeResource',
166199
class: AttributeResource::class,
167200
uriTemplate: '/attribute_resources/by_name/{name}',
168-
uriVariables: ['name' => new Link(fromClass: AttributeResource::class, identifiers: ['name'], parameterName: 'name')],
201+
uriVariables: ['name' => new Link(fromClass: AttributeResource::class, identifiers: ['name'], parameterName: 'name', key: 'name')],
169202
operations: [],
170203
),
171204
new ApiResource(
172205
shortName: 'AttributeResource',
173206
class: AttributeResource::class,
174207
uriTemplate: '/attribute_resources/by_name/{name}',
175-
uriVariables: ['name' => new Link(fromClass: AttributeResource::class, identifiers: ['name'], parameterName: 'name')],
208+
uriVariables: ['name' => new Link(fromClass: AttributeResource::class, identifiers: ['name'], parameterName: 'name', key: 'name')],
176209
operations: [],
177210
),
178211
new ApiResource(
179212
shortName: 'AttributeResource',
180213
class: AttributeResource::class,
181214
uriTemplate: '/dummy/{dummyId}/attribute_resources/{id}',
182-
uriVariables: ['dummyId' => new Link(fromClass: Dummy::class, identifiers: [], parameterName: 'dummyId', fromProperty: 'id'), 'id' => new Link(fromClass: AttributeResource::class, identifiers: [], parameterName: 'id', fromProperty: 'id')],
215+
uriVariables: [
216+
'dummyId' => new Link(fromClass: Dummy::class, identifiers: [], parameterName: 'dummyId', fromProperty: 'id', key: 'dummyId'),
217+
'id' => new Link(fromClass: AttributeResource::class, identifiers: [], parameterName: 'id', fromProperty: 'id', key: 'id'),
218+
],
183219
operations: [],
184220
),
185221
new ApiResource(
186222
shortName: 'AttributeResource',
187223
class: AttributeResource::class,
188224
uriTemplate: '/dummy/{dummyId}/attribute_resources/{id}',
189-
uriVariables: ['dummyId' => new Link(fromClass: Dummy::class, identifiers: ['id'], parameterName: 'dummyId'), 'id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id')],
225+
uriVariables: [
226+
'dummyId' => new Link(fromClass: Dummy::class, identifiers: ['id'], parameterName: 'dummyId', key: 'dummyId'),
227+
'id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id', key: 'id'),
228+
],
190229
operations: [],
191230
),
192231
new ApiResource(
193-
uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id')],
232+
uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id', key: 'id')],
194233
shortName: 'AttributeResource',
195234
class: AttributeResource::class,
196235
operations: [
@@ -199,7 +238,7 @@ class: AttributeResource::class,
199238
shortName: 'AttributeResource',
200239
class: AttributeResource::class,
201240
controller: 'api_platform.action.placeholder',
202-
uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id')],
241+
uriVariables: ['id' => new Link(fromClass: AttributeResource::class, identifiers: ['id'], parameterName: 'id', key: 'id')],
203242
routePrefix: '/prefix',
204243
name: '_api_/prefix/attribute_resources/{id}{._format}_get'),
205244
]

src/State/Provider/SecurityParameterProvider.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
5656

5757
if ($operation instanceof HttpOperation) {
5858
foreach ($operation->getUriVariables() ?? [] as $key => $uriVariable) {
59+
if ($uriVariable->getValue() instanceof ParameterNotFound) {
60+
$uriVariable->setValue($uriVariables[$key] ?? new ParameterNotFound());
61+
}
62+
5963
$parameters->add($key, $uriVariable->withKey($key));
6064
}
6165
}
@@ -69,7 +73,6 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
6973
$value = $parameter->getValue();
7074
if ($parameter instanceof Link) {
7175
$targetResource = $parameter->getFromClass() ?? $parameter->getToClass() ?? null;
72-
$value = $uriVariables[$parameter->getKey()] ?? new ParameterNotFound();
7376
}
7477

7578
if ($value instanceof ParameterNotFound) {

src/Symfony/Validator/State/ParameterValidatorProvider.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
namespace ApiPlatform\Symfony\Validator\State;
1515

16+
use ApiPlatform\Metadata\HttpOperation;
1617
use ApiPlatform\Metadata\Operation;
1718
use ApiPlatform\Metadata\Parameter;
19+
use ApiPlatform\Metadata\Parameters;
1820
use ApiPlatform\State\ParameterNotFound;
1921
use ApiPlatform\State\ProviderInterface;
2022
use ApiPlatform\State\Util\ParameterParserTrait;
@@ -52,12 +54,25 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
5254
}
5355

5456
$constraintViolationList = new ConstraintViolationList();
55-
foreach ($operation->getParameters() ?? [] as $parameter) {
57+
$parameters = $operation->getParameters() ?? new Parameters();
58+
59+
if ($operation instanceof HttpOperation) {
60+
foreach ($operation->getUriVariables() ?? [] as $key => $uriVariable) {
61+
if ($uriVariable->getValue() instanceof ParameterNotFound) {
62+
$uriVariable->setValue($uriVariables[$key] ?? new ParameterNotFound());
63+
}
64+
65+
$parameters->add($key, $uriVariable->withKey($key));
66+
}
67+
}
68+
69+
foreach ($parameters as $parameter) {
5670
if (!$constraints = $parameter->getConstraints()) {
5771
continue;
5872
}
5973

6074
$value = $parameter->getValue();
75+
6176
if ($value instanceof ParameterNotFound) {
6277
$value = null;
6378
}

tests/Fixtures/TestBundle/Entity/Company.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@
2323
use Doctrine\ORM\Mapping as ORM;
2424
use Symfony\Component\Serializer\Annotation\Groups;
2525

26+
#[ApiResource]
27+
#[GetCollection]
28+
#[Get]
2629
#[NotExposed(
2730
uriTemplate: '/company-by-name/{name}',
2831
provider: [self::class, 'provideCompanyByName'],
2932
uriVariables: ['name']
3033
)]
31-
#[ApiResource]
32-
#[GetCollection]
33-
#[Get]
3434
#[Post]
3535
#[ApiResource(
3636
uriTemplate: '/employees/{employeeId}/company',

tests/Fixtures/TestBundle/Entity/Employee.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use ApiPlatform\Metadata\Post;
2121
use Doctrine\ORM\Mapping as ORM;
2222
use Symfony\Component\Serializer\Annotation\Groups;
23+
use Symfony\Component\Validator\Constraints\IdenticalTo;
2324

2425
#[ApiResource]
2526
#[Post]
@@ -46,8 +47,9 @@
4647
identifiers: ['name'],
4748
fromClass: Company::class,
4849
toProperty: 'company',
49-
security: 'company.name == "Test"',
50-
extraProperties: ['uri_template' => '/company-by-name/{name}']
50+
security: 'company.name == "Test" or company.name == "NotTest"',
51+
extraProperties: ['uri_template' => '/company-by-name/{name}'],
52+
constraints: [new IdenticalTo('Test')]
5153
),
5254
],
5355
)]

tests/Functional/Parameters/LinkProviderParameterTest.php

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ public function testReadDummyProviderFromQueryParameterNoNotFound(): void
115115
*/
116116
public function testLinkSecurityWithSlug(): void
117117
{
118+
$container = static::getContainer();
119+
if ('mongodb' === $container->getParameter('kernel.environment')) {
120+
$this->markTestSkipped();
121+
}
122+
118123
$manager = $this->getManager();
119124
$employee = new Employee();
120125
$employee->setName('me');
@@ -125,13 +130,36 @@ public function testLinkSecurityWithSlug(): void
125130
$manager->persist($dummy);
126131
$manager->flush();
127132

133+
self::createClient()->request('GET', '/companies-by-name/Test/employees');
134+
self::assertJsonContains([
135+
'hydra:member' => [
136+
['company' => ['name' => 'Test']],
137+
],
138+
]);
139+
self::assertResponseStatusCodeSame(200);
140+
}
141+
142+
/**
143+
* See https://github.com/api-platform/core/issues/7061.
144+
*/
145+
public function testLinkSecurityWithConstraint(): void
146+
{
128147
$container = static::getContainer();
129148
if ('mongodb' === $container->getParameter('kernel.environment')) {
130149
$this->markTestSkipped();
131150
}
132151

133-
$response = self::createClient()->request('GET', '/companies-by-name/Test/employees');
134-
dd($response->toArray(false));
135-
self::assertEquals(200, $response->getStatusCode());
152+
$manager = $this->getManager();
153+
$employee = new Employee();
154+
$employee->setName('me');
155+
$dummy = new Company();
156+
$dummy->setName('Test');
157+
$employee->setCompany($dummy);
158+
$manager->persist($employee);
159+
$manager->persist($dummy);
160+
$manager->flush();
161+
162+
$response = self::createClient()->request('GET', '/companies-by-name/NotTest/employees');
163+
self::assertEquals(422, $response->getStatusCode());
136164
}
137165
}

0 commit comments

Comments
 (0)