Skip to content

Commit c5f33ef

Browse files
[Messenger] Support signing messages per handler
1 parent b4ca291 commit c5f33ef

File tree

12 files changed

+428
-23
lines changed

12 files changed

+428
-23
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2556,6 +2556,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
25562556
$senderAliases = [];
25572557
$transportRetryReferences = [];
25582558
$transportRateLimiterReferences = [];
2559+
$serializerIds = [];
25592560
foreach ($config['transports'] as $name => $transport) {
25602561
$serializerId = $transport['serializer'] ?? 'messenger.default_serializer';
25612562
$tags = [
@@ -2572,6 +2573,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
25722573
;
25732574
$container->setDefinition($transportId = 'messenger.transport.'.$name, $transportDefinition);
25742575
$senderAliases[$name] = $transportId;
2576+
$serializerIds[$transportId] = $serializerId;
25752577

25762578
if (null !== $transport['retry_strategy']['service']) {
25772579
$transportRetryReferences[$name] = new Reference($transport['retry_strategy']['service']);
@@ -2599,13 +2601,11 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
25992601
}
26002602

26012603
$senderReferences = [];
2602-
// alias => service_id
2603-
foreach ($senderAliases as $alias => $serviceId) {
2604-
$senderReferences[$alias] = new Reference($serviceId);
2604+
foreach ($senderAliases as $alias => $transportId) {
2605+
$senderReferences[$alias] = new Reference($transportId);
26052606
}
2606-
// service_id => service_id
2607-
foreach ($senderAliases as $serviceId) {
2608-
$senderReferences[$serviceId] = new Reference($serviceId);
2607+
foreach ($senderAliases as $transportId) {
2608+
$senderReferences[$transportId] = new Reference($transportId);
26092609
}
26102610

26112611
foreach ($config['transports'] as $name => $transport) {
@@ -2616,7 +2616,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
26162616
}
26172617
}
26182618

2619-
$failureTransportReferencesByTransportName = array_map(fn ($failureTransportName) => $senderReferences[$failureTransportName], $failureTransportsByName);
2619+
$failureTransportReferencesByTransportName = array_map(static fn ($failureTransportName) => $senderReferences[$failureTransportName], $failureTransportsByName);
26202620

26212621
$messageToSendersMapping = [];
26222622
foreach ($config['routing'] as $message => $messageConfiguration) {
@@ -2645,6 +2645,18 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
26452645
->replaceArgument(1, $sendersServiceLocator)
26462646
;
26472647

2648+
$messageToSerializersMapping = [];
2649+
foreach ($messageToSendersMapping as $message => $senders) {
2650+
foreach ($senders as $sender) {
2651+
$serializerId = $serializerIds[$senderAliases[$sender] ?? $sender];
2652+
$messageToSerializersMapping[$message][$serializerId] = $serializerId;
2653+
}
2654+
$messageToSerializersMapping[$message] = array_keys($messageToSerializersMapping[$message]);
2655+
}
2656+
2657+
$container->getDefinition('messenger.signing_serializer')
2658+
->replaceArgument(2, $messageToSerializersMapping);
2659+
26482660
$container->getDefinition('messenger.retry.send_failed_message_for_retry_listener')
26492661
->replaceArgument(0, $sendersServiceLocator)
26502662
;
@@ -2943,7 +2955,7 @@ private function registerCachingHttpClient(array $options, array $defaultOptions
29432955

29442956
$container
29452957
->register($name.'.caching', CachingHttpClient::class)
2946-
->setDecoratedService($name, null, 15)
2958+
->setDecoratedService($name, null, 15)
29472959
->setArguments([
29482960
new Reference('.inner'),
29492961
new Reference($options['cache_pool']),

src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,6 @@
407407
->args([
408408
service('console.messenger.application'),
409409
])
410-
->tag('messenger.message_handler')
410+
->tag('messenger.message_handler', ['sign' => true])
411411
;
412412
};

src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

14+
use Symfony\Component\DependencyInjection\Parameter;
1415
use Symfony\Component\DependencyInjection\ServiceLocator;
1516
use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory;
1617
use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory;
@@ -44,13 +45,13 @@
4445
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
4546
use Symfony\Component\Messenger\Transport\Serialization\Serializer;
4647
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
48+
use Symfony\Component\Messenger\Transport\Serialization\SigningSerializer;
4749
use Symfony\Component\Messenger\Transport\Sync\SyncTransportFactory;
4850
use Symfony\Component\Messenger\Transport\TransportFactory;
51+
use Symfony\Component\String\LazyString;
4952

5053
return static function (ContainerConfigurator $container) {
5154
$container->services()
52-
->alias(SerializerInterface::class, 'messenger.default_serializer')
53-
5455
// Asynchronous
5556
->set('messenger.senders_locator', SendersLocator::class)
5657
->args([
@@ -79,6 +80,22 @@
7980

8081
->set('messenger.transport.native_php_serializer', PhpSerializer::class)
8182
->alias('messenger.default_serializer', 'messenger.transport.native_php_serializer')
83+
->alias(SerializerInterface::class, 'messenger.default_serializer')
84+
85+
->set('messenger.signing_serializer', SigningSerializer::class)
86+
->abstract()
87+
->args([
88+
service('.inner'),
89+
inline_service('string') // wrap the signing key in a lazy string to prevent a hard dependency on the kernel.secret parameter
90+
->factory(class_exists(LazyString::class) ? [LazyString::class, 'fromCallable'] : 'current')
91+
->args([
92+
class_exists(LazyString::class) ? service_closure('.messenger.signing_serializer.signing_key') : [new Parameter('kernel.secret')],
93+
]),
94+
abstract_arg('message types to serializers'), // read and replaced by MessengerPass
95+
])
96+
->set('.messenger.signing_serializer.signing_key', 'string')
97+
->factory('current')
98+
->args([[new Parameter('kernel.secret')]])
8299

83100
// Middleware
84101
->set('messenger.middleware.handle_message', HandleMessageMiddleware::class)

src/Symfony/Bundle/FrameworkBundle/Resources/config/process.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@
1717
$container
1818
->services()
1919
->set('process.messenger.process_message_handler', RunProcessMessageHandler::class)
20-
->tag('messenger.message_handler')
20+
->tag('messenger.message_handler', ['sign' => true])
2121
;
2222
};

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\DependencyInjection\Exception\LogicException;
1919
use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
2020
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
21+
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
2122
use Symfony\Component\RateLimiter\CompoundRateLimiterFactory;
2223
use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
2324
use Symfony\Component\Validator\Constraints\Email;
@@ -425,6 +426,40 @@ public static function emailValidationModeProvider()
425426
}
426427
yield ['loose'];
427428
}
429+
430+
public function testMessengerSigningSerializerWiring()
431+
{
432+
$container = $this->createContainerFromClosure(function (ContainerBuilder $container) {
433+
$container->register('signed_handler', 'stdClass')
434+
->addTag('messenger.message_handler', ['handles' => DummyMessage::class, 'sign' => true]);
435+
436+
$container->loadFromExtension('framework', [
437+
'annotations' => false,
438+
'http_method_override' => false,
439+
'handle_all_throwables' => true,
440+
'php_errors' => ['log' => true],
441+
'messenger' => [
442+
'transports' => [
443+
'async' => ['dsn' => 'in-memory://'],
444+
],
445+
'routing' => [
446+
DummyMessage::class => ['senders' => ['async']],
447+
],
448+
'buses' => [
449+
'message_bus' => ['default_middleware' => ['enabled' => true]],
450+
],
451+
],
452+
]);
453+
});
454+
455+
$this->assertTrue($container->hasDefinition('messenger.signing_serializer'));
456+
$mapping = $container->getDefinition('messenger.signing_serializer')->getArgument(2);
457+
$this->assertArrayHasKey(DummyMessage::class, $mapping);
458+
$this->assertNotEmpty($mapping[DummyMessage::class]);
459+
460+
$this->assertTrue($container->hasDefinition('message_bus'));
461+
$this->assertSame('message_bus', (string) $container->getAlias('messenger.default_bus'));
462+
}
428463
}
429464

430465
class WorkflowValidatorWithConstructor implements DefinitionValidatorInterface

src/Symfony/Component/Messenger/Attribute/AsMessageHandler.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ public function __construct(
4444
* Priority of this handler when multiple handlers can process the same message.
4545
*/
4646
public int $priority = 0,
47+
48+
/**
49+
* Whether messages should be signed when sent on a transport.
50+
*/
51+
public bool $sign = false,
4752
) {
4853
}
4954
}

src/Symfony/Component/Messenger/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Add `Symfony\Component\Messenger\Middleware\AddDefaultStampsMiddleware` and `Symfony\Component\Messenger\Message\DefaultStampsProviderInterface`
1010
* Add the possibility to configure exchange to exchange bindings in AMQP transport
1111
* Add `MessageSentToTransportsEvent` that is dispatched only after the message was sent to at least one transport
12+
* Support signing messages per handler
1213

1314
7.3
1415
---

src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
1818
use Symfony\Component\DependencyInjection\ContainerBuilder;
1919
use Symfony\Component\DependencyInjection\Definition;
20-
use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
2120
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2221
use Symfony\Component\DependencyInjection\Reference;
2322
use Symfony\Component\Messenger\Handler\HandlerDescriptor;
@@ -32,9 +31,9 @@ class MessengerPass implements CompilerPassInterface
3231
{
3332
public function process(ContainerBuilder $container): void
3433
{
35-
$busIds = [];
36-
foreach ($container->findTaggedServiceIds('messenger.bus') as $busId => $tags) {
37-
$busIds[] = $busId;
34+
$busIds = array_keys($container->findTaggedServiceIds('messenger.bus'));
35+
36+
foreach ($busIds as $busId) {
3837
if ($container->hasParameter($busMiddlewareParameter = $busId.'.middleware')) {
3938
$this->registerBusMiddleware($container, $busId, $container->getParameter($busMiddlewareParameter));
4039

@@ -49,6 +48,7 @@ public function process(ContainerBuilder $container): void
4948
if ($container->hasDefinition('messenger.receiver_locator')) {
5049
$this->registerReceivers($container, $busIds);
5150
}
51+
5252
$this->registerHandlers($container, $busIds);
5353
}
5454

@@ -57,6 +57,7 @@ private function registerHandlers(ContainerBuilder $container, array $busIds): v
5757
$definitions = [];
5858
$handlersByBusAndMessage = [];
5959
$handlerToOriginalServiceIdMapping = [];
60+
$signedMessageTypes = [];
6061

6162
foreach ($container->findTaggedServiceIds('messenger.message_handler', true) as $serviceId => $tags) {
6263
foreach ($tags as $tag) {
@@ -101,6 +102,7 @@ private function registerHandlers(ContainerBuilder $container, array $busIds): v
101102
$priority = $options['priority'] ?? 0;
102103
$method = $options['method'] ?? '__invoke';
103104
$fromTransport = $options['from_transport'] ?? '';
105+
$sign = $options['sign'] ?? false;
104106

105107
if (isset($options['bus'])) {
106108
if (!\in_array($options['bus'], $busIds)) {
@@ -135,6 +137,10 @@ private function registerHandlers(ContainerBuilder $container, array $busIds): v
135137
foreach ($buses as $handlerBus) {
136138
$handlersByBusAndMessage[$handlerBus][$message][$priority][] = [$definitionId, $options];
137139
}
140+
141+
if ($sign && '*' !== $message) {
142+
$signedMessageTypes[$message] = true;
143+
}
138144
}
139145

140146
if (null === $message) {
@@ -164,6 +170,21 @@ private function registerHandlers(ContainerBuilder $container, array $busIds): v
164170
}
165171
$container->addDefinitions($definitions);
166172

173+
if ($signedMessageTypes && $container->hasDefinition('messenger.signing_serializer')) {
174+
$signingSerializerDefinition = $container->getDefinition('messenger.signing_serializer');
175+
$messageToSerializersMapping = $signingSerializerDefinition->getArgument(2);
176+
177+
$signedMessageTypes = array_intersect_key($signedMessageTypes, $messageToSerializersMapping);
178+
$signingSerializerDefinition->replaceArgument(2, array_keys($signedMessageTypes));
179+
180+
// because transports accept any message types - not only listed ones - we have to decorate all serializers regardless of message signing
181+
foreach (array_unique(array_merge(...array_values($messageToSerializersMapping))) as $serializerId) {
182+
$container->setDefinition('.signing.'.$serializerId, (new ChildDefinition('messenger.signing_serializer'))->setDecoratedService($serializerId));
183+
}
184+
} else {
185+
$container->removeDefinition('messenger.signing_serializer');
186+
}
187+
167188
foreach ($busIds as $bus) {
168189
$container->register($locatorId = $bus.'.messenger.handlers_locator', HandlersLocator::class)
169190
->setArgument(0, $handlersLocatorMappingByBus[$bus] ?? [])
@@ -299,11 +320,7 @@ private function registerReceivers(ContainerBuilder $container, array $busIds):
299320
}
300321

301322
$consumeCommandDefinition->replaceArgument(4, $consumableReceiverNames);
302-
try {
303-
$consumeCommandDefinition->replaceArgument(6, $busIds);
304-
} catch (OutOfBoundsException) {
305-
// ignore to preserve compatibility with symfony/framework-bundle < 5.4
306-
}
323+
$consumeCommandDefinition->replaceArgument(6, $busIds);
307324
}
308325

309326
if ($container->hasDefinition('console.command.messenger_setup_transports')) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
namespace Symfony\Component\Messenger\Exception;
13+
14+
class InvalidMessageSignatureException extends \RuntimeException implements ExceptionInterface
15+
{
16+
}

0 commit comments

Comments
 (0)