Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace tool_mfa\local;
  18  
  19  use tool_mfa\local\factor\object_factor_base;
  20  
  21  defined('MOODLE_INTERNAL') || die();
  22  
  23  require_once($CFG->libdir.'/ddllib.php');
  24  require_once($CFG->libdir.'/xmlize.php');
  25  require_once($CFG->libdir.'/messagelib.php');
  26  
  27  /**
  28   * Admin setting for MFA.
  29   *
  30   * @package     tool_mfa
  31   * @author      Mikhail Golenkov <golenkovm@gmail.com>
  32   * @copyright   Catalyst IT
  33   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class admin_setting_managemfa extends \admin_setting {
  36  
  37      /**
  38       * Calls parent::__construct with specific arguments
  39       */
  40      public function __construct() {
  41          $this->nosave = true;
  42          parent::__construct('mfaui', get_string('mfasettings', 'tool_mfa'), '', '');
  43      }
  44  
  45      /**
  46       * Always returns true
  47       *
  48       * @return bool
  49       */
  50      public function get_setting(): bool {
  51          return true;
  52      }
  53  
  54      /**
  55       * Always returns '' and doesn't write anything
  56       *
  57       * @param mixed $data
  58       * @return string Always returns ''
  59       */
  60      public function write_setting($data): string {
  61          return '';
  62      }
  63  
  64      /**
  65       * Returns XHTML to display Manage MFA admin page.
  66       *
  67       * @param mixed $data Unused
  68       * @param string $query
  69       *
  70       * @return string highlight
  71       * @throws \coding_exception
  72       * @throws \moodle_exception
  73       */
  74      public function output_html($data, $query=''): string {
  75          global $OUTPUT;
  76  
  77          $return = $OUTPUT->box_start('generalbox');
  78          $return .= $this->define_manage_mfa_table();
  79          $return .= $OUTPUT->box_end();
  80  
  81          $return .= $OUTPUT->heading(get_string('settings:combinations', 'tool_mfa'), 3);
  82          $return .= $OUTPUT->box_start('generalbox');
  83          $return .= $this->define_factor_combinations_table();
  84          $return .= $OUTPUT->box_end();
  85  
  86          return highlight($query, $return);
  87      }
  88  
  89      /**
  90       * Defines main table with configurable factors.
  91       *
  92       * @return string HTML code
  93       * @throws \coding_exception
  94       * @throws \moodle_exception
  95       */
  96      public function define_manage_mfa_table() {
  97          global $OUTPUT;
  98          $sesskey = sesskey();
  99  
 100          $txt = get_strings(['enable', 'disable', 'moveup', 'movedown', 'order', 'settings']);
 101          $txt->factor = get_string('factor', 'tool_mfa');
 102          $txt->weight = get_string('weight', 'tool_mfa');
 103          $txt->setup = get_string('setuprequired', 'tool_mfa');
 104          $txt->input = get_string('inputrequired', 'tool_mfa');
 105  
 106          $table = new \html_table();
 107          $table->id = 'managemfatable';
 108          $table->attributes['class'] = 'admintable generaltable';
 109          $table->head  = [
 110              $txt->factor,
 111              $txt->enable,
 112              $txt->order,
 113              $txt->weight,
 114              $txt->settings,
 115              $txt->setup,
 116              $txt->input,
 117          ];
 118          $table->colclasses = ['leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign'];
 119          $table->data  = [];
 120  
 121          $factors = \tool_mfa\plugininfo\factor::get_factors();
 122          $enabledfactors = \tool_mfa\plugininfo\factor::get_enabled_factors();
 123          $order = 1;
 124  
 125          foreach ($factors as $factor) {
 126              $settingsparams = ['section' => 'factor_'.$factor->name];
 127              $settingsurl = new \moodle_url('settings.php', $settingsparams);
 128              $settingslink = \html_writer::link($settingsurl, $txt->settings);
 129  
 130              if ($factor->is_enabled()) {
 131                  $hideshowparams = ['action' => 'disable', 'factor' => $factor->name, 'sesskey' => $sesskey];
 132                  $hideshowurl = new \moodle_url('tool/mfa/index.php', $hideshowparams);
 133                  $hideshowlink = \html_writer::link($hideshowurl, $OUTPUT->pix_icon('t/hide', $txt->disable));
 134                  $class = '';
 135  
 136                  if ($order > 1) {
 137                      $upparams = ['action' => 'up', 'factor' => $factor->name, 'sesskey' => $sesskey];
 138                      $upurl = new \moodle_url('tool/mfa/index.php', $upparams);
 139                      $uplink = \html_writer::link($upurl, $OUTPUT->pix_icon('t/up', $txt->moveup));
 140                  } else {
 141                      $uplink = \html_writer::link('', $uplink = $OUTPUT->spacer(['style' => 'margin-right: .5rem']));
 142                  }
 143  
 144                  if ($order < count($enabledfactors)) {
 145                      $downparams = ['action' => 'down', 'factor' => $factor->name, 'sesskey' => $sesskey];
 146                      $downurl = new \moodle_url('tool/mfa/index.php', $downparams);
 147                      $downlink = \html_writer::link($downurl, $OUTPUT->pix_icon('t/down', $txt->movedown));
 148                  } else {
 149                      $downlink = '';
 150                  }
 151                  $updownlink = $uplink.$downlink;
 152                  $order++;
 153              } else {
 154                  $hideshowparams = ['action' => 'enable', 'factor' => $factor->name, 'sesskey' => $sesskey];
 155                  $hideshowurl = new \moodle_url('tool/mfa/index.php', $hideshowparams);
 156                  $hideshowlink = \html_writer::link($hideshowurl, $OUTPUT->pix_icon('t/show', $txt->enable));
 157                  $class = 'dimmed_text';
 158                  $updownlink = '';
 159              }
 160  
 161              $hassetup = $factor->has_setup() ? get_string('yes') : get_string('no');
 162              $hasinput = $factor->has_input() ? get_string('yes') : get_string('no');
 163  
 164              $rowarray = [
 165                  $factor->get_display_name(),
 166                  $hideshowlink,
 167                  $updownlink,
 168                  $factor->get_weight(),
 169                  $settingslink,
 170                  $hassetup,
 171                  $hasinput,
 172              ];
 173              $row = new \html_table_row($rowarray);
 174              $row->attributes['class'] = $class;
 175  
 176              $table->data[] = $row;
 177          }
 178  
 179          return \html_writer::table($table);
 180      }
 181  
 182      /**
 183       * Defines supplementary table that shows available combinations of factors enough for successful authentication.
 184       *
 185       * @return string HTML code
 186       */
 187      public function define_factor_combinations_table() {
 188          global $OUTPUT;
 189  
 190          $factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
 191          $combinations = $this->get_factor_combinations($factors, 0, count($factors) - 1);
 192  
 193          if (empty($combinations)) {
 194              return $OUTPUT->notification(get_string('error:notenoughfactors', 'tool_mfa'), 'notifyproblem');
 195          }
 196  
 197          $txt = get_strings(['combination', 'totalweight'], 'tool_mfa');
 198          $table = new \html_table();
 199          $table->id = 'managemfatable';
 200          $table->attributes['class'] = 'admintable generaltable table table-bordered';
 201          $table->head  = [$txt->combination, $txt->totalweight];
 202          $table->colclasses = ['leftalign', 'centeralign'];
 203          $table->data  = [];
 204  
 205          $factorstringconnector = get_string('connector', 'tool_mfa');
 206          foreach ($combinations as $combination) {
 207              $factorstrings = array_map(static function(object_factor_base $factor): string {
 208                  return $factor->get_summary_condition() . ' <sup>' . $factor->get_weight() . '</sup>';
 209              }, $combination['combination']);
 210  
 211              $string = implode(" {$factorstringconnector} ", $factorstrings);
 212              $table->data[] = new \html_table_row([$string, $combination['totalweight']]);
 213          }
 214  
 215          return \html_writer::table($table);
 216      }
 217  
 218      /**
 219       * Recursive method to get all possible combinations of given factors.
 220       * Output is filtered by combination total weight (should be greater than 100).
 221       *
 222       * @param array $allfactors initial array of factor objects
 223       * @param int $start start position in initial array
 224       * @param int $end end position in initial array
 225       * @param int $totalweight total weight of combination
 226       * @param array $combination combination candidate
 227       * @param array $result array that includes combination total weight and subarray of factors combination
 228       *
 229       * @return array
 230       */
 231      public function get_factor_combinations($allfactors, $start = 0, $end = 0,
 232          $totalweight = 0, $combination = [], $result = []) {
 233  
 234          if ($totalweight >= 100) {
 235              // Ensure this is a valid combination before appending result.
 236              $valid = true;
 237              foreach ($combination as $factor) {
 238                  if (!$factor->check_combination($combination)) {
 239                      $valid = false;
 240                  }
 241              }
 242              if ($valid) {
 243                  $result[] = ['totalweight' => $totalweight, 'combination' => $combination];
 244              }
 245              return $result;
 246          } else if ($start > $end) {
 247              return $result;
 248          }
 249  
 250          $combinationnext = $combination;
 251          $combinationnext[] = $allfactors[$start];
 252  
 253          $result = $this->get_factor_combinations(
 254              $allfactors,
 255              $start + 1,
 256              $end,
 257              $totalweight + $allfactors[$start]->get_weight(),
 258              $combinationnext,
 259              $result);
 260  
 261          $result = $this->get_factor_combinations(
 262              $allfactors,
 263              $start + 1,
 264              $end,
 265              $totalweight,
 266              $combination,
 267              $result);
 268  
 269          return $result;
 270      }
 271  }