Skip to content

Commit 57f15cf

Browse files
authoredDec 16, 2024··
feat(state): strict query parameters (#6399)
* feat(state): strict query parameters * fixes
1 parent bd0e929 commit 57f15cf

23 files changed

+195
-1
lines changed
 

‎src/Metadata/ApiResource.php

+2
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,7 @@ public function __construct(
963963
?string $policy = null,
964964
array|string|null $middleware = null,
965965
array|Parameters|null $parameters = null,
966+
protected ?bool $strictQueryParameterValidation = null,
966967
protected array $extraProperties = [],
967968
) {
968969
parent::__construct(
@@ -1007,6 +1008,7 @@ class: $class,
10071008
rules: $rules,
10081009
policy: $policy,
10091010
middleware: $middleware,
1011+
strictQueryParameterValidation: $strictQueryParameterValidation,
10101012
extraProperties: $extraProperties
10111013
);
10121014

‎src/Metadata/Delete.php

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public function __construct(
9898
mixed $rules = null,
9999
?string $policy = null,
100100
array|string|null $middleware = null,
101+
?bool $strictQueryParameterValidation = null,
101102
array $extraProperties = [],
102103
) {
103104
parent::__construct(
@@ -178,6 +179,7 @@ class: $class,
178179
extraProperties: $extraProperties,
179180
collectDenormalizationErrors: $collectDenormalizationErrors,
180181
parameters: $parameters,
182+
strictQueryParameterValidation: $strictQueryParameterValidation,
181183
stateOptions: $stateOptions,
182184
);
183185
}

‎src/Metadata/Extractor/XmlResourceExtractor.php

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ private function buildExtendedBase(\SimpleXMLElement $resource): array
9494
'paginationViaCursor' => $this->buildPaginationViaCursor($resource),
9595
'exceptionToStatus' => $this->buildExceptionToStatus($resource),
9696
'queryParameterValidationEnabled' => $this->phpize($resource, 'queryParameterValidationEnabled', 'bool'),
97+
'strictQueryParameterValidation' => $this->phpize($resource, 'strictQueryParameterValidation', 'bool'),
9798
'stateOptions' => $this->buildStateOptions($resource),
9899
'links' => $this->buildLinks($resource),
99100
'headers' => $this->buildHeaders($resource),

‎src/Metadata/Extractor/YamlResourceExtractor.php

+1
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ private function buildOperations(array $resource, array $root): ?array
338338
'write' => $this->phpize($operation, 'write', 'bool'),
339339
'serialize' => $this->phpize($operation, 'serialize', 'bool'),
340340
'queryParameterValidate' => $this->phpize($operation, 'queryParameterValidate', 'bool'),
341+
'strictQueryParameterValidation' => $this->phpize($operation, 'strictQueryParameterValidation', 'bool'),
341342
'priority' => $this->phpize($operation, 'priority', 'integer'),
342343
'name' => $this->phpize($operation, 'name', 'string'),
343344
'class' => (string) $class,

‎src/Metadata/Extractor/schema/resources.xsd

+1
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@
515515

516516
<xsd:attributeGroup name="extendedBase">
517517
<xsd:attributeGroup ref="base"/>
518+
<xsd:attribute type="xsd:boolean" name="strictQueryParameterValidation"/>
518519
<xsd:attribute type="xsd:boolean" name="queryParameterValidationEnabled"/>
519520
<xsd:attribute type="xsd:string" name="routePrefix"/>
520521
<xsd:attribute type="xsd:boolean" name="stateless"/>

‎src/Metadata/Get.php

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public function __construct(
9898
mixed $rules = null,
9999
?string $policy = null,
100100
array|string|null $middleware = null,
101+
?bool $strictQueryParameterValidation = null,
101102
array $extraProperties = [],
102103
) {
103104
parent::__construct(
@@ -177,6 +178,7 @@ class: $class,
177178
rules: $rules,
178179
policy: $policy,
179180
middleware: $middleware,
181+
strictQueryParameterValidation: $strictQueryParameterValidation,
180182
extraProperties: $extraProperties,
181183
);
182184
}

‎src/Metadata/GetCollection.php

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public function __construct(
9898
array|string|null $rules = null,
9999
?string $policy = null,
100100
array|string|null $middleware = null,
101+
?bool $strictQueryParameterValidation = null,
101102
array $extraProperties = [],
102103
private ?string $itemUriTemplate = null,
103104
) {
@@ -178,6 +179,7 @@ class: $class,
178179
rules: $rules,
179180
policy: $policy,
180181
middleware: $middleware,
182+
strictQueryParameterValidation: $strictQueryParameterValidation,
181183
stateOptions: $stateOptions,
182184
);
183185
}

‎src/Metadata/HttpOperation.php

+2
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ public function __construct(
155155
protected ?array $exceptionToStatus = null,
156156
protected ?array $links = null,
157157
protected ?array $errors = null,
158+
protected ?bool $strictQueryParameterValidation = null,
158159

159160
?string $shortName = null,
160161
?string $class = null,
@@ -257,6 +258,7 @@ class: $class,
257258
policy: $policy,
258259
middleware: $middleware,
259260
queryParameterValidationEnabled: $queryParameterValidationEnabled,
261+
strictQueryParameterValidation: $strictQueryParameterValidation,
260262
extraProperties: $extraProperties
261263
);
262264
}

‎src/Metadata/Metadata.php

+14
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public function __construct(
8181
protected ?string $policy = null,
8282
protected array|string|null $middleware = null,
8383
protected ?bool $queryParameterValidationEnabled = null,
84+
protected ?bool $strictQueryParameterValidation = null,
8485
protected array $extraProperties = [],
8586
) {
8687
if (\is_array($parameters) && $parameters) {
@@ -666,4 +667,17 @@ public function withExtraProperties(array $extraProperties = []): static
666667

667668
return $self;
668669
}
670+
671+
public function getStrictQueryParameterValidation(): ?bool
672+
{
673+
return $this->strictQueryParameterValidation;
674+
}
675+
676+
public function withStrictQueryParameterValidation(bool $strictQueryParameterValidation): static
677+
{
678+
$self = clone $this;
679+
$self->strictQueryParameterValidation = $strictQueryParameterValidation;
680+
681+
return $self;
682+
}
669683
}

‎src/Metadata/Operation.php

+2
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,7 @@ public function __construct(
811811
?string $policy = null,
812812
array|string|null $middleware = null,
813813
?bool $queryParameterValidationEnabled = null,
814+
protected ?bool $strictQueryParameterValidation = null,
814815
protected array $extraProperties = [],
815816
) {
816817
parent::__construct(
@@ -856,6 +857,7 @@ class: $class,
856857
policy: $policy,
857858
middleware: $middleware,
858859
queryParameterValidationEnabled: $queryParameterValidationEnabled,
860+
strictQueryParameterValidation: $strictQueryParameterValidation,
859861
extraProperties: $extraProperties,
860862
);
861863
}

‎src/Metadata/Patch.php

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public function __construct(
9898
mixed $rules = null,
9999
?string $policy = null,
100100
array|string|null $middleware = null,
101+
?bool $strictQueryParameterValidation = null,
101102
array $extraProperties = [],
102103
) {
103104
parent::__construct(
@@ -178,6 +179,7 @@ class: $class,
178179
rules: $rules,
179180
policy: $policy,
180181
middleware: $middleware,
182+
strictQueryParameterValidation: $strictQueryParameterValidation,
181183
extraProperties: $extraProperties
182184
);
183185
}

‎src/Metadata/Post.php

+2
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public function __construct(
100100
array|string|null $middleware = null,
101101
array $extraProperties = [],
102102
private ?string $itemUriTemplate = null,
103+
?bool $strictQueryParameterValidation = null,
103104
) {
104105
parent::__construct(
105106
method: 'POST',
@@ -179,6 +180,7 @@ class: $class,
179180
rules: $rules,
180181
policy: $policy,
181182
middleware: $middleware,
183+
strictQueryParameterValidation: $strictQueryParameterValidation,
182184
extraProperties: $extraProperties
183185
);
184186
}

‎src/Metadata/Put.php

+2
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public function __construct(
9999
?string $policy = null,
100100
array|string|null $middleware = null,
101101
array $extraProperties = [],
102+
?bool $strictQueryParameterValidation = null,
102103
private ?bool $allowCreate = null,
103104
) {
104105
parent::__construct(
@@ -179,6 +180,7 @@ class: $class,
179180
rules: $rules,
180181
policy: $policy,
181182
middleware: $middleware,
183+
strictQueryParameterValidation: $strictQueryParameterValidation,
182184
extraProperties: $extraProperties
183185
);
184186
}

‎src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ final class XmlResourceAdapter implements ResourceAdapterInterface
6262
'securityPostValidation',
6363
'securityPostValidationMessage',
6464
'queryParameterValidationEnabled',
65+
'strictQueryParameterValidation',
6566
'stateOptions',
6667
'collectDenormalizationErrors',
6768
'links',

‎src/Metadata/Tests/Extractor/Adapter/resources.xml

+1-1
Large diffs are not rendered by default.

‎src/Metadata/Tests/Extractor/Adapter/resources.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ resources:
102102
exceptionToStatus:
103103
Symfony\Component\Serializer\Exception\ExceptionInterface: 404
104104
queryParameterValidationEnabled: false
105+
strictQueryParameterValidation: false
105106
read: true
106107
deserialize: false
107108
validate: false
@@ -339,6 +340,7 @@ resources:
339340
policy: null
340341
middleware: null
341342
parameters: null
343+
strictQueryParameterValidation: false
342344
extraProperties:
343345
custom_property: 'Lorem ipsum dolor sit amet'
344346
another_custom_property:

‎src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php

+3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ final class ResourceMetadataCompatibilityTest extends TestCase
9696
'securityPostValidation' => 'is_granted(\'ROLE_OWNER\')',
9797
'securityPostValidationMessage' => 'Sorry, you must the owner of this resource to access it.',
9898
'queryParameterValidationEnabled' => true,
99+
'strictQueryParameterValidation' => false,
99100
'types' => ['someirischema', 'anotheririschema'],
100101
'formats' => [
101102
'json' => null,
@@ -399,6 +400,7 @@ final class ResourceMetadataCompatibilityTest extends TestCase
399400
'Symfony\Component\Serializer\Exception\ExceptionInterface' => 404,
400401
],
401402
'queryParameterValidationEnabled' => false,
403+
'strictQueryParameterValidation' => false,
402404
'read' => true,
403405
'deserialize' => false,
404406
'validate' => false,
@@ -486,6 +488,7 @@ final class ResourceMetadataCompatibilityTest extends TestCase
486488
'condition',
487489
'controller',
488490
'queryParameterValidationEnabled',
491+
'strictQueryParameterValidation',
489492
'exceptionToStatus',
490493
'types',
491494
'formats',

‎src/Metadata/Tests/Extractor/XmlExtractorTest.php

+4
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public function testValidXML(): void
6666
'securityPostValidation' => null,
6767
'securityPostValidationMessage' => null,
6868
'queryParameterValidationEnabled' => null,
69+
'strictQueryParameterValidation' => null,
6970
'input' => null,
7071
'output' => null,
7172
'types' => null,
@@ -136,6 +137,7 @@ public function testValidXML(): void
136137
'securityPostValidation' => null,
137138
'securityPostValidationMessage' => null,
138139
'queryParameterValidationEnabled' => null,
140+
'strictQueryParameterValidation' => null,
139141
'input' => null,
140142
'output' => null,
141143
'types' => ['someirischema', 'anotheririschema'],
@@ -264,6 +266,7 @@ public function testValidXML(): void
264266
'write' => null,
265267
'serialize' => null,
266268
'queryParameterValidate' => null,
269+
'strictQueryParameterValidation' => null,
267270
'collection' => null,
268271
'method' => null,
269272
'priority' => null,
@@ -367,6 +370,7 @@ public function testValidXML(): void
367370
'write' => null,
368371
'serialize' => null,
369372
'queryParameterValidate' => null,
373+
'strictQueryParameterValidation' => null,
370374
'collection' => null,
371375
'method' => null,
372376
'priority' => null,

‎src/Metadata/Tests/Extractor/YamlExtractorTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ public function testValidYaml(): void
274274
'securityPostValidation' => null,
275275
'securityPostValidationMessage' => null,
276276
'queryParameterValidationEnabled' => null,
277+
'strictQueryParameterValidation' => null,
277278
'input' => null,
278279
'output' => null,
279280
'types' => ['someirischema'],
@@ -353,6 +354,7 @@ public function testValidYaml(): void
353354
'securityPostValidation' => null,
354355
'securityPostValidationMessage' => null,
355356
'queryParameterValidationEnabled' => null,
357+
'strictQueryParameterValidation' => null,
356358
'input' => null,
357359
'output' => null,
358360
'types' => ['anotheririschema'],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\State\Exception;
15+
16+
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
17+
use ApiPlatform\Metadata\Exception\RuntimeException;
18+
19+
final class ParameterNotSupportedException extends RuntimeException implements ProblemExceptionInterface
20+
{
21+
public function __construct(private readonly string $parameter, string $message = 'Parameter not supported', int $code = 0, ?\Throwable $previous = null)
22+
{
23+
parent::__construct($message, $code, $previous);
24+
}
25+
26+
public function getType(): string
27+
{
28+
return '/error/400';
29+
}
30+
31+
public function getTitle(): ?string
32+
{
33+
return $this->message;
34+
}
35+
36+
public function getStatus(): ?int
37+
{
38+
return 400;
39+
}
40+
41+
public function getDetail(): ?string
42+
{
43+
return \sprintf('Parameter "%s" not supported', $this->parameter);
44+
}
45+
46+
public function getInstance(): ?string
47+
{
48+
return $this->parameter;
49+
}
50+
}

‎src/State/Provider/ParameterProvider.php

+16
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
namespace ApiPlatform\State\Provider;
1515

16+
use ApiPlatform\Metadata\HttpOperation;
1617
use ApiPlatform\Metadata\Operation;
18+
use ApiPlatform\State\Exception\ParameterNotSupportedException;
1719
use ApiPlatform\State\Exception\ProviderNotFoundException;
1820
use ApiPlatform\State\ParameterNotFound;
1921
use ApiPlatform\State\ParameterProviderInterface;
@@ -50,6 +52,20 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
5052
}
5153

5254
$parameters = $operation->getParameters();
55+
56+
if ($operation instanceof HttpOperation && true === $operation->getStrictQueryParameterValidation()) {
57+
$keys = [];
58+
foreach ($parameters as $parameter) {
59+
$keys[] = $parameter->getKey();
60+
}
61+
62+
foreach (array_keys($request->attributes->get('_api_query_parameters')) as $key) {
63+
if (!\in_array($key, $keys, true)) {
64+
throw new ParameterNotSupportedException($key);
65+
}
66+
}
67+
}
68+
5369
foreach ($parameters ?? [] as $parameter) {
5470
$extraProperties = $parameter->getExtraProperties();
5571
unset($extraProperties['_api_values']);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\ApiResource;
15+
16+
use ApiPlatform\Metadata\Get;
17+
use ApiPlatform\Metadata\QueryParameter;
18+
19+
#[Get(
20+
uriTemplate: 'strict_query_parameters',
21+
strictQueryParameterValidation: true,
22+
parameters: [
23+
'foo' => new QueryParameter(),
24+
],
25+
provider: [self::class, 'provider']
26+
)]
27+
class StrictParameters
28+
{
29+
public string $id;
30+
31+
public static function provider()
32+
{
33+
return new self();
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\Functional\Parameters;
15+
16+
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\StrictParameters;
18+
use ApiPlatform\Tests\SetupClassResourcesTrait;
19+
20+
final class StrictParametersTest extends ApiTestCase
21+
{
22+
use SetupClassResourcesTrait;
23+
24+
/**
25+
* @return class-string[]
26+
*/
27+
public static function getResources(): array
28+
{
29+
return [StrictParameters::class];
30+
}
31+
32+
public function testErrorAsParameterIsNotAllowed(): void
33+
{
34+
self::createClient()->request('GET', 'strict_query_parameters?bar=test');
35+
$this->assertJsonContains(['detail' => 'Parameter not supported']);
36+
$this->assertResponseStatusCodeSame(400);
37+
}
38+
39+
public function testCorrectParameters(): void
40+
{
41+
self::createClient()->request('GET', 'strict_query_parameters');
42+
$this->assertResponseStatusCodeSame(200);
43+
self::createClient()->request('GET', 'strict_query_parameters?foo=test');
44+
$this->assertResponseStatusCodeSame(200);
45+
}
46+
}

0 commit comments

Comments
 (0)
Please sign in to comment.