Skip to content

Commit

Permalink
feature #54044 [Mailer] Add support for allowing some users even if `…
Browse files Browse the repository at this point in the history
…recipients` is defined in `EnvelopeListener` (lyrixx)

This PR was merged into the 7.1 branch.

Discussion
----------

[Mailer] Add support for allowing some users even if `recipients` is defined in `EnvelopeListener`

| Q             | A
| ------------- | ---
| Branch?       | 7.1
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | -
| License       | MIT

I'm migrate an application from SwiftMailer to symfony/mailer, and this options was used.

---

depends on #54292

Commits
-------

6228896 [Mailer] Add support for allowing some users even if `recipients` is defined in `EnvelopeListener`
  • Loading branch information
fabpot committed Apr 9, 2024
2 parents de924b2 + 6228896 commit ce408ab
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Expand Up @@ -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
---
Expand Down
Expand Up @@ -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()
Expand Down
Expand Up @@ -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);
Expand Down
Expand Up @@ -764,6 +764,7 @@
<xsd:sequence>
<xsd:element name="sender" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="recipient" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="allowed-recipient" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>

Expand Down
Expand Up @@ -13,6 +13,7 @@
'envelope' => [
'sender' => 'sender@example.org',
'recipients' => ['redirected@example.org'],
'allowed_recipients' => ['foobar@example\.org'],
],
'headers' => [
'from' => 'from@example.org',
Expand Down
Expand Up @@ -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',
Expand Down
Expand Up @@ -13,6 +13,7 @@
<framework:envelope>
<framework:sender>sender@example.org</framework:sender>
<framework:recipient>redirected@example.org</framework:recipient>
<framework:allowed-recipient>foobar@example\.org</framework:allowed-recipient>
</framework:envelope>
<framework:header name="from">from@example.org</framework:header>
<framework:header name="bcc">bcc1@example.org</framework:header>
Expand Down
Expand Up @@ -16,6 +16,8 @@
<framework:sender>sender@example.org</framework:sender>
<framework:recipient>redirected@example.org</framework:recipient>
<framework:recipient>redirected1@example.org</framework:recipient>
<framework:allowed-recipient>foobar@example\.org</framework:allowed-recipient>
<framework:allowed-recipient>.*@example\.com</framework:allowed-recipient>
</framework:envelope>
<framework:header name="from">from@example.org</framework:header>
<framework:header name="bcc">bcc1@example.org</framework:header>
Expand Down
Expand Up @@ -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]
Expand Down
Expand Up @@ -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]
Expand Down
Expand Up @@ -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',
Expand All @@ -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);

Expand All @@ -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'));
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Mailer/CHANGELOG.md
Expand Up @@ -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
---
Expand Down
31 changes: 28 additions & 3 deletions src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php
Expand Up @@ -20,6 +20,7 @@
* Manipulates the Envelope of a Message.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class EnvelopeListener implements EventSubscriberInterface
{
Expand All @@ -32,9 +33,13 @@ class EnvelopeListener implements EventSubscriberInterface

/**
* @param array<Address|string> $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);
}
Expand All @@ -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);
}
}

Expand Down
@@ -0,0 +1,47 @@
<?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\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']];
}
}

0 comments on commit ce408ab

Please sign in to comment.