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

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