Skip to content

Commit b5ce23f

Browse files
committed
refactor(state): merge parameter and link security
1 parent e6e7760 commit b5ce23f

File tree

18 files changed

+364
-242
lines changed

18 files changed

+364
-242
lines changed

src/Hydra/Serializer/CollectionFiltersNormalizer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ public function normalize(mixed $object, ?string $format = null, array $context
8484
$resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class']);
8585
$operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($context['operation_name'] ?? null);
8686

87-
$parameters = $operation->getParameters();
87+
$parameters = $operation->getParameters() ?? new Parameters();
8888
$resourceFilters = $operation->getFilters();
89-
if (!$resourceFilters && !$parameters) {
89+
if (!$resourceFilters && 0 === \count($parameters)) {
9090
return $data;
9191
}
9292

@@ -103,7 +103,7 @@ public function normalize(mixed $object, ?string $format = null, array $context
103103

104104
$resourceClass = $this->getStateOptionsClass($operation, $resourceClass);
105105

106-
if ($currentFilters || ($parameters && \count($parameters))) {
106+
if ($currentFilters || \count($parameters) > 0) {
107107
$hydraPrefix = $this->getHydraPrefix($context + $this->defaultContext);
108108
$data[$hydraPrefix.'search'] = $this->getSearch($resourceClass, $requestParts, $currentFilters, $parameters, $hydraPrefix);
109109
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace ApiPlatform\Metadata\Exception;
4+
5+
use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
6+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
7+
8+
final class AccessDeniedException extends AccessDeniedHttpException implements HttpExceptionInterface
9+
{
10+
public function getStatusCode(): int
11+
{
12+
return 403;
13+
}
14+
15+
public function getHeaders(): array
16+
{
17+
return [];
18+
}
19+
}

src/Metadata/HttpOperation.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class HttpOperation extends Operation
3434
* @param array<int|string, string|string[]>|string|null $formats {@see https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation}
3535
* @param array<int|string, string|string[]>|string|null $inputFormats {@see https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation}
3636
* @param array<int|string, string|string[]>|string|null $outputFormats {@see https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation}
37-
* @param array<string,array{
37+
* @param Parameters|array<string,array{
3838
* 0: string,
3939
* 1: string
4040
* }|array{
@@ -344,11 +344,17 @@ public function withOutputFormats($outputFormats = null): static
344344
return $self;
345345
}
346346

347-
public function getUriVariables()
347+
/**
348+
* @return Parameters|array<string, mixed>|null
349+
*/
350+
public function getUriVariables(): mixed
348351
{
349352
return $this->uriVariables;
350353
}
351354

355+
/**
356+
* @param Parameters|array<string, mixed>|array<int, Link>|list<string> $uriVariables
357+
*/
352358
public function withUriVariables($uriVariables): static
353359
{
354360
$self = clone $this;

src/Metadata/Link.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use ApiPlatform\OpenApi;
1717

1818
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_PARAMETER)]
19-
final class Link extends Parameter
19+
final class Link extends Parameter implements UriVariableParameterInterface
2020
{
2121
public function __construct(
2222
private ?string $parameterName = null,

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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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;
15+
16+
/**
17+
* @experimental
18+
*/
19+
interface UriVariableParameterInterface
20+
{
21+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
$value = [$value];
43+
}
44+
45+
$entities = [];
46+
foreach ($value as $v) {
47+
$entities[] = $this->iriConverter->getResourceFromIri($v, [
48+
'fetch_data' => $parameter->getExtraProperties()['fetch_data'] ?? false,
49+
]);
50+
}
51+
52+
$parameter->setValue($entities);
53+
54+
return $operation;
55+
}
56+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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\Link;
17+
use ApiPlatform\Metadata\Operation;
18+
use ApiPlatform\Metadata\Parameter;
19+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
20+
use ApiPlatform\State\Exception\ProviderNotFoundException;
21+
use ApiPlatform\State\ParameterProviderInterface;
22+
use ApiPlatform\State\ProviderInterface;
23+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
24+
25+
/**
26+
* Checks if the linked resources have security attributes and prepares them for access checking.
27+
*
28+
* @experimental
29+
*/
30+
final class ReadLinkParameterProvider implements ParameterProviderInterface
31+
{
32+
/**
33+
* @param ProviderInterface<object> $locator
34+
*/
35+
public function __construct(
36+
private readonly ProviderInterface $locator,
37+
private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
38+
) {
39+
}
40+
41+
public function provide(Parameter $parameter, array $parameters = [], array $context = []): ?Operation
42+
{
43+
$operation = $context['operation'];
44+
$extraProperties = $parameter->getExtraProperties();
45+
46+
if ($parameter instanceof Link) {
47+
$linkClass = $parameter->getFromClass() ?? $parameter->getToClass();
48+
$securityObjectName = $parameter->getSecurityObjectName() ?? $parameter->getToProperty() ?? $parameter->getFromProperty();
49+
}
50+
51+
$securityObjectName ??= $parameter->getKey();
52+
53+
$linkClass ??= $extraProperties['resource_class'] ?? $operation->getClass();
54+
55+
if (!$linkClass) {
56+
return $operation;
57+
}
58+
59+
$linkOperation = $this->resourceMetadataCollectionFactory
60+
->create($linkClass)
61+
->getOperation($operation->getExtraProperties()['parent_uri_template'] ?? $extraProperties['uri_template'] ?? null);
62+
63+
try {
64+
$relation = $this->locator->provide($linkOperation, [$parameter->getKey() => $parameter->getValue()], $context);
65+
} catch (ProviderNotFoundException) {
66+
$relation = null;
67+
}
68+
69+
$parameter->setValue($relation);
70+
71+
if (!$relation && true === ($extraProperties['throw_not_found'] ?? true)) {
72+
throw new NotFoundHttpException('Relation for link security not found.');
73+
}
74+
75+
$context['request']?->attributes->set($securityObjectName, $relation);
76+
77+
return $operation;
78+
}
79+
}

0 commit comments

Comments
 (0)