>ID);
if ($is_activated_for_user && $is_activated_by_user) {
// No private key yet, generate one. This shouldn't really be possible.
if (!$tfa_priv_key) $tfa_priv_key = $totp_controller->addPrivateKey($user->ID);
$code = $totp_controller->generateOTP($user->ID, $tfa_priv_key);
$result = true;
}
}
if ('array' != $response_format) return $result;
$ret = array('result' => $result);
if ($result) {
$ret['user_can_trust'] = $this->user_can_trust($user->ID);
if (!empty($params['trust_token']) && $this->user_trust_token_valid($user->ID, $params['trust_token'])) {
$ret['user_already_trusted'] = 1;
}
}
return $ret;
}
/**
* Print the radio buttons for enabling/disabling TFA
*
* @param Integer $user_id - the WordPress user ID
* @param Boolean $long_label - whether to use a long label rather than a short one
* @param String $style - valid values are "show_current" and "require_current"
*/
public function paint_enable_tfa_radios($user_id, $long_label = false, $style = 'show_current') {
if (!$user_id) return;
if ('require_current' != $style) $style = 'show_current';
$is_required = $this->is_required_for_user($user_id);
$is_activated = $this->is_activated_by_user($user_id);
if ($is_required) {
$require_after = absint($this->get_option('tfa_requireafter'));
echo '
'.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', 'all-in-one-wp-security-and-firewall'), $require_after).'
';
}
$tfa_enabled_label = $long_label ? __('Enable two-factor authentication', 'all-in-one-wp-security-and-firewall') : __('Enabled', 'all-in-one-wp-security-and-firewall');
if ('show_current' == $style) {
$tfa_enabled_label .= ' '.sprintf(__('(Current code: %s)', 'all-in-one-wp-security-and-firewall'), $this->get_controller('totp')->current_otp_code($user_id));
} elseif ('require_current' == $style) {
$tfa_enabled_label .= ' '.sprintf(__('(you must enter the current code: %s)', 'all-in-one-wp-security-and-firewall'), '');
}
$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;
$tfa_disabled_label = $long_label ? __('Disable two-factor authentication', 'all-in-one-wp-security-and-firewall') : __('Disabled', 'all-in-one-wp-security-and-firewall');
if ('require_current' == $style) echo ''."\n";
echo ' ';
// 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
// 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.
if ($show_disable) {
echo ' ';
}
}
/**
* Retrieve a saved option
*
* @param String $key - option key
*
* @return Mixed
*/
public function get_option($key) {
if (!is_multisite()) return get_option($key);
$main_site_id = function_exists('get_main_site_id') ? get_main_site_id() : 1;
$get_option_site_id = apply_filters('simba_tfa_get_option_site_id', $main_site_id);
switch_to_blog($get_option_site_id);
$value = get_option($key);
restore_current_blog();
return $value;
}
/**
* Updates an option.
*
* @param String $key - option key
* @param Mixed $value - option value
*
* @return Boolean
*/
public function update_option($key, $value) {
if (!is_multisite()) return update_option($key, $value);
$main_site_id = function_exists('get_main_site_id') ? get_main_site_id() : 1;
$update_option_site_id = apply_filters('simba_tfa_update_option_site_id', $main_site_id);
switch_to_blog($update_option_site_id);
$result = update_option($key, $value);
restore_current_blog();
return $result;
}
/**
* Deletes an option.
*
* @param String $key - option key
*
* @return Boolean
*/
public function delete_option($key) {
if (!is_multisite()) return delete_option($key);
$main_site_id = function_exists('get_main_site_id') ? get_main_site_id() : 1;
$delete_option_site_id = apply_filters('simba_tfa_delete_option_site_id', $main_site_id);
switch_to_blog($delete_option_site_id);
$result = delete_option($key);
restore_current_blog();
return $result;
}
/**
* Paint a list of checkboxes, one for each role
*
* @param String $prefix
* @param Integer $default - default value (0 or 1)
*/
public function list_user_roles_checkboxes($prefix = '', $default = 1) {
if (is_multisite()) {
// Not a real WP role; needs separate handling
$id = '_super_admin';
$name = __('Multisite Super Admin', 'all-in-one-wp-security-and-firewall');
$setting = $this->get_option('tfa_'.$prefix.$id);
$setting = ($setting === false) ? $default : ($setting ? 1 : 0);
echo ' \n";
}
global $wp_roles;
if (!isset($wp_roles)) $wp_roles = new WP_Roles();
foreach ($wp_roles->role_names as $id => $name) {
$setting = $this->get_option('tfa_'.$prefix.$id);
$setting = ($setting === false) ? $default : ($setting ? 1 : 0);
echo ' \n";
}
}
public function tfa_list_xmlrpc_status_radios() {
$setting = $this->get_option('tfa_xmlrpc_on');
$setting = $setting ? 1 : 0;
$types = array(
'0' => __('Do not require 2FA over XMLRPC (best option if you must use XMLRPC and your client does not support 2FA)', 'all-in-one-wp-security-and-firewall'),
'1' => __('Do require 2FA over XMLRPC (best option if you do not use XMLRPC or are unsure)', 'all-in-one-wp-security-and-firewall')
);
foreach($types as $id => $name) {
print ' \n";
}
}
protected function is_caller_active() {
if (!defined('XMLRPC_REQUEST') || !XMLRPC_REQUEST) return true;
$saved_data = $this->get_option('tfa_xmlrpc_on');
return $saved_data ? true : false;
}
/**
* @param Array $params
* @param Boolean $may_be_email
*
* @return WP_Error|Boolean|Integer - WP_Error or false means failure; true or 1 means success, but true means the TFA code was validated
*/
public function authorise_user_from_login($params, $may_be_email = false) {
$params = apply_filters('simbatfa_auth_user_from_login_params', $params);
global $wpdb;
if (!$this->is_caller_active()) return 1;
$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']);
$response = $wpdb->get_row($query);
if (!$response && $may_be_email && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) {
// Corner-case: login looks like an email, but is a username rather than email address
$response = $wpdb->get_row($wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_login=%s", $params['log']));
}
$user_id = is_object($response) ? $response->ID : false;
$user_registered = is_object($response) ? $response->user_registered : false;
$user_code = isset($params['two_factor_code']) ? str_replace(' ', '', trim($params['two_factor_code'])) : '';
// This condition in theory should not be possible
if (!$user_id) return new WP_Error('tfa_user_not_found', apply_filters('simbatfa_tfa_user_not_found', ''.__('Error:', 'all-in-one-wp-security-and-firewall').' '.__('The indicated user could not be found.', 'all-in-one-wp-security-and-firewall')));
if (!$this->is_activated_for_user($user_id)) return 1;
if (!empty($params['trust_token']) && $this->user_trust_token_valid($user_id, $params['trust_token'])) {
return 1;
}
if (!$this->is_activated_by_user($user_id)) {
if (!$this->is_required_for_user($user_id)) return 1;
$enforce_require_after_check = true;
$require_enforce_after = $this->get_option('tfa_require_enforce_after');
// Don't enforce if the setting has never been saved
if (is_string($require_enforce_after) && preg_match('#^(\d+)-(\d+)-(\d+)$#', $require_enforce_after, $enforce_matches)) {
// wp_date() is WP 5.3+, but performs translation into the site locale
$current_date = function_exists('wp_date') ? wp_date('Y-m-d') : get_date_from_gmt(gmdate('Y-m-d H:i:s'), 'Y-m-d');
if (preg_match('#^(\d+)-(\d+)-(\d+)$#', $current_date, $current_date_matches)) {
if ($current_date_matches[0] < $enforce_matches[0] || ($current_date_matches[0] == $enforce_matches[0] && ($current_date_matches[1] < $enforce_matches[1] || ($current_date_matches[1] == $enforce_matches[1] && $current_date_matches[2] < $enforce_matches[2])))) {
// Enforcement not yet begun; skip
$enforce_require_after_check = false;
}
}
}
$require_after = absint($this->get_option('tfa_requireafter')) * 86400;
$account_age = time() - strtotime($user_registered);
if ($account_age > $require_after && apply_filters('simbatfa_enforce_require_after_check', $enforce_require_after_check, $user_id, $require_after, $account_age)) {
return new WP_Error('tfa_required', apply_filters('simbatfa_notfa_forbidden_login', ''.__('Error:', 'all-in-one-wp-security-and-firewall').' '.__('The site owner has forbidden you to login without two-factor authentication. Please contact the site owner to re-gain access.', 'all-in-one-wp-security-and-firewall')));
}
return 1;
}
$tfa_creds_user_id = !empty($params['creds_user_id']) ? $params['creds_user_id'] : $user_id;
if ($tfa_creds_user_id != $user_id) {
// Authenticating using a different user's credentials (e.g. https://wordpress.org/plugins/use-administrator-password/)
// In this case, we require that different user to have TFA active - so that this mechanism can't be used to avoid TFA
if (!$this->is_activated_for_user($tfa_creds_user_id) || !$this->is_activated_by_user($tfa_creds_user_id)) {
return new WP_Error('tfa_required', apply_filters('simbatfa_notfa_forbidden_login_altuser', ''.__('Error:', 'all-in-one-wp-security-and-firewall').' '.__('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.', 'all-in-one-wp-security-and-firewall')));
}
}
return $this->get_controller('totp')->check_code_for_user($tfa_creds_user_id, $user_code);
}
/**
* Evaluate whether a trust token is valid for a user
*
* @param Integer $user_id - WP user ID
* @param String $trust_token - trust token
*
* @return Boolean
*/
protected function user_trust_token_valid($user_id, $trust_token) {
if (!is_string($trust_token) || strlen($trust_token) < 30) return false;
$trusted_devices = $this->user_get_trusted_devices($user_id);
$time_now = time();
foreach ($trusted_devices as $device) {
if (empty($device['until']) || $device['until'] <= $time_now) continue;
if (!empty($device['token']) && $device['token'] === $trust_token) {
return true;
}
}
return false;
}
/**
* 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.
*
* @return String
*/
protected function get_ajax_url() {
$ajax_url = admin_url('admin-ajax.php');
$parsed_url = parse_url($ajax_url);
if (strtolower($parsed_url['host']) !== strtolower($_SERVER['HTTP_HOST']) && !empty($parsed_url['path'])) {
// Mismatch - return the relative URL only
$ajax_url = $parsed_url['path'];
}
return $ajax_url;
}
/**
* 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.
*/
public function login_enqueue_scripts() {
if (!$this->should_enqueue_login_scripts()) {
return;
}
if (isset($_GET['action']) && 'logout ' != $_GET['action'] && 'login' != $_GET['action']) return;
static $already_done = false;
if ($already_done) return;
$already_done = true;
// Prevent caching when in debug mode
$script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : filemtime($this->includes_dir().'/tfa.js');
wp_enqueue_script('tfa-ajax-request', $this->includes_url().'/tfa.js', array('jquery'), $script_ver);
$trusted_for = $this->get_option('tfa_trusted_for');
$trusted_for = (false === $trusted_for) ? 30 : (string) absint($trusted_for);
$localize = array(
'ajaxurl' => $this->get_ajax_url(),
'click_to_enter_otp' => __("Click to enter One Time Password", 'all-in-one-wp-security-and-firewall'),
'enter_username_first' => __('You have to enter a username first.', 'all-in-one-wp-security-and-firewall'),
'otp' => __('One Time Password (i.e. 2FA)', 'all-in-one-wp-security-and-firewall'),
'otp_login_help' => __('(check your OTP app to get this password)', 'all-in-one-wp-security-and-firewall'),
'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, 'all-in-one-wp-security-and-firewall'), $trusted_for),
'is_trusted' => __('(Trusted device - no OTP code required)', 'all-in-one-wp-security-and-firewall'),
'nonce' => wp_create_nonce('simba_tfa_loginform_nonce'),
'login_form_selectors' => '',
'login_form_off_selectors' => '',
'error' => __('An error has occurred. Site owners can check the JavaScript console for more details.', 'all-in-one-wp-security-and-firewall'),
);
// Spinner exists since WC 3.8. Use the proper functions to avoid SSL warnings.
if (file_exists(ABSPATH.'wp-admin/images/spinner-2x.gif')) {
$localize['spinnerimg'] = admin_url('images/spinner-2x.gif');
} elseif (file_exists(ABSPATH.WPINC.'/images/spinner-2x.gif')) {
$localize['spinnerimg'] = includes_url('images/spinner-2x.gif');
}
$localize = apply_filters('simba_tfa_login_enqueue_localize', $localize);
wp_localize_script('tfa-ajax-request', 'simba_tfasettings', $localize);
}
/**
* Check whether TFA login scripts should be enqueued or not.
*
* @return boolean True if the TFA login script should be enqueued, otherwise false.
*/
private function should_enqueue_login_scripts() {
if (defined('TWO_FACTOR_DISABLE') && TWO_FACTOR_DISABLE) {
return apply_filters('simbatfa_enqueue_login_scripts', false);
}
global $wpdb;
$sql = $wpdb->prepare('SELECT COUNT(user_id) FROM ' . $wpdb->usermeta . ' WHERE meta_key = %s AND meta_value = %d LIMIT 1', 'tfa_enable_tfa', 1);
$count_user_id = $wpdb->get_var($sql);
if (is_null($count_user_id)) { // Error in query.
return apply_filters('simbatfa_enqueue_login_scripts', true);
} elseif ($count_user_id > 0) { // A user exists with TFA enabled.
return apply_filters('simbatfa_enqueue_login_scripts', true);
}
// No user exists with TFA enabled.
return apply_filters('simbatfa_enqueue_login_scripts', false);
}
/**
* Return or output view content
*
* @param String $path - path to template, usually relative to templates/ within the plugin directory
* @param Array $extract_these - key/value pairs for substitution into the scope of the template
* @param Boolean $return_instead_of_echo - what to do with the results
*
* @return String|Void
*/
public function include_template($path, $extract_these = array(), $return_instead_of_echo = false) {
if ($return_instead_of_echo) ob_start();
$template_file = apply_filters('simatfa_template_file', $this->templates_dir().'/'.$path, $path, $extract_these, $return_instead_of_echo);
do_action('simbatfa_before_template', $path, $return_instead_of_echo, $extract_these, $template_file);
if (!file_exists($template_file)) {
error_log("TFA: template not found: $template_file (from $path)");
echo __('Error:', 'all-in-one-wp-security-and-firewall').' '.__('Template path not found:', 'all-in-one-wp-security-and-firewall')." (".htmlspecialchars($path).")";
} else {
extract($extract_these);
// The following are useful variables which can be used in the template.
// They appear as unused, but may be used in the $template_file.
$wpdb = $GLOBALS['wpdb'];// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wpdb might be used in the included template
$simba_tfa = $this;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wp_optimize might be used in the included template
$totp_controller = $this->get_controller('totp');// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wp_optimize might be used in the included template
include $template_file;
}
do_action('simbatfa_after_template', $path, $return_instead_of_echo, $extract_these, $template_file);
if ($return_instead_of_echo) return ob_get_clean();
}
/**
* Make sure that self::$frontend is the instance of Simba_TFA_Frontend, and return it
*
* @return Simba_TFA_Frontend
*/
public function load_frontend() {
if (!class_exists('Simba_TFA_Frontend')) require_once($this->includes_dir().'/tfa_frontend.php');
if (empty($this->frontend)) $this->frontend = new Simba_TFA_Frontend($this);
return $this->frontend;
}
// __return_empty_string() does not exist until WP 3.7
public function shortcode_when_not_logged_in() {
return '';
}
/**
* Set authentication slug.
*
* @param String $authentication_slug - Authentication slug. Verify that two-factor authentication should not be repeated for the same slug.
*/
public function set_authentication_slug($authentication_slug) {
$this->authentication_slug = $authentication_slug;
}
}