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
← All changes | two-factor-login.php +381 -980 1.12.21.2.6 View file →
@@ -2,37 +2,25 @@
2 2 /*
3 3 Plugin Name: Two Factor Authentication
4 4 Plugin URI: https://www.simbahosting.co.uk/s3/product/two-factor-authentication/
5 5 Description: Secure your WordPress login forms with two factor authentication - including WooCommerce login forms
6 -Author: David Anderson, original plugin by Oskar Hane and enhanced by Dee Nutbourne
6 +Author: David Nutbourne + David Anderson, original plugin by Oskar Hane
7 7 Author URI: https://www.simbahosting.co.uk
8 -Version: 1.12.2
9 -Text Domain: two-factor-authentication
10 -Domain Path: /languages
8 +Version: 1.2.6
11 9 License: GPLv2 or later
12 10 */
13 11
14 -if (defined('SIMBA_TFA_PLUGIN_DIR') && file_exists(dirname(__FILE__).'/premium.php')) {
15 - throw new Exception('To activate Two Factor Authentication Premium, first de-activate the free version (only one can be active at once).');
16 -}
17 -
18 -define('SIMBA_TFA_PLUGIN_DIR', dirname(__FILE__));
19 -define('SIMBA_TFA_PLUGIN_FILE', __FILE__);
12 +define('SIMBA_TFA_TEXT_DOMAIN', 'two-factor-authentication');
13 +define('SIMBA_TFA_PLUGIN_DIR', dirname( __FILE__ ));
20 14 define('SIMBA_TFA_PLUGIN_URL', plugins_url('', __FILE__));
21 15
22 16 class Simba_Two_Factor_Authentication {
23 17
24 - public $version = '1.12.2';
18 + public $version = '1.2.6';
19 + private $php_required = '5.3';
25 20
26 - private $php_required = '5.6';
27 -
28 21 private $frontend;
29 -
30 - private $totp_controller;
31 22
32 - /**
33 - * Constructor, run upon plugin initiation
34 - */
35 23 public function __construct() {
36 24
37 25 if (version_compare(PHP_VERSION, $this->php_required, '<' )) {
38 26 add_action('all_admin_notices', array($this, 'admin_notice_insufficient_php'));
@@ -38,10 +26,10 @@
38 26 add_action('all_admin_notices', array($this, 'admin_notice_insufficient_php'));
39 27 $abort = true;
40 28 }
41 29
42 - if (!function_exists('mcrypt_get_iv_size') && !function_exists('openssl_cipher_iv_length')) {
43 - add_action('all_admin_notices', array($this, 'admin_notice_missing_mcrypt_and_openssl'));
30 + if (!function_exists('mcrypt_get_iv_size')) {
31 + add_action('all_admin_notices', array($this, 'admin_notice_missing_mcrypt'));
44 32 $abort = true;
45 33 }
46 34
47 35 if (!empty($abort)) return;
@@ -47,44 +35,48 @@
47 35 if (!empty($abort)) return;
48 36
49 37 if (file_exists(SIMBA_TFA_PLUGIN_DIR.'/premium.php')) include_once(SIMBA_TFA_PLUGIN_DIR.'/premium.php');
50 38
51 - require_once(SIMBA_TFA_PLUGIN_DIR.'/providers/totp-hotp/loader.php');
52 - $this->totp_controller = new Simba_TFA_Provider_TOTP($this);
53 -
54 - // Process login form AJAX events
55 39 add_action('wp_ajax_nopriv_simbatfa-init-otp', array($this, 'tfaInitLogin'));
56 - add_action('wp_ajax_simbatfa-init-otp', array($this, 'tfaInitLogin'));
57 40
58 41 add_action('wp_ajax_simbatfa_shared_ajax', array($this, 'shared_ajax'));
59 42
60 - require_once(SIMBA_TFA_PLUGIN_DIR.'/includes/login-form-integrations.php');
61 -
62 - new Simba_TFA_Login_Form_Integrations($this);
63 -
64 - require_once(SIMBA_TFA_PLUGIN_DIR.'/includes/admin-dashboard-integration.php');
65 -
66 - new Simba_TFA_Admin_Dashboard_Integration($this);
67 -
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 +
68 47 if (is_admin()) {
69 - // Save settings
48 + //Save settings
70 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 +
71 63 } else {
72 64 add_action('init', array($this, 'check_possible_reset'));
73 65 }
74 66
75 67 add_action('plugins_loaded', array($this, 'plugins_loaded'));
76 -
77 68 add_action('init', array($this, 'init'));
78 69
79 - // Show off-sync message for hotp
80 - add_action('admin_notices', array($this, 'tfa_show_hotp_off_sync_message'));
81 -
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 +
82 74 if (!defined('TWO_FACTOR_DISABLE') || !TWO_FACTOR_DISABLE) {
83 75 add_filter('authenticate', array($this, 'tfaVerifyCodeAndUser'), 99999999999, 3);
84 76 }
85 77
86 - if (file_exists(SIMBA_TFA_PLUGIN_DIR.'/updater.php')) include_once(SIMBA_TFA_PLUGIN_DIR.'/updater.php');
78 + if (file_exists(SIMBA_TFA_PLUGIN_DIR.'/updater/updater.php')) include_once(SIMBA_TFA_PLUGIN_DIR.'/updater/updater.php');
87 79
88 80 if (defined('DOING_AJAX') && DOING_AJAX && defined('WP_ADMIN') && WP_ADMIN && !empty($_REQUEST['action']) && 'simbatfa-init-otp' == $_REQUEST['action']) {
89 81 // Try to prevent PHP notices breaking the AJAX conversation
90 82 $this->output_buffering = true;
@@ -94,256 +86,8 @@
94 86 }
95 87
96 88 }
97 89
98 - /**
99 - * See whether TFA is available or not for a particular user - i.e. whether the administrator has permitted it for their user level
100 - *
101 - * @param Integer $user_id - WordPress user ID
102 - *
103 - * @return Boolean
104 - */
105 - public function is_activated_for_user($user_id) {
106 -
107 - if (empty($user_id)) return false;
108 -
109 - // Super admin is not a role (they are admins with an extra attribute); needs separate handling
110 - if (is_multisite() && is_super_admin($user_id)) {
111 - // This is always a final decision - we don't want it to drop through to the 'admin' role's setting
112 - $role = '_super_admin';
113 - $db_val = $this->get_option('tfa_'.$role);
114 - // Defaults to true if no setting has been saved
115 - return (false === $db_val || $db_val) ? true : false;
116 - }
117 -
118 - $roles = $this->get_user_roles($user_id);
119 -
120 - // N.B. This populates with roles on the current site within a multisite
121 - foreach ($roles as $role) {
122 - $db_val = $this->get_option('tfa_'.$role);
123 - if (false === $db_val || $db_val) return true;
124 - }
125 -
126 - return false;
127 -
128 - }
129 -
130 - /**
131 - * Get all user roles for a given user (if on multisite, amalgamates all roles from all sites)
132 - *
133 - * @param Integer $user_id - WordPress user ID
134 - *
135 - * @return Array
136 - */
137 - private function get_user_roles($user_id) {
138 -
139 - // Get roles on the main site
140 - $user = new WP_User($user_id);
141 - $roles = (array) $user->roles;
142 -
143 - // On multisite, also check roles on non-main sites
144 - if (is_multisite()) {
145 - global $wpdb, $table_prefix;
146 - $roles_db = $wpdb->get_results($wpdb->prepare("SELECT meta_key, meta_value FROM {$wpdb->usermeta} WHERE user_id=%d AND meta_key LIKE '".esc_sql($table_prefix)."%_capabilities'", $user_id));
147 - if (is_array($roles_db)) {
148 - foreach ($roles_db as $role_info) {
149 - if (empty($role_info->meta_key) || !preg_match('/^'.$table_prefix.'\d+_capabilities$/', $role_info->meta_key) || empty($role_info->meta_value) || !preg_match('/^a:/', $role_info->meta_value)) continue;
150 - $site_roles = unserialize($role_info->meta_value);
151 - if (!is_array($site_roles)) continue;
152 - foreach ($site_roles as $role => $active) {
153 - if ($active && !in_array($role, $roles)) $roles[] = $role;
154 - }
155 - }
156 - }
157 - }
158 -
159 - return $roles;
160 - }
161 -
162 - /**
163 - * Check if TFA is required for a specified user
164 - *
165 - * N.B. - This doesn't check is_activated_for_user() - the caller would normally want to do that first
166 - *
167 - * @param $user_id Integer - the WP user ID
168 - *
169 - * @return Boolean
170 - */
171 - public function is_required_for_user($user_id) {
172 - return apply_filters('simba_tfa_required_for_user', $this->user_property_active($user_id, 'required_'), $user_id);
173 - }
174 -
175 - /**
176 - * See if a particular user property is active
177 - *
178 - * @param Integer $user_id
179 - * @param String $prefix - e.g. "required_", "trusted_"
180 - *
181 - * @return Boolean
182 - */
183 - public function user_property_active($user_id, $prefix = 'required_') {
184 -
185 - if (empty($user_id)) return false;
186 -
187 - // Super admin is not a role (they are admins with an extra attribute); needs separate handling
188 - if (is_multisite() && is_super_admin($user_id)) {
189 - // This is always a final decision - we don't want it to drop through to the 'admin' role's setting
190 - $role = '_super_admin';
191 - $db_val = $this->get_option('tfa_'.$prefix.$role);
192 - return $db_val ? true : false;
193 - }
194 -
195 - $roles = $this->get_user_roles($user_id);
196 -
197 - foreach ($roles as $role) {
198 - $db_val = $this->get_option('tfa_'.$prefix.$role);
199 - if ($db_val) return true;
200 - }
201 -
202 - return false;
203 -
204 - }
205 -
206 - /**
207 - * Whether TFA is activated by a specific user. Note that this doesn't check if TFA is enabled for the user's role; the caller should check that first.
208 - *
209 - * @param Integer $user_id
210 - *
211 - * @return Boolean
212 - */
213 - public function is_activated_by_user($user_id) {
214 - $enabled = get_user_meta($user_id, 'tfa_enable_tfa', true);
215 - return !empty($enabled);
216 - }
217 -
218 - /**
219 - * Get a list of trusted devices for the user
220 - *
221 - * @param Integer $user_id - WordPress user ID
222 - *
223 - * @return Array
224 - */
225 - public function user_get_trusted_devices($user_id) {
226 -
227 - $trusted_devices = get_user_meta($user_id, 'tfa_trusted_devices', true);
228 -
229 - if (!is_array($trusted_devices)) $trusted_devices = array();
230 -
231 - return $trusted_devices;
232 - }
233 -
234 - /**
235 - * Trust the current device
236 - *
237 - * @param Integer $user_id - WordPress user ID
238 - * @param Integer $trusted_for - time to trust for, in days
239 - */
240 - public function trust_device($user_id, $trusted_for) {
241 -
242 - $trusted_devices = $this->user_get_trusted_devices($user_id);
243 -
244 - $time_now = time();
245 -
246 - foreach ($trusted_devices as $k => $device) {
247 - if (empty($device['until']) || $device['until'] <= $time_now) unset($trusted_devices[$k]);
248 - }
249 -
250 - $until = $time_now + $trusted_for * 86400;
251 -
252 - $token = bin2hex($this->random_bytes(40));
253 -
254 - $trusted_devices[] = array(
255 - 'ip' => $_SERVER['REMOTE_ADDR'],
256 - 'until' => $until,
257 - 'user_agent' => empty($_SERVER['HTTP_USER_AGENT']) ? '' : (string) $_SERVER['HTTP_USER_AGENT'],
258 - 'token' => $token
259 - );
260 -
261 - $this->user_set_trusted_devices($user_id, $trusted_devices);
262 -
263 - $this->set_cookie('simbatfa_trust_token', $token, $until);
264 - }
265 -
266 - /**
267 - * Returns true if running on a PHP version on which mcrypt has been deprecated
268 - *
269 - * @return Boolean
270 - */
271 - public function is_mcrypt_deprecated() {
272 - return (7 == PHP_MAJOR_VERSION && PHP_MINOR_VERSION >= 1);
273 - }
274 -
275 - /**
276 - * Return the specified number of bytes
277 - *
278 - * @param Integer $bytes
279 - *
280 - * @throws Exception
281 - *
282 - * @return String
283 - */
284 - public function random_bytes($bytes) {
285 - if (function_exists('random_bytes')) {
286 - return random_bytes($bytes);
287 - } elseif (function_exists('mcrypt_create_iv')) {
288 - return $this->is_mcrypt_deprecated() ? @mcrypt_create_iv($bytes, MCRYPT_RAND) : mcrypt_create_iv($bytes, MCRYPT_RAND);
289 - } elseif (function_exists('openssl_random_pseudo_bytes')) {
290 - return openssl_random_pseudo_bytes($bytes);
291 - }
292 - throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed');
293 - }
294 -
295 - /**
296 - * Set a cookie so that, however we logged in, it can be found
297 - *
298 - * @param String $name - the cookie name
299 - * @param String $value - the cookie value
300 - * @param Integer $expires - when the cookie expires, in epoch time. Defaults to 24 hours' time. Values in the past cause cookie deletion.
301 - */
302 - private function set_cookie($name, $value, $expires = null) {
303 - if (null === $expires) $expires = time() + 86400;
304 - $secure = is_ssl();
305 - $secure_logged_in_cookie = ($secure && 'https' === parse_url(get_option('home'), PHP_URL_SCHEME));
306 - $secure = apply_filters('secure_auth_cookie', $secure, get_current_user_id());
307 - $secure_logged_in_cookie = apply_filters('secure_logged_in_cookie', $secure_logged_in_cookie, get_current_user_id(), $secure);
308 -
309 - setcookie($name, $value, $expires, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true);
310 - setcookie($name, $value, $expires, COOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true);
311 - if (COOKIEPATH != SITECOOKIEPATH) {
312 - setcookie($name, $value, $expires, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true);
313 - }
314 - }
315 -
316 - /**
317 - * Get a list of trusted devices for the user
318 - *
319 - * @param Integer $user_id - WordPress user ID
320 - * @param Array $trusted_devices - the list of devices
321 - */
322 - public function user_set_trusted_devices($user_id, $trusted_devices) {
323 - update_user_meta($user_id, 'tfa_trusted_devices', $trusted_devices);
324 - }
325 -
326 - /**
327 - * Get the user capability needed for managing TFA users.
328 - * You'll want to think carefully about changing this to a non-admin, as it can give the ability to lock admins out (though, if you have FTP/files access, you can always disable TFA or any plugin)
329 - *
330 - * @return String
331 - */
332 - public function get_management_capability() {
333 - return apply_filters('simba_tfa_management_capability', 'manage_options');
334 - }
335 -
336 - /**
337 - * Used with set_error_handler()
338 - *
339 - * @param Integer $errno
340 - * @param String $errstr
341 - * @param String $errfile
342 - * @param Integer $errline
343 - *
344 - * @return Boolean
345 - */
346 90 public function get_php_errors($errno, $errstr, $errfile, $errline) {
347 91 if (0 == error_reporting()) return true;
348 92 $logline = $this->php_error_to_logline($errno, $errstr, $errfile, $errline);
349 93 $this->logged[] = $logline;
@@ -356,14 +100,14 @@
356 100 case 1: $e_type = 'E_ERROR'; break;
357 101 case 2: $e_type = 'E_WARNING'; break;
358 102 case 4: $e_type = 'E_PARSE'; break;
359 103 case 8: $e_type = 'E_NOTICE'; break;
360 - case 16: $e_type = 'E_CORE_ERROR'; break;
361 - case 32: $e_type = 'E_CORE_WARNING'; break;
362 - case 64: $e_type = 'E_COMPILE_ERROR'; break;
363 - case 128: $e_type = 'E_COMPILE_WARNING'; break;
364 - case 256: $e_type = 'E_USER_ERROR'; break;
365 - case 512: $e_type = 'E_USER_WARNING'; 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;
366 110 case 1024: $e_type = 'E_USER_NOTICE'; break;
367 111 case 2048: $e_type = 'E_STRICT'; break;
368 112 case 4096: $e_type = 'E_RECOVERABLE_ERROR'; break;
369 113 case 8192: $e_type = 'E_DEPRECATED'; break;
@@ -368,9 +112,9 @@
368 112 case 4096: $e_type = 'E_RECOVERABLE_ERROR'; break;
369 113 case 8192: $e_type = 'E_DEPRECATED'; break;
370 114 case 16384: $e_type = 'E_USER_DEPRECATED'; break;
371 115 case 30719: $e_type = 'E_ALL'; break;
372 - default: $e_type = "E_UNKNOWN ($errno)"; break;
116 + default: $e_type = "E_UNKNOWN ($errno)"; break;
373 117 }
374 118
375 119 if (!is_string($errstr)) $errstr = serialize($errstr);
376 120
@@ -379,11 +123,8 @@
379 123 return "PHP event: code $e_type: $errstr (line $errline, $errfile)";
380 124
381 125 }
382 126
383 - /**
384 - * Runs upon the WordPress 'init' action
385 - */
386 127 public function init() {
387 128 if ((!is_admin() || (defined('DOING_AJAX') && DOING_AJAX)) && is_user_logged_in() && file_exists(SIMBA_TFA_PLUGIN_DIR.'/includes/tfa_frontend.php')) {
388 129 $this->load_frontend();
389 130 } else {
@@ -390,39 +131,28 @@
390 131 add_shortcode('twofactor_user_settings', array($this, 'shortcode_when_not_logged_in'));
391 132 }
392 133 }
393 134
394 - /**
395 - * Runs conditionally on the WP action all_admin_notices
396 - */
397 135 public function admin_notice_insufficient_php() {
398 - $this->show_admin_warning('<strong>'.__('Higher PHP version required', 'two-factor-authentication').'</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');
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');
399 137 }
400 138
401 - /**
402 - * Runs conditionally on the WP action all_admin_notices
403 - */
404 - public function admin_notice_missing_mcrypt_and_openssl() {
405 - $this->show_admin_warning('<strong>'.__('PHP OpenSSL or mcrypt module required', 'two-factor-authentication').'</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');
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');
406 141 }
407 142
408 - /**
409 - * Paint out an admin notice
410 - *
411 - * @param String $message - the caller should already have taken care of escaping
412 - * @param String $class
413 - */
414 - public function show_admin_warning($message, $class = 'updated') {
143 + public function show_admin_warning($message, $class = "updated") {
415 144 echo '<div class="tfamessage '.$class.'">'."<p>$message</p></div>";
416 145 }
417 146
418 - /**
419 - * Return the Simba_TFA_Provider_TOTP object.
420 - *
421 - * @returns Simba_TFA_Provider_TOTP
422 - */
423 - public function get_totp_controller() {
424 - return $this->totp_controller;
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;
425 155 }
426 156
427 157 // "Shared" - i.e. could be called from either front-end or back-end
428 158 public function shared_ajax() {
@@ -427,60 +157,38 @@
427 157 // "Shared" - i.e. could be called from either front-end or back-end
428 158 public function shared_ajax() {
429 159 if (empty($_POST['subaction']) || empty($_POST['nonce']) || !is_user_logged_in() || !wp_verify_nonce($_POST['nonce'], 'tfa_shared_nonce')) die('Security check (3).');
430 160
431 - global $current_user;
161 + if ($_POST['subaction'] == 'refreshotp') {
432 162
433 - if ('refreshotp' == $_POST['subaction']) {
163 + global $current_user;
434 164
435 - $code = $this->totp_controller->get_current_code($current_user->ID);
436 -
437 - if (false === $code) die(json_encode(array('code' => '')));
165 + $tfa_priv_key_64 = get_user_meta($current_user->ID, 'tfa_priv_key_64', true);
438 166
439 - die(json_encode(array('code' => $code)));
440 -
441 - } elseif ('untrust_device' == $_POST['subaction'] && isset($_POST['device_id'])) {
442 - do_action('simba_tfa_untrust_device', stripslashes($_POST['device_id']));
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;
443 174 }
444 -
445 - exit;
446 175
447 176 }
448 177
449 - /**
450 - * Called upon the AJAX action simbatfa-init-otp . Will die.
451 - *
452 - * Uses these keys from $_POST: user
453 - */
454 178 public function tfaInitLogin() {
455 179
456 180 if (empty($_POST['user'])) die('Security check (2).');
457 181
458 182 if (defined('TWO_FACTOR_DISABLE') && TWO_FACTOR_DISABLE) {
459 - $res = array('result' => false, 'user_can_trust' => false);
183 + $res = false;
460 184 } else {
461 -
462 - if (!function_exists('sanitize_user')) require_once ABSPATH.WPINC.'/formatting.php';
463 -
464 - // WP's password-checking sanitizes the supplied user, so we must do the same to check if TFA is enabled for them
465 - $auth_info = array('log' => sanitize_user(stripslashes((string)$_POST['user'])));
466 -
467 - if (!empty($_COOKIE['simbatfa_trust_token'])) $auth_info['trust_token'] = (string) $_COOKIE['simbatfa_trust_token'];
468 -
469 - $res = $this->pre_auth($auth_info, 'array');
185 + $tfa = $this->getTFA();
186 + $res = $tfa->preAuth(array('log' => (string)$_POST['user']));
470 187 }
471 188
472 - $results = array(
473 - 'jsonstarter' => 'justhere',
474 - 'status' => $res['result'],
475 - );
476 -
477 - if (!empty($res['user_can_trust'])) {
478 - $results['user_can_trust'] = 1;
479 - if (!empty($res['user_already_trusted'])) $results['user_already_trusted'] = 1;
480 - }
189 + $results = array('jsonstarter' => 'justhere', 'status' => $res);
481 190
482 -
483 191 if (!empty($this->output_buffering)) {
484 192 if (!empty($this->logged)) {
485 193 $results['php_output'] = $this->logged;
486 194 }
@@ -488,249 +196,91 @@
488 196 $buffered = ob_get_clean();
489 197 if ($buffered) $results['extra_output'] = $buffered;
490 198 }
491 199
492 - $results = apply_filters('simbatfa_check_tfa_requirements_ajax_response', $results);
493 -
494 200 echo json_encode($results);
495 -
496 201 exit;
497 202 }
498 203
499 - /**
500 - * Enable or disable TFA for a user
501 - *
502 - * @param Integer $user_id - the WordPress user ID
503 - * @param String $setting - either "true" (to turn on) or "false" (to turn off)
504 - */
505 - public function change_tfa_enabled_status($user_id, $setting) {
506 - $previously_enabled = $this->is_activated_by_user($user_id) ? 1 : 0;
507 - $setting = ('true' === $setting) ? 1 : 0;
508 - update_user_meta($user_id, 'tfa_enable_tfa', $setting);
509 - do_action('simba_tfa_activation_status_saved', $user_id, $setting, $previously_enabled, $this);
510 - }
511 -
512 - /**
513 - * Here's where the login action happens. Called on the WP 'authenticate' action.
514 - *
515 - * @param WP_Error|WP_User $user
516 - * @param String $username - this is not necessarily the WP username; it is whatever was typed in the form, so can be an email address
517 - * @param String $password
518 - *
519 - * @return WP_Error|WP_User
520 - */
204 +
205 + // Here's where the login action happens. Called on the 'authenticate' action.
521 206 public function tfaVerifyCodeAndUser($user, $username, $password) {
522 207
523 - $original_user = $user;
524 - $params = stripslashes_deep($_POST);
208 + $tfa = $this->getTFA();
209 +
210 + if (is_wp_error($user)) return $user;
525 211
526 - // If (only) the error was a wrong password, but it looks like the user appended a TFA code to their password, then have another go
527 - if (is_wp_error($user) && array('incorrect_password') == $user->get_error_codes() && !isset($params['two_factor_code']) && false !== ($from_password = apply_filters('simba_tfa_tfa_from_password', false, $password))) {
528 - // This forces a new password authentication below
529 - $user = false;
530 - }
531 -
532 - if (is_wp_error($user)) {
533 - $ret = $user;
534 - } else {
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);
535 217
536 - if (is_object($user) && isset($user->ID) && isset($user->user_login)) {
537 - $params['log'] = $user->user_login;
538 - // Confirm that this is definitely a username regardless of its format
539 - $may_be_email = false;
540 - } else {
541 - $params['log'] = $username;
542 - $may_be_email = true;
543 - }
544 -
545 - $params['caller'] = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['REQUEST_URI'];
546 - if (!empty($_COOKIE['simbatfa_trust_token'])) $params['trust_token'] = (string) $_COOKIE['simbatfa_trust_token'];
218 + if (is_wp_error($code_ok)) return $code_ok;
547 219
548 - if (isset($from_password) && false !== $from_password) {
549 - // Support login forms that can't be hooked via appending to the password
550 - $speculatively_try_appendage = true;
551 - $params['two_factor_code'] = $from_password['tfa_code'];
552 - }
553 -
554 - $code_ok = $this->authorise_user_from_login($params, $may_be_email);
555 -
556 - if (is_wp_error($code_ok)) {
557 - $ret = $code_ok;
558 - } elseif (!$code_ok) {
559 - $ret = new WP_Error('authentication_failed', '<strong>'.__('Error:', 'two-factor-authentication').'</strong> '.__('The one-time password (TFA code) you entered was incorrect.', 'two-factor-authentication'));
560 - } elseif ($user) {
561 - $ret = $user;
562 - } else {
563 -
564 - if (!empty($speculatively_try_appendage) && true === $code_ok) {
565 - $password = $from_password['password'];
566 - }
567 -
568 - $username_is_email = false;
569 -
570 - if (function_exists('wp_authenticate_username_password') && $may_be_email && filter_var($username, FILTER_VALIDATE_EMAIL)) {
571 - global $wpdb;
572 - // This has to match self::authorise_user_from_login()
573 - $response = $wpdb->get_row($wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_email=%s", $username));
574 - if (is_object($response)) $username_is_email = true;
575 - }
576 -
577 - $ret = $username_is_email ? wp_authenticate_email_password(null, $username, $password) : wp_authenticate_username_password(null, $username, $password);
578 - }
579 -
580 - }
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));
581 221
582 - $ret = apply_filters('simbatfa_verify_code_and_user_result', $ret, $original_user, $username, $password);
583 -
584 - // If the TFA code was actually validated (not just not required, for example), then $code_ok is (boolean)true
585 - if (isset($code_ok) && true === $code_ok && is_a($ret, 'WP_User')) {
586 - if (!empty($params['simba_tfa_mark_as_trusted']) && $this->user_can_trust($ret->ID) && (is_ssl() || (!empty($_SERVER['SERVER_NAME']) && ('localhost' == $_SERVER['SERVER_NAME'] ||'127.0.0.1' == $_SERVER['SERVER_NAME'])))) {
587 -
588 - $trusted_for = $this->get_option('tfa_trusted_for');
589 - $trusted_for = (false === $trusted_for) ? 30 : (string) absint($trusted_for);
222 + if ($user) return $user;
590 223
591 - $this->trust_device($ret->ID, $trusted_for);
592 - }
593 - }
594 -
595 - return $ret;
224 + return wp_authenticate_username_password(null, $username, $password);
596 225 }
597 -
598 - // N.B. - This doesn't check is_activated_for_user() - the caller would normally want to do that first
599 - public function user_can_trust($user_id) {
600 - // Default is false because this is a new feature and we don't want to surprise existing users by granting broader access than they expected upon an upgrade
601 - return apply_filters('simba_tfa_user_can_trust', false, $user_id);
602 - }
603 -
604 - /**
605 - * Should the user be asked for a TFA code? And optionally, is the user allowed to trust devices?
606 - *
607 - * @param Array $params - the key used is 'log', indicating the username or email address
608 - * @param String $response_format - 'simple' (historic format) or 'array' (richer info)
609 - *
610 - * @return Boolean
611 - */
612 - public function pre_auth($params, $response_format = 'simple') {
613 - global $wpdb;
226 +
227 + public function tfaRegisterTwoFactorAuthSettings()
228 + {
229 + global $wp_roles;
230 + if (!isset($wp_roles))
231 + $wp_roles = new WP_Roles();
614 232
615 - $query = filter_var($params['log'], FILTER_VALIDATE_EMAIL) ? $wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_email=%s", $params['log']) : $wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_login=%s", $params['log']);
616 - $user = $wpdb->get_row($query);
617 -
618 - if (!$user && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) {
619 - // Corner-case: login looks like an email, but is a username rather than email address
620 - $user = $wpdb->get_row($wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_login=%s", $params['log']));
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);
621 237 }
622 238
623 - $is_activated_for_user = true;
624 - $is_activated_by_user = false;
625 -
626 - $result = false;
627 -
628 - $totp_controller = $this->totp_controller;
629 -
630 - if ($user) {
631 - $tfa_priv_key = get_user_meta($user->ID, 'tfa_priv_key_64', true);
632 - $is_activated_for_user = $this->is_activated_for_user($user->ID);
633 - $is_activated_by_user = $this->is_activated_by_user($user->ID);
634 -
635 - if ($is_activated_for_user && $is_activated_by_user) {
636 -
637 - // No private key yet, generate one. This shouldn't really be possible.
638 - if (!$tfa_priv_key) $tfa_priv_key = $totp_controller->addPrivateKey($user->ID);
639 -
640 - $code = $totp_controller->generateOTP($user->ID, $tfa_priv_key);
641 -
642 - $result = true;
643 - }
644 - }
645 -
646 - if ('array' != $response_format) return $result;
647 -
648 - $ret = array('result' => $result);
649 -
650 - if ($result) {
651 - $ret['user_can_trust'] = $this->user_can_trust($user->ID);
652 - if (!empty($params['trust_token']) && $this->user_trust_token_valid($user->ID, $params['trust_token'])) {
653 - $ret['user_already_trusted'] = 1;
654 - }
655 - }
656 -
657 - return $ret;
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');
658 242 }
659 -
660 - /**
661 - * Print the radio buttons for enabling/disabling TFA
662 - *
663 - * @param Integer $user_id - the WordPress user ID
664 - * @param Boolean $long_label - whether to use a long label rather than a short one
665 - * @param String $style - valid values are "show_current" and "require_current"
666 - */
667 - public function paint_enable_tfa_radios($user_id, $long_label = false, $style = 'show_current') {
668 243
669 - if (!$user_id) return;
244 + public function tfaListEnableRadios($user_id, $long_label = false)
245 + {
246 + if(!$user_id)
247 + return;
670 248
671 - if ('require_current' != $style) $style = 'show_current';
249 + $setting = get_user_meta($user_id, 'tfa_enable_tfa', true);
250 + $setting = !$setting ? false : $setting;
672 251
673 - $is_required = $this->is_required_for_user($user_id);
674 - $is_activated = $this->is_activated_by_user($user_id);
675 -
676 - if ($is_required) {
677 - $require_after = absint($this->get_option('tfa_requireafter'));
678 - echo '<p class="tfa_required_warning" style="font-weight:bold; font-style:italic;">'.sprintf(__('N.B. This site is configured to forbid you to log in if you disable two-factor authentication after your account is %d days old', 'two-factor-authentication'), $require_after).'</p>';
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>';
679 258 }
680 259
681 - $tfa_enabled_label = $long_label ? __('Enable two-factor authentication', 'two-factor-authentication') : __('Enabled', 'two-factor-authentication');
682 -
683 - if ('show_current' == $style) {
684 - $tfa_enabled_label .= ' '.sprintf(__('(Current code: %s)', 'two-factor-authentication'), $this->current_otp_code($user_id));
685 - } elseif ('require_current' == $style) {
686 - $tfa_enabled_label .= ' '.sprintf(__('(you must enter the current code: %s)', 'two-factor-authentication'), '<input type="text" class="tfa_enable_current" name="tfa_enable_current" size="6" style="height">');
687 - }
688 -
689 - $show_disable = ((is_multisite() && is_super_admin()) || (!is_multisite() && current_user_can($this->get_management_capability())) || false == $is_activated || !$is_required || !$this->get_option('tfa_hide_turn_off')) ? true : false;
690 -
691 - $tfa_disabled_label = $long_label ? __('Disable two-factor authentication', 'two-factor-authentication') : __('Disabled', 'two-factor-authentication');
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);
692 262
693 - if ('require_current' == $style) echo '<input type="hidden" name="require_current" value="1">'."\n";
694 -
695 - echo '<input type="radio" class="tfa_enable_radio" id="tfa_enable_tfa_true" name="tfa_enable_tfa" value="true" '.(true == $is_activated ? 'checked="checked"' : '').'> <label class="tfa_enable_radio_label" for="tfa_enable_tfa_true">'.apply_filters('simbatfa_radiolabel_enabled', $tfa_enabled_label, $long_label).'</label> <br>';
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>';
696 264
697 - // Show the 'disabled' option if the user is an admin, or if it is currently set, or if TFA is not compulsory, or if the site owner doesn't require it to be hidden
698 - // Note that this just hides the option in the UI. The user could POST to turn off TFA, but, since it's required, they won't be able to log in.
699 - if ($show_disable) {
700 - echo '<input type="radio" class="tfa_enable_radio" id="tfa_enable_tfa_false" name="tfa_enable_tfa" value="false" '.(false == $is_activated ? 'checked="checked"' :'').'> <label class="tfa_enable_radio_label" for="tfa_enable_tfa_false">'.apply_filters('simbatfa_radiolabel_disabled', $tfa_disabled_label, $long_label).'</label> <br>';
701 - }
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>';
702 266 }
267 +
703 268
704 - /**
705 - * Echo the radio buttons for changing between TOTP/HOTP
706 - *
707 - * TODO: Hide this choice on new installs (TOTP only)
708 - *
709 - * @param Integer $user_id
710 - */
711 - private function print_algorithm_choice_radios($user_id) {
712 - if (!$user_id) return;
269 + public function tfaListAlgorithmRadios($user_id)
270 + {
271 + if(!$user_id) return;
713 272
714 - $types = array(
715 - 'totp' => __('TOTP (time based - most common algorithm; used by Google Authenticator)', 'two-factor-authentication'),
716 - 'hotp' => __('HOTP (event based)', 'two-factor-authentication')
717 - );
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));
718 274
719 - $setting = $this->totp_controller->get_user_otp_algorithm($user_id);
275 + $setting = get_user_meta($user_id, 'tfa_algorithm_type', true);
276 + $setting = $setting === false || !$setting ? 'totp' : $setting;
720 277
721 - foreach ($types as $id => $name) {
278 + foreach($types as $id => $name) {
722 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";
723 280 }
724 281 }
725 282
726 - /**
727 - * Retrieve a saved option
728 - *
729 - * @param String $key - option key
730 - *
731 - * @return Mixed
732 - */
733 283 public function get_option($key) {
734 284 if (!is_multisite()) return get_option($key);
735 285 switch_to_blog(1);
736 286 $v = get_option($key);
@@ -737,46 +287,55 @@
737 287 restore_current_blog();
738 288 return $v;
739 289 }
740 290
741 - /**
742 - * Paint a list of checkboxes, one for each role
743 - *
744 - * @param String $prefix
745 - * @param Integer $default - default value (0 or 1)
746 - */
747 - public function list_user_roles_checkboxes($prefix = '', $default = 1) {
291 + public function tfaListUserRolesCheckboxes()
292 + {
748 293
749 294 if (is_multisite()) {
750 295 // Not a real WP role; needs separate handling
751 296 $id = '_super_admin';
752 - $name = __('Multisite Super Admin', 'two-factor-authentication');
753 - $setting = $this->get_option('tfa_'.$prefix.$id);
754 - $setting = ($setting === false) ? $default : ($setting ? 1 : 0);
297 + $name = __('Multisite Super Admin', SIMBA_TFA_TEXT_DOMAIN);
298 + $setting = $this->get_option('tfa_'.$id);
299 + $setting = $setting === false || $setting ? 1 : 0;
755 300
756 - echo '<input type="checkbox" id="tfa_'.$prefix.$id.'" name="tfa_'.$prefix.$id.'" value="1" '.($setting ? 'checked="checked"' :'').'> <label for="tfa_'.$prefix.$id.'">'.htmlspecialchars($name)."</label><br>\n";
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";
757 302 }
758 303
759 304 global $wp_roles;
760 305 if (!isset($wp_roles)) $wp_roles = new WP_Roles();
761 306
762 - foreach ($wp_roles->role_names as $id => $name) {
763 - $setting = $this->get_option('tfa_'.$prefix.$id);
764 - $setting = ($setting === false) ? $default : ($setting ? 1 : 0);
307 + foreach($wp_roles->role_names as $id => $name)
308 + {
309 + $setting = $this->get_option('tfa_'.$id);
310 + $setting = $setting === false || $setting ? 1 : 0;
765 311
766 - echo '<input type="checkbox" id="tfa_'.$prefix.$id.'" name="tfa_'.$prefix.$id.'" value="1" '.($setting ? 'checked="checked"' :'').'> <label for="tfa_'.$prefix.$id.'">'.htmlspecialchars($name)."</label><br>\n";
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";
767 313 }
768 314
769 315 }
770 316
771 - public function tfa_list_xmlrpc_status_radios() {
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 + }
772 328
329 + public function tfaListXMLRPCStatusRadios()
330 + {
331 + $tfa = $this->getTFA();
773 332 $setting = $this->get_option('tfa_xmlrpc_on');
774 333 $setting = $setting === false || !$setting ? 0 : 1;
775 334
776 335 $types = array(
777 - '0' => __('Do not require 2FA over XMLRPC (best option if you must use XMLRPC and your client does not support 2FA)', 'two-factor-authentication'),
778 - '1' => __('Do require 2FA over XMLRPC (best option if you do not use XMLRPC or are unsure)', 'two-factor-authentication')
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)
779 338 );
780 339
781 340 foreach($types as $id => $name)
782 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";
@@ -781,40 +340,84 @@
781 340 foreach($types as $id => $name)
782 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";
783 342 }
784 343
785 - /**
786 - * Runs upon the WP 'init' action
787 - */
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 +
788 401 public function check_possible_reset() {
789 - if(!empty($_GET['simbatfa_priv_key_reset']) && !empty($_REQUEST['nonce']) && wp_verify_nonce($_REQUEST['nonce'], 'simbatfa_reset_private_key')) {
402 + if(!empty($_GET['simbatfa_priv_key_reset']) && !empty($_REQUEST['nonce']) && wp_verify_nonce($_REQUEST['nonce'], 'simbatfa_reset_private_key'))
403 + {
790 404 $this->reset_private_key_and_emergency_codes();
405 +// if (empty($_REQUEST['noredirect'])) exit;
791 406 exit;
792 407 }
408 +
793 409 }
794 410
795 - /**
796 - * Remove private key and emergency codes for the specified (or logged-in) user
797 - *
798 - * @param Boolean|Integer $user_id - WP user ID, or false for the currently logged-in user
799 - * @param Boolean|Null $redirect - if this is not false, then a redirection will occur - where to depends upon the value of $_REQUEST['noredirect']
800 - */
801 - public function reset_private_key_and_emergency_codes($user_id = false, $redirect = null) {
802 -
803 - if (!$user_id) {
804 - global $current_user;
805 - $user_id = $current_user->ID;
806 - }
807 -
808 - delete_user_meta($user_id, 'tfa_priv_key_64');
809 - delete_user_meta($user_id, 'simba_tfa_emergency_codes_64');
810 -
811 - if (false === $redirect) return;
812 -
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');
813 415 if (empty($_REQUEST['noredirect'])) {
814 - wp_safe_redirect(admin_url('admin.php').'?page=two-factor-auth-user&settings-updated=1');
416 + wp_safe_redirect( admin_url('admin.php').'?page=two-factor-auth-user&settings-updated=1');
815 417 } else {
816 - $url = (is_ssl() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . remove_query_arg(array('simbatfa_priv_key_reset', 'noredirect', 'nonce'));
418 + $url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . remove_query_arg(array('simbatfa_priv_key_reset', 'noredirect', 'nonce'));
419 +
817 420 wp_redirect(esc_url_raw($url));
818 421 }
819 422 }
820 423
@@ -819,12 +422,13 @@
819 422 }
820 423
821 424 public function reset_link($admin = true) {
822 425
823 - $url_base = $admin ? admin_url('admin.php').'?page=two-factor-auth-user&settings-updated=1' : (( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST']);
426 + $url_base = ($admin) ? admin_url('admin.php').'?page=two-factor-auth-user&settings-updated=1' : (( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST']);
824 427
825 - $add_query_args = array('simbatfa_priv_key_reset' => 1);
826 -
428 + $add_query_args = array(
429 + 'simbatfa_priv_key_reset' => 1,
430 + );
827 431 if (!$admin) $add_query_args['noredirect'] = 1;
828 432
829 433 $url = $url_base.add_query_arg($add_query_args);
830 434
@@ -829,15 +433,12 @@
829 433 $url = $url_base.add_query_arg($add_query_args);
830 434
831 435 $url = wp_nonce_url($url, 'simbatfa_reset_private_key', 'nonce');
832 436
833 - 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>';
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>';
834 438
835 439 }
836 440
837 - /**
838 - * Runs upon the WP actions wp_footer and admin_footer
839 - */
840 441 public function footer() {
841 442 $ajax_url = admin_url('admin-ajax.php');
842 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.
843 444 if (!is_admin() && substr(strtolower($ajax_url), 0, 6) == 'https:' && !is_ssl()) {
@@ -844,31 +445,28 @@
844 445 $also_try = 'http:'.substr($ajax_url, 6);
845 446 }
846 447 ?>
847 448 <script>
848 - jQuery(function($) {
849 -
850 - // Render any QR codes
449 + jQuery(document).ready(function($) {
851 450 $('.simbaotp_qr_container').qrcode({
852 - 'render': 'image',
853 - 'text': $('.simbaotp_qr_container:first').data('qrcode'),
451 + "render": "image",
452 + "text": $('.simbaotp_qr_container:first').data('qrcode'),
854 453 });
855 -
856 - function update_otp_code() {
857 - $('.simba_current_otp').html('<em><?php echo esc_attr(__('Updating...', 'two-factor-authentication'));?></em>');
858 -
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>');
859 457 $.post('<?php echo esc_js($ajax_url);?>', {
860 - action: 'simbatfa_shared_ajax',
861 - subaction: 'refreshotp',
862 - nonce: '<?php echo esc_js(wp_create_nonce('tfa_shared_nonce'));?>'
458 + action: "simbatfa_shared_ajax",
459 + subaction: "refreshotp",
460 + nonce: "<?php echo esc_js(wp_create_nonce("tfa_shared_nonce"));?>"
863 461 }, function(response) {
864 462 var got_code = '';
865 463 try {
866 - var resp = JSON.parse(response);
464 + var resp = $.parseJSON(response);
867 465 got_code = resp.code;
868 466 } catch(err) {
869 467 <?php if (!isset($also_try)) { ?>
870 - alert("<?php echo esc_js(__('Response:', 'two-factor-authentication')); ?> "+response);
468 + alert("<?php echo esc_js(__('Response:', 'SIMBA_TFA_TEXT_DOMAIN')); ?> "+response);
871 469 <?php } ?>
872 470 console.log(response);
873 471 console.log(err);
874 472 }
@@ -880,17 +478,17 @@
880 478 subaction: "refreshotp",
881 479 nonce: "<?php echo esc_js(wp_create_nonce("tfa_shared_nonce"));?>"
882 480 }, function(response) {
883 481 try {
884 - var resp = JSON.parse(response);
482 + var resp = $.parseJSON(response);
885 483 if (resp.code) {
886 - $('.simba_current_otp').html(resp.code);
484 + $(".simba_current_otp").html(resp.code);
887 485 } else {
888 486 console.log(response);
889 487 console.log("TFA: no code found");
890 488 }
891 489 } catch(err) {
892 - alert("<?php echo esc_js(__('Response:', 'two-factor-authentication')); ?> "+response);
490 + alert("<?php echo esc_js(__('Response:', 'SIMBA_TFA_TEXT_DOMAIN')); ?> "+response);
893 491 console.log(response);
894 492 console.log(err);
895 493 }
896 494 });
@@ -895,57 +493,15 @@
895 493 }
896 494 });
897 495 <?php } else { ?>
898 496 if ('' != got_code) {
899 - $('.simba_current_otp').html(got_code);
497 + $(".simba_current_otp").html(got_code);
900 498 } else {
901 499 console.log("TFA: no code found");
902 500 }
903 501 <?php } ?>
904 502 });
905 - }
906 -
907 - var min_refresh_after = 30;
908 -
909 - if (0 == $('body.settings_page_two-factor-auth').length) {
910 - $('.simba_current_otp').each(function(ind, obj) {
911 - var refresh_after = $(obj).data('refresh_after');
912 - if (refresh_after > 0 && refresh_after < min_refresh_after) {
913 - min_refresh_after = refresh_after;
914 - }
915 - });
916 -
917 - // Update after the given seconds, and then every 30 seconds
918 - setTimeout(function() {
919 - setInterval(update_otp_code, 30000)
920 - update_otp_code();
921 - }, min_refresh_after * 1000);
922 - }
923 -
924 - // Handle clicks on the 'refresh' link
925 - $('.simbaotp_refresh').on('click', function(e) {
926 - e.preventDefault();
927 - update_otp_code();
928 503 });
929 -
930 - $('#tfa_trusted_devices_box').on('click', '.simbatfa-trust-remove', function(e) {
931 - e.preventDefault();
932 - var device_id = $(this).data('trusted-device-id');
933 - $(this).parents('.simbatfa_trusted_device').css('opacity', '0.5');
934 - if ('undefined' !== typeof device_id) {
935 - $.post('<?php echo esc_js($ajax_url);?>', {
936 - action: 'simbatfa_shared_ajax',
937 - subaction: 'untrust_device',
938 - nonce: '<?php echo esc_js(wp_create_nonce('tfa_shared_nonce'));?>',
939 - device_id: device_id
940 - }, function(response) {
941 - var resp = JSON.parse(response);
942 - if (resp.hasOwnProperty('trusted_list')) {
943 - $('#tfa_trusted_devices_box_inner').html(resp.trusted_list);
944 - }
945 - });
946 - }
947 - });
948 504 });
949 505 </script>
950 506 <?php
951 507 }
@@ -951,26 +507,26 @@
951 507 }
952 508
953 509 public function print_private_keys($admin, $type = 'full', $user_id = false) {
954 510
955 - $totp_controller = $this->totp_controller;
511 + $tfa = $this->getTFA();
956 512 global $current_user;
957 513
958 514 if ($user_id == false) $user_id = $current_user->ID;
959 515
960 516 $tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true);
961 - if (!$tfa_priv_key_64) $tfa_priv_key_64 = $totp_controller->addPrivateKey($user_id);
517 + if(!$tfa_priv_key_64) $tfa_priv_key_64 = $tfa->addPrivateKey($user_id);
962 518
963 - $tfa_priv_key = trim($totp_controller->getPrivateKeyPlain($tfa_priv_key_64, $user_id), "\x00..\x1F");
519 + $tfa_priv_key = trim($tfa->getPrivateKeyPlain($tfa_priv_key_64, $user_id));
964 520
965 521 $tfa_priv_key_32 = Base32::encode($tfa_priv_key);
966 522
967 523 if ('full' == $type) {
968 524 ?>
969 - <strong><?php echo __('Private key (base 32 - used by Google Authenticator and Authy):', 'two-factor-authentication');?></strong>
525 + <strong><?php echo __('Private key (base 32 - used by Google Authenticator and Authy):', SIMBA_TFA_TEXT_DOMAIN);?></strong>
970 526 <?php echo htmlspecialchars($tfa_priv_key_32); ?><br>
971 527
972 - <strong><?php echo __('Private key:', 'two-factor-authentication');?></strong>
528 + <strong><?php echo __('Private key:', SIMBA_TFA_TEXT_DOMAIN);?></strong>
973 529 <?php echo htmlspecialchars($tfa_priv_key); ?><br>
974 530 <?php
975 531 } elseif ('plain' == $type) {
976 532 echo htmlspecialchars($tfa_priv_key);
@@ -980,139 +536,24 @@
980 536 echo htmlspecialchars($tfa_priv_key_64);
981 537 }
982 538 }
983 539
984 - /**
985 - * Return an HTML snippet for the current OTP code
986 - *
987 - * @param Integer|Boolean $user_id
988 - *
989 - * @return String
990 - */
991 - public function current_otp_code($user_id = false) {
540 + public function current_otp_code($tfa, $user_id = false) {
992 541 global $current_user;
993 - $totp_controller = $this->totp_controller;
994 542 if (false == $user_id) $user_id = $current_user->ID;
995 543 $tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true);
996 - if (!$tfa_priv_key_64) $tfa_priv_key_64 = $totp_controller->addPrivateKey($user_id);
997 - $time_now = time();
998 - $refresh_after = 30 - ($time_now % 30);
999 - return '<span class="simba_current_otp" data-refresh_after="'.$refresh_after.'">'.$totp_controller->generateOTP($user_id, $tfa_priv_key_64).'</span>';
544 + return '<span class="simba_current_otp">'.$tfa->generateOTP($user_id, $tfa_priv_key_64).'</span>';
1000 545 }
1001 546
1002 - private function is_caller_active($params) {
1003 -
1004 - if (!defined('XMLRPC_REQUEST') || !XMLRPC_REQUEST) return true;
1005 -
1006 - global $simba_two_factor_authentication;
1007 - $saved_data = $simba_two_factor_authentication->get_option('tfa_xmlrpc_on');
1008 -
1009 - return $saved_data ? true : false;
1010 -
1011 - }
1012 -
1013 - /**
1014 - * @param Array $params
1015 - * @param Boolean $may_be_email
1016 - *
1017 - * @return WP_Error|Boolean|Integer - WP_Error or false means failure; true or 1 means success, but true means the TFA code was validated
1018 - */
1019 - public function authorise_user_from_login($params, $may_be_email = false) {
1020 -
1021 - $params = apply_filters('simbatfa_auth_user_from_login_params', $params);
1022 -
1023 - global $wpdb;
1024 -
1025 - if (!$this->is_caller_active($params)) return 1;
1026 -
1027 - $query = ($may_be_email && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) ? $wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_email=%s", $params['log']) : $wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_login=%s", $params['log']);
1028 - $response = $wpdb->get_row($query);
1029 -
1030 - if (!$response && $may_be_email && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) {
1031 - // Corner-case: login looks like an email, but is a username rather than email address
1032 - $response = $wpdb->get_row($wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_login=%s", $params['log']));
1033 - }
1034 -
1035 - $user_ID = is_object($response) ? $response->ID : false;
1036 - $user_registered = is_object($response) ? $response->user_registered : false;
1037 -
1038 - $user_code = isset($params['two_factor_code']) ? str_replace(' ', '', trim($params['two_factor_code'])) : '';
1039 -
1040 - // This condition in theory should not be possible
1041 - if (!$user_ID) return new WP_Error('tfa_user_not_found', apply_filters('simbatfa_tfa_user_not_found', '<strong>'.__('Error:', 'two-factor-authentication').'</strong> '.__('The indicated user could not be found.', 'two-factor-authentication')));
1042 -
1043 - if (!$this->is_activated_for_user($user_ID)) return 1;
1044 -
1045 - if (!empty($params['trust_token']) && $this->user_trust_token_valid($user_ID, $params['trust_token'])) {
1046 - return 1;
1047 - }
1048 -
1049 - if (!$this->is_activated_by_user($user_ID)) {
1050 -
1051 - if (!$this->is_required_for_user($user_ID)) return 1;
1052 -
1053 - $require_after = absint($this->get_option('tfa_requireafter')) * 86400;
1054 -
1055 - $account_age = time() - strtotime($user_registered);
1056 -
1057 - if ($account_age > $require_after && apply_filters('simbatfa_enforce_require_after_check', true, $user_ID, $require_after, $account_age)) {
1058 - return new WP_Error('tfa_required', apply_filters('simbatfa_notfa_forbidden_login', '<strong>'.__('Error:', 'two-factor-authentication').'</strong> '.__('The site owner has forbidden you to login without two-factor authentication. Please contact the site owner to re-gain access.', 'two-factor-authentication')));
1059 - }
1060 -
1061 - return 1;
1062 - }
1063 -
1064 - $tfa_creds_user_id = !empty($params['creds_user_id']) ? $params['creds_user_id'] : $user_ID;
1065 -
1066 - if ($tfa_creds_user_id != $user_ID) {
1067 -
1068 - // Authenticating using a different user's credentials (e.g. https://wordpress.org/plugins/use-administrator-password/)
1069 - // In this case, we require that different user to have TFA active - so that this mechanism can't be used to avoid TFA
1070 -
1071 - if (!$this->is_activated_for_user($tfa_creds_user_id) || !$this->is_activated_by_user($tfa_creds_user_id)) {
1072 - return new WP_Error('tfa_required', apply_filters('simbatfa_notfa_forbidden_login_altuser', '<strong>'.__('Error:', 'two-factor-authentication').'</strong> '.__('You are attempting to log in to an account that has two-factor authentication enabled; this requires you to also have two-factor authentication enabled on the account whose credentials you are using.', 'two-factor-authentication')));
1073 - }
1074 -
1075 - }
1076 -
1077 - return $this->totp_controller->check_code_for_user($tfa_creds_user_id, $user_code);
1078 -
1079 - }
1080 -
1081 - /**
1082 - * Evaluate whether a trust token is valid for a user
1083 - *
1084 - * @param Integer $user_id - WP user ID
1085 - * @param String $trust_token - trust token
1086 - *
1087 - * @return Boolean
1088 - */
1089 - private function user_trust_token_valid($user_id, $trust_token) {
1090 -
1091 - if (!is_string($trust_token) || strlen($trust_token) < 30) return false;
1092 -
1093 - $trusted_devices = $this->user_get_trusted_devices($user_id);
1094 -
1095 - $time_now = time();
1096 -
1097 - foreach ($trusted_devices as $device) {
1098 - if (empty($device['until']) || $device['until'] <= $time_now) continue;
1099 - if (!empty($device['token']) && $device['token'] === $trust_token) {
1100 - return true;
1101 - }
1102 - }
1103 -
1104 - return false;
1105 - }
1106 -
1107 547 public function add_footer($admin) {
1108 548 static $added_footer;
1109 549 if (empty($added_footer)) {
1110 550 $added_footer = true;
551 +// wp_enqueue_script('jquery');
1111 552 $script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : $this->version;
1112 - $script_file = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? 'jquery-qrcode.js' : 'jquery-qrcode.min.js';
1113 - wp_enqueue_script('jquery-qrcode', SIMBA_TFA_PLUGIN_URL.'/includes/jquery-qrcode/'.$script_file, array('jquery'), $script_ver);
1114 - add_action($admin ? 'admin_footer' : 'wp_footer', array($this, 'footer'));
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'));
1115 556 }
1116 557 }
1117 558
1118 559 public function current_codes_box($admin = true, $user_id = false) {
@@ -1118,11 +559,13 @@
1118 559 public function current_codes_box($admin = true, $user_id = false) {
1119 560
1120 561 global $current_user;
1121 562
1122 - if (false == $user_id) $user_id = $current_user->ID;
563 + if (false == $user_id) {
564 + $user_id = $current_user->ID;
565 + }
1123 566
1124 - $totp_controller = $this->totp_controller;
567 + $tfa = $this->getTFA();
1125 568
1126 569 $this->add_footer($admin);
1127 570
1128 571 $url = preg_replace('/^https?:\/\//', '', site_url());
@@ -1128,36 +571,38 @@
1128 571 $url = preg_replace('/^https?:\/\//', '', site_url());
1129 572
1130 573 $tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true);
1131 574
1132 - if (!$tfa_priv_key_64) $tfa_priv_key_64 = $totp_controller->addPrivateKey($user_id);
575 + if(!$tfa_priv_key_64) $tfa_priv_key_64 = $tfa->addPrivateKey($user_id);
1133 576
1134 - $tfa_priv_key = trim($totp_controller->getPrivateKeyPlain($tfa_priv_key_64, $user_id), "\x00..\x1F");
577 + $tfa_priv_key = trim($tfa->getPrivateKeyPlain($tfa_priv_key_64, $user_id));
1135 578
1136 579 $tfa_priv_key_32 = Base32::encode($tfa_priv_key);
1137 580
1138 - $algorithm_type = $totp_controller->get_user_otp_algorithm($user_id);
581 + $algorithm_type = $tfa->getUserAlgorithm($user_id);
1139 582
1140 583 if ($admin) {
1141 584 if ($current_user->ID == $user_id) {
1142 - if (!is_admin()) echo '<h2>'.__('Current codes', 'two-factor-authentication').'</h2>';
585 + echo '<h2>'.__('Current codes', SIMBA_TFA_TEXT_DOMAIN).'</h2>';
1143 586 } else {
1144 587 $user = get_user_by('id', $user_id);
1145 588 $user_descrip = htmlspecialchars($user->user_nicename.' - '.$user->user_email);
1146 - echo '<h2>'.sprintf(__('Current codes (login: %s)', 'two-factor-authentication'), $user_descrip).'</h2>';
589 + echo '<h2>'.sprintf(__('Current codes (login: %s)', SIMBA_TFA_TEXT_DOMAIN), $user_descrip).'</h2>';
1147 590 }
591 + } else {
592 +// echo '<h2>'.__('Current one-time password', SIMBA_TFA_TEXT_DOMAIN).' '.$this->reset_current_otp_link().'</h2>';
1148 593 }
1149 594
1150 595 ?>
1151 - <div class="postbox" style="clear:both;">
596 + <div class="postbox">
1152 597
1153 598 <?php if ($admin) { ?>
1154 599 <h3 style="padding: 10px 6px 0px; margin:4px 0 0; cursor: default;">
1155 - <span style="cursor: default;"><?php echo __('Current one-time password', 'two-factor-authentication').' ';
600 + <span style="cursor: default;"><?php echo __('Current one-time password', SIMBA_TFA_TEXT_DOMAIN).' ';
1156 601 if ($current_user->ID == $user_id) { echo $this->reset_current_otp_link(); } ?>
1157 602 </span>
1158 603 <div class="inside">
1159 - <p><strong style="font-size: 3em;"><?php echo $this->current_otp_code($user_id); ?></strong></p>
604 + <p><strong style="font-size: 3em;"><?php echo $this->current_otp_code($tfa, $user_id); ?></strong></p>
1160 605 </div>
1161 606 </h3>
1162 607 <?php } else {
1163 608 ?>
@@ -1163,16 +608,12 @@
1163 608 ?>
1164 609 <div class="inside">
1165 610 <p class="simbatfa-frontend-current-otp" style="font-size: 1.5em; margin-top:6px;">
1166 611 <strong>
1167 - <?php echo __('Current one-time password', 'two-factor-authentication').' '.$this->reset_current_otp_link(); ?>
612 + <?php echo __('Current one-time password', SIMBA_TFA_TEXT_DOMAIN).' '.$this->reset_current_otp_link(); ?>
1168 613 </strong> :
1169 614
1170 - <?php
1171 - $time_now = time();
1172 - $refresh_after = 30 - ($time_now % 30);
1173 -
1174 - ?><span class="simba_current_otp" data-refresh_after="<?php echo $refresh_after; ?>"><?php print $totp_controller->generateOTP($user_id, $tfa_priv_key_64); ?></span>
615 + <span class="simba_current_otp"><?php print $tfa->generateOTP($user_id, $tfa_priv_key_64); ?></span>
1175 616
1176 617 </p>
1177 618 </div>
1178 619
@@ -1178,55 +619,55 @@
1178 619
1179 620 <?php } ?>
1180 621
1181 622 <?php if ($admin) { ?>
1182 - <h3 style="padding-left: 10px; cursor: default;">
1183 - <span style="cursor: default;"><?php _e('Setting up - either scan the code, or type in the private key', 'two-factor-authentication'); ?></span>
1184 - </h3>
623 + <h3 style="padding-left: 10px; cursor: default;">
624 + <span style="cursor: default;"><?php _e('QR code', SIMBA_TFA_TEXT_DOMAIN); ?></span>
625 + </h3>
1185 626 <?php } else {
1186 - echo '<h2>'.__('Setting up', 'two-factor-authentication').'</h2>';
627 + echo '<h2>'.__('QR code', SIMBA_TFA_TEXT_DOMAIN).'</h2>';
1187 628 } ?>
1188 629 <div class="inside">
1189 - <p>
1190 - <?php
1191 - _e('For OTP apps that support using a camera to scan a setup code (below), that is the quickest way to set the app up (e.g. with Duo Mobile, Google Authenticator).', 'two-factor-authentication');
1192 - echo ' ';
1193 - _e('Otherwise, you can type the textual private key (shown below) into your app. Always keep private keys secret.', 'two-factor-authentication');
1194 - ?>
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); ?>.
1195 632
1196 - <?php printf(__('You are currently using %s, %s', 'two-factor-authentication'), strtoupper($algorithm_type), ($algorithm_type == 'totp') ? __('a time based algorithm', 'two-factor-authentication') : __('an event based algorithm', 'two-factor-authentication')); ?>.
1197 - </p>
1198 -
1199 - <?php $qr_url = $this->tfa_qr_code_url($algorithm_type, $url, $tfa_priv_key, $user_id) ?>
1200 - <div style="float: left; padding-right: 20px;" class="simbaotp_qr_container" data-qrcode="<?php echo esc_attr($qr_url); ?>"></div>
1201 -
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 +
1202 645 <p>
1203 646 <?php
1204 647 $this->print_private_keys($admin, 'full', $user_id);
1205 - if ($current_user->ID == $user_id) {
1206 - echo $this->reset_link($admin);
1207 - } else {
1208 - echo '<a id="tfa-reset-privkey-for-user" data-user_id="'.$user_id.'" href="#">'.__('Reset private key', 'two-factor-authentication').'</a>';
1209 - }
648 + if ($current_user->ID == $user_id) { echo $this->reset_link($admin);}
1210 649 ?>
1211 650 </p>
651 + </div>
1212 652
1213 653 <?php
1214 654 if ($admin || apply_filters('simba_tfa_emergency_codes_user_settings', false, $user_id) !== false) {
1215 655 ?>
1216 -
656 + <div class="inside">
1217 657
1218 - <div style="min-height: 100px;">
1219 - <h3 class="normal" style="cursor: default"><?php _e('Emergency codes', 'two-factor-authentication'); ?></h3>
658 + <h3 class="normal" style="cursor: default"><?php _e('Emergency codes', SIMBA_TFA_TEXT_DOMAIN); ?></h3>
659 +
660 + <p>
1220 661 <?php
1221 - $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>';
1222 - echo apply_filters('simba_tfa_emergency_codes_user_settings', $default_text, $user_id);
1223 - ?>
1224 - </div>
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>
1225 666
667 + </div>
668 +
1226 669 <?php } ?>
1227 -
1228 - </div>
1229 670
1230 671 </div>
1231 672 <?php
1232 673 }
@@ -1231,113 +672,69 @@
1231 672 <?php
1232 673 }
1233 674
1234 675 public function reset_current_otp_link($admin = true) {
1235 - return '<a href="#" class="simbaotp_refresh">'.__('(update)', 'two-factor-authentication').'</a>';
676 + return '<a href="#" class="simbaotp_refresh">'.__('(update)', SIMBA_TFA_TEXT_DOMAIN).'</a>';
1236 677 }
1237 678
1238 - /**
1239 - * Print out the advanced settings box
1240 - *
1241 - * @param Boolean|Callable $submit_button_callback - if not a callback, then <form> tags will be added
1242 - */
1243 679 public function advanced_settings_box($submit_button_callback = false) {
1244 - $totp_controller = $this->totp_controller;
680 + $tfa = $this->getTFA();
1245 681
1246 682 global $current_user;
1247 - $algorithm_type = $totp_controller->get_user_otp_algorithm($current_user->ID);
683 + $algorithm_type = $tfa->getUserAlgorithm($current_user->ID);
1248 684
1249 685 ?>
1250 - <h2 id="tfa_advanced_heading" style="clear:both;"><?php _e('Advanced settings', 'two-factor-authentication'); ?></h2>
686 + <h2><?php _e('Advanced settings', SIMBA_TFA_TEXT_DOMAIN); ?></h2>
1251 687
1252 688 <div id="tfa_advanced_box" class="tfa_settings_form" style="margin-top: 20px;">
1253 689
1254 - <?php if (false === $submit_button_callback) { ?>
1255 - <form method="post" action="<?php print esc_url(add_query_arg('settings-updated', 'true', $_SERVER['REQUEST_URI'])); ?>">
1256 - <?php wp_nonce_field('tfa_algorithm', '_tfa_algorithm_nonce', false, true); ?>
1257 - <?php } ?>
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 } ?>
1258 691
1259 - <?php _e('Choose which algorithm for One Time Passwords you want to use.', 'two-factor-authentication'); ?>
1260 - <p>
1261 - <?php
1262 - $this->print_algorithm_choice_radios($current_user->ID);
1263 - if ('hotp' == $algorithm_type) {
1264 - $counter = $totp_controller->getUserCounter($current_user->ID);
1265 - print '<br>'.__('Your counter on the server is currently on', 'two-factor-authentication').': '.$counter;
1266 - }
1267 - ?>
1268 -
1269 - </p>
1270 - <?php if (false === $submit_button_callback) { submit_button(); echo '</form>'; } else { call_user_func($submit_button_callback); } ?>
1271 -
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); } ?>
1272 705 </div>
1273 706 <?php
1274 707 }
1275 708
1276 - /**
1277 - * This deals with the issue that wp-login.php does not redirect to a canonical URL. As a result, if a website is available under more than one host, then admin_url('admin-ajax.php') might return a different one than the visitor is using, resulting in AJAX failing due to CORS errors.
1278 - *
1279 - * @return String
1280 - */
1281 - private function get_ajax_url() {
1282 - $ajax_url = admin_url('admin-ajax.php');
1283 - $parsed_url = parse_url($ajax_url);
1284 - if (strtolower($parsed_url['host']) !== strtolower($_SERVER['HTTP_HOST']) && !empty($parsed_url['path'])) {
1285 - // Mismatch - return the relative URL only
1286 - $ajax_url = $parsed_url['path'];
1287 - }
1288 - return $ajax_url;
1289 - }
1290 -
1291 - /**
1292 - * Called not only upon the WP action login_enqueue_scripts, but potentially upon the action 'init' and various others from other plugins too. It can handle being called multiple times.
1293 - */
1294 - public function login_enqueue_scripts() {
1295 -
1296 - if (isset($_GET['action']) && 'logout ' != $_GET['action'] && 'login' != $_GET['action']) return;
1297 -
1298 - static $already_done = false;
1299 - if ($already_done) return;
1300 - $already_done = true;
1301 -
709 + public function login_enqueue_scripts()
710 + {
711 +
712 + if(isset($_GET['action']) && $_GET['action'] != 'logout' && $_GET['action'] != 'login') return;
713 +
1302 714 // Prevent cacheing when in debug mode
1303 715 $script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : $this->version;
1304 716
1305 - wp_enqueue_script('tfa-ajax-request', SIMBA_TFA_PLUGIN_URL.'/includes/tfa.js', array('jquery'), $script_ver);
1306 -
1307 - $trusted_for = $this->get_option('tfa_trusted_for');
1308 - $trusted_for = (false === $trusted_for) ? 30 : (string) absint($trusted_for);
1309 -
717 + wp_enqueue_script( 'tfa-ajax-request', SIMBA_TFA_PLUGIN_URL . '/includes/tfa.js', array( 'jquery' ), $script_ver );
1310 718 $localize = array(
1311 - 'ajaxurl' => $this->get_ajax_url(),
1312 - 'click_to_enter_otp' => __("Click to enter One Time Password", 'two-factor-authentication'),
1313 - 'enter_username_first' => __('You have to enter a username first.', 'two-factor-authentication'),
1314 - 'otp' => __('One Time Password (i.e. 2FA)', 'two-factor-authentication'),
1315 - 'otp_login_help' => __('(check your OTP app to get this password)', 'two-factor-authentication'),
1316 - 'mark_as_trusted' => sprintf(_n('Trust this device (allow login without 2FA for %d day)', 'Trust this device (allow login without TFA for %d days)', $trusted_for, 'two-factor-authentication'), $trusted_for),
1317 - 'is_trusted' => __('(Trusted device)', 'two-factor-authentication'),
1318 - 'nonce' => wp_create_nonce('simba_tfa_loginform_nonce'),
1319 - 'login_form_selectors' => '',
1320 - 'login_form_off_selectors' => '',
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")
1321 725 );
1322 -
1323 726 // Spinner exists since WC 3.8. Use the proper functions to avoid SSL warnings.
1324 - if (file_exists(ABSPATH.'wp-admin/images/spinner-2x.gif')) {
1325 - $localize['spinnerimg'] = admin_url('images/spinner-2x.gif');
1326 - } elseif (file_exists(ABSPATH.WPINC.'/images/spinner-2x.gif')) {
1327 - $localize['spinnerimg'] = includes_url('images/spinner-2x.gif');
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');
1328 731 }
1329 -
1330 - $localize = apply_filters('simba_tfa_login_enqueue_localize', $localize);
1331 -
1332 - wp_localize_script('tfa-ajax-request', 'simba_tfasettings', $localize);
1333 -
732 + wp_localize_script( 'tfa-ajax-request', 'simba_tfasettings', $localize);
1334 733 }
1335 734
1336 - /**
1337 - * See if HOTP is off sync, and if show, print out a message
1338 - */
1339 - public function tfa_show_hotp_off_sync_message() {
735 + public function tfaShowHOTPOffSyncMessage()
736 + {
1340 737 global $current_user;
1341 738 $is_off_sync = get_user_meta($current_user->ID, 'tfa_hotp_off_sync', true);
1342 739 if(!$is_off_sync)
1343 740 return;
@@ -1343,15 +740,15 @@
1343 740 return;
1344 741
1345 742 ?>
1346 743 <div class="error">
1347 - <h3><?php _e('Two Factor Authentication re-sync needed', 'two-factor-authentication');?></h3>
744 + <h3><?php _e('Two Factor Authentication re-sync needed', SIMBA_TFA_TEXT_DOMAIN);?></h3>
1348 745 <p>
1349 - <?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'); ?>
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); ?>
1350 747 <br>
1351 - <?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');?>
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);?>
1352 749 <br><br>
1353 - <a href="<?php echo wp_nonce_url('admin.php?page=two-factor-auth-user&warning_button_clicked=1', 'tfaresync', 'resyncnonce'); ?>" class="button"><?php _e('Click here and re-scan the QR-Code', 'two-factor-authentication');?></a>
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>
1354 751 </p>
1355 752 </div>
1356 753
1357 754 <?php
@@ -1358,68 +755,51 @@
1358 755
1359 756 }
1360 757
1361 758 // QR code image
1362 - public function tfa_qr_code_url($algorithm_type, $url, $tfa_priv_key, $user_id = false) {
759 + public function tfa_qr_code_url($algorithm_type, $url, $tfa_priv_key, $user_id = false){
1363 760 global $current_user;
1364 761
1365 - $user = (false == $user_id) ? $current_user : get_user_by('id', $user_id);
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.'">';
1366 774
1367 - $encode = 'otpauth://'.$algorithm_type.'/'.$url.':'.rawurlencode($user->user_login).'?secret='.Base32::encode($tfa_priv_key).'&issuer='.$url.'&counter='.$this->totp_controller->getUserCounter($user->ID);
775 + // New
776 + $encode = 'otpauth://'.$algorithm_type.'/'.$url.':'.$user->user_login.'?secret='.Base32::encode($tfa_priv_key).'&issuer='.$url.'&counter='.$tfa->getUserCounter($user->ID);
1368 777
778 +// $ret = '<script>var qr_details = "'.$encode.'"</script>';
779 +
1369 780 return $encode;
1370 781 }
1371 782
1372 - /**
1373 - * Return or output view content
1374 - *
1375 - * @param String $path - path to template, usually relative to templates/ within the plugin directory
1376 - * @param Array $extract_these - key/value pairs for substitution into the scope of the template
1377 - * @param Boolean $return_instead_of_echo - what to do with the results
1378 - *
1379 - * @return String|Void
1380 - */
1381 - public function include_template($path, $extract_these = array(), $return_instead_of_echo = false) {
1382 -
1383 - if ($return_instead_of_echo) ob_start();
1384 -
1385 - $template_file = apply_filters('simatfa_template_file', SIMBA_TFA_PLUGIN_DIR.'/templates/'.$path, $path, $extract_these, $return_instead_of_echo);
1386 -
1387 - do_action('simbatfa_before_template', $path, $return_instead_of_echo, $extract_these, $template_file);
1388 -
1389 - if (!file_exists($template_file)) {
1390 - error_log("TFA: template not found: $template_file (from $path)");
1391 - echo __('Error:', 'two-factor-authentication').' '.__('two-factor-authentication', 'wp-optimize')." (".$path.")";
1392 - } else {
1393 - extract($extract_these);
1394 - // The following are useful variables which can be used in the template.
1395 - // They appear as unused, but may be used in the $template_file.
1396 - $wpdb = $GLOBALS['wpdb'];// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wpdb might be used in the included template
1397 - $simba_tfa = $this;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wp_optimize might be used in the included template
1398 - include $template_file;
1399 - }
1400 -
1401 - do_action('simbatfa_after_template', $path, $return_instead_of_echo, $extract_these, $template_file);
1402 -
1403 - if ($return_instead_of_echo) return ob_get_clean();
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
1404 792 }
1405 -
1406 - /**
1407 - * Run upon the WP plugins_loaded action
1408 - */
793 +
1409 794 public function plugins_loaded() {
1410 795 load_plugin_textdomain(
1411 - 'two-factor-authentication',
796 + SIMBA_TFA_TEXT_DOMAIN,
1412 797 false,
1413 - dirname(plugin_basename(__FILE__)).'/languages/'
798 + dirname( plugin_basename( __FILE__ ) ) . '/languages/'
1414 799 );
1415 800 }
1416 801
1417 - /**
1418 - * Make sure that self::$frontend is the instance of TFA_Frontend, and return it
1419 - *
1420 - * @return TFA_Frontend
1421 - */
1422 802 public function load_frontend() {
1423 803 if (!class_exists('TFA_Frontend')) require_once(SIMBA_TFA_PLUGIN_DIR.'/includes/tfa_frontend.php');
1424 804 if (empty($this->frontend)) $this->frontend = new TFA_Frontend($this);
1425 805 return $this->frontend;
@@ -1424,12 +804,33 @@
1424 804 if (empty($this->frontend)) $this->frontend = new TFA_Frontend($this);
1425 805 return $this->frontend;
1426 806 }
1427 807
1428 - // __return_empty_string() does not exist until WP 3.7
1429 808 public function shortcode_when_not_logged_in() {
1430 809 return '';
1431 810 }
1432 -
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 +
1433 834 }
1434 835
1435 -$GLOBALS['simba_two_factor_authentication'] = new Simba_Two_Factor_Authentication();
836 +$simba_two_factor_authentication = new Simba_Two_Factor_Authentication();