PluginProbe
Two Factor Authentication / 1.2.6
Two Factor Authentication v1.2.6
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 / two-factor-login.php

two-factor-login.php in Two Factor Authentication 1.2.6, at two-factor-login.php

837 lines 31.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: Two Factor Authentication
4 Plugin URI: https://www.simbahosting.co.uk/s3/product/two-factor-authentication/
5 Description: Secure your WordPress login forms with two factor authentication - including WooCommerce login forms
6 Author: David Nutbourne + David Anderson, original plugin by Oskar Hane
7 Author URI: https://www.simbahosting.co.uk
8 Version: 1.2.6
9 License: GPLv2 or later
10 */
11
12 define('SIMBA_TFA_TEXT_DOMAIN', 'two-factor-authentication');
13 define('SIMBA_TFA_PLUGIN_DIR', dirname( __FILE__ ));
14 define('SIMBA_TFA_PLUGIN_URL', plugins_url('', __FILE__));
15
16 class Simba_Two_Factor_Authentication {
17
18 public $version = '1.2.6';
19 private $php_required = '5.3';
20
21 private $frontend;
22
23 public function __construct() {
24
25 if (version_compare(PHP_VERSION, $this->php_required, '<' )) {
26 add_action('all_admin_notices', array($this, 'admin_notice_insufficient_php'));
27 $abort = true;
28 }
29
30 if (!function_exists('mcrypt_get_iv_size')) {
31 add_action('all_admin_notices', array($this, 'admin_notice_missing_mcrypt'));
32 $abort = true;
33 }
34
35 if (!empty($abort)) return;
36
37 if (file_exists(SIMBA_TFA_PLUGIN_DIR.'/premium.php')) include_once(SIMBA_TFA_PLUGIN_DIR.'/premium.php');
38
39 add_action('wp_ajax_nopriv_simbatfa-init-otp', array($this, 'tfaInitLogin'));
40
41 add_action('wp_ajax_simbatfa_shared_ajax', array($this, 'shared_ajax'));
42
43 add_action('woocommerce_before_customer_login_form', array($this, 'woocommerce_before_customer_login_form'));
44 // The login form on the checkout doesn't call the woocommerce_before_customer_login_form action
45 add_action('woocommerce_before_checkout_form', array($this, 'woocommerce_before_customer_login_form'));
46
47 if (is_admin()) {
48 //Save settings
49 add_action('admin_init', array($this, 'check_possible_reset'));
50
51 //Add to Settings menu on sites
52 add_action('admin_menu', array($this, 'menu_entry_for_admin'));
53
54 //Add settings link in plugin list
55 $plugin = plugin_basename(__FILE__);
56 add_filter("plugin_action_links_".$plugin, array($this, 'addPluginSettingsLink' ));
57 add_filter('network_admin_plugin_action_links_'.$plugin, array($this, 'addPluginSettingsLink' ));
58
59 // Entry that everybody gets
60 add_action('network_admin_menu', array($this, 'admin_menu'));
61 add_action('admin_menu', array($this, 'admin_menu'));
62
63 } else {
64 add_action('init', array($this, 'check_possible_reset'));
65 }
66
67 add_action('plugins_loaded', array($this, 'plugins_loaded'));
68 add_action('init', array($this, 'init'));
69
70 //Show off sync message for hotp
71 add_action('admin_notices', array($this, 'tfaShowHOTPOffSyncMessage'));
72 add_action('login_enqueue_scripts', array($this, 'login_enqueue_scripts'));
73
74 if (!defined('TWO_FACTOR_DISABLE') || !TWO_FACTOR_DISABLE) {
75 add_filter('authenticate', array($this, 'tfaVerifyCodeAndUser'), 99999999999, 3);
76 }
77
78 if (file_exists(SIMBA_TFA_PLUGIN_DIR.'/updater/updater.php')) include_once(SIMBA_TFA_PLUGIN_DIR.'/updater/updater.php');
79
80 if (defined('DOING_AJAX') && DOING_AJAX && defined('WP_ADMIN') && WP_ADMIN && !empty($_REQUEST['action']) && 'simbatfa-init-otp' == $_REQUEST['action']) {
81 // Try to prevent PHP notices breaking the AJAX conversation
82 $this->output_buffering = true;
83 $this->logged = array();
84 set_error_handler(array($this, 'get_php_errors'), E_ALL & ~E_STRICT);
85 ob_start();
86 }
87
88 }
89
90 public function get_php_errors($errno, $errstr, $errfile, $errline) {
91 if (0 == error_reporting()) return true;
92 $logline = $this->php_error_to_logline($errno, $errstr, $errfile, $errline);
93 $this->logged[] = $logline;
94 # Don't pass it up the chain (since it's going to be output to the user always)
95 return true;
96 }
97
98 public function php_error_to_logline($errno, $errstr, $errfile, $errline) {
99 switch ($errno) {
100 case 1: $e_type = 'E_ERROR'; break;
101 case 2: $e_type = 'E_WARNING'; break;
102 case 4: $e_type = 'E_PARSE'; break;
103 case 8: $e_type = 'E_NOTICE'; break;
104 case 16: $e_type = 'E_CORE_ERROR'; break;
105 case 32: $e_type = 'E_CORE_WARNING'; break;
106 case 64: $e_type = 'E_COMPILE_ERROR'; break;
107 case 128: $e_type = 'E_COMPILE_WARNING'; break;
108 case 256: $e_type = 'E_USER_ERROR'; break;
109 case 512: $e_type = 'E_USER_WARNING'; break;
110 case 1024: $e_type = 'E_USER_NOTICE'; break;
111 case 2048: $e_type = 'E_STRICT'; break;
112 case 4096: $e_type = 'E_RECOVERABLE_ERROR'; break;
113 case 8192: $e_type = 'E_DEPRECATED'; break;
114 case 16384: $e_type = 'E_USER_DEPRECATED'; break;
115 case 30719: $e_type = 'E_ALL'; break;
116 default: $e_type = "E_UNKNOWN ($errno)"; break;
117 }
118
119 if (!is_string($errstr)) $errstr = serialize($errstr);
120
121 if (0 === strpos($errfile, ABSPATH)) $errfile = substr($errfile, strlen(ABSPATH));
122
123 return "PHP event: code $e_type: $errstr (line $errline, $errfile)";
124
125 }
126
127 public function init() {
128 if ((!is_admin() || (defined('DOING_AJAX') && DOING_AJAX)) && is_user_logged_in() && file_exists(SIMBA_TFA_PLUGIN_DIR.'/includes/tfa_frontend.php')) {
129 $this->load_frontend();
130 } else {
131 add_shortcode('twofactor_user_settings', array($this, 'shortcode_when_not_logged_in'));
132 }
133 }
134
135 public function admin_notice_insufficient_php() {
136 $this->show_admin_warning('<strong>'.__('Higher PHP version required', 'updraftplus').'</strong><br> '.sprintf(__('The Two Factor Authentication plugin requires PHP version %s or higher - your current version is only %s.', SIMBA_TFA_TEXT_DOMAIN), $this->php_required, PHP_VERSION), 'error');
137 }
138
139 public function admin_notice_missing_mcrypt() {
140 $this->show_admin_warning('<strong>'.__('PHP Mcrypt module required', 'updraftplus').'</strong><br> '.__('The Two Factor Authentication plugin requires the PHP mcrypt module to be installed. Please ask your web hosting company to install it.', SIMBA_TFA_TEXT_DOMAIN), 'error');
141 }
142
143 public function show_admin_warning($message, $class = "updated") {
144 echo '<div class="tfamessage '.$class.'">'."<p>$message</p></div>";
145 }
146
147 public function getTFA() {
148 if (!class_exists('HOTP')) require_once(SIMBA_TFA_PLUGIN_DIR.'/hotp-php-master/hotp.php');
149 if (!class_exists('Base32')) require_once(SIMBA_TFA_PLUGIN_DIR.'/Base32/Base32.php');
150 if (!class_exists('Simba_TFA')) require_once(SIMBA_TFA_PLUGIN_DIR.'/includes/class.TFA.php');
151
152 $tfa = new Simba_TFA(new Base32(), new HOTP());
153
154 return $tfa;
155 }
156
157 // "Shared" - i.e. could be called from either front-end or back-end
158 public function shared_ajax() {
159 if (empty($_POST['subaction']) || empty($_POST['nonce']) || !is_user_logged_in() || !wp_verify_nonce($_POST['nonce'], 'tfa_shared_nonce')) die('Security check (3).');
160
161 if ($_POST['subaction'] == 'refreshotp') {
162
163 global $current_user;
164
165 $tfa_priv_key_64 = get_user_meta($current_user->ID, 'tfa_priv_key_64', true);
166
167 if (!$tfa_priv_key_64) {
168 echo json_encode(array('code' => ''));
169 die;
170 }
171
172 echo json_encode(array('code' => $this->getTFA()->generateOTP($current_user->ID, $tfa_priv_key_64)));
173 exit;
174 }
175
176 }
177
178 public function tfaInitLogin() {
179
180 if (empty($_POST['user'])) die('Security check (2).');
181
182 if (defined('TWO_FACTOR_DISABLE') && TWO_FACTOR_DISABLE) {
183 $res = false;
184 } else {
185 $tfa = $this->getTFA();
186 $res = $tfa->preAuth(array('log' => (string)$_POST['user']));
187 }
188
189 $results = array('jsonstarter' => 'justhere', 'status' => $res);
190
191 if (!empty($this->output_buffering)) {
192 if (!empty($this->logged)) {
193 $results['php_output'] = $this->logged;
194 }
195 restore_error_handler();
196 $buffered = ob_get_clean();
197 if ($buffered) $results['extra_output'] = $buffered;
198 }
199
200 echo json_encode($results);
201 exit;
202 }
203
204
205 // Here's where the login action happens. Called on the 'authenticate' action.
206 public function tfaVerifyCodeAndUser($user, $username, $password) {
207
208 $tfa = $this->getTFA();
209
210 if (is_wp_error($user)) return $user;
211
212 $params = $_POST;
213 $params['log'] = $username;
214 $params['caller'] = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['REQUEST_URI'];
215
216 $code_ok = $tfa->authUserFromLogin($params);
217
218 if (is_wp_error($code_ok)) return $code_ok;
219
220 if (!$code_ok) return new WP_Error('authentication_failed', '<strong>'.__('Error:', SIMBA_TFA_TEXT_DOMAIN).'</strong> '.__('The one-time password (TFA code) you entered was incorrect.', SIMBA_TFA_TEXT_DOMAIN));
221
222 if ($user) return $user;
223
224 return wp_authenticate_username_password(null, $username, $password);
225 }
226
227 public function tfaRegisterTwoFactorAuthSettings()
228 {
229 global $wp_roles;
230 if (!isset($wp_roles))
231 $wp_roles = new WP_Roles();
232
233 foreach($wp_roles->role_names as $id => $name)
234 {
235 register_setting('tfa_user_roles_group', 'tfa_'.$id);
236 register_setting('tfa_user_roles_required_group', 'tfa_required_'.$id);
237 }
238
239 register_setting('tfa_user_roles_required_group', 'tfa_requireafter');
240 register_setting('simba_tfa_default_hmac_group', 'tfa_default_hmac');
241 register_setting('tfa_xmlrpc_status_group', 'tfa_xmlrpc_on');
242 }
243
244 public function tfaListEnableRadios($user_id, $long_label = false)
245 {
246 if(!$user_id)
247 return;
248
249 $setting = get_user_meta($user_id, 'tfa_enable_tfa', true);
250 $setting = !$setting ? false : $setting;
251
252 $tfa = $this->getTFA();
253
254 if ($tfa->isRequiredForUser($user_id)) {
255 $requireafter = absint($this->get_option('tfa_requireafter'));
256
257 echo '<p class="tfa_required_warning" style="font-weight:bold; font-style:italics;">'.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', SIMBA_TFA_TEXT_DOMAIN), $requireafter).'</p>';
258 }
259
260 $tfa_enabled_label = ($long_label) ? __('Enable two-factor authentication', SIMBA_TFA_TEXT_DOMAIN) : __('Enabled', SIMBA_TFA_TEXT_DOMAIN);
261 $tfa_disabled_label = ($long_label) ? __('Disable two-factor authentication', SIMBA_TFA_TEXT_DOMAIN) : __('Disabled', SIMBA_TFA_TEXT_DOMAIN);
262
263 print '<input type="radio" class="tfa_enable_radio" id="tfa_enable_tfa_true" name="tfa_enable_tfa" value="true" '.($setting == true ? '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>';
264
265 print '<input type="radio" class="tfa_enable_radio" id="tfa_enable_tfa_false" name="tfa_enable_tfa" value="false" '.($setting == false ? '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>';
266 }
267
268
269 public function tfaListAlgorithmRadios($user_id)
270 {
271 if(!$user_id) return;
272
273 $types = array('totp' => __('TOTP (time based - most common algorithm; used by Google Authenticator)', SIMBA_TFA_TEXT_DOMAIN), 'hotp' => __('HOTP (event based)', SIMBA_TFA_TEXT_DOMAIN));
274
275 $setting = get_user_meta($user_id, 'tfa_algorithm_type', true);
276 $setting = $setting === false || !$setting ? 'totp' : $setting;
277
278 foreach($types as $id => $name) {
279 print '<input type="radio" id="tfa_algorithm_type_'.esc_attr($id).'" name="tfa_algorithm_type" value="'.$id.'" '.($setting == $id ? 'checked="checked"' :'').'> <label for="tfa_algorithm_type_'.esc_attr($id).'">'.$name."</label><br>\n";
280 }
281 }
282
283 public function get_option($key) {
284 if (!is_multisite()) return get_option($key);
285 switch_to_blog(1);
286 $v = get_option($key);
287 restore_current_blog();
288 return $v;
289 }
290
291 public function tfaListUserRolesCheckboxes()
292 {
293
294 if (is_multisite()) {
295 // Not a real WP role; needs separate handling
296 $id = '_super_admin';
297 $name = __('Multisite Super Admin', SIMBA_TFA_TEXT_DOMAIN);
298 $setting = $this->get_option('tfa_'.$id);
299 $setting = $setting === false || $setting ? 1 : 0;
300
301 print '<input type="checkbox" id="tfa_'.$id.'" name="tfa_'.$id.'" value="1" '.($setting ? 'checked="checked"' :'').'> <label for="tfa_'.$id.'">'.htmlspecialchars($name)."</label><br>\n";
302 }
303
304 global $wp_roles;
305 if (!isset($wp_roles)) $wp_roles = new WP_Roles();
306
307 foreach($wp_roles->role_names as $id => $name)
308 {
309 $setting = $this->get_option('tfa_'.$id);
310 $setting = $setting === false || $setting ? 1 : 0;
311
312 print '<input type="checkbox" id="tfa_'.$id.'" name="tfa_'.$id.'" value="1" '.($setting ? 'checked="checked"' :'').'> <label for="tfa_'.$id.'">'.htmlspecialchars($name)."</label><br>\n";
313 }
314
315 }
316
317 public function tfaListDefaultHMACRadios()
318 {
319 $tfa = $this->getTFA();
320 $setting = $this->get_option('tfa_default_hmac');
321 $setting = $setting === false || !$setting ? $tfa->default_hmac : $setting;
322
323 $types = array('totp' => __('TOTP (time based - most common algorithm; used by Google Authenticator)', SIMBA_TFA_TEXT_DOMAIN), 'hotp' => __('HOTP (event based)', SIMBA_TFA_TEXT_DOMAIN));
324
325 foreach($types as $id => $name)
326 print '<input type="radio" id="tfa_default_hmac_'.esc_attr($id).'" name="tfa_default_hmac" value="'.$id.'" '.($setting == $id ? 'checked="checked"' :'').'> '.'<label for="tfa_default_hmac_'.esc_attr($id).'">'."$name</label><br>\n";
327 }
328
329 public function tfaListXMLRPCStatusRadios()
330 {
331 $tfa = $this->getTFA();
332 $setting = $this->get_option('tfa_xmlrpc_on');
333 $setting = $setting === false || !$setting ? 0 : 1;
334
335 $types = array(
336 '0' => __('Do not require 2FA over XMLRPC (best option if you must use XMLRPC and your client does not support 2FA)', SIMBA_TFA_TEXT_DOMAIN),
337 '1' => __('Do require 2FA over XMLRPC (best option if you do not use XMLRPC or are unsure)', SIMBA_TFA_TEXT_DOMAIN)
338 );
339
340 foreach($types as $id => $name)
341 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.'">'.$name."</label><br>\n";
342 }
343
344 public function tfaShowAdminSettingsPage()
345 {
346 $tfa = $this->getTFA();
347 require_once(SIMBA_TFA_PLUGIN_DIR.'/includes/admin_settings.php');
348 }
349
350 public function tfaShowUserSettingsPage()
351 {
352 $tfa = $this->getTFA();
353 include SIMBA_TFA_PLUGIN_DIR.'/includes/user_settings.php';
354 }
355
356 public function admin_menu()
357 {
358 $tfa = $this->getTFA();
359
360 global $current_user;
361 if(!$tfa->isActivatedForUser($current_user->ID)) return;
362 add_menu_page(__('Two Factor Authentication', SIMBA_TFA_TEXT_DOMAIN), __('Two Factor Auth', SIMBA_TFA_TEXT_DOMAIN), 'read', 'two-factor-auth-user', array($this, 'tfaShowUserSettingsPage'), SIMBA_TFA_PLUGIN_URL.'/img/tfa_admin_icon_16x16.png', 72);
363 }
364
365 public function menu_entry_for_admin() {
366
367 // On multisite, only show the entry on site ID 1 - to ensure options get saved in the right place.
368 global $current_site, $wpdb;
369 // $current_site is not the right way to do this - it is internal, and could be anything
370 if (is_multisite() && (!is_super_admin() || !is_object($wpdb) || !isset($wpdb->blogid) || 1 != $wpdb->blogid)) return;
371
372 add_action( 'admin_init', array($this, 'tfaRegisterTwoFactorAuthSettings' ));
373
374 add_options_page(
375 __('Two Factor Authentication', SIMBA_TFA_TEXT_DOMAIN),
376 __('Two Factor Authentication', SIMBA_TFA_TEXT_DOMAIN),
377 'manage_options',
378 'two-factor-auth',
379 array($this, 'tfaShowAdminSettingsPage')
380 );
381 }
382
383 public function addPluginSettingsLink($links)
384 {
385 if (!is_network_admin()) {
386 $link = '<a href="options-general.php?page=two-factor-auth">'.__('Plugin settings', SIMBA_TFA_TEXT_DOMAIN).'</a>';
387 array_unshift($links, $link);
388 } else {
389 switch_to_blog(1);
390 $link = '<a href="'.admin_url('options-general.php').'?page=two-factor-auth">'.__('Plugin settings', SIMBA_TFA_TEXT_DOMAIN).'</a>';
391 restore_current_blog();
392 array_unshift($links, $link);
393 }
394
395 $link2 = '<a href="admin.php?page=two-factor-auth-user">'.__('User settings', SIMBA_TFA_TEXT_DOMAIN).'</a>';
396 array_unshift($links, $link2);
397
398 return $links;
399 }
400
401 public function check_possible_reset() {
402 if(!empty($_GET['simbatfa_priv_key_reset']) && !empty($_REQUEST['nonce']) && wp_verify_nonce($_REQUEST['nonce'], 'simbatfa_reset_private_key'))
403 {
404 $this->reset_private_key_and_emergency_codes();
405 // if (empty($_REQUEST['noredirect'])) exit;
406 exit;
407 }
408
409 }
410
411 public function reset_private_key_and_emergency_codes() {
412 global $current_user;
413 delete_user_meta($current_user->ID, 'tfa_priv_key_64');
414 delete_user_meta($current_user->ID, 'simba_tfa_emergency_codes_64');
415 if (empty($_REQUEST['noredirect'])) {
416 wp_safe_redirect( admin_url('admin.php').'?page=two-factor-auth-user&settings-updated=1');
417 } else {
418 $url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . remove_query_arg(array('simbatfa_priv_key_reset', 'noredirect', 'nonce'));
419
420 wp_redirect(esc_url_raw($url));
421 }
422 }
423
424 public function reset_link($admin = true) {
425
426 $url_base = ($admin) ? admin_url('admin.php').'?page=two-factor-auth-user&settings-updated=1' : (( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST']);
427
428 $add_query_args = array(
429 'simbatfa_priv_key_reset' => 1,
430 );
431 if (!$admin) $add_query_args['noredirect'] = 1;
432
433 $url = $url_base.add_query_arg($add_query_args);
434
435 $url = wp_nonce_url($url, 'simbatfa_reset_private_key', 'nonce');
436
437 return '<a href="javascript:if(confirm(\''.__('Warning: if you reset this key you will have to update your apps with the new one. Are you sure you want this?', SIMBA_TFA_TEXT_DOMAIN).'\')){ window.location = \''.esc_js($url).'\'; }">'.__('Reset private key', SIMBA_TFA_TEXT_DOMAIN).'</a>';
438
439 }
440
441 public function footer() {
442 $ajax_url = admin_url('admin-ajax.php');
443 // It's possible that FORCE_ADMIN_SSL will make that SSL, whilst the user is on the front-end having logged in over non-SSL - and as a result, their login cookies won't get sent, and they're not registered as logged in.
444 if (!is_admin() && substr(strtolower($ajax_url), 0, 6) == 'https:' && !is_ssl()) {
445 $also_try = 'http:'.substr($ajax_url, 6);
446 }
447 ?>
448 <script>
449 jQuery(document).ready(function($) {
450 $('.simbaotp_qr_container').qrcode({
451 "render": "image",
452 "text": $('.simbaotp_qr_container:first').data('qrcode'),
453 });
454 $('.simbaotp_refresh').click(function(e) {
455 e.preventDefault();
456 $(".simba_current_otp").html('<em><?php echo esc_attr(__('Updating...', SIMBA_TFA_TEXT_DOMAIN));?></em>');
457 $.post('<?php echo esc_js($ajax_url);?>', {
458 action: "simbatfa_shared_ajax",
459 subaction: "refreshotp",
460 nonce: "<?php echo esc_js(wp_create_nonce("tfa_shared_nonce"));?>"
461 }, function(response) {
462 var got_code = '';
463 try {
464 var resp = $.parseJSON(response);
465 got_code = resp.code;
466 } catch(err) {
467 <?php if (!isset($also_try)) { ?>
468 alert("<?php echo esc_js(__('Response:', 'SIMBA_TFA_TEXT_DOMAIN')); ?> "+response);
469 <?php } ?>
470 console.log(response);
471 console.log(err);
472 }
473 <?php
474 if (isset($also_try)) {
475 ?>
476 $.post('<?php echo esc_js($also_try);?>', {
477 action: "simbatfa_shared_ajax",
478 subaction: "refreshotp",
479 nonce: "<?php echo esc_js(wp_create_nonce("tfa_shared_nonce"));?>"
480 }, function(response) {
481 try {
482 var resp = $.parseJSON(response);
483 if (resp.code) {
484 $(".simba_current_otp").html(resp.code);
485 } else {
486 console.log(response);
487 console.log("TFA: no code found");
488 }
489 } catch(err) {
490 alert("<?php echo esc_js(__('Response:', 'SIMBA_TFA_TEXT_DOMAIN')); ?> "+response);
491 console.log(response);
492 console.log(err);
493 }
494 });
495 <?php } else { ?>
496 if ('' != got_code) {
497 $(".simba_current_otp").html(got_code);
498 } else {
499 console.log("TFA: no code found");
500 }
501 <?php } ?>
502 });
503 });
504 });
505 </script>
506 <?php
507 }
508
509 public function print_private_keys($admin, $type = 'full', $user_id = false) {
510
511 $tfa = $this->getTFA();
512 global $current_user;
513
514 if ($user_id == false) $user_id = $current_user->ID;
515
516 $tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true);
517 if(!$tfa_priv_key_64) $tfa_priv_key_64 = $tfa->addPrivateKey($user_id);
518
519 $tfa_priv_key = trim($tfa->getPrivateKeyPlain($tfa_priv_key_64, $user_id));
520
521 $tfa_priv_key_32 = Base32::encode($tfa_priv_key);
522
523 if ('full' == $type) {
524 ?>
525 <strong><?php echo __('Private key (base 32 - used by Google Authenticator and Authy):', SIMBA_TFA_TEXT_DOMAIN);?></strong>
526 <?php echo htmlspecialchars($tfa_priv_key_32); ?><br>
527
528 <strong><?php echo __('Private key:', SIMBA_TFA_TEXT_DOMAIN);?></strong>
529 <?php echo htmlspecialchars($tfa_priv_key); ?><br>
530 <?php
531 } elseif ('plain' == $type) {
532 echo htmlspecialchars($tfa_priv_key);
533 } elseif ('base32' == $type) {
534 echo htmlspecialchars($tfa_priv_key_32);
535 } elseif ('base64' == $type) {
536 echo htmlspecialchars($tfa_priv_key_64);
537 }
538 }
539
540 public function current_otp_code($tfa, $user_id = false) {
541 global $current_user;
542 if (false == $user_id) $user_id = $current_user->ID;
543 $tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true);
544 return '<span class="simba_current_otp">'.$tfa->generateOTP($user_id, $tfa_priv_key_64).'</span>';
545 }
546
547 public function add_footer($admin) {
548 static $added_footer;
549 if (empty($added_footer)) {
550 $added_footer = true;
551 // wp_enqueue_script('jquery');
552 $script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : $this->version;
553 $script_file = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? 'jquery.qrcode.js' : 'jquery.qrcode.min.js';
554 wp_enqueue_script( 'jquery-qrcode', SIMBA_TFA_PLUGIN_URL.'/includes/jquery-qrcode/'.$script_file, array('jquery'), $script_ver);
555 add_action( $admin ? 'admin_footer' : 'wp_footer' , array($this, 'footer'));
556 }
557 }
558
559 public function current_codes_box($admin = true, $user_id = false) {
560
561 global $current_user;
562
563 if (false == $user_id) {
564 $user_id = $current_user->ID;
565 }
566
567 $tfa = $this->getTFA();
568
569 $this->add_footer($admin);
570
571 $url = preg_replace('/^https?:\/\//', '', site_url());
572
573 $tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true);
574
575 if(!$tfa_priv_key_64) $tfa_priv_key_64 = $tfa->addPrivateKey($user_id);
576
577 $tfa_priv_key = trim($tfa->getPrivateKeyPlain($tfa_priv_key_64, $user_id));
578
579 $tfa_priv_key_32 = Base32::encode($tfa_priv_key);
580
581 $algorithm_type = $tfa->getUserAlgorithm($user_id);
582
583 if ($admin) {
584 if ($current_user->ID == $user_id) {
585 echo '<h2>'.__('Current codes', SIMBA_TFA_TEXT_DOMAIN).'</h2>';
586 } else {
587 $user = get_user_by('id', $user_id);
588 $user_descrip = htmlspecialchars($user->user_nicename.' - '.$user->user_email);
589 echo '<h2>'.sprintf(__('Current codes (login: %s)', SIMBA_TFA_TEXT_DOMAIN), $user_descrip).'</h2>';
590 }
591 } else {
592 // echo '<h2>'.__('Current one-time password', SIMBA_TFA_TEXT_DOMAIN).' '.$this->reset_current_otp_link().'</h2>';
593 }
594
595 ?>
596 <div class="postbox">
597
598 <?php if ($admin) { ?>
599 <h3 style="padding: 10px 6px 0px; margin:4px 0 0; cursor: default;">
600 <span style="cursor: default;"><?php echo __('Current one-time password', SIMBA_TFA_TEXT_DOMAIN).' ';
601 if ($current_user->ID == $user_id) { echo $this->reset_current_otp_link(); } ?>
602 </span>
603 <div class="inside">
604 <p><strong style="font-size: 3em;"><?php echo $this->current_otp_code($tfa, $user_id); ?></strong></p>
605 </div>
606 </h3>
607 <?php } else {
608 ?>
609 <div class="inside">
610 <p class="simbatfa-frontend-current-otp" style="font-size: 1.5em; margin-top:6px;">
611 <strong>
612 <?php echo __('Current one-time password', SIMBA_TFA_TEXT_DOMAIN).' '.$this->reset_current_otp_link(); ?>
613 </strong> :
614
615 <span class="simba_current_otp"><?php print $tfa->generateOTP($user_id, $tfa_priv_key_64); ?></span>
616
617 </p>
618 </div>
619
620 <?php } ?>
621
622 <?php if ($admin) { ?>
623 <h3 style="padding-left: 10px; cursor: default;">
624 <span style="cursor: default;"><?php _e('QR code', SIMBA_TFA_TEXT_DOMAIN); ?></span>
625 </h3>
626 <?php } else {
627 echo '<h2>'.__('QR code', SIMBA_TFA_TEXT_DOMAIN).'</h2>';
628 } ?>
629 <div class="inside">
630 <p>
631 <?php _e('For OTP apps that support scanning, scanning this code is the quickest way to set the app up (e.g. with Duo Mobile, Google Authenticator)', SIMBA_TFA_TEXT_DOMAIN); ?>.
632
633 <?php _e('You are currently using', SIMBA_TFA_TEXT_DOMAIN); ?> <?php print strtoupper($algorithm_type).', '.($algorithm_type == 'totp' ? __('a time based', SIMBA_TFA_TEXT_DOMAIN) : __('an event based', SIMBA_TFA_TEXT_DOMAIN)); ?> <?php _e('algorithm', SIMBA_TFA_TEXT_DOMAIN); ?>.
634 </p>
635 <p title="<?php echo sprintf(__("Private key: %s (base 32: %s)", SIMBA_TFA_TEXT_DOMAIN), $tfa_priv_key, $tfa_priv_key_32);?>">
636 <?php $qr_url = $this->tfa_qr_code_url($algorithm_type, $url, $tfa_priv_key) ?>
637 <div class="simbaotp_qr_container" data-qrcode="<?php echo esc_attr($qr_url); ?>"></div>
638 </p>
639 </div>
640
641 <div class="inside">
642
643 <h3 class="normal" style="cursor: default"><?php _e('Private key - always to be kept secret - type this into your app to set it up (instead of scanning the code)', SIMBA_TFA_TEXT_DOMAIN); ?></h3>
644
645 <p>
646 <?php
647 $this->print_private_keys($admin, 'full', $user_id);
648 if ($current_user->ID == $user_id) { echo $this->reset_link($admin);}
649 ?>
650 </p>
651 </div>
652
653 <?php
654 if ($admin || apply_filters('simba_tfa_emergency_codes_user_settings', false, $user_id) !== false) {
655 ?>
656 <div class="inside">
657
658 <h3 class="normal" style="cursor: default"><?php _e('Emergency codes', SIMBA_TFA_TEXT_DOMAIN); ?></h3>
659
660 <p>
661 <?php
662 $default_text = '<a href="https://www.simbahosting.co.uk/s3/product/two-factor-authentication/">'.__('One-time emergency codes are a feature of the Premium version of this plugin.', SIMBA_TFA_TEXT_DOMAIN).'</a>';
663 echo apply_filters('simba_tfa_emergency_codes_user_settings', $default_text, $user_id);
664 ?>
665 </p>
666
667 </div>
668
669 <?php } ?>
670
671 </div>
672 <?php
673 }
674
675 public function reset_current_otp_link($admin = true) {
676 return '<a href="#" class="simbaotp_refresh">'.__('(update)', SIMBA_TFA_TEXT_DOMAIN).'</a>';
677 }
678
679 public function advanced_settings_box($submit_button_callback = false) {
680 $tfa = $this->getTFA();
681
682 global $current_user;
683 $algorithm_type = $tfa->getUserAlgorithm($current_user->ID);
684
685 ?>
686 <h2><?php _e('Advanced settings', SIMBA_TFA_TEXT_DOMAIN); ?></h2>
687
688 <div id="tfa_advanced_box" class="tfa_settings_form" style="margin-top: 20px;">
689
690 <?php if (false === $submit_button_callback) { ?><form method="post" action="<?php print esc_url(add_query_arg('settings-updated', 'true', $_SERVER['REQUEST_URI'])); ?>"><?php } ?>
691
692 <?php _e('Choose which algorithm for One Time Passwords you want to use.', SIMBA_TFA_TEXT_DOMAIN); ?>
693 <p>
694 <?php
695 $this->tfaListAlgorithmRadios($current_user->ID);
696 if($algorithm_type == 'hotp')
697 {
698 $counter = $tfa->getUserCounter($current_user->ID);
699 print '<br>'.__('Your counter on the server is currently on', SIMBA_TFA_TEXT_DOMAIN).': '.$counter;
700 }
701 ?>
702
703 </p>
704 <?php if (false === $submit_button_callback) { submit_button(); echo '</form>'; } else { call_user_func($submit_button_callback); } ?>
705 </div>
706 <?php
707 }
708
709 public function login_enqueue_scripts()
710 {
711
712 if(isset($_GET['action']) && $_GET['action'] != 'logout' && $_GET['action'] != 'login') return;
713
714 // Prevent cacheing when in debug mode
715 $script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : $this->version;
716
717 wp_enqueue_script( 'tfa-ajax-request', SIMBA_TFA_PLUGIN_URL . '/includes/tfa.js', array( 'jquery' ), $script_ver );
718 $localize = array(
719 'ajaxurl' => admin_url('admin-ajax.php'),
720 'click_to_enter_otp' => __("Click to enter One Time Password", SIMBA_TFA_TEXT_DOMAIN),
721 'enter_username_first' => __('You have to enter a username first.', SIMBA_TFA_TEXT_DOMAIN),
722 'otp' => __("One Time Password (i.e. 2FA)", SIMBA_TFA_TEXT_DOMAIN),
723 'otp_login_help' => __('(check your OTP app to get this password)', SIMBA_TFA_TEXT_DOMAIN),
724 'nonce' => wp_create_nonce("simba_tfa_loginform_nonce")
725 );
726 // Spinner exists since WC 3.8. Use the proper functions to avoid SSL warnings.
727 if (file_exists(ABSPATH.'wp-admin/images/spinner.gif')) {
728 $localize['spinnerimg'] = admin_url('images/spinner.gif');
729 } elseif (file_exists(ABSPATH.WPINC.'/images/spinner.gif')) {
730 $localize['spinnerimg'] = includes_url('images/spinner.gif');
731 }
732 wp_localize_script( 'tfa-ajax-request', 'simba_tfasettings', $localize);
733 }
734
735 public function tfaShowHOTPOffSyncMessage()
736 {
737 global $current_user;
738 $is_off_sync = get_user_meta($current_user->ID, 'tfa_hotp_off_sync', true);
739 if(!$is_off_sync)
740 return;
741
742 ?>
743 <div class="error">
744 <h3><?php _e('Two Factor Authentication re-sync needed', SIMBA_TFA_TEXT_DOMAIN);?></h3>
745 <p>
746 <?php _e('You need to resync your device for Two Factor Authentication since the OTP you last used is many steps ahead of the server.', SIMBA_TFA_TEXT_DOMAIN); ?>
747 <br>
748 <?php _e('Please re-sync or you might not be able to log in if you generate more OTPs without logging in.', SIMBA_TFA_TEXT_DOMAIN);?>
749 <br><br>
750 <a href="admin.php?page=two-factor-auth-user&warning_button_clicked=1" class="button"><?php _e('Click here and re-scan the QR-Code', SIMBA_TFA_TEXT_DOMAIN);?></a>
751 </p>
752 </div>
753
754 <?php
755
756 }
757
758 // QR code image
759 public function tfa_qr_code_url($algorithm_type, $url, $tfa_priv_key, $user_id = false){
760 global $current_user;
761
762 if ($user_id == false) {
763 $user = $current_user;
764 } else {
765 $user = get_user_by('id', $user_id);
766 }
767
768 $tfa = $this->getTFA();
769
770 // Old
771 // $encode = 'otpauth://'.$algorithm_type.'/'.$url.':%2520'.$user->user_login.'%3Fsecret%3D'.Base32::encode($tfa_priv_key).'%26issuer='.$url.'%26counter='.$tfa->getUserCounter($user->ID);
772 //
773 // $ret = '<img src="https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl='.$encode.'">';
774
775 // New
776 $encode = 'otpauth://'.$algorithm_type.'/'.$url.':'.$user->user_login.'?secret='.Base32::encode($tfa_priv_key).'&issuer='.$url.'&counter='.$tfa->getUserCounter($user->ID);
777
778 // $ret = '<script>var qr_details = "'.$encode.'"</script>';
779
780 return $encode;
781 }
782
783 public function settings_intro_notices() {
784 ?>
785 <p class="simba_tfa_personal_settings_notice simba_tfa_intro_notice">
786 <?php echo __('These are your personal settings.', SIMBA_TFA_TEXT_DOMAIN).' '.__('Nothing you change here will have any effect on other users.', SIMBA_TFA_TEXT_DOMAIN); ?>
787 </p>
788 <p class="simba_tfa_verify_tfa_notice simba_tfa_intro_notice"><strong>
789 <?php _e('If you activate two-factor authentication, then verify that your two-factor application is showing the same One Time Password as shown on this page before you log out.', SIMBA_TFA_TEXT_DOMAIN); ?></strong> <?php if (current_user_can('manage_options')) { ?><a href="https://wordpress.org/plugins/two-factor-authentication/faq/"><?php _e('You should also bookmark the FAQs, which explain how to de-activate the plugin even if you cannot log in.', SIMBA_TFA_TEXT_DOMAIN);?></a><?php } ?>
790 </p>
791 <?php
792 }
793
794 public function plugins_loaded() {
795 load_plugin_textdomain(
796 SIMBA_TFA_TEXT_DOMAIN,
797 false,
798 dirname( plugin_basename( __FILE__ ) ) . '/languages/'
799 );
800 }
801
802 public function load_frontend() {
803 if (!class_exists('TFA_Frontend')) require_once(SIMBA_TFA_PLUGIN_DIR.'/includes/tfa_frontend.php');
804 if (empty($this->frontend)) $this->frontend = new TFA_Frontend($this);
805 return $this->frontend;
806 }
807
808 public function shortcode_when_not_logged_in() {
809 return '';
810 }
811
812 // WooCommerce login form
813 public function woocommerce_before_customer_login_form() {
814 $script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : $this->version;
815 wp_enqueue_script( 'tfa-wc-ajax-request', SIMBA_TFA_PLUGIN_URL.'/includes/wooextend.js', array('jquery'), $script_ver);
816 $localize = array(
817 'ajaxurl' => admin_url('admin-ajax.php'),
818 'click_to_enter_otp' => __("Enter One Time Password (if you have one)", SIMBA_TFA_TEXT_DOMAIN),
819 'enter_username_first' => __('You have to enter a username first.', SIMBA_TFA_TEXT_DOMAIN),
820 'otp' => __("One Time Password", SIMBA_TFA_TEXT_DOMAIN),
821 'nonce' => wp_create_nonce("simba_tfa_loginform_nonce"),
822 'otp_login_help' => __('(check your OTP app to get this password)', SIMBA_TFA_TEXT_DOMAIN),
823 );
824 // Spinner exists since WC 3.8. Use the proper functions to avoid SSL warnings.
825 if (file_exists(ABSPATH.'wp-admin/images/spinner.gif')) {
826 $localize['spinnerimg'] = admin_url('images/spinner.gif');
827 } elseif (file_exists(ABSPATH.WPINC.'/images/spinner.gif')) {
828 $localize['spinnerimg'] = includes_url('images/spinner.gif');
829 }
830
831 wp_localize_script( 'tfa-wc-ajax-request', 'simbatfa_wc_settings', $localize);
832 }
833
834 }
835
836 $simba_two_factor_authentication = new Simba_Two_Factor_Authentication();
837