From 5106e21690665675c0520291b3657b14fffc7f9a Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Tue, 14 Nov 2023 16:50:42 +0100 Subject: [PATCH 1/2] improve oauth setup for mailer --- .../DependencyInjection/Configuration.php | 9 +++++++ .../FrameworkExtension.php | 5 ++++ .../Resources/config/schema/symfony-1.0.xsd | 7 +++++ .../php/mailer_with_authenticators.php | 20 ++++++++++++++ .../xml/mailer_with_authenticators.xml | 19 ++++++++++++++ .../yml/mailer_with_authenticators.yml | 11 ++++++++ .../FrameworkExtensionTestCase.php | 7 +++++ .../Smtp/Auth/AuthTokenProviderInterface.php | 26 +++++++++++++++++++ .../Smtp/Auth/XOAuth2Authenticator.php | 10 ++++++- .../Mailer/Transport/Smtp/EsmtpTransport.php | 6 +++++ .../Transport/Smtp/EsmtpTransportFactory.php | 20 +++++++++++++- 11 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_authenticators.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_authenticators.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_authenticators.yml create mode 100644 src/Symfony/Component/Mailer/Transport/Smtp/Auth/AuthTokenProviderInterface.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 902d3468fc835..59c6883802af5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -2091,6 +2091,15 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl ->end() ->end() ->end() + ->arrayNode('smtp') + ->fixXmlConfig('authenticator') + ->children() + ->arrayNode('authenticators') + ->info('Services implementing AuthenticatorInterface to use with the EsmtpTransport') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index d03932f8c4840..7041545337e45 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2556,6 +2556,11 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co } else { $mailer->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE)); } + $authenticators = []; + foreach ($config['smtp']['authenticators'] ?? [] as $authenticator) { + $authenticators[] = new Reference($authenticator); + } + $container->getDefinition('mailer.transport_factory.smtp')->setArgument(3, $authenticators); $classToServices = [ MailerBridge\Brevo\Transport\BrevoTransportFactory::class => 'mailer.transport_factory.brevo', 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 532cf022d3c66..3fa69a6a25aac 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 @@ -737,6 +737,7 @@ + @@ -758,6 +759,12 @@ + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_authenticators.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_authenticators.php new file mode 100644 index 0000000000000..4e3bade10f552 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_authenticators.php @@ -0,0 +1,20 @@ +extension('framework', [ + 'annotations' => false, + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'mailer' => [ + 'smtp' => [ + 'authenticators' => [ + 'my_authenticator_service1', + 'my_authenticator_service2', + ], + ], + ], + ]); +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_authenticators.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_authenticators.xml new file mode 100644 index 0000000000000..ee9657ea7da59 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_authenticators.xml @@ -0,0 +1,19 @@ + + + + + + + + + + my_authenticator_service1 + my_authenticator_service2 + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_authenticators.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_authenticators.yml new file mode 100644 index 0000000000000..05d129c6aa4e8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_authenticators.yml @@ -0,0 +1,11 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + mailer: + smtp: + authenticators: + - my_authenticator_service1 + - my_authenticator_service2 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 9036fc6d58da7..add701479f8ed 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -2031,6 +2031,13 @@ public function testMailerWithSpecificMessageBus() $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('mailer.mailer')->getArgument(1)); } + public function testMailerWithAuthenticators() + { + $container = $this->createContainerFromFile('mailer_with_authenticators'); + + $this->assertCount(2, $container->getDefinition('mailer.mailer')->getArgument(3)); + } + public function testHttpClientMockResponseFactory() { $container = $this->createContainerFromFile('http_client_mock_response_factory'); diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Auth/AuthTokenProviderInterface.php b/src/Symfony/Component/Mailer/Transport/Smtp/Auth/AuthTokenProviderInterface.php new file mode 100644 index 0000000000000..4714aa4f2ae2c --- /dev/null +++ b/src/Symfony/Component/Mailer/Transport/Smtp/Auth/AuthTokenProviderInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp\Auth; + +/** + * The auth token provider knows how to create a valid token for the XOAuth2Authenticator. + * + * Usually with OAuth, you will need to do some web request to fetch the token. + * You also want to cache the token for as long as it is valid. + */ +interface AuthTokenProviderInterface +{ + /** + * Acquire the authentication token. + */ + public function getToken(): string; +} diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php b/src/Symfony/Component/Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php index c3aaa4909dbd0..e81a555bcfc63 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php @@ -22,6 +22,13 @@ */ class XOAuth2Authenticator implements AuthenticatorInterface { + private ?AuthTokenProviderInterface $tokenProvider; + + public function __construct(AuthTokenProviderInterface $tokenProvider = null) + { + $this->tokenProvider = $tokenProvider; + } + public function getAuthKeyword(): string { return 'XOAUTH2'; @@ -32,6 +39,7 @@ public function getAuthKeyword(): string */ public function authenticate(EsmtpTransport $client): void { - $client->executeCommand('AUTH XOAUTH2 '.base64_encode('user='.$client->getUsername()."\1auth=Bearer ".$client->getPassword()."\1\1")."\r\n", [235]); + $token = $this->tokenProvider ? $this->tokenProvider->getToken() : $client->getPassword(); + $client->executeCommand('AUTH XOAUTH2 '.base64_encode('user='.$client->getUsername()."\1auth=Bearer ".$token."\1\1")."\r\n", [235]); } } diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php index 2683050c9cb22..3551b8e65919c 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php @@ -27,11 +27,17 @@ */ class EsmtpTransport extends SmtpTransport { + /** + * @var AuthenticatorInterface[] + */ private array $authenticators = []; private string $username = ''; private string $password = ''; private array $capabilities; + /** + * @param AuthenticatorInterface[]|null $authenticators + */ public function __construct(string $host = 'localhost', int $port = 0, bool $tls = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null, AbstractStream $stream = null, array $authenticators = null) { parent::__construct($stream, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php index a15d12245d19b..fa5a537a7b242 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php @@ -11,23 +11,41 @@ namespace Symfony\Component\Mailer\Transport\Smtp; +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\Transport\AbstractTransportFactory; use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\Smtp\Auth\AuthenticatorInterface; use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; /** * @author Konstantin Myakshin */ final class EsmtpTransportFactory extends AbstractTransportFactory { + /** + * @var AuthenticatorInterface[] + */ + private ?array $authenticators; + + /** + * @param AuthenticatorInterface[]|null $authenticators + */ + public function __construct(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null, array $authenticators = null) + { + parent::__construct($dispatcher, $client, $logger); + $this->authenticators = $authenticators; + } + public function create(Dsn $dsn): TransportInterface { $tls = 'smtps' === $dsn->getScheme() ? true : null; $port = $dsn->getPort(0); $host = $dsn->getHost(); - $transport = new EsmtpTransport($host, $port, $tls, $this->dispatcher, $this->logger); + $transport = new EsmtpTransport($host, $port, $tls, $this->dispatcher, $this->logger, null, $this->authenticators); /** @var SocketStream $stream */ $stream = $transport->getStream(); From 09acfdf3e7ef4ba22b35b9041b1c62424e5be459 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 20 Nov 2023 14:28:40 +0100 Subject: [PATCH 2/2] Update src/Symfony/Component/Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php Co-authored-by: Nicolas Grekas --- .../Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php b/src/Symfony/Component/Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php index e81a555bcfc63..61a6d88a8d484 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php @@ -39,7 +39,7 @@ public function getAuthKeyword(): string */ public function authenticate(EsmtpTransport $client): void { - $token = $this->tokenProvider ? $this->tokenProvider->getToken() : $client->getPassword(); + $token = $this->tokenProvider?->getToken() : $client->getPassword(); $client->executeCommand('AUTH XOAUTH2 '.base64_encode('user='.$client->getUsername()."\1auth=Bearer ".$token."\1\1")."\r\n", [235]); } }