Browse Source

Add support for SASL XOAUTH2 to SmtpTransport.

Closes #16815
ADmad 3 years ago
parent
commit
b4b3ef98c1

+ 48 - 25
src/Mailer/Transport/SmtpTransport.php

@@ -30,6 +30,13 @@ class SmtpTransport extends AbstractTransport
 {
     protected const AUTH_PLAIN = 'PLAIN';
     protected const AUTH_LOGIN = 'LOGIN';
+    protected const AUTH_XOAUTH2 = 'XOAUTH2';
+
+    protected const SUPPORTED_AUTH_TYPES = [
+        self::AUTH_PLAIN,
+        self::AUTH_LOGIN,
+        self::AUTH_XOAUTH2,
+    ];
 
     /**
      * Default config for this class
@@ -240,16 +247,12 @@ class SmtpTransport extends AbstractTransport
             }
         }
 
-        if (strpos($auth, self::AUTH_PLAIN) !== false) {
-            $this->authType = self::AUTH_PLAIN;
-
-            return;
-        }
-
-        if (strpos($auth, self::AUTH_LOGIN) !== false) {
-            $this->authType = self::AUTH_LOGIN;
+        foreach (self::SUPPORTED_AUTH_TYPES as $type) {
+            if (strpos($auth, $type) !== false) {
+                $this->authType = $type;
 
-            return;
+                return;
+            }
         }
     }
 
@@ -322,27 +325,27 @@ class SmtpTransport extends AbstractTransport
 
         $username = $this->_config['username'];
         $password = $this->_config['password'];
-        if (empty($this->authType)) {
-            $replyCode = $this->_authPlain($username, $password);
-            if ($replyCode === '235') {
-                return;
-            }
-
-            $this->_authLogin($username, $password);
 
-            return;
-        }
+        switch ($this->authType) {
+            case self::AUTH_PLAIN:
+                $this->_authPlain($username, $password);
+                break;
 
-        if ($this->authType === self::AUTH_PLAIN) {
-            $this->_authPlain($username, $password);
+            case self::AUTH_LOGIN:
+                $this->_authLogin($username, $password);
+                break;
 
-            return;
-        }
+            case self::AUTH_XOAUTH2:
+                $this->_authXoauth2($username, $password);
+                break;
 
-        if ($this->authType === self::AUTH_LOGIN) {
-            $this->_authLogin($username, $password);
+            default:
+                $replyCode = $this->_authPlain($username, $password);
+                if ($replyCode === '235') {
+                    break;
+                }
 
-            return;
+                $this->_authLogin($username, $password);
         }
     }
 
@@ -395,6 +398,26 @@ class SmtpTransport extends AbstractTransport
     }
 
     /**
+     * Authenticate using AUTH XOAUTH2 mechanism.
+     *
+     * @param string $username Username.
+     * @param string $token Token.
+     * @return void
+     * @see https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#smtp-protocol-exchange
+     * @see https://developers.google.com/gmail/imap/xoauth2-protocol#smtp_protocol_exchange
+     */
+    protected function _authXoauth2(string $username, string $token): void
+    {
+        $authString = base64_encode(sprintf(
+            "user=%s\1auth=Bearer %s\1\1",
+            $username,
+            $token
+        ));
+
+        $this->_smtpSend('AUTH XOAUTH2 ' . $authString, '235');
+    }
+
+    /**
      * Prepares the `MAIL FROM` SMTP command.
      *
      * @param string $message The email address to send with the command.

+ 27 - 0
tests/TestCase/Mailer/Transport/SmtpTransportTest.php

@@ -315,6 +315,33 @@ class SmtpTransportTest extends TestCase
     }
 
     /**
+     * testAuth method
+     */
+    public function testAuthXoauth2(): void
+    {
+        $authString = base64_encode(sprintf(
+            "user=%s\1auth=Bearer %s\1\1",
+            $this->credentials['username'],
+            $this->credentials['password']
+        ));
+
+        $this->socket->expects($this->exactly(1))
+            ->method('read')
+            ->will($this->onConsecutiveCalls(
+                "235 OK\r\n"
+            ));
+        $this->socket->expects($this->exactly(1))
+            ->method('write')
+            ->withConsecutive(
+                ["AUTH XOAUTH2 {$authString}\r\n"],
+            );
+
+        $this->SmtpTransport->setConfig($this->credentials);
+        $this->SmtpTransport->setAuthType('XOAUTH2');
+        $this->SmtpTransport->auth();
+    }
+
+    /**
      * testAuthNotRecognized method
      */
     public function testAuthNotRecognized(): void

+ 5 - 0
tests/test_app/TestApp/Mailer/Transport/SmtpTestTransport.php

@@ -50,4 +50,9 @@ class SmtpTestTransport extends SmtpTransport
     {
         return $this->authType;
     }
+
+    public function setAuthType(string $type): void
+    {
+        $this->authType = $type;
+    }
 }