Skip to content

Commit 4830f8b

Browse files
committed
feat(laravel): parameter validator + security
1 parent fa430c6 commit 4830f8b

File tree

4 files changed

+122
-14
lines changed

4 files changed

+122
-14
lines changed

src/Laravel/ApiPlatformProvider.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
use ApiPlatform\Laravel\Routing\SkolemIriConverter;
108108
use ApiPlatform\Laravel\Security\ResourceAccessChecker;
109109
use ApiPlatform\Laravel\State\AccessCheckerProvider;
110+
use ApiPlatform\Laravel\State\ParameterValidatorProvider;
110111
use ApiPlatform\Laravel\State\SwaggerUiProcessor;
111112
use ApiPlatform\Laravel\State\SwaggerUiProvider;
112113
use ApiPlatform\Laravel\State\ValidateProvider;
@@ -175,6 +176,7 @@
175176
use ApiPlatform\State\Provider\DeserializeProvider;
176177
use ApiPlatform\State\Provider\ParameterProvider;
177178
use ApiPlatform\State\Provider\ReadProvider;
179+
use ApiPlatform\State\Provider\SecurityParameterProvider;
178180
use ApiPlatform\State\ProviderInterface;
179181
use ApiPlatform\State\SerializerContextBuilderInterface;
180182
use Illuminate\Config\Repository as ConfigRepository;
@@ -463,7 +465,15 @@ public function register(): void
463465
$this->app->singleton(ParameterProvider::class, function (Application $app) {
464466
$tagged = iterator_to_array($app->tagged(ParameterProviderInterface::class));
465467

466-
return new ParameterProvider($app->make(DeserializeProvider::class), new ServiceLocator($tagged));
468+
return new ParameterProvider(
469+
new ParameterValidatorProvider(
470+
new SecurityParameterProvider(
471+
$app->make(DeserializeProvider::class),
472+
$app->make(ResourceAccessCheckerInterface::class)
473+
),
474+
),
475+
new ServiceLocator($tagged)
476+
);
467477
});
468478

469479
$this->app->singleton(AccessCheckerProvider::class, function (Application $app) {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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\Laravel\State;
15+
16+
use ApiPlatform\Metadata\Operation;
17+
use ApiPlatform\State\ParameterNotFound;
18+
use ApiPlatform\State\ProviderInterface;
19+
use ApiPlatform\State\Util\ParameterParserTrait;
20+
use Illuminate\Support\Facades\Validator;
21+
use Illuminate\Validation\ValidationException;
22+
use Symfony\Component\HttpFoundation\Request;
23+
24+
/**
25+
* Validates parameters using the Symfony validator.
26+
*
27+
* @experimental
28+
*/
29+
final class ParameterValidatorProvider implements ProviderInterface
30+
{
31+
use ParameterParserTrait;
32+
use ValidationErrorTrait;
33+
34+
public function __construct(
35+
private readonly ?ProviderInterface $decorated = null,
36+
) {
37+
}
38+
39+
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
40+
{
41+
if (!($request = $context['request']) instanceof Request) {
42+
return $this->decorated->provide($operation, $uriVariables, $context);
43+
}
44+
45+
$operation = $request->attributes->get('_api_operation') ?? $operation;
46+
if (!($operation->getQueryParameterValidationEnabled() ?? true)) {
47+
return $this->decorated->provide($operation, $uriVariables, $context);
48+
}
49+
50+
$allConstraints = [];
51+
foreach ($operation->getParameters() ?? [] as $parameter) {
52+
if (!$constraints = $parameter->getConstraints()) {
53+
continue;
54+
}
55+
56+
$key = $parameter->getKey();
57+
$value = $parameter->getValue();
58+
if ($value instanceof ParameterNotFound) {
59+
$value = null;
60+
}
61+
62+
foreach ($constraints as $c) {
63+
$allConstraints[] = $c;
64+
}
65+
}
66+
67+
$validator = Validator::make($request->query(), $allConstraints);
68+
if ($validator->fails()) {
69+
throw $this->getValidationError($validator, new ValidationException($validator));
70+
}
71+
72+
return $this->decorated->provide($operation, $uriVariables, $context);
73+
}
74+
}

src/Laravel/State/ValidateProvider.php

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

1414
namespace ApiPlatform\Laravel\State;
1515

16-
use ApiPlatform\Laravel\ApiResource\ValidationError;
1716
use ApiPlatform\Metadata\Error;
1817
use ApiPlatform\Metadata\Operation;
1918
use ApiPlatform\State\ProviderInterface;
@@ -27,6 +26,8 @@
2726
*/
2827
final class ValidateProvider implements ProviderInterface
2928
{
29+
use ValidationErrorTrait;
30+
3031
/**
3132
* @param ProviderInterface<object> $inner
3233
*/
@@ -65,21 +66,11 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
6566
return $body;
6667
}
6768

68-
$validator = Validator::make($request->all(), $rules);
69+
$validator = Validator::make($request->body(), $rules);
6970
if ($validator->fails()) {
70-
throw $this->getValidationError(new ValidationException($validator));
71+
throw $this->getValidationError($validator, new ValidationException($validator));
7172
}
7273

7374
return $body;
7475
}
75-
76-
private function getValidationError(ValidationException $e): ValidationError
77-
{
78-
$violations = [];
79-
foreach ($e->validator->errors()->messages() as $prop => $message) {
80-
$violations[] = ['propertyPath' => $prop, 'message' => implode(\PHP_EOL, $message)];
81-
}
82-
83-
return new ValidationError($e->getMessage(), spl_object_hash($e), $e, $violations);
84-
}
8576
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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\Laravel\State;
15+
16+
use ApiPlatform\Laravel\ApiResource\ValidationError;
17+
use Illuminate\Contracts\Validation\Validator;
18+
use Illuminate\Validation\ValidationException;
19+
20+
trait ValidationErrorTrait
21+
{
22+
private function getValidationError(Validator $validator, ValidationException $e): ValidationError
23+
{
24+
$errors = $validator->errors();
25+
$violations = [];
26+
$id = hash('xxh3', implode(',', $errors->keys()));
27+
foreach ($errors->messages() as $prop => $message) {
28+
$violations[] = ['propertyPath' => $prop, 'message' => implode(\PHP_EOL, $message)];
29+
}
30+
31+
return new ValidationError($e->getMessage(), $id, $e, $violations);
32+
}
33+
}

0 commit comments

Comments
 (0)