diff --git a/admin/settings/server.php b/admin/settings/server.php
index d4f25635ee5..4b1672ae765 100644
--- a/admin/settings/server.php
+++ b/admin/settings/server.php
@@ -442,9 +442,35 @@ if ($hassiteconfig) {
'CRAM-MD5' => 'CRAM-MD5',
];
+ // Get all the issuers.
+ $issuers = \core\oauth2\api::get_all_issuers();
+ $enabledissuers = [];
+ foreach ($issuers as $issuer) {
+ // Get the enabled issuer only.
+ if ($issuer->get('enabled')) {
+ $enabledissuers[] = $issuer;
+ }
+ }
+
+ if (count($enabledissuers) > 0) {
+ $authtypeoptions['XOAUTH2'] = 'XOAUTH2';
+ }
+
$temp->add(new admin_setting_configselect('smtpauthtype', new lang_string('smtpauthtype', 'admin'),
new lang_string('configsmtpauthtype', 'admin'), 'LOGIN', $authtypeoptions));
+ if (count($enabledissuers) > 0) {
+ $oauth2services = [
+ '' => new lang_string('none', 'admin'),
+ ];
+ foreach ($enabledissuers as $issuer) {
+ $oauth2services[$issuer->get('id')] = s($issuer->get('name'));
+ }
+
+ $temp->add(new admin_setting_configselect('smtpoauthservice', new lang_string('issuer', 'auth_oauth2'),
+ new lang_string('configsmtpoauthservice', 'admin'), '', $oauth2services));
+ }
+
$temp->add(new admin_setting_configtext('smtpuser', new lang_string('smtpuser', 'admin'),
new lang_string('configsmtpuser', 'admin'), '', PARAM_NOTAGS));
diff --git a/admin/tests/behat/outgoing_mail.feature b/admin/tests/behat/outgoing_mail.feature
new file mode 100644
index 00000000000..960d18329c5
--- /dev/null
+++ b/admin/tests/behat/outgoing_mail.feature
@@ -0,0 +1,29 @@
+@core @core_admin
+Feature: Outgoing mail configuration
+ In order to send email from Moodle
+ As a Moodle administrator
+ I need to set mail configuration
+
+ Background:
+ Given I log in as "admin"
+
+ Scenario: SMTP Auth Type without OAuth 2 Service setup yet
+ Given I navigate to "Server > Email > Outgoing mail configuration" in site administration
+ And I should not see "XOAUTH2" in the "SMTP Auth Type" "select"
+ And I should see "LOGIN" in the "SMTP Auth Type" "select"
+ And I should see "PLAIN" in the "SMTP Auth Type" "select"
+
+ Scenario: SMTP Auth Type with OAuth 2 Service setup
+ Given I navigate to "Server > OAuth 2 services" in site administration
+ And I press "Google"
+ And I should see "Create new service: Google"
+ And I set the following fields to these values:
+ | Name | Testing service |
+ | Client ID | thisistheclientid |
+ | Client secret | supersecret |
+ And I press "Save changes"
+ When I navigate to "Server > Email > Outgoing mail configuration" in site administration
+ Then I should see "XOAUTH2" in the "SMTP Auth Type" "select"
+ And I should see "LOGIN" in the "SMTP Auth Type" "select"
+ And I should see "PLAIN" in the "SMTP Auth Type" "select"
+ And I should see "Testing service" in the "OAuth 2 Service" "select"
diff --git a/admin/tool/messageinbound/classes/manager.php b/admin/tool/messageinbound/classes/manager.php
index e440f870e7d..4864bef8029 100644
--- a/admin/tool/messageinbound/classes/manager.php
+++ b/admin/tool/messageinbound/classes/manager.php
@@ -112,6 +112,25 @@ class manager {
}
}
+ // XOAUTH2.
+ if ($CFG->messageinbound_hostoauth != '') {
+ // Get the issuer.
+ $issuer = \core\oauth2\api::get_issuer($CFG->messageinbound_hostoauth);
+ // Validate the issuer and check if it is enabled or not.
+ if ($issuer && $issuer->get('enabled')) {
+ // Get the OAuth Client.
+ if ($oauthclient = \core\oauth2\api::get_system_oauth_client($issuer)) {
+ $xoauth2token = new \Horde_Imap_Client_Password_Xoauth2(
+ $configuration['username'],
+ $oauthclient->get_accesstoken()->token
+ );
+ $configuration['xoauth2_token'] = $xoauth2token;
+ // Password is not necessary when using OAuth2 but Horde still needs it. We just set a random string here.
+ $configuration['password'] = random_string(64);
+ }
+ }
+ }
+
$this->client = new \Horde_Imap_Client_Socket($configuration);
try {
diff --git a/admin/tool/messageinbound/lang/en/tool_messageinbound.php b/admin/tool/messageinbound/lang/en/tool_messageinbound.php
index fd0b4657961..b4c63fb656f 100644
--- a/admin/tool/messageinbound/lang/en/tool_messageinbound.php
+++ b/admin/tool/messageinbound/lang/en/tool_messageinbound.php
@@ -75,6 +75,7 @@ $string['messageinboundenabled_desc'] = 'Incoming mail processing must be enable
$string['messageinboundgeneralconfiguration'] = 'General configuration';
$string['messageinboundgeneralconfiguration_desc'] = 'Inbound message processing allows you to receive and process email within Moodle. This has applications such as sending email replies to forum posts or adding files to a user\'s private files.';
$string['messageinboundhost'] = 'Incoming Mail Server';
+$string['messageinboundhostoauth_help'] = 'OAuth2 Service to use to access the IMAP server, using XOAUTH2 authentication. If the service does not exist yet, you will need to create it.';
$string['messageinboundhostpass'] = 'Password';
$string['messageinboundhostpass_desc'] = 'This is the password your service provider will have provided to log in to your email account with.';
$string['messageinboundhostssl'] = 'Use SSL';
diff --git a/admin/tool/messageinbound/settings.php b/admin/tool/messageinbound/settings.php
index 7fe0f1614e8..9b6edb5fdef 100644
--- a/admin/tool/messageinbound/settings.php
+++ b/admin/tool/messageinbound/settings.php
@@ -67,6 +67,27 @@ if ($hassiteconfig) {
new lang_string('messageinboundhostssl', 'tool_messageinbound'),
new lang_string('messageinboundhostssl_desc', 'tool_messageinbound'), 'ssl', $options));
+ // Get all the issuers.
+ $issuers = \core\oauth2\api::get_all_issuers();
+ $oauth2services = [
+ '' => new lang_string('none', 'admin'),
+ ];
+ foreach ($issuers as $issuer) {
+ // Get the enabled issuer only.
+ if ($issuer->get('enabled')) {
+ $oauth2services[$issuer->get('id')] = s($issuer->get('name'));
+ }
+ }
+
+ if (count($oauth2services) > 1) {
+ $settings->add(new admin_setting_configselect('messageinbound_hostoauth',
+ new lang_string('issuer', 'auth_oauth2'),
+ get_string('messageinboundhostoauth_help', 'tool_messageinbound'),
+ '',
+ $oauth2services
+ ));
+ }
+
$settings->add(new admin_setting_configtext('messageinbound_hostuser',
new lang_string('messageinboundhostuser', 'tool_messageinbound'),
new lang_string('messageinboundhostuser_desc', 'tool_messageinbound'), '', PARAM_NOTAGS));
diff --git a/admin/tool/messageinbound/tests/behat/incoming_mail.feature b/admin/tool/messageinbound/tests/behat/incoming_mail.feature
new file mode 100644
index 00000000000..664e6cba243
--- /dev/null
+++ b/admin/tool/messageinbound/tests/behat/incoming_mail.feature
@@ -0,0 +1,25 @@
+@tool @tool_messageinbound
+Feature: Incoming mail configuration
+ In order to receive email from external
+ As a Moodle administrator
+ I need to set mail configuration
+
+ Background:
+ Given I log in as "admin"
+
+ Scenario: Incoming mail server settings without OAuth 2 Service setup yet
+ Given I navigate to "Server > Email > Incoming mail configuration" in site administration
+ And "OAuth 2 Service" "select" should not exist
+
+ Scenario: Incoming mail server settings with OAuth 2 Service setup
+ Given I navigate to "Server > OAuth 2 services" in site administration
+ And I press "Google"
+ And I should see "Create new service: Google"
+ And I set the following fields to these values:
+ | Name | Testing service |
+ | Client ID | thisistheclientid |
+ | Client secret | supersecret |
+ And I press "Save changes"
+ When I navigate to "Server > Email > Incoming mail configuration" in site administration
+ Then "OAuth 2 Service" "select" should exist
+ And I should see "Testing service" in the "OAuth 2 Service" "select"
diff --git a/lang/en/admin.php b/lang/en/admin.php
index 3e7fc4ef44d..70d3bed7bcb 100644
--- a/lang/en/admin.php
+++ b/lang/en/admin.php
@@ -363,6 +363,7 @@ $string['configsitemaxcategorydepthhelp'] = 'This specifies the maximum depth of
$string['configslasharguments'] = '\'Slash arguments\' (using PATH_INFO) is required for SCORM packages and multiple-file resources to display correctly. If your web server doesn\'t support \'slash arguments\' and you are unable to configure it, this setting can be disabled, though it will result in things not working.
Note: The use of \'slash arguments\' will be required in future versions of Moodle.';
$string['configsmartpix'] = 'With this on, icons are served through a PHP script that searches the current theme, then all parent themes, then the Moodle /pix folder. This reduces the need to duplicate image files within themes, but has a slight performance cost.';
$string['configsmtpauthtype'] = 'This sets the authentication type to use on smtp server.';
+$string['configsmtpoauthservice'] = 'Select the OAuth 2 service that is configured to talk to the smtp server. If the service does not exist yet, you will need to create it. Please note: This setting only runs if the SMTP Auth Type is set to XOAUTH2';
$string['configsmtphosts'] = 'Give the full name of one or more local SMTP servers that Moodle should use to send mail (eg \'mail.a.com\' or \'mail.a.com;mail.b.com\'). To specify a non-default port (i.e other than port 25), you can use the [server]:[port] syntax (eg \'mail.a.com:587\'). For secure connections, port 465 is usually used with SSL, port 587 is usually used with TLS, specify security protocol below if required. If you leave this field blank, Moodle will use the PHP default method of sending mail.';
$string['configsmtpmaxbulk'] = 'Maximum number of messages sent per SMTP session. Grouping messages may speed up the sending of emails. Values lower than 2 force creation of new SMTP session for each email.';
$string['configsmtpsecure'] = 'If SMTP server requires secure connection, specify the correct protocol type.';
diff --git a/lib/phpmailer/moodle_phpmailer.php b/lib/phpmailer/moodle_phpmailer.php
index d1b58c66208..1d43fa090d3 100644
--- a/lib/phpmailer/moodle_phpmailer.php
+++ b/lib/phpmailer/moodle_phpmailer.php
@@ -54,6 +54,10 @@ class moodle_phpmailer extends \PHPMailer\PHPMailer\PHPMailer {
if (!empty($CFG->smtpauthtype)) {
$this->AuthType = $CFG->smtpauthtype;
+
+ if ($this->AuthType == 'XOAUTH2') {
+ $this->process_oauth();
+ }
}
// Some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis.
@@ -65,7 +69,7 @@ class moodle_phpmailer extends \PHPMailer\PHPMailer\PHPMailer {
}
/**
- * Extended AddCustomHeader function in order to stop duplicate
+ * Extended AddCustomHeader function in order to stop duplicate
* message-ids
* http://tracker.moodle.org/browse/MDL-3681
*/
@@ -83,7 +87,7 @@ class moodle_phpmailer extends \PHPMailer\PHPMailer\PHPMailer {
/**
* Use internal moodles own core_text to encode mimeheaders.
- * Fall back to phpmailers inbuilt functions if not
+ * Fall back to phpmailers inbuilt functions if not
*/
public function encodeHeader($str, $position = 'text') {
$encoded = core_text::encode_mimeheader($str, $this->CharSet);
@@ -142,4 +146,32 @@ class moodle_phpmailer extends \PHPMailer\PHPMailer\PHPMailer {
return parent::postSend();
}
}
+
+ /**
+ * Config the PHPMailer to use OAUTH if necessary.
+ */
+ private function process_oauth(): void {
+ global $CFG;
+
+ require_once($CFG->libdir . '/phpmailer/moodle_phpmailer_oauth.php');
+ if (!empty($CFG->smtpoauthservice)) {
+ // Get the issuer.
+ $issuer = \core\oauth2\api::get_issuer($CFG->smtpoauthservice);
+ // Validate the issuer and check if it is enabled or not.
+ if ($issuer && $issuer->get('enabled')) {
+ // Get the OAuth Client.
+ if ($oauthclient = \core\oauth2\api::get_system_oauth_client($issuer)) {
+ $oauth = new moodle_phpmailer_oauth([
+ 'provider' => $oauthclient,
+ 'clientId' => $oauthclient->get_clientid(),
+ 'clientSecret' => $oauthclient->get_clientsecret(),
+ 'refreshToken' => $oauthclient->get_refresh_token(),
+ 'userName' => $CFG->smtpuser,
+ ]);
+ // Set the OAuth.
+ $this->setOAuth($oauth);
+ }
+ }
+ }
+ }
}
diff --git a/lib/phpmailer/moodle_phpmailer_oauth.php b/lib/phpmailer/moodle_phpmailer_oauth.php
new file mode 100644
index 00000000000..9183b3882c6
--- /dev/null
+++ b/lib/phpmailer/moodle_phpmailer_oauth.php
@@ -0,0 +1,30 @@
+.
+
+/**
+ * Moodle Customised version of the PHPMailer OAuth class
+ *
+ * @package core
+ * @copyright 2022 Huong Nguyen
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class moodle_phpmailer_oauth extends \PHPMailer\PHPMailer\OAuth {
+
+ protected function getToken() {
+ return $this->provider->get_accesstoken()->token;
+ }
+
+}
diff --git a/lib/phpmailer/readme_moodle.txt b/lib/phpmailer/readme_moodle.txt
index f57db50c66d..175b88d9028 100644
--- a/lib/phpmailer/readme_moodle.txt
+++ b/lib/phpmailer/readme_moodle.txt
@@ -7,12 +7,11 @@ For more information on this version of PHPMailer, check out https://github.com/
To upgrade this library:
1. Download the latest release of PHPMailer in https://github.com/PHPMailer/PHPMailer/releases.
-2. Remove everything inside lib/phpmailer/ folder except README_MOODLE.txt and moodle_phpmailer.php.
+2. Remove everything inside lib/phpmailer/ folder except README_MOODLE.txt, moodle_phpmailer.php and moodle_phpmailer_oauth.php.
3. Extract the contents of the release archive to lib/phpmailer.
4. Remove the following files that were extracted:
- composer.json
- get_oauth_token.php
- SECURITY.md
- - src/OAuth.php
- src/POP3.php
5. Update lib/thirdpartylibs.xml.
diff --git a/lib/phpmailer/src/OAuth.php b/lib/phpmailer/src/OAuth.php
new file mode 100644
index 00000000000..c93d0be1b55
--- /dev/null
+++ b/lib/phpmailer/src/OAuth.php
@@ -0,0 +1,139 @@
+
+ * @author Jim Jagielski (jimjag)
+ * @author Andy Prevost (codeworxtech)
+ * @author Brent R. Matzelle (original founder)
+ * @copyright 2012 - 2020 Marcus Bointon
+ * @copyright 2010 - 2012 Jim Jagielski
+ * @copyright 2004 - 2009 Andy Prevost
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @note This program is distributed in the hope that it will be useful - WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+namespace PHPMailer\PHPMailer;
+
+use League\OAuth2\Client\Grant\RefreshToken;
+use League\OAuth2\Client\Provider\AbstractProvider;
+use League\OAuth2\Client\Token\AccessToken;
+
+/**
+ * OAuth - OAuth2 authentication wrapper class.
+ * Uses the oauth2-client package from the League of Extraordinary Packages.
+ *
+ * @see http://oauth2-client.thephpleague.com
+ *
+ * @author Marcus Bointon (Synchro/coolbru)
+ */
+class OAuth
+{
+ /**
+ * An instance of the League OAuth Client Provider.
+ *
+ * @var AbstractProvider
+ */
+ protected $provider;
+
+ /**
+ * The current OAuth access token.
+ *
+ * @var AccessToken
+ */
+ protected $oauthToken;
+
+ /**
+ * The user's email address, usually used as the login ID
+ * and also the from address when sending email.
+ *
+ * @var string
+ */
+ protected $oauthUserEmail = '';
+
+ /**
+ * The client secret, generated in the app definition of the service you're connecting to.
+ *
+ * @var string
+ */
+ protected $oauthClientSecret = '';
+
+ /**
+ * The client ID, generated in the app definition of the service you're connecting to.
+ *
+ * @var string
+ */
+ protected $oauthClientId = '';
+
+ /**
+ * The refresh token, used to obtain new AccessTokens.
+ *
+ * @var string
+ */
+ protected $oauthRefreshToken = '';
+
+ /**
+ * OAuth constructor.
+ *
+ * @param array $options Associative array containing
+ * `provider`, `userName`, `clientSecret`, `clientId` and `refreshToken` elements
+ */
+ public function __construct($options)
+ {
+ $this->provider = $options['provider'];
+ $this->oauthUserEmail = $options['userName'];
+ $this->oauthClientSecret = $options['clientSecret'];
+ $this->oauthClientId = $options['clientId'];
+ $this->oauthRefreshToken = $options['refreshToken'];
+ }
+
+ /**
+ * Get a new RefreshToken.
+ *
+ * @return RefreshToken
+ */
+ protected function getGrant()
+ {
+ return new RefreshToken();
+ }
+
+ /**
+ * Get a new AccessToken.
+ *
+ * @return AccessToken
+ */
+ protected function getToken()
+ {
+ return $this->provider->getAccessToken(
+ $this->getGrant(),
+ ['refresh_token' => $this->oauthRefreshToken]
+ );
+ }
+
+ /**
+ * Generate a base64-encoded OAuth token.
+ *
+ * @return string
+ */
+ public function getOauth64()
+ {
+ //Get a new token if it's not available or has expired
+ if (null === $this->oauthToken || $this->oauthToken->hasExpired()) {
+ $this->oauthToken = $this->getToken();
+ }
+
+ return base64_encode(
+ 'user=' .
+ $this->oauthUserEmail .
+ "\001auth=Bearer " .
+ $this->oauthToken .
+ "\001\001"
+ );
+ }
+}