Skip to content

Commit

Permalink
fix(state): security parameter with listeners
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Jul 8, 2024
1 parent 08b1d1b commit c64362e
Show file tree
Hide file tree
Showing 5 changed files with 18 additions and 22 deletions.
3 changes: 2 additions & 1 deletion src/Metadata/Parameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\Metadata;

use ApiPlatform\OpenApi;
use ApiPlatform\State\ParameterNotFound;
use ApiPlatform\State\ParameterProviderInterface;
use Symfony\Component\Validator\Constraint;

Expand Down Expand Up @@ -119,7 +120,7 @@ public function getSecurityMessage(): ?string
*/
public function getValue(): mixed
{
return $this->extraProperties['_api_values'] ?? null;
return $this->extraProperties['_api_values'] ?? new ParameterNotFound;
}

/**
Expand Down
25 changes: 10 additions & 15 deletions src/State/Provider/SecurityParameterProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
use ApiPlatform\State\ParameterNotFound;
use ApiPlatform\State\ProviderInterface;
use ApiPlatform\State\Util\ParameterParserTrait;
use ApiPlatform\Symfony\Security\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

/**
Expand All @@ -30,36 +30,31 @@ final class SecurityParameterProvider implements ProviderInterface
{
use ParameterParserTrait;

public function __construct(private readonly ?ProviderInterface $decorated = null, private readonly ?ResourceAccessCheckerInterface $resourceAccessChecker = null)
public function __construct(private readonly ProviderInterface $decorated, private readonly ?ResourceAccessCheckerInterface $resourceAccessChecker = null)
{
}

public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if (!($request = $context['request']) instanceof Request) {
return $this->decorated->provide($operation, $uriVariables, $context);
}

/** @var Operation $apiOperation */
$apiOperation = $request->attributes->get('_api_operation');
$body = $this->decorated->provide($operation, $uriVariables, $context);
$request = $context['request'] ?? null;

foreach ($apiOperation->getParameters() ?? [] as $parameter) {
$operation = $request?->attributes->get('_api_operation') ?? $operation;
foreach ($operation->getParameters() ?? [] as $parameter) {
if (null === $security = $parameter->getSecurity()) {
continue;
}

$key = $this->getParameterFlattenKey($parameter->getKey(), $this->extractParameterValues($parameter, $request, $context));
$apiValues = $parameter->getExtraProperties()['_api_values'] ?? [];
if (!isset($apiValues[$key])) {
if (($v = $parameter->getValue()) instanceof ParameterNotFound) {
continue;
}
$value = $apiValues[$key];

if (!$this->resourceAccessChecker->isGranted($context['resource_class'], $security, [$key => $value])) {
$securityContext = [$parameter->getKey() => $v, 'object' => $body];
if (!$this->resourceAccessChecker->isGranted($context['resource_class'], $security, $securityContext)) {
throw $operation instanceof GraphQlOperation ? new AccessDeniedHttpException($parameter->getSecurityMessage() ?? 'Access Denied.') : new AccessDeniedException($parameter->getSecurityMessage() ?? 'Access Denied.');
}
}

return $this->decorated->provide($operation, $uriVariables, $context);
return $body;
}
}
5 changes: 0 additions & 5 deletions src/Symfony/Bundle/Resources/config/state/provider.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,5 @@
<argument type="service" id="api_platform.state_provider.parameter.inner" />
<argument type="tagged_locator" tag="api_platform.parameter_provider" index-by="key" />
</service>

<service id="api_platform.state_provider.security_parameter" class="ApiPlatform\State\Provider\SecurityParameterProvider" decorates="api_platform.state_provider.main" decoration-priority="200">
<argument type="service" id="api_platform.state_provider.security_parameter.inner" />
<argument type="service" id="api_platform.security.resource_access_checker" />
</service>
</services>
</container>
5 changes: 5 additions & 0 deletions src/Symfony/Bundle/Resources/config/state/security.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@
<argument type="service" id="api_platform.security.resource_access_checker" />
<argument>post_denormalize</argument>
</service>

<service id="api_platform.state_provider.security_parameter" class="ApiPlatform\State\Provider\SecurityParameterProvider" decorates="api_platform.state_provider.access_checker">
<argument type="service" id="api_platform.state_provider.security_parameter.inner" />
<argument type="service" id="api_platform.security.resource_access_checker" />
</service>
</services>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\InMemoryUser;

class SecurityTests extends ApiTestCase
class SecurityTest extends ApiTestCase
{
public function dataUserAuthorization(): iterable
{
Expand Down

0 comments on commit c64362e

Please sign in to comment.