[RFC][HttpKernel][Security] Allowed adding attributes on controller arguments that will be passed to argument resolvers.
This commit is contained in:
parent
d7d479bb9f
commit
20f316906e
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Attribute;
|
||||
|
||||
/**
|
||||
* Marker interface for controller argument attributes.
|
||||
*/
|
||||
interface ArgumentInterface
|
||||
{
|
||||
}
|
@ -10,6 +10,7 @@ CHANGELOG
|
||||
`kernel.trusted_proxies` and `kernel.trusted_headers` parameters
|
||||
* content of request parameter `_password` is now also hidden
|
||||
in the request profiler raw content section
|
||||
* Allowed adding attributes on controller arguments that will be passed to argument resolvers.
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\HttpKernel\ControllerMetadata;
|
||||
|
||||
use Symfony\Component\HttpKernel\Attribute\ArgumentInterface;
|
||||
|
||||
/**
|
||||
* Responsible for storing metadata of an argument.
|
||||
*
|
||||
@ -24,8 +26,9 @@ class ArgumentMetadata
|
||||
private $hasDefaultValue;
|
||||
private $defaultValue;
|
||||
private $isNullable;
|
||||
private $attribute;
|
||||
|
||||
public function __construct(string $name, ?string $type, bool $isVariadic, bool $hasDefaultValue, $defaultValue, bool $isNullable = false)
|
||||
public function __construct(string $name, ?string $type, bool $isVariadic, bool $hasDefaultValue, $defaultValue, bool $isNullable = false, ?ArgumentInterface $attribute = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
@ -33,6 +36,7 @@ class ArgumentMetadata
|
||||
$this->hasDefaultValue = $hasDefaultValue;
|
||||
$this->defaultValue = $defaultValue;
|
||||
$this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue);
|
||||
$this->attribute = $attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,4 +108,12 @@ class ArgumentMetadata
|
||||
|
||||
return $this->defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute (if any) that was set on the argument.
|
||||
*/
|
||||
public function getAttribute(): ?ArgumentInterface
|
||||
{
|
||||
return $this->attribute;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
namespace Symfony\Component\HttpKernel\ControllerMetadata;
|
||||
|
||||
use Symfony\Component\HttpKernel\Attribute\ArgumentInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\InvalidMetadataException;
|
||||
|
||||
/**
|
||||
* Builds {@see ArgumentMetadata} objects based on the given Controller.
|
||||
*
|
||||
@ -34,7 +37,28 @@ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface
|
||||
}
|
||||
|
||||
foreach ($reflection->getParameters() as $param) {
|
||||
$arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $reflection), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull());
|
||||
$attribute = null;
|
||||
if (method_exists($param, 'getAttributes')) {
|
||||
$reflectionAttributes = $param->getAttributes(ArgumentInterface::class, \ReflectionAttribute::IS_INSTANCEOF);
|
||||
|
||||
if (\count($reflectionAttributes) > 1) {
|
||||
$representative = $controller;
|
||||
|
||||
if (\is_array($representative)) {
|
||||
$representative = sprintf('%s::%s()', \get_class($representative[0]), $representative[1]);
|
||||
} elseif (\is_object($representative)) {
|
||||
$representative = \get_class($representative);
|
||||
}
|
||||
|
||||
throw new InvalidMetadataException(sprintf('Controller "%s" has more than one attribute for "$%s" argument.', $representative, $param->getName()));
|
||||
}
|
||||
|
||||
if (isset($reflectionAttributes[0])) {
|
||||
$attribute = $reflectionAttributes[0]->newInstance();
|
||||
}
|
||||
}
|
||||
|
||||
$arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $reflection), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attribute);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
|
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Exception;
|
||||
|
||||
class InvalidMetadataException extends \LogicException
|
||||
{
|
||||
}
|
@ -15,6 +15,9 @@ use Fake\ImportedAndFake;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
|
||||
use Symfony\Component\HttpKernel\Exception\InvalidMetadataException;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\Attribute\Foo;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\AttributeController;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\BasicTypesController;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController;
|
||||
@ -117,6 +120,28 @@ class ArgumentMetadataFactoryTest extends TestCase
|
||||
], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8
|
||||
*/
|
||||
public function testAttributeSignature()
|
||||
{
|
||||
$arguments = $this->factory->createArgumentMetadata([new AttributeController(), 'action']);
|
||||
|
||||
$this->assertEquals([
|
||||
new ArgumentMetadata('baz', 'string', false, false, null, false, new Foo('bar')),
|
||||
], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8
|
||||
*/
|
||||
public function testAttributeSignatureError()
|
||||
{
|
||||
$this->expectException(InvalidMetadataException::class);
|
||||
|
||||
$this->factory->createArgumentMetadata([new AttributeController(), 'invalidAction']);
|
||||
}
|
||||
|
||||
private function signature1(self $foo, array $bar, callable $baz)
|
||||
{
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\Fixtures\Attribute;
|
||||
|
||||
use Attribute;
|
||||
use Symfony\Component\HttpKernel\Attribute\ArgumentInterface;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PARAMETER)]
|
||||
class Foo implements ArgumentInterface
|
||||
{
|
||||
private $foo;
|
||||
|
||||
public function __construct($foo)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller;
|
||||
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\Attribute\Foo;
|
||||
|
||||
class AttributeController
|
||||
{
|
||||
public function action(#[Foo('bar')] string $baz) {
|
||||
}
|
||||
|
||||
public function invalidAction(#[Foo('bar'), Foo('bar')] string $baz) {
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ CHANGELOG
|
||||
* Deprecated `setProviderKey()`/`getProviderKey()` in favor of `setFirewallName()/getFirewallName()` in `PreAuthenticatedToken`, `RememberMeToken`, `SwitchUserToken`, `UsernamePasswordToken`, `DefaultAuthenticationSuccessHandler`; and deprecated the `AbstractRememberMeServices::$providerKey` property in favor of `AbstractRememberMeServices::$firewallName`
|
||||
* Added `FirewallListenerInterface` to make the execution order of firewall listeners configurable
|
||||
* Added translator to `\Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator` and `\Symfony\Component\Security\Http\Firewall\UsernamePasswordJsonAuthenticationListener` to translate authentication failure messages
|
||||
* Added a CurrentUser attribute to force the UserValueResolver to resolve an argument to the current user.
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Http\Attribute;
|
||||
|
||||
use Attribute;
|
||||
use Symfony\Component\HttpKernel\Attribute\ArgumentInterface;
|
||||
|
||||
/**
|
||||
* Indicates that a controller argument should receive the current logged user.
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PARAMETER)]
|
||||
class CurrentUser implements ArgumentInterface
|
||||
{
|
||||
}
|
@ -17,6 +17,7 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
|
||||
/**
|
||||
* Supports the argument type of {@see UserInterface}.
|
||||
@ -34,6 +35,10 @@ final class UserValueResolver implements ArgumentValueResolverInterface
|
||||
|
||||
public function supports(Request $request, ArgumentMetadata $argument): bool
|
||||
{
|
||||
if ($argument->getAttribute() instanceof CurrentUser) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// only security user implementations are supported
|
||||
if (UserInterface::class !== $argument->getType()) {
|
||||
return false;
|
||||
|
@ -19,6 +19,7 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
use Symfony\Component\Security\Http\Controller\UserValueResolver;
|
||||
|
||||
class UserValueResolverTest extends TestCase
|
||||
@ -68,6 +69,20 @@ class UserValueResolverTest extends TestCase
|
||||
$this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata)));
|
||||
}
|
||||
|
||||
public function testResolveWithAttribute()
|
||||
{
|
||||
$user = $this->getMockBuilder(UserInterface::class)->getMock();
|
||||
$token = new UsernamePasswordToken($user, 'password', 'provider');
|
||||
$tokenStorage = new TokenStorage();
|
||||
$tokenStorage->setToken($token);
|
||||
|
||||
$resolver = new UserValueResolver($tokenStorage);
|
||||
$metadata = new ArgumentMetadata('foo', null, false, false, null, false, new CurrentUser());
|
||||
|
||||
$this->assertTrue($resolver->supports(Request::create('/'), $metadata));
|
||||
$this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata)));
|
||||
}
|
||||
|
||||
public function testIntegration()
|
||||
{
|
||||
$user = $this->getMockBuilder(UserInterface::class)->getMock();
|
||||
|
@ -20,7 +20,7 @@
|
||||
"symfony/deprecation-contracts": "^2.1",
|
||||
"symfony/security-core": "^5.2",
|
||||
"symfony/http-foundation": "^4.4.7|^5.0.7",
|
||||
"symfony/http-kernel": "^4.4|^5.0",
|
||||
"symfony/http-kernel": "^5.2",
|
||||
"symfony/polyfill-php80": "^1.15",
|
||||
"symfony/property-access": "^4.4|^5.0"
|
||||
},
|
||||
|
Reference in New Issue
Block a user