Skip to content

Commit c39f25a

Browse files
committed
improve oauth setup for mailer
1 parent d277959 commit c39f25a

File tree

11 files changed

+138
-2
lines changed

11 files changed

+138
-2
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2091,6 +2091,15 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
20912091
->end()
20922092
->end()
20932093
->end()
2094+
->arrayNode('smtp')
2095+
->fixXmlConfig('authenticator')
2096+
->children()
2097+
->arrayNode('authenticators')
2098+
->info('Services implementing AuthenticatorInterface to use with the EsmtpTransport')
2099+
->prototype('scalar')->end()
2100+
->end()
2101+
->end()
2102+
->end()
20942103
->end()
20952104
->end()
20962105
->end()

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2556,6 +2556,11 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
25562556
} else {
25572557
$mailer->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE));
25582558
}
2559+
$authenticators = [];
2560+
foreach ($config['smtp']['authenticators'] ?? [] as $authenticator) {
2561+
$authenticators[] = new Reference($authenticator);
2562+
}
2563+
$container->getDefinition('mailer.transport_factory.smtp')->setArgument(3, $authenticators);
25592564

25602565
$classToServices = [
25612566
MailerBridge\Brevo\Transport\BrevoTransportFactory::class => 'mailer.transport_factory.brevo',

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@
737737
<xsd:element name="transport" type="mailer_transport" minOccurs="0" maxOccurs="unbounded" />
738738
<xsd:element name="envelope" type="mailer_envelope" minOccurs="0" maxOccurs="1" />
739739
<xsd:element name="header" type="header" minOccurs="0" maxOccurs="unbounded" />
740+
<xsd:element name="smtp" type="smtp" minOccurs="0" maxOccurs="1" />
740741
</xsd:sequence>
741742
<xsd:attribute name="enabled" type="xsd:boolean" />
742743
<xsd:attribute name="dsn" type="xsd:string" />
@@ -758,6 +759,12 @@
758759
</xsd:sequence>
759760
</xsd:complexType>
760761

762+
<xsd:complexType name="smtp">
763+
<xsd:sequence>
764+
<xsd:element name="authenticator" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
765+
</xsd:sequence>
766+
</xsd:complexType>
767+
761768
<xsd:complexType name="http_cache">
762769
<xsd:sequence>
763770
<xsd:element name="private-header" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
4+
5+
return static function (ContainerConfigurator $container) {
6+
$container->extension('framework', [
7+
'annotations' => false,
8+
'http_method_override' => false,
9+
'handle_all_throwables' => true,
10+
'php_errors' => ['log' => true],
11+
'mailer' => [
12+
'smtp' => [
13+
'authenticators' => [
14+
'my_authenticator_service1',
15+
'my_authenticator_service2',
16+
],
17+
],
18+
],
19+
]);
20+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:framework="http://symfony.com/schema/dic/symfony"
6+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
7+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
8+
9+
<framework:config http-method-override="false" handle-all-throwables="true">
10+
<framework:annotations enabled="false" />
11+
<framework:php-errors log="true" />
12+
<framework:mailer dsn="smtp://example.com">
13+
<framework:smtp>
14+
<framework:authenticator>my_authenticator_service1</framework:authenticator>
15+
<framework:authenticator>my_authenticator_service2</framework:authenticator>
16+
</framework:smtp>
17+
</framework:mailer>
18+
</framework:config>
19+
</container>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
framework:
2+
annotations: false
3+
http_method_override: false
4+
handle_all_throwables: true
5+
php_errors:
6+
log: true
7+
mailer:
8+
smtp:
9+
authenticators:
10+
- my_authenticator_service1
11+
- my_authenticator_service2

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,13 @@ public function testMailerWithSpecificMessageBus()
20312031
$this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('mailer.mailer')->getArgument(1));
20322032
}
20332033

2034+
public function testMailerWithAuthenticators()
2035+
{
2036+
$container = $this->createContainerFromFile('mailer_with_authenticators');
2037+
2038+
$this->assertCount(2, $container->getDefinition('mailer.mailer')->getArgument(3));
2039+
}
2040+
20342041
public function testHttpClientMockResponseFactory()
20352042
{
20362043
$container = $this->createContainerFromFile('http_client_mock_response_factory');
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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\Mailer\Transport\Smtp\Auth;
13+
14+
/**
15+
* The auth token provider knows how to create a valid token for the XOAuth2Authenticator.
16+
*
17+
* Usually with OAuth, you will need to do some web request to fetch the token.
18+
* You also want to cache the token for as long as it is valid.
19+
*/
20+
interface AuthTokenProviderInterface
21+
{
22+
/**
23+
* Acquire the authentication token.
24+
*/
25+
public function getToken(): string;
26+
}

src/Symfony/Component/Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
*/
2323
class XOAuth2Authenticator implements AuthenticatorInterface
2424
{
25+
private ?AuthTokenProviderInterface $tokenProvider;
26+
27+
public function __construct(AuthTokenProviderInterface $tokenProvider = null)
28+
{
29+
$this->tokenProvider = $tokenProvider;
30+
}
31+
2532
public function getAuthKeyword(): string
2633
{
2734
return 'XOAUTH2';
@@ -32,6 +39,7 @@ public function getAuthKeyword(): string
3239
*/
3340
public function authenticate(EsmtpTransport $client): void
3441
{
35-
$client->executeCommand('AUTH XOAUTH2 '.base64_encode('user='.$client->getUsername()."\1auth=Bearer ".$client->getPassword()."\1\1")."\r\n", [235]);
42+
$token = $this->tokenProvider ? $this->tokenProvider->getToken() : $client->getPassword();
43+
$client->executeCommand('AUTH XOAUTH2 '.base64_encode('user='.$client->getUsername()."\1auth=Bearer ".$token."\1\1")."\r\n", [235]);
3644
}
3745
}

src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,17 @@
2727
*/
2828
class EsmtpTransport extends SmtpTransport
2929
{
30+
/**
31+
* @var AuthenticatorInterface[]
32+
*/
3033
private array $authenticators = [];
3134
private string $username = '';
3235
private string $password = '';
3336
private array $capabilities;
3437

38+
/**
39+
* @param AuthenticatorInterface[]|null $authenticators
40+
*/
3541
public function __construct(string $host = 'localhost', int $port = 0, bool $tls = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null, AbstractStream $stream = null, array $authenticators = null)
3642
{
3743
parent::__construct($stream, $dispatcher, $logger);

src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,41 @@
1111

1212
namespace Symfony\Component\Mailer\Transport\Smtp;
1313

14+
use Psr\EventDispatcher\EventDispatcherInterface;
15+
use Psr\Log\LoggerInterface;
1416
use Symfony\Component\Mailer\Transport\AbstractTransportFactory;
1517
use Symfony\Component\Mailer\Transport\Dsn;
18+
use Symfony\Component\Mailer\Transport\Smtp\Auth\AuthenticatorInterface;
1619
use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
1720
use Symfony\Component\Mailer\Transport\TransportInterface;
21+
use Symfony\Contracts\HttpClient\HttpClientInterface;
1822

1923
/**
2024
* @author Konstantin Myakshin <molodchick@gmail.com>
2125
*/
2226
final class EsmtpTransportFactory extends AbstractTransportFactory
2327
{
28+
/**
29+
* @var AuthenticatorInterface[]
30+
*/
31+
private ?array $authenticators;
32+
33+
/**
34+
* @param AuthenticatorInterface[]|null $authenticators
35+
*/
36+
public function __construct(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null, array $authenticators = null)
37+
{
38+
parent::__construct($dispatcher, $client, $logger);
39+
$this->authenticators = $authenticators;
40+
}
41+
2442
public function create(Dsn $dsn): TransportInterface
2543
{
2644
$tls = 'smtps' === $dsn->getScheme() ? true : null;
2745
$port = $dsn->getPort(0);
2846
$host = $dsn->getHost();
2947

30-
$transport = new EsmtpTransport($host, $port, $tls, $this->dispatcher, $this->logger);
48+
$transport = new EsmtpTransport($host, $port, $tls, $this->dispatcher, $this->logger, null, $this->authenticators);
3149

3250
/** @var SocketStream $stream */
3351
$stream = $transport->getStream();

0 commit comments

Comments
 (0)