diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/Amazon/CHANGELOG.md index 9830cadaa10c8..fe8c53365d84a 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/CHANGELOG.md @@ -5,9 +5,13 @@ CHANGELOG ----- * [BC BREAK] Renamed and moved `Symfony\Component\Mailer\Bridge\Amazon\Http\Api\SesTransport` - to `Symfony\Component\Mailer\Bridge\Amazon\Transpor\SesApiTransport`, `Symfony\Component\Mailer\Bridge\Amazon\Http\SesTransport` + to `Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiTransport`, `Symfony\Component\Mailer\Bridge\Amazon\Http\SesTransport` to `Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpTransport`, `Symfony\Component\Mailer\Bridge\Amazon\Smtp\SesTransport` to `Symfony\Component\Mailer\Bridge\Amazon\Transport\SesSmtpTransport`. + * [BC BREAK] changed `Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiTransport::__construct` username and password arguments to credential + * [BC BREAK] changed `Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpTransport::__construct` username and password arguments to credential + * [BC BREAK] changed `Symfony\Component\Mailer\Bridge\Amazon\Transport\SesSmtpTransport::__construct` username and password arguments to credential + * Added Instance Profile support 4.3.0 ----- diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Credential/ApiTokenCredential.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Credential/ApiTokenCredential.php new file mode 100644 index 0000000000000..c346b8f5ceeb0 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Credential/ApiTokenCredential.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Amazon\Credential; + +/** + * @author Karoly Gossler + */ +final class ApiTokenCredential +{ + private $accessKey; + + private $secretKey; + + private $token; + + private $expiration; + + public function __construct(string $accessKey, string $secretKey, string $token, \DateTime $expiration) + { + $this->accessKey = $accessKey; + $this->secretKey = $secretKey; + $this->token = $token; + $this->expiration = $expiration; + } + + public function getAccessKey(): string + { + return $this->accessKey; + } + + public function getSecretKey(): string + { + return $this->secretKey; + } + + public function getToken(): string + { + return $this->token; + } + + public function getExpiration(): \DateTime + { + return $this->expiration; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Credential/InstanceCredentialProvider.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Credential/InstanceCredentialProvider.php new file mode 100644 index 0000000000000..e2e4160e8d4b6 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Credential/InstanceCredentialProvider.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Amazon\Credential; + +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\Mailer\Exception\RuntimeException; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * Based on: aws-sdk-php / Credentials/InstanceProfileProvider.php. + * + * @author Karoly Gossler + */ +class InstanceCredentialProvider +{ + const SERVER_URI_TEMPLATE = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/%role_name%'; + + public function __construct(HttpClientInterface $client = null, int $retries = 3) + { + $this->retries = $retries; + $this->client = $client; + + if (null === $this->client) { + if (!class_exists(HttpClient::class)) { + throw new LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__)); + } + + $this->client = HttpClient::create(); + } + } + + public function getCredential(string $roleName): ApiTokenCredential + { + $attempts = 0; + + $instanceMetadataServerURL = str_replace('%role_name%', $roleName, self::SERVER_URI_TEMPLATE); + + while (true) { + try { + ++$attempts; + + $response = $this->client->request('GET', $instanceMetadataServerURL); + + if (200 === $response->getStatusCode()) { + $content = json_decode($response->getContent(), true); + + if (null === $content) { + throw new RuntimeException('Unexpected instance metadata response.'); + } + + if ('Success' !== $content['Code']) { + $msg = sprintf('Unexpected instance profile response: %s', $content['Code']); + throw new RuntimeException($msg); + } + + return new ApiTokenCredential($content['AccessKeyId'], $content['SecretAccessKey'], $content['Token'], new \DateTime($content['Expiration'])); + } elseif (404 === $response->getStatusCode()) { + $attempts = $this->retries + 1; + } + + sleep(pow(1.2, $attempts)); + } catch (\Exception $e) { + } + + if ($attempts > $this->retries) { + throw new RuntimeException('Error retrieving credentials from instance metadata server.'); + } + } + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Credential/UsernamePasswordCredential.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Credential/UsernamePasswordCredential.php new file mode 100644 index 0000000000000..0b06609d6a856 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Credential/UsernamePasswordCredential.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Amazon\Credential; + +/** + * @author Karoly Gossler + */ +final class UsernamePasswordCredential +{ + private $username; + + private $password; + + public function __construct(string $username, string $password) + { + $this->username = $username; + $this->password = $password; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getPassword(): string + { + return $this->password; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/SesRequest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/SesRequest.php new file mode 100644 index 0000000000000..a2c5076d88002 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/SesRequest.php @@ -0,0 +1,246 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Amazon; + +use Symfony\Component\Mailer\Bridge\Amazon\Credential\ApiTokenCredential; +use Symfony\Component\Mailer\Bridge\Amazon\Credential\UsernamePasswordCredential; +use Symfony\Component\Mailer\Exception\RuntimeException; +use Symfony\Component\Mailer\SmtpEnvelope; +use Symfony\Component\Mime\Email; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Karoly Gossler + */ +class SesRequest +{ + private const SERVICE_NAME = 'ses'; + private const ENDPOINT_HOST = 'email.%region%.amazonaws.com'; + public const REQUEST_MODE_API = 1; + public const REQUEST_MODE_HTTP = 2; + + private $mode = self::REQUEST_MODE_API; + + private $now; + private $action; + private $region; + private $credential; + + private $requestHeaders = []; + private $canonicalHeaders = ''; + private $signedHeaders = []; + + public function __construct(HttpClientInterface $client, string $region) + { + $this->client = $client; + $this->region = $region ?: 'eu-west-1'; + } + + public function getRegion(): string + { + return $this->region; + } + + public function setRegion(string $region): self + { + $this->region = $region; + + return $this; + } + + public function getMode(): int + { + return $this->mode; + } + + public function setMode(int $mode): self + { + $this->mode = $mode; + + return $this; + } + + public function getCredential() + { + return $this->credential; + } + + public function setCredential($credential): self + { + $this->credential = $credential; + + return $this; + } + + public function sendEmail(Email $email, SmtpEnvelope $envelope): ResponseInterface + { + $this->now = new \DateTime(); + $this->action = 'SendEmail'; + $this->method = 'POST'; + + $payload = [ + 'Action' => 'SendEmail', + 'Destination.ToAddresses.member' => $this->stringifyAddresses($this->getRecipients($email, $envelope)), + 'Message.Subject.Data' => $email->getSubject(), + 'Source' => $envelope->getSender()->toString(), + ]; + + if ($emails = $email->getCc()) { + $payload['Destination.CcAddresses.member'] = $this->stringifyAddresses($emails); + } + if ($emails = $email->getBcc()) { + $payload['Destination.BccAddresses.member'] = $this->stringifyAddresses($emails); + } + if ($email->getTextBody()) { + $payload['Message.Body.Text.Data'] = $email->getTextBody(); + } + if ($email->getHtmlBody()) { + $payload['Message.Body.Html.Data'] = $email->getHtmlBody(); + } + + $this->payload = $payload; + + $this->prepareRequestHeaders(); + + return $this->sendRequest(); + } + + public function sendRawEmail(string $rawEmail): ResponseInterface + { + $this->now = new \DateTime(); + $this->action = 'SendRawEmail'; + $this->method = 'POST'; + $this->payload = [ + 'Action' => 'SendRawEmail', + 'RawMessage.Data' => base64_encode($rawEmail), + ]; + + $this->prepareRequestHeaders(); + + return $this->sendRequest(); + } + + private function sendRequest(): ResponseInterface + { + $options = [ + 'headers' => $this->requestHeaders, + 'body' => $this->payload, + ]; + + return $this->client->request($this->method, 'https://'.$this->getEndpointHost(), $options); + } + + private function prepareRequestHeaders(): void + { + $this->requestHeaders = []; + + if (self::REQUEST_MODE_API === $this->mode) { + $this->requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + $this->requestHeaders['Host'] = $this->getEndpointHost(); + $this->requestHeaders['X-Amz-Date'] = $this->now->format('Ymd\THis\Z'); + if ($this->credential instanceof ApiTokenCredential) { + $this->requestHeaders['X-Amz-Security-Token'] = $this->credential->getToken(); + } + ksort($this->requestHeaders); + + $canonicalHeadersBuffer = []; + foreach ($this->requestHeaders as $key => $value) { + $canonicalHeadersBuffer[] = strtolower($key).':'.$value; + } + $canonicalHeaders = implode("\n", $canonicalHeadersBuffer); + + $signedHeadersBuffer = []; + foreach ($this->requestHeaders as $key => $value) { + $signedHeadersBuffer[] = strtolower($key); + } + $signedHeaders = implode(';', $signedHeadersBuffer); + + $hashedCanonicalRequest = hash('sha256', sprintf( + "%s\n/\n\n%s\n\n%s\n%s", + $this->method, + $canonicalHeaders, + $signedHeaders, + hash('sha256', $this->arrayToSignableString($this->payload)), + )); + + $scope = $this->now->format('Ymd').'/'.$this->region.'/'.self::SERVICE_NAME.'/aws4_request'; + + $stringToSign = sprintf( + "%s\n%s\n%s\n%s", + 'AWS4-HMAC-SHA256', + $this->now->format('Ymd\THis\Z'), + $scope, + $hashedCanonicalRequest, + ); + + if ($this->credential instanceof ApiTokenCredential) { + $keySecret = 'AWS4'.$this->credential->getSecretKey(); + } elseif ($this->credential instanceof UsernamePasswordCredential) { + $keySecret = 'AWS4'.$this->credential->getPassword(); + } else { + throw new RuntimeException('Unsupported credential'); + } + + $keyDate = hash_hmac('sha256', $this->now->format('Ymd'), $keySecret, true); + $keyRegion = hash_hmac('sha256', $this->region, $keyDate, true); + $keyService = hash_hmac('sha256', self::SERVICE_NAME, $keyRegion, true); + $keySigning = hash_hmac('sha256', 'aws4_request', $keyService, true); + $signature = hash_hmac('sha256', $stringToSign, $keySigning); + + if ($this->credential instanceof ApiTokenCredential) { + $fullCredentialString = $this->credential->getAccessKey().'/'.$scope; + } elseif ($this->credential instanceof UsernamePasswordCredential) { + $fullCredentialString = $this->credential->getUsername().'/'.$scope; + } + + $this->requestHeaders['Authorization'] = sprintf( + 'AWS4-HMAC-SHA256 Credential=%s, SignedHeaders=%s, Signature=%s', + $fullCredentialString, + $signedHeaders, + $signature + ); + } + + private function arrayToSignableString(array $array): string + { + $buffer = ''; + + foreach ($array as $key => $value) { + $key = str_replace('%7E', '~', rawurlencode($key)); + $value = str_replace('%7E', '~', rawurlencode($value)); + + $buffer .= '&'.$key.'='.$value; + } + + return substr($buffer, 1); + } + + private function getEndpointHost(): string + { + return str_replace('%region%', $this->region, self::ENDPOINT_HOST); + } + + /** + * @param Address[] $addresses + * + * @return string[] + */ + private function stringifyAddresses(array $addresses): array + { + return array_map(function (Address $a) { + return $a->toString(); + }, $addresses); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php index 5305540b39860..5ee004eac51eb 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php @@ -25,19 +25,16 @@ */ class SesApiTransport extends AbstractApiTransport { - private const ENDPOINT = 'https://email.%region%.amazonaws.com'; - - private $accessKey; - private $secretKey; + private $credential; private $region; /** - * @param string $region Amazon SES region (currently one of us-east-1, us-west-2, or eu-west-1) + * @param ApiTokenCredential|UsernamePasswordCredential $credential credential object for SES authentication. ApiTokenCredential and UsernamePasswordCredential are supported. + * @param string $region Amazon SES region (currently one of us-east-1, us-west-2, or eu-west-1) */ - public function __construct(string $accessKey, string $secretKey, string $region = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct($credential, string $region = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) { - $this->accessKey = $accessKey; - $this->secretKey = $secretKey; + $this->credential = $credential; $this->region = $region ?: 'eu-west-1'; parent::__construct($client, $dispatcher, $logger); @@ -45,23 +42,27 @@ public function __construct(string $accessKey, string $secretKey, string $region public function getName(): string { - return sprintf('api://%s@ses?region=%s', $this->accessKey, $this->region); + $login = null; + if ($this->credential instanceof ApiTokenCredential) { + $login = $this->credential->getAccessKey(); + } else { + $login = $this->credential->getUsername(); + } + + return sprintf('api://%s@ses?region=%s', $login, $this->region); } protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInterface { - $date = gmdate('D, d M Y H:i:s e'); - $auth = sprintf('AWS3-HTTPS AWSAccessKeyId=%s,Algorithm=HmacSHA256,Signature=%s', $this->accessKey, $this->getSignature($date)); + $request = new SesRequest($this->client, $this->region); + $request->setMode(SesRequest::REQUEST_MODE_API); + $request->setCredential($this->credential); - $endpoint = str_replace('%region%', $this->region, self::ENDPOINT); - $response = $this->client->request('POST', $endpoint, [ - 'headers' => [ - 'X-Amzn-Authorization' => $auth, - 'Date' => $date, - 'Content-Type' => 'application/x-www-form-urlencoded', - ], - 'body' => $this->getPayload($email, $envelope), - ]); + if ($email->getAttachments()) { + $response = $request->sendRawEmail($email->toString()); + } else { + $response = $request->sendEmail($email, $envelope); + } if (200 !== $response->getStatusCode()) { $error = new \SimpleXMLElement($response->getContent(false)); @@ -71,41 +72,4 @@ protected function doSendApi(Email $email, SmtpEnvelope $envelope): ResponseInte return $response; } - - private function getSignature(string $string): string - { - return base64_encode(hash_hmac('sha256', $string, $this->secretKey, true)); - } - - private function getPayload(Email $email, SmtpEnvelope $envelope): array - { - if ($email->getAttachments()) { - return [ - 'Action' => 'SendRawEmail', - 'RawMessage.Data' => base64_encode($email->toString()), - ]; - } - - $payload = [ - 'Action' => 'SendEmail', - 'Destination.ToAddresses.member' => $this->stringifyAddresses($this->getRecipients($email, $envelope)), - 'Message.Subject.Data' => $email->getSubject(), - 'Source' => $envelope->getSender()->toString(), - ]; - - if ($emails = $email->getCc()) { - $payload['Destination.CcAddresses.member'] = $this->stringifyAddresses($emails); - } - if ($emails = $email->getBcc()) { - $payload['Destination.BccAddresses.member'] = $this->stringifyAddresses($emails); - } - if ($email->getTextBody()) { - $payload['Message.Body.Text.Data'] = $email->getTextBody(); - } - if ($email->getHtmlBody()) { - $payload['Message.Body.Html.Data'] = $email->getHtmlBody(); - } - - return $payload; - } } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php index e93511fdde8a8..aab945ccc670c 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php @@ -12,6 +12,9 @@ namespace Symfony\Component\Mailer\Bridge\Amazon\Transport; use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Bridge\Amazon\Credential\ApiTokenCredential; +use Symfony\Component\Mailer\Bridge\Amazon\Credential\UsernamePasswordCredential; +use Symfony\Component\Mailer\Bridge\Amazon\SesRequest; use Symfony\Component\Mailer\Exception\HttpTransportException; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\AbstractHttpTransport; @@ -24,19 +27,16 @@ */ class SesHttpTransport extends AbstractHttpTransport { - private const ENDPOINT = 'https://email.%region%.amazonaws.com'; - - private $accessKey; - private $secretKey; + private $credential; private $region; /** - * @param string $region Amazon SES region (currently one of us-east-1, us-west-2, or eu-west-1) + * @param ApiTokenCredential|UsernamePasswordCredential $credential credential object for SES authentication. ApiTokenCredential and UsernamePasswordCredential are supported. + * @param string $region Amazon SES region (currently one of us-east-1, us-west-2, or eu-west-1) */ - public function __construct(string $accessKey, string $secretKey, string $region = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct($credential, string $region = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) { - $this->accessKey = $accessKey; - $this->secretKey = $secretKey; + $this->credential = $credential; $this->region = $region ?: 'eu-west-1'; parent::__construct($client, $dispatcher, $logger); @@ -44,25 +44,23 @@ public function __construct(string $accessKey, string $secretKey, string $region public function getName(): string { - return sprintf('http://%s@ses?region=%s', $this->accessKey, $this->region); + $login = null; + if ($this->credential instanceof ApiTokenCredential) { + $login = $this->credential->getAccessKey(); + } else { + $login = $this->credential->getUsername(); + } + + return sprintf('http://%s@ses?region=%s', $login, $this->region); } protected function doSendHttp(SentMessage $message): ResponseInterface { - $date = gmdate('D, d M Y H:i:s e'); - $auth = sprintf('AWS3-HTTPS AWSAccessKeyId=%s,Algorithm=HmacSHA256,Signature=%s', $this->accessKey, $this->getSignature($date)); + $request = new SesRequest($this->client, $this->region); + $request->setMode(SesRequest::REQUEST_MODE_HTTP); + $request->setCredential($this->credential); - $endpoint = str_replace('%region%', $this->region, self::ENDPOINT); - $response = $this->client->request('POST', $endpoint, [ - 'headers' => [ - 'X-Amzn-Authorization' => $auth, - 'Date' => $date, - ], - 'body' => [ - 'Action' => 'SendRawEmail', - 'RawMessage.Data' => base64_encode($message->toString()), - ], - ]); + $response = $request->sendRawEmail($message->toString()); if (200 !== $response->getStatusCode()) { $error = new \SimpleXMLElement($response->getContent(false)); @@ -72,9 +70,4 @@ protected function doSendHttp(SentMessage $message): ResponseInterface return $response; } - - private function getSignature(string $string): string - { - return base64_encode(hash_hmac('sha256', $string, $this->secretKey, true)); - } } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesSmtpTransport.php index 2c53200689514..18f2b137d1c65 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesSmtpTransport.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Mailer\Bridge\Amazon\Transport; use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Bridge\Amazon\Credential\UsernamePasswordCredential; use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -21,13 +22,14 @@ class SesSmtpTransport extends EsmtpTransport { /** - * @param string $region Amazon SES region (currently one of us-east-1, us-west-2, or eu-west-1) + * @param UsernamePasswordCredential $credential credential object for SES authentication + * @param string $region Amazon SES region (currently one of us-east-1, us-west-2, or eu-west-1) */ - public function __construct(string $username, string $password, string $region = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(UsernamePasswordCredential $credential, string $region = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) { parent::__construct(sprintf('email-smtp.%s.amazonaws.com', $region ?: 'eu-west-1'), 587, true, $dispatcher, $logger); - $this->setUsername($username); - $this->setPassword($password); + $this->setUsername($credential->getUsername()); + $this->setPassword($credential->getPassword()); } } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesTransportFactory.php index 0dba1d998b465..d2051313e9598 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesTransportFactory.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesTransportFactory.php @@ -11,6 +11,9 @@ namespace Symfony\Component\Mailer\Bridge\Amazon\Transport; +use Symfony\Component\Mailer\Bridge\Amazon\Credential\InstanceCredentialProvider; +use Symfony\Component\Mailer\Bridge\Amazon\Credential\UsernamePasswordCredential; +use Symfony\Component\Mailer\Exception\IncompleteDsnException; use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; use Symfony\Component\Mailer\Transport\AbstractTransportFactory; use Symfony\Component\Mailer\Transport\Dsn; @@ -21,23 +24,41 @@ */ final class SesTransportFactory extends AbstractTransportFactory { + private $credentialProvider; + + public function __construct(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null) + { + parent::__construct($dispatcher, $client, $logger); + + $this->credentialProvider = new InstanceCredentialProvider($client); + } + public function create(Dsn $dsn): TransportInterface { $scheme = $dsn->getScheme(); - $user = $this->getUser($dsn); - $password = $this->getPassword($dsn); + try { + $credential = new UsernamePasswordCredential($this->getUser($dsn), $this->getPassword($dsn)); + } catch (IncompleteDsnException $e) { + $role = $dsn->getOption('role'); + + if (null === $role) { + throw new IncompleteDsnException('User and password nor role is not set.'); + } + + $credential = $this->credentialProvider->getCredential($role); + } $region = $dsn->getOption('region'); if ('api' === $scheme) { - return new SesApiTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger); + return new SesApiTransport($credential, $region, $this->client, $this->dispatcher, $this->logger); } if ('http' === $scheme) { - return new SesHttpTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger); + return new SesHttpTransport($credential, $region, $this->client, $this->dispatcher, $this->logger); } if ('smtp' === $scheme || 'smtps' === $scheme) { - return new SesSmtpTransport($user, $password, $region, $this->dispatcher, $this->logger); + return new SesSmtpTransport($credential, $region, $this->dispatcher, $this->logger); } throw new UnsupportedSchemeException($dsn, ['api', 'http', 'smtp', 'smtps']); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy