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\plugininfo; 18 19 use moodle_url; 20 use stdClass; 21 22 /** 23 * Subplugin info class. 24 * 25 * @package tool_mfa 26 * @author Mikhail Golenkov <golenkovm@gmail.com> 27 * @copyright Catalyst IT 28 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 29 */ 30 class factor extends \core\plugininfo\base { 31 32 /** @var string */ 33 const STATE_UNKNOWN = 'unknown'; 34 35 /** @var string */ 36 const STATE_PASS = 'pass'; 37 38 /** @var string */ 39 const STATE_FAIL = 'fail'; 40 41 /** @var string */ 42 const STATE_NEUTRAL = 'neutral'; 43 44 /** @var string Locked state is identical to neutral, but can't be overridden */ 45 const STATE_LOCKED = 'locked'; 46 47 /** 48 * Finds all MFA factors. 49 * 50 * @return array of factor objects. 51 */ 52 public static function get_factors(): array { 53 $return = []; 54 $factors = \core_plugin_manager::instance()->get_plugins_of_type('factor'); 55 56 foreach ($factors as $factor) { 57 $classname = '\\factor_'.$factor->name.'\\factor'; 58 if (class_exists($classname)) { 59 $return[] = new $classname($factor->name); 60 } 61 } 62 return self::sort_factors_by_order($return); 63 } 64 65 /** 66 * Sorts factors by configured order. 67 * 68 * @param array $unsorted of factor objects 69 * @return array of factor objects 70 * @throws \dml_exception 71 */ 72 public static function sort_factors_by_order(array $unsorted): array { 73 $sorted = []; 74 $orderarray = explode(',', get_config('tool_mfa', 'factor_order')); 75 76 foreach ($orderarray as $order => $factorname) { 77 foreach ($unsorted as $key => $factor) { 78 if ($factor->name == $factorname) { 79 $sorted[] = $factor; 80 unset($unsorted[$key]); 81 } 82 } 83 } 84 85 $sorted = array_merge($sorted, $unsorted); 86 return $sorted; 87 } 88 89 /** 90 * Finds factor by its name. 91 * 92 * @param string $name 93 * 94 * @return mixed factor object or false if factor not found. 95 */ 96 public static function get_factor(string $name): object|bool { 97 $factors = \core_plugin_manager::instance()->get_plugins_of_type('factor'); 98 99 foreach ($factors as $factor) { 100 if ($name == $factor->name) { 101 $classname = '\\factor_'.$factor->name.'\\factor'; 102 if (class_exists($classname)) { 103 return new $classname($factor->name); 104 } 105 } 106 } 107 108 return false; 109 } 110 111 /** 112 * Finds all enabled factors. 113 * 114 * @return array of factor objects 115 */ 116 public static function get_enabled_factors(): array { 117 $return = []; 118 $factors = self::get_factors(); 119 120 foreach ($factors as $factor) { 121 if ($factor->is_enabled()) { 122 $return[] = $factor; 123 } 124 } 125 126 return $return; 127 } 128 129 /** 130 * Finds active factors for a user. 131 * If user is not specified, current user is used. 132 * 133 * @param mixed $user user object or null. 134 * @return array of factor objects. 135 */ 136 public static function get_active_user_factor_types(mixed $user = null): array { 137 global $USER; 138 if (is_null($user)) { 139 $user = $USER; 140 } 141 142 $return = []; 143 $factors = self::get_enabled_factors(); 144 145 foreach ($factors as $factor) { 146 $userfactors = $factor->get_active_user_factors($user); 147 if (count($userfactors) > 0) { 148 $return[] = $factor; 149 } 150 } 151 152 return $return; 153 } 154 155 /** 156 * Returns next factor to authenticate user. 157 * Only returns factors that require user input. 158 * 159 * @return mixed factor object the next factor to be authenticated or false. 160 */ 161 public static function get_next_user_login_factor(): mixed { 162 $factors = self::get_active_user_factor_types(); 163 164 foreach ($factors as $factor) { 165 if (!$factor->has_input()) { 166 continue; 167 } 168 169 if ($factor->get_state() == self::STATE_UNKNOWN) { 170 return $factor; 171 } 172 } 173 174 return new \tool_mfa\local\factor\fallback(); 175 } 176 177 /** 178 * Returns all factors that require user input. 179 * 180 * @return array of factor objects. 181 */ 182 public static function get_all_user_login_factors(): array { 183 $factors = self::get_active_user_factor_types(); 184 $loginfactors = []; 185 foreach ($factors as $factor) { 186 if ($factor->has_input()) { 187 $loginfactors[] = $factor; 188 } 189 190 } 191 return $loginfactors; 192 } 193 194 /** 195 * Returns the list of available actions with factor. 196 * 197 * @return array 198 */ 199 public static function get_factor_actions(): array { 200 $actions = []; 201 $actions[] = 'setup'; 202 $actions[] = 'revoke'; 203 $actions[] = 'enable'; 204 $actions[] = 'revoke'; 205 $actions[] = 'disable'; 206 $actions[] = 'up'; 207 $actions[] = 'down'; 208 209 return $actions; 210 } 211 212 /** 213 * Returns the information about plugin availability 214 * 215 * True means that the plugin is enabled. False means that the plugin is 216 * disabled. Null means that the information is not available, or the 217 * plugin does not support configurable availability or the availability 218 * can not be changed. 219 * 220 * @return null|bool 221 */ 222 public function is_enabled(): null|bool { 223 if (!$this->rootdir) { 224 // Plugin missing. 225 return false; 226 } 227 228 $factor = $this->get_factor($this->name); 229 230 if ($factor) { 231 return $factor->is_enabled(); 232 } 233 234 return false; 235 } 236 237 /** 238 * Returns section name for settings. 239 * 240 * @return string 241 */ 242 public function get_settings_section_name(): string { 243 return $this->type . '_' . $this->name; 244 } 245 246 /** 247 * Loads factor settings to the settings tree 248 * 249 * This function usually includes settings.php file in plugins folder. 250 * Alternatively it can create a link to some settings page (instance of admin_externalpage) 251 * 252 * @param \part_of_admin_tree $adminroot 253 * @param string $parentnodename 254 * @param bool $hassiteconfig whether the current user has moodle/site:config capability 255 */ 256 public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig): void { 257 258 if (!$this->is_installed_and_upgraded()) { 259 return; 260 } 261 262 if (!$hassiteconfig || !file_exists($this->full_path('settings.php'))) { 263 return; 264 } 265 266 $section = $this->get_settings_section_name(); 267 268 $settings = new \admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); 269 270 if ($adminroot->fulltree) { 271 include($this->full_path('settings.php')); 272 } 273 274 $adminroot->add($parentnodename, $settings); 275 } 276 277 /** 278 * Checks that given factor exists. 279 * 280 * @param string $factorname 281 * 282 * @return bool 283 */ 284 public static function factor_exists(string $factorname): bool { 285 $factor = self::get_factor($factorname); 286 return !$factor ? false : true; 287 } 288 289 /** 290 * Returns instance of any factor from the factorid. 291 * 292 * @param int $factorid 293 * 294 * @return stdClass|null Factor instance or nothing if not found. 295 */ 296 public static function get_instance_from_id(int $factorid): stdClass|null { 297 global $DB; 298 return $DB->get_record('tool_mfa', ['id' => $factorid]); 299 } 300 301 /** 302 * Return URL used for management of plugins of this type. 303 * 304 * @return moodle_url 305 */ 306 public static function get_manage_url(): moodle_url { 307 return new moodle_url('/admin/settings.php', [ 308 'section' => 'managemfa', 309 ]); 310 } 311 312 /** 313 * These subplugins can be uninstalled. 314 * 315 * @return bool 316 */ 317 public function is_uninstall_allowed(): bool { 318 return $this->name !== 'nosetup'; 319 } 320 321 /** 322 * Pre-uninstall hook. 323 * 324 * This is intended for disabling of plugin, some DB table purging, etc. 325 * 326 * NOTE: to be called from uninstall_plugin() only. 327 * @private 328 */ 329 public function uninstall_cleanup() { 330 global $DB, $CFG; 331 332 $DB->delete_records('tool_mfa', ['factor' => $this->name]); 333 $DB->delete_records('tool_mfa_secrets', ['factor' => $this->name]); 334 335 $order = explode(',', get_config('tool_mfa', 'factor_order')); 336 if (in_array($this->name, $order)) { 337 $order = array_diff($order, [$this->name]); 338 \tool_mfa\manager::set_factor_config(['factor_order' => implode(',', $order)], 'tool_mfa'); 339 } 340 341 parent::uninstall_cleanup(); 342 } 343 344 /** 345 * Sorts factors by state. 346 * 347 * @param array $factors The factors to sort. 348 * @param string $state The state to sort by. 349 * @return array $factors The sorted factors. 350 */ 351 public static function sort_factors_by_state(array $factors, string $state): array { 352 usort($factors, function ($a, $b) use ($state) { 353 $statea = $a->get_state(); 354 $stateb = $b->get_state(); 355 356 if ($statea === $state && $stateb !== $state) { 357 return -1; // A comes before B. 358 } 359 360 if ($stateb === $state && $statea !== $state) { 361 return 1; // B comes before A. 362 } 363 364 return 0; // They are the same, keep current order. 365 }); 366 367 return $factors; 368 } 369 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body