ite_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;
}
}