PluginProbe
Two Factor Authentication / 1.2.16
Two Factor Authentication v1.2.16
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.16, at two-factor-login.php

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