mirror of
https://github.com/moodle/moodle.git
synced 2025-08-10 11:26:41 +02:00
MDL-78509 tool_mfa: Boarded the tool_mfa into core
In this patch the "commitid" of the "upstream" has been included in this commit. Also, we did not include three factors, which are SMS, Security Questions and Login Banner. Co-authored-by: Peter Burnett <peterburnett@catalyst-au.net> Co-authored-by: Misha Golenkov <golenkovm@gmail.com> Co-authored-by: Brendan Heywood <brendan@catalyst-au.net> Co-authored-by: Alex Morris <alex.morris@catalyst.net.nz> Co-authored-by: Dan Marsden <dan@danmarsden.com> Co-authored-by: Kevin Pham <kevinpham@catalyst-au.net> Co-authored-by: Chris Pratt <tonyyeb@gmail.com> Co-authored-by: Andrew Lyons <andrew@nicols.co.uk> Co-authored-by: Tomo Tsuyuki <tomotsuyuki@catalyst-au.net> Co-authored-by: Liam Kearney <https://github.com/LiamKearn> Co-authored-by: Nicholas Hoobin <nicholashoobin@catalyst-au.net> Co-authored-by: Scott Verbeek <scottverbeek@catalyst-au.net> Co-authored-by: nomisge <https://github.com/nomisge> Co-authored-by: Dmitrii Metelkin <dmitriim@catalyst-au.net> Co-authored-by: Matthew Hilton <matthewhilton@catalyst-au.net> Co-authored-by: Michael Geering <https://github.com/dryj> Co-authored-by: alphadijkstra <https://github.com/alphadijkstra>
This commit is contained in:
parent
9bfcd77d51
commit
a639783409
180 changed files with 50614 additions and 0 deletions
15
admin/tool/mfa/.github/workflows/ci.yml
vendored
Normal file
15
admin/tool/mfa/.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
# .github/workflows/ci.yml
|
||||
name: ci
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
uses: catalyst/catalyst-moodle-workflows/.github/workflows/ci.yml@main
|
||||
secrets:
|
||||
# Required if you plan to publish (uncomment the below)
|
||||
moodle_org_token: ${{ secrets.MOODLE_ORG_TOKEN }}
|
||||
with:
|
||||
#Grunt fails due to CSS styling needing an !important.
|
||||
disable_grunt: true
|
||||
release_branches: master
|
12
admin/tool/mfa/README.md
Normal file
12
admin/tool/mfa/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
#NOTE: This master branch has been deprecated. Please see the table below for the correct supported branches.
|
||||
|
||||
## Branches
|
||||
|
||||
| Version | Branch | Patches |
|
||||
|-----------------|--------------|----------------------|
|
||||
| Moodle 4.0 - 4.2| MOODLE_400_STABLE | None |
|
||||
| Moodle 3.8 -3.9 | MOODLE_35_STABLE | None |
|
||||
| Moodle 3.7 | MOODLE_35_STABLE | MDL-66340 |
|
||||
| Moodle 3.5-3.6 | MOODLE_35_STABLE | MDL-66340, MDL-60470 |
|
||||
| Totara 12-15 | MOODLE_35_STABLE | MDL-66340, MDL-60470 |
|
||||
|
151
admin/tool/mfa/action.php
Normal file
151
admin/tool/mfa/action.php
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?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/>.
|
||||
/**
|
||||
* Configure user factor page
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
use tool_mfa\local\form\setup_factor_form;
|
||||
use tool_mfa\local\form\revoke_factor_form;
|
||||
|
||||
require_login(null, false);
|
||||
if (isguestuser()) {
|
||||
throw new require_login_exception('Guests are not allowed here.');
|
||||
}
|
||||
|
||||
$action = optional_param('action', '', PARAM_ALPHANUMEXT);
|
||||
$factor = optional_param('factor', '', PARAM_ALPHANUMEXT);
|
||||
$factorid = optional_param('factorid', '', PARAM_INT);
|
||||
|
||||
$params = ['action' => $action, 'factor' => $factor, 'factorid' => $factorid];
|
||||
$currenturl = new moodle_url('/admin/tool/mfa/action.php', $params);
|
||||
|
||||
$returnurl = new moodle_url('/admin/tool/mfa/user_preferences.php');
|
||||
|
||||
if (empty($factor) || empty($action)) {
|
||||
throw new moodle_exception('error:directaccess', 'tool_mfa', $returnurl);
|
||||
}
|
||||
|
||||
if (!\tool_mfa\plugininfo\factor::factor_exists($factor)) {
|
||||
throw new moodle_exception('error:factornotfound', 'tool_mfa', $returnurl, $factor);
|
||||
}
|
||||
|
||||
if (!in_array($action, \tool_mfa\plugininfo\factor::get_factor_actions())) {
|
||||
throw new moodle_exception('error:actionnotfound', 'tool_mfa', $returnurl, $action);
|
||||
}
|
||||
|
||||
if (!empty($factorid) && !\tool_mfa\manager::is_factorid_valid($factorid, $USER)) {
|
||||
throw new moodle_exception('error:incorrectfactorid', 'tool_mfa', $returnurl, $factorid);
|
||||
}
|
||||
|
||||
$factorobject = \tool_mfa\plugininfo\factor::get_factor($factor);
|
||||
|
||||
$context = context_user::instance($USER->id);
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_url('/admin/tool/mfa/action.php');
|
||||
$PAGE->set_pagelayout('standard');
|
||||
$PAGE->set_title(get_string($action.'factor', 'tool_mfa'));
|
||||
$PAGE->set_cacheable(false);
|
||||
|
||||
if ($node = $PAGE->settingsnav->find('usercurrentsettings', null)) {
|
||||
$PAGE->navbar->add($node->get_content(), $node->action());
|
||||
}
|
||||
$PAGE->navbar->add(get_string('preferences:header', 'tool_mfa'), new \moodle_url('/admin/tool/mfa/user_preferences.php'));
|
||||
|
||||
switch ($action) {
|
||||
case 'setup':
|
||||
if (!$factorobject || !$factorobject->has_setup()) {
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
$PAGE->navbar->add(get_string('setupfactor', 'factor_'.$factor));
|
||||
$OUTPUT = $PAGE->get_renderer('tool_mfa');
|
||||
$form = new setup_factor_form($currenturl, ['factorname' => $factor]);
|
||||
|
||||
if ($form->is_submitted()) {
|
||||
$form->is_validated();
|
||||
|
||||
if ($form->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
if ($data = $form->get_data()) {
|
||||
$record = $factorobject->setup_user_factor($data);
|
||||
if (!empty($record)) {
|
||||
$factorobject->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
|
||||
$finalurl = new moodle_url($returnurl, ['action' => 'setup', 'factorid' => $record->id]);
|
||||
redirect($finalurl);
|
||||
}
|
||||
|
||||
throw new moodle_exception('error:setupfactor', 'tool_mfa', $returnurl);
|
||||
}
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
$form->display();
|
||||
|
||||
break;
|
||||
|
||||
case 'revoke':
|
||||
// Ensure sesskey is valid.
|
||||
require_sesskey();
|
||||
|
||||
if (!$factorobject || !$factorobject->has_revoke()) {
|
||||
throw new moodle_exception('error:revoke', 'tool_mfa', $returnurl);
|
||||
}
|
||||
|
||||
$PAGE->navbar->add(get_string('action:revoke', 'factor_'.$factor));
|
||||
$OUTPUT = $PAGE->get_renderer('tool_mfa');
|
||||
|
||||
$revokeparams = [
|
||||
'factorname' => $factorobject->get_display_name(),
|
||||
'devicename' => $factorobject->get_label($factorid),
|
||||
];
|
||||
$form = new revoke_factor_form($currenturl, $revokeparams);
|
||||
|
||||
if ($form->is_submitted()) {
|
||||
$form->is_validated();
|
||||
|
||||
if ($form->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
if ($form->get_data()) {
|
||||
if ($factorobject->revoke_user_factor($factorid)) {
|
||||
$finalurl = new moodle_url($returnurl, ['action' => 'revoked', 'factorid' => $factorid]);
|
||||
redirect($finalurl);
|
||||
}
|
||||
|
||||
throw new moodle_exception('error:revoke', 'tool_mfa', $returnurl);
|
||||
}
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
$form->display();
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
echo $OUTPUT->footer();
|
10
admin/tool/mfa/amd/build/autosubmit_verification_code.min.js
vendored
Normal file
10
admin/tool/mfa/amd/build/autosubmit_verification_code.min.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Module to autosubmit the verification code element when it reaches 6 characters.
|
||||
*
|
||||
* @module tool_mfa/autosubmit_verification_code
|
||||
* @copyright 2020 Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("tool_mfa/autosubmit_verification_code",[],(function(){return{init:function(){document.querySelector("#id_verificationcode").addEventListener("keyup",(function(){6==this.value.length&&this.closest("form").submit()}))}}}));
|
||||
|
||||
//# sourceMappingURL=autosubmit_verification_code.min.js.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"autosubmit_verification_code.min.js","sources":["../src/autosubmit_verification_code.js"],"sourcesContent":["\n// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Module to autosubmit the verification code element when it reaches 6 characters.\n *\n * @module tool_mfa/autosubmit_verification_code\n * @copyright 2020 Peter Burnett <peterburnett@catalyst-au.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([], function() {\n return {\n init: function() {\n document.querySelector(\"#id_verificationcode\").addEventListener('keyup', function() {\n if (this.value.length == 6) {\n // Submits the closes form (parent).\n this.closest(\"form\").submit();\n }\n });\n }\n };\n});\n"],"names":["define","init","document","querySelector","addEventListener","this","value","length","closest","submit"],"mappings":";;;;;;;AAwBAA,+CAAO,IAAI,iBACA,CACHC,KAAM,WACFC,SAASC,cAAc,wBAAwBC,iBAAiB,SAAS,WAC5C,GAArBC,KAAKC,MAAMC,aAENC,QAAQ,QAAQC"}
|
36
admin/tool/mfa/amd/src/autosubmit_verification_code.js
Normal file
36
admin/tool/mfa/amd/src/autosubmit_verification_code.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Module to autosubmit the verification code element when it reaches 6 characters.
|
||||
*
|
||||
* @module tool_mfa/autosubmit_verification_code
|
||||
* @copyright 2020 Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define([], function() {
|
||||
return {
|
||||
init: function() {
|
||||
document.querySelector("#id_verificationcode").addEventListener('keyup', function() {
|
||||
if (this.value.length == 6) {
|
||||
// Submits the closes form (parent).
|
||||
this.closest("form").submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
110
admin/tool/mfa/auth.php
Normal file
110
admin/tool/mfa/auth.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?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/>.
|
||||
/**
|
||||
* MFA page
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->dirroot . '/admin/tool/mfa/lib.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
|
||||
use tool_mfa\local\form\login_form;
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$context = context_user::instance($USER->id);
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_url('/admin/tool/mfa/auth.php');
|
||||
$PAGE->set_pagelayout('secure');
|
||||
$PAGE->blocks->show_only_fake_blocks();
|
||||
$pagetitle = $SITE->shortname.': '.get_string('mfa', 'tool_mfa');
|
||||
$PAGE->set_title($pagetitle);
|
||||
|
||||
// The only page action allowed here is a logout if it was requested.
|
||||
$logout = optional_param('logout', false, PARAM_BOOL);
|
||||
if ($logout) {
|
||||
if (!empty($SESSION->wantsurl)) {
|
||||
// If we have the wantsurl, we should redirect there, to keep it intact.
|
||||
$wantsurl = $SESSION->wantsurl;
|
||||
} else {
|
||||
// Else redirect home.
|
||||
$wantsurl = new \moodle_url($CFG->wwwroot);
|
||||
}
|
||||
|
||||
\tool_mfa\manager::mfa_logout();
|
||||
redirect($wantsurl);
|
||||
}
|
||||
|
||||
$currenturl = new moodle_url('/admin/tool/mfa/auth.php');
|
||||
|
||||
// Perform state check.
|
||||
\tool_mfa\manager::resolve_mfa_status();
|
||||
|
||||
// We have a valid landing here, before doing any actions, clear any redir loop progress.
|
||||
\tool_mfa\manager::clear_redirect_counter();
|
||||
|
||||
$factor = \tool_mfa\plugininfo\factor::get_next_user_factor();
|
||||
// If ok, perform form actions for input factor.
|
||||
$form = new login_form($currenturl, ['factor' => $factor]);
|
||||
if ($form->is_submitted()) {
|
||||
if (!$form->is_validated() && !$form->is_cancelled()) {
|
||||
// Increment the fail counter for the factor,
|
||||
// And let the factor handle locking logic.
|
||||
$factor->increment_lock_counter();
|
||||
\tool_mfa\manager::resolve_mfa_status(false);
|
||||
} else {
|
||||
// Set state from user actions.
|
||||
if ($form->is_cancelled()) {
|
||||
$factor->process_cancel_action();
|
||||
// Move to next factor.
|
||||
\tool_mfa\manager::resolve_mfa_status(true);
|
||||
} else {
|
||||
if ($data = $form->get_data()) {
|
||||
// Validation has passed, so before processing, lets action the global form submissions as well.
|
||||
$form->globalmanager->submit($data);
|
||||
|
||||
// Did user submit something that causes a fail state?
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
\tool_mfa\manager::resolve_mfa_status(true);
|
||||
}
|
||||
|
||||
$factor->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
|
||||
// Move to next factor.
|
||||
\tool_mfa\manager::resolve_mfa_status(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$renderer = $PAGE->get_renderer('tool_mfa');
|
||||
echo $OUTPUT->header();
|
||||
|
||||
\tool_mfa\manager::display_debug_notification();
|
||||
|
||||
echo $OUTPUT->heading(get_string('pluginname', 'factor_'.$factor->name));
|
||||
// Check if a notification is required for factor lockouts.
|
||||
$remattempts = $factor->get_remaining_attempts();
|
||||
if ($remattempts < get_config('tool_mfa', 'lockout')) {
|
||||
echo $OUTPUT->notification(get_string('lockoutnotification', 'tool_mfa', $remattempts), 'notifyerror');
|
||||
}
|
||||
$form->display();
|
||||
|
||||
echo $renderer->guide_link();
|
||||
echo $OUTPUT->footer();
|
95
admin/tool/mfa/classes/event/user_deleted_factor.php
Normal file
95
admin/tool/mfa/classes/event/user_deleted_factor.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?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 tool_mfa\event;
|
||||
|
||||
/**
|
||||
* Event for when user factor is deleted.
|
||||
*
|
||||
* @property-read array $other {
|
||||
* Extra information about event.
|
||||
* }
|
||||
*
|
||||
* @package 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 user_deleted_factor extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Create instance of event.
|
||||
*
|
||||
* @param stdClass $user the User object of the User who had the factor deleted.
|
||||
* @param stdClass $deleteuser the user who performed the factor delete.
|
||||
* @param string $factorname deleted factor
|
||||
*
|
||||
* @return user_factor_deleted the user_factor_deleted event
|
||||
*
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function user_deleted_factor_event($user, $deleteuser, $factorname) {
|
||||
|
||||
$data = [
|
||||
'relateduserid' => $user->id,
|
||||
'context' => \context_user::instance($user->id),
|
||||
'other' => [
|
||||
'userid' => $user->id,
|
||||
'factorname' => $factorname,
|
||||
'delete' => $deleteuser->id,
|
||||
],
|
||||
];
|
||||
|
||||
return self::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'd';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of what happened.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
// The log message changed from logging the deleter user object to the ID. This must be kept for backwards compat
|
||||
// With old log events.
|
||||
if (is_object($this->other['delete'])) {
|
||||
return "The user with id '{$this->other['delete']->id}' successfully deleted
|
||||
{$this->other['factorname']} factor for user with id '{$this->other['userid']}'";
|
||||
} else {
|
||||
return "The user with id '{$this->other['delete']}' successfully deleted
|
||||
{$this->other['factorname']} factor for user with id '{$this->other['userid']}'";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return localised event name.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('event:userdeletedfactor', 'tool_mfa');
|
||||
}
|
||||
}
|
98
admin/tool/mfa/classes/event/user_failed_mfa.php
Normal file
98
admin/tool/mfa/classes/event/user_failed_mfa.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?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 tool_mfa\event;
|
||||
|
||||
/**
|
||||
* Event for when user successfully passed all MFA factor checks.
|
||||
*
|
||||
* @property-read array $other {
|
||||
* Extra information about event.
|
||||
* }
|
||||
*
|
||||
* @package 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 user_failed_mfa extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Create instance of event.
|
||||
*
|
||||
* @param int $user the User object of the User who failed MFA authentication.
|
||||
*
|
||||
* @return user_failed_mfa the user_passed_mfa event
|
||||
*
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function user_failed_mfa_event($user) {
|
||||
// Build debug info string.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$debug = '';
|
||||
$failurereason = get_string('event:failnotenoughfactors', 'tool_mfa');
|
||||
foreach ($factors as $factor) {
|
||||
$debug .= "<br> Factor {$factor->name} status: {$factor->get_state()}";
|
||||
if ($factor->get_state() === \tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
$failurereason = get_string('event:failfactor', 'tool_mfa');
|
||||
} else if ($factor->get_state() === \tool_mfa\plugininfo\factor::STATE_LOCKED) {
|
||||
$failurereason = get_string('event:faillockout', 'tool_mfa');
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'relateduserid' => null,
|
||||
'context' => \context_user::instance($user->id),
|
||||
'other' => [
|
||||
'userid' => $user->id,
|
||||
'debug' => $debug,
|
||||
'failurereason' => $failurereason,
|
||||
],
|
||||
];
|
||||
|
||||
return self::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'r';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of what happened.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
return "The user with id '{$this->other['userid']}' failed authenticating with MFA.
|
||||
<br> Information: {$this->other['failurereason']}{$this->other['debug']}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return localised event name.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('event:userfailedmfa', 'tool_mfa');
|
||||
}
|
||||
}
|
91
admin/tool/mfa/classes/event/user_passed_mfa.php
Normal file
91
admin/tool/mfa/classes/event/user_passed_mfa.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?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 tool_mfa\event;
|
||||
|
||||
/**
|
||||
* Event for when user successfully passed all MFA factor checks.
|
||||
*
|
||||
* @property-read array $other {
|
||||
* Extra information about event.
|
||||
* }
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_passed_mfa extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Create instance of event.
|
||||
*
|
||||
* @param int $user the User object of the User who passed all MFA factor checks.
|
||||
*
|
||||
* @return user_passed_mfa the user_passed_mfa event
|
||||
*
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function user_passed_mfa_event($user) {
|
||||
|
||||
// Build debug info string.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$debug = '';
|
||||
foreach ($factors as $factor) {
|
||||
$debug .= "<br> Factor {$factor->name} status: {$factor->get_state()}";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'relateduserid' => null,
|
||||
'context' => \context_user::instance($user->id),
|
||||
'other' => [
|
||||
'userid' => $user->id,
|
||||
'debug' => $debug,
|
||||
],
|
||||
];
|
||||
|
||||
return self::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'r';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of what happened.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
return "The user with id '{$this->other['userid']}' successfully passed MFA. <br> Information: {$this->other['debug']}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return localised event name.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('event:userpassedmfa', 'tool_mfa');
|
||||
}
|
||||
}
|
85
admin/tool/mfa/classes/event/user_revoked_factor.php
Normal file
85
admin/tool/mfa/classes/event/user_revoked_factor.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?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 tool_mfa\event;
|
||||
|
||||
/**
|
||||
* Event for when user successfully revoked MFA Factor.
|
||||
*
|
||||
* @property-read array $other {
|
||||
* Extra information about event.
|
||||
* }
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_revoked_factor extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Create instance of event.
|
||||
*
|
||||
* @param int $user the User object of the User who has revoked new factor
|
||||
* @param string $factorname revoked factor
|
||||
*
|
||||
* @return user_passed_mfa the user_passed_mfa event
|
||||
*
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function user_revoked_factor_event($user, $factorname) {
|
||||
|
||||
$data = [
|
||||
'relateduserid' => null,
|
||||
'context' => \context_user::instance($user->id),
|
||||
'other' => [
|
||||
'userid' => $user->id,
|
||||
'factorname' => $factorname,
|
||||
],
|
||||
];
|
||||
|
||||
return self::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'd';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of what happened.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
return "The user with id '{$this->other['userid']}' successfully revoked {$this->other['factorname']}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return localised event name.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('event:userrevokedfactor', 'tool_mfa');
|
||||
}
|
||||
}
|
85
admin/tool/mfa/classes/event/user_setup_factor.php
Normal file
85
admin/tool/mfa/classes/event/user_setup_factor.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?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 tool_mfa\event;
|
||||
|
||||
/**
|
||||
* Event for when user successfully setup new MFA Factor.
|
||||
*
|
||||
* @property-read array $other {
|
||||
* Extra information about event.
|
||||
* }
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_setup_factor extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Create instance of event.
|
||||
*
|
||||
* @param object $user the User object of the User who has setup new factor
|
||||
* @param string $factorname setup factor
|
||||
*
|
||||
* @return user_passed_mfa the user_passed_mfa event
|
||||
*
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function user_setup_factor_event($user, $factorname) {
|
||||
|
||||
$data = [
|
||||
'relateduserid' => null,
|
||||
'context' => \context_user::instance($user->id),
|
||||
'other' => [
|
||||
'userid' => $user->id,
|
||||
'factorname' => $factorname,
|
||||
],
|
||||
];
|
||||
|
||||
return self::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'c';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of what happened.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
return "The user with id '{$this->other['userid']}' successfully setup {$this->other['factorname']}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return localised event name.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('event:usersetupfactor', 'tool_mfa');
|
||||
}
|
||||
}
|
270
admin/tool/mfa/classes/local/admin_setting_managemfa.php
Normal file
270
admin/tool/mfa/classes/local/admin_setting_managemfa.php
Normal file
|
@ -0,0 +1,270 @@
|
|||
<?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 tool_mfa\local;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/ddllib.php');
|
||||
require_once($CFG->libdir.'/xmlize.php');
|
||||
require_once($CFG->libdir.'/messagelib.php');
|
||||
|
||||
/**
|
||||
* Admin setting for MFA.
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class admin_setting_managemfa extends \admin_setting {
|
||||
|
||||
/**
|
||||
* Calls parent::__construct with specific arguments
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->nosave = true;
|
||||
parent::__construct('mfaui', get_string('mfasettings', 'tool_mfa'), '', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function get_setting() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns '' and doesn't write anything
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return string Always returns ''
|
||||
*/
|
||||
public function write_setting($data) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns XHTML to display Manage MFA admin page.
|
||||
*
|
||||
* @param mixed $data Unused
|
||||
* @param string $query
|
||||
*
|
||||
* @return string highlight
|
||||
* @throws \coding_exception
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public function output_html($data, $query='') {
|
||||
global $OUTPUT;
|
||||
|
||||
$return = $OUTPUT->box_start('generalbox');
|
||||
$return .= $this->define_manage_mfa_table();
|
||||
$return .= $OUTPUT->box_end();
|
||||
|
||||
$return .= $OUTPUT->heading(get_string('settings:combinations', 'tool_mfa'), 3);
|
||||
$return .= $OUTPUT->box_start('generalbox');
|
||||
$return .= $this->define_factor_combinations_table();
|
||||
$return .= $OUTPUT->box_end();
|
||||
|
||||
return highlight($query, $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines main table with configurable factors.
|
||||
*
|
||||
* @return string HTML code
|
||||
* @throws \coding_exception
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public function define_manage_mfa_table() {
|
||||
global $OUTPUT;
|
||||
$sesskey = sesskey();
|
||||
|
||||
$txt = get_strings(['enable', 'disable', 'moveup', 'movedown', 'order', 'settings']);
|
||||
$txt->factor = get_string('factor', 'tool_mfa');
|
||||
$txt->weight = get_string('weight', 'tool_mfa');
|
||||
$txt->setup = get_string('setuprequired', 'tool_mfa');
|
||||
$txt->input = get_string('inputrequired', 'tool_mfa');
|
||||
|
||||
$table = new \html_table();
|
||||
$table->id = 'managemfatable';
|
||||
$table->attributes['class'] = 'admintable generaltable';
|
||||
$table->head = [
|
||||
$txt->factor,
|
||||
$txt->enable,
|
||||
$txt->order,
|
||||
$txt->weight,
|
||||
$txt->settings,
|
||||
$txt->setup,
|
||||
$txt->input,
|
||||
];
|
||||
$table->colclasses = ['leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign'];
|
||||
$table->data = [];
|
||||
|
||||
$factors = \tool_mfa\plugininfo\factor::get_factors();
|
||||
$enabledfactors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$order = 1;
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
$settingsparams = ['section' => 'factor_'.$factor->name];
|
||||
$settingsurl = new \moodle_url('settings.php', $settingsparams);
|
||||
$settingslink = \html_writer::link($settingsurl, $txt->settings);
|
||||
|
||||
if ($factor->is_enabled()) {
|
||||
$hideshowparams = ['action' => 'disable', 'factor' => $factor->name, 'sesskey' => $sesskey];
|
||||
$hideshowurl = new \moodle_url('tool/mfa/index.php', $hideshowparams);
|
||||
$hideshowlink = \html_writer::link($hideshowurl, $OUTPUT->pix_icon('t/hide', $txt->disable));
|
||||
$class = '';
|
||||
|
||||
if ($order > 1) {
|
||||
$upparams = ['action' => 'up', 'factor' => $factor->name, 'sesskey' => $sesskey];
|
||||
$upurl = new \moodle_url('tool/mfa/index.php', $upparams);
|
||||
$uplink = \html_writer::link($upurl, $OUTPUT->pix_icon('t/up', $txt->moveup));
|
||||
} else {
|
||||
$uplink = \html_writer::link('', $uplink = $OUTPUT->spacer(['style' => 'margin-right: .5rem']));
|
||||
}
|
||||
|
||||
if ($order < count($enabledfactors)) {
|
||||
$downparams = ['action' => 'down', 'factor' => $factor->name, 'sesskey' => $sesskey];
|
||||
$downurl = new \moodle_url('tool/mfa/index.php', $downparams);
|
||||
$downlink = \html_writer::link($downurl, $OUTPUT->pix_icon('t/down', $txt->movedown));
|
||||
} else {
|
||||
$downlink = '';
|
||||
}
|
||||
$updownlink = $uplink.$downlink;
|
||||
$order++;
|
||||
} else {
|
||||
$hideshowparams = ['action' => 'enable', 'factor' => $factor->name, 'sesskey' => $sesskey];
|
||||
$hideshowurl = new \moodle_url('tool/mfa/index.php', $hideshowparams);
|
||||
$hideshowlink = \html_writer::link($hideshowurl, $OUTPUT->pix_icon('t/show', $txt->enable));
|
||||
$class = 'dimmed_text';
|
||||
$updownlink = '';
|
||||
}
|
||||
|
||||
$hassetup = $factor->has_setup() ? get_string('yes') : get_string('no');
|
||||
$hasinput = $factor->has_input() ? get_string('yes') : get_string('no');
|
||||
|
||||
$rowarray = [
|
||||
$factor->get_display_name(),
|
||||
$hideshowlink,
|
||||
$updownlink,
|
||||
$factor->get_weight(),
|
||||
$settingslink,
|
||||
$hassetup,
|
||||
$hasinput,
|
||||
];
|
||||
$row = new \html_table_row($rowarray);
|
||||
$row->attributes['class'] = $class;
|
||||
|
||||
$table->data[] = $row;
|
||||
}
|
||||
|
||||
return \html_writer::table($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines supplementary table that shows available combinations of factors enough for successful authentication.
|
||||
*
|
||||
* @return string HTML code
|
||||
*/
|
||||
public function define_factor_combinations_table() {
|
||||
global $OUTPUT;
|
||||
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$combinations = $this->get_factor_combinations($factors, 0, count($factors) - 1);
|
||||
|
||||
if (empty($combinations)) {
|
||||
return $OUTPUT->notification(get_string('error:notenoughfactors', 'tool_mfa'), 'notifyproblem');
|
||||
}
|
||||
|
||||
$txt = get_strings(['combination', 'totalweight'], 'tool_mfa');
|
||||
$table = new \html_table();
|
||||
$table->id = 'managemfatable';
|
||||
$table->attributes['class'] = 'admintable generaltable table table-bordered';
|
||||
$table->head = [$txt->combination, $txt->totalweight];
|
||||
$table->colclasses = ['leftalign', 'centeralign'];
|
||||
$table->data = [];
|
||||
|
||||
foreach ($combinations as $combination) {
|
||||
$string = '';
|
||||
foreach ($combination['combination'] as $factor) {
|
||||
$string .= ' ' . get_string('connector', 'tool_mfa') . ' ' . $factor->get_summary_condition()
|
||||
. ' <sup>' . $factor->get_weight() . '</sup>';
|
||||
}
|
||||
|
||||
$string = substr($string, 4);
|
||||
$table->data[] = new \html_table_row([$string, $combination['totalweight']]);
|
||||
}
|
||||
|
||||
return \html_writer::table($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive method to get all possible combinations of given factors.
|
||||
* Output is filtered by combination total weight (should be greater than 100).
|
||||
*
|
||||
* @param array $allfactors initial array of factor objects
|
||||
* @param int $start start position in initial array
|
||||
* @param int $end end position in initial array
|
||||
* @param int $totalweight total weight of combination
|
||||
* @param array $combination combination candidate
|
||||
* @param array $result array that includes combination total weight and subarray of factors combination
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_factor_combinations($allfactors, $start = 0, $end = 0,
|
||||
$totalweight = 0, $combination = [], $result = []) {
|
||||
|
||||
if ($totalweight >= 100) {
|
||||
// Ensure this is a valid combination before appending result.
|
||||
$valid = true;
|
||||
foreach ($combination as $factor) {
|
||||
if (!$factor->check_combination($combination)) {
|
||||
$valid = false;
|
||||
}
|
||||
}
|
||||
if ($valid) {
|
||||
$result[] = ['totalweight' => $totalweight, 'combination' => $combination];
|
||||
}
|
||||
return $result;
|
||||
} else if ($start > $end) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$combinationnext = $combination;
|
||||
$combinationnext[] = $allfactors[$start];
|
||||
|
||||
$result = $this->get_factor_combinations(
|
||||
$allfactors,
|
||||
$start + 1,
|
||||
$end,
|
||||
$totalweight + $allfactors[$start]->get_weight(),
|
||||
$combinationnext,
|
||||
$result);
|
||||
|
||||
$result = $this->get_factor_combinations(
|
||||
$allfactors,
|
||||
$start + 1,
|
||||
$end,
|
||||
$totalweight,
|
||||
$combination,
|
||||
$result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
67
admin/tool/mfa/classes/local/factor/fallback.php
Normal file
67
admin/tool/mfa/classes/local/factor/fallback.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 tool_mfa\local\factor;
|
||||
|
||||
/**
|
||||
* Fallback factor class.
|
||||
*
|
||||
* @package 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 fallback extends object_factor_base {
|
||||
|
||||
/**
|
||||
* Overridden constructor. Name is hard set to 'fallback'.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->name = 'fallback';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_display_name() {
|
||||
return get_string('fallback', 'tool_mfa');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_info() {
|
||||
return get_string('fallback_info', 'tool_mfa');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
return \tool_mfa\plugininfo\factor::STATE_FAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the factor check into the session.
|
||||
* Returns whether storing the var was successful.
|
||||
*
|
||||
* @param mixed $state
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
return false;
|
||||
}
|
||||
}
|
311
admin/tool/mfa/classes/local/factor/object_factor.php
Normal file
311
admin/tool/mfa/classes/local/factor/object_factor.php
Normal file
|
@ -0,0 +1,311 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* MFA factor interface.
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_mfa\local\factor;
|
||||
|
||||
interface object_factor {
|
||||
|
||||
/**
|
||||
* Returns true if factor is enabled, otherwise false.
|
||||
*
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function is_enabled();
|
||||
|
||||
/**
|
||||
* Returns configured factor weight.
|
||||
*
|
||||
* @return int
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function get_weight();
|
||||
|
||||
/**
|
||||
* Returns factor name from language string.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function get_display_name();
|
||||
|
||||
/**
|
||||
* Returns factor info from language string.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function get_info();
|
||||
|
||||
/**
|
||||
* Defines setup_factor form definition page for particular factor.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function setup_factor_form_definition($mform);
|
||||
|
||||
/**
|
||||
* Defines setup_factor form definition page after form data has been set.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function setup_factor_form_definition_after_data($mform);
|
||||
|
||||
/**
|
||||
* Implements setup_factor form validation for particular factor.
|
||||
* 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($data);
|
||||
|
||||
/**
|
||||
* Defines login form definition page for particular factor.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function login_form_definition($mform);
|
||||
|
||||
/**
|
||||
* Defines login form definition page after form data has been set.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function login_form_definition_after_data($mform);
|
||||
|
||||
/**
|
||||
* Implements login form validation for particular factor.
|
||||
* Returns an array of errors, where array key = field id and array value = error text.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function login_form_validation($data);
|
||||
|
||||
/**
|
||||
* Setups given factor and adds it to user's active factors list.
|
||||
* Returns true if factor has been successfully added, otherwise false.
|
||||
*
|
||||
* @param array $data
|
||||
* @return stdClass the factor record, or null.
|
||||
*/
|
||||
public function setup_user_factor($data);
|
||||
|
||||
/**
|
||||
* Returns an array of all user factors of given type (both active and revoked).
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user);
|
||||
|
||||
/**
|
||||
* Returns an array of active user factor records.
|
||||
* Filters get_all_user_factors() output.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_active_user_factors($user);
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Marks factor record as revoked.
|
||||
* If factorid is not provided, revoke all instances of factor.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @return bool
|
||||
*/
|
||||
public function revoke_user_factor($factorid);
|
||||
|
||||
/**
|
||||
* When validation code is correct - update lastverified field for given factor.
|
||||
* If factor id is not provided, update all factor entries for user.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @return bool
|
||||
*/
|
||||
public function update_lastverified($factorid);
|
||||
|
||||
/**
|
||||
* Gets lastverified timestamp.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @return int
|
||||
*/
|
||||
public function get_lastverified($factorid);
|
||||
|
||||
/**
|
||||
* Returns true if factor needs to be setup by user and has setup_form.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_setup();
|
||||
|
||||
/**
|
||||
* If has_setup returns true, decides if the setup buttons should be shown on the preferences page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function show_setup_buttons();
|
||||
|
||||
/**
|
||||
* Returns true if factor requires user input for success or failure during login.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_input();
|
||||
|
||||
/**
|
||||
* Returns the state of the factor check
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_state();
|
||||
|
||||
/**
|
||||
* Sets the state of the factor check into the session.
|
||||
* Returns whether storing the var was successful.
|
||||
*
|
||||
* @param mixed $state
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state);
|
||||
|
||||
/**
|
||||
* Fires any additional actions required by the factor once the user reaches the pass state.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function post_pass_state();
|
||||
|
||||
/**
|
||||
* Retrieves label for a factorid.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @return string
|
||||
*/
|
||||
public function get_label($factorid);
|
||||
|
||||
/**
|
||||
* Returns a list of urls to not redirect from.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_no_redirect_urls();
|
||||
|
||||
/**
|
||||
* Returns all possible states for a user.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states($user);
|
||||
|
||||
/**
|
||||
* Return summary condition for passing factor.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_summary_condition();
|
||||
|
||||
/**
|
||||
* Checks whether the factor combination is valid based on factor behaviour.
|
||||
* E.g. a combination with nosetup and another factor is not valid,
|
||||
* as you cannot pass nosetup with another factor.
|
||||
*
|
||||
* @param array $combination array of factors that make up the combination
|
||||
* @return bool
|
||||
*/
|
||||
public function check_combination($combination);
|
||||
|
||||
/**
|
||||
* Gets the string for setup button on preferences page.
|
||||
*
|
||||
* @return string the string to display on the button.
|
||||
*/
|
||||
public function get_setup_string();
|
||||
|
||||
/**
|
||||
* Deletes all instances of a factor for user.
|
||||
*
|
||||
* @param stdClass $user the user to delete for.
|
||||
*/
|
||||
public function delete_factor_for_user($user);
|
||||
|
||||
/**
|
||||
* Process a cancel action from a user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_cancel_action();
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Form to inject global elements into.
|
||||
* @return void
|
||||
*/
|
||||
public function global_definition($mform);
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Form to inject global elements into.
|
||||
* @return void
|
||||
*/
|
||||
public function global_definition_after_data($mform);
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param array $data Data from the form.
|
||||
* @param array $files Files form the form.
|
||||
* @return array of errors from validation.
|
||||
*/
|
||||
public function global_validation($data, $files): array;
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param object $data Data from the form.
|
||||
*/
|
||||
public function global_submit($data);
|
||||
}
|
610
admin/tool/mfa/classes/local/factor/object_factor_base.php
Normal file
610
admin/tool/mfa/classes/local/factor/object_factor_base.php
Normal file
|
@ -0,0 +1,610 @@
|
|||
<?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 tool_mfa\local\factor;
|
||||
|
||||
/**
|
||||
* MFA factor abstract class.
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class object_factor_base implements object_factor {
|
||||
|
||||
/** @var string Factor name */
|
||||
public $name;
|
||||
|
||||
/** @var int Lock counter */
|
||||
private $lockcounter;
|
||||
|
||||
/**
|
||||
* Secret manager
|
||||
*
|
||||
* @var \tool_mfa\local\secret_manager
|
||||
*/
|
||||
protected $secretmanager;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param string $name factor name
|
||||
*/
|
||||
public function __construct($name) {
|
||||
global $DB, $USER;
|
||||
$this->name = $name;
|
||||
|
||||
// Setup secret manager.
|
||||
$this->secretmanager = new \tool_mfa\local\secret_manager($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* This loads the locked state from the DB
|
||||
*
|
||||
* Base class implementation.
|
||||
*/
|
||||
public function load_locked_state() {
|
||||
global $DB, $USER;
|
||||
|
||||
// Check if lockcounter column exists (incase upgrade hasnt run yet).
|
||||
// Only 'input factors' are lockable.
|
||||
if ($this->is_enabled() && $this->is_lockable()) {
|
||||
try {
|
||||
// Setup the lock counter.
|
||||
$sql = "SELECT MAX(lockcounter) FROM {tool_mfa} WHERE userid = ? AND factor = ? AND revoked = ?";
|
||||
@$this->lockcounter = $DB->get_field_sql($sql, [$USER->id, $this->name, 0]);
|
||||
|
||||
if (empty($this->lockcounter)) {
|
||||
$this->lockcounter = 0;
|
||||
}
|
||||
|
||||
// Now lock this factor if over the counter.
|
||||
$lockthreshold = get_config('tool_mfa', 'lockout');
|
||||
if ($this->lockcounter >= $lockthreshold) {
|
||||
$this->set_state(\tool_mfa\plugininfo\factor::STATE_LOCKED);
|
||||
}
|
||||
} catch (\dml_exception $e) {
|
||||
// Set counter to -1.
|
||||
$this->lockcounter = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if factor is enabled, otherwise false.
|
||||
*
|
||||
* Base class implementation.
|
||||
*
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function is_enabled() {
|
||||
$status = get_config('factor_'.$this->name, 'enabled');
|
||||
if ($status == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns configured factor weight.
|
||||
*
|
||||
* Base class implementation.
|
||||
*
|
||||
* @return int
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function get_weight() {
|
||||
$weight = get_config('factor_'.$this->name, 'weight');
|
||||
if ($weight) {
|
||||
return (int) $weight;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns factor name from language string.
|
||||
*
|
||||
* Base class implementation.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function get_display_name() {
|
||||
return get_string('pluginname', 'factor_'.$this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns factor help from language string.
|
||||
*
|
||||
* Base class implementation.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function get_info() {
|
||||
return get_string('info', 'factor_'.$this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines setup_factor form definition page for particular factor.
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
*/
|
||||
public function setup_factor_form_definition($mform) {
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines setup_factor form definition page after form data has been set.
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
*/
|
||||
public function setup_factor_form_definition_after_data($mform) {
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements setup_factor form validation for particular factor.
|
||||
* Returns an array of errors, where array key = field id and array value = error text.
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function setup_factor_form_validation($data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setups given factor and adds it to user's active factors list.
|
||||
* Returns true if factor has been successfully added, otherwise false.
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param array $data
|
||||
* @return stdClass the record if created, or null.
|
||||
*/
|
||||
public function setup_user_factor($data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all user factors of given type (both active and revoked).
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of active user factor records.
|
||||
* Filters get_all_user_factors() output.
|
||||
*
|
||||
* @param stdClass $user object to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_active_user_factors($user) {
|
||||
$return = [];
|
||||
$factors = $this->get_all_user_factors($user);
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->revoked == 0) {
|
||||
$return[] = $factor;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines login form definition page for particular factor.
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
*/
|
||||
public function login_form_definition($mform) {
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines login form definition page after form data has been set.
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
*/
|
||||
public function login_form_definition_after_data($mform) {
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements login form validation for particular factor.
|
||||
* Returns an array of errors, where array key = field id and array value = error text.
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function login_form_validation($data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if factor class has factor records that might be revoked.
|
||||
* It means that user can revoke factor record from their profile.
|
||||
*
|
||||
* Override in child class if necessary.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_revoke() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks factor record as revoked.
|
||||
* If factorid is not provided, revoke all instances of factor.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function revoke_user_factor($factorid = null) {
|
||||
global $DB, $USER;
|
||||
|
||||
if (!empty($factorid)) {
|
||||
// If we have an explicit factor id, this means we need to be careful about the user.
|
||||
$params = ['id' => $factorid];
|
||||
$existing = $DB->get_record('tool_mfa', $params);
|
||||
if (empty($existing)) {
|
||||
return false;
|
||||
}
|
||||
$matchinguser = $existing->userid == $USER->id;
|
||||
if (!is_siteadmin() && !$matchinguser) {
|
||||
// We aren't admin, and this isn't our factor.
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$params = ['userid' => $USER->id, 'factor' => $this->name];
|
||||
}
|
||||
$DB->set_field('tool_mfa', 'revoked', 1, $params);
|
||||
|
||||
$event = \tool_mfa\event\user_revoked_factor::user_revoked_factor_event($USER, $this->get_display_name());
|
||||
$event->trigger();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When validation code is correct - update lastverified field for given factor.
|
||||
* If factor id is not provided, update all factor entries for user.
|
||||
* @param int $factorid
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function update_lastverified($factorid = null) {
|
||||
global $DB, $USER;
|
||||
if (!empty($factorid)) {
|
||||
$params = ['id' => $factorid];
|
||||
} else {
|
||||
$params = ['factor' => $this->name, 'userid' => $USER->id];
|
||||
}
|
||||
return $DB->set_field('tool_mfa', 'lastverified', time(), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets lastverified timestamp.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @return int|bool the lastverified timestamp, or false if not found.
|
||||
*/
|
||||
public function get_lastverified($factorid) {
|
||||
global $DB;
|
||||
|
||||
$record = $DB->get_record('tool_mfa', ['id' => $factorid]);
|
||||
return $record->lastverified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if factor needs to be setup by user and has setup_form.
|
||||
*
|
||||
* Override in child class if necessary.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_setup() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If has_setup returns true, decides if the setup buttons should be shown on the preferences page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function show_setup_buttons() {
|
||||
return $this->has_setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a factor requires input from the user to verify.
|
||||
*
|
||||
* Override in child class if necessary
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_input() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a factor is able to be locked if it fails.
|
||||
*
|
||||
* Generally only input factors are lockable.
|
||||
* Override in child class if necessary
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_lockable() {
|
||||
return $this->has_input();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state of the factor from session information.
|
||||
*
|
||||
* Implementation for factors that require input.
|
||||
* Should be overridden in child classes with no input.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_state() {
|
||||
global $SESSION;
|
||||
|
||||
$property = 'factor_'.$this->name;
|
||||
|
||||
if (property_exists($SESSION, $property)) {
|
||||
return $SESSION->$property;
|
||||
} else {
|
||||
return \tool_mfa\plugininfo\factor::STATE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the factor into the session.
|
||||
*
|
||||
* Implementation for factors that require input.
|
||||
* Should be overridden in child classes with no input.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
global $SESSION;
|
||||
|
||||
// Do not allow overwriting fail states.
|
||||
if ($this->get_state() == \tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$property = 'factor_'.$this->name;
|
||||
$SESSION->$property = $state;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event when user successfully setup a factor
|
||||
*
|
||||
* @param object $user
|
||||
* @return void
|
||||
*/
|
||||
public function create_event_after_factor_setup($user) {
|
||||
$event = \tool_mfa\event\user_setup_factor::user_setup_factor_event($user, $this->get_display_name());
|
||||
$event->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for factor actions in the pass state.
|
||||
* Override in child class if necessary.
|
||||
*/
|
||||
public function post_pass_state() {
|
||||
// Update lastverified for factor.
|
||||
if ($this->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
$this->update_lastverified();
|
||||
}
|
||||
|
||||
// Now clean temp secrets for factor.
|
||||
$this->secretmanager->cleanup_temp_secrets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to retrieve the label for a factorid.
|
||||
*
|
||||
* @param int $factorid
|
||||
*/
|
||||
public function get_label($factorid) {
|
||||
global $DB;
|
||||
return $DB->get_field('tool_mfa', 'label', ['id' => $factorid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get urls that should not be redirected from.
|
||||
*/
|
||||
public function get_no_redirect_urls() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get possible states for a user from factor.
|
||||
* Implementation where state is based on deterministic user data.
|
||||
* This should be overridden in factors where state is non-deterministic.
|
||||
* E.g. IP changes based on whether a user is using a VPN.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states($user) {
|
||||
return [$this->get_state()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns condition for passing factor.
|
||||
* Implementation for basic conditions.
|
||||
* Override for complex conditions such as auth type.
|
||||
*/
|
||||
public function get_summary_condition() {
|
||||
return get_string('summarycondition', 'factor_'.$this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the factor combination is valid based on factor behaviour.
|
||||
* E.g. a combination with nosetup and another factor is not valid,
|
||||
* as you cannot pass nosetup with another factor.
|
||||
*
|
||||
* @param array $combination array of factors that make up the combination
|
||||
* @return bool
|
||||
*/
|
||||
public function check_combination($combination) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string for setup button on preferences page.
|
||||
*/
|
||||
public function get_setup_string() {
|
||||
return get_string('setupfactor', 'tool_mfa');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all instances of factor for a user.
|
||||
*
|
||||
* @param stdClass $user the user to delete for.
|
||||
*/
|
||||
public function delete_factor_for_user($user) {
|
||||
global $DB, $USER;
|
||||
$DB->delete_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
|
||||
// Emit event for deletion.
|
||||
$event = \tool_mfa\event\user_deleted_factor::user_deleted_factor_event($user, $USER, $this->name);
|
||||
$event->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the lock counter for a factor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function increment_lock_counter() {
|
||||
global $DB, $USER;
|
||||
|
||||
// First make sure the state is loaded.
|
||||
$this->load_locked_state();
|
||||
|
||||
// If lockcounter is negative, the field does not exist yet.
|
||||
if ($this->lockcounter === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->lockcounter++;
|
||||
// Update record in DB.
|
||||
$DB->set_field('tool_mfa', 'lockcounter', $this->lockcounter, ['userid' => $USER->id, 'factor' => $this->name]);
|
||||
|
||||
// Now lock this factor if over the counter.
|
||||
$lockthreshold = get_config('tool_mfa', 'lockout');
|
||||
if ($this->lockcounter >= $lockthreshold) {
|
||||
$this->set_state(\tool_mfa\plugininfo\factor::STATE_LOCKED);
|
||||
|
||||
// Lastly output a notification showing the user the factor is locked.
|
||||
\core\notification::error(get_string('factorlocked', 'tool_mfa', $this->get_display_name()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of remaining attempts at this factor.
|
||||
*
|
||||
* @return int the number of attempts at this factor remaining.
|
||||
*/
|
||||
public function get_remaining_attempts() {
|
||||
$lockthreshold = get_config('tool_mfa', 'lockout');
|
||||
if ($this->lockcounter === -1) {
|
||||
// If upgrade.php hasnt been run yet, just return 10.
|
||||
return $lockthreshold;
|
||||
} else {
|
||||
return $lockthreshold - $this->lockcounter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a cancel input from a user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_cancel_action() {
|
||||
$this->set_state(\tool_mfa\plugininfo\factor::STATE_NEUTRAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Form to inject global elements into.
|
||||
* @return void
|
||||
*/
|
||||
public function global_definition($mform) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Form to inject global elements into.
|
||||
* @return void
|
||||
*/
|
||||
public function global_definition_after_data($mform) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param array $data Data from the form.
|
||||
* @param array $files Files form the form.
|
||||
* @return array of errors from validation.
|
||||
*/
|
||||
public function global_validation($data, $files): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param object $data Data from the form.
|
||||
*/
|
||||
public function global_submit($data) {
|
||||
return;
|
||||
}
|
||||
}
|
90
admin/tool/mfa/classes/local/form/global_form_manager.php
Normal file
90
admin/tool/mfa/classes/local/form/global_form_manager.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?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 tool_mfa\local\form;
|
||||
|
||||
use tool_mfa\plugininfo\factor;
|
||||
|
||||
/**
|
||||
* MFA login form
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class global_form_manager {
|
||||
/** @var array factors to call hooks upon. */
|
||||
private $activefactors;
|
||||
|
||||
/**
|
||||
* Create an instance of this class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->activefactors = factor::get_active_user_factor_types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Form to inject global elements into.
|
||||
* @return void
|
||||
*/
|
||||
public function definition(&$mform) {
|
||||
foreach ($this->activefactors as $factor) {
|
||||
$factor->global_definition($mform);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Form to inject global elements into.
|
||||
* @return void
|
||||
*/
|
||||
public function definition_after_data(&$mform) {
|
||||
foreach ($this->activefactors as $factor) {
|
||||
$factor->global_definition_after_data($mform);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point for global auth form action hooks.
|
||||
*
|
||||
* @param array $data Data from the form.
|
||||
* @param array $files Files form the form.
|
||||
* @return array of errors from validation.
|
||||
*/
|
||||
public function validation($data, $files) {
|
||||
$errors = [];
|
||||
foreach ($this->activefactors as $factor) {
|
||||
$errors = array_merge($errors, $factor->global_validation($data, $files));
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point for global auth form submission hooks.
|
||||
*
|
||||
* @param \stdClass $data Data from the form.
|
||||
* @return void
|
||||
*/
|
||||
public function submit(\stdClass $data) {
|
||||
foreach ($this->activefactors as $factor) {
|
||||
$factor->global_submit($data);
|
||||
}
|
||||
}
|
||||
}
|
111
admin/tool/mfa/classes/local/form/login_form.php
Normal file
111
admin/tool/mfa/classes/local/form/login_form.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?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 tool_mfa\local\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . "/formslib.php");
|
||||
|
||||
/**
|
||||
* MFA login form
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class login_form extends \moodleform {
|
||||
|
||||
/** @var \tool_mfa\local\form\global_form_manager */
|
||||
public $globalmanager;
|
||||
|
||||
/**
|
||||
* Create an instance of this class.
|
||||
*
|
||||
* @param mixed $action the action attribute for the form. If empty defaults to auto detect the
|
||||
* current url. If a moodle_url object then outputs params as hidden variables.
|
||||
* @param mixed $customdata if your form defintion method needs access to data such as $course
|
||||
* $cm, etc. to construct the form definition then pass it in this array. You can
|
||||
* use globals for somethings.
|
||||
* @param string $method if you set this to anything other than 'post' then _GET and _POST will
|
||||
* be merged and used as incoming data to the form.
|
||||
* @param string $target target frame for form submission. You will rarely use this. Don't use
|
||||
* it if you don't need to as the target attribute is deprecated in xhtml strict.
|
||||
* @param mixed $attributes you can pass a string of html attributes here or an array.
|
||||
* Special attribute 'data-random-ids' will randomise generated elements ids. This
|
||||
* is necessary when there are several forms on the same page.
|
||||
* Special attribute 'data-double-submit-protection' set to 'off' will turn off
|
||||
* double-submit protection JavaScript - this may be necessary if your form sends
|
||||
* downloadable files in response to a submit button, and can't call
|
||||
* \core_form\util::form_download_complete();
|
||||
* @param bool $editable
|
||||
* @param array $ajaxformdata Forms submitted via ajax, must pass their data here, instead of relying on _GET and _POST.
|
||||
*/
|
||||
public function __construct($action = null, $customdata = null, $method = 'post', $target = '',
|
||||
$attributes = null, $editable = true, $ajaxformdata = null) {
|
||||
$this->globalmanager = new \tool_mfa\local\form\global_form_manager();
|
||||
parent::__construct($action, $customdata, $method, $target, $attributes, $editable, $ajaxformdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see moodleform::definition()
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
$factor = $this->_customdata['factor'];
|
||||
$mform = $factor->login_form_definition($mform);
|
||||
$this->globalmanager->definition($mform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes factor login_form_definition_after_data() method after form data has been set.
|
||||
*/
|
||||
public function definition_after_data() {
|
||||
$mform = $this->_form;
|
||||
$factor = $this->_customdata['factor'];
|
||||
|
||||
$factor->login_form_definition_after_data($mform);
|
||||
$this->globalmanager->definition_after_data($mform);
|
||||
|
||||
$buttonarray = [];
|
||||
$buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('loginsubmit', 'factor_' . $factor->name));
|
||||
$buttonarray[] = &$mform->createElement('cancel', '', get_string('loginskip', 'factor_' . $factor->name));
|
||||
$mform->addGroup($buttonarray, 'buttonar', '', [' '], false);
|
||||
$mform->closeHeaderBefore('buttonar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the login form with given factor validation method.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
* @return array
|
||||
*/
|
||||
public function validation($data, $files) {
|
||||
$errors = parent::validation($data, $files);
|
||||
|
||||
$factor = $this->_customdata['factor'];
|
||||
$errors += $factor->login_form_validation($data);
|
||||
$errors += $this->globalmanager->validation($data, $files);
|
||||
|
||||
// Execute sleep time bruteforce mitigation.
|
||||
\tool_mfa\manager::sleep_timer();
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
95
admin/tool/mfa/classes/local/form/reset_factor.php
Normal file
95
admin/tool/mfa/classes/local/form/reset_factor.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?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 tool_mfa\local\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once("$CFG->libdir/formslib.php");
|
||||
|
||||
/**
|
||||
* Form to reset gracemode timer for users.
|
||||
*
|
||||
* @package 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 reset_factor extends \moodleform {
|
||||
|
||||
/**
|
||||
* Form definition.
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
$factors = $this->_customdata['factors'];
|
||||
$bulkaction = $this->_customdata['bulk'];
|
||||
|
||||
$mform->addElement('hidden', 'bulkaction', $bulkaction);
|
||||
$mform->setType('bulkaction', PARAM_BOOL);
|
||||
|
||||
$factors = array_map(function ($element) {
|
||||
return $element->get_display_name();
|
||||
}, $factors);
|
||||
// Add an 'all' action.
|
||||
$factors['all'] = get_string('all');
|
||||
|
||||
$mform->addElement('select', 'factor', get_string('selectfactor', 'tool_mfa'), $factors);
|
||||
|
||||
if (!$bulkaction) {
|
||||
$mform->addElement('text', 'resetfactor', get_string('resetuser', 'tool_mfa'),
|
||||
['placeholder' => get_string('resetfactorplaceholder', 'tool_mfa')]);
|
||||
$mform->setType('resetfactor', PARAM_TEXT);
|
||||
$mform->addRule('resetfactor', get_string('userempty', 'tool_mfa'), 'required');
|
||||
}
|
||||
|
||||
$this->add_action_buttons(true, get_string('resetconfirm', 'tool_mfa'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Form validation.
|
||||
*
|
||||
* Server side rules do not work for uploaded files, implement serverside rules here if needed.
|
||||
*
|
||||
* @param array $data array of ("fieldname"=>value) of submitted data
|
||||
* @param array $files array of uploaded files "element_name"=>tmp_file_path
|
||||
* @return array of "element_name"=>"error_description" if there are errors,
|
||||
* or an empty array if everything is OK (true allowed for backwards compatibility too).
|
||||
*/
|
||||
public function validation($data, $files) {
|
||||
global $DB;
|
||||
$errors = parent::validation($data, $files);
|
||||
|
||||
if (!$data['bulkaction']) {
|
||||
$userinfo = $data['resetfactor'];
|
||||
// Try input as username first, then email.
|
||||
$user = $DB->get_record('user', ['username' => $userinfo]);
|
||||
if (empty($user)) {
|
||||
// If not found, try username.
|
||||
$user = $DB->get_record('user', ['email' => $userinfo]);
|
||||
}
|
||||
|
||||
if (empty($user)) {
|
||||
$errors['resetfactor'] = get_string('usernotfound', 'tool_mfa');
|
||||
} else {
|
||||
// Add custom field to store user.
|
||||
$this->_form->addElement('hidden', 'user', $user);
|
||||
$this->_form->setType('user', PARAM_RAW);
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
49
admin/tool/mfa/classes/local/form/revoke_factor_form.php
Normal file
49
admin/tool/mfa/classes/local/form/revoke_factor_form.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?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 tool_mfa\local\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . "/formslib.php");
|
||||
|
||||
/**
|
||||
* Revoke factor form
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class revoke_factor_form extends \moodleform {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see moodleform::definition()
|
||||
*/
|
||||
public function definition() {
|
||||
global $OUTPUT;
|
||||
$mform = $this->_form;
|
||||
$factorname = $this->_customdata['factorname'];
|
||||
$devicename = $this->_customdata['devicename'];
|
||||
|
||||
$mform->addElement('html', $OUTPUT->heading(get_string('areyousure', 'tool_mfa'), 4));
|
||||
$mform->addElement('html', $OUTPUT->heading(get_string('factor', 'tool_mfa').': '.$factorname, 5));
|
||||
$mform->addElement('html', $OUTPUT->heading(get_string('devicename', 'tool_mfa').': '.$devicename, 5));
|
||||
|
||||
$this->add_action_buttons(true, get_string('revoke', 'tool_mfa'));
|
||||
}
|
||||
}
|
96
admin/tool/mfa/classes/local/form/setup_factor_form.php
Normal file
96
admin/tool/mfa/classes/local/form/setup_factor_form.php
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?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 tool_mfa\local\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . "/formslib.php");
|
||||
|
||||
/**
|
||||
* Setup factor form
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class setup_factor_form extends \moodleform {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see moodleform::definition()
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$factorname = $this->_customdata['factorname'];
|
||||
$factor = \tool_mfa\plugininfo\factor::get_factor($factorname);
|
||||
$mform = $factor->setup_factor_form_definition($mform);
|
||||
$this->xss_whitelist_static_form_elements($mform);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates setup_factor form with given factor validation method.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
* @return array
|
||||
*/
|
||||
public function validation($data, $files) {
|
||||
$errors = parent::validation($data, $files);
|
||||
|
||||
$factorname = $this->_customdata['factorname'];
|
||||
$factor = \tool_mfa\plugininfo\factor::get_factor($factorname);
|
||||
$errors += $factor->setup_factor_form_validation($data);
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes factor setup_factor_form_definition_after_data() method after form data has been set.
|
||||
*/
|
||||
public function definition_after_data() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$factorname = $this->_customdata['factorname'];
|
||||
$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();
|
||||
}
|
||||
|
||||
/**
|
||||
* In newer versions of Totara with consistent cleaning enabled we need to ensure to mark static elements
|
||||
* as "xss safe". Or in Totara's ideal world to use 'html' if form-like display is not required.
|
||||
*
|
||||
* @param \HTML_QuickForm $mform
|
||||
* @return void
|
||||
*/
|
||||
private function xss_whitelist_static_form_elements($mform) {
|
||||
if (!method_exists('MoodleQuickForm_static', 'set_allow_xss')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$elements = $mform->_elements;
|
||||
foreach ($elements as $element) {
|
||||
if (is_a($element, 'MoodleQuickForm_static')) {
|
||||
$element->set_allow_xss(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
124
admin/tool/mfa/classes/local/form/verification_field.php
Normal file
124
admin/tool/mfa/classes/local/form/verification_field.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?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 tool_mfa\local\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once($CFG->libdir . '/form/text.php');
|
||||
|
||||
/**
|
||||
* MFA Verification code element.
|
||||
*
|
||||
* @package 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 verification_field extends \MoodleQuickForm_text {
|
||||
|
||||
/** @var bool */
|
||||
private $appendjs;
|
||||
|
||||
/**
|
||||
* Verification field is a text entry box that features some useful extras.
|
||||
*
|
||||
* Contains JS to autosubmit the auth page when code is entered, as well as additional styling.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param boolean $auth is this constructed in auth.php loginform_* definitions. Set to false to prevent autosubmission of form.
|
||||
*/
|
||||
public function __construct($attributes = null, $auth = true) {
|
||||
global $PAGE;
|
||||
|
||||
// Force attributes.
|
||||
if (empty($attributes)) {
|
||||
$attributes = [];
|
||||
}
|
||||
|
||||
$attributes['autocomplete'] = 'one-time-code';
|
||||
$attributes['inputmode'] = 'numeric';
|
||||
$attributes['pattern'] = '[0-9]*';
|
||||
$attributes['class'] = 'tool-mfa-verification-code';
|
||||
|
||||
// If we aren't on the auth page, this might be part of a larger form such as for setup.
|
||||
// We shouldn't autofocus here, as it probably isn't the only element, or main target.
|
||||
if ($auth) {
|
||||
$attributes['autofocus'] = 'autofocus';
|
||||
}
|
||||
|
||||
// If we are on the auth page, load JS for element.
|
||||
$this->appendjs = false;
|
||||
if ($auth) {
|
||||
if ($PAGE->pagelayout === 'secure') {
|
||||
$this->appendjs = true;
|
||||
} else {
|
||||
$PAGE->requires->js_call_amd('tool_mfa/autosubmit_verification_code', 'init', []);
|
||||
}
|
||||
}
|
||||
|
||||
// Force element name to match JS.
|
||||
$elementname = 'verificationcode';
|
||||
$elementlabel = get_string('verificationcode', 'tool_mfa');
|
||||
|
||||
return parent::__construct($elementname, $elementlabel, $attributes);
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Returns HTML for this form element.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHtml() {
|
||||
// Empty the value after all attributes decided.
|
||||
$this->_attributes['value'] = '';
|
||||
$result = parent::toHtml();
|
||||
|
||||
$submitjs = "<script>
|
||||
document.querySelector('#id_verificationcode').addEventListener('keyup', function() {
|
||||
if (this.value.length == 6) {
|
||||
// Submits the closes form (parent).
|
||||
this.closest('form').submit();
|
||||
}
|
||||
});
|
||||
</script>";
|
||||
|
||||
if ($this->appendjs) {
|
||||
$result .= $submitjs;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
/**
|
||||
* Setup and return the script for autosubmission while inside the secure layout.
|
||||
*
|
||||
* @return string the JS to inline attach to the rendered object.
|
||||
*/
|
||||
public function secure_js(): string {
|
||||
// Empty the value after all attributes decided.
|
||||
$this->_attributes['value'] = '';
|
||||
|
||||
return "<script>
|
||||
document.querySelector('#id_verificationcode').addEventListener('keyup', function() {
|
||||
if (this.value.length == 6) {
|
||||
// Submits the closes form (parent).
|
||||
this.closest('form').submit();
|
||||
}
|
||||
});
|
||||
</script>";
|
||||
}
|
||||
}
|
244
admin/tool/mfa/classes/local/secret_manager.php
Normal file
244
admin/tool/mfa/classes/local/secret_manager.php
Normal file
|
@ -0,0 +1,244 @@
|
|||
<?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 tool_mfa\local;
|
||||
|
||||
/**
|
||||
* MFA secret management class.
|
||||
*
|
||||
* @package 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 secret_manager {
|
||||
|
||||
/** @var string */
|
||||
const REVOKED = 'revoked';
|
||||
|
||||
/** @var string */
|
||||
const VALID = 'valid';
|
||||
|
||||
/** @var string */
|
||||
const NONVALID = 'nonvalid';
|
||||
|
||||
/** @var string */
|
||||
private $factor;
|
||||
|
||||
/** @var string|false */
|
||||
private $sessionid;
|
||||
|
||||
/**
|
||||
* Initialises a secret manager instance
|
||||
*
|
||||
* @param string $factor
|
||||
*/
|
||||
public function __construct(string $factor) {
|
||||
$this->factor = $factor;
|
||||
$this->sessionid = session_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates or takes a secret, and stores it in the database or session.
|
||||
*
|
||||
* @param int $expires the length of time the secret is valid. e.g. 1 min = 60
|
||||
* @param bool $session whether this secret should be linked to the session.
|
||||
* @param string $secret an optional provided secret
|
||||
* @return string the secret code, or 0 if no new code created.
|
||||
*/
|
||||
public function create_secret(int $expires, bool $session, string $secret = null): string {
|
||||
// Check if there already an active secret, unless we are forcibly given a code.
|
||||
if ($this->has_active_secret($session) && empty($secret)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Setup a secret if not provided.
|
||||
if (empty($secret)) {
|
||||
$secret = random_int(100000, 999999);
|
||||
}
|
||||
|
||||
// Now pass the code where it needs to go.
|
||||
if ($session) {
|
||||
$this->add_secret_to_db($secret, $expires, $this->sessionid);
|
||||
} else {
|
||||
$this->add_secret_to_db($secret, $expires);
|
||||
}
|
||||
|
||||
return $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the provided secret into the database with a given expiry duration.
|
||||
*
|
||||
* @param string $secret the secret to store
|
||||
* @param int $expires expiry duration in seconds
|
||||
* @param string $sessionid an optional sessionID to tie this record to
|
||||
* @return void
|
||||
*/
|
||||
private function add_secret_to_db(string $secret, int $expires, string $sessionid = null) {
|
||||
global $DB, $USER;
|
||||
$expirytime = time() + $expires;
|
||||
|
||||
$data = [
|
||||
'userid' => $USER->id,
|
||||
'factor' => $this->factor,
|
||||
'secret' => $secret,
|
||||
'timecreated' => time(),
|
||||
'expiry' => $expirytime,
|
||||
'revoked' => 0,
|
||||
];
|
||||
if (!empty($sessionid)) {
|
||||
$data['sessionid'] = $sessionid;
|
||||
}
|
||||
$DB->insert_record('tool_mfa_secrets', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the provided secret is currently valid.
|
||||
*
|
||||
* @param string $secret the secret to check
|
||||
* @param bool $keep should the secret be kept for reuse until expiry?
|
||||
* @return string a secret manager state constant
|
||||
*/
|
||||
public function validate_secret(string $secret, bool $keep = false): string {
|
||||
global $DB, $USER;
|
||||
$status = $this->check_secret_against_db($secret, $this->sessionid);
|
||||
if ($status !== self::NONVALID) {
|
||||
if ($status === self::VALID && !$keep) {
|
||||
// Cleanup DB $record.
|
||||
$DB->delete_records('tool_mfa_secrets', ['userid' => $USER->id, 'factor' => $this->factor]);
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
// This is always nonvalid.
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given secret is valid from the Database.
|
||||
*
|
||||
* @param string $secret the secret to check.
|
||||
* @param string $sessionid the session id to check for.
|
||||
* @return string a secret manager state constant.
|
||||
*/
|
||||
private function check_secret_against_db(string $secret, string $sessionid): string {
|
||||
global $DB, $USER;
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM {tool_mfa_secrets}
|
||||
WHERE secret = :secret
|
||||
AND expiry > :now
|
||||
AND userid = :userid
|
||||
AND factor = :factor";
|
||||
|
||||
$params = [
|
||||
'secret' => $secret,
|
||||
'now' => time(),
|
||||
'userid' => $USER->id,
|
||||
'factor' => $this->factor,
|
||||
];
|
||||
|
||||
$record = $DB->get_record_sql($sql, $params);
|
||||
|
||||
if (!empty($record)) {
|
||||
// If revoked it should always be revoked status.
|
||||
if ($record->revoked) {
|
||||
return self::REVOKED;
|
||||
}
|
||||
|
||||
// Check if this is valid in only one session.
|
||||
if (!empty($record->sessionid)) {
|
||||
if ($record->sessionid === $sessionid) {
|
||||
return self::VALID;
|
||||
}
|
||||
return self::NONVALID;
|
||||
}
|
||||
return self::VALID;
|
||||
}
|
||||
return self::NONVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes the provided secret code for the user.
|
||||
*
|
||||
* @param string $secret the secret to revoke.
|
||||
* @param int $userid the userid to revoke the secret for.
|
||||
* @return void
|
||||
*/
|
||||
public function revoke_secret(string $secret, $userid = null) {
|
||||
global $DB, $USER;
|
||||
|
||||
$userid = $userid ?? $USER->id;
|
||||
|
||||
// We do not need to worry about session vs global here.
|
||||
// A factor should only ever use one.
|
||||
// We know this secret is valid, so we don't need to check expiry.
|
||||
$DB->set_field('tool_mfa_secrets', 'revoked', 1, ['userid' => $userid, 'factor' => $this->factor, 'secret' => $secret]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this factor currently has an active secret, and should not add another.
|
||||
*
|
||||
* @param bool $checksession should we only check if a current session secret is active?
|
||||
* @return bool
|
||||
*/
|
||||
private function has_active_secret(bool $checksession = false): bool {
|
||||
global $DB, $USER;
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM {tool_mfa_secrets}
|
||||
WHERE expiry > :now
|
||||
AND userid = :userid
|
||||
AND factor = :factor
|
||||
AND revoked = 0";
|
||||
|
||||
$params = [
|
||||
'now' => time(),
|
||||
'userid' => $USER->id,
|
||||
'factor' => $this->factor,
|
||||
];
|
||||
|
||||
if ($checksession) {
|
||||
$sql .= ' AND sessionid = :sessionid';
|
||||
$params['sessionid'] = $this->sessionid;
|
||||
}
|
||||
|
||||
if ($DB->record_exists_sql($sql, $params)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes any user secrets hanging around in the database.
|
||||
*
|
||||
* @param int $userid the userid to cleanup temp secrets for.
|
||||
* @return void
|
||||
*/
|
||||
public function cleanup_temp_secrets($userid = null) {
|
||||
global $DB, $USER;
|
||||
// Session records are autocleaned up.
|
||||
// Only DB cleanup required.
|
||||
|
||||
$userid = $userid ?? $USER->id;
|
||||
$sql = 'DELETE FROM {tool_mfa_secrets}
|
||||
WHERE userid = :userid
|
||||
AND factor = :factor';
|
||||
|
||||
$DB->execute($sql, ['userid' => $userid, 'factor' => $this->factor]);
|
||||
}
|
||||
}
|
867
admin/tool/mfa/classes/manager.php
Normal file
867
admin/tool/mfa/classes/manager.php
Normal file
|
@ -0,0 +1,867 @@
|
|||
<?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 tool_mfa;
|
||||
|
||||
/**
|
||||
* MFA management class.
|
||||
*
|
||||
* @package 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 manager {
|
||||
|
||||
/** @var int */
|
||||
const REDIRECT = 1;
|
||||
|
||||
/** @var int */
|
||||
const NO_REDIRECT = 0;
|
||||
|
||||
/** @var int */
|
||||
const REDIRECT_EXCEPTION = -1;
|
||||
|
||||
/** @var int */
|
||||
const REDIR_LOOP_THRESHOLD = 5;
|
||||
|
||||
/**
|
||||
* Displays a debug table with current factor information.
|
||||
*/
|
||||
public static function display_debug_notification() {
|
||||
global $OUTPUT, $PAGE;
|
||||
|
||||
if (!get_config('tool_mfa', 'debugmode')) {
|
||||
return;
|
||||
}
|
||||
$html = $OUTPUT->heading(get_string('debugmode:heading', 'tool_mfa'), 3);
|
||||
|
||||
$table = new \html_table();
|
||||
$table->head = [
|
||||
get_string('weight', 'tool_mfa'),
|
||||
get_string('factor', 'tool_mfa'),
|
||||
get_string('setup', 'tool_mfa'),
|
||||
get_string('achievedweight', 'tool_mfa'),
|
||||
get_string('status'),
|
||||
];
|
||||
$table->attributes['class'] = 'admintable generaltable table table-bordered';
|
||||
$table->colclasses = [
|
||||
'text-right',
|
||||
'',
|
||||
'',
|
||||
'text-right',
|
||||
'text-center',
|
||||
];
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$userfactors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$runningtotal = 0;
|
||||
$weighttoggle = false;
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
$namespace = 'factor_'.$factor->name;
|
||||
$name = get_string('pluginname', $namespace);
|
||||
|
||||
// If factor is unknown, pending from here.
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_UNKNOWN) {
|
||||
$weighttoggle = true;
|
||||
}
|
||||
|
||||
// Stop adding weight if 100 achieved.
|
||||
if (!$weighttoggle) {
|
||||
$achieved = $factor->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS ? $factor->get_weight() : 0;
|
||||
$achieved = '+'.$achieved;
|
||||
$runningtotal += $achieved;
|
||||
} else {
|
||||
$achieved = '';
|
||||
}
|
||||
|
||||
// Setup.
|
||||
if ($factor->has_setup()) {
|
||||
$found = false;
|
||||
foreach ($userfactors as $userfactor) {
|
||||
if ($userfactor->name == $factor->name) {
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
$setup = $found ? get_string('yes') : get_string('no');
|
||||
} else {
|
||||
$setup = get_string('na', 'tool_mfa');
|
||||
}
|
||||
|
||||
// Status.
|
||||
$OUTPUT = $PAGE->get_renderer('tool_mfa');
|
||||
// If toggle has been flipped, fall to default pending badge.
|
||||
if ($weighttoggle) {
|
||||
$state = $OUTPUT->get_state_badge('');
|
||||
} else {
|
||||
$state = $OUTPUT->get_state_badge($factor->get_state());
|
||||
}
|
||||
|
||||
$table->data[] = [
|
||||
$factor->get_weight(),
|
||||
$name,
|
||||
$setup,
|
||||
$achieved,
|
||||
$state,
|
||||
];
|
||||
|
||||
// If we just hit 100, flip toggle.
|
||||
if ($runningtotal >= 100) {
|
||||
$weighttoggle = true;
|
||||
}
|
||||
}
|
||||
|
||||
$finalstate = self::get_status();
|
||||
$table->data[] = [
|
||||
'',
|
||||
'',
|
||||
'<b>' . get_string('overall', 'tool_mfa') . '</b>',
|
||||
self::get_cumulative_weight(),
|
||||
$OUTPUT->get_state_badge($finalstate),
|
||||
];
|
||||
|
||||
$html .= \html_writer::table($table);
|
||||
echo $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total weight from all factors currently enabled for user.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function get_total_weight() {
|
||||
$totalweight = 0;
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
$totalweight += $factor->get_weight();
|
||||
}
|
||||
}
|
||||
return $totalweight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that provided factorid exists and belongs to current user.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @param object $user
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function is_factorid_valid($factorid, $user) {
|
||||
global $DB;
|
||||
return $DB->record_exists('tool_mfa', ['userid' => $user->id, 'id' => $factorid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to display to the user that they cannot login, then log them out.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function cannot_login() {
|
||||
global $ME, $PAGE, $SESSION, $USER;
|
||||
|
||||
// Determine page URL without triggering warnings from $PAGE.
|
||||
if (!preg_match("~(\/admin\/tool\/mfa\/auth.php)~", $ME)) {
|
||||
// If URL isn't set, we need to redir to auth.php.
|
||||
// This ensures URL and required info is correctly set.
|
||||
// Then we arrive back here.
|
||||
redirect(new \moodle_url('/admin/tool/mfa/auth.php'));
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('tool_mfa');
|
||||
|
||||
echo $renderer->header();
|
||||
if (get_config('tool_mfa', 'debugmode')) {
|
||||
self::display_debug_notification();
|
||||
}
|
||||
echo $renderer->not_enough_factors();
|
||||
echo $renderer->footer();
|
||||
// Emit an event for failure, then logout.
|
||||
$event = \tool_mfa\event\user_failed_mfa::user_failed_mfa_event($USER);
|
||||
$event->trigger();
|
||||
|
||||
// We should set the redir flag, as this page is generated through auth.php.
|
||||
$SESSION->tool_mfa_has_been_redirected = true;
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function mfa_logout() {
|
||||
$authsequence = get_enabled_auth_plugins();
|
||||
foreach ($authsequence as $authname) {
|
||||
$authplugin = get_auth_plugin($authname);
|
||||
$authplugin->logoutpage_hook();
|
||||
}
|
||||
require_logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get the overall status of a user's authentication.
|
||||
*
|
||||
* @return mixed a STATE variable from plugininfo
|
||||
*/
|
||||
public static function get_status() {
|
||||
global $SESSION;
|
||||
|
||||
// Check for any instant fail states.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
foreach ($factors as $factor) {
|
||||
$factor->load_locked_state();
|
||||
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
$passcondition = ((isset($SESSION->tool_mfa_authenticated) && $SESSION->tool_mfa_authenticated) ||
|
||||
self::passed_enough_factors());
|
||||
|
||||
// Check next factor for instant fail (fallback).
|
||||
if (\tool_mfa\plugininfo\factor::get_next_user_factor()->get_state() ==
|
||||
\tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
// We need to handle a special case here, where someone reached the fallback,
|
||||
// If they were able to modify their state on the error page, such as passing iprange,
|
||||
// We must return pass.
|
||||
if ($passcondition) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
|
||||
return \tool_mfa\plugininfo\factor::STATE_FAIL;
|
||||
}
|
||||
|
||||
// Now check for general passing state. If found, ensure that session var is set.
|
||||
if ($passcondition) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
|
||||
// Else return neutral state.
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check the overall status of a users authentication,
|
||||
* and perform any required actions.
|
||||
*
|
||||
* @param bool $shouldreload whether the function should reload (used for auth.php).
|
||||
* @return void
|
||||
*/
|
||||
public static function resolve_mfa_status($shouldreload = false) {
|
||||
global $SESSION;
|
||||
|
||||
$state = self::get_status();
|
||||
if ($state == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
self::set_pass_state();
|
||||
// Check if user even had to reach auth page.
|
||||
if (isset($SESSION->tool_mfa_has_been_redirected)) {
|
||||
if (empty($SESSION->wantsurl)) {
|
||||
$wantsurl = '/';
|
||||
} else {
|
||||
$wantsurl = $SESSION->wantsurl;
|
||||
}
|
||||
unset($SESSION->wantsurl);
|
||||
redirect(new \moodle_url($wantsurl));
|
||||
} else {
|
||||
// Don't touch anything, let user be on their way.
|
||||
return;
|
||||
}
|
||||
} else if ($state == \tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
self::cannot_login();
|
||||
} else if ($shouldreload) {
|
||||
// Set a session variable to track whether user is where they want to be.
|
||||
$SESSION->tool_mfa_has_been_redirected = true;
|
||||
$authurl = new \moodle_url('/admin/tool/mfa/auth.php');
|
||||
redirect($authurl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether user has passed enough factors to be allowed in.
|
||||
*
|
||||
* @return bool true if user has passed enough factors.
|
||||
*/
|
||||
public static function passed_enough_factors() {
|
||||
|
||||
// Check for any instant fail states.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
self::mfa_logout();
|
||||
}
|
||||
}
|
||||
|
||||
$totalweight = self::get_cumulative_weight();
|
||||
if ($totalweight >= 100) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session variable for pass_state, if not already set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function set_pass_state() {
|
||||
global $DB, $SESSION, $USER;
|
||||
if (!isset($SESSION->tool_mfa_authenticated)) {
|
||||
$SESSION->tool_mfa_authenticated = true;
|
||||
$event = \tool_mfa\event\user_passed_mfa::user_passed_mfa_event($USER);
|
||||
$event->trigger();
|
||||
|
||||
// Add/update record in DB for users last mfa auth.
|
||||
self::update_pass_time();
|
||||
|
||||
// Unset session vars during mfa auth.
|
||||
unset($SESSION->mfa_redir_referer);
|
||||
unset($SESSION->mfa_redir_count);
|
||||
|
||||
// Unset user preferences during mfa auth.
|
||||
unset_user_preference('mfa_sleep_duration', $USER);
|
||||
|
||||
try {
|
||||
// Clear locked user factors, they may now reauth with anything.
|
||||
@$DB->set_field('tool_mfa', 'lockcounter', 0, ['userid' => $USER->id]);
|
||||
// @codingStandardsIgnoreStart
|
||||
} catch (\Exception $e) {
|
||||
// This occurs when upgrade.php hasn't been run. Nothing to do here.
|
||||
// Coding standards ignored, they break on empty catches.
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
// Fire post pass state factor actions.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
foreach ($factors as $factor) {
|
||||
$factor->post_pass_state();
|
||||
// Also set the states for this session to neutral if they were locked.
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_LOCKED) {
|
||||
$factor->set_state(\tool_mfa\plugininfo\factor::STATE_NEUTRAL);
|
||||
}
|
||||
}
|
||||
|
||||
// Output notifications if any factors were reset for this user.
|
||||
$enabledfactors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
foreach ($enabledfactors as $factor) {
|
||||
$pref = 'tool_mfa_reset_' . $factor->name;
|
||||
$factorpref = get_user_preferences($pref, false);
|
||||
if ($factorpref) {
|
||||
$url = new \moodle_url('/admin/tool/mfa/user_preferences.php');
|
||||
$link = \html_writer::link($url, get_string('preferenceslink', 'tool_mfa'));
|
||||
$data = ['factor' => $factor->get_display_name(), 'url' => $link];
|
||||
\core\notification::warning(get_string('factorreset', 'tool_mfa', $data));
|
||||
unset_user_preference($pref);
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for a global reset.
|
||||
// TODO: Delete this in a ferw months, the reset all preference is no longer set.
|
||||
$allfactor = get_user_preferences('tool_mfa_reset_all', false);
|
||||
if ($allfactor) {
|
||||
$url = new \moodle_url('/admin/tool/mfa/user_preferences.php');
|
||||
$link = \html_writer::link($url, get_string('preferenceslink', 'tool_mfa'));
|
||||
\core\notification::warning(get_string('factorresetall', 'tool_mfa', $link));
|
||||
unset_user_preference('tool_mfa_reset_all');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts or updates user's last MFA pass time in DB.
|
||||
* This should only be called from set_pass_state.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function update_pass_time() {
|
||||
global $DB, $USER;
|
||||
|
||||
$exists = $DB->record_exists('tool_mfa_auth', ['userid' => $USER->id]);
|
||||
|
||||
if ($exists) {
|
||||
$DB->set_field('tool_mfa_auth', 'lastverified', time(), ['userid' => $USER->id]);
|
||||
} else {
|
||||
$DB->insert_record('tool_mfa_auth', ['userid' => $USER->id, 'lastverified' => time()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user should be redirected from the provided url.
|
||||
*
|
||||
* @param \moodle_url $url
|
||||
* @param bool $preventredirect
|
||||
* @return int
|
||||
*/
|
||||
public static function should_require_mfa($url, $preventredirect) {
|
||||
global $CFG, $USER, $SESSION;
|
||||
|
||||
// If no cookies then no session so cannot do MFA.
|
||||
// Unit testing based on defines is not viable.
|
||||
if (NO_MOODLE_COOKIES && !PHPUNIT_TEST) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
|
||||
// Remove all params before comparison.
|
||||
$url->remove_all_params();
|
||||
|
||||
// Checks for upgrades pending.
|
||||
if (is_siteadmin()) {
|
||||
// We should only allow an upgrade from the frontend to complete.
|
||||
// After that is completed, only the settings shouldn't redirect.
|
||||
// Everything else should be safe to enforce MFA.
|
||||
if (moodle_needs_upgrading()) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
// An upgrade isn't complete if there are settings that must be saved.
|
||||
$upgradesettings = new \moodle_url('/admin/upgradesettings.php');
|
||||
if ($url->compare($upgradesettings, URL_MATCH_BASE)) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
}
|
||||
|
||||
// Dont redirect logo images from pluginfile.php (for example: logo in header).
|
||||
$logourl = new \moodle_url('/pluginfile.php/1/core_admin/logocompact/');
|
||||
if ($url->compare($logourl)) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
|
||||
// Admin not setup.
|
||||
if (!empty($CFG->adminsetuppending)) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
|
||||
// Initial installation.
|
||||
// We get this for free from get_plugins_with_function.
|
||||
|
||||
// Upgrade check.
|
||||
// We get this for free from get_plugins_with_function.
|
||||
|
||||
// Honor prevent_redirect.
|
||||
if ($preventredirect) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
|
||||
// User not properly setup.
|
||||
if (user_not_fully_set_up($USER)) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
|
||||
// Enrolment.
|
||||
$enrol = new \moodle_url('/enrol/index.php');
|
||||
if ($enrol->compare($url, URL_MATCH_BASE)) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
|
||||
// Guest access.
|
||||
if (isguestuser()) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
|
||||
// Forced password changes.
|
||||
if (get_user_preferences('auth_forcepasswordchange')) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
|
||||
// Login as.
|
||||
if (\core\session\manager::is_loggedinas()) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
|
||||
// Site policy.
|
||||
if (isset($USER->policyagreed) && !$USER->policyagreed) {
|
||||
// Privacy classes may not exist in older Moodles/Totara.
|
||||
if (class_exists('\core_privacy\local\sitepolicy\manager')) {
|
||||
$manager = new \core_privacy\local\sitepolicy\manager();
|
||||
$policyurl = $manager->get_redirect_url(false);
|
||||
if (!empty($policyurl) && $url->compare($policyurl, URL_MATCH_BASE)) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WS/AJAX check.
|
||||
if (WS_SERVER || AJAX_SCRIPT) {
|
||||
if (isset($SESSION->mfa_pending) && !empty($SESSION->mfa_pending)) {
|
||||
// Allow AJAX and WS, but never from auth.php.
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
return self::REDIRECT_EXCEPTION;
|
||||
}
|
||||
|
||||
// Check factor defined safe urls.
|
||||
$factorurls = self::get_no_redirect_urls();
|
||||
foreach ($factorurls as $factorurl) {
|
||||
if ($factorurl->compare($url)) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
}
|
||||
|
||||
// Circular checks.
|
||||
$authurl = new \moodle_url('/admin/tool/mfa/auth.php');
|
||||
$authlocal = $authurl->out_as_local_url();
|
||||
if (isset($SESSION->mfa_redir_referer)
|
||||
&& $SESSION->mfa_redir_referer != $authlocal) {
|
||||
if ($SESSION->mfa_redir_referer == get_local_referer(true)) {
|
||||
// Possible redirect loop.
|
||||
if (!isset($SESSION->mfa_redir_count)) {
|
||||
$SESSION->mfa_redir_count = 1;
|
||||
} else {
|
||||
$SESSION->mfa_redir_count++;
|
||||
}
|
||||
if ($SESSION->mfa_redir_count > self::REDIR_LOOP_THRESHOLD) {
|
||||
return self::REDIRECT_EXCEPTION;
|
||||
}
|
||||
} else {
|
||||
// If not a match, reset counter.
|
||||
$SESSION->mfa_redir_count = 0;
|
||||
}
|
||||
}
|
||||
// Set referer after checks.
|
||||
$SESSION->mfa_redir_referer = get_local_referer(true);
|
||||
|
||||
// Don't redirect if already on auth.php.
|
||||
if ($url->compare($authurl, URL_MATCH_BASE)) {
|
||||
return self::NO_REDIRECT;
|
||||
}
|
||||
|
||||
return self::REDIRECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the redirect counter for infinite redirect loops. Called from auth.php when a valid load is resolved.
|
||||
*
|
||||
*/
|
||||
public static function clear_redirect_counter() {
|
||||
global $SESSION;
|
||||
|
||||
unset($SESSION->mfa_redir_referer);
|
||||
unset($SESSION->mfa_redir_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all defined factor urls that should not redirect.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_no_redirect_urls() {
|
||||
$factors = \tool_mfa\plugininfo\factor::get_factors();
|
||||
$urls = [
|
||||
new \moodle_url('/login/logout.php'),
|
||||
new \moodle_url('/admin/tool/mfa/guide.php'),
|
||||
];
|
||||
foreach ($factors as $factor) {
|
||||
$urls = array_merge($urls, $factor->get_no_redirect_urls());
|
||||
}
|
||||
|
||||
// Allow forced redirection exclusions.
|
||||
if ($exclusions = get_config('tool_mfa', 'redir_exclusions')) {
|
||||
foreach (explode("\n", $exclusions) as $exclusion) {
|
||||
$urls[] = new \moodle_url($exclusion);
|
||||
}
|
||||
}
|
||||
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleeps for an increasing period of time.
|
||||
*/
|
||||
public static function sleep_timer() {
|
||||
global $USER;
|
||||
|
||||
$duration = get_user_preferences('mfa_sleep_duration', null, $USER);
|
||||
if (!empty($duration)) {
|
||||
// Double current time.
|
||||
$duration *= 2;
|
||||
$duration = min(2, $duration);
|
||||
} else {
|
||||
// No duration set.
|
||||
$duration = 0.05;
|
||||
}
|
||||
set_user_preference('mfa_sleep_duration', $duration, $USER);
|
||||
sleep($duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* If MFA Plugin is ready check tool_mfa_authenticated USER property and
|
||||
* start MFA authentication if it's not set or false.
|
||||
*
|
||||
* @param mixed $courseorid
|
||||
* @param mixed $autologinguest
|
||||
* @param mixed $cm
|
||||
* @param mixed $setwantsurltome
|
||||
* @param mixed $preventredirect
|
||||
* @return void
|
||||
*/
|
||||
public static function require_auth($courseorid = null, $autologinguest = null, $cm = null,
|
||||
$setwantsurltome = null, $preventredirect = null) {
|
||||
global $PAGE, $SESSION, $FULLME;
|
||||
|
||||
// Guest user should never interact with MFA,
|
||||
// And $SESSION->tool_mfa_authenticated should never be set in a guest session.
|
||||
if (isguestuser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self::is_ready()) {
|
||||
// Set session var so if MFA becomes ready, you dont get locked from session.
|
||||
$SESSION->tool_mfa_authenticated = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($SESSION->tool_mfa_authenticated) || !$SESSION->tool_mfa_authenticated) {
|
||||
if ($PAGE->has_set_url()) {
|
||||
$cleanurl = $PAGE->url;
|
||||
} else {
|
||||
// Use $FULLME instead.
|
||||
$cleanurl = new \moodle_url($FULLME);
|
||||
}
|
||||
$authurl = new \moodle_url('/admin/tool/mfa/auth.php');
|
||||
|
||||
$redir = self::should_require_mfa($cleanurl, $preventredirect);
|
||||
|
||||
if ($redir == self::NO_REDIRECT && !$cleanurl->compare($authurl, URL_MATCH_BASE)) {
|
||||
// A non-MFA page that should take precedence.
|
||||
// This check is for any pages, such as site policy, that must occur before MFA.
|
||||
// This check allows AJAX and WS requests to fire on these pages without throwing an exception.
|
||||
$SESSION->mfa_pending = true;
|
||||
}
|
||||
|
||||
if ($redir == self::REDIRECT) {
|
||||
if (empty($SESSION->wantsurl)) {
|
||||
!empty($setwantsurltome)
|
||||
? $SESSION->wantsurl = qualified_me()
|
||||
: $SESSION->wantsurl = new \moodle_url('/');
|
||||
|
||||
$SESSION->tool_mfa_setwantsurl = true;
|
||||
}
|
||||
// Remove pending status.
|
||||
// We must now auth with MFA, now that pending statuses are resolved.
|
||||
unset($SESSION->mfa_pending);
|
||||
|
||||
// Call resolve_status to instantly pass if no redirect is required.
|
||||
self::resolve_mfa_status(true);
|
||||
} else if ($redir == self::REDIRECT_EXCEPTION) {
|
||||
if (!empty($SESSION->mfa_redir_referer)) {
|
||||
throw new \moodle_exception('redirecterrordetected', 'tool_mfa',
|
||||
$SESSION->mfa_redir_referer, $SESSION->mfa_redir_referer);
|
||||
} else {
|
||||
throw new \moodle_exception('redirecterrordetected', 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets config variable for given factor.
|
||||
*
|
||||
* @param array $data
|
||||
* @param string $factor
|
||||
*
|
||||
* @return bool true or exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function set_factor_config($data, $factor) {
|
||||
$factorconf = get_config($factor);
|
||||
foreach ($data as $key => $newvalue) {
|
||||
if (empty($factorconf->$key)) {
|
||||
add_to_config_log($key, null, $newvalue, $factor);
|
||||
set_config($key, $newvalue, $factor);
|
||||
} else if ($factorconf->$key != $newvalue) {
|
||||
add_to_config_log($key, $factorconf->$key, $newvalue, $factor);
|
||||
set_config($key, $newvalue, $factor);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if MFA Plugin is enabled and has enabled factor.
|
||||
* If plugin is disabled or there is no enabled factors,
|
||||
* it means there is nothing to do from user side.
|
||||
* Thus, login flow shouldn't be extended with MFA.
|
||||
*
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function is_ready() {
|
||||
global $CFG, $USER;
|
||||
|
||||
if (!empty($CFG->upgraderunning)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pluginenabled = get_config('tool_mfa', 'enabled');
|
||||
if (empty($pluginenabled)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if user can interact with MFA.
|
||||
$usercontext = \context_user::instance($USER->id);
|
||||
if (!has_capability('tool/mfa:mfaaccess', $usercontext)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$enabledfactors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
if (count($enabledfactors) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs factor actions for given factor.
|
||||
* Change factor order and enable/disable.
|
||||
*
|
||||
* @param string $factorname
|
||||
* @param string $action
|
||||
*
|
||||
* @return void
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function do_factor_action($factorname, $action) {
|
||||
$order = explode(',', get_config('tool_mfa', 'factor_order'));
|
||||
$key = array_search($factorname, $order);
|
||||
|
||||
switch ($action) {
|
||||
case 'up':
|
||||
if ($key >= 1) {
|
||||
$fsave = $order[$key];
|
||||
$order[$key] = $order[$key - 1];
|
||||
$order[$key - 1] = $fsave;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'down':
|
||||
if ($key < (count($order) - 1)) {
|
||||
$fsave = $order[$key];
|
||||
$order[$key] = $order[$key + 1];
|
||||
$order[$key + 1] = $fsave;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'enable':
|
||||
if (!$key) {
|
||||
$order[] = $factorname;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'disable':
|
||||
if ($key) {
|
||||
unset($order[$key]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
self::set_factor_config(['factor_order' => implode(',', $order)], 'tool_mfa');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a factor that can make a user pass can be setup.
|
||||
* It checks if a user will always pass regardless,
|
||||
* then checks if there are factors that can be setup to let a user pass.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function possible_factor_setup() {
|
||||
global $USER;
|
||||
|
||||
// Get all active factors.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
|
||||
// Check if there are enough factors that a user can ONLY pass, if so, don't display the menu.
|
||||
$weight = 0;
|
||||
foreach ($factors as $factor) {
|
||||
$states = $factor->possible_states($USER);
|
||||
if (count($states) == 1 && reset($states) == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
$weight += $factor->get_weight();
|
||||
if ($weight >= 100) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now if there is a factor that can be setup, that may return a pass state for the user, display menu.
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->has_setup()) {
|
||||
if (in_array(\tool_mfa\plugininfo\factor::STATE_PASS, $factor->possible_states($USER))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current user weight, up until first unknown factor.
|
||||
*/
|
||||
public static function get_cumulative_weight() {
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$totalweight = 0;
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
$totalweight += $factor->get_weight();
|
||||
// If over 100, break. Dont care about >100.
|
||||
if ($totalweight >= 100) {
|
||||
break;
|
||||
}
|
||||
} else if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_UNKNOWN) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $totalweight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the factor was actually used in the login process.
|
||||
*
|
||||
* @param string $factorname the name of the factor.
|
||||
* @return bool true if factor is pending.
|
||||
*/
|
||||
public static function check_factor_pending($factorname) {
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
// Setup vars.
|
||||
$pending = [];
|
||||
$totalweight = 0;
|
||||
$weighttoggle = false;
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
// If toggle is reached, put in pending and continue.
|
||||
if ($weighttoggle) {
|
||||
$pending[] = $factor->name;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
$totalweight += $factor->get_weight();
|
||||
if ($totalweight >= 100) {
|
||||
$weighttoggle = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether factor falls into pending category.
|
||||
return in_array($factorname, $pending);
|
||||
}
|
||||
}
|
293
admin/tool/mfa/classes/plugininfo/factor.php
Normal file
293
admin/tool/mfa/classes/plugininfo/factor.php
Normal file
|
@ -0,0 +1,293 @@
|
|||
<?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 tool_mfa\plugininfo;
|
||||
|
||||
/**
|
||||
* Subplugin info class.
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class factor extends \core\plugininfo\base {
|
||||
|
||||
/** @var string */
|
||||
const STATE_UNKNOWN = 'unknown';
|
||||
|
||||
/** @var string */
|
||||
const STATE_PASS = 'pass';
|
||||
|
||||
/** @var string */
|
||||
const STATE_FAIL = 'fail';
|
||||
|
||||
/** @var string */
|
||||
const STATE_NEUTRAL = 'neutral';
|
||||
|
||||
/** @var string Locked state is identical to neutral, but can't be overridden */
|
||||
const STATE_LOCKED = 'locked';
|
||||
|
||||
/**
|
||||
* Finds all MFA factors.
|
||||
*
|
||||
* @return array of factor objects.
|
||||
*/
|
||||
public static function get_factors() {
|
||||
$return = [];
|
||||
$factors = \core_plugin_manager::instance()->get_plugins_of_type('factor');
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
$classname = '\\factor_'.$factor->name.'\\factor';
|
||||
if (class_exists($classname)) {
|
||||
$return[] = new $classname($factor->name);
|
||||
}
|
||||
}
|
||||
return self::sort_factors_by_order($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts factors by configured order.
|
||||
*
|
||||
* @param array $unsorted of factor objects
|
||||
* @return array of factor objects
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function sort_factors_by_order($unsorted) {
|
||||
$sorted = [];
|
||||
$orderarray = explode(',', get_config('tool_mfa', 'factor_order'));
|
||||
|
||||
foreach ($orderarray as $order => $factorname) {
|
||||
foreach ($unsorted as $key => $factor) {
|
||||
if ($factor->name == $factorname) {
|
||||
$sorted[] = $factor;
|
||||
unset($unsorted[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sorted = array_merge($sorted, $unsorted);
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds factor by its name.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed factor object or false if factor not found.
|
||||
*/
|
||||
public static function get_factor($name) {
|
||||
$factors = \core_plugin_manager::instance()->get_plugins_of_type('factor');
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
if ($name == $factor->name) {
|
||||
$classname = '\\factor_'.$factor->name.'\\factor';
|
||||
if (class_exists($classname)) {
|
||||
return new $classname($factor->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all enabled factors.
|
||||
*
|
||||
* @return array of factor objects
|
||||
*/
|
||||
public static function get_enabled_factors() {
|
||||
$return = [];
|
||||
$factors = self::get_factors();
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->is_enabled()) {
|
||||
$return[] = $factor;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds active factors for current user.
|
||||
*
|
||||
* @return array of factor objects.
|
||||
*/
|
||||
public static function get_active_user_factor_types() {
|
||||
global $USER;
|
||||
$return = [];
|
||||
$factors = self::get_enabled_factors();
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
$userfactors = $factor->get_active_user_factors($USER);
|
||||
if (count($userfactors) > 0) {
|
||||
$return[] = $factor;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds active factors for given user.
|
||||
*
|
||||
* @param stdClass $user the user to get types for.
|
||||
* @return array of factor objects.
|
||||
*/
|
||||
public static function get_active_other_user_factor_types($user) {
|
||||
$return = [];
|
||||
$factors = self::get_enabled_factors();
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
$userfactors = $factor->get_active_user_factors($user);
|
||||
if (count($userfactors) > 0) {
|
||||
$return[] = $factor;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next factor to authenticate user.
|
||||
*
|
||||
* @return mixed factor object the next factor to be authenticated or false.
|
||||
*/
|
||||
public static function get_next_user_factor() {
|
||||
$factors = self::get_active_user_factor_types();
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
if (!$factor->has_input()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($factor->get_state() == self::STATE_UNKNOWN) {
|
||||
return $factor;
|
||||
}
|
||||
}
|
||||
|
||||
return new \tool_mfa\local\factor\fallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of available actions with factor.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_factor_actions() {
|
||||
$actions = [];
|
||||
$actions[] = 'setup';
|
||||
$actions[] = 'revoke';
|
||||
$actions[] = 'enable';
|
||||
$actions[] = 'revoke';
|
||||
$actions[] = 'disable';
|
||||
$actions[] = 'up';
|
||||
$actions[] = 'down';
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information about plugin availability
|
||||
*
|
||||
* True means that the plugin is enabled. False means that the plugin is
|
||||
* disabled. Null means that the information is not available, or the
|
||||
* plugin does not support configurable availability or the availability
|
||||
* can not be changed.
|
||||
*
|
||||
* @return null|bool
|
||||
*/
|
||||
public function is_enabled() {
|
||||
if (!$this->rootdir) {
|
||||
// Plugin missing.
|
||||
return false;
|
||||
}
|
||||
|
||||
$factor = $this->get_factor($this->name);
|
||||
|
||||
if ($factor) {
|
||||
return $factor->is_enabled();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns section name for settings.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_settings_section_name() {
|
||||
return $this->type . '_' . $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads factor settings to the settings tree
|
||||
*
|
||||
* This function usually includes settings.php file in plugins folder.
|
||||
* Alternatively it can create a link to some settings page (instance of admin_externalpage)
|
||||
*
|
||||
* @param \part_of_admin_tree $adminroot
|
||||
* @param string $parentnodename
|
||||
* @param bool $hassiteconfig whether the current user has moodle/site:config capability
|
||||
*/
|
||||
public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
|
||||
|
||||
if (!$this->is_installed_and_upgraded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$hassiteconfig || !file_exists($this->full_path('settings.php'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$section = $this->get_settings_section_name();
|
||||
|
||||
$settings = new \admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false);
|
||||
|
||||
if ($adminroot->fulltree) {
|
||||
include($this->full_path('settings.php'));
|
||||
}
|
||||
|
||||
$adminroot->add($parentnodename, $settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that given factor exists.
|
||||
*
|
||||
* @param string $factorname
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function factor_exists($factorname) {
|
||||
$factor = self::get_factor($factorname);
|
||||
return !$factor ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns instance of any factor from the factorid.
|
||||
*
|
||||
* @param int $factorid
|
||||
*
|
||||
* @return stdClass|null Factor instance or nothing if not found.
|
||||
*/
|
||||
public static function get_instance_from_id($factorid) {
|
||||
global $DB;
|
||||
return $DB->get_record('tool_mfa', ['id' => $factorid]);
|
||||
}
|
||||
}
|
212
admin/tool/mfa/classes/privacy/provider.php
Normal file
212
admin/tool/mfa/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,212 @@
|
|||
<?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/>.
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_mfa\privacy;
|
||||
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\approved_userlist;
|
||||
use core_privacy\local\request\writer;
|
||||
use core_privacy\local\request\userlist;
|
||||
|
||||
/**
|
||||
* Privacy provider
|
||||
*
|
||||
* @package tool_mfa
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\core_privacy\local\request\data_provider {
|
||||
|
||||
/**
|
||||
* Returns metadata about this plugin's privacy policy.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection): collection {
|
||||
$collection->add_database_table(
|
||||
'tool_mfa',
|
||||
[
|
||||
'id' => 'privacy:metadata:tool_mfa:id',
|
||||
'userid' => 'privacy:metadata:tool_mfa:userid',
|
||||
'factor' => 'privacy:metadata:tool_mfa:factor',
|
||||
'secret' => 'privacy:metadata:tool_mfa:secret',
|
||||
'label' => 'privacy:metadata:tool_mfa:label',
|
||||
'timecreated' => 'privacy:metadata:tool_mfa:timecreated',
|
||||
'createdfromip' => 'privacy:metadata:tool_mfa:createdfromip',
|
||||
'timemodified' => 'privacy:metadata:tool_mfa:timemodified',
|
||||
'lastverified' => 'privacy:metadata:tool_mfa:lastverified',
|
||||
],
|
||||
'privacy:metadata:tool_mfa'
|
||||
);
|
||||
|
||||
$collection->add_database_table(
|
||||
'tool_mfa_secrets',
|
||||
[
|
||||
'userid' => 'privacy:metadata:tool_mfa_secrets:userid',
|
||||
'factor' => 'privacy:metadata:tool_mfa_secrets:factor',
|
||||
'secret' => 'privacy:metadata:tool_mfa_secrets:secret',
|
||||
'sessionid' => 'privacy:metadata:tool_mfa_secrets:sessionid',
|
||||
],
|
||||
'privacy:metadata:tool_mfa_secrets'
|
||||
);
|
||||
|
||||
$collection->add_database_table(
|
||||
'tool_mfa_auth',
|
||||
[
|
||||
'userid' => 'privacy:metadata:tool_mfa_auth:userid',
|
||||
'lastverified' => 'privacy:metadata:tool_mfa_auth:lastverified',
|
||||
],
|
||||
'privacy:metadata:tool_mfa_auth'
|
||||
);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the given user.
|
||||
*
|
||||
* @param int $userid the userid to search.
|
||||
* @return contextlist the contexts in which data is contained.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid): contextlist {
|
||||
$contextlist = new \core_privacy\local\request\contextlist();
|
||||
$contextlist->add_user_context($userid);
|
||||
$contextlist->add_system_context();
|
||||
return $contextlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of users who have data with a context. Secrets context is a subset of this table.
|
||||
*
|
||||
* @param userlist $userlist the userlist containing users who have data in this context.
|
||||
*/
|
||||
public static function get_users_in_context(userlist $userlist) {
|
||||
$context = $userlist->get_context();
|
||||
// If current context is system, all users are contained within, get all users.
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
$sql = "
|
||||
SELECT *
|
||||
FROM {tool_mfa}";
|
||||
$userlist->add_from_sql('userid', $sql, []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports all data stored in provided contexts for user. Secrets should not be exported as they are transient.
|
||||
*
|
||||
* @param approved_contextlist $contextlist the list of contexts to export for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
$userid = $contextlist->get_user()->id;
|
||||
foreach ($contextlist as $context) {
|
||||
// If not in system context, exit loop.
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
$parentclass = [];
|
||||
|
||||
// Get records for user ID.
|
||||
$rows = $DB->get_records('tool_mfa', ['userid' => $userid]);
|
||||
|
||||
if (count($rows) > 0) {
|
||||
$i = 0;
|
||||
foreach ($rows as $row) {
|
||||
$parentclass[$i]['userid'] = $row->userid;
|
||||
$timecreated = \core_privacy\local\request\transform::datetime($row->timecreated);
|
||||
$parentclass[$i]['factor'] = $row->factor;
|
||||
$parentclass[$i]['timecreated'] = $timecreated;
|
||||
$parentclass[$i]['createdfromip'] = $row->createdfromip;
|
||||
$timemodified = \core_privacy\local\request\transform::datetime($row->timemodified);
|
||||
$parentclass[$i]['timemodified'] = $timemodified;
|
||||
$lastverified = \core_privacy\local\request\transform::datetime($row->lastverified);
|
||||
$parentclass[$i]['lastverified'] = $lastverified;
|
||||
$parentclass[$i]['revoked'] = $row->revoked;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Also get lastverified auth time for user, and add.
|
||||
$lastverifiedauth = $DB->get_field('tool_mfa_auth', 'lastverified', ['userid' => $userid]);
|
||||
if (!empty($lastverifiedauth)) {
|
||||
$lastverifiedauth = \core_privacy\local\request\transform::datetime($lastverifiedauth);
|
||||
$parentclass['lastverifiedauth'] = $lastverifiedauth;
|
||||
}
|
||||
|
||||
writer::with_context($context)->export_data(
|
||||
[get_string('privacy:metadata:tool_mfa', 'tool_mfa')],
|
||||
(object) $parentclass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes data for all users in context.
|
||||
*
|
||||
* @param context $context The context to delete for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(\context $context) {
|
||||
global $DB;
|
||||
// All data contained in system context.
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
$DB->delete_records('tool_mfa', []);
|
||||
$DB->delete_records('tool_mfa_secrets', []);
|
||||
$DB->delete_records('tool_mfa_auth', []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all data in all provided contexts for user.
|
||||
*
|
||||
* @param approved_contextlist $contextlist the list of contexts to delete for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
$userid = $contextlist->get_user()->id;
|
||||
foreach ($contextlist as $context) {
|
||||
// If not in system context, skip context.
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
$DB->delete_records('tool_mfa', ['userid' => $userid]);
|
||||
$DB->delete_records('tool_mfa_secrets', ['userid' => $userid]);
|
||||
$DB->delete_records('tool_mfa_auth', ['userid' => $userid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a userlist, deletes all data in all provided contexts for the users
|
||||
*
|
||||
* @param approved_userlist $userlist the list of users to delete data for
|
||||
*/
|
||||
public static function delete_data_for_users(approved_userlist $userlist) {
|
||||
$users = $userlist->get_users();
|
||||
foreach ($users as $user) {
|
||||
// Create contextlist.
|
||||
$contextlist = new approved_contextlist($user, 'tool_mfa', [CONTEXT_SYSTEM]);
|
||||
// Call delete data.
|
||||
self::delete_data_for_user($contextlist);
|
||||
}
|
||||
}
|
||||
}
|
33
admin/tool/mfa/db/access.php
Normal file
33
admin/tool/mfa/db/access.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Definition of MFA sub-plugins (factors).
|
||||
*
|
||||
* @package 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;
|
||||
|
||||
$capabilities = [
|
||||
'tool/mfa:mfaaccess' => [
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'archetypes' => ['user' => CAP_ALLOW],
|
||||
],
|
||||
];
|
62
admin/tool/mfa/db/install.xml
Normal file
62
admin/tool/mfa/db/install.xml
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="admin/tool/mfa/db" VERSION="20210219" COMMENT="XMLDB file for Moodle admin/tool/mfa"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
<TABLES>
|
||||
<TABLE NAME="tool_mfa" COMMENT="Table to store factor configurations for users">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="User ID"/>
|
||||
<FIELD NAME="factor" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false" COMMENT="Factor type"/>
|
||||
<FIELD NAME="secret" TYPE="char" LENGTH="1333" NOTNULL="false" SEQUENCE="false" COMMENT="Any secret data for factor"/>
|
||||
<FIELD NAME="label" TYPE="char" LENGTH="1333" NOTNULL="false" SEQUENCE="false" COMMENT="label for factor instance, eg device or email."/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="15" NOTNULL="false" SEQUENCE="false" COMMENT="Time the factor instance was setup"/>
|
||||
<FIELD NAME="createdfromip" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="IP that the factor was setup from"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="15" NOTNULL="false" SEQUENCE="false" COMMENT="Time factor was last modified."/>
|
||||
<FIELD NAME="lastverified" TYPE="int" LENGTH="15" NOTNULL="false" SEQUENCE="false" COMMENT="Time user was last verified with this factor."/>
|
||||
<FIELD NAME="revoked" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="lockcounter" TYPE="int" LENGTH="5" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Counter of failed attempts"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="userid" UNIQUE="false" FIELDS="userid"/>
|
||||
<INDEX NAME="factor" UNIQUE="false" FIELDS="factor"/>
|
||||
<INDEX NAME="lockcounter" UNIQUE="false" FIELDS="userid, factor, lockcounter"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_mfa_secrets" COMMENT="Table to store factor secrets">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="factor" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="secret" TYPE="char" LENGTH="1333" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="15" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="expiry" TYPE="int" LENGTH="15" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="revoked" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="sessionid" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="factor" UNIQUE="false" FIELDS="factor"/>
|
||||
<INDEX NAME="expiry" UNIQUE="false" FIELDS="expiry"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_mfa_auth" COMMENT="Stores the last time a successful MFA auth was registered for a userid">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="User id"/>
|
||||
<FIELD NAME="lastverified" TYPE="int" LENGTH="15" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Timestamp of last MFA verification."/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id" COMMENT="Link to user table"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
5
admin/tool/mfa/db/subplugins.json
Normal file
5
admin/tool/mfa/db/subplugins.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"plugintypes": {
|
||||
"factor": "admin\/tool\/mfa\/factor"
|
||||
}
|
||||
}
|
28
admin/tool/mfa/db/subplugins.php
Normal file
28
admin/tool/mfa/db/subplugins.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Definition of MFA sub-plugins (factors).
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$subplugins = (array) json_decode(file_get_contents($CFG->dirroot."/admin/tool/mfa/db/subplugins.json"))->plugintypes;
|
136
admin/tool/mfa/db/upgrade.php
Normal file
136
admin/tool/mfa/db/upgrade.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* MFA upgrade library.
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @copyright 2020 Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function to upgrade tool_mfa.
|
||||
*
|
||||
* @param int $oldversion the version we are upgrading from
|
||||
* @return bool result
|
||||
*/
|
||||
function xmldb_tool_mfa_upgrade($oldversion) {
|
||||
global $DB;
|
||||
|
||||
$dbman = $DB->get_manager();
|
||||
|
||||
if ($oldversion < 2020050700) {
|
||||
// Define field lockcounter to be added to tool_mfa.
|
||||
$table = new xmldb_table('tool_mfa');
|
||||
$field = new xmldb_field('lockcounter', XMLDB_TYPE_INTEGER, '5', null, XMLDB_NOTNULL, null, '0', 'revoked');
|
||||
|
||||
// Conditionally launch add field lockcounter.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// MFA savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2020050700, 'tool', 'mfa');
|
||||
}
|
||||
|
||||
if ($oldversion < 2020051900) {
|
||||
// Define index userid (not unique) to be added to tool_mfa.
|
||||
$table = new xmldb_table('tool_mfa');
|
||||
$index = new xmldb_index('userid', XMLDB_INDEX_NOTUNIQUE, ['userid']);
|
||||
|
||||
// Conditionally launch add index userid.
|
||||
if (!$dbman->index_exists($table, $index)) {
|
||||
$dbman->add_index($table, $index);
|
||||
}
|
||||
|
||||
// Define index factor (not unique) to be added to tool_mfa.
|
||||
$table = new xmldb_table('tool_mfa');
|
||||
$index = new xmldb_index('factor', XMLDB_INDEX_NOTUNIQUE, ['factor']);
|
||||
|
||||
// Conditionally launch add index factor.
|
||||
if (!$dbman->index_exists($table, $index)) {
|
||||
$dbman->add_index($table, $index);
|
||||
}
|
||||
|
||||
// Define index lockcounter (not unique) to be added to tool_mfa.
|
||||
$table = new xmldb_table('tool_mfa');
|
||||
$index = new xmldb_index('lockcounter', XMLDB_INDEX_NOTUNIQUE, ['userid', 'factor', 'lockcounter']);
|
||||
|
||||
// Conditionally launch add index lockcounter.
|
||||
if (!$dbman->index_exists($table, $index)) {
|
||||
$dbman->add_index($table, $index);
|
||||
}
|
||||
|
||||
// Mfa savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2020051900, 'tool', 'mfa');
|
||||
}
|
||||
|
||||
if ($oldversion < 2020090300) {
|
||||
// Define table tool_mfa_secrets to be created.
|
||||
$table = new xmldb_table('tool_mfa_secrets');
|
||||
|
||||
// Adding fields to table tool_mfa_secrets.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('factor', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('secret', XMLDB_TYPE_CHAR, '1333', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '15', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('expiry', XMLDB_TYPE_INTEGER, '15', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('revoked', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('sessionid', XMLDB_TYPE_CHAR, '100', null, null, null, null);
|
||||
|
||||
// Adding keys to table tool_mfa_secrets.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
|
||||
|
||||
// Adding indexes to table tool_mfa_secrets.
|
||||
$table->add_index('factor', XMLDB_INDEX_NOTUNIQUE, ['factor']);
|
||||
$table->add_index('expiry', XMLDB_INDEX_NOTUNIQUE, ['expiry']);
|
||||
|
||||
// Conditionally launch create table for tool_mfa_secrets.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Mfa savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2020090300, 'tool', 'mfa');
|
||||
}
|
||||
|
||||
if ($oldversion < 2021021900) {
|
||||
// Define table tool_mfa_auth to be created.
|
||||
$table = new xmldb_table('tool_mfa_auth');
|
||||
|
||||
// Adding fields to table tool_mfa_auth.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('lastverified', XMLDB_TYPE_INTEGER, '15', null, XMLDB_NOTNULL, null, '0');
|
||||
|
||||
// Adding keys to table tool_mfa_auth.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
|
||||
|
||||
// Conditionally launch create table for tool_mfa_auth.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Mfa savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2021021900, 'tool', 'mfa');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
0
admin/tool/mfa/example1
Normal file
0
admin/tool/mfa/example1
Normal file
0
admin/tool/mfa/example2
Normal file
0
admin/tool/mfa/example2
Normal file
92
admin/tool/mfa/factor/admin/classes/factor.php
Normal file
92
admin/tool/mfa/factor/admin/classes/factor.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?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_admin;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* Admin factor class.
|
||||
*
|
||||
* @package factor_admin
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* Admin Factor implementation.
|
||||
* Factor is a singleton, can only be one instance.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
$records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
|
||||
if (!empty($records)) {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Null records returned, build new record.
|
||||
$record = [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'timecreated' => time(),
|
||||
'createdfromip' => $user->lastip,
|
||||
'timemodified' => time(),
|
||||
'revoked' => 0,
|
||||
];
|
||||
$record['id'] = $DB->insert_record('tool_mfa', $record, true);
|
||||
return [(object) $record];
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Factor implementation.
|
||||
* Factor does not have input.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_input() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Factor implementation.
|
||||
* State check is performed here, as there is no form to do it in.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
if (is_siteadmin()) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Factor implementation.
|
||||
* The state can never be set. Always return true.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
return true;
|
||||
}
|
||||
}
|
42
admin/tool/mfa/factor/admin/classes/privacy/provider.php
Normal file
42
admin/tool/mfa/factor/admin/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_admin\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_admin
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements null_provider {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
30
admin/tool/mfa/factor/admin/lang/en/factor_admin.php
Normal file
30
admin/tool/mfa/factor/admin/lang/en/factor_admin.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?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_admin
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['info'] = 'This factor allows for NOT being an administrator to count as a factor. Its intended use is to ensure administators require tighter security, so regular users get the weight for free, while admins must use other factors.';
|
||||
$string['pluginname'] = 'Non-administrator';
|
||||
$string['privacy:metadata'] = 'The Admin factor plugin does not store any personal data';
|
||||
$string['settings:weight_help'] = 'Weight is given to regular users for this factor, so admins must have more factors than a regular user to pass.';
|
||||
$string['summarycondition'] = 'is not an admin';
|
38
admin/tool/mfa/factor/admin/settings.php
Normal file
38
admin/tool/mfa/factor/admin/settings.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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 factor Settings.
|
||||
*
|
||||
* @package factor_admin
|
||||
* @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();
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_admin/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('admin', get_config('factor_admin', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_admin/weight',
|
||||
new lang_string('settings:weight', 'tool_mfa'),
|
||||
new lang_string('settings:weight_help', 'factor_admin'), 100, PARAM_INT));
|
34
admin/tool/mfa/factor/admin/version.php
Normal file
34
admin/tool/mfa/factor/admin/version.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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_admin
|
||||
* @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 = 2019102400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
|
||||
$plugin->component = 'factor_admin';
|
||||
$plugin->release = 'v0.1';
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->dependencies = ['tool_mfa' => 2019102400];
|
114
admin/tool/mfa/factor/auth/classes/factor.php
Normal file
114
admin/tool/mfa/factor/auth/classes/factor.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?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_auth;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* Auth factor class.
|
||||
*
|
||||
* @package factor_auth
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class factor extends object_factor_base {
|
||||
|
||||
/**
|
||||
* Auth Factor implementation.
|
||||
* Factor is a singleton, can only be one instance.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
$records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
|
||||
if (!empty($records)) {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Null records returned, build new record.
|
||||
$record = [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'timecreated' => time(),
|
||||
'createdfromip' => $user->lastip,
|
||||
'timemodified' => time(),
|
||||
'revoked' => 0,
|
||||
];
|
||||
$record['id'] = $DB->insert_record('tool_mfa', $record, true);
|
||||
return [(object) $record];
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth Factor implementation.
|
||||
* Factor does not have input.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_input() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth Factor implementation.
|
||||
* State check is performed here, as there is no form to do it in.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
global $USER;
|
||||
|
||||
$safetypes = get_config('factor_auth', 'goodauth');
|
||||
if (strlen($safetypes) != 0) {
|
||||
$safetypes = explode(',', $safetypes);
|
||||
|
||||
// Check all safetypes against user auth.
|
||||
if (in_array($USER->auth, $safetypes, true)) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
} else {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth Factor implementation.
|
||||
* The state can never be set. Always return true.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth factor implementation.
|
||||
* Return list of auth types that are safe.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_summary_condition() {
|
||||
$safetypes = get_config('factor_auth', 'goodauth');
|
||||
|
||||
return get_string('summarycondition', 'factor_'.$this->name, $safetypes);
|
||||
}
|
||||
}
|
42
admin/tool/mfa/factor/auth/classes/privacy/provider.php
Normal file
42
admin/tool/mfa/factor/auth/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_auth\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_auth
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements null_provider {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
50
admin/tool/mfa/factor/auth/db/upgrade.php
Normal file
50
admin/tool/mfa/factor/auth/db/upgrade.php
Normal 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/>.
|
||||
|
||||
/**
|
||||
* factor_auth upgrade library.
|
||||
*
|
||||
* @package factor_auth
|
||||
* @copyright 2021 Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Factor auth upgrade helper function
|
||||
*
|
||||
* @param int $oldversion
|
||||
*/
|
||||
function xmldb_factor_auth_upgrade($oldversion) {
|
||||
|
||||
if ($oldversion < 2021020500) {
|
||||
$authtypes = get_enabled_auth_plugins(true);
|
||||
// Upgrade goodauth config from number to name.
|
||||
$goodauth = explode(',', get_config('factor_auth', 'goodauth'));
|
||||
$newauths = [];
|
||||
foreach ($goodauth as $auth) {
|
||||
// Check if index exists before access. If not, ignore, settings were out of sync.
|
||||
if (array_key_exists($auth, $authtypes)) {
|
||||
$newauths[] = $authtypes[$auth];
|
||||
}
|
||||
}
|
||||
set_config('goodauth', implode(',', $newauths), 'factor_auth');
|
||||
|
||||
// MFA savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2021020500, 'factor', 'auth');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
31
admin/tool/mfa/factor/auth/lang/en/factor_auth.php
Normal file
31
admin/tool/mfa/factor/auth/lang/en/factor_auth.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?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_auth
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['info'] = 'Check the type of authentication used to login as an MFA factor.';
|
||||
$string['pluginname'] = 'Authentication type';
|
||||
$string['privacy:metadata'] = 'The Auth Factor plugin does not store any personal data';
|
||||
$string['settings:goodauth'] = 'Factor authentication types';
|
||||
$string['settings:goodauth_help'] = 'Select all authentication types to use as a factor for MFA. Any types not selected will not be treated as a FAIL in MFA.';
|
||||
$string['summarycondition'] = 'has an authentication type of {$a}';
|
49
admin/tool/mfa/factor/auth/settings.php
Normal file
49
admin/tool/mfa/factor/auth/settings.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?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_auth
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_auth/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('auth', get_config('factor_auth', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_auth/weight',
|
||||
new lang_string('settings:weight', 'tool_mfa'),
|
||||
new lang_string('settings:weight_help', 'tool_mfa'), 100, PARAM_INT));
|
||||
|
||||
$authtypes = get_enabled_auth_plugins(true);
|
||||
$authselect = [];
|
||||
foreach ($authtypes as $type) {
|
||||
$auth = get_auth_plugin($type);
|
||||
$authselect[$type] = $auth->get_title();
|
||||
}
|
||||
|
||||
$settings->add(new admin_setting_configmulticheckbox('factor_auth/goodauth',
|
||||
get_string('settings:goodauth', 'factor_auth'),
|
||||
get_string('settings:goodauth_help', 'factor_auth'), [], $authselect));
|
34
admin/tool/mfa/factor/auth/version.php
Normal file
34
admin/tool/mfa/factor/auth/version.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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_auth
|
||||
* @subpackage tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2021020500; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
|
||||
$plugin->component = 'factor_auth';
|
||||
$plugin->release = 2021020500;
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->dependencies = ['tool_mfa' => 2019102400];
|
110
admin/tool/mfa/factor/capability/classes/factor.php
Normal file
110
admin/tool/mfa/factor/capability/classes/factor.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?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_capability;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* User capability factor class.
|
||||
*
|
||||
* @package factor_capability
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* User capability implementation.
|
||||
* This factor is a singleton, return single instance.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
$records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
|
||||
if (!empty($records)) {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Null records returned, build new record.
|
||||
$record = [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'timecreated' => time(),
|
||||
'createdfromip' => $user->lastip,
|
||||
'timemodified' => time(),
|
||||
'revoked' => 0,
|
||||
];
|
||||
$record['id'] = $DB->insert_record('tool_mfa', $record, true);
|
||||
return [(object) $record];
|
||||
}
|
||||
|
||||
/**
|
||||
* User capability implementation.
|
||||
* Factor has no input
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_input() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* User capability implementation.
|
||||
* Checks whether user has the negative capability.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
global $USER;
|
||||
$adminpass = (bool) get_config('factor_capability', 'adminpasses');
|
||||
|
||||
// Do anything check is controlled from factor config.
|
||||
if (!has_capability('factor/capability:cannotpassfactor', \context_system::instance(), $USER, $adminpass)) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
} else {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User Capability implementation.
|
||||
* Cannot set state, return true.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* User capability implementation.
|
||||
* Possible states are either neutral or pass.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states($user) {
|
||||
return [
|
||||
\tool_mfa\plugininfo\factor::STATE_PASS,
|
||||
\tool_mfa\plugininfo\factor::STATE_NEUTRAL,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?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_capability\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_capability
|
||||
* @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 {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
33
admin/tool/mfa/factor/capability/db/access.php
Normal file
33
admin/tool/mfa/factor/capability/db/access.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* User capability factor access declaration.
|
||||
*
|
||||
* @package factor_capability
|
||||
* @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();
|
||||
|
||||
$capabilities = [
|
||||
'factor/capability:cannotpassfactor' => [
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
],
|
||||
];
|
|
@ -0,0 +1,33 @@
|
|||
<?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_capability
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['capability:cannotpassfactor'] = 'STOPS a role from passing the MFA user capability factor.';
|
||||
$string['pluginname'] = 'User capability';
|
||||
$string['privacy:metadata'] = 'The user capability factor plugin does not store any personal data';
|
||||
$string['settings:adminpasses'] = 'Site admins can pass this factor';
|
||||
$string['settings:adminpasses_help'] = 'By default admins pass all capability checks, including this one which uses \'factor/capability:cannotpassfactor\', which means they will fail this factor.
|
||||
If checked then all site admins will pass this factor if they do not have this capability from another role.
|
||||
If unchecked site admins will fail this factor.';
|
||||
$string['summarycondition'] = 'does NOT have the factor/capability:cannotpassfactor capability in any role including site administrator.';
|
43
admin/tool/mfa/factor/capability/settings.php
Normal file
43
admin/tool/mfa/factor/capability/settings.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?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_capability
|
||||
* @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();
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_capability/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('capability', get_config('factor_capability', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_capability/weight',
|
||||
new lang_string('settings:weight', 'tool_mfa'),
|
||||
new lang_string('settings:weight_help', 'tool_mfa'), 100, PARAM_INT));
|
||||
|
||||
// Admin passes bool logic is inverted due to negative capability check.
|
||||
$settings->add(new admin_setting_configcheckbox('factor_capability/adminpasses',
|
||||
new lang_string('settings:adminpasses', 'factor_capability'),
|
||||
new lang_string('settings:adminpasses_help', 'factor_capability'), 1, 0, 1));
|
34
admin/tool/mfa/factor/capability/version.php
Normal file
34
admin/tool/mfa/factor/capability/version.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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_capability
|
||||
* @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 = 2020071400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
|
||||
$plugin->component = 'factor_capability';
|
||||
$plugin->release = 'v0.1';
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->dependencies = ['tool_mfa' => 2019102400];
|
139
admin/tool/mfa/factor/cohort/classes/factor.php
Normal file
139
admin/tool/mfa/factor/cohort/classes/factor.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?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_cohort;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once(__DIR__ . '/../../../../../../cohort/lib.php');
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* cohort factor class.
|
||||
*
|
||||
* @package factor_cohort
|
||||
* @author Chris Pratt <tonyyeb@gmail.com>
|
||||
* @copyright Chris Pratt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class factor extends object_factor_base {
|
||||
|
||||
/**
|
||||
* cohort implementation.
|
||||
* This factor is a singleton, return single instance.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
$records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
if (!empty($records)) {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Null records returned, build new record.
|
||||
$record = [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'timecreated' => time(),
|
||||
'createdfromip' => $user->lastip,
|
||||
'timemodified' => time(),
|
||||
'revoked' => 0,
|
||||
];
|
||||
$record['id'] = $DB->insert_record('tool_mfa', $record, true);
|
||||
return [(object) $record];
|
||||
}
|
||||
|
||||
/**
|
||||
* cohort implementation.
|
||||
* Factor has no input
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_input() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* cohort implementation.
|
||||
* Checks whether the user has selected cohorts in any context.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
global $USER;
|
||||
$cohortstring = get_config('factor_cohort', 'cohorts');
|
||||
// Nothing selected, everyone passes.
|
||||
if (empty($cohortstring)) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
|
||||
$selected = explode(',', $cohortstring);
|
||||
foreach ($selected as $id) {
|
||||
if (cohort_is_member($id, $USER->id)) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here, no cohorts matched, allow access.
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* cohort implementation.
|
||||
* Cannot set state, return true.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* cohort implementation.
|
||||
* User can not influence. Result is whatever current state is.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states($user) {
|
||||
return [$this->get_state()];
|
||||
}
|
||||
|
||||
/**
|
||||
* cohort implementation
|
||||
* Formats the cohort list nicely.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_summary_condition() {
|
||||
global $DB;
|
||||
$selectedcohorts = get_config('factor_cohort', 'cohorts');
|
||||
if (empty($selectedcohorts)) {
|
||||
return get_string('summarycondition', 'factor_cohort', get_string('none'));
|
||||
} else {
|
||||
$selectedcohorts = explode(',', $selectedcohorts);
|
||||
}
|
||||
$names = [];
|
||||
foreach ($selectedcohorts as $cohort) {
|
||||
$record = $DB->get_record('cohort', ['id' => $cohort]);
|
||||
$names[] = $record->name;
|
||||
}
|
||||
$string = implode(', ', $names);
|
||||
return get_string('summarycondition', 'factor_cohort', $string);
|
||||
}
|
||||
}
|
42
admin/tool/mfa/factor/cohort/classes/privacy/provider.php
Normal file
42
admin/tool/mfa/factor/cohort/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_cohort\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_cohort
|
||||
* @author Chris Pratt <tonyyeb@gmail.com>
|
||||
* @copyright Chris Pratt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements null_provider {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
30
admin/tool/mfa/factor/cohort/lang/en/factor_cohort.php
Normal file
30
admin/tool/mfa/factor/cohort/lang/en/factor_cohort.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?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_cohort
|
||||
* @author Chris Pratt <tonyyeb@gmail.com>
|
||||
* @copyright Chris Pratt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Cohort Factor';
|
||||
$string['privacy:metadata'] = 'The cohort membership factor plugin does not store any personal data';
|
||||
$string['settings:cohort'] = 'Non-passing cohorts';
|
||||
$string['settings:cohort_help'] = 'Select the cohorts that will not pass this factor. This allows you to force these cohorts to use other factors to authenticate.';
|
||||
$string['summarycondition'] = 'does NOT have any of the following cohorts assigned in any context: {$a}';
|
52
admin/tool/mfa/factor/cohort/settings.php
Normal file
52
admin/tool/mfa/factor/cohort/settings.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?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_cohort
|
||||
* @author Chris Pratt <tonyyeb@gmail.com>
|
||||
* @copyright Chris Pratt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once(__DIR__ . '/../../../../../cohort/lib.php');
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_cohort/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('cohort', get_config('factor_cohort', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_cohort/weight',
|
||||
new lang_string('settings:weight', 'tool_mfa'),
|
||||
new lang_string('settings:weight_help', 'tool_mfa'), 100, PARAM_INT));
|
||||
|
||||
$cohorts = cohort_get_all_cohorts();
|
||||
$choices = [];
|
||||
|
||||
foreach ($cohorts['cohorts'] as $cohort) {
|
||||
$choices[$cohort->id] = $cohort->name;
|
||||
}
|
||||
|
||||
if (!empty($choices)) {
|
||||
$settings->add(new admin_setting_configmultiselect('factor_cohort/cohorts',
|
||||
new lang_string('settings:cohort', 'factor_cohort'),
|
||||
new lang_string('settings:cohort_help', 'factor_cohort'), [], $choices));
|
||||
}
|
34
admin/tool/mfa/factor/cohort/version.php
Normal file
34
admin/tool/mfa/factor/cohort/version.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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_cohort
|
||||
* @subpackage tool_mfa
|
||||
* @author Chris Pratt <tonyyeb@gmail.com>
|
||||
* @copyright Chris Pratt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2022101100; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
|
||||
$plugin->component = 'factor_cohort';
|
||||
$plugin->release = 'v0.2';
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->dependencies = ['tool_mfa' => 2022090600];
|
88
admin/tool/mfa/factor/email/classes/event/unauth_email.php
Normal file
88
admin/tool/mfa/factor/email/classes/event/unauth_email.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?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_email\event;
|
||||
|
||||
/**
|
||||
* Event for when a user receives an unauthorised email from MFA.
|
||||
*
|
||||
* @property-read array $other {
|
||||
* Extra information about event.
|
||||
* }
|
||||
*
|
||||
* @package factor_email
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class unauth_email extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Create instance of event.
|
||||
*
|
||||
* @param stdClass $user the User object of the User who passed all MFA factor checks.
|
||||
* @param string $ip the ip address the unauthorised email came from.
|
||||
* @param string $useragent the browser fingerpring the unauthorised email came from.
|
||||
*
|
||||
* @return user_passed_mfa the user_passed_mfa event
|
||||
*
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function unauth_email_event($user, $ip, $useragent) {
|
||||
|
||||
$data = [
|
||||
'relateduserid' => null,
|
||||
'context' => \context_user::instance($user->id),
|
||||
'other' => [
|
||||
'userid' => $user->id,
|
||||
'ip' => $ip,
|
||||
'useragent' => $useragent,
|
||||
],
|
||||
];
|
||||
|
||||
return self::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'r';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of what happened.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
return "The user with id '{$this->other['userid']}' made an unauthorised login attempt using email verification from
|
||||
IP '{$this->other['ip']}' with browser agent '{$this->other['useragent']}'.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return localised event name.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('event:unauthemail', 'factor_email');
|
||||
}
|
||||
}
|
291
admin/tool/mfa/factor/email/classes/factor.php
Normal file
291
admin/tool/mfa/factor/email/classes/factor.php
Normal file
|
@ -0,0 +1,291 @@
|
|||
<?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_email;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* Email factor class.
|
||||
*
|
||||
* @package factor_email
|
||||
* @subpackage tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class factor extends object_factor_base {
|
||||
|
||||
/**
|
||||
* E-Mail Factor implementation.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
*/
|
||||
public function login_form_definition($mform) {
|
||||
|
||||
$mform->addElement('text', 'verificationcode', get_string('verificationcode', 'factor_email'));
|
||||
$mform->setType('verificationcode', PARAM_ALPHANUM);
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* E-Mail Factor implementation.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Form to inject global elements into.
|
||||
* @return object $mform
|
||||
*/
|
||||
public function login_form_definition_after_data($mform) {
|
||||
$this->generate_and_email_code();
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends and e-mail to user with given verification code.
|
||||
*
|
||||
* @param int $instanceid
|
||||
*/
|
||||
public static function email_verification_code($instanceid) {
|
||||
global $PAGE, $USER;
|
||||
$noreplyuser = \core_user::get_noreply_user();
|
||||
$subject = get_string('email:subject', 'factor_email');
|
||||
$renderer = $PAGE->get_renderer('factor_email');
|
||||
$body = $renderer->generate_email($instanceid);
|
||||
email_to_user($USER, $noreplyuser, $subject, $body, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* E-Mail Factor implementation.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function login_form_validation($data) {
|
||||
global $USER;
|
||||
$return = [];
|
||||
|
||||
if (!$this->check_verification_code($data['verificationcode'])) {
|
||||
$return['verificationcode'] = get_string('error:wrongverification', 'factor_email');
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* E-Mail Factor implementation.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
|
||||
$records = $DB->get_records('tool_mfa', [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'label' => $user->email,
|
||||
]);
|
||||
|
||||
if (!empty($records)) {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Null records returned, build new record.
|
||||
$record = [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'label' => $user->email,
|
||||
'createdfromip' => $user->lastip,
|
||||
'timecreated' => time(),
|
||||
'revoked' => 0,
|
||||
];
|
||||
$record['id'] = $DB->insert_record('tool_mfa', $record, true);
|
||||
return [(object) $record];
|
||||
}
|
||||
|
||||
/**
|
||||
* E-Mail Factor implementation.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_input() {
|
||||
if (self::is_ready()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* E-Mail Factor implementation.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
if (!self::is_ready()) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
|
||||
return parent::get_state();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether user email is correctly configured.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_ready() {
|
||||
global $DB, $USER;
|
||||
|
||||
if (empty($USER->email)) {
|
||||
return false;
|
||||
}
|
||||
if (!validate_email($USER->email)) {
|
||||
return false;
|
||||
}
|
||||
if (over_bounce_threshold($USER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this factor is revoked, set to not ready.
|
||||
if ($DB->record_exists('tool_mfa', ['userid' => $USER->id, 'factor' => 'email', 'revoked' => 1])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and emails the code for login to the user, stores codes in DB.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function generate_and_email_code() {
|
||||
global $DB, $USER;
|
||||
|
||||
// Get instance that isnt parent email type (label check).
|
||||
// This check must exclude the main singleton record, with the label as the email.
|
||||
// It must only grab the record with the user agent as the label.
|
||||
$sql = 'SELECT *
|
||||
FROM {tool_mfa}
|
||||
WHERE userid = ?
|
||||
AND factor = ?
|
||||
AND NOT label = ?';
|
||||
|
||||
$record = $DB->get_record_sql($sql, [$USER->id, 'email', $USER->email]);
|
||||
$duration = get_config('factor_email', 'duration');
|
||||
$newcode = random_int(100000, 999999);
|
||||
|
||||
if (empty($record)) {
|
||||
// No code active, generate new code.
|
||||
$instanceid = $DB->insert_record('tool_mfa', [
|
||||
'userid' => $USER->id,
|
||||
'factor' => 'email',
|
||||
'secret' => $newcode,
|
||||
'label' => $_SERVER['HTTP_USER_AGENT'],
|
||||
'timecreated' => time(),
|
||||
'createdfromip' => $USER->lastip,
|
||||
'timemodified' => time(),
|
||||
'lastverified' => time(),
|
||||
'revoked' => 0,
|
||||
], true);
|
||||
$this->email_verification_code($instanceid);
|
||||
} else if ($record->timecreated + $duration < time()) {
|
||||
// Old code found. Keep id, update fields.
|
||||
$DB->update_record('tool_mfa', [
|
||||
'id' => $record->id,
|
||||
'secret' => $newcode,
|
||||
'label' => $_SERVER['HTTP_USER_AGENT'],
|
||||
'timecreated' => time(),
|
||||
'createdfromip' => $USER->lastip,
|
||||
'timemodified' => time(),
|
||||
'lastverified' => time(),
|
||||
'revoked' => 0,
|
||||
]);
|
||||
$instanceid = $record->id;
|
||||
$this->email_verification_code($instanceid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies entered code against stored DB record.
|
||||
*
|
||||
* @param string $enteredcode
|
||||
* @return bool
|
||||
*/
|
||||
private function check_verification_code($enteredcode) {
|
||||
global $DB, $USER;
|
||||
$duration = get_config('factor_email', 'duration');
|
||||
|
||||
// Get instance that isnt parent email type (label check).
|
||||
// This check must exclude the main singleton record, with the label as the email.
|
||||
// It must only grab the record with the user agent as the label.
|
||||
$sql = 'SELECT *
|
||||
FROM {tool_mfa}
|
||||
WHERE userid = ?
|
||||
AND factor = ?
|
||||
AND NOT label = ?';
|
||||
$record = $DB->get_record_sql($sql, [$USER->id, 'email', $USER->email]);
|
||||
|
||||
if ($enteredcode == $record->secret) {
|
||||
if ($record->timecreated + $duration > time()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up email records once MFA passed.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function post_pass_state() {
|
||||
global $DB, $USER;
|
||||
// Delete all email records except base record.
|
||||
$selectsql = 'userid = ?
|
||||
AND factor = ?
|
||||
AND NOT label = ?';
|
||||
$DB->delete_records_select('tool_mfa', $selectsql, [$USER->id, 'email', $USER->email]);
|
||||
|
||||
// Update factor timeverified.
|
||||
parent::post_pass_state();
|
||||
}
|
||||
|
||||
/**
|
||||
* Email factor implementation.
|
||||
* Email page must be safe to authorise session from link.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_no_redirect_urls() {
|
||||
$email = new \moodle_url('/admin/tool/mfa/factor/email/email.php');
|
||||
return [$email];
|
||||
}
|
||||
|
||||
/**
|
||||
* Email factor implementation.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states($user) {
|
||||
// Email can return all states.
|
||||
return [
|
||||
\tool_mfa\plugininfo\factor::STATE_FAIL,
|
||||
\tool_mfa\plugininfo\factor::STATE_PASS,
|
||||
\tool_mfa\plugininfo\factor::STATE_NEUTRAL,
|
||||
\tool_mfa\plugininfo\factor::STATE_UNKNOWN,
|
||||
];
|
||||
}
|
||||
}
|
56
admin/tool/mfa/factor/email/classes/form/email.php
Normal file
56
admin/tool/mfa/factor/email/classes/form/email.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?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_email\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . "/formslib.php");
|
||||
|
||||
/**
|
||||
* Revoke email form.
|
||||
*
|
||||
* @package factor_email
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class email extends \moodleform {
|
||||
|
||||
/**
|
||||
* Form definition.
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
$mform->addElement('html', get_string('email:accident', 'factor_email'));
|
||||
$this->add_action_buttons(true, get_string('continue'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Form validation.
|
||||
*
|
||||
* Server side rules do not work for uploaded files, implement serverside rules here if needed.
|
||||
*
|
||||
* @param array $data array of ("fieldname"=>value) of submitted data
|
||||
* @param array $files array of uploaded files "element_name"=>tmp_file_path
|
||||
* @return array of "element_name"=>"error_description" if there are errors,
|
||||
* or an empty array if everything is OK (true allowed for backwards compatibility too).
|
||||
*/
|
||||
public function validation($data, $files) {
|
||||
$errors = parent::validation($data, $files);
|
||||
return $errors;
|
||||
}
|
||||
}
|
42
admin/tool/mfa/factor/email/classes/privacy/provider.php
Normal file
42
admin/tool/mfa/factor/email/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_email\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_email
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements null_provider {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
105
admin/tool/mfa/factor/email/email.php
Normal file
105
admin/tool/mfa/factor/email/email.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Page to revoke and disable an email code.
|
||||
*
|
||||
* @package factor_email
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
// Ignore coding standards for login check, this page does not require login.
|
||||
// @codingStandardsIgnoreStart
|
||||
require_once(__DIR__ . '../../../../../../config.php');
|
||||
$instanceid = required_param('instance', PARAM_INT);
|
||||
$pass = optional_param('pass', '0', PARAM_INT);
|
||||
$secret = optional_param('secret', 0, PARAM_INT);
|
||||
// @codingStandardsIgnoreEnds
|
||||
|
||||
$context = context_system::instance();
|
||||
$PAGE->set_context($context);
|
||||
$url = new moodle_url('/admin/tool/mfa/factor/email/email.php',
|
||||
['instance' => $instanceid, 'pass' => $pass, 'secret' => $secret]);
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_pagelayout('secure');
|
||||
$PAGE->set_title(get_string('unauthemail', 'factor_email'));
|
||||
$PAGE->set_cacheable(false);
|
||||
$instance = $DB->get_record('tool_mfa', ['id' => $instanceid]);
|
||||
$factor = \tool_mfa\plugininfo\factor::get_factor('email');
|
||||
|
||||
// If pass is set, require login to force $SESSION and user, and pass for that session.
|
||||
if (!empty($instance) && $pass != 0 && $secret != 0) {
|
||||
require_login();
|
||||
if ($factor->get_state() === \tool_mfa\plugininfo\factor::STATE_LOCKED) {
|
||||
// Redirect through to auth, this will bounce them to the next factor.
|
||||
redirect(new moodle_url('/admin/tool/mfa/auth.php'));
|
||||
}
|
||||
// Check the code with the same measures on the page entry.
|
||||
if ($instance->secret != $secret) {
|
||||
\tool_mfa\manager::sleep_timer();
|
||||
$factor->increment_lock_counter();
|
||||
throw new moodle_exception('error:parameters', 'factor_email');
|
||||
}
|
||||
$factor = \tool_mfa\plugininfo\factor::get_factor('email');
|
||||
$factor->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
|
||||
// If wantsurl is already set in session, go to it.
|
||||
if (!empty($SESSION->wantsurl)) {
|
||||
redirect($SESSION->wantsurl);
|
||||
} else {
|
||||
redirect(new moodle_url('/'));
|
||||
}
|
||||
}
|
||||
|
||||
$form = new \factor_email\form\email($url);
|
||||
|
||||
if ($form->is_cancelled()) {
|
||||
redirect(new moodle_url('/'));
|
||||
} else if ($fromform = $form->get_data()) {
|
||||
if (empty($instance)) {
|
||||
$message = get_string('error:badcode', 'factor_email');
|
||||
} else {
|
||||
$user = $DB->get_record('user', ['id' => $instance->userid]);
|
||||
|
||||
// Stop attacker from using email factor at all, by revoking all email until admin fixes.
|
||||
$DB->set_field('tool_mfa', 'revoked', 1, ['userid' => $user->id, 'factor' => 'email']);
|
||||
|
||||
// Remotely logout all sessions for user.
|
||||
$manager = \core\session\manager::kill_user_sessions($instance->userid);
|
||||
|
||||
// Log event.
|
||||
$ip = $instance->createdfromip;
|
||||
$useragent = $instance->label;
|
||||
$event = \factor_email\event\unauth_email::unauth_email_event($user, $ip, $useragent);
|
||||
$event->trigger();
|
||||
|
||||
// Suspend user account.
|
||||
if (get_config('factor_email', 'suspend')) {
|
||||
$DB->set_field('user', 'suspended', 1, ['id' => $userid]);
|
||||
}
|
||||
|
||||
$message = get_string('email:revokesuccess', 'factor_email', fullname($user));
|
||||
}
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('unauthemail', 'factor_email'));
|
||||
if (!empty($message)) {
|
||||
echo $message;
|
||||
} else {
|
||||
$form->display();
|
||||
}
|
||||
echo $OUTPUT->footer();
|
57
admin/tool/mfa/factor/email/lang/en/factor_email.php
Normal file
57
admin/tool/mfa/factor/email/lang/en/factor_email.php
Normal 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/>.
|
||||
|
||||
/**
|
||||
* Language strings.
|
||||
*
|
||||
* @package factor_email
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['email:accident'] = 'If you did not request this email, click continue to attempt to invalidate the login attempt.
|
||||
If you clicked this link by accident, click cancel, and no action will be taken.';
|
||||
$string['email:browseragent'] = 'The browser details for this request are: \'{$a}\'';
|
||||
$string['email:geoinfo'] = 'This request appears to have originated from approximately {$a->city}, {$a->country}.';
|
||||
$string['email:ipinfo'] = 'IP Information';
|
||||
$string['email:link'] = 'this link';
|
||||
$string['email:message'] = 'You are trying to log in to Moodle. Your confirmation code is \'{$a->secret}\'.
|
||||
Alternatively you can click {$a->link} from the same device to authorise this session.';
|
||||
$string['email:originatingip'] = 'This login request was made from \'{$a}\'';
|
||||
$string['email:revokelink'] = 'If this wasn\'t you, follow {$a} to stop this login attempt.';
|
||||
$string['email:revokesuccess'] = 'This code has been successfully revoked. All sessions for {$a} have been ended.
|
||||
Email will not be usable as a factor until account security has been verified.';
|
||||
$string['email:subject'] = 'Your confirmation code';
|
||||
$string['email:uadescription'] = 'Browser identity for this request:';
|
||||
$string['error:badcode'] = 'Code was not found. This may be an old link, a new code may have been emailed, or the login attempt with this code was successful.';
|
||||
$string['error:parameters'] = 'Incorrect page parameters.';
|
||||
$string['error:wrongverification'] = 'Incorrect verification code';
|
||||
$string['event:unauthemail'] = 'Unauthorised email received';
|
||||
$string['info'] = '<p>Built-in factor. Uses e-mail address mentioned in user profile for sending verification codes</p>';
|
||||
$string['loginskip'] = "I didn't receive a code";
|
||||
$string['loginsubmit'] = 'Verify code';
|
||||
$string['pluginname'] = 'E-Mail Factor';
|
||||
$string['privacy:metadata'] = 'The E-Mail Factor plugin does not store any personal data';
|
||||
$string['settings:duration'] = 'Validity duration';
|
||||
$string['settings:duration_help'] = 'The period of time that the code is valid.';
|
||||
$string['settings:suspend'] = 'Suspend unauthorised accounts';
|
||||
$string['settings:suspend_help'] = 'Check this to suspend user accounts if an unauthorised email verification is received.';
|
||||
$string['setupfactor'] = 'E-Mail Factor setup';
|
||||
$string['summarycondition'] = 'has valid email setup';
|
||||
$string['unauthemail'] = 'Unauthorised Email';
|
||||
$string['verificationcode'] = 'Enter verification code for confirmation';
|
||||
$string['verificationcode_help'] = 'Verification code has been sent to your email address';
|
61
admin/tool/mfa/factor/email/renderer.php
Normal file
61
admin/tool/mfa/factor/email/renderer.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/>.
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once(__DIR__ . '../../../../../../iplookup/lib.php');
|
||||
|
||||
/**
|
||||
* Email renderer.
|
||||
*
|
||||
* @package factor_email
|
||||
* @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_email_renderer extends plugin_renderer_base {
|
||||
|
||||
/**
|
||||
* Generates an email
|
||||
*
|
||||
* @param int $instanceid
|
||||
* @return string|boolean
|
||||
*/
|
||||
public function generate_email($instanceid) {
|
||||
global $DB;
|
||||
$instance = $DB->get_record('tool_mfa', ['id' => $instanceid]);
|
||||
$authurl = new \moodle_url('/admin/tool/mfa/factor/email/email.php',
|
||||
['instance' => $instance->id, 'pass' => 1, 'secret' => $instance->secret]);
|
||||
$authurlstring = \html_writer::link($authurl, get_string('email:link', 'factor_email'));
|
||||
$messagestrings = ['secret' => $instance->secret, 'link' => $authurlstring];
|
||||
$geoinfo = iplookup_find_location($instance->createdfromip);
|
||||
$info = ['city' => $geoinfo['city'], 'country' => $geoinfo['country']];
|
||||
$blockurl = new \moodle_url('/admin/tool/mfa/factor/email/email.php',
|
||||
['instance' => $instanceid]);
|
||||
$blockurlstring = \html_writer::link($blockurl, get_string('email:link', 'factor_email'));
|
||||
|
||||
$templateinfo = [
|
||||
'title' => get_string('email:subject', 'factor_email'),
|
||||
'message' => get_string('email:message', 'factor_email', $messagestrings),
|
||||
'ipinformation' => get_string('email:ipinfo', 'factor_email'),
|
||||
'ip' => get_string('email:originatingip', 'factor_email', $instance->createdfromip),
|
||||
'geoinfo' => get_string('email:geoinfo', 'factor_email', $info),
|
||||
'uadescription' => get_string('email:uadescription', 'factor_email'),
|
||||
'ua' => $instance->label,
|
||||
'linkstring' => get_string('email:revokelink', 'factor_email', $blockurlstring),
|
||||
];
|
||||
return $this->render_from_template('factor_email/email', $templateinfo);
|
||||
}
|
||||
}
|
46
admin/tool/mfa/factor/email/settings.php
Normal file
46
admin/tool/mfa/factor/email/settings.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?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_email
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_email/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('email', get_config('factor_email', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_email/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_email/duration',
|
||||
get_string('settings:duration', 'factor_email'),
|
||||
get_string('settings:duration_help', 'factor_email'), 30 * MINSECS, MINSECS));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('factor_email/suspend',
|
||||
get_string('settings:suspend', 'factor_email'),
|
||||
get_string('settings:suspend_help', 'factor_email'), 0));
|
41
admin/tool/mfa/factor/email/templates/email.mustache
Normal file
41
admin/tool/mfa/factor/email/templates/email.mustache
Normal file
|
@ -0,0 +1,41 @@
|
|||
{{!
|
||||
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_email/email
|
||||
|
||||
Template for email body sent to users.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"title": "Your verification code",
|
||||
"message": "Your verification code is 123456.",
|
||||
"ipinformation": "IP Information",
|
||||
"ip": "The IP was 127.0.0.1",
|
||||
"geoinfo": "Request originated from Brisbane, Australia",
|
||||
"uadescription": "User agent",
|
||||
"ua": "The user agent is Firefox 70",
|
||||
"linkstring": "If this wasn't you, click here"
|
||||
}
|
||||
}}
|
||||
<h2>{{title}}</h2>
|
||||
<p>{{{message}}}</p>
|
||||
<h3>{{ipinformation}}</h3>
|
||||
<p>{{ip}}</p>
|
||||
<p>{{geoinfo}}</p>
|
||||
<h3>{{uadescription}}</h3>
|
||||
<p>{{ua}}</p>
|
||||
<p>{{{linkstring}}}</p>
|
34
admin/tool/mfa/factor/email/version.php
Normal file
34
admin/tool/mfa/factor/email/version.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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_email
|
||||
* @subpackage tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2019102400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
|
||||
$plugin->component = 'factor_email';
|
||||
$plugin->release = 'v0.1';
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->dependencies = ['tool_mfa' => 2019102400];
|
305
admin/tool/mfa/factor/grace/classes/factor.php
Normal file
305
admin/tool/mfa/factor/grace/classes/factor.php
Normal file
|
@ -0,0 +1,305 @@
|
|||
<?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_grace;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* Grace period factor class.
|
||||
*
|
||||
* @package factor_grace
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* Grace Factor implementation.
|
||||
* This factor is a singleton, return single instance.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
|
||||
$records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
|
||||
if (!empty($records)) {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Null records returned, build new record.
|
||||
$record = [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'createdfromip' => $user->lastip,
|
||||
'timecreated' => time(),
|
||||
'revoked' => 0,
|
||||
];
|
||||
$record['id'] = $DB->insert_record('tool_mfa', $record, true);
|
||||
return [(object) $record];
|
||||
}
|
||||
|
||||
/**
|
||||
* Grace Factor implementation.
|
||||
* Singleton instance, no additional filtering needed.
|
||||
*
|
||||
* @param stdClass $user object to check against.
|
||||
* @return array the array of active factors.
|
||||
*/
|
||||
public function get_active_user_factors($user) {
|
||||
return $this->get_all_user_factors($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grace Factor implementation.
|
||||
* Factor has no input.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_input() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grace Factor implementation.
|
||||
* Checks the user login time against their first login after MFA activation.
|
||||
*
|
||||
* @param bool $redirectable should this state call be allowed to redirect the user?
|
||||
* @return string state constant
|
||||
*/
|
||||
public function get_state($redirectable = true) {
|
||||
global $FULLME, $SESSION, $USER;
|
||||
$records = ($this->get_all_user_factors($USER));
|
||||
$record = reset($records);
|
||||
|
||||
// First check if user has any other input or setup factors active.
|
||||
$factors = $this->get_affecting_factors();
|
||||
$total = 0;
|
||||
foreach ($factors as $factor) {
|
||||
$total += $factor->get_weight();
|
||||
// If we have hit 100 total, then we know it is possible to auth with the current setup.
|
||||
// Gracemode should no longer give points.
|
||||
if ($total >= 100) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
}
|
||||
|
||||
$starttime = $record->timecreated;
|
||||
// If no start time is recorded, status is unknown.
|
||||
if (empty($starttime)) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_UNKNOWN;
|
||||
} else {
|
||||
$duration = get_config('factor_grace', 'graceperiod');
|
||||
|
||||
if (!empty($duration)) {
|
||||
if (time() > $starttime + $duration) {
|
||||
// If gracemode would have given points, but now doesnt,
|
||||
// Jump out of the loop and force a factor setup.
|
||||
// We will return once there is a setup, or the user tries to leave.
|
||||
if (get_config('factor_grace', 'forcesetup') && $redirectable) {
|
||||
if (empty($SESSION->mfa_gracemode_recursive)) {
|
||||
// Set a gracemode lock so any further recursive gets fall past any recursive calls.
|
||||
$SESSION->mfa_gracemode_recursive = true;
|
||||
|
||||
$factorurls = \tool_mfa\manager::get_no_redirect_urls();
|
||||
$cleanurl = new \moodle_url($FULLME);
|
||||
|
||||
foreach ($factorurls as $factorurl) {
|
||||
if ($factorurl->compare($cleanurl)) {
|
||||
$redirectable = false;
|
||||
}
|
||||
}
|
||||
|
||||
// We should never redirect if we have already passed.
|
||||
if ($redirectable && \tool_mfa\manager::get_cumulative_weight() >= 100) {
|
||||
$redirectable = false;
|
||||
}
|
||||
|
||||
unset($SESSION->mfa_gracemode_recursive);
|
||||
|
||||
if ($redirectable) {
|
||||
redirect(new \moodle_url('/admin/tool/mfa/user_preferences.php'),
|
||||
get_string('redirectsetup', 'factor_grace'));
|
||||
}
|
||||
}
|
||||
}
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
} else {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
} else {
|
||||
return \tool_mfa\plugininfo\factor::STATE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grace Factor implementation.
|
||||
* State cannot be set. Return true.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grace Factor implementation.
|
||||
* Add a notification on the next page.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function post_pass_state() {
|
||||
global $USER;
|
||||
parent::post_pass_state();
|
||||
|
||||
// Ensure grace factor passed before displaying notification.
|
||||
if ($this->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS
|
||||
&& !\tool_mfa\manager::check_factor_pending($this->name)) {
|
||||
$url = new \moodle_url('/admin/tool/mfa/user_preferences.php');
|
||||
$link = \html_writer::link($url, get_string('preferences', 'factor_grace'));
|
||||
|
||||
$records = ($this->get_all_user_factors($USER));
|
||||
$record = reset($records);
|
||||
$starttime = $record->timecreated;
|
||||
$timeremaining = ($starttime + get_config('factor_grace', 'graceperiod')) - time();
|
||||
$time = format_time($timeremaining);
|
||||
|
||||
$data = ['url' => $link, 'time' => $time];
|
||||
|
||||
$customwarning = get_config('factor_grace', 'customwarning');
|
||||
if (!empty($customwarning)) {
|
||||
// Clean text, then swap placeholders for time and the setup link.
|
||||
$message = preg_replace("/{timeremaining}/", $time, $customwarning);
|
||||
$message = preg_replace("/{setuplink}/", $url, $message);
|
||||
$message = clean_text($message, FORMAT_MOODLE);
|
||||
} else {
|
||||
$message = get_string('setupfactors', 'factor_grace', $data);
|
||||
}
|
||||
|
||||
\core\notification::error($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grace Factor implementation.
|
||||
* Gracemode should not be a valid combination with another factor.
|
||||
*
|
||||
* @param array $combination array of factors that make up the combination
|
||||
* @return bool
|
||||
*/
|
||||
public function check_combination($combination) {
|
||||
// If this combination has more than 1 factor that has setup or input, not valid.
|
||||
foreach ($combination as $factor) {
|
||||
if ($factor->has_setup() || $factor->has_input()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grace Factor implementation.
|
||||
* Gracemode can change outcome just by waiting, or based on other factors.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states($user) {
|
||||
return [
|
||||
\tool_mfa\plugininfo\factor::STATE_PASS,
|
||||
\tool_mfa\plugininfo\factor::STATE_NEUTRAL,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Grace factor implementation.
|
||||
*
|
||||
* If grace period should redirect at end, make this a no-redirect url.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_no_redirect_urls() {
|
||||
$redirect = get_config('factor_grace', 'forcesetup');
|
||||
|
||||
// First check if user has any other input or setup factors active.
|
||||
$factors = $this->get_affecting_factors();
|
||||
$total = 0;
|
||||
foreach ($factors as $factor) {
|
||||
$total += $factor->get_weight();
|
||||
// If we have hit 100 total, then we know it is possible to auth with the current setup.
|
||||
// The setup URL should no longer be a no-redirect URL. User MUST use existing auth.
|
||||
if ($total >= 100) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if ($redirect && $this->get_state(false) === \tool_mfa\plugininfo\factor::STATE_NEUTRAL) {
|
||||
// If the config is enabled, the user should be able to access + setup a factor using these pages.
|
||||
return [
|
||||
new \moodle_url('/admin/tool/mfa/user_preferences.php'),
|
||||
new \moodle_url('/admin/tool/mfa/action.php'),
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of factor objects that can affect gracemode giving points.
|
||||
*
|
||||
* Only factors that a user can setup or manually use can affect whether gracemode gives points.
|
||||
* The intest is to provide a grace period for users to go in, setup factors, phone numbers, etc.,
|
||||
* so that they are able to authenticate correctly once the grace period ends.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_affecting_factors(): array {
|
||||
// Check if user has any other input or setup factors active.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_factors();
|
||||
$factors = array_filter($factors, function ($el) {
|
||||
return $el->has_input() || $el->has_setup();
|
||||
});
|
||||
return $factors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the factor list that is currently affecting gracemode. Active and not ignored.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_affecting_factors(): array {
|
||||
// We need to filter all active user factors against the affecting factors and ignorelist.
|
||||
// Map active to names for filtering.
|
||||
$active = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$active = array_map(function ($el) {
|
||||
return $el->name;
|
||||
}, $active);
|
||||
$factors = $this->get_all_affecting_factors();
|
||||
|
||||
$ignorelist = get_config('factor_grace', 'ignorelist');
|
||||
$ignorelist = !empty($ignorelist) ? explode(',', $ignorelist) : [];
|
||||
|
||||
$factors = array_filter($factors, function ($el) use ($ignorelist, $active) {
|
||||
return !in_array($el->name, $ignorelist) && in_array($el->name, $active);
|
||||
});
|
||||
return $factors;
|
||||
}
|
||||
}
|
42
admin/tool/mfa/factor/grace/classes/privacy/provider.php
Normal file
42
admin/tool/mfa/factor/grace/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_grace\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_grace
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements null_provider {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Scheduled task to revoke expired factors
|
||||
*
|
||||
* @package factor_grace
|
||||
* @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_grace\task;
|
||||
|
||||
/**
|
||||
* Scheduled task to revoke expired gracemode factors
|
||||
*/
|
||||
class revoke_expired_factors extends \core\task\scheduled_task {
|
||||
|
||||
/**
|
||||
* Return the task's name as shown in admin screens.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('revokeexpiredfactors', 'factor_grace');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the task.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute() {
|
||||
mtrace('Starting to revoke expired Grace factors');
|
||||
$this->revoke_factors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes all grace factors that have a valid timecreated and are outside the duration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function revoke_factors() {
|
||||
global $DB;
|
||||
|
||||
// If config is not set, pull out.
|
||||
$duration = get_config('factor_grace', 'graceperiod');
|
||||
if (!$duration) {
|
||||
mtrace('Gracemode duration is not set. Exiting...');
|
||||
return;
|
||||
}
|
||||
$revoketime = time() - $duration;
|
||||
|
||||
// Single query implementation.
|
||||
$sql = "UPDATE {tool_mfa}
|
||||
SET revoked = 1,
|
||||
timemodified = :timemodified
|
||||
WHERE timecreated < :revoketime
|
||||
AND factor = :factor";
|
||||
$DB->execute($sql, ['timemodified' => time(), 'revoketime' => $revoketime, 'factor' => 'grace']);
|
||||
|
||||
mtrace('Finished revoking expired Grace factors');
|
||||
}
|
||||
}
|
38
admin/tool/mfa/factor/grace/db/tasks.php
Normal file
38
admin/tool/mfa/factor/grace/db/tasks.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Task scheduler
|
||||
*
|
||||
* @package factor_grace
|
||||
* @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();
|
||||
|
||||
$tasks = [
|
||||
[
|
||||
'classname' => 'factor_grace\task\revoke_expired_factors',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => '0',
|
||||
'day' => '*',
|
||||
'month' => '*',
|
||||
'dayofweek' => '*',
|
||||
],
|
||||
];
|
42
admin/tool/mfa/factor/grace/lang/en/factor_grace.php
Normal file
42
admin/tool/mfa/factor/grace/lang/en/factor_grace.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_grace
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['info'] = 'Allows login without other factor for a specified period of time.';
|
||||
$string['pluginname'] = 'Grace period';
|
||||
$string['preferences'] = 'User preferences';
|
||||
$string['privacy:metadata'] = 'The Grace period factor plugin does not store any personal data';
|
||||
$string['redirectsetup'] = 'You must complete setup for Multi-factor authentication before you can proceed.';
|
||||
$string['revokeexpiredfactors'] = 'Revoke expired gracemode factors';
|
||||
$string['settings:customwarning'] = 'Warning banner content';
|
||||
$string['settings:customwarning_help'] = 'Add content here to replace the grace warning notification with custom HTML contents. Adding {timeremaining} in text will replace it with the current grace duration for the user, and {setuplink} will replace with the URL of the setup page for the user.';
|
||||
$string['settings:forcesetup'] = 'Force factor setup';
|
||||
$string['settings:forcesetup_help'] = 'Forces a user to the preferences page to setup MFA when the gracemode period expires. If set to off, users will be unable to authenticate when the grace period expires.';
|
||||
$string['settings:graceperiod'] = 'Grace period';
|
||||
$string['settings:graceperiod_help'] = 'Period of time when users can access Moodle without configured and enabled factors';
|
||||
$string['settings:ignorelist'] = 'Ignored factors';
|
||||
$string['settings:ignorelist_help'] = 'Grace will not give points if there are other factors that users can use to authenticate with MFA. Any factors here will not be counted by grace when deciding whether to give points. This can allow Grace to allow authentication if another factor like email, is suffering configuration or system issues.';
|
||||
$string['setupfactors'] = 'You are currently in grace mode, and may not have enough factors setup to login once the grace period is over.
|
||||
Visit {$a->url} to check your authentication status, and setup more authentication factors. Your grace period expires in {$a->time}.';
|
||||
$string['summarycondition'] = 'is within grace period';
|
60
admin/tool/mfa/factor/grace/settings.php
Normal file
60
admin/tool/mfa/factor/grace/settings.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?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_grace
|
||||
* @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();
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_grace/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('grace', get_config('factor_grace', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_grace/weight',
|
||||
new lang_string('settings:weight', 'tool_mfa'),
|
||||
new lang_string('settings:weight_help', 'tool_mfa'), 100, PARAM_INT));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('factor_grace/forcesetup',
|
||||
new lang_string('settings:forcesetup', 'factor_grace'),
|
||||
new lang_string('settings:forcesetup_help', 'factor_grace'), 0));
|
||||
|
||||
$settings->add(new admin_setting_configduration('factor_grace/graceperiod',
|
||||
new lang_string('settings:graceperiod', 'factor_grace'),
|
||||
new lang_string('settings:graceperiod_help', 'factor_grace'), '604800'));
|
||||
|
||||
$gracefactor = \tool_mfa\plugininfo\factor::get_factor('grace');
|
||||
$factors = $gracefactor->get_all_affecting_factors();
|
||||
$gracefactors = [];
|
||||
foreach ($factors as $factor) {
|
||||
$gracefactors[$factor->name] = $factor->get_display_name();
|
||||
}
|
||||
$settings->add(new admin_setting_configmultiselect('factor_grace/ignorelist',
|
||||
new lang_string('settings:ignorelist', 'factor_grace'),
|
||||
new lang_string('settings:ignorelist_help', 'factor_grace'), [], $gracefactors));
|
||||
|
||||
$settings->add(new admin_setting_confightmleditor('factor_grace/customwarning',
|
||||
new lang_string('settings:customwarning', 'factor_grace'),
|
||||
new lang_string('settings:customwarning_help', 'factor_grace'), '', PARAM_RAW));
|
58
admin/tool/mfa/factor/grace/tests/factor_test.php
Normal file
58
admin/tool/mfa/factor/grace/tests/factor_test.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?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_grace\tests;
|
||||
|
||||
/**
|
||||
* Tests for grace factor.
|
||||
*
|
||||
* @package factor_grace
|
||||
* @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_test extends \advanced_testcase {
|
||||
|
||||
public function test_affecting_factors() {
|
||||
$this->resetAfterTest(true);
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
$grace = \tool_mfa\plugininfo\factor::get_factor('grace');
|
||||
$affecting = $grace->get_affecting_factors();
|
||||
$this->assertEquals(0, count($affecting));
|
||||
|
||||
set_config('enabled', 1, 'factor_totp');
|
||||
$totpfactor = \tool_mfa\plugininfo\factor::get_factor('totp');
|
||||
$totpdata = [
|
||||
'secret' => 'fakekey',
|
||||
'devicename' => 'fakedevice',
|
||||
];
|
||||
$totpfactor->setup_user_factor((object) $totpdata);
|
||||
|
||||
// Confirm that MFA is the only affecting factor.
|
||||
$affecting = $grace->get_affecting_factors();
|
||||
$this->assertEquals(1, count($affecting));
|
||||
$totp = reset($affecting);
|
||||
$this->assertTrue($totp instanceof \factor_totp\factor);
|
||||
|
||||
// Now put it in the ignorelist.
|
||||
set_config('ignorelist', 'totp', 'factor_grace');
|
||||
// Confirm that MFA is the only affecting factor.
|
||||
$affecting = $grace->get_affecting_factors();
|
||||
$this->assertEquals(0, count($affecting));
|
||||
}
|
||||
}
|
34
admin/tool/mfa/factor/grace/version.php
Normal file
34
admin/tool/mfa/factor/grace/version.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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_grace
|
||||
* @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 = 2022020401; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
|
||||
$plugin->component = 'factor_grace';
|
||||
$plugin->release = 'v0.1';
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->dependencies = ['tool_mfa' => 2019102400];
|
113
admin/tool/mfa/factor/iprange/classes/factor.php
Normal file
113
admin/tool/mfa/factor/iprange/classes/factor.php
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?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_iprange;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* IP Range factor class.
|
||||
*
|
||||
* @package factor_iprange
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* IP Range Factor implementation.
|
||||
* This factor is a singleton, return single instance.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
$records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
|
||||
if (!empty($records)) {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Null records returned, build new record.
|
||||
$record = [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'timecreated' => time(),
|
||||
'createdfromip' => $user->lastip,
|
||||
'timemodified' => time(),
|
||||
'revoked' => 0,
|
||||
];
|
||||
$record['id'] = $DB->insert_record('tool_mfa', $record, true);
|
||||
return [(object) $record];
|
||||
}
|
||||
|
||||
/**
|
||||
* IP Range Factor implementation.
|
||||
* Factor has no input
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_input() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* IP Range Factor implementation.
|
||||
* Checks a users current IP against allowed and disallowed ranges.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
$safeips = get_config('factor_iprange', 'safeips');
|
||||
|
||||
// TODO: Check for failures here.
|
||||
|
||||
if (!empty($safeips)) {
|
||||
if (remoteip_in_list($safeips)) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
}
|
||||
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* IP Range Factor implementation.
|
||||
* Cannot set state, return true.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* IP Range Factor implementation.
|
||||
* User can influence state prior to login.
|
||||
* Possible states are either neutral or pass.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states($user) {
|
||||
return [
|
||||
\tool_mfa\plugininfo\factor::STATE_PASS,
|
||||
\tool_mfa\plugininfo\factor::STATE_NEUTRAL,
|
||||
];
|
||||
}
|
||||
}
|
42
admin/tool/mfa/factor/iprange/classes/privacy/provider.php
Normal file
42
admin/tool/mfa/factor/iprange/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_iprange\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_iprange
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements null_provider {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
33
admin/tool/mfa/factor/iprange/lang/en/factor_iprange.php
Normal file
33
admin/tool/mfa/factor/iprange/lang/en/factor_iprange.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?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_iprange
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['allowedipsempty'] = 'Nobody will pass this factor! You can add your own IP address (<i>{$a->ip}</i>)';
|
||||
$string['allowedipshasmyip'] = 'Your IP (<i>{$a->ip}</i>) is in the list and you will pass this factor.';
|
||||
$string['allowedipshasntmyip'] = 'Your IP (<i>{$a->ip}</i>) is not in the list and you will not pass this factor.';
|
||||
$string['pluginname'] = 'IP Range Factor';
|
||||
$string['privacy:metadata'] = 'The IP Range Factor plugin does not store any personal data';
|
||||
$string['settings:safeips'] = 'Safe IP ranges';
|
||||
$string['settings:safeips_help'] = 'Enter a list of IP addresses or subnets to be counted as a pass in factor. If empty noone will pass this factor. {$a->info} {$a->syntax}';
|
||||
$string['summarycondition'] = 'is on a secured network';
|
61
admin/tool/mfa/factor/iprange/settings.php
Normal file
61
admin/tool/mfa/factor/iprange/settings.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/>.
|
||||
|
||||
/**
|
||||
* Settings
|
||||
*
|
||||
* @package factor_iprange
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $OUTPUT;
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_iprange/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('iprange', get_config('factor_iprange', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_iprange/weight',
|
||||
new lang_string('settings:weight', 'tool_mfa'),
|
||||
new lang_string('settings:weight_help', 'tool_mfa'), 100, PARAM_INT));
|
||||
|
||||
|
||||
// Current IP validation against list for description.
|
||||
$allowedips = get_config('factor_iprange', 'safeips');
|
||||
if (trim($allowedips) == '') {
|
||||
$message = 'allowedipsempty';
|
||||
$type = 'notifyerror';
|
||||
} else if (remoteip_in_list($allowedips)) {
|
||||
$message = 'allowedipshasmyip';
|
||||
$type = 'notifysuccess';
|
||||
} else {
|
||||
$message = 'allowedipshasntmyip';
|
||||
$type = 'notifyerror';
|
||||
};
|
||||
$info = $OUTPUT->notification(get_string($message, 'factor_iprange', ['ip' => getremoteaddr()]), $type);
|
||||
|
||||
$settings->add(new admin_setting_configiplist('factor_iprange/safeips',
|
||||
new lang_string('settings:safeips', 'factor_iprange'),
|
||||
new lang_string('settings:safeips_help', 'factor_iprange',
|
||||
['info' => $info, 'syntax' => get_string('ipblockersyntax', 'admin')]), '', PARAM_TEXT));
|
||||
|
33
admin/tool/mfa/factor/iprange/version.php
Normal file
33
admin/tool/mfa/factor/iprange/version.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?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_iprange
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2019102400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
|
||||
$plugin->component = 'factor_iprange';
|
||||
$plugin->release = 'v0.1';
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->dependencies = ['tool_mfa' => 2019102400];
|
127
admin/tool/mfa/factor/nosetup/classes/factor.php
Normal file
127
admin/tool/mfa/factor/nosetup/classes/factor.php
Normal file
|
@ -0,0 +1,127 @@
|
|||
<?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_nosetup;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* No setup factor class.
|
||||
*
|
||||
* @package factor_nosetup
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* No Setup Factor implementation.
|
||||
* Factor is a singleton, can only be one instance.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
$records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
|
||||
if (!empty($records)) {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Null records returned, build new record.
|
||||
$record = [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'timecreated' => time(),
|
||||
'createdfromip' => $user->lastip,
|
||||
'timemodified' => time(),
|
||||
'revoked' => 0,
|
||||
];
|
||||
$record['id'] = $DB->insert_record('tool_mfa', $record, true);
|
||||
return [(object) $record];
|
||||
}
|
||||
|
||||
/**
|
||||
* No Setup Factor implementation.
|
||||
* Factor does not have input.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_input() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* No Setup Factor implementation.
|
||||
* State check is performed here, as there is no form to do it in.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
// Check if user has any other input or setup factors active.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->has_input() || $factor->has_setup()) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
}
|
||||
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* No setup implementation.
|
||||
* Copy of get_state, but can take other user..
|
||||
*
|
||||
* @param stdClass $user
|
||||
* @return void
|
||||
*/
|
||||
public function possible_states($user) {
|
||||
// We return Neutral here because to support optional rollouts
|
||||
// it needs to report neutral or the menu to setup will not display.
|
||||
return [\tool_mfa\plugininfo\factor::STATE_NEUTRAL];
|
||||
}
|
||||
|
||||
/**
|
||||
* No Setup Factor implementation.
|
||||
* The state can never be set. Always return true.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* No Setup Factor implementation.
|
||||
* nosetup should not be a valid combination with another factor.
|
||||
*
|
||||
* @param array $combination array of factors that make up the combination
|
||||
* @return bool
|
||||
*/
|
||||
public function check_combination($combination) {
|
||||
// If this combination has more than 1 factor that has setup or input, not valid.
|
||||
foreach ($combination as $factor) {
|
||||
if ($factor->has_setup() || $factor->has_input()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
42
admin/tool/mfa/factor/nosetup/classes/privacy/provider.php
Normal file
42
admin/tool/mfa/factor/nosetup/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_nosetup\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_nosetup
|
||||
* @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 {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Scheduled task to revoke unusable factors that will never pass.
|
||||
*
|
||||
* @package factor_nosetup
|
||||
* @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_nosetup\task;
|
||||
|
||||
/**
|
||||
* Scheduled task to add log events into DB table.
|
||||
*/
|
||||
class delete_unusable_factors extends \core\task\scheduled_task {
|
||||
|
||||
/**
|
||||
* Return the task's name as shown in admin screens.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('deleteunusablefactors', 'factor_nosetup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the task.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute() {
|
||||
mtrace('Starting to revoke unusable Nosetup factors');
|
||||
$this->revoke_factors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes all nosetup factors that will now always fail.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function revoke_factors() {
|
||||
global $DB;
|
||||
|
||||
$factorobject = \tool_mfa\plugininfo\factor::get_factor('nosetup');
|
||||
// We need to get all nosetup factors, and check that for ones that no longer have a pass state.
|
||||
$allfactorssql = "SELECT DISTINCT tm.userid
|
||||
FROM {tool_mfa} tm
|
||||
JOIN {user} u ON u.id = tm.userid
|
||||
WHERE tm.factor = :factor
|
||||
AND u.suspended = 0
|
||||
AND u.deleted = 0
|
||||
AND (
|
||||
SELECT COUNT(id) as count
|
||||
FROM {tool_mfa}
|
||||
WHERE userid = tm.userid
|
||||
AND factor <> :notfactor
|
||||
) > 0";
|
||||
$useridrecordset = $DB->get_recordset_sql($allfactorssql, ['factor' => 'nosetup', 'notfactor' => 'nosetup']);
|
||||
|
||||
foreach ($useridrecordset as $userid) {
|
||||
// If pass state is no longer possible, add delete user factor.
|
||||
$user = \core_user::get_user($userid->userid);
|
||||
if (!in_array(\tool_mfa\plugininfo\factor::STATE_PASS, $factorobject->possible_states($user))) {
|
||||
$factorobject->delete_factor_for_user($user);
|
||||
}
|
||||
}
|
||||
$useridrecordset->close();
|
||||
mtrace('Finished revoking unusable Nosetup factors');
|
||||
}
|
||||
}
|
38
admin/tool/mfa/factor/nosetup/db/tasks.php
Normal file
38
admin/tool/mfa/factor/nosetup/db/tasks.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Task scheduler
|
||||
*
|
||||
* @package factor_nosetup
|
||||
* @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();
|
||||
|
||||
$tasks = [
|
||||
[
|
||||
'classname' => 'factor_nosetup\task\delete_unusable_factors',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => '0',
|
||||
'day' => '*',
|
||||
'month' => '*',
|
||||
'dayofweek' => '*',
|
||||
],
|
||||
];
|
30
admin/tool/mfa/factor/nosetup/lang/en/factor_nosetup.php
Normal file
30
admin/tool/mfa/factor/nosetup/lang/en/factor_nosetup.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?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_nosetup
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['deleteunusablefactors'] = 'Delete unusable Nosetup factors';
|
||||
$string['info'] = 'This factor passes if the user has no other factors setup.';
|
||||
$string['pluginname'] = 'No other factors';
|
||||
$string['privacy:metadata'] = 'The No other factors plugin does not store any personal data';
|
||||
$string['summarycondition'] = 'has no other factors setup';
|
38
admin/tool/mfa/factor/nosetup/settings.php
Normal file
38
admin/tool/mfa/factor/nosetup/settings.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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_nosetup
|
||||
* @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();
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_nosetup/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('nosetup', get_config('factor_nosetup', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_nosetup/weight',
|
||||
new lang_string('settings:weight', 'tool_mfa'),
|
||||
new lang_string('settings:weight_help', 'tool_mfa'), 100, PARAM_INT));
|
34
admin/tool/mfa/factor/nosetup/version.php
Normal file
34
admin/tool/mfa/factor/nosetup/version.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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_nosetup
|
||||
* @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 = 2020042302; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
|
||||
$plugin->component = 'factor_nosetup';
|
||||
$plugin->release = 'v0.1';
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->dependencies = ['tool_mfa' => 2019102400];
|
163
admin/tool/mfa/factor/role/classes/factor.php
Normal file
163
admin/tool/mfa/factor/role/classes/factor.php
Normal file
|
@ -0,0 +1,163 @@
|
|||
<?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_role;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* Role factor class.
|
||||
*
|
||||
* @package factor_role
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* Role implementation.
|
||||
* This factor is a singleton, return single instance.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
$records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
|
||||
if (!empty($records)) {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Null records returned, build new record.
|
||||
$record = [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'timecreated' => time(),
|
||||
'createdfromip' => $user->lastip,
|
||||
'timemodified' => time(),
|
||||
'revoked' => 0,
|
||||
];
|
||||
$record['id'] = $DB->insert_record('tool_mfa', $record, true);
|
||||
return [(object) $record];
|
||||
}
|
||||
|
||||
/**
|
||||
* Role implementation.
|
||||
* Factor has no input
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_input() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Role implementation.
|
||||
* Checks whether the user has selected roles in any context.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
global $USER;
|
||||
$rolestring = get_config('factor_role', 'roles');
|
||||
|
||||
// Nothing selected, everyone passes.
|
||||
if (empty($rolestring)) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
|
||||
$selected = explode(',', $rolestring);
|
||||
$syscon = \context_system::instance();
|
||||
$specials = get_user_roles_with_special($syscon, $USER->id);
|
||||
// Transform the special roles to the matching format.
|
||||
$specials = array_map(function ($el) {
|
||||
return $el->roleid;
|
||||
}, $specials);
|
||||
|
||||
foreach ($selected as $id) {
|
||||
if ($id === 'admin') {
|
||||
if (is_siteadmin()) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
} else {
|
||||
if (user_has_role_assignment($USER->id, $id)) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
|
||||
// Some system default roles do not have an explicit binding. eg Authenticated user.
|
||||
if (in_array((int) $id, $specials)) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here, no roles matched, allow access.
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Role implementation.
|
||||
* Cannot set state, return true.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Role implementation.
|
||||
* User can not influence. Result is whatever current state is.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states($user) {
|
||||
return [$this->get_state()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Role implementation
|
||||
* Formats the role list nicely.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_summary_condition() {
|
||||
global $DB;
|
||||
|
||||
$selectedroles = get_config('factor_role', 'roles');
|
||||
if (empty($selectedroles)) {
|
||||
return get_string('summarycondition', 'factor_role', get_string('none'));
|
||||
} else {
|
||||
$selectedroles = explode(',', $selectedroles);
|
||||
}
|
||||
|
||||
$names = [];
|
||||
foreach ($selectedroles as $role) {
|
||||
if ($role === 'admin') {
|
||||
$names[] = get_string('administrator');
|
||||
} else {
|
||||
$record = $DB->get_record('role', ['id' => $role]);
|
||||
$names[] = role_get_name($record);
|
||||
}
|
||||
}
|
||||
|
||||
$string = implode(', ', $names);
|
||||
return get_string('summarycondition', 'factor_role', $string);
|
||||
}
|
||||
}
|
42
admin/tool/mfa/factor/role/classes/privacy/provider.php
Normal file
42
admin/tool/mfa/factor/role/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_role\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_role
|
||||
* @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 {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
30
admin/tool/mfa/factor/role/lang/en/factor_role.php
Normal file
30
admin/tool/mfa/factor/role/lang/en/factor_role.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?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_role
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Role Factor';
|
||||
$string['privacy:metadata'] = 'The user capability factor plugin does not store any personal data';
|
||||
$string['settings:roles'] = 'Non-passing roles';
|
||||
$string['settings:roles_help'] = 'Select the roles that will not pass this factor. This allows you to force these roles to use other factors to authenticate.';
|
||||
$string['summarycondition'] = 'does NOT have any of the following roles assigned in any context: {$a}';
|
48
admin/tool/mfa/factor/role/settings.php
Normal file
48
admin/tool/mfa/factor/role/settings.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?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_role
|
||||
* @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();
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_role/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('role', get_config('factor_role', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_role/weight',
|
||||
new lang_string('settings:weight', 'tool_mfa'),
|
||||
new lang_string('settings:weight_help', 'tool_mfa'), 100, PARAM_INT));
|
||||
|
||||
$choices = ['admin' => get_string('administrator')];
|
||||
$roles = get_all_roles();
|
||||
foreach ($roles as $role) {
|
||||
$choices[$role->id] = role_get_name($role);
|
||||
}
|
||||
|
||||
$settings->add(new admin_setting_configmultiselect('factor_role/roles',
|
||||
new lang_string('settings:roles', 'factor_role'),
|
||||
new lang_string('settings:roles_help', 'factor_role'), ['admin'], $choices));
|
34
admin/tool/mfa/factor/role/version.php
Normal file
34
admin/tool/mfa/factor/role/version.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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_role
|
||||
* @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 = 2020072100; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
|
||||
$plugin->component = 'factor_role';
|
||||
$plugin->release = 'v0.1';
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->dependencies = ['tool_mfa' => 2019102400];
|
89
admin/tool/mfa/factor/token/classes/event/token_created.php
Normal file
89
admin/tool/mfa/factor/token/classes/event/token_created.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?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_token\event;
|
||||
/**
|
||||
* Event for a token being created for a user.
|
||||
*
|
||||
* @package factor_token
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class token_created extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Create instance of event.
|
||||
*
|
||||
* @param stdClass $user the User object of the User who had the token creeated.
|
||||
* @param array $state an array of the state of the token.
|
||||
*
|
||||
* @return token_created the token_created_event event
|
||||
*
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function token_created_event($user, $state) {
|
||||
$data = [
|
||||
'relateduserid' => $user->id,
|
||||
'context' => \context_user::instance($user->id),
|
||||
'other' => [
|
||||
'userid' => $user->id,
|
||||
'state' => json_encode($state),
|
||||
],
|
||||
];
|
||||
|
||||
return self::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'c';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of what happened.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
$info = json_decode($this->other['state']);
|
||||
$string = '<br>';
|
||||
foreach ($info as $name => $value) {
|
||||
if ($name === 'expiry') {
|
||||
$value = userdate($value);
|
||||
}
|
||||
|
||||
$string .= ucwords($name) . ': ' . $value . '<br>';
|
||||
}
|
||||
|
||||
return "The user with id '{$this->other['userid']}' had an MFA token stored on their device. <br> Information:" . $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return localised event name.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('event:token_created', 'factor_token');
|
||||
}
|
||||
}
|
253
admin/tool/mfa/factor/token/classes/factor.php
Normal file
253
admin/tool/mfa/factor/token/classes/factor.php
Normal file
|
@ -0,0 +1,253 @@
|
|||
<?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_token;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
use tool_mfa\local\secret_manager;
|
||||
|
||||
/**
|
||||
* Token factor class.
|
||||
*
|
||||
* @package factor_token
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* Token implementation.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_input() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token implementation.
|
||||
* This factor is a singleton, return single instance.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
$records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
|
||||
if (!empty($records)) {
|
||||
return $records;
|
||||
}
|
||||
|
||||
// Null records returned, build new record.
|
||||
$record = [
|
||||
'userid' => $user->id,
|
||||
'factor' => $this->name,
|
||||
'timecreated' => time(),
|
||||
'createdfromip' => $user->lastip,
|
||||
'timemodified' => time(),
|
||||
'revoked' => 0,
|
||||
];
|
||||
$record['id'] = $DB->insert_record('tool_mfa', $record, true);
|
||||
return [(object) $record];
|
||||
}
|
||||
|
||||
/**
|
||||
* Token implementation.
|
||||
* Checks whether the user has selected roles in any context.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
global $USER;
|
||||
|
||||
// Check if there was a previous locked status to return.
|
||||
$state = parent::get_state();
|
||||
if ($state === \tool_mfa\plugininfo\factor::STATE_LOCKED) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_LOCKED;
|
||||
}
|
||||
|
||||
// Check cookie Exists.
|
||||
$cookie = 'MFA_TOKEN_' . $USER->id;
|
||||
if (NO_MOODLE_COOKIES || empty($_COOKIE[$cookie])) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
$token = $_COOKIE[$cookie];
|
||||
|
||||
$secretmanager = new secret_manager($this->name);
|
||||
$verified = $secretmanager->validate_secret($token, true);
|
||||
|
||||
// If we got a bad cookie value, someone is likely being dodgy.
|
||||
// In this instance we should just lock and make the user re-MFA.
|
||||
if ($verified === secret_manager::NONVALID) {
|
||||
$this->set_state(\tool_mfa\plugininfo\factor::STATE_LOCKED);
|
||||
return \tool_mfa\plugininfo\factor::STATE_LOCKED;
|
||||
} else if ($verified === secret_manager::VALID) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
}
|
||||
|
||||
// We should never get here. Factor cannot be revoked.
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Implementation.
|
||||
* We can't get_state like the parent here or it will recurse forever.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state($state) {
|
||||
global $SESSION;
|
||||
$property = 'factor_' . $this->name;
|
||||
$SESSION->$property = $state;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token implementation.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states($user) {
|
||||
return [
|
||||
\tool_mfa\plugininfo\factor::STATE_PASS,
|
||||
\tool_mfa\plugininfo\factor::STATE_NEUTRAL,
|
||||
\tool_mfa\plugininfo\factor::STATE_LOCKED,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Token implementation.
|
||||
* Inject a checkbox into every auth form if needed.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Form to inject global elements into.
|
||||
* @return void
|
||||
*/
|
||||
public function global_definition_after_data($mform) {
|
||||
global $SESSION;
|
||||
|
||||
// First thing, we need to decide on whether we should show the checkbox.
|
||||
$noproperty = !property_exists($SESSION, 'tool_mfa_factor_token');
|
||||
$nostate = $this->get_state() !== \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
|
||||
if ($noproperty && $nostate) {
|
||||
$expiry = get_config('factor_token', 'expiry');
|
||||
$expirystring = format_time($expiry);
|
||||
$mform->addElement('advcheckbox', 'factor_token_trust', '', get_string('form:trust', 'factor_token', $expirystring));
|
||||
$mform->setType('factor_token_trust', PARAM_BOOL);
|
||||
$mform->setDefault('factor_token_trust', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Token implementation.
|
||||
* Store information about the token status.
|
||||
*
|
||||
* @param object $data Data from the form.
|
||||
*/
|
||||
public function global_submit($data) {
|
||||
global $SESSION;
|
||||
|
||||
// Store any kind of response here, we shouldnt show again.
|
||||
$trust = $data->factor_token_trust;
|
||||
$SESSION->tool_mfa_factor_token = $trust;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token implementation.
|
||||
* Pass hook to set the cookie for use in subsequent auths.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function post_pass_state() {
|
||||
global $CFG, $SESSION, $USER;
|
||||
|
||||
if (!property_exists($SESSION, 'tool_mfa_factor_token')) {
|
||||
return;
|
||||
}
|
||||
$settoken = $SESSION->tool_mfa_factor_token;
|
||||
if (!$settoken) {
|
||||
return;
|
||||
}
|
||||
$cookie = 'MFA_TOKEN_' . $USER->id;
|
||||
|
||||
list($expirytime, $expiry) = $this->calculate_expiry_time();
|
||||
|
||||
// Store this secret in the database.
|
||||
$secretmanager = new secret_manager($this->name);
|
||||
$secret = base64_encode(random_bytes(256));
|
||||
$secretmanager->create_secret($expiry, false, $secret);
|
||||
|
||||
// All the prep is now done, we can set this cookie.
|
||||
setcookie($cookie, $secret, $expirytime, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, false, true);
|
||||
|
||||
// Finally emit a log event for storing the cookie.
|
||||
$state = [
|
||||
'expiry' => $expirytime,
|
||||
'cookie' => $cookie,
|
||||
];
|
||||
$event = \factor_token\event\token_created::token_created_event($USER, $state);
|
||||
$event->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the expiry time of the token, based on configuration.
|
||||
*
|
||||
* @param integer|null $basetime time to use for calcalations.
|
||||
* @return array
|
||||
*/
|
||||
public function calculate_expiry_time($basetime = null): array {
|
||||
if (empty($basetime)) {
|
||||
$basetime = time();
|
||||
}
|
||||
|
||||
// Calculate the expiry time. This is provided by config,
|
||||
// But optionally might need to be rounded to expire a few hours after 0000 server time.
|
||||
$expiry = get_config('factor_token', 'expiry');
|
||||
$expirytime = $basetime + $expiry;
|
||||
|
||||
// If expiring overnight, it should expire at 2am the following morning, if required.
|
||||
$expireovernight = get_config('factor_token', 'expireovernight');
|
||||
if ($expireovernight) {
|
||||
// Find out what 2am the following morning time is.
|
||||
$datetime = new \DateTime();
|
||||
$timezone = \core_date::get_user_timezone_object();
|
||||
|
||||
// Bit to ensure 'expireovernight' works when 'expire' is longer than one day.
|
||||
$difftime = 0;
|
||||
if ($expiry > DAYSECS) {
|
||||
// Ensures a safe amount of days is added before doing the 2am checks.
|
||||
$difftime = $expiry - DAYSECS;
|
||||
}
|
||||
|
||||
// Calculte the overnight expiry time, ignoring 'expiry' duration period.
|
||||
$workingexpirytime = $basetime + $difftime;
|
||||
$datetime->setTimezone($timezone);
|
||||
$datetime->setTimestamp($workingexpirytime);
|
||||
$datetime->add(new \DateInterval('P1D'));
|
||||
$datetime->setTime(2, 0); // Set the hour to 2am.
|
||||
|
||||
// Ensure whatever happens, ensure the expiry never goes over the default 'expiry' time.
|
||||
$overnightexpirytime = $datetime->getTimestamp();
|
||||
$expirytime = min($overnightexpirytime, $expirytime);
|
||||
$expiry = $expirytime - $basetime;
|
||||
}
|
||||
|
||||
return [$expirytime, $expiry];
|
||||
}
|
||||
}
|
42
admin/tool/mfa/factor/token/classes/privacy/provider.php
Normal file
42
admin/tool/mfa/factor/token/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_token\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_token
|
||||
* @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 {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
34
admin/tool/mfa/factor/token/lang/en/factor_token.php
Normal file
34
admin/tool/mfa/factor/token/lang/en/factor_token.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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_token
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['event:token_created'] = 'MFA token created.';
|
||||
$string['form:trust'] = 'Trust this device for {$a}.';
|
||||
$string['pluginname'] = 'Trust this device';
|
||||
$string['privacy:metadata'] = 'The token factor plugin does not store any personal data.';
|
||||
$string['settings:expireovernight'] = 'Expire trust overnight';
|
||||
$string['settings:expireovernight_help'] = 'This forces tokens to expire overnight, preventing mid-day interruptions for users. Instead they will be asked to MFA authenticate at the start of a day after expiry.';
|
||||
$string['settings:expiry'] = 'Trust duration';
|
||||
$string['settings:expiry_help'] = 'The duration a device is trusted before requiring a new MFA authentication.';
|
||||
$string['summarycondition'] = 'the user has previously trusted this device';
|
47
admin/tool/mfa/factor/token/settings.php
Normal file
47
admin/tool/mfa/factor/token/settings.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?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_token
|
||||
* @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();
|
||||
|
||||
$enabled = new admin_setting_configcheckbox('factor_token/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('token', get_config('factor_token', 'enabled') ? 'enable' : 'disable');
|
||||
});
|
||||
$settings->add($enabled);
|
||||
|
||||
$settings->add(new admin_setting_configtext('factor_token/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_token/expiry',
|
||||
new lang_string('settings:expiry', 'factor_token'),
|
||||
new lang_string('settings:expiry_help', 'factor_token'), DAYSECS));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('factor_token/expireovernight',
|
||||
new lang_string('settings:expireovernight', 'factor_token'),
|
||||
new lang_string('settings:expireovernight_help', 'factor_token'), 1));
|
274
admin/tool/mfa/factor/token/tests/factor_test.php
Normal file
274
admin/tool/mfa/factor/token/tests/factor_test.php
Normal file
|
@ -0,0 +1,274 @@
|
|||
<?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_token\tests;
|
||||
|
||||
/**
|
||||
* Tests for MFA manager class.
|
||||
*
|
||||
* @package factor_token
|
||||
* @author Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @author Kevin Pham <kevinpham@catalyst-au.net>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class factor_test extends \advanced_testcase {
|
||||
|
||||
public function setUp(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->factor = new \factor_token\factor('token');
|
||||
}
|
||||
|
||||
public function test_calculate_expiry_time_in_general() {
|
||||
$timestamp = 1642213800; // 1230 UTC.
|
||||
|
||||
set_config('expireovernight', 0, 'factor_token');
|
||||
$method = new \ReflectionMethod($this->factor, 'calculate_expiry_time');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test that non-overnight timestamps are just exactly as configured.
|
||||
// We don't need to care about 0 or negative ints, they will just make the cookie expire immediately.
|
||||
$expiry = $method->invoke($this->factor, $timestamp);
|
||||
$this->assertEquals($expiry[1], DAYSECS);
|
||||
|
||||
set_config('expiry', HOURSECS, 'factor_token');
|
||||
$expiry = $method->invoke($this->factor, $timestamp);
|
||||
$this->assertGreaterThan(HOURSECS - 30, $expiry[1]);
|
||||
$this->assertLessThan(HOURSECS + 30, $expiry[1]);
|
||||
|
||||
set_config('expireovernight', 1, 'factor_token');
|
||||
// Manually calculate the next reset time.
|
||||
$reset = strtotime('tomorrow 0200', $timestamp);
|
||||
$resetdelta = $reset - $timestamp;
|
||||
// Confirm that a timestamp that doesnt reach reset time.
|
||||
if ($timestamp + HOURSECS < $reset) {
|
||||
$expiry = $method->invoke($this->factor, $timestamp);
|
||||
$this->assertGreaterThan(HOURSECS - 30, $expiry[1]);
|
||||
$this->assertLessThan(HOURSECS + 30, $expiry[1]);
|
||||
}
|
||||
|
||||
set_config('expiry', 2 * DAYSECS, 'factor_token');
|
||||
// Now confirm that the returned expiry is less than the absolute amount.
|
||||
$expiry = $method->invoke($this->factor, $timestamp);
|
||||
$this->assertGreaterThan(DAYSECS, $expiry[1]);
|
||||
$this->assertLessThan(2 * DAYSECS, $expiry[1]);
|
||||
$this->assertGreaterThan($resetdelta + DAYSECS - 30, $expiry[1]);
|
||||
$this->assertLessThan($resetdelta + DAYSECS + 30, $expiry[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything should end at 2am unless adding the hours lands it between
|
||||
* 0 <= x < 2am, which in that case it should just expire using the raw
|
||||
* value, provided it never goes past raw value expiry time, and when it
|
||||
* needs to be 2am, it's 2am on the following morning.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* @dataProvider timestamp_provider
|
||||
*/
|
||||
public function test_calculate_expiry_time_for_overnight_expiry_with_one_day_expiry($timestamp) {
|
||||
// Setup configuration.
|
||||
$method = new \ReflectionMethod($this->factor, 'calculate_expiry_time');
|
||||
$method->setAccessible(true);
|
||||
set_config('expireovernight', 1, 'factor_token');
|
||||
set_config('expiry', DAYSECS, 'factor_token');
|
||||
|
||||
// All the results here, should be for 2am the following morning from the timestamp provided.
|
||||
$expiry = $method->invoke($this->factor, $timestamp);
|
||||
list($expiresat, $secondstillexpiry) = $expiry;
|
||||
|
||||
// Calculate the expected raw expiry if not considering 'overnight'.
|
||||
$timezone = \core_date::get_user_timezone_object();
|
||||
$datetime = new \DateTime();
|
||||
$datetime->setTimezone($timezone);
|
||||
|
||||
$rawexpiry = $timestamp + DAYSECS;
|
||||
$datetime->setTimestamp($rawexpiry);
|
||||
$rawhour = $datetime->format('H');
|
||||
$rawminute = $datetime->format('m');
|
||||
|
||||
// Sanity check, that the $secondstillexpiry is in the appropriate ranges.
|
||||
$this->assertGreaterThan(0, $secondstillexpiry);
|
||||
$this->assertLessThan(DAYSECS + 1, $secondstillexpiry);
|
||||
|
||||
if ($rawhour >= 0 && $rawhour < 2 || $rawhour == 2 && $rawminute == 0) {
|
||||
// Should just use expiry time, if the hours will land between 0 and 2am.
|
||||
$this->assertEquals($datetime->getTimestamp(), $expiresat);
|
||||
// Ensure the $secondstillexpiry is calculated correctly.
|
||||
$this->assertEquals($expiresat - $timestamp, $secondstillexpiry);
|
||||
} else {
|
||||
// Otherwise it should fall on 2am the following day.
|
||||
$followingdayattwoam = strtotime('tomorrow 0200', $timestamp);
|
||||
$this->assertEquals($followingdayattwoam, $expiresat);
|
||||
// Ensure the $secondstillexpiry is calculated correctly.
|
||||
$this->assertEquals($followingdayattwoam - $timestamp, $secondstillexpiry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything should end at 2am unless adding the hours lands it between
|
||||
* 0 <= x < 2am, which in that case it should just expire using the raw
|
||||
* value, provided it never goes past raw value expiry time, and when it
|
||||
* needs to be 2am, it's 2am on the morning after tomorrow.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* @dataProvider timestamp_provider
|
||||
*/
|
||||
public function test_calculate_expiry_time_for_overnight_expiry_with_two_day_expiry($timestamp) {
|
||||
// Setup configuration.
|
||||
$method = new \ReflectionMethod($this->factor, 'calculate_expiry_time');
|
||||
$method->setAccessible(true);
|
||||
set_config('expireovernight', 1, 'factor_token');
|
||||
set_config('expiry', 2 * DAYSECS, 'factor_token');
|
||||
|
||||
// All the results here, should be for 2am the following morning from the timestamp provided.
|
||||
$expiry = $method->invoke($this->factor, $timestamp);
|
||||
list($expiresat, $secondstillexpiry) = $expiry;
|
||||
|
||||
// Calculate the expected raw expiry if not considering 'overnight'.
|
||||
$timezone = \core_date::get_user_timezone_object();
|
||||
$datetime = new \DateTime();
|
||||
$datetime->setTimezone($timezone);
|
||||
|
||||
$rawexpiry = $timestamp + (2 * DAYSECS);
|
||||
$datetime->setTimestamp($rawexpiry);
|
||||
$rawhour = $datetime->format('H');
|
||||
$rawminute = $datetime->format('m');
|
||||
|
||||
// Sanity check, that the $secondstillexpiry is in the appropriate ranges.
|
||||
$this->assertGreaterThan(0, $secondstillexpiry);
|
||||
$this->assertLessThan((2 * DAYSECS) + 1, $secondstillexpiry);
|
||||
|
||||
if ($rawhour >= 0 && $rawhour < 2 || $rawhour == 2 && $rawminute == 0) {
|
||||
// Should just use expiry time, if the hours will land between 0 and 2am.
|
||||
$this->assertEquals($datetime->getTimestamp(), $expiresat);
|
||||
// Ensure the $secondstillexpiry is calculated correctly.
|
||||
$this->assertEquals($expiresat - $timestamp, $secondstillexpiry);
|
||||
} else {
|
||||
// Otherwise it should fall on 2am the following day after tomorrow.
|
||||
$followingdayattwoam = strtotime('tomorrow 0200', $timestamp) + DAYSECS;
|
||||
$this->assertEquals($followingdayattwoam, $expiresat);
|
||||
// Ensure the $secondstillexpiry is calculated correctly.
|
||||
$this->assertEquals($followingdayattwoam - $timestamp, $secondstillexpiry);
|
||||
}
|
||||
|
||||
// Expiry should always be more than one day for an expiry duration of
|
||||
// more than 1 day, but the overnight check should apply for the
|
||||
// duration of the final night.
|
||||
$this->assertGreaterThan(DAYSECS, $secondstillexpiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* This should check if the 3am expiry is pushed back to 2am as expected, but everything else appears as expected
|
||||
*
|
||||
* @param int $timestamp
|
||||
* @dataProvider timestamp_provider
|
||||
*/
|
||||
public function test_calculate_expiry_time_for_overnight_expiry_with_three_hour_expiry($timestamp) {
|
||||
// Setup configuration.
|
||||
$method = new \ReflectionMethod($this->factor, 'calculate_expiry_time');
|
||||
$method->setAccessible(true);
|
||||
set_config('expireovernight', 1, 'factor_token');
|
||||
set_config('expiry', 3 * HOURSECS, 'factor_token');
|
||||
|
||||
// All the results here, should be for 2am the following morning from the timestamp provided.
|
||||
$expiry = $method->invoke($this->factor, $timestamp);
|
||||
list($expiresat, $secondstillexpiry) = $expiry;
|
||||
|
||||
// Calculate the expected raw expiry if not considering 'overnight'.
|
||||
$timezone = \core_date::get_user_timezone_object();
|
||||
$datetime = new \DateTime();
|
||||
$datetime->setTimezone($timezone);
|
||||
|
||||
$rawexpiry = $timestamp + (3 * HOURSECS);
|
||||
$datetime->setTimestamp($rawexpiry);
|
||||
|
||||
// Sanity check, that the $secondstillexpiry is in the appropriate ranges.
|
||||
$this->assertGreaterThan(0, $secondstillexpiry);
|
||||
$this->assertLessThan((3 * HOURSECS) + 1, $secondstillexpiry);
|
||||
|
||||
// If the raw timestamp of the expiry, is less than tomorrow at 2am,
|
||||
// then use the raw expiry time.
|
||||
$followingdayattwoam = strtotime('tomorrow 0200', $timestamp);
|
||||
if ($datetime->getTimestamp() < $followingdayattwoam) {
|
||||
$this->assertEquals($datetime->getTimestamp(), $expiresat);
|
||||
// Ensure the $secondstillexpiry is calculated correctly.
|
||||
$this->assertEquals($expiresat - $timestamp, $secondstillexpiry);
|
||||
} else {
|
||||
// Otherwsie it should be pushed back to 2am.
|
||||
$this->assertEquals($followingdayattwoam, $expiresat);
|
||||
// Ensure the $secondstillexpiry is calculated correctly.
|
||||
$this->assertEquals($followingdayattwoam - $timestamp, $secondstillexpiry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only relevant based on the hour padding used, which is currently set to 2 hours (2am).
|
||||
*
|
||||
* @param int $timestamp
|
||||
* @dataProvider timestamp_provider
|
||||
*/
|
||||
public function test_calculate_expiry_time_for_overnight_expiry_with_an_hour_expiry($timestamp) {
|
||||
// Setup configuration.
|
||||
$method = new \ReflectionMethod($this->factor, 'calculate_expiry_time');
|
||||
$method->setAccessible(true);
|
||||
set_config('expireovernight', 1, 'factor_token');
|
||||
set_config('expiry', HOURSECS, 'factor_token');
|
||||
|
||||
// All the results here, should be for 2am the following morning from the timestamp provided.
|
||||
$expiry = $method->invoke($this->factor, $timestamp);
|
||||
list($expiresat, $secondstillexpiry) = $expiry;
|
||||
|
||||
// Calculate the expected raw expiry if not considering 'overnight'.
|
||||
$timezone = \core_date::get_user_timezone_object();
|
||||
$datetime = new \DateTime();
|
||||
$datetime->setTimezone($timezone);
|
||||
|
||||
$rawexpiry = $timestamp + HOURSECS;
|
||||
$datetime->setTimestamp($rawexpiry);
|
||||
|
||||
// Sanity check, that the $secondstillexpiry is in the appropriate ranges.
|
||||
$this->assertGreaterThan(0, $secondstillexpiry);
|
||||
$this->assertLessThan(HOURSECS + 1, $secondstillexpiry);
|
||||
|
||||
// If the raw timestamp of the expiry, is less than tomorrow at 2am,
|
||||
// then use the raw expiry time.
|
||||
$followingdayattwoam = strtotime('tomorrow 0200', $timestamp);
|
||||
if ($datetime->getTimestamp() < $followingdayattwoam) {
|
||||
$this->assertEquals($datetime->getTimestamp(), $expiresat);
|
||||
// Ensure the $secondstillexpiry is calculated correctly.
|
||||
$this->assertEquals($expiresat - $timestamp, $secondstillexpiry);
|
||||
} else {
|
||||
// Otherwsie it should be pushed back to 2am.
|
||||
$this->assertEquals($followingdayattwoam, $expiresat);
|
||||
// Ensure the $secondstillexpiry is calculated correctly.
|
||||
$this->assertEquals($followingdayattwoam - $timestamp, $secondstillexpiry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps for a 24 hour period starting from a fixed time.
|
||||
* Increments by 30 minutes to cover half hour and hour cases.
|
||||
* Starting timestamp: 2022-01-15 07:30:00 Australia/Melbourne time.
|
||||
*/
|
||||
public function timestamp_provider() {
|
||||
$starttimestamp = 1642192200;
|
||||
foreach (range(0, 23) as $i) {
|
||||
$timestamps[] = [$starttimestamp + ($i * HOURSECS)];
|
||||
$timestamps[] = [$starttimestamp + ($i * HOURSECS) + (30 * MINSECS)];
|
||||
}
|
||||
return $timestamps;
|
||||
}
|
||||
}
|
34
admin/tool/mfa/factor/token/version.php
Normal file
34
admin/tool/mfa/factor/token/version.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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_token
|
||||
* @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 = 2022011700; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
|
||||
$plugin->component = 'factor_token';
|
||||
$plugin->release = 2022011700;
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->dependencies = ['tool_mfa' => 2019102400];
|
448
admin/tool/mfa/factor/totp/classes/factor.php
Normal file
448
admin/tool/mfa/factor/totp/classes/factor.php
Normal file
|
@ -0,0 +1,448 @@
|
|||
<?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_totp;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/tcpdf/tcpdf_barcodes_2d.php');
|
||||
require_once(__DIR__.'/../extlib/OTPHP/OTPInterface.php');
|
||||
require_once(__DIR__.'/../extlib/OTPHP/TOTPInterface.php');
|
||||
require_once(__DIR__.'/../extlib/OTPHP/ParameterTrait.php');
|
||||
require_once(__DIR__.'/../extlib/OTPHP/OTP.php');
|
||||
require_once(__DIR__.'/../extlib/OTPHP/TOTP.php');
|
||||
|
||||
require_once(__DIR__.'/../extlib/Assert/Assertion.php');
|
||||
require_once(__DIR__.'/../extlib/Assert/AssertionFailedException.php');
|
||||
require_once(__DIR__.'/../extlib/Assert/InvalidArgumentException.php');
|
||||
require_once(__DIR__.'/../extlib/ParagonIE/ConstantTime/EncoderInterface.php');
|
||||
require_once(__DIR__.'/../extlib/ParagonIE/ConstantTime/Binary.php');
|
||||
require_once(__DIR__.'/../extlib/ParagonIE/ConstantTime/Base32.php');
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
use OTPHP\TOTP;
|
||||
|
||||
/**
|
||||
* TOTP factor class.
|
||||
*
|
||||
* @package factor_totp
|
||||
* @subpackage tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class factor extends object_factor_base {
|
||||
|
||||
/** @var string */
|
||||
const TOTP_OLD = 'old';
|
||||
|
||||
/** @var string */
|
||||
const TOTP_FUTURE = 'future';
|
||||
|
||||
/** @var string */
|
||||
const TOTP_USED = 'used';
|
||||
|
||||
/** @var string */
|
||||
const TOTP_VALID = 'valid';
|
||||
|
||||
/** @var string */
|
||||
const TOTP_INVALID = 'invalid';
|
||||
|
||||
/**
|
||||
* Generates TOTP URI for given secret key.
|
||||
* Uses site name, hostname and user name to make GA account look like:
|
||||
* "Sitename hostname (username)".
|
||||
*
|
||||
* @param string $secret
|
||||
* @return string
|
||||
*/
|
||||
public function generate_totp_uri($secret) {
|
||||
global $USER, $SITE, $CFG;
|
||||
$host = parse_url($CFG->wwwroot, PHP_URL_HOST);
|
||||
$sitename = str_replace(':', '', $SITE->fullname);
|
||||
$issuer = $sitename.' '.$host;
|
||||
$totp = TOTP::create($secret);
|
||||
$totp->setLabel($USER->username);
|
||||
$totp->setIssuer($issuer);
|
||||
return $totp->getProvisioningUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML sting with QR code for given secret key.
|
||||
*
|
||||
* @param string $secret
|
||||
* @return string
|
||||
*/
|
||||
public function generate_qrcode($secret) {
|
||||
$uri = $this->generate_totp_uri($secret);
|
||||
$qrcode = new \TCPDF2DBarcode($uri, 'QRCODE');
|
||||
$image = $qrcode->getBarcodePngData(7, 7);
|
||||
$html = \html_writer::tag('p', get_string('setupfactor:scanwithapp', 'factor_totp'));
|
||||
$html .= \html_writer::img('data:image/png;base64,' . base64_encode($image), '', ['width' => '150px']);
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP state
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_state() {
|
||||
global $USER;
|
||||
$userfactors = $this->get_active_user_factors($USER);
|
||||
|
||||
// If no codes are setup then we must be neutral not unknown.
|
||||
if (count($userfactors) == 0) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
}
|
||||
|
||||
return parent::get_state();
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
*/
|
||||
public function setup_factor_form_definition($mform) {
|
||||
$secret = $this->generate_secret_code();
|
||||
$mform->addElement('hidden', 'secret', $secret);
|
||||
$mform->setType('secret', PARAM_ALPHANUM);
|
||||
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
*/
|
||||
public function setup_factor_form_definition_after_data($mform) {
|
||||
global $OUTPUT, $SITE, $USER;
|
||||
|
||||
// Array of elements to allow XSS on when we're running Totara.
|
||||
$xssallowedelements = [];
|
||||
|
||||
$mform->addElement('html', $OUTPUT->heading(get_string('setupfactor', 'factor_totp'), 2));
|
||||
$mform->addElement('html', \html_writer::tag('p', get_string('info', 'factor_totp')));
|
||||
$mform->addElement('html', \html_writer::tag('hr', ''));
|
||||
|
||||
$mform->addElement('text', 'devicename', get_string('devicename', 'factor_totp'), [
|
||||
'placeholder' => get_string('devicenameexample', 'factor_totp'),
|
||||
'autofocus' => 'autofocus',
|
||||
]);
|
||||
$mform->addHelpButton('devicename', 'devicename', 'factor_totp');
|
||||
$mform->setType('devicename', PARAM_TEXT);
|
||||
$mform->addRule('devicename', get_string('required'), 'required', null, 'client');
|
||||
|
||||
// Scan.
|
||||
$secretfield = $mform->getElement('secret');
|
||||
$secret = $secretfield->getValue();
|
||||
$qrcode = $this->generate_qrcode($secret);
|
||||
|
||||
$html = \html_writer::tag('p', $qrcode);
|
||||
$xssallowedelements[] = $mform->addElement('static', 'scan', get_string('setupfactor:scan', 'factor_totp'), $html);
|
||||
|
||||
// Link.
|
||||
if (get_config('factor_totp', 'totplink')) {
|
||||
$uri = $this->generate_totp_uri($secret);
|
||||
$html = $OUTPUT->action_link($uri, get_string('setupfactor:linklabel', 'factor_totp'));
|
||||
$xssallowedelements[] = $mform->addElement('static', 'link', get_string('setupfactor:link', 'factor_totp'), $html);
|
||||
$mform->addHelpButton('link', 'setupfactor:link', 'factor_totp');
|
||||
}
|
||||
|
||||
// Enter manually.
|
||||
$secret = wordwrap($secret, 4, ' ', true) . '</code>';
|
||||
$secret = \html_writer::tag('code', $secret);
|
||||
|
||||
$manualtable = new \html_table();
|
||||
$manualtable->id = 'manualattributes';
|
||||
$manualtable->attributes['class'] = 'generaltable table table-bordered table-sm w-auto';
|
||||
$manualtable->attributes['style'] = 'width: auto;';
|
||||
$manualtable->data = [
|
||||
[get_string('setupfactor:key', 'factor_totp'), $secret],
|
||||
[get_string('setupfactor:account', 'factor_totp'), "$SITE->fullname ($USER->username)"],
|
||||
[get_string('setupfactor:mode', 'factor_totp'), get_string('setupfactor:mode:timebased', 'factor_totp')],
|
||||
];
|
||||
|
||||
$html = \html_writer::table($manualtable);
|
||||
$html = \html_writer::tag('p', get_string('setupfactor:enter', 'factor_totp')) . $html;
|
||||
// Wrap the table in a couple of divs to be controlled via bootstrap.
|
||||
$html = \html_writer::div($html, 'card card-body', ['style' => 'padding-left: 0 !important;']);
|
||||
$html = \html_writer::div($html, 'collapse', ['id' => 'collapseManualAttributes']);
|
||||
|
||||
$togglelink = \html_writer::tag('btn', get_string('setupfactor:scanfail', 'factor_totp'), [
|
||||
'class' => 'btn btn-secondary',
|
||||
'type' => 'button',
|
||||
'data-toggle' => 'collapse',
|
||||
'data-target' => '#collapseManualAttributes',
|
||||
'aria-expanded' => 'false',
|
||||
'aria-controls' => 'collapseManualAttributes',
|
||||
'style' => 'font-size: 14px;',
|
||||
]);
|
||||
|
||||
$html = $togglelink . $html;
|
||||
$xssallowedelements[] = $mform->addElement('static', 'enter', '', $html);
|
||||
|
||||
// Allow XSS on Totara.
|
||||
if (method_exists('MoodleQuickForm_static', 'set_allow_xss')) {
|
||||
foreach ($xssallowedelements as $xssallowedelement) {
|
||||
$xssallowedelement->set_allow_xss(true);
|
||||
}
|
||||
}
|
||||
|
||||
$mform->addElement(new \tool_mfa\local\form\verification_field(null, false));
|
||||
$mform->setType('verificationcode', PARAM_ALPHANUM);
|
||||
$mform->addHelpButton('verificationcode', 'verificationcode', 'factor_totp');
|
||||
$mform->addRule('verificationcode', get_string('required'), 'required', null, 'client');
|
||||
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function setup_factor_form_validation($data) {
|
||||
$errors = [];
|
||||
|
||||
$totp = TOTP::create($data['secret']);
|
||||
if (!$totp->verify($data['verificationcode'], time(), 1)) {
|
||||
$errors['verificationcode'] = get_string('error:wrongverification', 'factor_totp');
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
*/
|
||||
public function login_form_definition($mform) {
|
||||
|
||||
$mform->disable_form_change_checker();
|
||||
$mform->addElement(new \tool_mfa\local\form\verification_field());
|
||||
$mform->setType('verificationcode', PARAM_ALPHANUM);
|
||||
$mform->addHelpButton('verificationcode', 'verificationcode', 'factor_totp');
|
||||
|
||||
return $mform;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function login_form_validation($data) {
|
||||
global $USER;
|
||||
$factors = $this->get_active_user_factors($USER);
|
||||
$result = ['verificationcode' => get_string('error:wrongverification', 'factor_totp')];
|
||||
$windowconfig = get_config('factor_totp', 'window');
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
$totp = TOTP::create($factor->secret);
|
||||
// Convert seconds to windows.
|
||||
$window = (int) floor($windowconfig / $totp->getPeriod());
|
||||
$factorresult = $this->validate_code($data['verificationcode'], $window, $totp, $factor);
|
||||
$time = userdate(time(), get_string('systimeformat', 'factor_totp'));
|
||||
|
||||
switch ($factorresult) {
|
||||
case self::TOTP_USED:
|
||||
return ['verificationcode' => get_string('error:codealreadyused', 'factor_totp')];
|
||||
|
||||
case self::TOTP_OLD:
|
||||
return ['verificationcode' => get_string('error:oldcode', 'factor_totp', $time)];
|
||||
|
||||
case self::TOTP_FUTURE:
|
||||
return ['verificationcode' => get_string('error:futurecode', 'factor_totp', $time)];
|
||||
|
||||
case self::TOTP_VALID:
|
||||
$this->update_lastverified($factor->id);
|
||||
return [];
|
||||
|
||||
default:
|
||||
continue(2);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the code for reuse, clock skew, and validity.
|
||||
*
|
||||
* @param string $code the code to check.
|
||||
* @param int $window the window to check validity for.
|
||||
* @param TOTP $totp the totp object to check against.
|
||||
* @param stdClass $factor the factor with information required.
|
||||
*
|
||||
* @return const constant with verification state.
|
||||
*/
|
||||
public function validate_code($code, $window, $totp, $factor) {
|
||||
// First check if this code matches the last verified timestamp.
|
||||
$lastverified = $this->get_lastverified($factor->id);
|
||||
if ($lastverified > 0 && $totp->verify($code, $lastverified, $window)) {
|
||||
return self::TOTP_USED;
|
||||
}
|
||||
|
||||
// The window in which to check for clock skew, 5 increments past valid window.
|
||||
$skewwindow = $window + 5;
|
||||
$pasttimestamp = time() - ($skewwindow * $totp->getPeriod());
|
||||
$futuretimestamp = time() + ($skewwindow * $totp->getPeriod());
|
||||
|
||||
if ($totp->verify($code, time(), $window)) {
|
||||
return self::TOTP_VALID;
|
||||
} else if ($totp->verify($code, $pasttimestamp, $skewwindow)) {
|
||||
// Check for clock skew in the past 10 periods.
|
||||
return self::TOTP_OLD;
|
||||
} else if ($totp->verify($code, $futuretimestamp, $skewwindow)) {
|
||||
// Check for clock skew in the future 10 periods.
|
||||
return self::TOTP_FUTURE;
|
||||
} else {
|
||||
// In all other cases, code is invalid.
|
||||
return self::TOTP_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates cryptographically secure pseudo-random 16-digit secret code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate_secret_code() {
|
||||
$totp = TOTP::create();
|
||||
return substr($totp->getSecret(), 0, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
*
|
||||
* @param stdClass $data
|
||||
* @return stdClass the factor record, or null.
|
||||
*/
|
||||
public function setup_user_factor($data) {
|
||||
global $DB, $USER;
|
||||
|
||||
if (!empty($data->secret)) {
|
||||
$row = new \stdClass();
|
||||
$row->userid = $USER->id;
|
||||
$row->factor = $this->name;
|
||||
$row->secret = $data->secret;
|
||||
$row->label = $data->devicename;
|
||||
$row->timecreated = time();
|
||||
$row->createdfromip = $USER->lastip;
|
||||
$row->timemodified = time();
|
||||
$row->lastverified = 0;
|
||||
$row->revoked = 0;
|
||||
|
||||
// Check if a record with this configuration already exists, warning the user accordingly.
|
||||
$record = $DB->get_record('tool_mfa', [
|
||||
'userid' => $row->userid,
|
||||
'secret' => $row->secret,
|
||||
'factor' => $row->factor,
|
||||
], '*', IGNORE_MULTIPLE);
|
||||
if ($record) {
|
||||
\core\notification::warning(get_string('error:alreadyregistered', 'factor_totp'));
|
||||
return $record;
|
||||
}
|
||||
|
||||
$id = $DB->insert_record('tool_mfa', $row);
|
||||
$record = $DB->get_record('tool_mfa', ['id' => $id]);
|
||||
$this->create_event_after_factor_setup($USER);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
*
|
||||
* @param stdClass $user the user to check against.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_user_factors($user) {
|
||||
global $DB;
|
||||
return $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_revoke() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function has_setup() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function show_setup_buttons() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
* Empty override of parent.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function post_pass_state() {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
* TOTP cannot return fail state.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
*/
|
||||
public function possible_states($user) {
|
||||
return [
|
||||
\tool_mfa\plugininfo\factor::STATE_PASS,
|
||||
\tool_mfa\plugininfo\factor::STATE_NEUTRAL,
|
||||
\tool_mfa\plugininfo\factor::STATE_UNKNOWN,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP Factor implementation.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_setup_string() {
|
||||
return get_string('factorsetup', 'factor_totp');
|
||||
}
|
||||
}
|
42
admin/tool/mfa/factor/totp/classes/privacy/provider.php
Normal file
42
admin/tool/mfa/factor/totp/classes/privacy/provider.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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_totp\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
use core_privacy\local\legacy_polyfill;
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package factor_totp
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements null_provider {
|
||||
use legacy_polyfill;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
2825
admin/tool/mfa/factor/totp/extlib/Assert/Assertion.php
Normal file
2825
admin/tool/mfa/factor/totp/extlib/Assert/Assertion.php
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue