Proyectos de Subversion Moodle

Rev

| 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 factor_grace;
18
 
19
use stdClass;
20
use tool_mfa\local\factor\object_factor_base;
21
 
22
/**
23
 * Grace period factor class.
24
 *
25
 * @package     factor_grace
26
 * @author      Peter Burnett <peterburnett@catalyst-au.net>
27
 * @copyright   Catalyst IT
28
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29
 */
30
class factor extends object_factor_base {
31
 
32
    /**
33
     * Grace Factor implementation.
34
     * This factor is a singleton, return single instance.
35
     *
36
     * @param stdClass $user the user to check against.
37
     * @return array
38
     */
39
    public function get_all_user_factors(stdClass $user): array {
40
        global $DB;
41
 
42
        $records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
43
 
44
        if (!empty($records)) {
45
            return $records;
46
        }
47
 
48
        // Null records returned, build new record.
49
        $record = [
50
            'userid' => $user->id,
51
            'factor' => $this->name,
52
            'createdfromip' => $user->lastip,
53
            'timecreated' => time(),
54
            'revoked' => 0,
55
        ];
56
        $record['id'] = $DB->insert_record('tool_mfa', $record, true);
57
        return [(object) $record];
58
    }
59
 
60
    /**
61
     * Grace Factor implementation.
62
     * Singleton instance, no additional filtering needed.
63
     *
64
     * @param stdClass $user object to check against.
65
     * @return array the array of active factors.
66
     */
67
    public function get_active_user_factors(stdClass $user): array {
68
        return $this->get_all_user_factors($user);
69
    }
70
 
71
    /**
72
     * Grace Factor implementation.
73
     * Factor has no input.
74
     *
75
     * {@inheritDoc}
76
     */
77
    public function has_input(): bool {
78
        return false;
79
    }
80
 
81
    /**
82
     * Grace Factor implementation.
83
     * Checks the user login time against their first login after MFA activation.
84
     *
85
     * @param bool $redirectable should this state call be allowed to redirect the user?
86
     * @return string state constant
87
     */
88
    public function get_state($redirectable = true): string {
89
        global $FULLME, $SESSION, $USER;
90
        $records = ($this->get_all_user_factors($USER));
91
        $record = reset($records);
92
 
93
        // First check if user has any other input or setup factors active.
94
        $factors = $this->get_affecting_factors();
95
        $total = 0;
96
        foreach ($factors as $factor) {
97
            $total += $factor->get_weight();
98
            // If we have hit 100 total, then we know it is possible to auth with the current setup.
99
            // Gracemode should no longer give points.
100
            if ($total >= 100) {
101
                return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
102
            }
103
        }
104
 
105
        $starttime = $record->timecreated;
106
        // If no start time is recorded, status is unknown.
107
        if (empty($starttime)) {
108
            return \tool_mfa\plugininfo\factor::STATE_UNKNOWN;
109
        } else {
110
            $duration = get_config('factor_grace', 'graceperiod');
111
 
112
            if (!empty($duration)) {
113
                if (time() > $starttime + $duration) {
114
                    // If gracemode would have given points, but now doesnt,
115
                    // Jump out of the loop and force a factor setup.
116
                    // We will return once there is a setup, or the user tries to leave.
117
                    if (get_config('factor_grace', 'forcesetup') && $redirectable) {
118
                        if (empty($SESSION->mfa_gracemode_recursive)) {
119
                            // Set a gracemode lock so any further recursive gets fall past any recursive calls.
120
                            $SESSION->mfa_gracemode_recursive = true;
121
 
122
                            $factorurls = \tool_mfa\manager::get_no_redirect_urls();
123
                            $cleanurl = new \moodle_url($FULLME);
124
 
125
                            foreach ($factorurls as $factorurl) {
126
                                if ($factorurl->compare($cleanurl)) {
127
                                    $redirectable = false;
128
                                }
129
                            }
130
 
131
                            // We should never redirect if we have already passed.
132
                            if ($redirectable && \tool_mfa\manager::get_cumulative_weight() >= 100) {
133
                                $redirectable = false;
134
                            }
135
 
136
                            unset($SESSION->mfa_gracemode_recursive);
137
 
138
                            if ($redirectable) {
139
                                redirect(new \moodle_url('/admin/tool/mfa/user_preferences.php'),
140
                                    get_string('redirectsetup', 'factor_grace'));
141
                            }
142
                        }
143
                    }
144
                    return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
145
                } else {
146
                    return \tool_mfa\plugininfo\factor::STATE_PASS;
147
                }
148
            } else {
149
                return \tool_mfa\plugininfo\factor::STATE_UNKNOWN;
150
            }
151
        }
152
    }
153
 
154
    /**
155
     * Grace Factor implementation.
156
     * State cannot be set. Return true.
157
     *
158
     * @param string $state the state constant to set
159
     * @return bool
160
     */
161
    public function set_state(string $state): bool {
162
        return true;
163
    }
164
 
165
    /**
166
     * Grace Factor implementation.
167
     * Add a notification on the next page.
168
     *
169
     * {@inheritDoc}
170
     */
171
    public function post_pass_state(): void {
172
        global $USER;
173
        parent::post_pass_state();
174
 
175
        // Ensure grace factor passed before displaying notification.
176
        if ($this->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS
177
            && !\tool_mfa\manager::check_factor_pending($this->name)) {
178
            $url = new \moodle_url('/admin/tool/mfa/user_preferences.php');
179
            $link = \html_writer::link($url, get_string('preferences', 'factor_grace'));
180
 
181
            $records = ($this->get_all_user_factors($USER));
182
            $record = reset($records);
183
            $starttime = $record->timecreated;
184
            $timeremaining = ($starttime + get_config('factor_grace', 'graceperiod')) - time();
185
            $time = format_time($timeremaining);
186
 
187
            $data = ['url' => $link, 'time' => $time];
188
 
189
            $customwarning = get_config('factor_grace', 'customwarning');
190
            if (!empty($customwarning)) {
191
                // Clean text, then swap placeholders for time and the setup link.
192
                $message = preg_replace("/{timeremaining}/", $time, $customwarning);
193
                $message = preg_replace("/{setuplink}/", $url, $message);
194
                $message = clean_text($message, FORMAT_MOODLE);
195
            } else {
196
                $message = get_string('setupfactors', 'factor_grace', $data);
197
            }
198
 
199
            \core\notification::error($message);
200
        }
201
    }
202
 
203
    /**
204
     * Grace Factor implementation.
205
     * Gracemode should not be a valid combination with another factor.
206
     *
207
     * @param array $combination array of factors that make up the combination
208
     * @return bool
209
     */
210
    public function check_combination(array $combination): bool {
211
        // If this combination has more than 1 factor that has setup or input, not valid.
212
        foreach ($combination as $factor) {
213
            if ($factor->has_setup() || $factor->has_input()) {
214
                return false;
215
            }
216
        }
217
        return true;
218
    }
219
 
220
    /**
221
     * Grace Factor implementation.
222
     * Gracemode can change outcome just by waiting, or based on other factors.
223
     *
224
     * @param stdClass $user
225
     * @return array
226
     */
227
    public function possible_states(stdClass $user): array {
228
        return [
229
            \tool_mfa\plugininfo\factor::STATE_PASS,
230
            \tool_mfa\plugininfo\factor::STATE_NEUTRAL,
231
        ];
232
    }
233
 
234
    /**
235
     * Grace factor implementation.
236
     *
237
     * If grace period should redirect at end, make this a no-redirect url.
238
     *
239
     * @return array
240
     */
241
    public function get_no_redirect_urls(): array {
242
        $redirect = get_config('factor_grace', 'forcesetup');
243
 
244
        // First check if user has any other input or setup factors active.
245
        $factors = $this->get_affecting_factors();
246
        $total = 0;
247
        foreach ($factors as $factor) {
248
            $total += $factor->get_weight();
249
            // If we have hit 100 total, then we know it is possible to auth with the current setup.
250
            // The setup URL should no longer be a no-redirect URL. User MUST use existing auth.
251
            if ($total >= 100) {
252
                return [];
253
            }
254
        }
255
 
256
        if ($redirect && $this->get_state(false) === \tool_mfa\plugininfo\factor::STATE_NEUTRAL) {
257
            // If the config is enabled, the user should be able to access + setup a factor using these pages.
258
            return [
259
                new \moodle_url('/admin/tool/mfa/user_preferences.php'),
260
                new \moodle_url('/admin/tool/mfa/action.php'),
261
            ];
262
        } else {
263
            return [];
264
        }
265
    }
266
 
267
    /**
268
     * Returns a list of factor objects that can affect gracemode giving points.
269
     *
270
     * Only factors that a user can setup or manually use can affect whether gracemode gives points.
271
     * The intest is to provide a grace period for users to go in, setup factors, phone numbers, etc.,
272
     * so that they are able to authenticate correctly once the grace period ends.
273
     *
274
     * @return array
275
     */
276
    public function get_all_affecting_factors(): array {
277
        // Check if user has any other input or setup factors active.
278
        $factors = \tool_mfa\plugininfo\factor::get_factors();
279
        $factors = array_filter($factors, function ($el) {
280
            return $el->has_input() || $el->has_setup();
281
        });
282
        return $factors;
283
    }
284
 
285
    /**
286
     * Get the factor list that is currently affecting gracemode. Active and not ignored.
287
     *
288
     * @return array
289
     */
290
    public function get_affecting_factors(): array {
291
        // We need to filter all active user factors against the affecting factors and ignorelist.
292
        // Map active to names for filtering.
293
        $active = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
294
        $active = array_map(function ($el) {
295
            return $el->name;
296
        }, $active);
297
        $factors = $this->get_all_affecting_factors();
298
 
299
        $ignorelist = get_config('factor_grace', 'ignorelist');
300
        $ignorelist = !empty($ignorelist) ? explode(',', $ignorelist) : [];
301
 
302
        $factors = array_filter($factors, function ($el) use ($ignorelist, $active) {
303
            return !in_array($el->name, $ignorelist) && in_array($el->name, $active);
304
        });
305
        return $factors;
306
    }
307
}