mirror of
https://github.com/moodle/moodle.git
synced 2025-08-10 19:36:41 +02:00
Merge branch 'MDL-78511-master2' of https://github.com/raortegar/moodle
This commit is contained in:
commit
d1024fae70
2393 changed files with 85375 additions and 15 deletions
|
@ -29,7 +29,7 @@ use tool_mfa\local\form\revoke_factor_form;
|
|||
|
||||
require_login(null, false);
|
||||
if (isguestuser()) {
|
||||
throw new require_login_exception('Guests are not allowed here.');
|
||||
throw new require_login_exception('error:isguestuser', 'tool_mfa');
|
||||
}
|
||||
|
||||
$action = optional_param('action', '', PARAM_ALPHANUMEXT);
|
||||
|
@ -85,6 +85,7 @@ switch ($action) {
|
|||
$form->is_validated();
|
||||
|
||||
if ($form->is_cancelled()) {
|
||||
$factorobject->setup_factor_form_is_cancelled($factorid);
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ interface object_factor {
|
|||
* Defines setup_factor form definition page for particular factor.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function setup_factor_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm;
|
||||
|
@ -74,7 +74,7 @@ interface object_factor {
|
|||
* Defines setup_factor form definition page after form data has been set.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function setup_factor_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm;
|
||||
|
@ -92,7 +92,7 @@ interface object_factor {
|
|||
* Defines login form definition page for particular factor.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm;
|
||||
|
@ -101,7 +101,7 @@ interface object_factor {
|
|||
* Defines login form definition page after form data has been set.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function login_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm;
|
||||
|
@ -115,6 +115,21 @@ interface object_factor {
|
|||
*/
|
||||
public function login_form_validation(array $data): array;
|
||||
|
||||
/**
|
||||
* Setups in given factor when the form is cancelled
|
||||
*
|
||||
* @param int $factorid
|
||||
* @return void
|
||||
*/
|
||||
public function setup_factor_form_is_cancelled(int $factorid): void;
|
||||
|
||||
/**
|
||||
* Setup submit button string in given factor
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function setup_factor_form_submit_button_string(): ?string;
|
||||
|
||||
/**
|
||||
* Setups given factor and adds it to user's active factors list.
|
||||
* Returns true if factor has been successfully added, otherwise false.
|
||||
|
|
|
@ -152,7 +152,7 @@ abstract class object_factor_base implements object_factor {
|
|||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_factor_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
return $mform;
|
||||
|
@ -164,7 +164,7 @@ abstract class object_factor_base implements object_factor {
|
|||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_factor_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
return $mform;
|
||||
|
@ -183,6 +183,28 @@ abstract class object_factor_base implements object_factor {
|
|||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setups in given factor when the form is cancelled
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @return void
|
||||
*/
|
||||
public function setup_factor_form_is_cancelled(int $factorid): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup submit button string in given factor
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function setup_factor_form_submit_button_string(): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setups given factor and adds it to user's active factors list.
|
||||
* Returns true if factor has been successfully added, otherwise false.
|
||||
|
@ -232,7 +254,7 @@ abstract class object_factor_base implements object_factor {
|
|||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
return $mform;
|
||||
|
@ -244,7 +266,7 @@ abstract class object_factor_base implements object_factor {
|
|||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function login_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
return $mform;
|
||||
|
|
|
@ -71,7 +71,7 @@ class setup_factor_form extends \moodleform {
|
|||
$factor = \tool_mfa\plugininfo\factor::get_factor($factorname);
|
||||
$mform = $factor->setup_factor_form_definition_after_data($mform);
|
||||
$this->xss_whitelist_static_form_elements($mform);
|
||||
$this->add_action_buttons();
|
||||
$this->add_action_buttons(true, $factor->setup_factor_form_submit_button_string());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,7 +49,7 @@ class factor extends object_factor_base {
|
|||
* E-Mail Factor implementation.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Form to inject global elements into.
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function login_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
$this->generate_and_email_code();
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Admin setting for AWS regions.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Dmitrii Metelkin <dmitriim@catalyst-au.net>
|
||||
* @copyright 2020 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace factor_sms;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/lib/adminlib.php');
|
||||
|
||||
/**
|
||||
* Admin setting for a list of AWS regions.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @copyright 2020 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class admin_settings_aws_region extends \admin_setting_configtext {
|
||||
|
||||
/**
|
||||
* Return part of form with setting.
|
||||
*
|
||||
* @param mixed $data array or string depending on setting
|
||||
* @param string $query
|
||||
* @return string
|
||||
*/
|
||||
public function output_html($data, $query='') {
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
$default = $this->get_defaultsetting();
|
||||
$options = [];
|
||||
$all = require_once($CFG->dirroot . '/lib/aws-sdk/src/data/endpoints.json.php');
|
||||
$ends = $all['partitions'][0]['regions'];
|
||||
if ($ends) {
|
||||
foreach ($ends as $key => $value) {
|
||||
$options[] = [
|
||||
'value' => $key,
|
||||
'label' => $key . ' - ' . $value['description'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$context = [
|
||||
'list' => $this->get_full_name(),
|
||||
'name' => $this->get_full_name(),
|
||||
'id' => $this->get_id(),
|
||||
'value' => $data,
|
||||
'size' => $this->size,
|
||||
'options' => $options,
|
||||
];
|
||||
$element = $OUTPUT->render_from_template('factor_sms/setting_aws_region', $context);
|
||||
return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
|
||||
}
|
||||
}
|
62
admin/tool/mfa/factor/sms/classes/event/sms_sent.php
Normal file
62
admin/tool/mfa/factor/sms/classes/event/sms_sent.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?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\event;
|
||||
|
||||
/**
|
||||
* Event for a sent SMS
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Alex Morris <alex.morris@catalyst.net.nz>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class sms_sent extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Init sms sent event
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'r';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns non-localised event description with id's for admin use only.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description(): string {
|
||||
|
||||
$content = [
|
||||
'userid' => $this->other['userid'],
|
||||
'debuginfo' => is_array($this->other['debug']) ? json_encode($this->other['debug']) : $this->other['debug'],
|
||||
];
|
||||
|
||||
return get_string('event:smssentdescription', 'factor_sms', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns localised general event name.
|
||||
*
|
||||
* Override in subclass, we can not make it static and abstract at the same time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name(): string {
|
||||
return get_string('event:smssent', 'factor_sms');
|
||||
}
|
||||
}
|
457
admin/tool/mfa/factor/sms/classes/factor.php
Normal file
457
admin/tool/mfa/factor/sms/classes/factor.php
Normal file
|
@ -0,0 +1,457 @@
|
|||
<?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 moodle_url;
|
||||
use stdClass;
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* SMS Factor implementation.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @subpackage tool_mfa
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class factor extends object_factor_base {
|
||||
|
||||
/** @var string Factor icon */
|
||||
protected $icon = 'fa-commenting-o';
|
||||
|
||||
/**
|
||||
* Defines login form definition page for SMS Factor.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
$mform->addElement(new \tool_mfa\local\form\verification_field());
|
||||
$mform->setType('verificationcode', PARAM_ALPHANUM);
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines login form definition page after form data has been set.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Form to inject global elements into.
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function login_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
$this->generate_and_sms_code();
|
||||
|
||||
// Disable the form check prompt.
|
||||
$mform->disable_form_change_checker();
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements login form validation for SMS Factor.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function login_form_validation(array $data): array {
|
||||
$return = [];
|
||||
|
||||
if (!$this->check_verification_code($data['verificationcode'])) {
|
||||
$return['verificationcode'] = get_string('error:wrongverification', 'factor_sms');
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string for setup button on preferences page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_setup_string(): string {
|
||||
return get_string('setupfactorbutton', 'factor_sms');
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines setup_factor form definition page for SMS Factor.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_factor_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
global $OUTPUT, $USER, $DB;
|
||||
|
||||
if (!empty(
|
||||
$phonenumber = $DB->get_field('tool_mfa', 'label', ['factor' => $this->name, 'userid' => $USER->id, 'revoked' => 0])
|
||||
)) {
|
||||
redirect(
|
||||
new \moodle_url('/admin/tool/mfa/user_preferences.php'),
|
||||
get_string('factorsetup', 'tool_mfa', $phonenumber),
|
||||
null,
|
||||
\core\output\notification::NOTIFY_SUCCESS);
|
||||
}
|
||||
|
||||
$mform->addElement('html', $OUTPUT->heading(get_string('setupfactor', 'factor_sms'), 2));
|
||||
|
||||
if (empty($this->get_phonenumber())) {
|
||||
$mform->addElement('hidden', 'verificationcode', 0);
|
||||
$mform->setType('verificationcode', PARAM_ALPHANUM);
|
||||
|
||||
// Add field for phone number setup.
|
||||
$mform->addElement('text', 'phonenumber', get_string('addnumber', 'factor_sms'),
|
||||
[
|
||||
'autocomplete' => 'tel',
|
||||
'inputmode' => 'tel',
|
||||
]);
|
||||
$mform->setType('phonenumber', PARAM_TEXT);
|
||||
|
||||
// HTML to display a message about the phone number.
|
||||
$message = \html_writer::tag('div', '', ['class' => 'col-md-3']);
|
||||
$message .= \html_writer::tag(
|
||||
'div', \html_writer::tag('p', get_string('phonehelp', 'factor_sms')), ['class' => 'col-md-9']);
|
||||
$mform->addElement('html', \html_writer::tag('div', $message, ['class' => 'row']));
|
||||
}
|
||||
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines setup_factor form definition page after form data has been set.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_factor_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
global $OUTPUT;
|
||||
|
||||
$phonenumber = $this->get_phonenumber();
|
||||
if (empty($phonenumber)) {
|
||||
return $mform;
|
||||
}
|
||||
|
||||
$duration = get_config('factor_sms', 'duration');
|
||||
$code = $this->secretmanager->create_secret($duration, true);
|
||||
if (!empty($code)) {
|
||||
$this->sms_verification_code($code, $phonenumber);
|
||||
}
|
||||
$message = get_string('logindesc', 'factor_sms', '<b>' . $phonenumber . '</b><br/>');
|
||||
$message .= get_string('editphonenumberinfo', 'factor_sms');
|
||||
$mform->addElement('html', \html_writer::tag('p', $OUTPUT->notification($message, 'success')));
|
||||
|
||||
$mform->addElement(new \tool_mfa\local\form\verification_field());
|
||||
$mform->setType('verificationcode', PARAM_ALPHANUM);
|
||||
|
||||
$editphonenumber = \html_writer::link(
|
||||
new \moodle_url('/admin/tool/mfa/factor/sms/editphonenumber.php', ['sesskey' => sesskey()]),
|
||||
get_string('editphonenumber', 'factor_sms'),
|
||||
['class' => 'btn btn-secondary', 'type' => 'button']);
|
||||
|
||||
$mform->addElement('html', \html_writer::tag('div', $editphonenumber, ['class' => 'float-sm-left col-md-4']));
|
||||
|
||||
// Disable the form check prompt.
|
||||
$mform->disable_form_change_checker();
|
||||
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the phone number from the current session or from the user profile data.
|
||||
* @return string|null
|
||||
*/
|
||||
private function get_phonenumber(): ?string {
|
||||
global $SESSION, $USER, $DB;
|
||||
|
||||
if (!empty($SESSION->tool_mfa_sms_number)) {
|
||||
return $SESSION->tool_mfa_sms_number;
|
||||
}
|
||||
$phonenumber = $DB->get_field('tool_mfa', 'label', ['factor' => $this->name, 'userid' => $USER->id, 'revoked' => 0]);
|
||||
if (!empty($phonenumber)) {
|
||||
return $phonenumber;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of errors, where array key = field id and array value = error text.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function setup_factor_form_validation(array $data): array {
|
||||
$errors = [];
|
||||
|
||||
// Phone number validation.
|
||||
if (!empty($data["phonenumber"]) && empty(helper::is_valid_phonenumber($data["phonenumber"]))) {
|
||||
$errors['phonenumber'] = get_string('error:wrongphonenumber', 'factor_sms');
|
||||
|
||||
} else if (!empty($this->get_phonenumber())) {
|
||||
// Code validation.
|
||||
if (empty($data["verificationcode"])) {
|
||||
$errors['verificationcode'] = get_string('error:emptyverification', 'factor_sms');
|
||||
} else if ($this->secretmanager->validate_secret($data['verificationcode']) !== $this->secretmanager::VALID) {
|
||||
$errors['verificationcode'] = get_string('error:wrongverification', 'factor_sms');
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset values of the session data of the given factor.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @return void
|
||||
*/
|
||||
public function setup_factor_form_is_cancelled(int $factorid): void {
|
||||
global $SESSION;
|
||||
if (!empty($SESSION->tool_mfa_sms_number)) {
|
||||
unset($SESSION->tool_mfa_sms_number);
|
||||
}
|
||||
// Clean temp secrets code.
|
||||
$secretmanager = new \tool_mfa\local\secret_manager('sms');
|
||||
$secretmanager->cleanup_temp_secrets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup submit button string in given factor
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function setup_factor_form_submit_button_string(): ?string {
|
||||
global $SESSION;
|
||||
if (!empty($SESSION->tool_mfa_sms_number)) {
|
||||
return get_string('setupsubmitcode', 'factor_sms');
|
||||
}
|
||||
return get_string('setupsubmitphone', 'factor_sms');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an instance of the factor for a user, from form data.
|
||||
*
|
||||
* @param stdClass $data
|
||||
* @return stdClass|null the factor record, or null.
|
||||
*/
|
||||
public function setup_user_factor(stdClass $data): ?stdClass {
|
||||
global $DB, $SESSION, $USER;
|
||||
|
||||
// Handle phone number submission.
|
||||
if (empty($SESSION->tool_mfa_sms_number)) {
|
||||
$SESSION->tool_mfa_sms_number = !empty($data->phonenumber) ? $data->phonenumber : '';
|
||||
|
||||
$addurl = new \moodle_url('/admin/tool/mfa/action.php', [
|
||||
'action' => 'setup',
|
||||
'factor' => 'sms',
|
||||
]);
|
||||
redirect($addurl);
|
||||
}
|
||||
|
||||
// If the user somehow gets here through form resubmission.
|
||||
// We dont want two phones active.
|
||||
if ($DB->record_exists('tool_mfa', ['userid' => $USER->id, 'factor' => $this->name, 'revoked' => 0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$time = time();
|
||||
$label = $this->get_phonenumber();
|
||||
|
||||
$row = new \stdClass();
|
||||
$row->userid = $USER->id;
|
||||
$row->factor = $this->name;
|
||||
$row->secret = '';
|
||||
$row->label = $label;
|
||||
$row->timecreated = $time;
|
||||
$row->createdfromip = $USER->lastip;
|
||||
$row->timemodified = $time;
|
||||
$row->lastverified = $time;
|
||||
$row->revoked = 0;
|
||||
|
||||
$id = $DB->insert_record('tool_mfa', $row);
|
||||
$record = $DB->get_record('tool_mfa', ['id' => $id]);
|
||||
$this->create_event_after_factor_setup($USER);
|
||||
|
||||
// Remove session phone number.
|
||||
unset($SESSION->tool_mfa_sms_number);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all user factors of given type.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors(stdClass $user): array {
|
||||
global $DB;
|
||||
|
||||
$sql = 'SELECT *
|
||||
FROM {tool_mfa}
|
||||
WHERE userid = ?
|
||||
AND factor = ?
|
||||
AND label IS NOT NULL
|
||||
AND revoked = 0';
|
||||
|
||||
return $DB->get_records_sql($sql, [$user->id, $this->name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information about factor availability.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled(): bool {
|
||||
if (empty(get_config('factor_sms', 'gateway'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$class = '\factor_sms\local\smsgateway\\' . get_config('factor_sms', 'gateway');
|
||||
if (!call_user_func($class . '::is_gateway_enabled')) {
|
||||
return false;
|
||||
}
|
||||
return parent::is_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if a factor requires input from the user to verify.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_input(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if factor needs to be setup by user and has setup_form.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_setup(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if the setup buttons should be shown on the preferences page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function show_setup_buttons(): bool {
|
||||
global $DB, $USER;
|
||||
|
||||
// If there is already a factor setup, don't allow multiple (for now).
|
||||
$record = $DB->get_record('tool_mfa',
|
||||
['userid' => $USER->id, 'factor' => $this->name, 'secret' => '', 'revoked' => 0]);
|
||||
|
||||
return empty($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if factor class has factor records that might be revoked.
|
||||
* It means that user can revoke factor record from their profile.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_revoke(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and sms' the code for login to the user, stores codes in DB.
|
||||
*
|
||||
* @return int|null the instance ID being used.
|
||||
*/
|
||||
private function generate_and_sms_code(): ?int {
|
||||
global $DB, $USER;
|
||||
|
||||
$duration = get_config('factor_sms', 'duration');
|
||||
$instance = $DB->get_record('tool_mfa', ['factor' => $this->name, 'userid' => $USER->id, 'revoked' => 0]);
|
||||
if (empty($instance)) {
|
||||
return null;
|
||||
}
|
||||
$secret = $this->secretmanager->create_secret($duration, false);
|
||||
// There is a new code that needs to be sent.
|
||||
if (!empty($secret)) {
|
||||
// Grab the singleton SMS record.
|
||||
$this->sms_verification_code($secret, $instance->label);
|
||||
}
|
||||
return $instance->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function sends an SMS code to the user based on the phonenumber provided.
|
||||
*
|
||||
* @param int $secret the secret to send.
|
||||
* @param string|null $phonenumber the phonenumber to send the verification code to.
|
||||
* @return void
|
||||
*/
|
||||
private function sms_verification_code(int $secret, ?string $phonenumber): void {
|
||||
global $CFG, $SITE;
|
||||
|
||||
// Here we should get the information, then construct the message.
|
||||
$url = new moodle_url($CFG->wwwroot);
|
||||
$content = [
|
||||
'fullname' => $SITE->fullname,
|
||||
'url' => $url->get_host(),
|
||||
'code' => $secret,
|
||||
];
|
||||
$message = get_string('smsstring', 'factor_sms', $content);
|
||||
|
||||
$class = '\factor_sms\local\smsgateway\\' . get_config('factor_sms', 'gateway');
|
||||
$gateway = new $class();
|
||||
$gateway->send_sms_message($message, $phonenumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies entered code against stored DB record.
|
||||
*
|
||||
* @param string $enteredcode
|
||||
* @return bool
|
||||
*/
|
||||
private function check_verification_code(string $enteredcode): bool {
|
||||
return ($this->secretmanager->validate_secret($enteredcode) === \tool_mfa\local\secret_manager::VALID) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all possible states for a user.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states(\stdClass $user): array {
|
||||
return [
|
||||
\tool_mfa\plugininfo\factor::STATE_PASS,
|
||||
\tool_mfa\plugininfo\factor::STATE_NEUTRAL,
|
||||
\tool_mfa\plugininfo\factor::STATE_FAIL,
|
||||
\tool_mfa\plugininfo\factor::STATE_UNKNOWN,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login description associated with this factor.
|
||||
* Override for factors that have a user input.
|
||||
*
|
||||
* @return string The login option.
|
||||
*/
|
||||
public function get_login_desc(): string {
|
||||
|
||||
$phonenumber = $this->get_phonenumber();
|
||||
|
||||
if (empty($phonenumber)) {
|
||||
return get_string('errorsmssent', 'factor_sms');
|
||||
} else {
|
||||
return get_string('logindesc', 'factor_' . $this->name, $phonenumber);
|
||||
}
|
||||
}
|
||||
}
|
67
admin/tool/mfa/factor/sms/classes/helper.php
Normal file
67
admin/tool/mfa/factor/sms/classes/helper.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* Helper class for shared sms gateway functions
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Alex Morris <alex.morris@catalyst.net.nz>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
|
||||
/**
|
||||
* This function internationalises a number to E.164 standard.
|
||||
* https://46elks.com/kb/e164
|
||||
*
|
||||
* @param string $phonenumber the phone number to format.
|
||||
* @return string the formatted phone number.
|
||||
*/
|
||||
public static function format_number(string $phonenumber): string {
|
||||
// Remove all whitespace, dashes and brackets.
|
||||
$phonenumber = preg_replace('/[ \(\)-]/', '', $phonenumber);
|
||||
|
||||
// Number is already in international format. Do nothing.
|
||||
if (str_starts_with ($phonenumber, '+')) {
|
||||
return $phonenumber;
|
||||
}
|
||||
|
||||
// Strip leading 0 if found.
|
||||
if (str_starts_with ($phonenumber, '0')) {
|
||||
$phonenumber = substr($phonenumber, 1);
|
||||
}
|
||||
|
||||
// Prepend country code.
|
||||
$countrycode = get_config('factor_sms', 'countrycode');
|
||||
$phonenumber = !empty($countrycode) ? '+' . $countrycode . $phonenumber : $phonenumber;
|
||||
|
||||
return $phonenumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate phone number with E.164 format. https://en.wikipedia.org/wiki/E.164
|
||||
*
|
||||
* @param string $phonenumber from the given user input
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_valid_phonenumber(string $phonenumber) : bool {
|
||||
$phonenumber = self::format_number($phonenumber);
|
||||
return (preg_match("/^\+[1-9]\d{1,14}$/", $phonenumber)) ? true : false;
|
||||
}
|
||||
}
|
100
admin/tool/mfa/factor/sms/classes/local/aws_helper.php
Normal file
100
admin/tool/mfa/factor/sms/classes/local/aws_helper.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* AWS helper class. Contains useful functions when interacting with the SDK.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright 2020 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace factor_sms\local;
|
||||
|
||||
use Aws\CommandInterface;
|
||||
use Aws\AwsClient;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* This class contains functions that help plugins to interact with the AWS SDK.
|
||||
*
|
||||
* @copyright 2020 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aws_helper {
|
||||
|
||||
/**
|
||||
* This creates a proxy string suitable for use with the AWS SDK.
|
||||
*
|
||||
* @return string the string to use for proxy settings.
|
||||
*/
|
||||
public static function get_proxy_string(): string {
|
||||
global $CFG;
|
||||
$proxy = '';
|
||||
if (empty($CFG->proxytype)) {
|
||||
return $proxy;
|
||||
}
|
||||
if ($CFG->proxytype === 'SOCKS5') {
|
||||
// If it is a SOCKS proxy, append the protocol info.
|
||||
$protocol = 'socks5://';
|
||||
} else {
|
||||
$protocol = '';
|
||||
}
|
||||
if (!empty($CFG->proxyhost)) {
|
||||
$proxy = $CFG->proxyhost;
|
||||
if (!empty($CFG->proxyport)) {
|
||||
$proxy .= ':'. $CFG->proxyport;
|
||||
}
|
||||
if (!empty($CFG->proxyuser) && !empty($CFG->proxypassword)) {
|
||||
$proxy = $protocol . $CFG->proxyuser . ':' . $CFG->proxypassword . '@' . $proxy;
|
||||
}
|
||||
}
|
||||
return $proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the provided AWS client to route traffic via the moodle proxy for any hosts not excluded.
|
||||
*
|
||||
* @param AwsClient $client
|
||||
* @return AwsClient
|
||||
*/
|
||||
public static function configure_client_proxy(AwsClient $client): AwsClient {
|
||||
$client->getHandlerList()->appendBuild(self::add_proxy_when_required(), 'proxy_bypass');
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a middleware higher order function to wrap the handler and append proxy configuration based on target.
|
||||
*
|
||||
* @return callable Middleware high order callable.
|
||||
*/
|
||||
protected static function add_proxy_when_required(): callable {
|
||||
return function (callable $fn) {
|
||||
return function (CommandInterface $command, ?RequestInterface $request = null) use ($fn) {
|
||||
if (isset($request)) {
|
||||
$target = (string) $request->getUri();
|
||||
if (!is_proxybypass($target)) {
|
||||
$command['@http']['proxy'] = self::get_proxy_string();
|
||||
}
|
||||
}
|
||||
|
||||
$promise = $fn($command, $request);
|
||||
return $promise;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
62
admin/tool/mfa/factor/sms/classes/local/client_factory.php
Normal file
62
admin/tool/mfa/factor/sms/classes/local/client_factory.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* AWS Client factory. Retrieves a client with moodle specific HTTP configuration.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright 2022 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace factor_sms\local;
|
||||
use Aws\AwsClient;
|
||||
|
||||
/**
|
||||
* AWS Client factory. Retrieves a client with moodle specific HTTP configuration.
|
||||
*
|
||||
* @copyright 2022 Catalyst IT
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class client_factory {
|
||||
/**
|
||||
* Get an AWS client with moodle specific HTTP configuration.
|
||||
*
|
||||
* @param string $class Fully qualified AWS classname e.g. \Aws\S3\S3Client
|
||||
* @param array $opts array of constructor options for AWS Client.
|
||||
* @return AwsClient
|
||||
*/
|
||||
public static function get_client(string $class, array $opts): AwsClient {
|
||||
// Modify the opts to add HTTP timeouts.
|
||||
if (empty($opts['http'])) {
|
||||
$opts['http'] = ['connect_timeout' => HOURSECS];
|
||||
} else if (!array_key_exists('connect_timeout', $opts['http'])) {
|
||||
// Try not to override existing settings.
|
||||
$opts['http']['connect_timeout'] = HOURSECS;
|
||||
}
|
||||
|
||||
// Blindly trust the call here. If it exceptions, the raw message is the most useful.
|
||||
$client = new $class($opts);
|
||||
if (!$client instanceof \Aws\AwsClient) {
|
||||
throw new \moodle_exception('clientnotfound', 'factor_sms');
|
||||
}
|
||||
|
||||
// Now we can configure the proxy with the routing aware middleware.
|
||||
return aws_helper::configure_client_proxy($client);
|
||||
}
|
||||
}
|
156
admin/tool/mfa/factor/sms/classes/local/smsgateway/aws_sns.php
Normal file
156
admin/tool/mfa/factor/sms/classes/local/smsgateway/aws_sns.php
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?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\local\smsgateway;
|
||||
|
||||
use factor_sms\admin_settings_aws_region;
|
||||
use factor_sms\event\sms_sent;
|
||||
use factor_sms\local\aws_helper;
|
||||
|
||||
/**
|
||||
* AWS SNS SMS Gateway class
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aws_sns implements gateway_interface {
|
||||
|
||||
/**
|
||||
* Create an instance of this class.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/aws-sdk/src/functions.php');
|
||||
require_once($CFG->libdir . '/guzzlehttp/guzzle/src/functions_include.php');
|
||||
require_once($CFG->libdir . '/guzzlehttp/promises/src/functions_include.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message using the AWS SNS API
|
||||
*
|
||||
* @param string $messagecontent the content to send in the SMS message.
|
||||
* @param string $phonenumber the destination for the message.
|
||||
* @return bool true on message send success
|
||||
*/
|
||||
public function send_sms_message(string $messagecontent, string $phonenumber): bool {
|
||||
global $SITE, $USER;
|
||||
|
||||
$config = get_config('factor_sms');
|
||||
|
||||
// Setup client params and instantiate client.
|
||||
$params = [
|
||||
'version' => 'latest',
|
||||
'region' => $config->api_region,
|
||||
'http' => ['proxy' => aws_helper::get_proxy_string()],
|
||||
];
|
||||
if (!$config->usecredchain) {
|
||||
$params['credentials'] = [
|
||||
'key' => $config->api_key,
|
||||
'secret' => $config->api_secret,
|
||||
];
|
||||
}
|
||||
$client = new \Aws\Sns\SnsClient($params);
|
||||
|
||||
// Transform the phone number to international standard.
|
||||
$phonenumber = \factor_sms\helper::format_number($phonenumber);
|
||||
|
||||
// Setup the sender information.
|
||||
$senderid = $SITE->shortname;
|
||||
// Remove spaces and non-alphanumeric characters from ID.
|
||||
$senderid = preg_replace("/[^A-Za-z0-9]/", '', trim($senderid));
|
||||
// We have to truncate the senderID to 11 chars.
|
||||
$senderid = substr($senderid, 0, 11);
|
||||
|
||||
if (defined('BEHAT_SITE_RUNNING')) {
|
||||
// Fake SMS sending in behat.
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// These messages need to be transactional.
|
||||
$client->SetSMSAttributes([
|
||||
'attributes' => [
|
||||
'DefaultSMSType' => 'Transactional',
|
||||
'DefaultSenderID' => $senderid,
|
||||
],
|
||||
]);
|
||||
|
||||
// Actually send the message.
|
||||
$result = $client->publish([
|
||||
'Message' => $messagecontent,
|
||||
'PhoneNumber' => $phonenumber,
|
||||
]);
|
||||
|
||||
$data = [
|
||||
'relateduserid' => null,
|
||||
'context' => \context_user::instance($USER->id),
|
||||
'other' => [
|
||||
'userid' => $USER->id,
|
||||
'debug' => [
|
||||
'messageid' => $result->get('MessageId'),
|
||||
],
|
||||
],
|
||||
];
|
||||
$event = sms_sent::create($data);
|
||||
$event->trigger();
|
||||
|
||||
return true;
|
||||
} catch (\Aws\Exception\AwsException $e) {
|
||||
throw new \moodle_exception('errorawsconection', 'factor_sms', '', $e->getAwsErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add gateway specific settings to the SMS factor settings page.
|
||||
*
|
||||
* @param \admin_settingpage $settings
|
||||
* @return void
|
||||
*/
|
||||
public static function add_settings(\admin_settingpage $settings): void {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/admin/tool/mfa/factor/sms/classes/admin_settings_aws_region.php');
|
||||
$settings->add(new \admin_setting_configcheckbox('factor_sms/usecredchain',
|
||||
get_string('settings:aws:usecredchain', 'factor_sms'), '', 0));
|
||||
|
||||
if (!get_config('factor_sms', 'usecredchain')) {
|
||||
// AWS Settings.
|
||||
$settings->add(new \admin_setting_configtext('factor_sms/api_key',
|
||||
get_string('settings:aws:key', 'factor_sms'),
|
||||
get_string('settings:aws:key_help', 'factor_sms'), ''));
|
||||
|
||||
$settings->add(new \admin_setting_configpasswordunmask('factor_sms/api_secret',
|
||||
get_string('settings:aws:secret', 'factor_sms'),
|
||||
get_string('settings:aws:secret_help', 'factor_sms'), ''));
|
||||
}
|
||||
|
||||
$settings->add(new admin_settings_aws_region('factor_sms/api_region',
|
||||
get_string('settings:aws:region', 'factor_sms'),
|
||||
get_string('settings:aws:region_help', 'factor_sms'),
|
||||
'ap-southeast-2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the gateway is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_gateway_enabled(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* SMS Gateway interface
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace factor_sms\local\smsgateway;
|
||||
|
||||
interface gateway_interface {
|
||||
|
||||
/**
|
||||
* Sends an SMS message
|
||||
*
|
||||
* @param string $messagecontent the content to send in the SMS message.
|
||||
* @param string $phonenumber the destination for the message.
|
||||
* @return bool true on message send success
|
||||
*/
|
||||
public function send_sms_message(string $messagecontent, string $phonenumber): bool;
|
||||
|
||||
/**
|
||||
* Add gateway specific settings to the SMS factor settings page.
|
||||
*
|
||||
* @param \admin_settingpage $settings
|
||||
* @return void
|
||||
*/
|
||||
public static function add_settings(\admin_settingpage $settings): void;
|
||||
|
||||
/**
|
||||
* Returns whether or not the gateway is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_gateway_enabled(): bool;
|
||||
}
|
40
admin/tool/mfa/factor/sms/classes/privacy/provider.php
Normal file
40
admin/tool/mfa/factor/sms/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?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\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements null_provider {
|
||||
|
||||
/**
|
||||
* Get the language string identifier with the component's language
|
||||
* file to explain why this plugin stores no data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_reason(): string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
44
admin/tool/mfa/factor/sms/editphonenumber.php
Normal file
44
admin/tool/mfa/factor/sms/editphonenumber.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Edit phonenumber redirect
|
||||
*
|
||||
* @package factor_sms
|
||||
* @copyright 2023 Raquel Ortega <raquel.ortega@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '../../../../../../config.php');
|
||||
|
||||
require_login(null, false);
|
||||
if (isguestuser()) {
|
||||
throw new require_login_exception('error:isguestuser', 'tool_mfa');
|
||||
}
|
||||
|
||||
$sesskey = optional_param('sesskey', false, PARAM_TEXT);
|
||||
require_sesskey();
|
||||
|
||||
// Remove session phone number.
|
||||
unset($SESSION->tool_mfa_sms_number);
|
||||
// Clean temp secrets code.
|
||||
$secretmanager = new \tool_mfa\local\secret_manager('sms');
|
||||
$secretmanager->cleanup_temp_secrets();
|
||||
|
||||
redirect(new \moodle_url('/admin/tool/mfa/action.php', [
|
||||
'action' => 'setup',
|
||||
'factor' => 'sms',
|
||||
]));
|
70
admin/tool/mfa/factor/sms/lang/en/factor_sms.php
Normal file
70
admin/tool/mfa/factor/sms/lang/en/factor_sms.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Language strings.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['action:revoke'] = 'Revoke mobile phone number';
|
||||
$string['addnumber'] = 'Mobile number';
|
||||
$string['clientnotfound'] = 'AWS Service client not found. Client must be fully qualified classname e.g. \Aws\S3\S3Client';
|
||||
$string['editphonenumber'] = 'Edit phone number';
|
||||
$string['editphonenumberinfo'] = "If you didn't get the code or entered the wrong number, please edit number and try again.";
|
||||
$string['errorawsconection'] = 'Error connecting to AWS server: {$a}';
|
||||
$string['errorsmssent'] = 'Error sending a SMS message containing your verification code.';
|
||||
$string['error:emptyverification'] = 'Empty code. Try again.';
|
||||
$string['error:wrongphonenumber'] = 'The phone number you provided is not in a valid format.';
|
||||
$string['error:wrongverification'] = 'Wrong code. Try again.';
|
||||
$string['event:smssent'] = 'SMS Message sent';
|
||||
$string['event:smssentdescription'] = 'The user with id {$a->userid} had a verification code sent to them via SMS <br> Information: {$a->debuginfo}';
|
||||
$string['info'] = '<p>Setup Mobile phone to receive authentication code.</p>';
|
||||
$string['logindesc'] = 'We\'ve just sent an SMS containing a 6-digit code to your mobile number: {$a}';
|
||||
$string['loginoption'] = 'Have a code sent to you mobile phone';
|
||||
$string['loginskip'] = "I didn't receive a code";
|
||||
$string['loginsubmit'] = 'Continue';
|
||||
$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['pluginname'] = 'SMS Mobile phone';
|
||||
$string['privacy:metadata'] = 'The mobile phone SMS factor plugin does not store any personal data';
|
||||
$string['settings:aws'] = 'AWS SNS';
|
||||
$string['settings:aws:key'] = 'Key';
|
||||
$string['settings:aws:key_help'] = 'Amazon API key credential.';
|
||||
$string['settings:aws:region'] = 'Region';
|
||||
$string['settings:aws:region_help'] = 'Amazon API gateway region.';
|
||||
$string['settings:aws:secret'] = 'Secret';
|
||||
$string['settings:aws:secret_help'] = 'Amazon API secret credential.';
|
||||
$string['settings:aws:usecredchain'] = 'Use the default credential provider chain to find AWS credentials';
|
||||
$string['settings:countrycode'] = 'Country number code';
|
||||
$string['settings:countrycode_help'] = 'The calling code without the leading + as a default if users do not enter an international number with a + prefix.
|
||||
|
||||
See this link for a list of calling codes: {$a}';
|
||||
$string['settings:duration'] = 'Validity duration';
|
||||
$string['settings:duration_help'] = 'The period of time that the code is valid.';
|
||||
$string['settings:gateway'] = 'SMS Gateway';
|
||||
$string['settings:gateway_help'] = 'The SMS provider you wish to send messages via';
|
||||
$string['setupfactor'] = 'SMS Setup';
|
||||
$string['setupfactorbutton'] = 'Setup SMS';
|
||||
$string['setupsubmitcode'] = 'Save';
|
||||
$string['setupsubmitphone'] = 'Send code';
|
||||
$string['smsstring'] = '{$a->code} is your {$a->fullname} one-time security code.
|
||||
|
||||
@{$a->url} #{$a->code}';
|
||||
$string['summarycondition'] = 'Using an SMS one-time security code';
|
66
admin/tool/mfa/factor/sms/settings.php
Normal file
66
admin/tool/mfa/factor/sms/settings.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Settings
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_sms/enabled',
|
||||
new lang_string('settings:enablefactor', 'tool_mfa'),
|
||||
new lang_string('settings:enablefactor_help', 'tool_mfa'), 0);
|
||||
$enabled->set_updatedcallback(function () {
|
||||
\tool_mfa\manager::do_factor_action('sms', get_config('factor_sms', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_sms/weight',
|
||||
new lang_string('settings:weight', 'tool_mfa'),
|
||||
new lang_string('settings:weight_help', 'tool_mfa'), 100, PARAM_INT));
|
||||
|
||||
$settings->add(new admin_setting_configduration('factor_sms/duration',
|
||||
get_string('settings:duration', 'tool_mfa'),
|
||||
get_string('settings:duration_help', 'tool_mfa'), 30 * MINSECS, MINSECS));
|
||||
|
||||
$codeslink = 'https://en.wikipedia.org/wiki/List_of_country_calling_codes';
|
||||
$link = \html_writer::link($codeslink, $codeslink);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_sms/countrycode',
|
||||
get_string('settings:countrycode', 'factor_sms'),
|
||||
get_string('settings:countrycode_help', 'factor_sms', $link), '', PARAM_INT));
|
||||
|
||||
$gateways = [
|
||||
'aws_sns' => get_string('settings:aws', 'factor_sms'),
|
||||
];
|
||||
|
||||
$settings->add(new admin_setting_configselect('factor_sms/gateway',
|
||||
get_string('settings:gateway', 'factor_sms'),
|
||||
get_string('settings:gateway_help', 'factor_sms'),
|
||||
'aws_sns', $gateways));
|
||||
|
||||
if (empty(get_config('factor_sms', 'gateway'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$class = '\factor_sms\local\smsgateway\\' . get_config('factor_sms', 'gateway');
|
||||
call_user_func($class . '::add_settings', $settings);
|
|
@ -0,0 +1,50 @@
|
|||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template factor_sms/setting_aws_region
|
||||
|
||||
Admin aws region setting template.
|
||||
|
||||
Context variables required for this template:
|
||||
* list - form list name
|
||||
* name - form element name
|
||||
* id - element id
|
||||
* value - element value
|
||||
* size - element size
|
||||
* options - list of data list options: label, value.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"list": "test",
|
||||
"name": "test",
|
||||
"id": "test0",
|
||||
"value": "A tall, dark stranger will have more fun than you.",
|
||||
"size": "21",
|
||||
"options": [ { "label": "eu-north-1 - Europe (Stockholm)", "value": "eu-north-1" } ]
|
||||
}
|
||||
}}
|
||||
{{!
|
||||
Setting config aws region
|
||||
}}
|
||||
<div class="form-text defaultsnext">
|
||||
<input type="text" list="{{list}}" name="{{name}}" value="{{value}}" size="{{size}}" id="{{id}}" class="form-control text-ltr" {{#readonly}}disabled{{/readonly}}>
|
||||
<datalist id="{{list}}">
|
||||
{{#options}}
|
||||
<option value="{{value}}" label="{{label}}"></option>
|
||||
{{/options}}
|
||||
</datalist>
|
||||
</div>
|
|
@ -0,0 +1,64 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* factor_sms unit tests.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Mikhail Golenkov <mikhailgolenkov@catalyst-au.net>
|
||||
* @copyright 2020 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace factor_sms;
|
||||
|
||||
/**
|
||||
* Testcase for the list of AWS regions admin setting.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Mikhail Golenkov <mikhailgolenkov@catalyst-au.net>
|
||||
* @copyright 2020 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \admin_settings_aws_region_test
|
||||
*/
|
||||
class admin_settings_aws_region_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Cleanup after all tests are executed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function tearDown(): void {
|
||||
$admin = admin_get_root();
|
||||
$admin->purge_children(true);
|
||||
}
|
||||
/**
|
||||
* Test that output_html() method works and returns HTML string with expected content.
|
||||
*/
|
||||
public function test_output_html(): void {
|
||||
$this->resetAfterTest();
|
||||
$setting = new admin_settings_aws_region('test_aws_region',
|
||||
'Test visible name', 'Test description', 'Test default setting');
|
||||
$html = $setting->output_html('');
|
||||
$this->assertTrue(str_contains($html, 'Test visible name'));
|
||||
$this->assertTrue(str_contains($html, 'Test description'));
|
||||
$this->assertTrue(str_contains($html, 'Default: Test default setting'));
|
||||
$this->assertTrue(str_contains($html,
|
||||
'<input type="text" list="s__test_aws_region" name="s__test_aws_region" value=""'));
|
||||
$this->assertTrue(str_contains($html, '<datalist id="s__test_aws_region">'));
|
||||
$this->assertTrue(str_contains($html, '<option value="'));
|
||||
}
|
||||
}
|
61
admin/tool/mfa/factor/sms/tests/aws_helper_test.php
Normal file
61
admin/tool/mfa/factor/sms/tests/aws_helper_test.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* factor_sms unit tests.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright 2020 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \factor_sms\local\aws_helper
|
||||
*/
|
||||
|
||||
namespace factor_sms;
|
||||
|
||||
use factor_sms\local\aws_helper;
|
||||
|
||||
/**
|
||||
* Testcase for the AWS helper.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright 2020 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \factor_sms\classes\local\aws_helper
|
||||
*/
|
||||
class aws_helper_test extends \advanced_testcase {
|
||||
|
||||
public function test_get_proxy_string():void {
|
||||
global $CFG;
|
||||
$this->resetAfterTest();
|
||||
// Confirm with no config an empty string is returned.
|
||||
$CFG->proxyhost = '';
|
||||
$this->assertEquals('', aws_helper::get_proxy_string());
|
||||
|
||||
// Now set some configs.
|
||||
$CFG->proxyhost = '127.0.0.1';
|
||||
$CFG->proxyuser = 'user';
|
||||
$CFG->proxypassword = 'password';
|
||||
$CFG->proxyport = '1337';
|
||||
$this->assertEquals('user:password@127.0.0.1:1337', aws_helper::get_proxy_string());
|
||||
|
||||
// Now change to SOCKS proxy.
|
||||
$CFG->proxytype = 'SOCKS5';
|
||||
$this->assertEquals('socks5://user:password@127.0.0.1:1337', aws_helper::get_proxy_string());
|
||||
}
|
||||
|
||||
}
|
55
admin/tool/mfa/factor/sms/tests/behat/behat_factor_sms.php
Normal file
55
admin/tool/mfa/factor/sms/tests/behat/behat_factor_sms.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Behat custom steps and configuration for factor_sms.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @category test
|
||||
* @copyright 2023 <raquel.ortega@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../../../../../lib/behat/behat_base.php');
|
||||
require_once(__DIR__ . '/../../../../../../../lib/behat/behat_field_manager.php');
|
||||
|
||||
/**
|
||||
* Behat custom steps and configuration for factor_sms.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @category test
|
||||
* @copyright 2023 <raquel.ortega@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_factor_sms extends behat_base {
|
||||
|
||||
/**
|
||||
* Sets the given field with a valid code created in tool_mfa_secrets table
|
||||
*
|
||||
* @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" with valid code$/
|
||||
*
|
||||
* @param string $field
|
||||
*/
|
||||
public function i_set_the_field_with_valid_code(string $field): void {
|
||||
global $DB, $USER;
|
||||
|
||||
$record = $DB->get_record('tool_mfa_secrets',
|
||||
['userid' => $USER->id, 'revoked' => '0']
|
||||
);
|
||||
$field = behat_field_manager::get_form_field_from_label($field, $this);
|
||||
$field->set_value($record->secret);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
@tool_admin_mfa
|
||||
Feature: Login user with sms authentication factor
|
||||
In order to login using SMS factor authentication
|
||||
As an user
|
||||
I need to be able to login
|
||||
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
And the following config values are set as admin:
|
||||
| enabled | 1 | tool_mfa |
|
||||
| lockout | 3 | tool_mfa |
|
||||
And the following config values are set as admin:
|
||||
| enabled | 1 | factor_sms |
|
||||
# Set up user SMS factor in user preferences.
|
||||
When I follow "Preferences" in the user menu
|
||||
And I click on "Multi-factor authentication preferences" "link"
|
||||
And I click on "Setup SMS" "button"
|
||||
And I set the field "Mobile number" to "+34649709233"
|
||||
And I press "Send code"
|
||||
And I set the field "Enter code" with valid code
|
||||
Then I press "Save"
|
||||
|
||||
Scenario: Revoke factor
|
||||
Given I click on "Revoke" "link"
|
||||
And I should see "Are you sure you want to revoke factor?"
|
||||
And I press "Revoke"
|
||||
And I should see "successfully revoked"
|
||||
When I log out
|
||||
And I log in as "admin"
|
||||
Then I should see "Unable to authenticate"
|
||||
|
||||
Scenario: Login user successfully with sms verification
|
||||
Given I log out
|
||||
And I log in as "admin"
|
||||
And I should see "2-step verification"
|
||||
And I should see "Enter code"
|
||||
When I set the field "Enter code" with valid code
|
||||
And I click on "Continue" "button"
|
||||
Then I am logged in as "admin"
|
||||
|
||||
Scenario: Wrong code number end of possible attempts
|
||||
Given I log out
|
||||
And I log in as "admin"
|
||||
And I should see "2-step verification"
|
||||
And I should see "Enter code"
|
||||
When I set the field "Enter code" to "555556"
|
||||
And I click on "Continue" "button"
|
||||
And I should see "Wrong code."
|
||||
And I should see "You have 2 attempts left."
|
||||
And I set the field "Enter code" to "555553"
|
||||
And I click on "Continue" "button"
|
||||
And I should see "Wrong code."
|
||||
And I should see "1 attempts left."
|
||||
And I set the field "Enter code" to "555553"
|
||||
And I click on "Continue" "button"
|
||||
Then I should see "Unable to authenticate"
|
|
@ -0,0 +1,49 @@
|
|||
@tool_admin_mfa
|
||||
Feature: Setup SMS factor in user preferences
|
||||
In order check the setup SMS factor verification
|
||||
As an admin
|
||||
I want to setup and enable the SMS factor for the current user
|
||||
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
And the following config values are set as admin:
|
||||
| enabled | 1 | tool_mfa |
|
||||
And the following config values are set as admin:
|
||||
| enabled | 1 | factor_sms |
|
||||
When I follow "Preferences" in the user menu
|
||||
And I click on "Multi-factor authentication preferences" "link"
|
||||
And I click on "Setup SMS" "button"
|
||||
|
||||
Scenario: Phone number setup form validation
|
||||
Given I set the field "Mobile number" to "++5555sss"
|
||||
And I press "Send code"
|
||||
And I should see "The phone number you provided is not in a valid format."
|
||||
And I set the field "Mobile number" to "0123456789"
|
||||
And I press "Send code"
|
||||
And I should see "The phone number you provided is not in a valid format."
|
||||
And I set the field "Mobile number" to "786-307-3615"
|
||||
And I press "Send code"
|
||||
And I should see "The phone number you provided is not in a valid format."
|
||||
When I set the field "Mobile number" to "649709233"
|
||||
And I press "Send code"
|
||||
Then I should see "The phone number you provided is not in a valid format."
|
||||
|
||||
Scenario: Edit phone number
|
||||
Given I set the field "Mobile number" to "+34649709233"
|
||||
And I press "Send code"
|
||||
And I click on "Edit phone number" "link"
|
||||
And I should see "Mobile number"
|
||||
When I set the field "Mobile number" to "+34649709232"
|
||||
And I press "Send code"
|
||||
Then I should see "Enter code"
|
||||
|
||||
Scenario: Code setup form validation
|
||||
Given I set the field "Mobile number" to "+34649709233"
|
||||
And I press "Send code"
|
||||
And I should see "Enter code"
|
||||
When I set the field "Enter code" to "555556"
|
||||
And I click on "Save" "button"
|
||||
And I should see "Wrong code. Try again"
|
||||
And I set the field "Enter code" to "ddddd5"
|
||||
And I click on "Save" "button"
|
||||
Then I should see "Wrong code. Try again"
|
166
admin/tool/mfa/factor/sms/tests/factor_test.php
Normal file
166
admin/tool/mfa/factor/sms/tests/factor_test.php
Normal file
|
@ -0,0 +1,166 @@
|
|||
<?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;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for sms factor.
|
||||
*
|
||||
* @covers \factor_sms\factor
|
||||
* @package factor_sms
|
||||
* @copyright 2023 Raquel Ortega <raquel.ortega@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class factor_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for test_format_number().
|
||||
*
|
||||
* @return array of different country codes and phone numbers.
|
||||
*/
|
||||
public function format_number_provider(): array {
|
||||
|
||||
return [
|
||||
'Phone number with local format' => [
|
||||
'phonenumber' => '0123456789',
|
||||
'expected' => '+34123456789',
|
||||
'countrycode' => '34',
|
||||
],
|
||||
'Phone number without international format' => [
|
||||
'phonenumber' => '123456789',
|
||||
'expected' => '+34123456789',
|
||||
'countrycode' => '34',
|
||||
],
|
||||
'Phone number with international format' => [
|
||||
'phonenumber' => '+39123456789',
|
||||
'expected' => '+39123456789',
|
||||
],
|
||||
'Phone number with spaces using international format' => [
|
||||
'phonenumber' => '+34 123 456 789',
|
||||
'expected' => '+34123456789',
|
||||
],
|
||||
'Phone number with spaces using local format with country code' => [
|
||||
'phonenumber' => '0 123 456 789',
|
||||
'expected' => '+34123456789',
|
||||
'countrycode' => '34',
|
||||
],
|
||||
'Phone number with spaces using local format without country code' => [
|
||||
'phonenumber' => '0 123 456 789',
|
||||
'expected' => '123456789',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test format number with different phones and different country codes
|
||||
* @covers \factor_sms\helper::format_number
|
||||
* @dataProvider format_number_provider
|
||||
*
|
||||
* @param string $phonenumber Phone number.
|
||||
* @param string $expected Expected value.
|
||||
* @param string|null $countrycode Country code.
|
||||
*/
|
||||
public function test_format_number(string $phonenumber, string $expected, ?string $countrycode = null): void {
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
set_config('countrycode', $countrycode, 'factor_sms');
|
||||
|
||||
$this->assertEquals($expected, \factor_sms\helper::format_number($phonenumber));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_is_valid__phonenumber().
|
||||
*
|
||||
* @return array with different phone numebr tests
|
||||
*/
|
||||
public function is_valid_phonenumber_provider(): array {
|
||||
return [
|
||||
['+919367788755', true],
|
||||
['8989829304', false],
|
||||
['+16308520397', true],
|
||||
['786-307-3615', false],
|
||||
['+14155552671', true],
|
||||
['+551155256325', true],
|
||||
['649709233', false],
|
||||
['+34649709233', true],
|
||||
['+aaasss', false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is valid phone number in E.164 format (https://en.wikipedia.org/wiki/E.164)
|
||||
* @covers \factor_sms\helper::is_valid_phonenumber
|
||||
* @dataProvider is_valid_phonenumber_provider
|
||||
*
|
||||
* @param string $phonenumber
|
||||
* @param bool $valid True if the given phone number is valid, false if is invalid
|
||||
*/
|
||||
public function test_is_valid_phonenumber(string $phonenumber, bool $valid): void {
|
||||
$this->resetAfterTest(true);
|
||||
if ($valid) {
|
||||
$this->assertTrue(\factor_sms\helper::is_valid_phonenumber($phonenumber));
|
||||
} else {
|
||||
$this->assertFalse(\factor_sms\helper::is_valid_phonenumber($phonenumber));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test set up user factor and verification code with a random phone number
|
||||
* @covers ::setup_user_factor
|
||||
* @covers ::check_verification_code
|
||||
* @covers ::revoke_user_factor
|
||||
*/
|
||||
public function test_check_verification_code(): void {
|
||||
global $SESSION;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create and login a user and set up the phone number.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
// Generate a fake phone number and save it in session.
|
||||
$phonenumber = '+34' . (string)random_int(100000000, 999999999);
|
||||
$SESSION->tool_mfa_sms_number = $phonenumber;
|
||||
|
||||
$smsfactor = \tool_mfa\plugininfo\factor::get_factor('sms');
|
||||
$rc = new \ReflectionClass($smsfactor::class);
|
||||
|
||||
$smsdata = [];
|
||||
$factorinstance = $smsfactor->setup_user_factor((object) $smsdata);
|
||||
|
||||
// Check if user factor was created successful.
|
||||
$this->assertNotEmpty($factorinstance);
|
||||
$this->assertEquals(1, count($smsfactor->get_active_user_factors($user)));
|
||||
|
||||
// Create the secret code.
|
||||
$secretmanager = new \tool_mfa\local\secret_manager('sms');
|
||||
$secretcode = $secretmanager->create_secret(1800, true);
|
||||
|
||||
// Check verification code.
|
||||
$rcm = $rc->getMethod('check_verification_code');
|
||||
$rcm->setAccessible(true);
|
||||
$this->assertTrue($rcm->invoke($smsfactor, $secretcode));
|
||||
|
||||
// Test that calling the revoke on the generic type revokes all.
|
||||
$smsfactor->revoke_user_factor($factorinstance->id);
|
||||
$this->assertEquals(0, count($smsfactor->get_active_user_factors($user)));
|
||||
|
||||
unset($SESSION->tool_mfa_sms_number);
|
||||
}
|
||||
}
|
32
admin/tool/mfa/factor/sms/version.php
Normal file
32
admin/tool/mfa/factor/sms/version.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Plugin version and other meta-data are defined here.
|
||||
*
|
||||
* @package factor_sms
|
||||
* @subpackage tool_mfa
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2023042400.00; // Requires this Moodle version.
|
||||
$plugin->component = 'factor_sms'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->maturity = MATURITY_STABLE;
|
|
@ -341,7 +341,7 @@ class factor extends object_factor_base {
|
|||
* TOTP Factor implementation.
|
||||
*
|
||||
* @param stdClass $data
|
||||
* @return stdClass the factor record, or null.
|
||||
* @return stdClass|null the factor record, or null.
|
||||
*/
|
||||
public function setup_user_factor(stdClass $data): stdClass|null {
|
||||
global $DB, $USER;
|
||||
|
|
|
@ -242,7 +242,7 @@ class factor extends object_factor_base {
|
|||
* WebAuthn Factor implementation.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_factor_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
global $PAGE, $USER, $SESSION;
|
||||
|
|
|
@ -41,6 +41,7 @@ $string['error:actionnotfound'] = 'Action \'{$a}\' not supported';
|
|||
$string['error:directaccess'] = 'This page shouldn\'t be accessed directly';
|
||||
$string['error:factornotenabled'] = 'Multi-factor authentication factor \'{$a}\' not enabled';
|
||||
$string['error:factornotfound'] = 'Multi-factor authentication factor \'{$a}\' not found';
|
||||
$string['error:isguestuser'] = 'Guests are not allowed here.';
|
||||
$string['error:notenoughfactors'] = 'Unable to authenticate';
|
||||
$string['error:reauth'] = 'We couldn\'t confirm your identity sufficiently to meet the site authentication security policy.<br>This may be due to: <br> 1) Steps being locked - please wait a few minutes and try again.
|
||||
<br> 2) Steps being failed - please double check the details for each step. <br> 3) Steps were skipped - please reload this page or try logging in again.';
|
||||
|
|
|
@ -262,4 +262,29 @@ class secret_manager_test extends \advanced_testcase {
|
|||
$reflectedsessionid->setValue($secman, 'diffsession');
|
||||
$this->assertFalse($reflectedmethod->invoke($secman, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with cleanup temporal secrets
|
||||
*
|
||||
* @covers ::cleanup_temp_secrets
|
||||
*/
|
||||
public function test_cleanup_temp_secrets(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$secman = new \tool_mfa\local\secret_manager('mock');
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
// Create secrets.
|
||||
$secman->create_secret(1800, true);
|
||||
$secman->create_secret(1800, true);
|
||||
|
||||
// Cleanup current user secrets.
|
||||
$secman->cleanup_temp_secrets();
|
||||
|
||||
// Check there are no secrets of the current user.
|
||||
$records = $DB->get_records('tool_mfa_secrets', ['userid' => $user->id]);
|
||||
$this->assertEmpty($records);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ require_once(__DIR__ . '/../../../config.php');
|
|||
|
||||
require_login(null, false);
|
||||
if (isguestuser()) {
|
||||
throw new require_login_exception('Guests are not allowed here.');
|
||||
throw new require_login_exception('error:isguestuser', 'tool_mfa');
|
||||
}
|
||||
|
||||
$action = optional_param('action', '', PARAM_TEXT);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue