PluginProbe
Two Factor Authentication / 1.14.4
Two Factor Authentication v1.14.4
1.12.2 1.13.0 1.14.10 1.14.11 1.14.14 1.14.15 1.14.16 1.14.17 1.14.23 1.14.24 1.14.26 1.14.27 1.14.3 1.14.4 1.14.5 1.14.7 1.14.8 1.15.5 1.16.0 1.2.10 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 All 98 releases
two-factor-authentication / simba-tfa / simba-tfa.php

simba-tfa.php in Two Factor Authentication 1.14.4, at simba-tfa/simba-tfa.php

1,249 lines 42.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) die('Access denied.');
4
5 class Simba_Two_Factor_Authentication {
6
7 protected $frontend;
8
9 protected $totp_controller;
10
11 /**
12 * URL slug for the plugin's option page
13 *
14 * @var String
15 */
16 private $user_settings_page_slug;
17
18 /**
19 * Settings page heading for plugin's option page
20 *
21 * @var String
22 */
23 private $settings_page_heading;
24
25 /**
26 * Plugin translate url
27 *
28 * @var string
29 */
30 private $plugin_translate_url;
31
32 /**
33 * URL slug for the site-wide administration options
34 *
35 * @var String
36 */
37 private $site_wide_administration_url;
38
39 /**
40 * URL for the premium version
41 *
42 * @var String
43 */
44 private $premium_version_url;
45
46 /**
47 * URL for the FAQ
48 *
49 * @var String
50 */
51 private $faq_url;
52
53 /**
54 * Authentication slug. Verify that two-factor authentication should not be repeated for the same slug.
55 *
56 * @var String
57 */
58 private $authentication_slug = 'updraft';
59
60 private static $is_authenticated = array();
61
62 /**
63 * Class Constructor, Set basic settings.
64 *
65 * @return Void
66 */
67 public function __construct() {
68
69 require_once(__DIR__.'/providers/totp-hotp/loader.php');
70
71 $this->totp_controller = new Simba_TFA_Provider_TOTP($this);
72
73 // Process login form AJAX events
74 add_action('wp_ajax_nopriv_simbatfa-init-otp', array($this, 'tfaInitLogin'));
75 add_action('wp_ajax_simbatfa-init-otp', array($this, 'tfaInitLogin'));
76
77 add_action('wp_ajax_simbatfa_shared_ajax', array($this, 'shared_ajax'));
78
79 require_once($this->includes_dir().'/login-form-integrations.php');
80 new Simba_TFA_Login_Form_Integrations($this);
81
82 // Add TFA column on admin users list
83 add_action('manage_users_columns', array($this, 'manage_users_columns_tfa'));
84 add_action('wpmu_users_columns', array($this, 'manage_users_columns_tfa'));
85 add_action('manage_users_custom_column', array($this, 'manage_users_custom_column_tfa'), 10, 3);
86
87 // CSS for admin users screen
88 add_action('admin_print_styles-users.php', array($this, 'load_users_css'), 10, 0);
89
90 add_action('admin_init', array($this, 'register_two_factor_auth_settings'));
91 add_action('init', array($this, 'init'));
92
93 if (!defined('TWO_FACTOR_DISABLE') || !TWO_FACTOR_DISABLE) {
94 add_filter('authenticate', array($this, 'tfaVerifyCodeAndUser'), 99999999999, 3);
95 }
96
97 if (defined('DOING_AJAX') && DOING_AJAX && defined('WP_ADMIN') && WP_ADMIN && !empty($_REQUEST['action']) && 'simbatfa-init-otp' == $_REQUEST['action']) {
98 // Try to prevent PHP notices breaking the AJAX conversation
99 $this->output_buffering = true;
100 $this->logged = array();
101 set_error_handler(array($this, 'get_php_errors'), E_ALL & ~E_STRICT);
102 ob_start();
103 }
104 }
105
106 /**
107 * Give the filesystem path to the plugin's includes directory
108 *
109 * @return String
110 */
111 public function includes_dir() {
112 return __DIR__.'/includes';
113 }
114
115 /**
116 * Give the URL for the plugin's includes directory
117 *
118 * @return String
119 */
120 public function includes_url() {
121 return plugins_url('', __FILE__).'/includes';
122 }
123
124 /**
125 * Set URL slug for the plugin's option page.
126 *
127 * @param String Setting page URL slug.
128 * @return Void
129 */
130 public function set_user_settings_page_slug($user_settings_page_slug) {
131 $this->user_settings_page_slug = $user_settings_page_slug;
132 }
133
134 /**
135 * Get URL slug for the plugin's option page.
136 *
137 * @return String Setting page URL slug.
138 */
139 public function get_user_settings_page_slug() {
140 return $this->user_settings_page_slug;
141 }
142
143 /**
144 * Set settings page heading for plugin's option page
145 *
146 * @param String $settings_page_heading String.
147 *
148 * @return String
149 */
150 public function set_settings_page_heading($settings_page_heading) {
151 $this->settings_page_heading = $settings_page_heading;
152 }
153
154 /**
155 * Get settings page heading for plugin's option page.
156 *
157 * @return String Setting page heading.
158 */
159 public function get_settings_page_heading() {
160 return $this->settings_page_heading;
161 }
162
163 /**
164 * Set plugin translate url
165 *
166 * @param String $plugin_translate_url Plugin translation URL.
167 * @return Void
168 */
169 public function set_plugin_translate_url($plugin_translate_url) {
170 $this->plugin_translate_url = $plugin_translate_url;
171 }
172
173 /**
174 * Get plugin translate url
175 *
176 * @return String Plugin translate URL
177 */
178 public function get_plugin_translate_url() {
179 return $this->plugin_translate_url;
180 }
181
182 /**
183 * Set plugin premium version url
184 *
185 * @param String $premium_version_url Plugin premium version url.
186 * @return Void
187 */
188 public function set_premium_version_url($premium_version_url) {
189 $this->premium_version_url = $premium_version_url;
190 }
191
192 /**
193 * Get plugin premium version URL.
194 *
195 * @return String Plugin premium version URL.
196 */
197 public function get_premium_version_url() {
198 return $this->premium_version_url;
199 }
200
201 /**
202 * Set plugin FAQ URL
203 *
204 * @param String $faq_url Plugin FAQ URL.
205 * @return Void
206 */
207 public function set_faq_url($faq_url) {
208 $this->faq_url = $faq_url;
209 }
210
211 /**
212 * Get plugin FAQ URL.
213 *
214 * @return String Plugin FAQ URL.
215 */
216 public function get_faq_url() {
217 return $this->faq_url;
218 }
219
220 /**
221 * Set plugin site wide administration URL
222 *
223 * @param String $site_wide_administration_url Plugin site wide administration URL.
224 * @return Void
225 */
226 public function set_site_wide_administration_url($site_wide_administration_url) {
227 $this->site_wide_administration_url = $site_wide_administration_url;
228 }
229
230 /**
231 * Get plugin site wide administration URL.
232 *
233 * @return String Plugin site wide administration URL
234 */
235 public function get_site_wide_administration_url() {
236 return $this->site_wide_administration_url;
237 }
238
239 /**
240 * Give the filesystem path to the plugin's templates directory
241 *
242 * @return String
243 */
244 public function templates_dir() {
245 return __DIR__.'/templates';
246 }
247
248 /**
249 * Include the user settings page code
250 */
251 public function show_dashboard_user_settings_page() {
252 $this->include_template('user-settings.php');
253 }
254
255 /**
256 * Enqueue CSS styling on the users page
257 */
258 public function load_users_css() {
259 wp_enqueue_style(
260 'tfa-users-css',
261 $this->includes_url().'/users.css',
262 array(),
263 $this->version,
264 'screen'
265 );
266 }
267
268 /**
269 * Add the 2FA label to the users list table header.
270 *
271 * @param Array $columns Table columns.
272 *
273 * @return Array
274 */
275 public function manage_users_columns_tfa($columns = array()) {
276 $columns['tfa-status'] = __('2FA', 'two-factor-authentication');
277 return $columns;
278 }
279
280 /**
281 * Add status into TFA column.
282 *
283 * @param String $value String.
284 * @param String $column_name Column name.
285 * @param Integer $user_id User ID.
286 *
287 * @return String
288 */
289 public function manage_users_custom_column_tfa($value = '', $column_name = '', $user_id = 0) {
290
291 // Only for this column name.
292 if ('tfa-status' === $column_name) {
293
294 if (!$this->is_activated_for_user($user_id)) {
295 $value = '&#8212;';
296 } elseif ($this->is_activated_by_user($user_id)) {
297 // Use value.
298 $value = '<span title="' . __( 'Enabled', 'two-factor-authentication' ) . '" class="dashicons dashicons-yes"></span>';
299 } else {
300 // No group.
301 $value = '<span title="' . __( 'Disabled', 'two-factor-authentication' ) . '" class="dashicons dashicons-no"></span>';
302 }
303 }
304
305 return $value;
306 }
307
308 /**
309 * Paint out an admin notice
310 *
311 * @param String $message - the caller should already have taken care of any escaping
312 * @param String $class
313 */
314 public function show_admin_warning($message, $class = 'updated') {
315 echo '<div class="tfamessage '.$class.'">'."<p>$message</p></div>";
316 }
317
318 /**
319 * Runs upon the WP action admin_init
320 */
321 public function register_two_factor_auth_settings() {
322 global $wp_roles;
323 if (!isset($wp_roles)) $wp_roles = new WP_Roles();
324
325 foreach ($wp_roles->role_names as $id => $name) {
326 register_setting('tfa_user_roles_group', 'tfa_'.$id);
327 register_setting('tfa_user_roles_trusted_group', 'tfa_trusted_'.$id);
328 register_setting('tfa_user_roles_required_group', 'tfa_required_'.$id);
329 }
330
331 if (is_multisite()) {
332 register_setting('tfa_user_roles_group', 'tfa__super_admin');
333 register_setting('tfa_user_roles_trusted_group', 'tfa_trusted__super_admin');
334 register_setting('tfa_user_roles_required_group', 'tfa_required__super_admin');
335 }
336
337 register_setting('tfa_user_roles_required_group', 'tfa_requireafter');
338 register_setting('tfa_user_roles_required_group', 'tfa_if_required_redirect_to');
339 register_setting('tfa_user_roles_required_group', 'tfa_hide_turn_off');
340 register_setting('tfa_user_roles_trusted_group', 'tfa_trusted_for');
341 register_setting('simba_tfa_woocommerce_group', 'tfa_wc_add_section');
342 register_setting('simba_tfa_woocommerce_group', 'tfa_bot_protection');
343 register_setting('simba_tfa_default_hmac_group', 'tfa_default_hmac');
344 register_setting('tfa_xmlrpc_status_group', 'tfa_xmlrpc_on');
345 }
346
347 /**
348 * See whether TFA is available or not for a particular user - i.e. whether the administrator has permitted it for their user level
349 *
350 * @param Integer $user_id - WordPress user ID
351 *
352 * @return Boolean
353 */
354 public function is_activated_for_user($user_id) {
355
356 if (empty($user_id)) return false;
357
358 // Super admin is not a role (they are admins with an extra attribute); needs separate handling
359 if (is_multisite() && is_super_admin($user_id)) {
360 // This is always a final decision - we don't want it to drop through to the 'admin' role's setting
361 $role = '_super_admin';
362 $db_val = $this->get_option('tfa_'.$role);
363 // Defaults to true if no setting has been saved
364 return (false === $db_val || $db_val) ? true : false;
365 }
366
367 $roles = $this->get_user_roles($user_id);
368
369 // N.B. This populates with roles on the current site within a multisite
370 foreach ($roles as $role) {
371 $db_val = $this->get_option('tfa_'.$role);
372 if (false === $db_val || $db_val) return true;
373 }
374
375 return false;
376
377 }
378
379 /**
380 * Get all user roles for a given user (if on multisite, amalgamates all roles from all sites)
381 *
382 * @param Integer $user_id - WordPress user ID
383 *
384 * @return Array
385 */
386 protected function get_user_roles($user_id) {
387
388 // Get roles on the main site
389 $user = new WP_User($user_id);
390 $roles = (array) $user->roles;
391
392 // On multisite, also check roles on non-main sites
393 if (is_multisite()) {
394 global $wpdb, $table_prefix;
395 $roles_db = $wpdb->get_results($wpdb->prepare("SELECT meta_key, meta_value FROM {$wpdb->usermeta} WHERE user_id=%d AND meta_key LIKE '".esc_sql($table_prefix)."%_capabilities'", $user_id));
396 if (is_array($roles_db)) {
397 foreach ($roles_db as $role_info) {
398 if (empty($role_info->meta_key) || !preg_match('/^'.$table_prefix.'\d+_capabilities$/', $role_info->meta_key) || empty($role_info->meta_value) || !preg_match('/^a:/', $role_info->meta_value)) continue;
399 $site_roles = unserialize($role_info->meta_value);
400 if (!is_array($site_roles)) continue;
401 foreach ($site_roles as $role => $active) {
402 if ($active && !in_array($role, $roles)) $roles[] = $role;
403 }
404 }
405 }
406 }
407
408 return $roles;
409 }
410
411 /**
412 * Check if TFA is required for a specified user
413 *
414 * N.B. - This doesn't check is_activated_for_user() - the caller would normally want to do that first
415 *
416 * @param $user_id Integer - the WP user ID
417 *
418 * @return Boolean
419 */
420 public function is_required_for_user($user_id) {
421 return apply_filters('simba_tfa_required_for_user', $this->user_property_active($user_id, 'required_'), $user_id);
422 }
423
424 /**
425 * See if a particular user property is active
426 *
427 * @param Integer $user_id
428 * @param String $prefix - e.g. "required_", "trusted_"
429 *
430 * @return Boolean
431 */
432 public function user_property_active($user_id, $prefix = 'required_') {
433
434 if (empty($user_id)) return false;
435
436 // Super admin is not a role (they are admins with an extra attribute); needs separate handling
437 if (is_multisite() && is_super_admin($user_id)) {
438 // This is always a final decision - we don't want it to drop through to the 'admin' role's setting
439 $role = '_super_admin';
440 $db_val = $this->get_option('tfa_'.$prefix.$role);
441 return $db_val ? true : false;
442 }
443
444 $roles = $this->get_user_roles($user_id);
445
446 foreach ($roles as $role) {
447 $db_val = $this->get_option('tfa_'.$prefix.$role);
448 if ($db_val) return true;
449 }
450
451 return false;
452
453 }
454
455 /**
456 * Whether TFA is activated by a specific user. Note that this doesn't check if TFA is enabled for the user's role; the caller should check that first.
457 *
458 * @param Integer $user_id
459 *
460 * @return Boolean
461 */
462 public function is_activated_by_user($user_id) {
463 $enabled = get_user_meta($user_id, 'tfa_enable_tfa', true);
464 return !empty($enabled);
465 }
466
467 /**
468 * Get a list of trusted devices for the user
469 *
470 * @param Integer|Boolean $user_id - WordPress user ID, or false for the current user
471 *
472 * @return Array
473 */
474 public function user_get_trusted_devices($user_id = false) {
475
476 if (false === $user_id) {
477 global $current_user;
478 $user_id = $current_user->ID;
479 }
480
481 $trusted_devices = get_user_meta($user_id, 'tfa_trusted_devices', true);
482
483 if (!is_array($trusted_devices)) $trusted_devices = array();
484
485 return $trusted_devices;
486 }
487
488 /**
489 * Trust the current device
490 *
491 * @param Integer $user_id - WordPress user ID
492 * @param Integer $trusted_for - time to trust for, in days
493 */
494 public function trust_device($user_id, $trusted_for) {
495
496 $trusted_devices = $this->user_get_trusted_devices($user_id);
497
498 $time_now = time();
499
500 foreach ($trusted_devices as $k => $device) {
501 if (empty($device['until']) || $device['until'] <= $time_now) unset($trusted_devices[$k]);
502 }
503
504 $until = $time_now + $trusted_for * 86400;
505
506 $token = bin2hex($this->random_bytes(40));
507
508 $trusted_devices[] = array(
509 'ip' => $_SERVER['REMOTE_ADDR'],
510 'until' => $until,
511 'user_agent' => empty($_SERVER['HTTP_USER_AGENT']) ? '' : (string) $_SERVER['HTTP_USER_AGENT'],
512 'token' => $token
513 );
514
515 $this->user_set_trusted_devices($user_id, $trusted_devices);
516
517 $this->set_cookie('simbatfa_trust_token', $token, $until);
518 }
519
520 /**
521 * Returns true if running on a PHP version on which mcrypt has been deprecated
522 *
523 * @return Boolean
524 */
525 public function is_mcrypt_deprecated() {
526 return (7 == PHP_MAJOR_VERSION && PHP_MINOR_VERSION >= 1);
527 }
528
529 /**
530 * Return the specified number of bytes
531 *
532 * @param Integer $bytes
533 *
534 * @throws Exception
535 *
536 * @return String
537 */
538 public function random_bytes($bytes) {
539 if (function_exists('random_bytes')) {
540 return random_bytes($bytes);
541 } elseif (function_exists('mcrypt_create_iv')) {
542 return $this->is_mcrypt_deprecated() ? @mcrypt_create_iv($bytes, MCRYPT_RAND) : mcrypt_create_iv($bytes, MCRYPT_RAND);
543 } elseif (function_exists('openssl_random_pseudo_bytes')) {
544 return openssl_random_pseudo_bytes($bytes);
545 }
546 throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed');
547 }
548
549 /**
550 * Set a cookie so that, however we logged in, it can be found
551 *
552 * @param String $name - the cookie name
553 * @param String $value - the cookie value
554 * @param Integer $expires - when the cookie expires, in epoch time. Defaults to 24 hours' time. Values in the past cause cookie deletion.
555 */
556 protected function set_cookie($name, $value, $expires = null) {
557 if (null === $expires) $expires = time() + 86400;
558 $secure = is_ssl();
559 $secure_logged_in_cookie = ($secure && 'https' === parse_url(get_option('home'), PHP_URL_SCHEME));
560 $secure = apply_filters('secure_auth_cookie', $secure, get_current_user_id());
561 $secure_logged_in_cookie = apply_filters('secure_logged_in_cookie', $secure_logged_in_cookie, get_current_user_id(), $secure);
562
563 setcookie($name, $value, $expires, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true);
564 setcookie($name, $value, $expires, COOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true);
565 if (COOKIEPATH != SITECOOKIEPATH) {
566 setcookie($name, $value, $expires, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true);
567 }
568 }
569
570 /**
571 * Get a list of trusted devices for the user
572 *
573 * @param Integer $user_id - WordPress user ID
574 * @param Array $trusted_devices - the list of devices
575 */
576 public function user_set_trusted_devices($user_id, $trusted_devices) {
577 update_user_meta($user_id, 'tfa_trusted_devices', $trusted_devices);
578 }
579
580 /**
581 * Get the user capability needed for managing TFA users.
582 * You'll want to think carefully about changing this to a non-admin, as it can give the ability to lock admins out (though, if you have FTP/files access, you can always disable TFA or any plugin)
583 *
584 * @return String
585 */
586 public function get_management_capability() {
587 return apply_filters('simba_tfa_management_capability', 'manage_options');
588 }
589
590 /**
591 * Used with set_error_handler()
592 *
593 * @param Integer $errno
594 * @param String $errstr
595 * @param String $errfile
596 * @param Integer $errline
597 *
598 * @return Boolean
599 */
600 public function get_php_errors($errno, $errstr, $errfile, $errline) {
601 if (0 == error_reporting()) return true;
602 $logline = $this->php_error_to_logline($errno, $errstr, $errfile, $errline);
603 $this->logged[] = $logline;
604 # Don't pass it up the chain (since it's going to be output to the user always)
605 return true;
606 }
607
608 public function php_error_to_logline($errno, $errstr, $errfile, $errline) {
609 switch ($errno) {
610 case 1: $e_type = 'E_ERROR'; break;
611 case 2: $e_type = 'E_WARNING'; break;
612 case 4: $e_type = 'E_PARSE'; break;
613 case 8: $e_type = 'E_NOTICE'; break;
614 case 16: $e_type = 'E_CORE_ERROR'; break;
615 case 32: $e_type = 'E_CORE_WARNING'; break;
616 case 64: $e_type = 'E_COMPILE_ERROR'; break;
617 case 128: $e_type = 'E_COMPILE_WARNING'; break;
618 case 256: $e_type = 'E_USER_ERROR'; break;
619 case 512: $e_type = 'E_USER_WARNING'; break;
620 case 1024: $e_type = 'E_USER_NOTICE'; break;
621 case 2048: $e_type = 'E_STRICT'; break;
622 case 4096: $e_type = 'E_RECOVERABLE_ERROR'; break;
623 case 8192: $e_type = 'E_DEPRECATED'; break;
624 case 16384: $e_type = 'E_USER_DEPRECATED'; break;
625 case 30719: $e_type = 'E_ALL'; break;
626 default: $e_type = "E_UNKNOWN ($errno)"; break;
627 }
628
629 if (!is_string($errstr)) $errstr = serialize($errstr);
630
631 if (0 === strpos($errfile, ABSPATH)) $errfile = substr($errfile, strlen(ABSPATH));
632
633 return "PHP event: code $e_type: $errstr (line $errline, $errfile)";
634
635 }
636
637 /**
638 * Runs upon the WordPress 'init' action
639 */
640 public function init() {
641 if ((!is_admin() || (defined('DOING_AJAX') && DOING_AJAX)) && is_user_logged_in() && file_exists($this->includes_dir().'/tfa_frontend.php')) {
642 $this->load_frontend();
643 } else {
644 add_shortcode('twofactor_user_settings', array($this, 'shortcode_when_not_logged_in'));
645 }
646 }
647
648 /**
649 * Return the Simba_TFA_Provider_TOTP object.
650 *
651 * @returns Simba_TFA_Provider_TOTP
652 */
653 public function get_totp_controller() {
654 return $this->totp_controller;
655 }
656
657 /**
658 * "Shared" - i.e. could be called from either front-end or back-end
659 */
660 public function shared_ajax() {
661
662 if (empty($_POST['subaction']) || empty($_POST['nonce']) || !is_user_logged_in() || !wp_verify_nonce($_POST['nonce'], 'tfa_shared_nonce')) die('Security check (3).');
663
664 global $current_user;
665
666 $subaction = $_POST['subaction'];
667
668 if ('refreshotp' == $subaction) {
669
670 $code = $this->totp_controller->get_current_code($current_user->ID);
671
672 if (false === $code) die(json_encode(array('code' => '')));
673
674 die(json_encode(array('code' => $code)));
675
676 } elseif ('untrust_device' == $subaction && isset($_POST['device_id'])) {
677 $this->untrust_device(stripslashes($_POST['device_id']));
678 ob_start();
679 $this->include_template('trusted-devices-inner-box.php', array('trusted_devices' => $this->user_get_trusted_devices()));
680 echo json_encode(array('trusted_list' => ob_get_clean()));
681 }
682
683 exit;
684
685 }
686
687 /**
688 * Mark a device as untrusted for the current user
689 *
690 * @param String $device_id
691 */
692 protected function untrust_device($device_id) {
693
694 $trusted_devices = $this->user_get_trusted_devices();
695
696 unset($trusted_devices[$device_id]);
697
698 global $current_user;
699 $current_user_id = $current_user->ID;
700
701 $this->user_set_trusted_devices($current_user_id, $trusted_devices);
702
703 }
704
705 /**
706 * Called upon the AJAX action simbatfa-init-otp . Will die.
707 *
708 * Uses these keys from $_POST: user
709 */
710 public function tfaInitLogin() {
711
712 if (empty($_POST['user'])) die('Security check (2).');
713
714 if (defined('TWO_FACTOR_DISABLE') && TWO_FACTOR_DISABLE) {
715 $res = array('result' => false, 'user_can_trust' => false);
716 } else {
717
718 if (!function_exists('sanitize_user')) require_once ABSPATH.WPINC.'/formatting.php';
719
720 // WP's password-checking sanitizes the supplied user, so we must do the same to check if TFA is enabled for them
721 $auth_info = array('log' => sanitize_user(stripslashes((string)$_POST['user'])));
722
723 if (!empty($_COOKIE['simbatfa_trust_token'])) $auth_info['trust_token'] = (string) $_COOKIE['simbatfa_trust_token'];
724
725 $res = $this->pre_auth($auth_info, 'array');
726 }
727
728 $results = array(
729 'jsonstarter' => 'justhere',
730 'status' => $res['result'],
731 );
732
733 if (!empty($res['user_can_trust'])) {
734 $results['user_can_trust'] = 1;
735 if (!empty($res['user_already_trusted'])) $results['user_already_trusted'] = 1;
736 }
737
738
739 if (!empty($this->output_buffering)) {
740 if (!empty($this->logged)) {
741 $results['php_output'] = $this->logged;
742 }
743 restore_error_handler();
744 $buffered = ob_get_clean();
745 if ($buffered) $results['extra_output'] = $buffered;
746 }
747
748 $results = apply_filters('simbatfa_check_tfa_requirements_ajax_response', $results);
749
750 echo json_encode($results);
751
752 exit;
753 }
754
755 /**
756 * Enable or disable TFA for a user
757 *
758 * @param Integer $user_id - the WordPress user ID
759 * @param String $setting - either "true" (to turn on) or "false" (to turn off)
760 */
761 public function change_tfa_enabled_status($user_id, $setting) {
762 $previously_enabled = $this->is_activated_by_user($user_id) ? 1 : 0;
763 $setting = ('true' === $setting) ? 1 : 0;
764 update_user_meta($user_id, 'tfa_enable_tfa', $setting);
765 do_action('simba_tfa_activation_status_saved', $user_id, $setting, $previously_enabled, $this);
766 }
767
768 /**
769 * Here's where the login action happens. Called on the WP 'authenticate' action.
770 *
771 * @param WP_Error|WP_User $user
772 * @param String $username - this is not necessarily the WP username; it is whatever was typed in the form, so can be an email address
773 * @param String $password
774 *
775 * @return WP_Error|WP_User
776 */
777 public function tfaVerifyCodeAndUser($user, $username, $password) {
778 // When both AIOWPS and Two Factor Authentication plugins are active, this function called more than once, To prevent it, this code is written.
779 if (isset(self::$is_authenticated[$this->authentication_slug]) && self::$is_authenticated[$this->authentication_slug]) {
780 return $user;
781 }
782
783 $original_user = $user;
784 $params = stripslashes_deep($_POST);
785
786 // If (only) the error was a wrong password, but it looks like the user appended a TFA code to their password, then have another go
787 if (is_wp_error($user) && array('incorrect_password') == $user->get_error_codes() && !isset($params['two_factor_code']) && false !== ($from_password = apply_filters('simba_tfa_tfa_from_password', false, $password))) {
788 // This forces a new password authentication below
789 $user = false;
790 }
791
792 if (is_wp_error($user)) {
793 $ret = $user;
794 } else {
795
796 if (is_object($user) && isset($user->ID) && isset($user->user_login)) {
797 $params['log'] = $user->user_login;
798 // Confirm that this is definitely a username regardless of its format
799 $may_be_email = false;
800 } else {
801 $params['log'] = $username;
802 $may_be_email = true;
803 }
804
805 $params['caller'] = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['REQUEST_URI'];
806 if (!empty($_COOKIE['simbatfa_trust_token'])) $params['trust_token'] = (string) $_COOKIE['simbatfa_trust_token'];
807
808 if (isset($from_password) && false !== $from_password) {
809 // Support login forms that can't be hooked via appending to the password
810 $speculatively_try_appendage = true;
811 $params['two_factor_code'] = $from_password['tfa_code'];
812 }
813
814 $code_ok = $this->authorise_user_from_login($params, $may_be_email);
815
816 if (is_wp_error($code_ok)) {
817 $ret = $code_ok;
818 } elseif (!$code_ok) {
819 $ret = new WP_Error('authentication_failed', '<strong>'.__('Error:', 'two-factor-authentication').'</strong> '.__('The one-time password (TFA code) you entered was incorrect.', 'two-factor-authentication'));
820 } elseif ($user) {
821 $ret = $user;
822 } else {
823
824 if (!empty($speculatively_try_appendage) && true === $code_ok) {
825 $password = $from_password['password'];
826 }
827
828 $username_is_email = false;
829
830 if (function_exists('wp_authenticate_username_password') && $may_be_email && filter_var($username, FILTER_VALIDATE_EMAIL)) {
831 global $wpdb;
832 // This has to match self::authorise_user_from_login()
833 $response = $wpdb->get_row($wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_email=%s", $username));
834 if (is_object($response)) $username_is_email = true;
835 }
836
837 $ret = $username_is_email ? wp_authenticate_email_password(null, $username, $password) : wp_authenticate_username_password(null, $username, $password);
838 }
839
840 }
841
842 $ret = apply_filters('simbatfa_verify_code_and_user_result', $ret, $original_user, $username, $password);
843
844 // If the TFA code was actually validated (not just not required, for example), then $code_ok is (boolean)true
845 if (isset($code_ok) && true === $code_ok && is_a($ret, 'WP_User')) {
846 if (!empty($params['simba_tfa_mark_as_trusted']) && $this->user_can_trust($ret->ID) && (is_ssl() || (!empty($_SERVER['SERVER_NAME']) && ('localhost' == $_SERVER['SERVER_NAME'] ||'127.0.0.1' == $_SERVER['SERVER_NAME'])))) {
847
848 $trusted_for = $this->get_option('tfa_trusted_for');
849 $trusted_for = (false === $trusted_for) ? 30 : (string) absint($trusted_for);
850
851 $this->trust_device($ret->ID, $trusted_for);
852 }
853 }
854
855 self::$is_authenticated[$this->authentication_slug] = true;
856
857 return $ret;
858 }
859
860 // N.B. - This doesn't check is_activated_for_user() - the caller would normally want to do that first
861 public function user_can_trust($user_id) {
862 // Default is false because this is a new feature and we don't want to surprise existing users by granting broader access than they expected upon an upgrade
863 return apply_filters('simba_tfa_user_can_trust', false, $user_id);
864 }
865
866 /**
867 * Should the user be asked for a TFA code? And optionally, is the user allowed to trust devices?
868 *
869 * @param Array $params - the key used is 'log', indicating the username or email address
870 * @param String $response_format - 'simple' (historic format) or 'array' (richer info)
871 *
872 * @return Boolean
873 */
874 public function pre_auth($params, $response_format = 'simple') {
875 global $wpdb;
876
877 $query = filter_var($params['log'], FILTER_VALIDATE_EMAIL) ? $wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_email=%s", $params['log']) : $wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_login=%s", $params['log']);
878 $user = $wpdb->get_row($query);
879
880 if (!$user && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) {
881 // Corner-case: login looks like an email, but is a username rather than email address
882 $user = $wpdb->get_row($wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_login=%s", $params['log']));
883 }
884
885 $is_activated_for_user = true;
886 $is_activated_by_user = false;
887
888 $result = false;
889
890 $totp_controller = $this->totp_controller;
891
892 if ($user) {
893 $tfa_priv_key = get_user_meta($user->ID, 'tfa_priv_key_64', true);
894 $is_activated_for_user = $this->is_activated_for_user($user->ID);
895 $is_activated_by_user = $this->is_activated_by_user($user->ID);
896
897 if ($is_activated_for_user && $is_activated_by_user) {
898
899 // No private key yet, generate one. This shouldn't really be possible.
900 if (!$tfa_priv_key) $tfa_priv_key = $totp_controller->addPrivateKey($user->ID);
901
902 $code = $totp_controller->generateOTP($user->ID, $tfa_priv_key);
903
904 $result = true;
905 }
906 }
907
908 if ('array' != $response_format) return $result;
909
910 $ret = array('result' => $result);
911
912 if ($result) {
913 $ret['user_can_trust'] = $this->user_can_trust($user->ID);
914 if (!empty($params['trust_token']) && $this->user_trust_token_valid($user->ID, $params['trust_token'])) {
915 $ret['user_already_trusted'] = 1;
916 }
917 }
918
919 return $ret;
920 }
921
922 /**
923 * Print the radio buttons for enabling/disabling TFA
924 *
925 * @param Integer $user_id - the WordPress user ID
926 * @param Boolean $long_label - whether to use a long label rather than a short one
927 * @param String $style - valid values are "show_current" and "require_current"
928 */
929 public function paint_enable_tfa_radios($user_id, $long_label = false, $style = 'show_current') {
930
931 if (!$user_id) return;
932
933 if ('require_current' != $style) $style = 'show_current';
934
935 $is_required = $this->is_required_for_user($user_id);
936 $is_activated = $this->is_activated_by_user($user_id);
937
938 if ($is_required) {
939 $require_after = absint($this->get_option('tfa_requireafter'));
940 echo '<p class="tfa_required_warning" style="font-weight:bold; font-style:italic;">'.sprintf(__('N.B. This site is configured to forbid you to log in if you disable two-factor authentication after your account is %d days old', 'two-factor-authentication'), $require_after).'</p>';
941 }
942
943 $tfa_enabled_label = $long_label ? __('Enable two-factor authentication', 'two-factor-authentication') : __('Enabled', 'two-factor-authentication');
944
945 if ('show_current' == $style) {
946 $tfa_enabled_label .= ' '.sprintf(__('(Current code: %s)', 'two-factor-authentication'), $this->get_totp_controller()->current_otp_code($user_id));
947 } elseif ('require_current' == $style) {
948 $tfa_enabled_label .= ' '.sprintf(__('(you must enter the current code: %s)', 'two-factor-authentication'), '<input type="text" class="tfa_enable_current" name="tfa_enable_current" size="6" style="height">');
949 }
950
951 $show_disable = ((is_multisite() && is_super_admin()) || (!is_multisite() && current_user_can($this->get_management_capability())) || false == $is_activated || !$is_required || !$this->get_option('tfa_hide_turn_off')) ? true : false;
952
953 $tfa_disabled_label = $long_label ? __('Disable two-factor authentication', 'two-factor-authentication') : __('Disabled', 'two-factor-authentication');
954
955 if ('require_current' == $style) echo '<input type="hidden" name="require_current" value="1">'."\n";
956
957 echo '<input type="radio" class="tfa_enable_radio" id="tfa_enable_tfa_true" name="tfa_enable_tfa" value="true" '.(true == $is_activated ? 'checked="checked"' : '').'> <label class="tfa_enable_radio_label" for="tfa_enable_tfa_true">'.apply_filters('simbatfa_radiolabel_enabled', $tfa_enabled_label, $long_label).'</label> <br>';
958
959 // Show the 'disabled' option if the user is an admin, or if it is currently set, or if TFA is not compulsory, or if the site owner doesn't require it to be hidden
960 // Note that this just hides the option in the UI. The user could POST to turn off TFA, but, since it's required, they won't be able to log in.
961 if ($show_disable) {
962 echo '<input type="radio" class="tfa_enable_radio" id="tfa_enable_tfa_false" name="tfa_enable_tfa" value="false" '.(false == $is_activated ? 'checked="checked"' :'').'> <label class="tfa_enable_radio_label" for="tfa_enable_tfa_false">'.apply_filters('simbatfa_radiolabel_disabled', $tfa_disabled_label, $long_label).'</label> <br>';
963 }
964 }
965
966 /**
967 * Retrieve a saved option
968 *
969 * @param String $key - option key
970 *
971 * @return Mixed
972 */
973 public function get_option($key) {
974 if (!is_multisite()) return get_option($key);
975 switch_to_blog(1);
976 $v = get_option($key);
977 restore_current_blog();
978 return $v;
979 }
980
981 /**
982 * Paint a list of checkboxes, one for each role
983 *
984 * @param String $prefix
985 * @param Integer $default - default value (0 or 1)
986 */
987 public function list_user_roles_checkboxes($prefix = '', $default = 1) {
988 if (is_multisite()) {
989 // Not a real WP role; needs separate handling
990 $id = '_super_admin';
991 $name = __('Multisite Super Admin', 'two-factor-authentication');
992 $setting = $this->get_option('tfa_'.$prefix.$id);
993 $setting = ($setting === false) ? $default : ($setting ? 1 : 0);
994
995 echo '<input type="checkbox" id="tfa_'.$prefix.$id.'" name="tfa_'.$prefix.$id.'" value="1" '.($setting ? 'checked="checked"' :'').'> <label for="tfa_'.$prefix.$id.'">'.htmlspecialchars($name)."</label><br>\n";
996 }
997
998 global $wp_roles;
999 if (!isset($wp_roles)) $wp_roles = new WP_Roles();
1000
1001 foreach ($wp_roles->role_names as $id => $name) {
1002 $setting = $this->get_option('tfa_'.$prefix.$id);
1003 $setting = ($setting === false) ? $default : ($setting ? 1 : 0);
1004
1005 echo '<input type="checkbox" id="tfa_'.$prefix.$id.'" name="tfa_'.$prefix.$id.'" value="1" '.($setting ? 'checked="checked"' :'').'> <label for="tfa_'.$prefix.$id.'">'.htmlspecialchars($name)."</label><br>\n";
1006 }
1007
1008 }
1009
1010 public function tfa_list_xmlrpc_status_radios() {
1011
1012 $setting = $this->get_option('tfa_xmlrpc_on');
1013 $setting = $setting ? 1 : 0;
1014
1015 $types = array(
1016 '0' => __('Do not require 2FA over XMLRPC (best option if you must use XMLRPC and your client does not support 2FA)', 'two-factor-authentication'),
1017 '1' => __('Do require 2FA over XMLRPC (best option if you do not use XMLRPC or are unsure)', 'two-factor-authentication')
1018 );
1019
1020 foreach($types as $id => $name) {
1021 print '<input type="radio" name="tfa_xmlrpc_on" id="tfa_xmlrpc_on_'.$id.'" value="'.$id.'" '.($setting == $id ? 'checked="checked"' : '').'> <label for="tfa_xmlrpc_on_'.$id.'">'.htmlspecialchars($name)."</label><br>\n";
1022 }
1023 }
1024
1025 protected function is_caller_active() {
1026
1027 if (!defined('XMLRPC_REQUEST') || !XMLRPC_REQUEST) return true;
1028
1029 $saved_data = $this->get_option('tfa_xmlrpc_on');
1030
1031 return $saved_data ? true : false;
1032
1033 }
1034
1035 /**
1036 * @param Array $params
1037 * @param Boolean $may_be_email
1038 *
1039 * @return WP_Error|Boolean|Integer - WP_Error or false means failure; true or 1 means success, but true means the TFA code was validated
1040 */
1041 public function authorise_user_from_login($params, $may_be_email = false) {
1042
1043 $params = apply_filters('simbatfa_auth_user_from_login_params', $params);
1044
1045 global $wpdb;
1046
1047 if (!$this->is_caller_active()) return 1;
1048
1049 $query = ($may_be_email && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) ? $wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_email=%s", $params['log']) : $wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_login=%s", $params['log']);
1050 $response = $wpdb->get_row($query);
1051
1052 if (!$response && $may_be_email && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) {
1053 // Corner-case: login looks like an email, but is a username rather than email address
1054 $response = $wpdb->get_row($wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_login=%s", $params['log']));
1055 }
1056
1057 $user_ID = is_object($response) ? $response->ID : false;
1058 $user_registered = is_object($response) ? $response->user_registered : false;
1059
1060 $user_code = isset($params['two_factor_code']) ? str_replace(' ', '', trim($params['two_factor_code'])) : '';
1061
1062 // This condition in theory should not be possible
1063 if (!$user_ID) return new WP_Error('tfa_user_not_found', apply_filters('simbatfa_tfa_user_not_found', '<strong>'.__('Error:', 'two-factor-authentication').'</strong> '.__('The indicated user could not be found.', 'two-factor-authentication')));
1064
1065 if (!$this->is_activated_for_user($user_ID)) return 1;
1066
1067 if (!empty($params['trust_token']) && $this->user_trust_token_valid($user_ID, $params['trust_token'])) {
1068 return 1;
1069 }
1070
1071 if (!$this->is_activated_by_user($user_ID)) {
1072
1073 if (!$this->is_required_for_user($user_ID)) return 1;
1074
1075 $require_after = absint($this->get_option('tfa_requireafter')) * 86400;
1076
1077 $account_age = time() - strtotime($user_registered);
1078
1079 if ($account_age > $require_after && apply_filters('simbatfa_enforce_require_after_check', true, $user_ID, $require_after, $account_age)) {
1080 return new WP_Error('tfa_required', apply_filters('simbatfa_notfa_forbidden_login', '<strong>'.__('Error:', 'two-factor-authentication').'</strong> '.__('The site owner has forbidden you to login without two-factor authentication. Please contact the site owner to re-gain access.', 'two-factor-authentication')));
1081 }
1082
1083 return 1;
1084 }
1085
1086 $tfa_creds_user_id = !empty($params['creds_user_id']) ? $params['creds_user_id'] : $user_ID;
1087
1088 if ($tfa_creds_user_id != $user_ID) {
1089
1090 // Authenticating using a different user's credentials (e.g. https://wordpress.org/plugins/use-administrator-password/)
1091 // In this case, we require that different user to have TFA active - so that this mechanism can't be used to avoid TFA
1092
1093 if (!$this->is_activated_for_user($tfa_creds_user_id) || !$this->is_activated_by_user($tfa_creds_user_id)) {
1094 return new WP_Error('tfa_required', apply_filters('simbatfa_notfa_forbidden_login_altuser', '<strong>'.__('Error:', 'two-factor-authentication').'</strong> '.__('You are attempting to log in to an account that has two-factor authentication enabled; this requires you to also have two-factor authentication enabled on the account whose credentials you are using.', 'two-factor-authentication')));
1095 }
1096
1097 }
1098
1099 return $this->totp_controller->check_code_for_user($tfa_creds_user_id, $user_code);
1100
1101 }
1102
1103 /**
1104 * Evaluate whether a trust token is valid for a user
1105 *
1106 * @param Integer $user_id - WP user ID
1107 * @param String $trust_token - trust token
1108 *
1109 * @return Boolean
1110 */
1111 protected function user_trust_token_valid($user_id, $trust_token) {
1112
1113 if (!is_string($trust_token) || strlen($trust_token) < 30) return false;
1114
1115 $trusted_devices = $this->user_get_trusted_devices($user_id);
1116
1117 $time_now = time();
1118
1119 foreach ($trusted_devices as $device) {
1120 if (empty($device['until']) || $device['until'] <= $time_now) continue;
1121 if (!empty($device['token']) && $device['token'] === $trust_token) {
1122 return true;
1123 }
1124 }
1125
1126 return false;
1127 }
1128
1129 /**
1130 * This deals with the issue that wp-login.php does not redirect to a canonical URL. As a result, if a website is available under more than one host, then admin_url('admin-ajax.php') might return a different one than the visitor is using, resulting in AJAX failing due to CORS errors.
1131 *
1132 * @return String
1133 */
1134 protected function get_ajax_url() {
1135 $ajax_url = admin_url('admin-ajax.php');
1136 $parsed_url = parse_url($ajax_url);
1137 if (strtolower($parsed_url['host']) !== strtolower($_SERVER['HTTP_HOST']) && !empty($parsed_url['path'])) {
1138 // Mismatch - return the relative URL only
1139 $ajax_url = $parsed_url['path'];
1140 }
1141 return $ajax_url;
1142 }
1143
1144 /**
1145 * Called not only upon the WP action login_enqueue_scripts, but potentially upon the action 'init' and various others from other plugins too. It can handle being called multiple times.
1146 */
1147 public function login_enqueue_scripts() {
1148
1149 if (isset($_GET['action']) && 'logout ' != $_GET['action'] && 'login' != $_GET['action']) return;
1150
1151 static $already_done = false;
1152 if ($already_done) return;
1153 $already_done = true;
1154
1155 // Prevent cacheing when in debug mode
1156 $script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : filemtime($this->includes_dir().'/tfa.js');
1157
1158 wp_enqueue_script('tfa-ajax-request', $this->includes_url().'/tfa.js', array('jquery'), $script_ver);
1159
1160 $trusted_for = $this->get_option('tfa_trusted_for');
1161 $trusted_for = (false === $trusted_for) ? 30 : (string) absint($trusted_for);
1162
1163 $localize = array(
1164 'ajaxurl' => $this->get_ajax_url(),
1165 'click_to_enter_otp' => __("Click to enter One Time Password", 'two-factor-authentication'),
1166 'enter_username_first' => __('You have to enter a username first.', 'two-factor-authentication'),
1167 'otp' => __('One Time Password (i.e. 2FA)', 'two-factor-authentication'),
1168 'otp_login_help' => __('(check your OTP app to get this password)', 'two-factor-authentication'),
1169 'mark_as_trusted' => sprintf(_n('Trust this device (allow login without 2FA for %d day)', 'Trust this device (allow login without TFA for %d days)', $trusted_for, 'two-factor-authentication'), $trusted_for),
1170 'is_trusted' => __('(Trusted device)', 'two-factor-authentication'),
1171 'nonce' => wp_create_nonce('simba_tfa_loginform_nonce'),
1172 'login_form_selectors' => '',
1173 'login_form_off_selectors' => '',
1174 );
1175
1176 // Spinner exists since WC 3.8. Use the proper functions to avoid SSL warnings.
1177 if (file_exists(ABSPATH.'wp-admin/images/spinner-2x.gif')) {
1178 $localize['spinnerimg'] = admin_url('images/spinner-2x.gif');
1179 } elseif (file_exists(ABSPATH.WPINC.'/images/spinner-2x.gif')) {
1180 $localize['spinnerimg'] = includes_url('images/spinner-2x.gif');
1181 }
1182
1183 $localize = apply_filters('simba_tfa_login_enqueue_localize', $localize);
1184
1185 wp_localize_script('tfa-ajax-request', 'simba_tfasettings', $localize);
1186
1187 }
1188
1189 /**
1190 * Return or output view content
1191 *
1192 * @param String $path - path to template, usually relative to templates/ within the plugin directory
1193 * @param Array $extract_these - key/value pairs for substitution into the scope of the template
1194 * @param Boolean $return_instead_of_echo - what to do with the results
1195 *
1196 * @return String|Void
1197 */
1198 public function include_template($path, $extract_these = array(), $return_instead_of_echo = false) {
1199
1200 if ($return_instead_of_echo) ob_start();
1201
1202 $template_file = apply_filters('simatfa_template_file', $this->templates_dir().'/'.$path, $path, $extract_these, $return_instead_of_echo);
1203
1204 do_action('simbatfa_before_template', $path, $return_instead_of_echo, $extract_these, $template_file);
1205
1206 if (!file_exists($template_file)) {
1207 error_log("TFA: template not found: $template_file (from $path)");
1208 echo __('Error:', 'two-factor-authentication').' '.__('two-factor-authentication', 'wp-optimize')." (".$path.")";
1209 } else {
1210 extract($extract_these);
1211 // The following are useful variables which can be used in the template.
1212 // They appear as unused, but may be used in the $template_file.
1213 $wpdb = $GLOBALS['wpdb'];// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wpdb might be used in the included template
1214 $simba_tfa = $this;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wp_optimize might be used in the included template
1215 include $template_file;
1216 }
1217
1218 do_action('simbatfa_after_template', $path, $return_instead_of_echo, $extract_these, $template_file);
1219
1220 if ($return_instead_of_echo) return ob_get_clean();
1221 }
1222
1223 /**
1224 * Make sure that self::$frontend is the instance of Simba_TFA_Frontend, and return it
1225 *
1226 * @return Simba_TFA_Frontend
1227 */
1228 public function load_frontend() {
1229 if (!class_exists('Simba_TFA_Frontend')) require_once($this->includes_dir().'/tfa_frontend.php');
1230 if (empty($this->frontend)) $this->frontend = new Simba_TFA_Frontend($this);
1231 return $this->frontend;
1232 }
1233
1234 // __return_empty_string() does not exist until WP 3.7
1235 public function shortcode_when_not_logged_in() {
1236 return '';
1237 }
1238
1239 /**
1240 * Set authentication slug.
1241 *
1242 * @param String $authentication_slug - Authentication slug. Verify that two-factor authentication should not be repeated for the same slug.
1243 */
1244 public function set_authentication_slug($authentication_slug) {
1245 $this->authentication_slug = $authentication_slug;
1246 }
1247
1248 }
1249