From 62288967cbb9f6d8dfb39924e5d3ea23b26e760a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 23 Feb 2024 16:46:28 +0100 Subject: [PATCH] [Mailer] Add support for allowing some users even if `recipients` is defined in `EnvelopeListener` --- .../Bundle/FrameworkBundle/CHANGELOG.md | 2 + .../DependencyInjection/Configuration.php | 13 ++++- .../FrameworkExtension.php | 1 + .../Resources/config/schema/symfony-1.0.xsd | 1 + .../Fixtures/php/mailer_with_dsn.php | 1 + .../Fixtures/php/mailer_with_transports.php | 1 + .../Fixtures/xml/mailer_with_dsn.xml | 1 + .../Fixtures/xml/mailer_with_transports.xml | 2 + .../Fixtures/yml/mailer_with_dsn.yml | 2 + .../Fixtures/yml/mailer_with_transports.yml | 3 ++ .../FrameworkExtensionTestCase.php | 5 +- src/Symfony/Component/Mailer/CHANGELOG.md | 1 + .../Mailer/EventListener/EnvelopeListener.php | 31 ++++++++++-- .../EventListener/EnvelopeListenerTest.php | 47 +++++++++++++++++++ 14 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/Mailer/Tests/EventListener/EnvelopeListenerTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 52624c2cd173..df0d692ebdf7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -13,6 +13,8 @@ CHANGELOG * Add `secrets:reveal` command * Add `rate_limiter` option to `http_client.default_options` and `http_client.scoped_clients` * Attach the workflow's configuration to the `workflow` tag + * Add the `allowed_recipients` option for mailer to allow some users to receive + emails even if `recipients` is defined. 7.0 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 8d54f74ef389..92c20d139da6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -2117,12 +2117,23 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl ->arrayNode('envelope') ->info('Mailer Envelope configuration') ->fixXmlConfig('recipient') + ->fixXmlConfig('allowed_recipient') ->children() ->scalarNode('sender')->end() ->arrayNode('recipients') ->performNoDeepMerging() ->beforeNormalization() - ->ifArray() + ->ifArray() + ->then(fn ($v) => array_filter(array_values($v))) + ->end() + ->prototype('scalar')->end() + ->end() + ->arrayNode('allowed_recipients') + ->info('A list of regular expressions that allow recipients when "recipients" option is defined.') + ->example(['.*@example\.com']) + ->performNoDeepMerging() + ->beforeNormalization() + ->ifArray() ->then(fn ($v) => array_filter(array_values($v))) ->end() ->prototype('scalar')->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 1fd7881f3487..86355e5ca1e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2647,6 +2647,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co $envelopeListener = $container->getDefinition('mailer.envelope_listener'); $envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null); $envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null); + $envelopeListener->setArgument(2, $config['envelope']['allowed_recipients'] ?? []); if ($config['headers']) { $headers = new Definition(Headers::class); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index ad02ec370e45..d8d23168d188 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -764,6 +764,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php index 68387298270a..3357bf354182 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php @@ -13,6 +13,7 @@ 'envelope' => [ 'sender' => 'sender@example.org', 'recipients' => ['redirected@example.org'], + 'allowed_recipients' => ['foobar@example\.org'], ], 'headers' => [ 'from' => 'from@example.org', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_transports.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_transports.php index 361fe731ccb0..e51fd056b591 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_transports.php @@ -16,6 +16,7 @@ 'envelope' => [ 'sender' => 'sender@example.org', 'recipients' => ['redirected@example.org', 'redirected1@example.org'], + 'allowed_recipients' => ['foobar@example\.org', '.*@example\.com'], ], 'headers' => [ 'from' => 'from@example.org', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml index 3436cf417caf..d48b7423afb0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml @@ -13,6 +13,7 @@ sender@example.org redirected@example.org + foobar@example\.org from@example.org bcc1@example.org diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml index 1cd8523b680f..9bfd18d9160b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml @@ -16,6 +16,8 @@ sender@example.org redirected@example.org redirected1@example.org + foobar@example\.org + .*@example\.com from@example.org bcc1@example.org diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml index e826d6bdcff9..ea703bdad8d1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml @@ -10,6 +10,8 @@ framework: sender: sender@example.org recipients: - redirected@example.org + allowed_recipients: + - foobar@example\.org headers: from: from@example.org bcc: [bcc1@example.org, bcc2@example.org] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_transports.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_transports.yml index 59a5f14fd315..ae10f6aee889 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_transports.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_transports.yml @@ -13,6 +13,9 @@ framework: recipients: - redirected@example.org - redirected1@example.org + allowed_recipients: + - foobar@example\.org + - .*@example\.com headers: from: from@example.org bcc: [bcc1@example.org, bcc2@example.org] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index dd836c06ffd1..2a26e786b436 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -2043,6 +2043,7 @@ public static function provideMailer(): iterable 'mailer_with_dsn', ['main' => 'smtp://example.com'], ['redirected@example.org'], + ['foobar@example\.org'], ]; yield [ 'mailer_with_transports', @@ -2051,13 +2052,14 @@ public static function provideMailer(): iterable 'transport2' => 'smtp://example2.com', ], ['redirected@example.org', 'redirected1@example.org'], + ['foobar@example\.org', '.*@example\.com'], ]; } /** * @dataProvider provideMailer */ - public function testMailer(string $configFile, array $expectedTransports, array $expectedRecipients) + public function testMailer(string $configFile, array $expectedTransports, array $expectedRecipients, array $expectedAllowedRecipients) { $container = $this->createContainerFromFile($configFile); @@ -2070,6 +2072,7 @@ public function testMailer(string $configFile, array $expectedTransports, array $l = $container->getDefinition('mailer.envelope_listener'); $this->assertSame('sender@example.org', $l->getArgument(0)); $this->assertSame($expectedRecipients, $l->getArgument(1)); + $this->assertSame($expectedAllowedRecipients, $l->getArgument(2)); $this->assertEquals(new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE), $container->getDefinition('mailer.mailer')->getArgument(1)); $this->assertTrue($container->hasDefinition('mailer.message_listener')); diff --git a/src/Symfony/Component/Mailer/CHANGELOG.md b/src/Symfony/Component/Mailer/CHANGELOG.md index 53b03a0d70fd..54e2a6cde092 100644 --- a/src/Symfony/Component/Mailer/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Dispatch Postmark's "406 - Inactive recipient" API error code as a `PostmarkDeliveryEvent` instead of throwing an exception * Add DSN param `auto_tls` to disable automatic STARTTLS + * Add support for allowing some users even if `recipients` is defined in `EnvelopeListener` 7.0 --- diff --git a/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php b/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php index db9dd723adee..9a1e39c69257 100644 --- a/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php +++ b/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php @@ -20,6 +20,7 @@ * Manipulates the Envelope of a Message. * * @author Fabien Potencier + * @author Grégoire Pineau */ class EnvelopeListener implements EventSubscriberInterface { @@ -32,9 +33,13 @@ class EnvelopeListener implements EventSubscriberInterface /** * @param array $recipients + * @param string[] $allowedRecipients An array of regex to match the allowed recipients */ - public function __construct(Address|string|null $sender = null, ?array $recipients = null) - { + public function __construct( + Address|string|null $sender = null, + ?array $recipients = null, + private array $allowedRecipients = [], + ) { if (null !== $sender) { $this->sender = Address::create($sender); } @@ -57,7 +62,27 @@ public function onMessage(MessageEvent $event): void } if ($this->recipients) { - $event->getEnvelope()->setRecipients($this->recipients); + $recipients = $this->recipients; + if ($this->allowedRecipients) { + foreach ($event->getEnvelope()->getRecipients() as $recipient) { + foreach ($this->allowedRecipients as $allowedRecipient) { + if (!preg_match('{\A'.$allowedRecipient.'\z}', $recipient->getAddress())) { + continue; + } + // dedup + foreach ($recipients as $r) { + if ($r->getName() === $recipient->getName() && $r->getAddress() === $recipient->getAddress()) { + continue 2; + } + } + + $recipients[] = $recipient; + continue 2; + } + } + } + + $event->getEnvelope()->setRecipients($recipients); } } diff --git a/src/Symfony/Component/Mailer/Tests/EventListener/EnvelopeListenerTest.php b/src/Symfony/Component/Mailer/Tests/EventListener/EnvelopeListenerTest.php new file mode 100644 index 000000000000..4a8d8ffc9b24 --- /dev/null +++ b/src/Symfony/Component/Mailer/Tests/EventListener/EnvelopeListenerTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\EventListener\EnvelopeListener; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\RawMessage; + +class EnvelopeListenerTest extends TestCase +{ + /** + * @dataProvider provideRecipientsTests + */ + public function testRecipients(array $expected, ?array $recipients = null, array $allowedRecipients = []) + { + $listener = new EnvelopeListener(null, $recipients, $allowedRecipients); + $message = new RawMessage('message'); + $envelope = new Envelope(new Address('sender@example.com'), [new Address('r1@example.com'), new Address('r2@symfony.com')]); + $event = new MessageEvent($message, $envelope, 'default'); + + $listener->onMessage($event); + + $recipients = array_map(fn (Address $a): string => $a->getAddress(), $event->getEnvelope()->getRecipients()); + $this->assertSame($expected, $recipients); + } + + public static function provideRecipientsTests(): iterable + { + yield [['r1@example.com', 'r2@symfony.com'], null, []]; + yield [['admin@admin.com'], ['admin@admin.com'], []]; + yield [['admin@admin.com', 'r1@example.com'], ['admin@admin.com'], ['.*@example\.com']]; + yield [['admin@admin.com', 'r1@example.com', 'r2@symfony.com'], ['admin@admin.com'], ['.*@example\.com', '.*@symfony\.com']]; + yield [['r1@example.com', 'r2@symfony.com'], ['r1@example.com'], ['.*@example\.com', '.*@symfony\.com']]; + } +}