Skip to content

Commit 898b555

Browse files
committed
refactor(state): merge parameter and link security
1 parent 53577fd commit 898b555

21 files changed

+639
-243
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\Metadata\Exception;
15+
16+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
17+
18+
final class AccessDeniedException extends AccessDeniedHttpException implements HttpExceptionInterface
19+
{
20+
public function getStatusCode(): int
21+
{
22+
return 403;
23+
}
24+
25+
public function getHeaders(): array
26+
{
27+
return [];
28+
}
29+
}

src/Metadata/HttpOperation.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,9 @@ public function getUriVariables()
349349
return $this->uriVariables;
350350
}
351351

352+
/**
353+
* @param array<string, mixed>|array<int, Link>|list<string> $uriVariables
354+
*/
352355
public function withUriVariables($uriVariables): static
353356
{
354357
$self = clone $this;

src/Metadata/Parameter.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ public function getValue(mixed $default = new ParameterNotFound()): mixed
133133
return $this->extraProperties['_api_values'] ?? $default;
134134
}
135135

136+
/**
137+
* Only use this in a parameter provider, the ApiPlatform\State\Provider\ParameterProvider
138+
* resets this value to extract the correct value on each request.
139+
* It's also possible to set the `_api_query_parameters` request attribute directly and
140+
* API Platform will extract the value from there.
141+
*/
136142
public function setValue(mixed $value): static
137143
{
138144
$this->extraProperties['_api_values'] = $value;

src/Metadata/Parameters.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,19 @@ public function has(string $key, string $parameterClass = QueryParameter::class)
122122
return false;
123123
}
124124

125+
/**
126+
* @return list<string>
127+
*/
128+
public function keys(): array
129+
{
130+
$keys = [];
131+
foreach ($this->parameters as [$key]) {
132+
$keys[] = $key;
133+
}
134+
135+
return $keys;
136+
}
137+
125138
public function count(): int
126139
{
127140
return \count($this->parameters);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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\State\ParameterProvider;
15+
16+
use ApiPlatform\Metadata\IriConverterInterface;
17+
use ApiPlatform\Metadata\Operation;
18+
use ApiPlatform\Metadata\Parameter;
19+
use ApiPlatform\State\ParameterNotFound;
20+
use ApiPlatform\State\ParameterProviderInterface;
21+
22+
/**
23+
* @experimental
24+
*
25+
* @author Vincent Amstoutz
26+
*/
27+
final readonly class IriConverterParameterProvider implements ParameterProviderInterface
28+
{
29+
public function __construct(
30+
private IriConverterInterface $iriConverter,
31+
) {
32+
}
33+
34+
public function provide(Parameter $parameter, array $parameters = [], array $context = []): ?Operation
35+
{
36+
$operation = $context['operation'] ?? null;
37+
if (!($value = $parameter->getValue()) || $value instanceof ParameterNotFound) {
38+
return $operation;
39+
}
40+
41+
if (\is_array($value)) {
42+
$entities = [];
43+
foreach ($value as $v) {
44+
$entities[] = $this->iriConverter->getResourceFromIri($v, [
45+
'fetch_data' => $parameter->getExtraProperties()['fetch_data'] ?? false,
46+
]);
47+
}
48+
49+
$parameter->setValue($entities);
50+
} else {
51+
$parameter->setValue($this->iriConverter->getResourceFromIri($value, [
52+
'fetch_data' => $parameter->getExtraProperties()['fetch_data'] ?? false,
53+
]));
54+
}
55+
56+
return $operation;
57+
}
58+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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\State\ParameterProvider;
15+
16+
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
17+
use ApiPlatform\Metadata\HttpOperation;
18+
use ApiPlatform\Metadata\Link;
19+
use ApiPlatform\Metadata\Operation;
20+
use ApiPlatform\Metadata\Parameter;
21+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
22+
use ApiPlatform\State\Exception\ProviderNotFoundException;
23+
use ApiPlatform\State\ParameterProviderInterface;
24+
use ApiPlatform\State\ProviderInterface;
25+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
26+
27+
/**
28+
* Checks if the linked resources have security attributes and prepares them for access checking.
29+
*
30+
* @experimental
31+
*/
32+
final class ReadLinkParameterProvider implements ParameterProviderInterface
33+
{
34+
/**
35+
* @param ProviderInterface<object> $locator
36+
*/
37+
public function __construct(
38+
private readonly ProviderInterface $locator,
39+
private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
40+
) {
41+
}
42+
43+
public function provide(Parameter $parameter, array $parameters = [], array $context = []): ?Operation
44+
{
45+
$operation = $context['operation'];
46+
$extraProperties = $parameter->getExtraProperties();
47+
48+
if ($parameter instanceof Link) {
49+
$linkClass = $parameter->getFromClass() ?? $parameter->getToClass();
50+
$securityObjectName = $parameter->getSecurityObjectName() ?? $parameter->getToProperty() ?? $parameter->getFromProperty();
51+
}
52+
53+
$securityObjectName ??= $parameter->getKey();
54+
55+
$linkClass ??= $extraProperties['resource_class'] ?? $operation->getClass();
56+
57+
if (!$linkClass) {
58+
return $operation;
59+
}
60+
61+
$linkOperation = $this->resourceMetadataCollectionFactory
62+
->create($linkClass)
63+
->getOperation($operation->getExtraProperties()['parent_uri_template'] ?? $extraProperties['uri_template'] ?? null);
64+
65+
$value = $parameter->getValue();
66+
67+
if (\is_array($value) && array_is_list($value)) {
68+
$relation = [];
69+
70+
foreach ($value as $v) {
71+
try {
72+
$r = $relation[] = $this->locator->provide($linkOperation, $this->getUriVariables($v, $parameter, $linkOperation), $context);
73+
} catch (ProviderNotFoundException) {
74+
}
75+
}
76+
} else {
77+
try {
78+
$relation = $this->locator->provide($linkOperation, $this->getUriVariables($value, $parameter, $linkOperation), $context);
79+
} catch (ProviderNotFoundException) {
80+
$relation = null;
81+
}
82+
}
83+
84+
$parameter->setValue($relation);
85+
86+
if (null === $relation && true === ($extraProperties['throw_not_found'] ?? true)) {
87+
throw new NotFoundHttpException('Relation for link security not found.');
88+
}
89+
90+
$context['request']?->attributes->set($securityObjectName, $relation);
91+
92+
return $operation;
93+
}
94+
95+
/**
96+
* @return array<string, string>
97+
*/
98+
private function getUriVariables(mixed $value, Parameter $parameter, Operation $operation): array
99+
{
100+
$extraProperties = $parameter->getExtraProperties();
101+
102+
if ($operation instanceof HttpOperation) {
103+
$links = $operation->getUriVariables();
104+
} elseif ($operation instanceof GraphQlOperation) {
105+
$links = $operation->getLinks();
106+
} else {
107+
$links = [];
108+
}
109+
110+
if (!\is_array($value)) {
111+
return [1 === \count($links) ? current($links)->getKey() : ($extraProperties['uri_variable'] ?? $parameter->getKey()) => $value];
112+
}
113+
114+
return $value;
115+
}
116+
}

0 commit comments

Comments
 (0)