PluginProbe
Two Factor Authentication / 1.14.8
Two Factor Authentication v1.14.8
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.8, at simba-tfa/simba-tfa.php

1,342 lines 45.5 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 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_if_required_redirect_to');
378 register_setting('tfa_user_roles_required_group', 'tfa_hide_turn_off');
379 register_setting('tfa_user_roles_trusted_group', 'tfa_trusted_for');
380 register_setting('simba_tfa_woocommerce_group', 'tfa_wc_add_section');
381 register_setting('simba_tfa_woocommerce_group', 'tfa_bot_protection');
382 register_setting('simba_tfa_default_hmac_group', 'tfa_default_hmac');
383 register_setting('tfa_xmlrpc_status_group', 'tfa_xmlrpc_on');
384 }
385
386 /**
387 * See whether TFA is available or not for a particular user - i.e. whether the administrator has permitted it for their user level
388 *
389 * @param Integer $user_id - WordPress user ID
390 *
391 * @return Boolean
392 */
393 public function is_activated_for_user($user_id) {
394
395 if (empty($user_id)) return false;
396
397 // Super admin is not a role (they are admins with an extra attribute); needs separate handling
398 if (is_multisite() && is_super_admin($user_id)) {
399 // This is always a final decision - we don't want it to drop through to the 'admin' role's setting
400 $role = '_super_admin';
401 $db_val = $this->get_option('tfa_'.$role);
402 // Defaults to true if no setting has been saved
403 return (false === $db_val || $db_val) ? true : false;
404 }
405
406 $roles = $this->get_user_roles($user_id);
407
408 // N.B. This populates with roles on the current site within a multisite
409 foreach ($roles as $role) {
410 $db_val = $this->get_option('tfa_'.$role);
411 if (false === $db_val || $db_val) return true;
412 }
413
414 return false;
415
416 }
417
418 /**
419 * Get all user roles for a given user (if on multisite, amalgamates all roles from all sites)
420 *
421 * @param Integer $user_id - WordPress user ID
422 *
423 * @return Array
424 */
425 protected function get_user_roles($user_id) {
426
427 // Get roles on the main site
428 $user = new WP_User($user_id);
429 $roles = (array) $user->roles;
430
431 // On multisite, also check roles on non-main sites
432 if (is_multisite()) {
433 global $wpdb, $table_prefix;
434 $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));
435 if (is_array($roles_db)) {
436 foreach ($roles_db as $role_info) {
437 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;
438 $site_roles = unserialize($role_info->meta_value);
439 if (!is_array($site_roles)) continue;
440 foreach ($site_roles as $role => $active) {
441 if ($active && !in_array($role, $roles)) $roles[] = $role;
442 }
443 }
444 }
445 }
446
447 return $roles;
448 }
449
450 /**
451 * Check if TFA is required for a specified user
452 *
453 * N.B. - This doesn't check is_activated_for_user() - the caller would normally want to do that first
454 *
455 * @param $user_id Integer - the WP user ID
456 *
457 * @return Boolean
458 */
459 public function is_required_for_user($user_id) {
460 return apply_filters('simba_tfa_required_for_user', $this->user_property_active($user_id, 'required_'), $user_id);
461 }
462
463 /**
464 * See if a particular user property is active
465 *
466 * @param Integer $user_id
467 * @param String $prefix - e.g. "required_", "trusted_"
468 *
469 * @return Boolean
470 */
471 public function user_property_active($user_id, $prefix = 'required_') {
472
473 if (empty($user_id)) return false;
474
475 // Super admin is not a role (they are admins with an extra attribute); needs separate handling
476 if (is_multisite() && is_super_admin($user_id)) {
477 // This is always a final decision - we don't want it to drop through to the 'admin' role's setting
478 $role = '_super_admin';
479 $db_val = $this->get_option('tfa_'.$prefix.$role);
480 return $db_val ? true : false;
481 }
482
483 $roles = $this->get_user_roles($user_id);
484
485 foreach ($roles as $role) {
486 $db_val = $this->get_option('tfa_'.$prefix.$role);
487 if ($db_val) return true;
488 }
489
490 return false;
491
492 }
493
494 /**
495 * 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.
496 *
497 * @param Integer $user_id
498 *
499 * @return Boolean
500 */
501 public function is_activated_by_user($user_id) {
502 $enabled = get_user_meta($user_id, 'tfa_enable_tfa', true);
503 return !empty($enabled);
504 }
505
506 /**
507 * Get a list of trusted devices for the user
508 *
509 * @param Integer|Boolean $user_id - WordPress user ID, or false for the current user
510 *
511 * @return Array
512 */
513 public function user_get_trusted_devices($user_id = false) {
514
515 if (false === $user_id) {
516 global $current_user;
517 $user_id = $current_user->ID;
518 }
519
520 $trusted_devices = get_user_meta($user_id, 'tfa_trusted_devices', true);
521
522 if (!is_array($trusted_devices)) $trusted_devices = array();
523
524 return $trusted_devices;
525 }
526
527 /**
528 * Trust the current device
529 *
530 * @param Integer $user_id - WordPress user ID
531 * @param Integer $trusted_for - time to trust for, in days
532 */
533 public function trust_device($user_id, $trusted_for) {
534
535 $trusted_devices = $this->user_get_trusted_devices($user_id);
536
537 $time_now = time();
538
539 foreach ($trusted_devices as $k => $device) {
540 if (empty($device['until']) || $device['until'] <= $time_now) unset($trusted_devices[$k]);
541 }
542
543 $until = $time_now + $trusted_for * 86400;
544
545 $token = bin2hex($this->random_bytes(40));
546
547 $trusted_devices[] = array(
548 'ip' => $_SERVER['REMOTE_ADDR'],
549 'until' => $until,
550 'user_agent' => empty($_SERVER['HTTP_USER_AGENT']) ? '' : (string) $_SERVER['HTTP_USER_AGENT'],
551 'token' => $token
552 );
553
554 $this->user_set_trusted_devices($user_id, $trusted_devices);
555
556 $this->set_cookie('simbatfa_trust_token', $token, $until);
557 }
558
559 /**
560 * Returns true if running on a PHP version on which mcrypt has been deprecated
561 *
562 * @return Boolean
563 */
564 public function is_mcrypt_deprecated() {
565 return (7 == PHP_MAJOR_VERSION && PHP_MINOR_VERSION >= 1);
566 }
567
568 /**
569 * Return the specified number of bytes
570 *
571 * @param Integer $bytes
572 *
573 * @throws Exception
574 *
575 * @return String
576 */
577 public function random_bytes($bytes) {
578 if (function_exists('random_bytes')) {
579 return random_bytes($bytes);
580 } elseif (function_exists('mcrypt_create_iv')) {
581 return $this->is_mcrypt_deprecated() ? @mcrypt_create_iv($bytes, MCRYPT_RAND) : mcrypt_create_iv($bytes, MCRYPT_RAND);
582 } elseif (function_exists('openssl_random_pseudo_bytes')) {
583 return openssl_random_pseudo_bytes($bytes);
584 }
585 throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed');
586 }
587
588 /**
589 * Set a cookie so that, however we logged in, it can be found
590 *
591 * @param String $name - the cookie name
592 * @param String $value - the cookie value
593 * @param Integer $expires - when the cookie expires, in epoch time. Defaults to 24 hours' time. Values in the past cause cookie deletion.
594 */
595 protected function set_cookie($name, $value, $expires = null) {
596 if (null === $expires) $expires = time() + 86400;
597 $secure = is_ssl();
598 $secure_logged_in_cookie = ($secure && 'https' === parse_url(get_option('home'), PHP_URL_SCHEME));
599 $secure = apply_filters('secure_auth_cookie', $secure, get_current_user_id());
600 $secure_logged_in_cookie = apply_filters('secure_logged_in_cookie', $secure_logged_in_cookie, get_current_user_id(), $secure);
601
602 setcookie($name, $value, $expires, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true);
603 setcookie($name, $value, $expires, COOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true);
604 if (COOKIEPATH != SITECOOKIEPATH) {
605 setcookie($name, $value, $expires, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true);
606 }
607 }
608
609 /**
610 * Get a list of trusted devices for the user
611 *
612 * @param Integer $user_id - WordPress user ID
613 * @param Array $trusted_devices - the list of devices
614 */
615 public function user_set_trusted_devices($user_id, $trusted_devices) {
616 update_user_meta($user_id, 'tfa_trusted_devices', $trusted_devices);
617 }
618
619 /**
620 * Get the user capability needed for managing TFA users.
621 * 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)
622 *
623 * @return String
624 */
625 public function get_management_capability() {
626 return apply_filters('simba_tfa_management_capability', 'manage_options');
627 }
628
629 /**
630 * Used with set_error_handler()
631 *
632 * @param Integer $errno
633 * @param String $errstr
634 * @param String $errfile
635 * @param Integer $errline
636 *
637 * @return Boolean
638 */
639 public function get_php_errors($errno, $errstr, $errfile, $errline) {
640 if (0 == error_reporting()) return true;
641 $logline = $this->php_error_to_logline($errno, $errstr, $errfile, $errline);
642 $this->logged[] = $logline;
643 # Don't pass it up the chain (since it's going to be output to the user always)
644 return true;
645 }
646
647 public function php_error_to_logline($errno, $errstr, $errfile, $errline) {
648 switch ($errno) {
649 case 1: $e_type = 'E_ERROR'; break;
650 case 2: $e_type = 'E_WARNING'; break;
651 case 4: $e_type = 'E_PARSE'; break;
652 case 8: $e_type = 'E_NOTICE'; break;
653 case 16: $e_type = 'E_CORE_ERROR'; break;
654 case 32: $e_type = 'E_CORE_WARNING'; break;
655 case 64: $e_type = 'E_COMPILE_ERROR'; break;
656 case 128: $e_type = 'E_COMPILE_WARNING'; break;
657 case 256: $e_type = 'E_USER_ERROR'; break;
658 case 512: $e_type = 'E_USER_WARNING'; break;
659 case 1024: $e_type = 'E_USER_NOTICE'; break;
660 case 2048: $e_type = 'E_STRICT'; break;
661 case 4096: $e_type = 'E_RECOVERABLE_ERROR'; break;
662 case 8192: $e_type = 'E_DEPRECATED'; break;
663 case 16384: $e_type = 'E_USER_DEPRECATED'; break;
664 case 30719: $e_type = 'E_ALL'; break;
665 default: $e_type = "E_UNKNOWN ($errno)"; break;
666 }
667
668 if (!is_string($errstr)) $errstr = serialize($errstr);
669
670 if (0 === strpos($errfile, ABSPATH)) $errfile = substr($errfile, strlen(ABSPATH));
671
672 return "PHP event: code $e_type: $errstr (line $errline, $errfile)";
673
674 }
675
676 /**
677 * Runs upon the WordPress 'init' action
678 */
679 public function init() {
680 if ((!is_admin() || (defined('DOING_AJAX') && DOING_AJAX)) && is_user_logged_in() && file_exists($this->includes_dir().'/tfa_frontend.php')) {
681 $this->load_frontend();
682 } else {
683 add_shortcode('twofactor_user_settings', array($this, 'shortcode_when_not_logged_in'));
684 }
685 }
686
687 /**
688 * Return the TOTP provider object.
689 *
690 * @param String $controller_id - which controller
691 *
692 * @return Simba_TFA_Provider_totp
693 */
694 public function get_controller($controller_id = 'totp') {
695 return $this->controllers[$controller_id];
696 }
697
698 /**
699 * Return all OTP controllers
700 *
701 * @return Array
702 */
703 public function get_controllers() {
704 return $this->controllers;
705 }
706
707 /**
708 * Deprecated synonym for get_controller('totp')
709 *
710 * @return Simba_TFA_Provider_totp
711 */
712 public function get_totp_controller() {
713 trigger_error("Deprecated: Call get_controller('totp'), not get_totp_controller()", E_USER_WARNING);
714 return $this->get_controller('totp');
715 }
716
717 /**
718 * "Shared" - i.e. could be called from either front-end or back-end
719 */
720 public function shared_ajax() {
721
722 if (empty($_POST['subaction']) || empty($_POST['nonce']) || !is_user_logged_in() || !wp_verify_nonce($_POST['nonce'], 'tfa_shared_nonce')) die('Security check (3).');
723
724 global $current_user;
725
726 $subaction = $_POST['subaction'];
727
728 if ('refreshotp' == $subaction) {
729
730 $code = $this->get_controller('totp')->get_current_code($current_user->ID);
731
732 if (false === $code) die(json_encode(array('code' => '')));
733
734 die(json_encode(array('code' => $code)));
735
736 } elseif ('untrust_device' == $subaction && isset($_POST['device_id'])) {
737 $this->untrust_device(stripslashes($_POST['device_id']));
738 ob_start();
739 $this->include_template('trusted-devices-inner-box.php', array('trusted_devices' => $this->user_get_trusted_devices()));
740 echo json_encode(array('trusted_list' => ob_get_clean()));
741 }
742
743 exit;
744
745 }
746
747 /**
748 * Mark a device as untrusted for the current user
749 *
750 * @param String $device_id
751 */
752 protected function untrust_device($device_id) {
753
754 $trusted_devices = $this->user_get_trusted_devices();
755
756 unset($trusted_devices[$device_id]);
757
758 global $current_user;
759 $current_user_id = $current_user->ID;
760
761 $this->user_set_trusted_devices($current_user_id, $trusted_devices);
762
763 }
764
765 /**
766 * Called upon the AJAX action simbatfa-init-otp . Will die.
767 *
768 * Uses these keys from $_POST: user
769 */
770 public function tfaInitLogin() {
771
772 if (empty($_POST['user'])) die('Security check (2).');
773
774 if (defined('TWO_FACTOR_DISABLE') && TWO_FACTOR_DISABLE) {
775 $res = array('result' => false, 'user_can_trust' => false);
776 } else {
777
778 if (!function_exists('sanitize_user')) require_once ABSPATH.WPINC.'/formatting.php';
779
780 // WP's password-checking sanitizes the supplied user, so we must do the same to check if TFA is enabled for them
781 $auth_info = array('log' => sanitize_user(stripslashes((string)$_POST['user'])));
782
783 if (!empty($_COOKIE['simbatfa_trust_token'])) $auth_info['trust_token'] = (string) $_COOKIE['simbatfa_trust_token'];
784
785 $res = $this->pre_auth($auth_info, 'array');
786 }
787
788 $results = array(
789 'jsonstarter' => 'justhere',
790 'status' => $res['result'],
791 );
792
793 if (!empty($res['user_can_trust'])) {
794 $results['user_can_trust'] = 1;
795 if (!empty($res['user_already_trusted'])) $results['user_already_trusted'] = 1;
796 }
797
798
799 if (!empty($this->output_buffering)) {
800 if (!empty($this->logged)) {
801 $results['php_output'] = $this->logged;
802 }
803 restore_error_handler();
804 $buffered = ob_get_clean();
805 if ($buffered) $results['extra_output'] = $buffered;
806 }
807
808 $results = apply_filters('simbatfa_check_tfa_requirements_ajax_response', $results);
809
810 echo json_encode($results);
811
812 exit;
813 }
814
815 /**
816 * Enable or disable TFA for a user
817 *
818 * @param Integer $user_id - the WordPress user ID
819 * @param String $setting - either "true" (to turn on) or "false" (to turn off)
820 */
821 public function change_tfa_enabled_status($user_id, $setting) {
822 $previously_enabled = $this->is_activated_by_user($user_id) ? 1 : 0;
823 $setting = ('true' === $setting) ? 1 : 0;
824 update_user_meta($user_id, 'tfa_enable_tfa', $setting);
825 do_action('simba_tfa_activation_status_saved', $user_id, $setting, $previously_enabled, $this);
826 }
827
828 /**
829 * 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).
830 *
831 * @param WP_Error|WP_User $user
832 * @param String $username - this is not necessarily the WP username; it is whatever was typed in the form, so can be an email address
833 * @param String $password
834 *
835 * @return WP_Error|WP_User
836 */
837 public function tfaVerifyCodeAndUser($user, $username, $password) {
838 // When both the AIOWPS and Two Factor Authentication plugins are active, this function is called more than once; that should be short-circuited.
839 if (isset(self::$is_authenticated[$this->authentication_slug]) && self::$is_authenticated[$this->authentication_slug]) {
840 return $user;
841 }
842
843 $original_user = $user;
844 $params = stripslashes_deep($_POST);
845
846 // 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
847 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))) {
848 // This forces a new password authentication below
849 $user = false;
850 }
851
852 if (is_wp_error($user)) {
853 $ret = $user;
854 } else {
855
856 if (is_object($user) && isset($user->ID) && isset($user->user_login)) {
857 $params['log'] = $user->user_login;
858 // Confirm that this is definitely a username regardless of its format
859 $may_be_email = false;
860 } else {
861 $params['log'] = $username;
862 $may_be_email = true;
863 }
864
865 $params['caller'] = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['REQUEST_URI'];
866 if (!empty($_COOKIE['simbatfa_trust_token'])) $params['trust_token'] = (string) $_COOKIE['simbatfa_trust_token'];
867
868 if (isset($from_password) && false !== $from_password) {
869 // Support login forms that can't be hooked via appending to the password
870 $speculatively_try_appendage = true;
871 $params['two_factor_code'] = $from_password['tfa_code'];
872 }
873
874 $code_ok = $this->authorise_user_from_login($params, $may_be_email);
875
876 if (is_wp_error($code_ok)) {
877 $ret = $code_ok;
878 } elseif (!$code_ok) {
879 $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')));
880 } elseif ($user) {
881 $ret = $user;
882 } else {
883
884 if (!empty($speculatively_try_appendage) && true === $code_ok) {
885 $password = $from_password['password'];
886 }
887
888 $username_is_email = false;
889
890 if (function_exists('wp_authenticate_username_password') && $may_be_email && filter_var($username, FILTER_VALIDATE_EMAIL)) {
891 global $wpdb;
892 // This has to match self::authorise_user_from_login()
893 $response = $wpdb->get_row($wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_email=%s", $username));
894 if (is_object($response)) $username_is_email = true;
895 }
896
897 $ret = $username_is_email ? wp_authenticate_email_password(null, $username, $password) : wp_authenticate_username_password(null, $username, $password);
898 }
899
900 }
901
902 $ret = apply_filters('simbatfa_verify_code_and_user_result', $ret, $original_user, $username, $password);
903
904 // If the TFA code was actually validated (not just not required, for example), then $code_ok is (boolean)true
905 if (isset($code_ok) && true === $code_ok && is_a($ret, 'WP_User')) {
906 // Though $_SERVER['SERVER_NAME'] can't always be trusted (if the webserver is misconfigured), anyone using this already has password and TFA clearance.
907 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']))))) {
908
909 $trusted_for = $this->get_option('tfa_trusted_for');
910 $trusted_for = (false === $trusted_for) ? 30 : (string) absint($trusted_for);
911
912 $this->trust_device($ret->ID, $trusted_for);
913 }
914 }
915
916 self::$is_authenticated[$this->authentication_slug] = true;
917
918 return $ret;
919 }
920
921 // N.B. - This doesn't check is_activated_for_user() - the caller would normally want to do that first
922 public function user_can_trust($user_id) {
923 // 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
924 return apply_filters('simba_tfa_user_can_trust', false, $user_id);
925 }
926
927 /**
928 * Should the user be asked for a TFA code? And optionally, is the user allowed to trust devices?
929 *
930 * @param Array $params - the key used is 'log', indicating the username or email address
931 * @param String $response_format - 'simple' (historic format) or 'array' (richer info)
932 *
933 * @return Boolean
934 */
935 public function pre_auth($params, $response_format = 'simple') {
936 global $wpdb;
937
938 $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']);
939 $user = $wpdb->get_row($query);
940
941 if (!$user && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) {
942 // Corner-case: login looks like an email, but is a username rather than email address
943 $user = $wpdb->get_row($wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_login=%s", $params['log']));
944 }
945
946 $is_activated_for_user = true;
947 $is_activated_by_user = false;
948
949 $result = false;
950
951 $totp_controller = $this->get_controller('totp');
952
953 if ($user) {
954 $tfa_priv_key = get_user_meta($user->ID, 'tfa_priv_key_64', true);
955 $is_activated_for_user = $this->is_activated_for_user($user->ID);
956 $is_activated_by_user = $this->is_activated_by_user($user->ID);
957
958 if ($is_activated_for_user && $is_activated_by_user) {
959
960 // No private key yet, generate one. This shouldn't really be possible.
961 if (!$tfa_priv_key) $tfa_priv_key = $totp_controller->addPrivateKey($user->ID);
962
963 $code = $totp_controller->generateOTP($user->ID, $tfa_priv_key);
964
965 $result = true;
966 }
967 }
968
969 if ('array' != $response_format) return $result;
970
971 $ret = array('result' => $result);
972
973 if ($result) {
974 $ret['user_can_trust'] = $this->user_can_trust($user->ID);
975 if (!empty($params['trust_token']) && $this->user_trust_token_valid($user->ID, $params['trust_token'])) {
976 $ret['user_already_trusted'] = 1;
977 }
978 }
979
980 return $ret;
981 }
982
983 /**
984 * Print the radio buttons for enabling/disabling TFA
985 *
986 * @param Integer $user_id - the WordPress user ID
987 * @param Boolean $long_label - whether to use a long label rather than a short one
988 * @param String $style - valid values are "show_current" and "require_current"
989 */
990 public function paint_enable_tfa_radios($user_id, $long_label = false, $style = 'show_current') {
991
992 if (!$user_id) return;
993
994 if ('require_current' != $style) $style = 'show_current';
995
996 $is_required = $this->is_required_for_user($user_id);
997 $is_activated = $this->is_activated_by_user($user_id);
998
999 if ($is_required) {
1000 $require_after = absint($this->get_option('tfa_requireafter'));
1001 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>';
1002 }
1003
1004 $tfa_enabled_label = $long_label ? __('Enable two-factor authentication', 'two-factor-authentication') : __('Enabled', 'two-factor-authentication');
1005
1006 if ('show_current' == $style) {
1007 $tfa_enabled_label .= ' '.sprintf(__('(Current code: %s)', 'two-factor-authentication'), $this->get_controller('totp')->current_otp_code($user_id));
1008 } elseif ('require_current' == $style) {
1009 $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">');
1010 }
1011
1012 $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;
1013
1014 $tfa_disabled_label = $long_label ? __('Disable two-factor authentication', 'two-factor-authentication') : __('Disabled', 'two-factor-authentication');
1015
1016 if ('require_current' == $style) echo '<input type="hidden" name="require_current" value="1">'."\n";
1017
1018 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>';
1019
1020 // 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
1021 // 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.
1022 if ($show_disable) {
1023 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>';
1024 }
1025 }
1026
1027 /**
1028 * Retrieve a saved option
1029 *
1030 * @param String $key - option key
1031 *
1032 * @return Mixed
1033 */
1034 public function get_option($key) {
1035 if (!is_multisite()) return get_option($key);
1036 $main_site_id = function_exists('get_main_site_id') ? get_main_site_id() : 1;
1037 $get_option_site_id = apply_filters('simba_tfa_get_option_site_id', $main_site_id);
1038 switch_to_blog($get_option_site_id);
1039 $value = get_option($key);
1040 restore_current_blog();
1041 return $value;
1042 }
1043
1044 /**
1045 * Paint a list of checkboxes, one for each role
1046 *
1047 * @param String $prefix
1048 * @param Integer $default - default value (0 or 1)
1049 */
1050 public function list_user_roles_checkboxes($prefix = '', $default = 1) {
1051 if (is_multisite()) {
1052 // Not a real WP role; needs separate handling
1053 $id = '_super_admin';
1054 $name = __('Multisite Super Admin', 'two-factor-authentication');
1055 $setting = $this->get_option('tfa_'.$prefix.$id);
1056 $setting = ($setting === false) ? $default : ($setting ? 1 : 0);
1057
1058 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";
1059 }
1060
1061 global $wp_roles;
1062 if (!isset($wp_roles)) $wp_roles = new WP_Roles();
1063
1064 foreach ($wp_roles->role_names as $id => $name) {
1065 $setting = $this->get_option('tfa_'.$prefix.$id);
1066 $setting = ($setting === false) ? $default : ($setting ? 1 : 0);
1067
1068 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";
1069 }
1070
1071 }
1072
1073 public function tfa_list_xmlrpc_status_radios() {
1074
1075 $setting = $this->get_option('tfa_xmlrpc_on');
1076 $setting = $setting ? 1 : 0;
1077
1078 $types = array(
1079 '0' => __('Do not require 2FA over XMLRPC (best option if you must use XMLRPC and your client does not support 2FA)', 'two-factor-authentication'),
1080 '1' => __('Do require 2FA over XMLRPC (best option if you do not use XMLRPC or are unsure)', 'two-factor-authentication')
1081 );
1082
1083 foreach($types as $id => $name) {
1084 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";
1085 }
1086 }
1087
1088 protected function is_caller_active() {
1089
1090 if (!defined('XMLRPC_REQUEST') || !XMLRPC_REQUEST) return true;
1091
1092 $saved_data = $this->get_option('tfa_xmlrpc_on');
1093
1094 return $saved_data ? true : false;
1095
1096 }
1097
1098 /**
1099 * @param Array $params
1100 * @param Boolean $may_be_email
1101 *
1102 * @return WP_Error|Boolean|Integer - WP_Error or false means failure; true or 1 means success, but true means the TFA code was validated
1103 */
1104 public function authorise_user_from_login($params, $may_be_email = false) {
1105
1106 $params = apply_filters('simbatfa_auth_user_from_login_params', $params);
1107
1108 global $wpdb;
1109
1110 if (!$this->is_caller_active()) return 1;
1111
1112 $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']);
1113 $response = $wpdb->get_row($query);
1114
1115 if (!$response && $may_be_email && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) {
1116 // Corner-case: login looks like an email, but is a username rather than email address
1117 $response = $wpdb->get_row($wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_login=%s", $params['log']));
1118 }
1119
1120 $user_ID = is_object($response) ? $response->ID : false;
1121 $user_registered = is_object($response) ? $response->user_registered : false;
1122
1123 $user_code = isset($params['two_factor_code']) ? str_replace(' ', '', trim($params['two_factor_code'])) : '';
1124
1125 // This condition in theory should not be possible
1126 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')));
1127
1128 if (!$this->is_activated_for_user($user_ID)) return 1;
1129
1130 if (!empty($params['trust_token']) && $this->user_trust_token_valid($user_ID, $params['trust_token'])) {
1131 return 1;
1132 }
1133
1134 if (!$this->is_activated_by_user($user_ID)) {
1135
1136 if (!$this->is_required_for_user($user_ID)) return 1;
1137
1138 $require_after = absint($this->get_option('tfa_requireafter')) * 86400;
1139
1140 $account_age = time() - strtotime($user_registered);
1141
1142 if ($account_age > $require_after && apply_filters('simbatfa_enforce_require_after_check', true, $user_ID, $require_after, $account_age)) {
1143 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')));
1144 }
1145
1146 return 1;
1147 }
1148
1149 $tfa_creds_user_id = !empty($params['creds_user_id']) ? $params['creds_user_id'] : $user_ID;
1150
1151 if ($tfa_creds_user_id != $user_ID) {
1152
1153 // Authenticating using a different user's credentials (e.g. https://wordpress.org/plugins/use-administrator-password/)
1154 // In this case, we require that different user to have TFA active - so that this mechanism can't be used to avoid TFA
1155
1156 if (!$this->is_activated_for_user($tfa_creds_user_id) || !$this->is_activated_by_user($tfa_creds_user_id)) {
1157 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')));
1158 }
1159
1160 }
1161
1162 return $this->get_controller('totp')->check_code_for_user($tfa_creds_user_id, $user_code);
1163
1164 }
1165
1166 /**
1167 * Evaluate whether a trust token is valid for a user
1168 *
1169 * @param Integer $user_id - WP user ID
1170 * @param String $trust_token - trust token
1171 *
1172 * @return Boolean
1173 */
1174 protected function user_trust_token_valid($user_id, $trust_token) {
1175
1176 if (!is_string($trust_token) || strlen($trust_token) < 30) return false;
1177
1178 $trusted_devices = $this->user_get_trusted_devices($user_id);
1179
1180 $time_now = time();
1181
1182 foreach ($trusted_devices as $device) {
1183 if (empty($device['until']) || $device['until'] <= $time_now) continue;
1184 if (!empty($device['token']) && $device['token'] === $trust_token) {
1185 return true;
1186 }
1187 }
1188
1189 return false;
1190 }
1191
1192 /**
1193 * 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.
1194 *
1195 * @return String
1196 */
1197 protected function get_ajax_url() {
1198 $ajax_url = admin_url('admin-ajax.php');
1199 $parsed_url = parse_url($ajax_url);
1200 if (strtolower($parsed_url['host']) !== strtolower($_SERVER['HTTP_HOST']) && !empty($parsed_url['path'])) {
1201 // Mismatch - return the relative URL only
1202 $ajax_url = $parsed_url['path'];
1203 }
1204 return $ajax_url;
1205 }
1206
1207 /**
1208 * 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.
1209 */
1210 public function login_enqueue_scripts() {
1211 if (!$this->should_enqueue_login_scripts()) {
1212 return;
1213 }
1214
1215 if (isset($_GET['action']) && 'logout ' != $_GET['action'] && 'login' != $_GET['action']) return;
1216
1217 static $already_done = false;
1218 if ($already_done) return;
1219 $already_done = true;
1220
1221 // Prevent caching when in debug mode
1222 $script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : filemtime($this->includes_dir().'/tfa.js');
1223
1224 wp_enqueue_script('tfa-ajax-request', $this->includes_url().'/tfa.js', array('jquery'), $script_ver);
1225
1226 $trusted_for = $this->get_option('tfa_trusted_for');
1227 $trusted_for = (false === $trusted_for) ? 30 : (string) absint($trusted_for);
1228
1229 $localize = array(
1230 'ajaxurl' => $this->get_ajax_url(),
1231 'click_to_enter_otp' => __("Click to enter One Time Password", 'two-factor-authentication'),
1232 'enter_username_first' => __('You have to enter a username first.', 'two-factor-authentication'),
1233 'otp' => __('One Time Password (i.e. 2FA)', 'two-factor-authentication'),
1234 'otp_login_help' => __('(check your OTP app to get this password)', 'two-factor-authentication'),
1235 '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),
1236 'is_trusted' => __('(Trusted device - no OTP code required)', 'two-factor-authentication'),
1237 'nonce' => wp_create_nonce('simba_tfa_loginform_nonce'),
1238 'login_form_selectors' => '',
1239 'login_form_off_selectors' => '',
1240 'error' => __('An error has occurred. Site owners can check the JavaScript console for more details.', 'two-factor-authentication'),
1241 );
1242
1243 // Spinner exists since WC 3.8. Use the proper functions to avoid SSL warnings.
1244 if (file_exists(ABSPATH.'wp-admin/images/spinner-2x.gif')) {
1245 $localize['spinnerimg'] = admin_url('images/spinner-2x.gif');
1246 } elseif (file_exists(ABSPATH.WPINC.'/images/spinner-2x.gif')) {
1247 $localize['spinnerimg'] = includes_url('images/spinner-2x.gif');
1248 }
1249
1250 $localize = apply_filters('simba_tfa_login_enqueue_localize', $localize);
1251
1252 wp_localize_script('tfa-ajax-request', 'simba_tfasettings', $localize);
1253
1254 }
1255
1256 /**
1257 * Check whether TFA login scripts should be enqueued or not.
1258 *
1259 * @return boolean True if the TFA login script should be enqueued, otherwise false.
1260 */
1261 private function should_enqueue_login_scripts() {
1262 if (defined('TWO_FACTOR_DISABLE') && TWO_FACTOR_DISABLE) {
1263 return apply_filters('simbatfa_enqueue_login_scripts', false);
1264 }
1265
1266 global $wpdb;
1267 $sql = $wpdb->prepare('SELECT COUNT(user_id) FROM ' . $wpdb->usermeta . ' WHERE meta_key = %s AND meta_value = %d LIMIT 1', 'tfa_enable_tfa', 1);
1268 $count_user_id = $wpdb->get_var($sql);
1269
1270 if (is_null($count_user_id)) { // Error in query.
1271 return apply_filters('simbatfa_enqueue_login_scripts', true);
1272 } elseif ($count_user_id > 0) { // A user exists with TFA enabled.
1273 return apply_filters('simbatfa_enqueue_login_scripts', true);
1274 }
1275
1276 // No user exists with TFA enabled.
1277 return apply_filters('simbatfa_enqueue_login_scripts', false);
1278 }
1279
1280
1281 /**
1282 * Return or output view content
1283 *
1284 * @param String $path - path to template, usually relative to templates/ within the plugin directory
1285 * @param Array $extract_these - key/value pairs for substitution into the scope of the template
1286 * @param Boolean $return_instead_of_echo - what to do with the results
1287 *
1288 * @return String|Void
1289 */
1290 public function include_template($path, $extract_these = array(), $return_instead_of_echo = false) {
1291
1292 if ($return_instead_of_echo) ob_start();
1293
1294 $template_file = apply_filters('simatfa_template_file', $this->templates_dir().'/'.$path, $path, $extract_these, $return_instead_of_echo);
1295
1296 do_action('simbatfa_before_template', $path, $return_instead_of_echo, $extract_these, $template_file);
1297
1298 if (!file_exists($template_file)) {
1299 error_log("TFA: template not found: $template_file (from $path)");
1300 echo __('Error:', 'two-factor-authentication').' '.__('two-factor-authentication', 'wp-optimize')." (".$path.")";
1301 } else {
1302 extract($extract_these);
1303 // The following are useful variables which can be used in the template.
1304 // They appear as unused, but may be used in the $template_file.
1305 $wpdb = $GLOBALS['wpdb'];// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wpdb might be used in the included template
1306 $simba_tfa = $this;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wp_optimize might be used in the included template
1307 $totp_controller = $this->get_controller('totp');// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wp_optimize might be used in the included template
1308 include $template_file;
1309 }
1310
1311 do_action('simbatfa_after_template', $path, $return_instead_of_echo, $extract_these, $template_file);
1312
1313 if ($return_instead_of_echo) return ob_get_clean();
1314 }
1315
1316 /**
1317 * Make sure that self::$frontend is the instance of Simba_TFA_Frontend, and return it
1318 *
1319 * @return Simba_TFA_Frontend
1320 */
1321 public function load_frontend() {
1322 if (!class_exists('Simba_TFA_Frontend')) require_once($this->includes_dir().'/tfa_frontend.php');
1323 if (empty($this->frontend)) $this->frontend = new Simba_TFA_Frontend($this);
1324 return $this->frontend;
1325 }
1326
1327 // __return_empty_string() does not exist until WP 3.7
1328 public function shortcode_when_not_logged_in() {
1329 return '';
1330 }
1331
1332 /**
1333 * Set authentication slug.
1334 *
1335 * @param String $authentication_slug - Authentication slug. Verify that two-factor authentication should not be repeated for the same slug.
1336 */
1337 public function set_authentication_slug($authentication_slug) {
1338 $this->authentication_slug = $authentication_slug;
1339 }
1340
1341 }
1342