Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Mailer] Improve oauth setup #52585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: 7.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2091,6 +2091,15 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
->end()
->end()
->end()
->arrayNode('smtp')
->fixXmlConfig('authenticator')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of adding a new config entry, I'd suggest passing a tagged_locator in the service definition
we should define the default authenticators as services with low priority so that then adding a new authenticator is just a matter of creating a new class that implements AuthenticatorInterface, thanks to registerForAutoconfiguration.

->children()
->arrayNode('authenticators')
->info('Services implementing AuthenticatorInterface to use with the EsmtpTransport')
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->end()
->end()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

according to mailer_transport.php this service is the EsmtpTransportFactory, but the CI tells that we somehow end up setting an argument to "Symfony\Component\Mailer\Mailer" - any idea what i am messing up here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the weird error message could be because of inlining the transport factory services. It might be inside one of the inlined services inside the mailer service.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that a bug of the DI container, or should i be doing something differently?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not call it a bug. The hard thing is that inline definitions don't have an id. So that's hard to know how to mention them in error messages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it is not a bug, what do i need differently to set the parameter on the correct place?


$classToServices = [
MailerBridge\Brevo\Transport\BrevoTransportFactory::class => 'mailer.transport_factory.brevo',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,7 @@
<xsd:element name="transport" type="mailer_transport" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="envelope" type="mailer_envelope" minOccurs="0" maxOccurs="1" />
<xsd:element name="header" type="header" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="smtp" type="mailer_smtp" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="enabled" type="xsd:boolean" />
<xsd:attribute name="dsn" type="xsd:string" />
Expand All @@ -758,6 +759,12 @@
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="mailer_smtp">
<xsd:sequence>
<xsd:element name="authenticator" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="http_cache">
<xsd:sequence>
<xsd:element name="private-header" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return static function (ContainerConfigurator $container) {
$container->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',
],
],
],
]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

<framework:config http-method-override="false" handle-all-throwables="true">
<framework:annotations enabled="false" />
<framework:php-errors log="true" />
<framework:mailer dsn="smtp://example.com">
<framework:smtp>
<framework:authenticator>my_authenticator_service1</framework:authenticator>
<framework:authenticator>my_authenticator_service2</framework:authenticator>
</framework:smtp>
</framework:mailer>
</framework:config>
</container>
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TokenProviderInterface?

{
/**
* Acquire the authentication token.
*/
public function getToken(): string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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?->getToken() : $client->getPassword();
$client->executeCommand('AUTH XOAUTH2 '.base64_encode('user='.$client->getUsername()."\1auth=Bearer ".$token."\1\1")."\r\n", [235]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>
*/
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need the new argument: calling setAuthenticators on the resulting instance should be enough

{
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();
Expand Down