Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 
1441 ariadna 129
    #[\Override]
130
    public static function enable_plugin(string $pluginname, int $enabled): bool {
131
        $enabledfactors = array_map(fn($f) => $f->name, self::get_enabled_factors());
132
        $currentlyenabled = in_array($pluginname, $enabledfactors);
133
 
134
        // Determine if there's a change in the enabled status.
135
        if ($enabled && !$currentlyenabled) {
136
            $action = 'enable';
137
        } else if (!$enabled && $currentlyenabled) {
138
            $action = 'disable';
139
        } else {
140
            return false; // No change needed.
141
        }
142
 
143
        // Execute the configuration and action based on the determined action.
144
        \tool_mfa\manager::set_factor_config(['enabled' => $enabled], 'factor_' . $pluginname);
145
        \tool_mfa\manager::do_factor_action($pluginname, $action);
146
 
147
        \core\session\manager::gc(); // Remove stale sessions.
148
        \core_plugin_manager::reset_caches();
149
 
150
        return true;
151
    }
152
 
1 efrain 153
    /**
154
     * Finds active factors for a user.
155
     * If user is not specified, current user is used.
156
     *
157
     * @param mixed $user user object or null.
158
     * @return array of factor objects.
159
     */
160
    public static function get_active_user_factor_types(mixed $user = null): array {
161
        global $USER;
162
        if (is_null($user)) {
163
            $user = $USER;
164
        }
165
 
166
        $return = [];
167
        $factors = self::get_enabled_factors();
168
 
169
        foreach ($factors as $factor) {
170
            $userfactors = $factor->get_active_user_factors($user);
171
            if (count($userfactors) > 0) {
172
                $return[] = $factor;
173
            }
174
        }
175
 
176
        return $return;
177
    }
178
 
179
    /**
180
     * Returns next factor to authenticate user.
181
     * Only returns factors that require user input.
182
     *
183
     * @return mixed factor object the next factor to be authenticated or false.
184
     */
185
    public static function get_next_user_login_factor(): mixed {
186
        $factors = self::get_active_user_factor_types();
187
 
188
        foreach ($factors as $factor) {
189
            if (!$factor->has_input()) {
190
                continue;
191
            }
192
 
193
            if ($factor->get_state() == self::STATE_UNKNOWN) {
194
                return $factor;
195
            }
196
        }
197
 
198
        return new \tool_mfa\local\factor\fallback();
199
    }
200
 
201
    /**
202
     * Returns all factors that require user input.
203
     *
204
     * @return array of factor objects.
205
     */
206
    public static function get_all_user_login_factors(): array {
207
        $factors = self::get_active_user_factor_types();
208
        $loginfactors = [];
209
        foreach ($factors as $factor) {
210
            if ($factor->has_input()) {
211
                $loginfactors[] = $factor;
212
            }
213
 
214
        }
215
        return $loginfactors;
216
    }
217
 
218
    /**
219
     * Returns the list of available actions with factor.
220
     *
221
     * @return array
222
     */
223
    public static function get_factor_actions(): array {
224
        $actions = [];
225
        $actions[] = 'setup';
226
        $actions[] = 'revoke';
227
        $actions[] = 'enable';
228
        $actions[] = 'revoke';
229
        $actions[] = 'disable';
230
        $actions[] = 'up';
231
        $actions[] = 'down';
232
        $actions[] = 'manage';
233
        $actions[] = 'replace';
234
 
235
        return $actions;
236
    }
237
 
238
    /**
239
     * Returns the information about plugin availability
240
     *
241
     * True means that the plugin is enabled. False means that the plugin is
242
     * disabled. Null means that the information is not available, or the
243
     * plugin does not support configurable availability or the availability
244
     * can not be changed.
245
     *
246
     * @return null|bool
247
     */
248
    public function is_enabled(): null|bool {
249
        if (!$this->rootdir) {
250
            // Plugin missing.
251
            return false;
252
        }
253
 
254
        $factor = $this->get_factor($this->name);
255
 
256
        if ($factor) {
257
            return $factor->is_enabled();
258
        }
259
 
260
        return false;
261
    }
262
 
263
    /**
264
     * Returns section name for settings.
265
     *
266
     * @return string
267
     */
268
    public function get_settings_section_name(): string {
269
        return $this->type . '_' . $this->name;
270
    }
271
 
272
    /**
273
     * Loads factor settings to the settings tree
274
     *
275
     * This function usually includes settings.php file in plugins folder.
276
     * Alternatively it can create a link to some settings page (instance of admin_externalpage)
277
     *
278
     * @param \part_of_admin_tree $adminroot
279
     * @param string $parentnodename
280
     * @param bool $hassiteconfig whether the current user has moodle/site:config capability
281
     */
282
    public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig): void {
1441 ariadna 283
        global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them.
284
        /** @var \admin_root $ADMIN */
285
        $ADMIN = $adminroot; // May be used in settings.php.
286
        $plugininfo = $this; // Also can be used inside settings.php.
1 efrain 287
 
288
        if (!$this->is_installed_and_upgraded()) {
289
            return;
290
        }
291
 
292
        if (!$hassiteconfig || !file_exists($this->full_path('settings.php'))) {
293
            return;
294
        }
295
 
296
        $section = $this->get_settings_section_name();
297
 
298
        $settings = new \admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false);
299
 
1441 ariadna 300
        include($this->full_path('settings.php'));
1 efrain 301
 
302
        $adminroot->add($parentnodename, $settings);
303
    }
304
 
305
    /**
306
     * Checks that given factor exists.
307
     *
308
     * @param string $factorname
309
     *
310
     * @return bool
311
     */
312
    public static function factor_exists(string $factorname): bool {
313
        $factor = self::get_factor($factorname);
314
        return !$factor ? false : true;
315
    }
316
 
317
    /**
318
     * Returns instance of any factor from the factorid.
319
     *
320
     * @param int $factorid
321
     *
322
     * @return stdClass|null Factor instance or nothing if not found.
323
     */
324
    public static function get_instance_from_id(int $factorid): stdClass|null {
325
        global $DB;
326
        return $DB->get_record('tool_mfa', ['id' => $factorid]);
327
    }
328
 
329
    /**
330
     * Return URL used for management of plugins of this type.
331
     *
332
     * @return moodle_url
333
     */
334
    public static function get_manage_url(): moodle_url {
335
        return new moodle_url('/admin/settings.php', [
336
            'section' => 'managemfa',
337
        ]);
338
    }
339
 
340
    /**
341
     * These subplugins can be uninstalled.
342
     *
343
     * @return bool
344
     */
345
    public function is_uninstall_allowed(): bool {
346
        return $this->name !== 'nosetup';
347
    }
348
 
1441 ariadna 349
    #[\Override]
350
    public static function plugintype_supports_disabling(): bool {
351
        return true;
352
    }
353
 
354
    #[\Override]
355
    public static function plugintype_supports_ordering(): bool {
356
        return true;
357
    }
358
 
1 efrain 359
    /**
360
     * Pre-uninstall hook.
361
     *
362
     * This is intended for disabling of plugin, some DB table purging, etc.
363
     *
364
     * NOTE: to be called from uninstall_plugin() only.
365
     * @private
366
     */
367
    public function uninstall_cleanup() {
368
        global $DB, $CFG;
369
 
370
        $DB->delete_records('tool_mfa', ['factor' => $this->name]);
371
        $DB->delete_records('tool_mfa_secrets', ['factor' => $this->name]);
372
 
373
        $order = explode(',', get_config('tool_mfa', 'factor_order'));
374
        if (in_array($this->name, $order)) {
375
            $order = array_diff($order, [$this->name]);
376
            \tool_mfa\manager::set_factor_config(['factor_order' => implode(',', $order)], 'tool_mfa');
377
        }
378
 
379
        parent::uninstall_cleanup();
380
    }
381
 
382
    /**
383
     * Sorts factors by state.
384
     *
385
     * @param array $factors The factors to sort.
386
     * @param string $state The state to sort by.
387
     * @return array $factors The sorted factors.
388
     */
389
    public static function sort_factors_by_state(array $factors, string $state): array {
390
        usort($factors, function ($a, $b) use ($state) {
391
            $statea = $a->get_state();
392
            $stateb = $b->get_state();
393
 
394
            if ($statea === $state && $stateb !== $state) {
395
                return -1;  // A comes before B.
396
            }
397
 
398
            if ($stateb === $state && $statea !== $state) {
399
                return 1;  // B comes before A.
400
            }
401
 
402
            return 0;  // They are the same, keep current order.
403
        });
404
 
405
        return $factors;
406
    }
407
 
408
    /**
409
     * Check if the current user has more than one active factor.
410
     *
411
     * @return bool Returns true if there are more than one.
412
     */
413
    public static function user_has_more_than_one_active_factors(): bool {
414
        $factors = self::get_active_user_factor_types();
415
        $count = count(array_filter($factors, function($factor) {
416
            // Include only user factors that can be set.
417
            return $factor->has_input();
418
        }));
419
 
420
        return $count > 1;
421
    }
1441 ariadna 422
 
423
    #[\Override]
424
    public static function get_sorted_plugins(bool $enabledonly = false): ?array {
425
        $pluginmanager = \core_plugin_manager::instance();
426
        $plugins = $pluginmanager->get_plugins_of_type('factor');
427
        $orders = self::get_factors();
428
        $sortedplugins = [];
429
        foreach ($orders as $order) {
430
            $sortedplugins[$order->name] = $plugins[$order->name];
431
        }
432
 
433
        return $sortedplugins;
434
    }
435
 
436
    #[\Override]
437
    public static function change_plugin_order(string $pluginname, int $direction): bool {
438
        $activefactors = array_keys(self::get_sorted_plugins(true));
439
        $key = array_search($pluginname, $activefactors);
440
 
441
        if ($key === false) {
442
            return false;
443
        }
444
 
445
        if ($direction === self::MOVE_DOWN && $key < (count($activefactors) - 1)) {
446
            $action = 'down';
447
        } else if ($direction === self::MOVE_UP && $key >= 1) {
448
            $action = 'up';
449
        } else {
450
            return false;
451
        }
452
 
453
        \tool_mfa\manager::do_factor_action($pluginname, $action);
454
 
455
        \core\session\manager::gc(); // Remove stale sessions.
456
        \core_plugin_manager::reset_caches();
457
 
458
        return true;
459
    }
1 efrain 460
}