PluginProbe
Two Factor Authentication / 1.2.14
Two Factor Authentication v1.2.14
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.14, at two-factor-login.php

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