Skip to content

Commit c59b53f

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

File tree

7 files changed

+104
-16
lines changed

7 files changed

+104
-16
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: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,37 @@ class: AttributeResource::class,
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')],
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')],
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')],
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(

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
@@ -130,8 +130,36 @@ public function testLinkSecurityWithSlug(): void
130130
$this->markTestSkipped();
131131
}
132132

133-
$response = self::createClient()->request('GET', '/companies-by-name/Test/employees');
134-
dd($response->toArray(false));
135-
self::assertEquals(200, $response->getStatusCode());
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+
{
147+
$manager = $this->getManager();
148+
$employee = new Employee();
149+
$employee->setName('me');
150+
$dummy = new Company();
151+
$dummy->setName('Test');
152+
$employee->setCompany($dummy);
153+
$manager->persist($employee);
154+
$manager->persist($dummy);
155+
$manager->flush();
156+
157+
$container = static::getContainer();
158+
if ('mongodb' === $container->getParameter('kernel.environment')) {
159+
$this->markTestSkipped();
160+
}
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)