Proyectos de Subversion Moodle

Rev

Rev 1 | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

Rev 1 Rev 1441
Línea 23... Línea 23...
23
require_once(__DIR__.'/../extlib/OTPHP/TOTPInterface.php');
23
require_once(__DIR__.'/../extlib/OTPHP/TOTPInterface.php');
24
require_once(__DIR__.'/../extlib/OTPHP/ParameterTrait.php');
24
require_once(__DIR__.'/../extlib/OTPHP/ParameterTrait.php');
25
require_once(__DIR__.'/../extlib/OTPHP/OTP.php');
25
require_once(__DIR__.'/../extlib/OTPHP/OTP.php');
26
require_once(__DIR__.'/../extlib/OTPHP/TOTP.php');
26
require_once(__DIR__.'/../extlib/OTPHP/TOTP.php');
Línea 27... Línea -...
27
 
-
 
28
require_once(__DIR__.'/../extlib/Assert/Assertion.php');
-
 
29
require_once(__DIR__.'/../extlib/Assert/AssertionFailedException.php');
-
 
30
require_once(__DIR__.'/../extlib/Assert/InvalidArgumentException.php');
27
 
31
require_once(__DIR__.'/../extlib/ParagonIE/ConstantTime/EncoderInterface.php');
28
require_once(__DIR__.'/../extlib/ParagonIE/ConstantTime/EncoderInterface.php');
32
require_once(__DIR__.'/../extlib/ParagonIE/ConstantTime/Binary.php');
29
require_once(__DIR__.'/../extlib/ParagonIE/ConstantTime/Binary.php');
Línea -... Línea 30...
-
 
30
require_once(__DIR__.'/../extlib/ParagonIE/ConstantTime/Base32.php');
33
require_once(__DIR__.'/../extlib/ParagonIE/ConstantTime/Base32.php');
31
 
34
 
32
use MoodleQuickForm;
35
use tool_mfa\local\factor\object_factor_base;
33
use tool_mfa\local\factor\object_factor_base;
-
 
34
use OTPHP\TOTP;
-
 
35
use stdClass;
-
 
36
use core\clock;
Línea 36... Línea 37...
36
use OTPHP\TOTP;
37
use core\context\system;
37
use stdClass;
38
use core\di;
38
 
39
 
39
/**
40
/**
Línea 63... Línea 64...
63
    const TOTP_INVALID = 'invalid';
64
    const TOTP_INVALID = 'invalid';
Línea 64... Línea 65...
64
 
65
 
65
    /** @var string Factor icon */
66
    /** @var string Factor icon */
Línea -... Línea 67...
-
 
67
    protected $icon = 'fa-mobile-screen';
-
 
68
 
-
 
69
    /** @var clock */
-
 
70
    private readonly clock $clock;
-
 
71
 
-
 
72
    /**
-
 
73
     * Constructor.
-
 
74
     *
-
 
75
     * @param string $name
-
 
76
     */
-
 
77
    public function __construct(string $name) {
-
 
78
        parent::__construct($name);
-
 
79
        $this->clock = di::get(clock::class);
Línea 66... Línea 80...
66
    protected $icon = 'fa-mobile-screen';
80
    }
67
 
81
 
68
 
82
 
69
    /**
83
    /**
Línea 75... Línea 89...
75
     * @return string
89
     * @return string
76
     */
90
     */
77
    public function generate_totp_uri(string $secret): string {
91
    public function generate_totp_uri(string $secret): string {
78
        global $USER, $SITE, $CFG;
92
        global $USER, $SITE, $CFG;
79
        $host = parse_url($CFG->wwwroot, PHP_URL_HOST);
93
        $host = parse_url($CFG->wwwroot, PHP_URL_HOST);
80
        $sitename = str_replace(':', '', $SITE->fullname);
94
        $sitename = str_replace(':', '', format_string($SITE->fullname, true, ['context' => system::instance()]));
81
        $issuer = $sitename.' '.$host;
95
        $issuer = $sitename.' '.$host;
82
        $totp = TOTP::create($secret);
96
        $totp = TOTP::create($secret, clock: $this->clock);
83
        $totp->setLabel($USER->username);
97
        $totp->setLabel($USER->username);
84
        $totp->setIssuer($issuer);
98
        $totp->setIssuer($issuer);
85
        return $totp->getProvisioningUri();
99
        return $totp->getProvisioningUri();
86
    }
100
    }
Línea 117... Línea 131...
117
    }
131
    }
Línea 118... Línea 132...
118
 
132
 
119
    /**
133
    /**
120
     * TOTP Factor implementation.
134
     * TOTP Factor implementation.
121
     *
135
     *
122
     * @param \MoodleQuickForm $mform
136
     * @param MoodleQuickForm $mform
123
     * @return \MoodleQuickForm $mform
137
     * @return MoodleQuickForm $mform
124
     */
138
     */
125
    public function setup_factor_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm {
139
    public function setup_factor_form_definition(MoodleQuickForm $mform): MoodleQuickForm {
126
        $secret = $this->generate_secret_code();
140
        $secret = $this->generate_secret_code();
127
        $mform->addElement('hidden', 'secret', $secret);
141
        $mform->addElement('hidden', 'secret', $secret);
Línea 128... Línea 142...
128
        $mform->setType('secret', PARAM_ALPHANUM);
142
        $mform->setType('secret', PARAM_ALPHANUM);
129
 
143
 
Línea 130... Línea 144...
130
        return $mform;
144
        return $mform;
131
    }
145
    }
132
 
146
 
133
    /**
147
    /**
134
     * TOTP Factor implementation.
148
     * TOTP Factor implementation.
135
     *
149
     *
136
     * @param \MoodleQuickForm $mform
150
     * @param MoodleQuickForm $mform
137
     * @return \MoodleQuickForm $mform
151
     * @return MoodleQuickForm $mform
Línea 138... Línea 152...
138
     */
152
     */
139
    public function setup_factor_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm {
153
    public function setup_factor_form_definition_after_data(MoodleQuickForm $mform): MoodleQuickForm {
Línea 175... Línea 189...
175
 
189
 
176
        // Enter manually.
190
        // Enter manually.
177
        $secret = wordwrap($secret, 4, ' ', true) . '</code>';
191
        $secret = wordwrap($secret, 4, ' ', true) . '</code>';
Línea -... Línea 192...
-
 
192
        $secret = \html_writer::tag('code', $secret);
-
 
193
 
178
        $secret = \html_writer::tag('code', $secret);
194
        $sitefullname = format_string($SITE->fullname, true, ['context' => system::instance()]);
179
 
195
 
180
        $manualtable = new \html_table();
196
        $manualtable = new \html_table();
181
        $manualtable->id = 'manualattributes';
197
        $manualtable->id = 'manualattributes';
182
        $manualtable->attributes['class'] = 'generaltable table table-bordered table-sm w-auto';
198
        $manualtable->attributes['class'] = 'generaltable table table-bordered table-sm w-auto';
183
        $manualtable->attributes['style'] = 'width: auto;';
199
        $manualtable->attributes['style'] = 'width: auto;';
184
        $manualtable->data = [
200
        $manualtable->data = [
185
            [get_string('setupfactor:key', 'factor_totp'), $secret],
201
            [get_string('setupfactor:key', 'factor_totp'), $secret],
186
            [get_string('setupfactor:account', 'factor_totp'), "$SITE->fullname ($USER->username)"],
202
            [get_string('setupfactor:account', 'factor_totp'), "{$sitefullname} ({$USER->username})"],
Línea 187... Línea 203...
187
            [get_string('setupfactor:mode', 'factor_totp'), get_string('setupfactor:mode:timebased', 'factor_totp')],
203
            [get_string('setupfactor:mode', 'factor_totp'), get_string('setupfactor:mode:timebased', 'factor_totp')],
188
        ];
204
        ];
189
 
205
 
Línea 190... Línea 206...
190
        $html = \html_writer::table($manualtable);
206
        $html = \html_writer::table($manualtable);
191
        // Wrap the table in a couple of divs to be controlled via bootstrap.
207
        // Wrap the table in a couple of divs to be controlled via bootstrap.
192
        $html = \html_writer::div($html, 'collapse', ['id' => 'collapseManualAttributes']);
208
        $html = \html_writer::div($html, 'collapse', ['id' => 'collapseManualAttributes']);
193
 
209
 
194
        $togglelink = \html_writer::tag('a', get_string('setupfactor:link', 'factor_totp'), [
210
        $togglelink = \html_writer::tag('a', get_string('setupfactor:link', 'factor_totp'), [
195
            'data-toggle' => 'collapse',
211
            'data-bs-toggle' => 'collapse',
196
            'data-target' => '#collapseManualAttributes',
212
            'data-bs-target' => '#collapseManualAttributes',
Línea 232... Línea 248...
232
     * @return array
248
     * @return array
233
     */
249
     */
234
    public function setup_factor_form_validation(array $data): array {
250
    public function setup_factor_form_validation(array $data): array {
235
        $errors = [];
251
        $errors = [];
Línea 236... Línea 252...
236
 
252
 
237
        $totp = TOTP::create($data['secret']);
253
        $totp = TOTP::create($data['secret'], clock: $this->clock);
238
        if (!$totp->verify($data['verificationcode'], time(), 1)) {
254
        if (!$totp->verify($data['verificationcode'], $this->clock->time(), 1)) {
239
            $errors['verificationcode'] = get_string('error:wrongverification', 'factor_totp');
255
            $errors['verificationcode'] = get_string('error:wrongverification', 'factor_totp');
Línea 240... Línea 256...
240
        }
256
        }
241
 
257
 
Línea 242... Línea 258...
242
        return $errors;
258
        return $errors;
243
    }
259
    }
244
 
260
 
245
    /**
261
    /**
246
     * TOTP Factor implementation.
262
     * TOTP Factor implementation.
247
     *
263
     *
248
     * @param \MoodleQuickForm $mform
264
     * @param MoodleQuickForm $mform
Línea 249... Línea 265...
249
     * @return \MoodleQuickForm $mform
265
     * @return MoodleQuickForm $mform
250
     */
266
     */
251
    public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm {
267
    public function login_form_definition(MoodleQuickForm $mform): MoodleQuickForm {
Línea 265... Línea 281...
265
     */
281
     */
266
    public function login_form_validation(array $data): array {
282
    public function login_form_validation(array $data): array {
267
        global $USER;
283
        global $USER;
268
        $factors = $this->get_active_user_factors($USER);
284
        $factors = $this->get_active_user_factors($USER);
269
        $result = ['verificationcode' => get_string('error:wrongverification', 'factor_totp')];
285
        $result = ['verificationcode' => get_string('error:wrongverification', 'factor_totp')];
270
        $windowconfig = get_config('factor_totp', 'window');
286
        $window = get_config('factor_totp', 'window');
Línea 271... Línea 287...
271
 
287
 
272
        foreach ($factors as $factor) {
288
        foreach ($factors as $factor) {
273
            $totp = TOTP::create($factor->secret);
-
 
274
            // Convert seconds to windows.
-
 
275
            $window = (int) floor($windowconfig / $totp->getPeriod());
289
            $totp = TOTP::create($factor->secret, clock: $this->clock);
276
            $factorresult = $this->validate_code($data['verificationcode'], $window, $totp, $factor);
290
            $factorresult = $this->validate_code($data['verificationcode'], $window, $totp, $factor);
Línea 277... Línea 291...
277
            $time = userdate(time(), get_string('systimeformat', 'factor_totp'));
291
            $time = userdate($this->clock->time(), get_string('systimeformat', 'factor_totp'));
278
 
292
 
279
            switch ($factorresult) {
293
            switch ($factorresult) {
Línea 312... Línea 326...
312
        $lastverified = $this->get_lastverified($factor->id);
326
        $lastverified = $this->get_lastverified($factor->id);
313
        if ($lastverified > 0 && $totp->verify($code, $lastverified, $window)) {
327
        if ($lastverified > 0 && $totp->verify($code, $lastverified, $window)) {
314
            return self::TOTP_USED;
328
            return self::TOTP_USED;
315
        }
329
        }
Línea 316... Línea 330...
316
 
330
 
317
        // The window in which to check for clock skew, 5 increments past valid window.
-
 
318
        $skewwindow = $window + 5;
-
 
319
        $pasttimestamp = time() - ($skewwindow * $totp->getPeriod());
-
 
320
        $futuretimestamp = time() + ($skewwindow * $totp->getPeriod());
-
 
321
 
331
        // Check if the code is valid, returning early.
322
        if ($totp->verify($code, time(), $window)) {
332
        if ($totp->verify($code, $this->clock->time(), $window)) {
323
            return self::TOTP_VALID;
-
 
324
        } else if ($totp->verify($code, $pasttimestamp, $skewwindow)) {
-
 
325
            // Check for clock skew in the past 10 periods.
-
 
326
            return self::TOTP_OLD;
-
 
327
        } else if ($totp->verify($code, $futuretimestamp, $skewwindow)) {
-
 
328
            // Check for clock skew in the future 10 periods.
-
 
329
            return self::TOTP_FUTURE;
-
 
330
        } else {
-
 
331
            // In all other cases, code is invalid.
-
 
332
            return self::TOTP_INVALID;
333
            return self::TOTP_VALID;
-
 
334
        }
-
 
335
 
-
 
336
        // Check for clock skew in the past and future 10 periods.
-
 
337
        for ($i = 1; $i <= 10; $i++) {
-
 
338
            $pasttimestamp = $this->clock->time() - $i * $totp->getPeriod();
-
 
339
            $futuretimestamp = $this->clock->time() + $i * $totp->getPeriod();
-
 
340
 
-
 
341
            if ($totp->verify($code, $pasttimestamp, $window)) {
-
 
342
                return self::TOTP_OLD;
-
 
343
            }
-
 
344
 
-
 
345
            if ($totp->verify($code, $futuretimestamp, $window)) {
-
 
346
                return self::TOTP_FUTURE;
-
 
347
            }
-
 
348
        }
-
 
349
 
-
 
350
        // In all other cases, the code is invalid.
333
        }
351
        return self::TOTP_INVALID;
Línea 334... Línea 352...
334
    }
352
    }
335
 
353
 
336
    /**
354
    /**
337
     * Generates cryptographically secure pseudo-random 16-digit secret code.
355
     * Generates cryptographically secure pseudo-random 16-digit secret code.
338
     *
356
     *
339
     * @return string
357
     * @return string
340
     */
358
     */
341
    public function generate_secret_code(): string {
359
    public function generate_secret_code(): string {
342
        $totp = TOTP::create();
360
        $totp = TOTP::create(clock: $this->clock);
Línea 343... Línea 361...
343
        return substr($totp->getSecret(), 0, 16);
361
        return substr($totp->getSecret(), 0, 16);
344
    }
362
    }
Línea 356... Línea 374...
356
            $row = new stdClass();
374
            $row = new stdClass();
357
            $row->userid = $USER->id;
375
            $row->userid = $USER->id;
358
            $row->factor = $this->name;
376
            $row->factor = $this->name;
359
            $row->secret = $data->secret;
377
            $row->secret = $data->secret;
360
            $row->label = $data->devicename;
378
            $row->label = $data->devicename;
361
            $row->timecreated = time();
379
            $row->timecreated = $this->clock->time();
362
            $row->createdfromip = $USER->lastip;
380
            $row->createdfromip = $USER->lastip;
363
            $row->timemodified = time();
381
            $row->timemodified = $this->clock->time();
364
            $row->lastverified = 0;
382
            $row->lastverified = 0;
365
            $row->revoked = 0;
383
            $row->revoked = 0;
Línea 366... Línea 384...
366
 
384
 
367
            // Check if a record with this configuration already exists, warning the user accordingly.
385
            // Check if a record with this configuration already exists, warning the user accordingly.