From 1d8202c73c8c63925ad98b95c1a759572f5f19cf Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Fri, 9 May 2025 17:31:16 -0300 Subject: [PATCH] [HttpFoundation] Add `#[IsSignatureValid]` attribute --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../Compiler/UnusedTagsPass.php | 1 + .../FrameworkExtension.php | 3 + .../DependencyInjection/SecurityExtension.php | 4 + .../Resources/config/security.php | 8 + .../Component/HttpFoundation/CHANGELOG.md | 1 + .../Exception/ExpiredSignedUriException.php | 4 + .../Exception/SignedUriException.php | 4 + .../Http/Attribute/IsSignatureValid.php | 52 ++++ .../Component/Security/Http/CHANGELOG.md | 1 + .../IsSignatureValidAttributeListener.php | 65 +++++ .../IsSignatureValidAttributeListenerTest.php | 246 ++++++++++++++++++ .../Http/Tests/Fixtures/ExtendedSigner.php | 18 ++ .../IsSignatureValidAttributeController.php | 22 ++ ...gnatureValidAttributeMethodsController.php | 52 ++++ 15 files changed, 482 insertions(+) create mode 100644 src/Symfony/Component/Security/Http/Attribute/IsSignatureValid.php create mode 100644 src/Symfony/Component/Security/Http/EventListener/IsSignatureValidAttributeListener.php create mode 100644 src/Symfony/Component/Security/Http/Tests/EventListener/IsSignatureValidAttributeListenerTest.php create mode 100644 src/Symfony/Component/Security/Http/Tests/Fixtures/ExtendedSigner.php create mode 100644 src/Symfony/Component/Security/Http/Tests/Fixtures/IsSignatureValidAttributeController.php create mode 100644 src/Symfony/Component/Security/Http/Tests/Fixtures/IsSignatureValidAttributeMethodsController.php diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 76b3cb9479256..69a2fbd786b31 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Allow using their name without added suffix when using `#[Target]` for custom services * Deprecate `Symfony\Bundle\FrameworkBundle\Console\Application::add()` in favor of `Symfony\Bundle\FrameworkBundle\Console\Application::addCommand()` * Add `assertEmailAddressNotContains()` to the `MailerAssertionsTrait` + * Add autoconfiguration tag `security.uri_signer` to `Symfony\Component\HttpFoundation\UriSigner` 7.3 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 53361e3127e34..94475492cb5c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -89,6 +89,7 @@ class UnusedTagsPass implements CompilerPassInterface 'security.authenticator.login_linker', 'security.expression_language_provider', 'security.remember_me_handler', + 'security.uri_signer', 'security.voter', 'serializer.encoder', 'serializer.normalizer', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index e055f5f8bea53..cfeea7ebe01d4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -98,6 +98,7 @@ use Symfony\Component\HttpClient\ThrottlingHttpClient; use Symfony\Component\HttpClient\UriTemplateHttpClient; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\UriSigner; use Symfony\Component\HttpKernel\Attribute\AsController; use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; @@ -749,6 +750,8 @@ public function load(array $configs, ContainerBuilder $container): void ->addTag('mime.mime_type_guesser'); $container->registerForAutoconfiguration(LoggerAwareInterface::class) ->addMethodCall('setLogger', [new Reference('logger')]); + $container->registerForAutoconfiguration(UriSigner::class) + ->addTag('security.uri_signer'); $container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) { $tagAttributes = get_object_vars($attribute); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index c349a55cd94a9..2a05fe54e4dea 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -64,6 +64,7 @@ use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator; use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; use Symfony\Component\Security\Http\Event\CheckPassportEvent; +use Symfony\Component\Security\Http\EventListener\IsSignatureValidAttributeListener; /** * SecurityExtension. @@ -134,6 +135,9 @@ public function load(array $configs, ContainerBuilder $container): void $container->removeDefinition('form.type_extension.form.password_hasher'); $container->removeDefinition('form.type_extension.password.password_hasher'); } + if (!class_exists(IsSignatureValidAttributeListener::class)) { + $container->removeDefinition('controller.is_signature_valid_attribute_listener'); + } // set some global scalars $container->setParameter('security.access.denied_url', $config['access_denied_url']); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php index 7b08ebe5fa35d..43793a90551d6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php @@ -48,6 +48,7 @@ use Symfony\Component\Security\Http\Controller\SecurityTokenValueResolver; use Symfony\Component\Security\Http\Controller\UserValueResolver; use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener; +use Symfony\Component\Security\Http\EventListener\IsSignatureValidAttributeListener; use Symfony\Component\Security\Http\Firewall; use Symfony\Component\Security\Http\FirewallMapInterface; use Symfony\Component\Security\Http\HttpUtils; @@ -323,5 +324,12 @@ ->set('cache.security_is_csrf_token_valid_attribute_expression_language') ->parent('cache.system') ->tag('cache.pool') + + ->set('controller.is_signature_valid_attribute_listener', IsSignatureValidAttributeListener::class) + ->args([ + service('uri_signer'), + tagged_locator('security.uri_signer'), + ]) + ->tag('kernel.event_subscriber') ; }; diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index ca58a4032d8b8..5283586fd0e6b 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Deprecate using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead + * Add `#[WithHttpStatus]` to define status codes: 404 for `SignedUriException` and 403 for `ExpiredSignedUriException` 7.3 --- diff --git a/src/Symfony/Component/HttpFoundation/Exception/ExpiredSignedUriException.php b/src/Symfony/Component/HttpFoundation/Exception/ExpiredSignedUriException.php index 613e08ef46c63..8264c1b5a67ee 100644 --- a/src/Symfony/Component/HttpFoundation/Exception/ExpiredSignedUriException.php +++ b/src/Symfony/Component/HttpFoundation/Exception/ExpiredSignedUriException.php @@ -11,9 +11,13 @@ namespace Symfony\Component\HttpFoundation\Exception; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; + /** * @author Kevin Bond */ +#[WithHttpStatus(Response::HTTP_FORBIDDEN)] final class ExpiredSignedUriException extends SignedUriException { /** diff --git a/src/Symfony/Component/HttpFoundation/Exception/SignedUriException.php b/src/Symfony/Component/HttpFoundation/Exception/SignedUriException.php index 17b729d315d70..8b4a28be94919 100644 --- a/src/Symfony/Component/HttpFoundation/Exception/SignedUriException.php +++ b/src/Symfony/Component/HttpFoundation/Exception/SignedUriException.php @@ -11,9 +11,13 @@ namespace Symfony\Component\HttpFoundation\Exception; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; + /** * @author Kevin Bond */ +#[WithHttpStatus(Response::HTTP_NOT_FOUND)] abstract class SignedUriException extends \RuntimeException implements ExceptionInterface { } diff --git a/src/Symfony/Component/Security/Http/Attribute/IsSignatureValid.php b/src/Symfony/Component/Security/Http/Attribute/IsSignatureValid.php new file mode 100644 index 0000000000000..c39ebba56ec9f --- /dev/null +++ b/src/Symfony/Component/Security/Http/Attribute/IsSignatureValid.php @@ -0,0 +1,52 @@ + + * + * 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 Symfony\Component\HttpFoundation\UriSigner; + +/** + * Validates the request signature for specific HTTP methods. + * + * This class determines whether a request's signature should be validated + * based on the configured HTTP methods. If the request method matches one + * of the specified methods (or if no methods are specified), the signature + * is checked. + * + * If the signature is invalid, a {@see \Symfony\Component\HttpFoundation\Exception\SignedUriException} + * is thrown during validation. + * + * @author Santiago San Martin + */ +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)] +final class IsSignatureValid +{ + public readonly array $methods; + public readonly ?string $signer; + + /** + * @param string[]|string $methods HTTP methods that require signature validation. An empty array means that no method filtering is done + * @param string $signer The ID of the UriSigner service to use for signature validation. Defaults to 'uri_signer' + * + * @throws \LogicException If the UriSigner class does not support the 'verify' method (requires symfony/http-foundation >= 7.3) + */ + public function __construct( + array|string $methods = [], + ?string $signer = null, + ) { + if (!method_exists(UriSigner::class, 'verify')) { + throw new \LogicException('The `IsSignatureValid` attribute requires symfony/http-foundation >= 7.3.'); + } + + $this->methods = (array) $methods; + $this->signer = $signer; + } +} diff --git a/src/Symfony/Component/Security/Http/CHANGELOG.md b/src/Symfony/Component/Security/Http/CHANGELOG.md index 6c485dc6e5450..45cb9a6b7b51e 100644 --- a/src/Symfony/Component/Security/Http/CHANGELOG.md +++ b/src/Symfony/Component/Security/Http/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Deprecate callable firewall listeners, extend `AbstractListener` or implement `FirewallListenerInterface` instead * Deprecate `AbstractListener::__invoke` + * Add `#[IsSignatureValid]` attribute to validate URI signatures 7.3 --- diff --git a/src/Symfony/Component/Security/Http/EventListener/IsSignatureValidAttributeListener.php b/src/Symfony/Component/Security/Http/EventListener/IsSignatureValidAttributeListener.php new file mode 100644 index 0000000000000..288806e2bc882 --- /dev/null +++ b/src/Symfony/Component/Security/Http/EventListener/IsSignatureValidAttributeListener.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\EventListener; + +use Psr\Container\ContainerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\UriSigner; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Http\Attribute\IsSignatureValid; + +/** + * Handles the IsSignatureValid attribute. + * + * @author Santiago San Martin + */ +class IsSignatureValidAttributeListener implements EventSubscriberInterface +{ + public function __construct( + private readonly UriSigner $uriSigner, + private readonly ContainerInterface $container, + ) { + } + + public function onKernelControllerArguments(ControllerArgumentsEvent $event): void + { + if (!$attributes = $event->getAttributes(IsSignatureValid::class)) { + return; + } + + $request = $event->getRequest(); + foreach ($attributes as $attribute) { + $methods = array_map('strtoupper', $attribute->methods); + if ($methods && !\in_array($request->getMethod(), $methods, true)) { + continue; + } + + if (null === $attribute->signer) { + $this->uriSigner->verify($request); + continue; + } + + $signer = $this->container->get($attribute->signer); + if (!$signer instanceof UriSigner) { + throw new \LogicException(\sprintf('The service "%s" is not an instance of "%s".', $attribute->signer, UriSigner::class)); + } + + $signer->verify($request); + } + } + + public static function getSubscribedEvents(): array + { + return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 30]]; + } +} diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/IsSignatureValidAttributeListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/IsSignatureValidAttributeListenerTest.php new file mode 100644 index 0000000000000..c8bf5294547bc --- /dev/null +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/IsSignatureValidAttributeListenerTest.php @@ -0,0 +1,246 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Exception\UnsignedUriException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\UriSigner; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Security\Http\EventListener\IsSignatureValidAttributeListener; +use Symfony\Component\Security\Http\Tests\Fixtures\ExtendedSigner; +use Symfony\Component\Security\Http\Tests\Fixtures\IsSignatureValidAttributeController; +use Symfony\Component\Security\Http\Tests\Fixtures\IsSignatureValidAttributeMethodsController; + +/** + * @requires function \Symfony\Component\HttpFoundation\UriSigner::verify + */ +class IsSignatureValidAttributeListenerTest extends TestCase +{ + public function testInvokableControllerWithValidSignature() + { + $request = new Request(); + + $signer = $this->createMock(UriSigner::class); + $signer->expects($this->once())->method('verify')->with($request); + $kernel = $this->createMock(HttpKernelInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->never())->method('get'); + + $event = new ControllerArgumentsEvent( + $kernel, + new IsSignatureValidAttributeController(), + [], + $request, + null + ); + + $listener = new IsSignatureValidAttributeListener($signer, $container); + $listener->onKernelControllerArguments($event); + } + + public function testNoAttributeSkipsValidation() + { + $kernel = $this->createMock(HttpKernelInterface::class); + $signer = $this->createMock(UriSigner::class); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->never())->method('get'); + + $event = new ControllerArgumentsEvent( + $kernel, + [new IsSignatureValidAttributeMethodsController(), 'noAttribute'], + [], + new Request(), + null + ); + + $listener = new IsSignatureValidAttributeListener($signer, $container); + $listener->onKernelControllerArguments($event); + } + + public function testDefaultCheckRequestSucceeds() + { + $request = new Request(); + $signer = $this->createMock(UriSigner::class); + $signer->expects($this->once())->method('verify')->with($request); + $kernel = $this->createMock(HttpKernelInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->never())->method('get'); + + $event = new ControllerArgumentsEvent( + $kernel, + [new IsSignatureValidAttributeMethodsController(), 'withDefaultBehavior'], + [], + $request, + null + ); + + $listener = new IsSignatureValidAttributeListener($signer, $container); + $listener->onKernelControllerArguments($event); + } + + public function testCheckRequestFailsThrowsHttpException() + { + $request = new Request(); + $signer = $this->createMock(UriSigner::class); + $signer->expects($this->once())->method('verify')->willThrowException(new UnsignedUriException()); + $kernel = $this->createMock(HttpKernelInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->never())->method('get'); + + $event = new ControllerArgumentsEvent( + $kernel, + [new IsSignatureValidAttributeMethodsController(), 'withDefaultBehavior'], + [], + $request, + null + ); + + $listener = new IsSignatureValidAttributeListener($signer, $container); + + $this->expectException(UnsignedUriException::class); + $listener->onKernelControllerArguments($event); + } + + public function testMultipleAttributesAllValid() + { + $request = new Request(); + + $signer = $this->createMock(UriSigner::class); + $signer->expects($this->exactly(2))->method('verify')->with($request); + $kernel = $this->createMock(HttpKernelInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->never())->method('get'); + + $event = new ControllerArgumentsEvent( + $kernel, + [new IsSignatureValidAttributeMethodsController(), 'withMultiple'], + [], + $request, + null + ); + + $listener = new IsSignatureValidAttributeListener($signer, $container); + $listener->onKernelControllerArguments($event); + } + + public function testValidationWithStringMethod() + { + $request = new Request([], [], [], [], [], ['REQUEST_METHOD' => 'POST']); + + $signer = $this->createMock(UriSigner::class); + $signer->expects($this->once())->method('verify')->with($request); + $kernel = $this->createMock(HttpKernelInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->never())->method('get'); + + $event = new ControllerArgumentsEvent( + $kernel, + [new IsSignatureValidAttributeMethodsController(), 'withPostOnly'], + [], + $request, + null + ); + + $listener = new IsSignatureValidAttributeListener($signer, $container); + $listener->onKernelControllerArguments($event); + } + + public function testValidationWithArrayMethods() + { + $request = new Request([], [], [], [], [], ['REQUEST_METHOD' => 'POST']); + + $signer = $this->createMock(UriSigner::class); + $signer->expects($this->once())->method('verify')->with($request); + $kernel = $this->createMock(HttpKernelInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->never())->method('get'); + + $event = new ControllerArgumentsEvent( + $kernel, + [new IsSignatureValidAttributeMethodsController(), 'withGetAndPost'], + [], + $request, + null + ); + + $listener = new IsSignatureValidAttributeListener($signer, $container); + $listener->onKernelControllerArguments($event); + } + + public function testValidationSkippedForNonMatchingMethod() + { + $request = new Request([], [], [], [], [], ['REQUEST_METHOD' => 'GET']); + + $kernel = $this->createMock(HttpKernelInterface::class); + $signer = $this->createMock(UriSigner::class); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->never())->method('get'); + + $event = new ControllerArgumentsEvent( + $kernel, + [new IsSignatureValidAttributeMethodsController(), 'withPostOnly'], + [], + $request, + null + ); + + $listener = new IsSignatureValidAttributeListener($signer, $container); + $listener->onKernelControllerArguments($event); + } + + public function testValidationWithSigner() + { + $request = new Request(); + $signer = $this->createMock(UriSigner::class); + $customSigner = $this->createMock(UriSigner::class); + $customSigner->expects($this->once())->method('verify')->with($request); + $kernel = $this->createMock(HttpKernelInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->once())->method('get')->with('app.test.signer')->willReturn($customSigner); + + $event = new ControllerArgumentsEvent( + $kernel, + [new IsSignatureValidAttributeMethodsController(), 'withCustomSigner'], + [], + $request, + null + ); + + $listener = new IsSignatureValidAttributeListener($signer, $container); + $listener->onKernelControllerArguments($event); + } + + public function testValidationWithExtendedSigner() + { + $request = new Request(); + $signer = $this->createMock(UriSigner::class); + $extendedSigner = $this->createMock(ExtendedSigner::class); + $extendedSigner->expects($this->once())->method('verify')->with($request); + $kernel = $this->createMock(HttpKernelInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->once())->method('get')->with('app.test.extended_signer')->willReturn($extendedSigner); + + $event = new ControllerArgumentsEvent( + $kernel, + [new IsSignatureValidAttributeMethodsController(), 'withCustomExtendedSigner'], + [], + $request, + null + ); + + $listener = new IsSignatureValidAttributeListener($signer, $container); + $listener->onKernelControllerArguments($event); + } +} diff --git a/src/Symfony/Component/Security/Http/Tests/Fixtures/ExtendedSigner.php b/src/Symfony/Component/Security/Http/Tests/Fixtures/ExtendedSigner.php new file mode 100644 index 0000000000000..9a818fc6a1e4b --- /dev/null +++ b/src/Symfony/Component/Security/Http/Tests/Fixtures/ExtendedSigner.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Tests\Fixtures; + +use Symfony\Component\HttpFoundation\UriSigner; + +class ExtendedSigner extends UriSigner +{ +} diff --git a/src/Symfony/Component/Security/Http/Tests/Fixtures/IsSignatureValidAttributeController.php b/src/Symfony/Component/Security/Http/Tests/Fixtures/IsSignatureValidAttributeController.php new file mode 100644 index 0000000000000..c5dd5ac87c671 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Tests/Fixtures/IsSignatureValidAttributeController.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Tests\Fixtures; + +use Symfony\Component\Security\Http\Attribute\IsSignatureValid; + +#[IsSignatureValid] +class IsSignatureValidAttributeController +{ + public function __invoke() + { + } +} diff --git a/src/Symfony/Component/Security/Http/Tests/Fixtures/IsSignatureValidAttributeMethodsController.php b/src/Symfony/Component/Security/Http/Tests/Fixtures/IsSignatureValidAttributeMethodsController.php new file mode 100644 index 0000000000000..94e2e602c1859 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Tests/Fixtures/IsSignatureValidAttributeMethodsController.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Tests\Fixtures; + +use Symfony\Component\Security\Http\Attribute\IsSignatureValid; + +class IsSignatureValidAttributeMethodsController +{ + public function noAttribute() + { + } + + #[IsSignatureValid] + public function withDefaultBehavior() + { + } + + #[IsSignatureValid] + #[IsSignatureValid] + public function withMultiple() + { + } + + #[IsSignatureValid(methods: 'POST')] + public function withPostOnly() + { + } + + #[IsSignatureValid(methods: ['GET', 'POST'])] + public function withGetAndPost() + { + } + + #[IsSignatureValid(signer: 'app.test.signer')] + public function withCustomSigner() + { + } + + #[IsSignatureValid(signer: 'app.test.extended_signer')] + public function withCustomExtendedSigner() + { + } +}