MDL-81924 factor_sms: Add support for SMS gateway selection

Includes migrating AWS settings from the SMS MFA factor to a new gateway
in the SMS subsystem and notifying admins of the update.

Originally implemented as MDL-81732 and MDL-82660.

Co-authored by: Michael Hawkins <michaelh@moodle.com>
This commit is contained in:
Safat 2024-06-29 16:30:26 +10:00 committed by Huong Nguyen
parent cf75a3aad1
commit a1209ab882
No known key found for this signature in database
GPG key ID: 40D88AB693A3E72A
8 changed files with 260 additions and 21 deletions

View file

@ -409,10 +409,11 @@ class factor extends object_factor_base {
recipientnumber: $phonenumber, recipientnumber: $phonenumber,
content: $message, content: $message,
component: 'factor_sms', component: 'factor_sms',
messagetype: 'factor', messagetype: 'mfa',
recipientuserid: null, recipientuserid: null,
sensitive: true, issensitive: true,
async: false, async: false,
gatewayid: get_config('factor_sms', 'smsgateway'),
); );
} }

View file

@ -0,0 +1,50 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace factor_sms;
use core_sms\hook\before_gateway_deleted;
use core_sms\hook\before_gateway_disabled;
/**
* Hook listener for SMS factor.
*
* @package factor_sms
* @copyright 2024 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class hook_listener {
/**
* Hook listener before a gateway is deleted or disabled.
*
* This listener will check if the SMS gateway is currently in use before disabling or deleting the gateway.
*
* @param before_gateway_deleted|before_gateway_disabled $hook Hook instance before the gateway is deleted
*/
public static function check_gateway_usage_in_mfa(
before_gateway_deleted|before_gateway_disabled $hook,
): void {
try {
$smsgatewayid = (int)get_config('factor_sms', 'smsgateway');
if ($smsgatewayid && $smsgatewayid === (int)$hook->gateway->id) {
$hook->stop_propagation();
}
} catch (\dml_exception $exception) {
$hook->stop_propagation();
}
}
}

View file

@ -0,0 +1,57 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace factor_sms\task;
use core\task\adhoc_task;
use moodle_url;
/**
* Notification for admins to notify about the migration of SMS setup from MFA to SMS gateway plugins.
*
* @package factor_sms
* @copyright 2024 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sms_gateway_migration_notification extends adhoc_task {
public function execute(): void {
$siteadmins = get_admins();
foreach ($siteadmins as $siteadmin) {
$smsconfigureurl = new moodle_url('/sms/sms_gateways.php');
$messagebody = get_string('notification:smsgatewaymigrationinfo', 'factor_sms', $smsconfigureurl);
$messagesubject = get_string('notification:smsgatewaymigration', 'factor_sms');
$message = new \core\message\message();
$message->component = 'moodle';
$message->name = 'notices';
$message->userfrom = \core_user::get_noreply_user();
$message->subject = $messagesubject;
$message->fullmessageformat = FORMAT_HTML;
$message->notification = 1;
$message->userto = $siteadmin;
$message->smallmessage = $messagesubject;
$message->fullmessagehtml = $messagebody;
$message->fullmessage = html_to_text($messagebody);
$message->contexturl = $smsconfigureurl;
$message->contexturlname = $messagesubject;
// Send message.
message_send($message);
}
}
}

View file

@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Hooks register for SMS factor.
*
* @package factor_sms
* @copyright 2024 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$callbacks = [
[
'hook' => \core_sms\hook\before_gateway_deleted::class,
'callback' => \factor_sms\hook_listener::class . '::check_gateway_usage_in_mfa',
],
[
'hook' => \core_sms\hook\before_gateway_disabled::class,
'callback' => \factor_sms\hook_listener::class . '::check_gateway_usage_in_mfa',
],
];

View file

@ -29,7 +29,7 @@
* @return bool * @return bool
*/ */
function xmldb_factor_sms_upgrade(int $oldversion): bool { function xmldb_factor_sms_upgrade(int $oldversion): bool {
if ($oldversion < 2024050300) { if ($oldversion < 2024082200) {
$config = get_config('factor_sms'); $config = get_config('factor_sms');
// If the sms factor is enabled, then do the migration to the sms subsystem. // If the sms factor is enabled, then do the migration to the sms subsystem.
if ((int)$config->enabled === 1) { if ((int)$config->enabled === 1) {
@ -43,14 +43,21 @@ function xmldb_factor_sms_upgrade(int $oldversion): bool {
$smsconfig->api_region = $config->api_region; $smsconfig->api_region = $config->api_region;
// Now insert the record. // Now insert the record.
$manager = \core\di::get(\core_sms\manager::class); $manager = \core\di::get(\core_sms\manager::class);
$manager->create_gateway_instance( $gateway = $manager->create_gateway_instance(
classname: \smsgateway_aws\gateway::class, classname: \smsgateway_aws\gateway::class,
name: 'MFA AWS',
enabled: $config->enabled, enabled: $config->enabled,
config: $smsconfig, config: $smsconfig,
); );
// Set the mfa config for the sms gateway.
set_config('smsgateway', $gateway->id, 'factor_sms');
// Now add the task to send notification to admins about this migration.
$task = new \factor_sms\task\sms_gateway_migration_notification();
\core\task\manager::queue_adhoc_task($task, true);
} }
// MFA savepoint reached. // MFA savepoint reached.
upgrade_plugin_savepoint(true, 2024050300, 'factor', 'sms'); upgrade_plugin_savepoint(true, 2024082200, 'factor', 'sms');
} }
return true; return true;

View file

@ -44,6 +44,8 @@ $string['loginsubmit'] = 'Continue';
$string['managefactor'] = 'Manage SMS'; $string['managefactor'] = 'Manage SMS';
$string['managefactorbutton'] = 'Manage'; $string['managefactorbutton'] = 'Manage';
$string['manageinfo'] = 'You are using \'{$a}\' to authenticate.'; $string['manageinfo'] = 'You are using \'{$a}\' to authenticate.';
$string['notification:smsgatewaymigration'] = 'SMS settings have moved';
$string['notification:smsgatewaymigrationinfo'] = 'A new SMS subsystem is now available for managing and configuring all SMS-related functions. Your existing SMS configurations have been seamlessly migrated. You can find and manage them on the new <a href="{$a}">SMS Gateways page</a>.';
$string['logintitle'] = 'Enter the verification code sent to your mobile'; $string['logintitle'] = 'Enter the verification code sent to your mobile';
$string['phonehelp'] = 'Enter your mobile number (including country code) to receive a verification code.'; $string['phonehelp'] = 'Enter your mobile number (including country code) to receive a verification code.';
$string['pluginname'] = 'SMS mobile phone'; $string['pluginname'] = 'SMS mobile phone';
@ -65,6 +67,12 @@ $string['settings:duration'] = 'Validity duration';
$string['settings:duration_help'] = 'The period of time that the code is valid.'; $string['settings:duration_help'] = 'The period of time that the code is valid.';
$string['settings:gateway'] = 'SMS gateway'; $string['settings:gateway'] = 'SMS gateway';
$string['settings:gateway_help'] = 'The SMS provider you wish to send messages via'; $string['settings:gateway_help'] = 'The SMS provider you wish to send messages via';
$string['settings:heading'] = 'Users will receive an SMS with 6-digit code during login, which they must enter to complete the login process.
Users will need to register their mobile phone number first.';
$string['settings:setupdesc'] = '<br><br>To use SMS as an authentication factor, you first need to <a href="{$a}">set up an SMS gateway</a>.';
$string['settings:smsgateway'] = 'SMS gateway';
$string['settings:smsgateway_help'] = 'Select a gateway from the list, or <a href="{$a}">create a new gateway</a>.';
$string['setupfactor'] = 'Set up SMS'; $string['setupfactor'] = 'Set up SMS';
$string['setupfactorbutton'] = 'Set up'; $string['setupfactorbutton'] = 'Set up';
$string['setupsubmitcode'] = 'Save'; $string['setupsubmitcode'] = 'Save';

View file

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/** /**
* Settings * Settings for SMS MFA factor.
* *
* @package factor_sms * @package factor_sms
* @author Peter Burnett <peterburnett@catalyst-au.net> * @author Peter Burnett <peterburnett@catalyst-au.net>
@ -24,24 +24,103 @@
*/ */
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
global $CFG, $OUTPUT;
$enabled = new admin_setting_configcheckbox('factor_sms/enabled', global $CFG;
// Get the gateway records.
$manager = \core\di::get(\core_sms\manager::class);
$gatewayrecords = $manager->get_gateway_records(['enabled' => 1]);
$smsconfigureurl = new moodle_url(
'/sms/configure.php',
[
'returnurl' => new moodle_url(
'/admin/settings.php',
['section' => 'factor_sms'],
),
],
);
$smsconfigureurl = $smsconfigureurl->out();
$settings->add(
new admin_setting_heading(
'factor_sms/heading',
'',
new lang_string(
'settings:heading',
'factor_sms',
),
),
);
if (count($gatewayrecords) > 0) {
$gateways = [0 => new lang_string('none')];
foreach ($gatewayrecords as $record) {
$values = explode('\\', $record->gateway);
$gatewayname = new lang_string('pluginname', $values[0]);
$gateways[$record->id] = $record->name . ' (' . $gatewayname . ')';
}
$settings->add(
new admin_setting_configselect(
'factor_sms/smsgateway',
new lang_string('settings:smsgateway', 'factor_sms'),
new lang_string('settings:smsgateway_help', 'factor_sms', $smsconfigureurl),
0,
$gateways,
),
);
$enabled = new admin_setting_configcheckbox(
'factor_sms/enabled',
new lang_string('settings:enablefactor', 'tool_mfa'), new lang_string('settings:enablefactor', 'tool_mfa'),
new lang_string('settings:enablefactor_help', 'tool_mfa'), 0); new lang_string('settings:enablefactor_help', 'tool_mfa'),
0,
);
$enabled->set_updatedcallback(function () { $enabled->set_updatedcallback(function () {
\tool_mfa\manager::do_factor_action('sms', get_config('factor_sms', 'enabled') ? 'enable' : 'disable'); \tool_mfa\manager::do_factor_action(
'sms',
get_config('factor_sms', 'enabled') ? 'enable' : 'disable',
);
}); });
$settings->add($enabled); $settings->add($enabled);
$settings->add(new admin_setting_configtext('factor_sms/weight', $settings->add(
new admin_setting_configtext(
'factor_sms/weight',
new lang_string('settings:weight', 'tool_mfa'), new lang_string('settings:weight', 'tool_mfa'),
new lang_string('settings:weight_help', 'tool_mfa'), 100, PARAM_INT)); new lang_string('settings:weight_help', 'tool_mfa'),
100,
PARAM_INT,
),
);
$settings->hide_if('factor_sms/weight', 'factor_sms/enabled');
$settings->add(new admin_setting_configduration('factor_sms/duration', $settings->add(
get_string('settings:duration', 'tool_mfa'), new admin_setting_configduration(
get_string('settings:duration_help', 'tool_mfa'), 30 * MINSECS, MINSECS)); 'factor_sms/duration',
new lang_string('settings:duration', 'tool_mfa'),
new lang_string('settings:duration_help', 'tool_mfa'),
30 * MINSECS,
MINSECS,
),
);
$settings->hide_if('factor_sms/duration', 'factor_sms/enabled');
} else {
$settings->add(
new admin_setting_description(
'factor_sms/setupdesc',
'',
new lang_string(
'settings:setupdesc',
'factor_sms',
$smsconfigureurl,
),
),
);
}
// TODO MDL-80962 Remove these settings, strings and associated codes (if any).
/*
$codeslink = 'https://en.wikipedia.org/wiki/List_of_country_calling_codes'; $codeslink = 'https://en.wikipedia.org/wiki/List_of_country_calling_codes';
$link = \html_writer::link($codeslink, $codeslink); $link = \html_writer::link($codeslink, $codeslink);
@ -64,3 +143,4 @@ if (empty(get_config('factor_sms', 'gateway'))) {
$class = '\factor_sms\local\smsgateway\\' . get_config('factor_sms', 'gateway'); $class = '\factor_sms\local\smsgateway\\' . get_config('factor_sms', 'gateway');
call_user_func($class . '::add_settings', $settings); call_user_func($class . '::add_settings', $settings);
*/

View file

@ -26,7 +26,7 @@
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024050300; // The current plugin version (Date: YYYYMMDDXX). $plugin->version = 2024082200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version. $plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'factor_sms'; // Full name of the plugin (used for diagnostics). $plugin->component = 'factor_sms'; // Full name of the plugin (used for diagnostics).
$plugin->maturity = MATURITY_STABLE; $plugin->maturity = MATURITY_STABLE;