PluginProbe
Two Factor Authentication / 1.14.10
Two Factor Authentication v1.14.10
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.10, at simba-tfa/simba-tfa.php

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