1023 lines
44 KiB
PHP
Executable File
1023 lines
44 KiB
PHP
Executable File
<?php
|
|
if (!defined('ABSPATH')) {
|
|
exit;//Exit if accessed directly
|
|
}
|
|
|
|
class AIOWPSecurity_User_Login {
|
|
|
|
public $key_login_msg;// This will store a URI query string key for passing messages to the login form
|
|
|
|
public function __construct() {
|
|
global $aio_wp_security;
|
|
$this->key_login_msg = 'aiowps_login_msg_id';
|
|
// As a first authentication step, check if user's IP is locked.
|
|
add_filter('authenticate', array($this, 'block_ip_if_locked'), 1, 1);
|
|
// Check whether user needs to be manually approved after default WordPress authenticate hooks (with priority 20).
|
|
add_filter('authenticate', array($this, 'check_manual_registration_approval'), 30, 1);
|
|
// Check login CAPTCHA
|
|
if ($aio_wp_security->configs->get_value('aiowps_enable_login_captcha')) {
|
|
add_filter('authenticate', array($this, 'check_captcha'), 20, 1);
|
|
}
|
|
// As a last authentication step, perform post authentication steps
|
|
add_filter('authenticate', array($this, 'post_authenticate'), 100, 3);
|
|
add_action('aiowps_force_logout_check', array($this, 'aiowps_force_logout_action_handler'));
|
|
add_action('wp_logout', array($this, 'wp_logout_action_handler'), 10, 1);
|
|
add_filter('login_message', array($this, 'aiowps_login_message')); //WP filter to add or modify messages on the login page
|
|
|
|
// Display disable lockdown message
|
|
if (is_admin() && AIOWPSecurity_Utility_Permissions::has_manage_cap() && $aio_wp_security->is_login_lockdown_by_const() && $this->is_admin_page_to_display_disable_login_lockdown_by_const_notice()) {
|
|
add_action('all_admin_notices', array($this, 'disable_login_lockdown_by_const_notice'));
|
|
}
|
|
|
|
add_action('set_auth_cookie', array($this, 'handle_logged_in_user'), 10, 4);
|
|
|
|
//cron job to remove expired users from logged_in table
|
|
add_action('delete_expired_logged_in_users_event', array($this, 'delete_expired_logged_in_users'));
|
|
|
|
add_filter('retrieve_password_message', array($this, 'aiowps_retrieve_password_message'), 10, 1);
|
|
}
|
|
|
|
/**
|
|
* Check whether the admin page is to display disable login lockdown by const notice.
|
|
*
|
|
* @return boolean True if the notice will be displayed, Otherwise false.
|
|
*/
|
|
private function is_admin_page_to_display_disable_login_lockdown_by_const_notice() {
|
|
global $pagenow;
|
|
if (in_array($pagenow, array('index.php', 'plugins.php'))) {
|
|
return true;
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. Ignore.
|
|
} elseif (('admin.php' == $pagenow && isset($_GET['page']) && false !== strpos(sanitize_title(wp_unslash($_GET['page'])), AIOWPSEC_MENU_SLUG_PREFIX)) && !$this->is_locked_ip_addresses_tab_admin_page()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check whether the admin page is Locked IP Addresses Tab page.
|
|
*
|
|
* @return boolean True if is Locked IP Addresses Tab page, Otherwise false.
|
|
*/
|
|
private function is_locked_ip_addresses_tab_admin_page() {
|
|
global $pagenow;
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- PCP warning. No nonce.
|
|
return ('admin.php' == $pagenow && isset($_GET['page']) && 'aiowpsec' == $_GET['page'] && isset($_GET['tab']) && 'locked-ip' == $_GET['tab']);
|
|
}
|
|
|
|
/**
|
|
* Displays admin to disable lockdown message.
|
|
*
|
|
* @return Void
|
|
*/
|
|
public function disable_login_lockdown_by_const_notice() {
|
|
|
|
if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
|
|
return;
|
|
}
|
|
|
|
echo '<div class="notice notice-error">
|
|
<p>'.
|
|
esc_html__('You have disabled login lockout by defining the AIOS_DISABLE_LOGIN_LOCKOUT constant value as true, and the login lockout setting has enabled it.', 'all-in-one-wp-security-and-firewall') . ' ' .
|
|
/* translators: 1: Locked IP Addresses admin page link */
|
|
sprintf(esc_html__('Delete your login lockout IP from %s and define the AIOS_DISABLE_LOGIN_LOCKOUT constant value as false.', 'all-in-one-wp-security-and-firewall'),
|
|
'<a href="' . esc_url(admin_url('admin.php?page=aiowpsec&tab=locked-ip')) . '">' . esc_html__('Locked IP addresses', 'all-in-one-wp-security-and-firewall') . '</a>'
|
|
).
|
|
'</p>
|
|
</div>';
|
|
}
|
|
|
|
/**
|
|
* Terminate the execution via wp_die with 503 status code, if current
|
|
* user's IP is currently locked.
|
|
*
|
|
* @global AIO_WP_Security $aio_wp_security
|
|
* @param WP_Error|WP_User $user
|
|
* @return WP_User
|
|
*/
|
|
public function block_ip_if_locked($user) {
|
|
global $aio_wp_security;
|
|
|
|
// Allow users to login when disable AIOWPS_DISABLE_LOCK_DOWN defined true
|
|
if ($aio_wp_security->is_login_lockdown_by_const()) {
|
|
return $user;
|
|
}
|
|
|
|
$user_locked = $this->check_locked_user();
|
|
if (null != $user_locked) {
|
|
$aio_wp_security->debug_logger->log_debug("Login attempt from blocked IP range - ".$user_locked['failed_login_ip'], 2);
|
|
// Allow the error message to be filtered.
|
|
/* translators: %s: Error notification with strong HTML tag. */
|
|
$error_msg = apply_filters('aiowps_ip_blocked_error_msg', sprintf(__('%s: Access from your IP address has been blocked for security reasons.', 'all-in-one-wp-security-and-firewall'), '<strong>' . __('ERROR', 'all-in-one-wp-security-and-firewall') . '</strong>') . ' ' . __('Please contact the administrator.', 'all-in-one-wp-security-and-firewall'));
|
|
// If unlock requests are allowed, add the "Request Unlock" button to the message.
|
|
$unlock_form = '';
|
|
if ($aio_wp_security->configs->get_value('aiowps_allow_unlock_requests') == '1') {
|
|
$unlock_form = $this->get_unlock_request_form();
|
|
$error_msg .= $unlock_form;
|
|
}
|
|
$error_msg = apply_filters('aiowps_ip_blocked_output_page', $error_msg, $unlock_form); //filter the complete output of the locked page
|
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Can not escape form html inside $error_msg.
|
|
wp_die($error_msg, esc_html__('Service temporarily unavailable', 'all-in-one-wp-security-and-firewall'), 503);
|
|
} else {
|
|
return $user;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check login CAPTCHA (if enabled).
|
|
*
|
|
* @global AIO_WP_Security $aio_wp_security
|
|
* @param WP_Error|WP_User $user
|
|
* @return WP_Error|WP_User
|
|
*/
|
|
public function check_captcha($user) {
|
|
global $aio_wp_security;
|
|
if (is_wp_error($user) || $aio_wp_security->is_login_lockdown_by_const()) {
|
|
// Authentication has failed already at some earlier step.
|
|
return $user;
|
|
}
|
|
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- PCP warning. No nonce.
|
|
if (! (isset($_POST['log']) && isset($_POST['pwd']))) {
|
|
// XML-RPC authentication (not via wp-login.php), nothing to do here.
|
|
return $user;
|
|
}
|
|
|
|
if ($aio_wp_security->configs->get_value('aiowps_enable_login_captcha') != '1') {
|
|
// CAPTCHA not enabled, nothing to do here.
|
|
return $user;
|
|
}
|
|
|
|
/* translators: %s: Error notification with strong HTML tag. */
|
|
$captcha_error = new WP_Error('authentication_failed', sprintf(__('%s: Your answer was incorrect - please try again.', 'all-in-one-wp-security-and-firewall'), '<strong>' . __('ERROR', 'all-in-one-wp-security-and-firewall') . '</strong>'));
|
|
$verify_captcha = $aio_wp_security->captcha_obj->verify_captcha_submit();
|
|
if (false === $verify_captcha) {
|
|
return $captcha_error;
|
|
}
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Check, whether $user needs to be manually approved by site admin yet.
|
|
*
|
|
* @global AIO_WP_Security $aio_wp_security
|
|
* @param WP_Error|WP_User $user
|
|
* @return WP_Error|WP_User
|
|
*/
|
|
public function check_manual_registration_approval($user) {
|
|
global $aio_wp_security;
|
|
if (!($user instanceof WP_User)) {
|
|
// Not a WP_User - nothing to do here.
|
|
return $user;
|
|
}
|
|
//Check if auto pending new account status feature is enabled
|
|
if ($aio_wp_security->configs->get_value('aiowps_enable_manual_registration_approval') == '1') {
|
|
$aiowps_account_status = get_user_meta($user->ID, 'aiowps_account_status', true);
|
|
if ('pending' == $aiowps_account_status) {
|
|
// Account needs to be activated yet
|
|
/* translators: %s: Notification with strong HTML tag. */
|
|
return new WP_Error('account_pending', sprintf(__('%s: Your account is currently not active.', 'all-in-one-wp-security-and-firewall'), '<strong>' . __('ACCOUNT PENDING', 'all-in-one-wp-security-and-firewall') . '</strong>') . ' '. __('An administrator needs to activate your account before you can login.', 'all-in-one-wp-security-and-firewall'));
|
|
}
|
|
}
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Handle post authentication steps (in case of failed login):
|
|
* - increment number of failed logins for $username
|
|
* - (optionally) lock the user
|
|
* - (optionally) display a generic error message
|
|
*
|
|
* @global AIO_WP_Security $aio_wp_security
|
|
* @param WP_Error|WP_User $user
|
|
* @param string $username
|
|
* @param string $password
|
|
* @return WP_Error|WP_User
|
|
*/
|
|
public function post_authenticate($user, $username, $password) {
|
|
global $aio_wp_security;
|
|
if (!is_wp_error($user)) {
|
|
// Authentication has been successful, there's nothing to do here.
|
|
return $user;
|
|
}
|
|
if (empty($username) || empty($password)) {
|
|
// Neither log nor block login attempts with empty username or password.
|
|
return $user;
|
|
}
|
|
if ($user->get_error_code() === 'account_pending') {
|
|
// Neither log nor block users attempting to log in before their registration is approved.
|
|
return $user;
|
|
}
|
|
// Login failed for non-trivial reason
|
|
AIOWPSecurity_Audit_Events::event_failed_login($username);
|
|
if ($aio_wp_security->configs->get_value('aiowps_enable_login_lockdown') == '1') {
|
|
$is_whitelisted = false;
|
|
//check if lockout whitelist enabled
|
|
if ($aio_wp_security->configs->get_value('aiowps_lockdown_enable_whitelisting') == '1') {
|
|
$whitelisted_ips = $aio_wp_security->configs->get_value('aiowps_lockdown_allowed_ip_addresses');
|
|
$is_whitelisted = AIOWPSecurity_Utility_IP::is_userip_whitelisted($whitelisted_ips);
|
|
}
|
|
|
|
if (false === $is_whitelisted) {
|
|
// Too many failed logins from user's IP?
|
|
$login_attempts_permitted = absint($aio_wp_security->configs->get_value('aiowps_max_login_attempts'));
|
|
$too_many_failed_logins = $login_attempts_permitted <= $this->get_login_fail_count();
|
|
|
|
// Is an invalid username or email the reason for login error?
|
|
$invalid_username = ($user->get_error_code() === 'invalid_username' || $user->get_error_code() == 'invalid_email');
|
|
// Should an invalid username be immediately locked?
|
|
$invalid_username_lockdown = $aio_wp_security->configs->get_value('aiowps_enable_invalid_username_lockdown') == '1';
|
|
$lock_invalid_username = $invalid_username && $invalid_username_lockdown;
|
|
|
|
// Should an invalid username be blocked as per blacklist?
|
|
$instant_lockout_users_list = $aio_wp_security->configs->get_value('aiowps_instantly_lockout_specific_usernames');
|
|
if (!is_array($instant_lockout_users_list)) {
|
|
$instant_lockout_users_list = array();
|
|
}
|
|
$username_blacklisted = $invalid_username && in_array($username, $instant_lockout_users_list);
|
|
|
|
$lock_reasons = array();
|
|
if ($too_many_failed_logins) {
|
|
$lock_reasons[] = 'too_many_failed_logins';
|
|
}
|
|
if ($lock_invalid_username) {
|
|
$lock_reasons[] = 'invalid_username';
|
|
}
|
|
if ($username_blacklisted) {
|
|
$lock_reasons[] = 'username_blacklisted';
|
|
}
|
|
if ($lock_reasons) {
|
|
$this->lock_the_user($username, implode(',', $lock_reasons));
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($aio_wp_security->configs->get_value('aiowps_set_generic_login_msg') == '1') {
|
|
// Return generic error message if configured
|
|
return new WP_Error('authentication_failed', __('<strong>ERROR</strong>: Invalid login credentials.', 'all-in-one-wp-security-and-firewall'));
|
|
}
|
|
return $user;
|
|
}
|
|
/**
|
|
* This function queries the aiowps_login_lockdown table.
|
|
* If the release_date has not expired AND the current visitor IP addr matches
|
|
* it will return a record
|
|
*/
|
|
public function check_locked_user() {
|
|
global $wpdb;
|
|
$login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKOUT;
|
|
$ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user
|
|
if (empty($ip)) return false;
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
|
|
$locked_user = $wpdb->get_row($wpdb->prepare("SELECT * FROM $login_lockdown_table WHERE `released` > UNIX_TIMESTAMP() AND `failed_login_ip` = %s", $ip), ARRAY_A);
|
|
return $locked_user;
|
|
}
|
|
/**
|
|
* This function queries the aiowps_audit_log table and returns the number of failures for current IP range within allowed failure period
|
|
*/
|
|
public function get_login_fail_count() {
|
|
|
|
global $wpdb, $aio_wp_security;
|
|
|
|
$audit_log_table = AIOWPSEC_TBL_AUDIT_LOG;
|
|
$login_retry_interval = $aio_wp_security->configs->get_value('aiowps_retry_time_period') * 60;
|
|
$now = time();
|
|
$ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); // Get the users IP address
|
|
|
|
if (empty($ip)) return false;
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
|
|
$login_failures = $wpdb->get_var("SELECT COUNT(ID) FROM $audit_log_table " . "WHERE created + " . esc_sql($login_retry_interval) . " > '" . esc_sql($now) . "' AND " . "ip = '" . esc_sql($ip) . "' AND event_type = 'failed_login'");
|
|
return $login_failures;
|
|
}
|
|
|
|
/**
|
|
* Get lockout time dynamically multiplied with default lockout time
|
|
*
|
|
* @return Integer get lockout time length.
|
|
*/
|
|
public function get_dynamic_lockout_time_length() {
|
|
global $aio_wp_security;
|
|
|
|
$login_fail_count = $this->get_login_fail_count();
|
|
$lockout_time_default = $aio_wp_security->configs->get_value('aiowps_lockout_time_length');
|
|
if (!is_numeric($lockout_time_default)) {
|
|
$lockout_time_default = 5;
|
|
}
|
|
$lockout_time_max = $aio_wp_security->configs->get_value('aiowps_max_lockout_time_length');
|
|
if (!is_numeric($lockout_time_max)) {
|
|
$lockout_time_max = 60;
|
|
}
|
|
$lockout_time_length = (int) ($login_fail_count > 0 ? (3 * $lockout_time_default * ($login_fail_count + 1)) : $lockout_time_default);
|
|
|
|
return $lockout_time_length >= $lockout_time_max ? $lockout_time_max : $lockout_time_length;
|
|
}
|
|
|
|
/**
|
|
* Adds an entry to the `aiowps_login_lockdown` table.
|
|
*
|
|
* @param string $username User's username or email
|
|
* @param string $lock_reason
|
|
* @param bool $is_lockout_email_sent flag for lockout email send
|
|
*/
|
|
public function lock_the_user($username, $lock_reason = 'login_fail', $is_lockout_email_sent = 0) {
|
|
global $aio_wp_security;
|
|
$login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKOUT;
|
|
$lock_minutes = $this->get_dynamic_lockout_time_length();
|
|
$ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user
|
|
if (empty($ip)) return;
|
|
$ip_range = AIOWPSecurity_Utility_IP::get_sanitized_ip_range($ip); //Get the IP range of the current user
|
|
$user = is_email($username) ? get_user_by('email', $username) : get_user_by('login', $username); //Returns WP_User object if exists
|
|
$ip_range = apply_filters('aiowps_before_lockdown', $ip_range);
|
|
if ($user) {
|
|
//If the login attempt was made using a valid user set variables for DB storage later on
|
|
$user_id = $user->ID;
|
|
} else {
|
|
//If the login attempt was made using a non-existent user then let's set user_id to blank and record the attempted user login name for DB storage later on
|
|
$user_id = 0;
|
|
}
|
|
|
|
$lock_time = current_time('mysql', true);
|
|
$date = new DateTime($lock_time);
|
|
$add_interval = 'PT'.absint($lock_minutes).'M';
|
|
$date->add(new DateInterval($add_interval));
|
|
$release_time = $date->format('Y-m-d H:i:s');
|
|
$backtrace_log = '';
|
|
if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_php_backtrace_in_email')) {
|
|
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace, PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection -- PCP and compatibility warnings. Safe to ignore.
|
|
$backtrace_log = AIOWPSecurity_Utility::normalise_call_stack_args(debug_backtrace());
|
|
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- PCP warning. Ignore.
|
|
$backtrace_log = print_r($backtrace_log, true);
|
|
}
|
|
$is_lockout_email_sent = (1 == $aio_wp_security->configs->get_value('aiowps_enable_email_notify') ? 0 : -1);
|
|
$ip_lookup_result = AIOS_Helper::get_ip_reverse_lookup($ip);
|
|
$ip_lookup_result = wp_json_encode($ip_lookup_result);
|
|
if (false === $ip_lookup_result) $ip_lookup_result = null;
|
|
|
|
$lock_seconds = $lock_minutes * MINUTE_IN_SECONDS;
|
|
|
|
$data = array(
|
|
'user_id' => $user_id,
|
|
'user_login' => $username,
|
|
'lockdown_date' => $lock_time,
|
|
'release_date' => $release_time,
|
|
'failed_login_IP' => $ip,
|
|
'lock_reason' => $lock_reason,
|
|
'is_lockout_email_sent' => $is_lockout_email_sent,
|
|
'backtrace_log' => $backtrace_log,
|
|
'ip_lookup_result' => $ip_lookup_result,
|
|
'lock_seconds' => $lock_seconds
|
|
);
|
|
|
|
$result = AIOWPSecurity_Utility::add_lockout($data);
|
|
|
|
if (false === $result) {
|
|
$aio_wp_security->debug_logger->log_debug("Error inserting record into ".$login_lockdown_table, 4);
|
|
} else {
|
|
do_action('aiowps_lockdown_event', $ip_range, $username);
|
|
$aio_wp_security->debug_logger->log_debug("The following IP address range has been locked out for exceeding the maximum login attempts: ".$ip_range, 2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send IP Lock notification.
|
|
*
|
|
* @param Array $lockout_ips_list have username, ip_range, ip
|
|
* @param String $backtrace_filepath
|
|
*
|
|
* @return Boolean True if mail sent otherwise false.
|
|
*/
|
|
private function send_ip_lock_notification_email($lockout_ips_list = array(), $backtrace_filepath = '') {
|
|
global $aio_wp_security;
|
|
$send_mail = false;
|
|
if (0 != count($lockout_ips_list)) {
|
|
$email_notification_enabled = $aio_wp_security->configs->get_value('aiowps_enable_email_notify');
|
|
if (1 == $email_notification_enabled) {
|
|
$to_email_address = AIOWPSecurity_Utility::get_array_from_textarea_val($aio_wp_security->configs->get_value('aiowps_email_address'));
|
|
if (empty($to_email_address)) {
|
|
$to_email_address = array(get_site_option('admin_email'));
|
|
}
|
|
$subject = '['.get_option('home').'] '. __('Site Lockout Notification', 'all-in-one-wp-security-and-firewall');
|
|
$email_msg = __('User login lockout events had occurred due to too many failed login attempts or invalid username:', 'all-in-one-wp-security-and-firewall')."\n\n";
|
|
|
|
foreach ($lockout_ips_list as $lockout_ip) {
|
|
/* translators: %s: User name. */
|
|
$email_msg .= sprintf(__('Username: %s', 'all-in-one-wp-security-and-firewall'), $lockout_ip['username']) . "\n";
|
|
|
|
/* translators: %s: IP Address. */
|
|
$email_msg .= sprintf(__('IP address: %s', 'all-in-one-wp-security-and-firewall'), $lockout_ip['ip']) . "\n";
|
|
if ('' != $lockout_ip['ip_range']) {
|
|
/* translators: %s: IP Range. */
|
|
$email_msg .= sprintf(__('IP range: %s', 'all-in-one-wp-security-and-firewall'), $lockout_ip['ip_range']) . '.*' . "\n";
|
|
}
|
|
if (!empty($lockout_ip['ip_lookup_result'])) {
|
|
$ip_lookup_result = json_decode($lockout_ip['ip_lookup_result'], true);
|
|
|
|
$org = empty($ip_lookup_result['org']) ? __('Not Found', 'all-in-one-wp-security-and-firewall') : $ip_lookup_result['org'];
|
|
$as = empty($ip_lookup_result['as']) ? __('Not Found', 'all-in-one-wp-security-and-firewall') : $ip_lookup_result['as'];
|
|
|
|
/* translators: %s: Org. */
|
|
$email_msg .= sprintf(__('Org: %s', 'all-in-one-wp-security-and-firewall'), $org) . "\n";
|
|
/* translators: %s: AS. */
|
|
$email_msg .= sprintf(__('AS: %s', 'all-in-one-wp-security-and-firewall'), $as) . "\n";
|
|
|
|
$email_msg = apply_filters('aiowps_login_lockdown_email_message', $email_msg, $ip_lookup_result);
|
|
}
|
|
$email_msg .= "\n";
|
|
}
|
|
|
|
$email_msg .= __("Log into your site WordPress administration panel to see the duration of the lockout or to unlock the user.", 'all-in-one-wp-security-and-firewall') . "\n";
|
|
|
|
$email_header = '';
|
|
$send_mail = wp_mail($to_email_address, $subject, $email_msg, $email_header, $backtrace_filepath);
|
|
|
|
if (false === $send_mail) {
|
|
$ips_list = implode(', ', wp_list_pluck($lockout_ips_list, 'ip'));
|
|
$aio_wp_security->debug_logger->log_debug("Lockout notification email failed to send to " . implode(', ', $to_email_address) . " for IPs ".$ips_list, 4);
|
|
}
|
|
}
|
|
}
|
|
return $send_mail;
|
|
}
|
|
|
|
/**
|
|
* Generates and returns an unlock request link which will be used to send to the user.
|
|
*
|
|
* @global type $wpdb
|
|
* @global AIO_WP_Security $aio_wp_security
|
|
* @param type $ip_range
|
|
* @return string or false on failure
|
|
*/
|
|
public static function generate_unlock_request_link($ip_range) {
|
|
//Get the locked user row from locout table
|
|
global $wpdb, $aio_wp_security;
|
|
$unlock_link = '';
|
|
$lockout_table_name = AIOWPSEC_TBL_LOGIN_LOCKOUT;
|
|
$secret_rand_key = (md5(uniqid(wp_rand(), true)));
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP wanting. Ignore.
|
|
$res = $wpdb->query($wpdb->prepare("UPDATE $lockout_table_name SET unlock_key = %s WHERE released > UNIX_TIMESTAMP() AND failed_login_ip LIKE %s", $secret_rand_key, "%" . esc_sql($ip_range) . "%"));
|
|
if (null == $res) {
|
|
$aio_wp_security->debug_logger->log_debug("No locked user found with IP range ".$ip_range, 4);
|
|
return false;
|
|
} else {
|
|
// Check if unlock request or submitted from a WooCommerce account login page
|
|
if (isset($_POST['aiowps-woo-login'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- PCP warning. No nonce.
|
|
$date_time = current_time('mysql');
|
|
$data = array('date_time' => $date_time, 'meta_key1' => 'woo_unlock_request_key', 'meta_value1' => $secret_rand_key);
|
|
$aiowps_global_meta_tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA;
|
|
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder, WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2.
|
|
$sql = $wpdb->prepare("INSERT INTO ".$aiowps_global_meta_tbl_name." (date_time, meta_key1, meta_value1, created) VALUES ('%s', '%s', '%s', UNIX_TIMESTAMP())", $data['date_time'], $data['meta_key1'], $data['meta_value1']);
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above.
|
|
$result = $wpdb->query($sql);
|
|
if (false === $result) {
|
|
$aio_wp_security->debug_logger->log_debug("generate_unlock_request_link() - Error inserting woo_unlock_request_key to AIOWPSEC_TBL_GLOBAL_META_DATA table for secret key ".$secret_rand_key, 4);
|
|
}
|
|
}
|
|
$query_param = array('aiowps_auth_key' => $secret_rand_key);
|
|
$wp_site_url = AIOWPSEC_WP_URL;
|
|
$unlock_link = esc_url(add_query_arg($query_param, $wp_site_url));
|
|
}
|
|
return $unlock_link;
|
|
}
|
|
|
|
/**
|
|
* This function will process an unlock request when someone clicks on the special URL
|
|
* It will check if the special random code matches that in lockdown table for the relevant user
|
|
* If so, it will unlock the user
|
|
*
|
|
* @param string $unlock_key
|
|
* @return void
|
|
*/
|
|
public static function process_unlock_request($unlock_key) {
|
|
global $wpdb, $aio_wp_security;
|
|
$lockout_table_name = AIOWPSEC_TBL_LOGIN_LOCKOUT;
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2.
|
|
$unlock_command = $wpdb->prepare("UPDATE ".$lockout_table_name." SET released = UNIX_TIMESTAMP() WHERE unlock_key = %s", $unlock_key);
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above.
|
|
$result = $wpdb->query($unlock_command);
|
|
if (false === $result) {
|
|
$aio_wp_security->debug_logger->log_debug("Error unlocking user with unlock_key ".$unlock_key, 4);
|
|
} else {
|
|
// Now check if this unlock operation is for a WooCommerce login
|
|
$aiowps_global_meta_tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA;
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2.
|
|
$sql = $wpdb->prepare("SELECT * FROM $aiowps_global_meta_tbl_name WHERE meta_key1=%s AND meta_value1=%s", 'woo_unlock_request_key', $unlock_key);
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above.
|
|
$woo_result = $wpdb->get_row($sql, OBJECT);
|
|
if (empty($woo_result)) {
|
|
$woo_unlock = false;
|
|
} else {
|
|
$woo_unlock = true;
|
|
}
|
|
if ($aio_wp_security->configs->get_value('aiowps_enable_rename_login_page')=='1') {
|
|
if (get_option('permalink_structure')) {
|
|
$home_url = trailingslashit(home_url());
|
|
} else {
|
|
$home_url = trailingslashit(home_url()) . '?';
|
|
}
|
|
if ($woo_unlock) {
|
|
$login_url = wc_get_page_permalink('myaccount'); //redirect to woo login page if applicable
|
|
//Now let's cleanup after ourselves and delete the woo-related row in the AIOWPSEC_TBL_GLOBAL_META_DATA table
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore
|
|
$delete = $wpdb->delete($aiowps_global_meta_tbl_name, array('meta_key1' => 'woo_unlock_request_key', 'meta_value1' => $unlock_key));
|
|
if (false === $delete) {
|
|
$aio_wp_security->debug_logger->log_debug("process_unlock_request(): Error deleting row from AIOWPSEC_TBL_GLOBAL_META_DATA for meta_key1=woo_unlock_request_key and meta_value1=".$unlock_key, 4);
|
|
}
|
|
} else {
|
|
$login_url = $home_url.$aio_wp_security->configs->get_value('aiowps_login_page_slug');
|
|
}
|
|
|
|
AIOWPSecurity_Utility::redirect_to_url($login_url);
|
|
} else {
|
|
AIOWPSecurity_Utility::redirect_to_url(wp_login_url());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function sends an unlock request email to a locked out user
|
|
*
|
|
* @param string $email
|
|
* @param string $unlock_link
|
|
* @return void
|
|
*/
|
|
public static function send_unlock_request_email($email, $unlock_link) {
|
|
global $aio_wp_security;
|
|
$subject = '['.network_site_url().'] '. __('Unlock request notification', 'all-in-one-wp-security-and-firewall');
|
|
/* translators: 1: Email 2: Link */
|
|
$email_msg = sprintf(__('You have requested for the account with email address %s to be unlocked.', 'all-in-one-wp-security-and-firewall') . ' ' . __('Please press the link below to unlock your account:', 'all-in-one-wp-security-and-firewall'), $email) . "\n" . sprintf(__('Unlock link: %s', 'all-in-one-wp-security-and-firewall'), $unlock_link) . "\n\n" . __('After pressing the above link you will be able to login to the WordPress administration panel.', 'all-in-one-wp-security-and-firewall') . "\n";
|
|
|
|
$sendMail = wp_mail($email, $subject, $email_msg);
|
|
if (false === $sendMail) {
|
|
$aio_wp_security->debug_logger->log_debug("Unlock Request Notification email failed to send to " . $email, 4);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check the settings and log the user after the configured time period
|
|
*
|
|
* @param bool $return_url Optional. If true, the function returns the logout URL with a nonce.
|
|
* Otherwise, it redirects to the logout URL. Default is false.
|
|
*
|
|
* @return void|string
|
|
*/
|
|
public function aiowps_force_logout_action_handler($return_url = false) {
|
|
global $aio_wp_security;
|
|
//$aio_wp_security->debug_logger->log_debug("Force Logout - Checking if any user need to be logged out...");
|
|
//if this feature is enabled then do something
|
|
if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_forced_logout')) {
|
|
if (is_user_logged_in()) {
|
|
$current_user = wp_get_current_user();
|
|
$user_id = $current_user->ID;
|
|
$current_time = current_time('mysql', true);
|
|
$login_time = $this->get_wp_user_aiowps_last_login_time($user_id);
|
|
if (empty($login_time)) {
|
|
return;
|
|
}
|
|
$diff = strtotime($current_time) - strtotime($login_time);
|
|
$logout_time_interval_value = $aio_wp_security->configs->get_value('aiowps_logout_time_period');
|
|
$logout_time_interval_val_seconds = $logout_time_interval_value * 60;
|
|
if ($diff > $logout_time_interval_val_seconds) {
|
|
$aio_wp_security->debug_logger->log_debug("Force Logout - This user logged in more than (".$logout_time_interval_value.") minutes ago. Doing a force log out for the user with username: ".$current_user->user_login);
|
|
$this->wp_logout_action_handler($user_id); //this will register the logout time/date in the logout_date column
|
|
|
|
|
|
$curr_page_url = AIOWPSecurity_Utility::get_current_page_url();
|
|
$after_logout_payload = array('redirect_to' => $curr_page_url, 'msg' => $this->key_login_msg.'=session_expired');
|
|
//Save some of the logout redirect data to a transient
|
|
is_multisite() ? set_site_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60) : set_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60);
|
|
$logout_url = AIOWPSEC_WP_URL.'?aiowpsec_do_log_out=1';
|
|
$logout_url = AIOWPSecurity_Utility::add_query_data_to_url($logout_url, 'al_additional_data', '1');
|
|
$logout_url_with_nonce = html_entity_decode(wp_nonce_url($logout_url, 'aio_logout'));
|
|
if ($return_url) {
|
|
return $logout_url_with_nonce;
|
|
}
|
|
AIOWPSecurity_Utility::redirect_to_url($logout_url_with_nonce);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get last logged in time of given user id.
|
|
*
|
|
* @param integer $user_id
|
|
* @return mixed Last login time. False for an invalid $user_id (non-numeric, zero, or negative value). An empty string if a valid but non-existing user ID is passed.
|
|
*/
|
|
public function get_wp_user_aiowps_last_login_time($user_id) {
|
|
$last_login = apply_filters('aiowps_get_last_login_time', get_user_meta($user_id, 'aiowps_last_login_time', true), $user_id);
|
|
return $last_login;
|
|
}
|
|
|
|
/**
|
|
* Updates the last login time in user meta, the login activity table.
|
|
*
|
|
* @global wpdb $wpdb
|
|
* @global AIO_WP_Security $aio_wp_security
|
|
*
|
|
* @param string $user_login
|
|
* @param WP_User $user
|
|
*
|
|
* @return void
|
|
*/
|
|
private static function update_login_activity($user_login, $user) {
|
|
AIOWPSecurity_Audit_Events::event_successful_login($user_login);
|
|
$login_date_time = current_time('mysql', true);
|
|
|
|
update_user_meta($user->ID, 'aiowps_last_login_time', $login_date_time); //store last login time in meta table
|
|
}
|
|
|
|
/**
|
|
* Remove the last login time for all users from meta table on deactivation.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function remove_login_activity() {
|
|
delete_metadata('user', '0', 'aiowps_last_login_time', '', true); //remove from meta table for all users last login time
|
|
}
|
|
|
|
public static function wp_login_action_handler($user_login, $user = '') {
|
|
global $aio_wp_security;
|
|
|
|
if ('' == $user) {
|
|
//Try and get user object
|
|
$user = get_user_by('login', $user_login); //This should return WP_User obj
|
|
if (!$user) {
|
|
$aio_wp_security->debug_logger->log_debug("AIOWPSecurity_User_Login::wp_login_action_handler: Unable to get WP_User object for login ".$user_login, 4);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (is_super_admin($user->ID)) {
|
|
$logging_into_correct_site = true;
|
|
} else {
|
|
$user_sites = get_blogs_of_user($user->ID);
|
|
|
|
$current_site_id = get_current_blog_id();
|
|
|
|
$logging_into_correct_site = false;
|
|
|
|
foreach ($user_sites as $site) {
|
|
if ($site->userblog_id == $current_site_id) {
|
|
$logging_into_correct_site = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($logging_into_correct_site) {
|
|
self::update_login_activity($user_login, $user);
|
|
} else {
|
|
$user_primary_site = get_active_blog_for_user($user->ID);
|
|
switch_to_blog($user_primary_site->blog_id);
|
|
self::update_login_activity($user_login, $user);
|
|
|
|
restore_current_blog();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles logout events and modifies the login activity record for the current user.
|
|
*
|
|
* @param int $user_id - ID of user logging out
|
|
* @param boolean $force_logout - if user is force logged out
|
|
*
|
|
* @return void
|
|
*/
|
|
public function wp_logout_action_handler($user_id, $force_logout = false) {
|
|
global $aio_wp_security;
|
|
$user = get_userdata($user_id);
|
|
|
|
if (false === $user) {
|
|
$aio_wp_security->debug_logger->log_debug("AIOWPSecurity_User_Login::wp_logout_action_handler: Unable to get WP_User object", 4);
|
|
return;
|
|
}
|
|
|
|
$this->delete_logged_in_user($user->ID);
|
|
|
|
if (is_super_admin($user->ID)) {
|
|
$logging_out_of_correct_site = true;
|
|
} else {
|
|
$user_sites = get_blogs_of_user($user->ID);
|
|
|
|
$current_site_id = get_current_blog_id();
|
|
|
|
$logging_out_of_correct_site = false;
|
|
|
|
foreach ($user_sites as $site) {
|
|
if ($site->userblog_id == $current_site_id) {
|
|
$logging_out_of_correct_site = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($logging_out_of_correct_site) {
|
|
AIOWPSecurity_Audit_Events::event_successful_logout($user->user_login, $force_logout);
|
|
} else {
|
|
$user_primary_site = get_active_blog_for_user($user->ID);
|
|
switch_to_blog($user_primary_site->blog_id);
|
|
AIOWPSecurity_Audit_Events::event_successful_logout($user->user_login, $force_logout);
|
|
|
|
restore_current_blog();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The handler for the WP "login_message" filter
|
|
* Adds custom messages to the other messages that appear above the login form.
|
|
*
|
|
* NOTE: This method is automatically called by WordPress for displaying
|
|
* text above the login form.
|
|
*
|
|
* @param string $message the output from earlier login_message filters
|
|
* @return string
|
|
*/
|
|
public function aiowps_login_message($message = '') {
|
|
global $aio_wp_security;
|
|
$msg = '';
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No nonce.
|
|
if (isset($_GET[$this->key_login_msg]) && !empty($_GET[$this->key_login_msg])) {
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No nonce.
|
|
$logout_msg = wp_strip_all_tags(sanitize_title(wp_unslash($_GET[$this->key_login_msg])));
|
|
}
|
|
if (!empty($logout_msg)) {
|
|
switch ($logout_msg) {
|
|
case 'session_expired':
|
|
/* translators: %s: Minute count */
|
|
$msg = sprintf(__('Your session has expired because it has been over %d minutes since your last login.', 'all-in-one-wp-security-and-firewall'), $aio_wp_security->configs->get_value('aiowps_logout_time_period'));
|
|
$msg .= ' ' . __('Please log back in to continue.', 'all-in-one-wp-security-and-firewall');
|
|
break;
|
|
case 'admin_user_changed':
|
|
$msg = __('You were logged out because you just changed the "admin" username.', 'all-in-one-wp-security-and-firewall');
|
|
$msg .= ' ' . __('Please log back in to continue.', 'all-in-one-wp-security-and-firewall');
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
if (!empty($msg)) {
|
|
$msg = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8');
|
|
$message .= '<p class="login message">'. $msg . '</p>';
|
|
}
|
|
return $message;
|
|
}
|
|
/**
|
|
* This function will generate an unlock request form to be inserted inside
|
|
* error message when user gets locked out.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_unlock_request_form() {
|
|
global $aio_wp_security;
|
|
$unlock_request_form = '';
|
|
//Let's encode some hidden data and make a form
|
|
$unlock_secret_string = $aio_wp_security->configs->get_value('aiowps_unlock_request_secret_key');
|
|
$current_time = time();
|
|
$enc_result = base64_encode($current_time.$unlock_secret_string);
|
|
$unlock_request_form .= '<form method="post" action=""><div style="padding-bottom:10px;"><input type="hidden" name="aiowps-unlock-string-info" id="aiowps-unlock-string-info" value="'.$enc_result.'" />';
|
|
$unlock_request_form .= '<input type="hidden" name="aiowps-unlock-temp-string" id="aiowps-unlock-temp-string" value="'.$current_time.'" />';
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- No nonce.
|
|
if (isset($_POST['woocommerce-login-nonce'])) {
|
|
$unlock_request_form .= '<input type="hidden" name="aiowps-woo-login" id="aiowps-woo-login" value="1" />';
|
|
}
|
|
$unlock_request_form .= '<button type="submit" name="aiowps_unlock_request" id="aiowps_unlock_request" class="button">'.__('Request unlock', 'all-in-one-wp-security-and-firewall').'</button></div></form>';
|
|
return $unlock_request_form;
|
|
}
|
|
|
|
/**
|
|
* Returns all logged in users for specific subsite of multisite installation.
|
|
*
|
|
* @param bool $sitewide - checks if logged in users should be fetched sitewide
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function get_logged_in_users($sitewide = true) {
|
|
global $wpdb;
|
|
|
|
$logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS;
|
|
if ($sitewide) {
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
|
|
$users_online = $wpdb->get_results("SELECT * FROM `{$logged_in_users_table}`", 'ARRAY_A');
|
|
} else {
|
|
$current_blog_id = get_current_blog_id();
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
|
|
$users_online = $wpdb->get_results($wpdb->prepare("SELECT * FROM `{$logged_in_users_table}` WHERE site_id = %d", $current_blog_id), 'ARRAY_A');
|
|
}
|
|
|
|
if (empty($users_online)) return array();
|
|
|
|
return $users_online;
|
|
}
|
|
|
|
/**
|
|
* Send email notification to an user that has flag is_lockout_email_sent is 0.
|
|
*
|
|
* @return Void
|
|
*/
|
|
public function send_login_lockout_emails() {
|
|
global $wpdb, $aio_wp_security;
|
|
// if user enabled notification email then only have to send
|
|
$email_notification_enabled = $aio_wp_security->configs->get_value('aiowps_enable_email_notify');
|
|
if (0 == $email_notification_enabled) {
|
|
return;
|
|
}
|
|
// get recent lockout records on top to notify
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Direct query required.
|
|
$sql = $wpdb->prepare('SELECT id, user_login, failed_login_ip, backtrace_log, ip_lookup_result FROM ' .AIOWPSEC_TBL_LOGIN_LOCKOUT. ' WHERE is_lockout_email_sent = %d ORDER BY id DESC', 0);
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above.
|
|
$result = $wpdb->get_results($sql);
|
|
if (empty($result)) {
|
|
return;
|
|
}
|
|
$login_lockout_ids_send_emails = array();
|
|
$lockout_ips_backtrace_log = array();
|
|
$lockout_ips_list = array();
|
|
$backtrace_filepath = '';
|
|
foreach ($result as $row) {
|
|
$ip_range = AIOWPSecurity_Utility_IP::get_sanitized_ip_range($row->failed_login_ip);
|
|
$lockout_ips_list[] = array('username' => $row->user_login, 'ip' => $row->failed_login_ip, 'ip_range' => $ip_range, 'ip_lookup_result' => $row->ip_lookup_result);
|
|
$login_lockout_ids_send_emails[] = $row->id;
|
|
if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_php_backtrace_in_email') && '' != $row->backtrace_log) {
|
|
$lockout_ips_backtrace_log[] = array('backtrace_log' => $row->backtrace_log);
|
|
}
|
|
}
|
|
|
|
if (0 != count($lockout_ips_backtrace_log)) {
|
|
$backtrace_filepath = AIOWPSecurity_Utility::login_lockdown_email_backtrace_log_file($lockout_ips_backtrace_log);
|
|
}
|
|
|
|
$this->send_ip_lock_notification_email($lockout_ips_list, $backtrace_filepath);
|
|
|
|
if ('' != $backtrace_filepath) {
|
|
wp_delete_file($backtrace_filepath);
|
|
}
|
|
|
|
if (!empty($login_lockout_ids_send_emails)) {
|
|
$aio_wp_security->debug_logger->log_debug(sprintf('The IP lock notification emails of login lockout ids [%s] are sent.', implode(', ', $login_lockout_ids_send_emails)), 4);
|
|
// update all email to as sent.
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2.
|
|
$sql = $wpdb->prepare('UPDATE '.AIOWPSEC_TBL_LOGIN_LOCKOUT.' SET is_lockout_email_sent = %d WHERE is_lockout_email_sent = %d', 1, 0);
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above.
|
|
$update_result = $wpdb->query($sql);
|
|
if (false === $update_result) {
|
|
$error_msg = empty($wpdb->last_error) ? 'Could not receive the reason for the failure' : $wpdb->last_error;
|
|
$aio_wp_security->debug_logger->log_debug_cron("Lockout email flag is not updated in database due to error: {$error_msg}", 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stores logged-in user in the logged_in_user table
|
|
*
|
|
* @param int $user_id - id of user logging in
|
|
* @param int $expiration - expiration timestamp of cookie
|
|
*
|
|
* @return void
|
|
*/
|
|
public function store_logged_in_user($user_id, $expiration) {
|
|
global $wpdb, $aio_wp_security;
|
|
|
|
$logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS;
|
|
$ip_address = AIOWPSecurity_Utility_IP::get_user_ip_address();
|
|
$userdata = get_userdata($user_id);
|
|
$username = $userdata->user_login;
|
|
$login_time = time();
|
|
|
|
// Check if a record with the given user_id already exists
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2.
|
|
$existing_record = $wpdb->get_row($wpdb->prepare("SELECT * FROM " . $logged_in_users_table . " WHERE user_id = %d", $user_id));
|
|
|
|
if ($existing_record) {
|
|
// Update the existing record
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
|
|
$result = $wpdb->update(
|
|
$logged_in_users_table,
|
|
array(
|
|
'ip_address' => $ip_address,
|
|
'site_id' => get_current_blog_id(),
|
|
'username' => $username,
|
|
'expires' => $expiration
|
|
),
|
|
array('user_id' => $user_id)
|
|
);
|
|
} else {
|
|
// Create a new record
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
|
|
$result = $wpdb->insert(
|
|
$logged_in_users_table,
|
|
array(
|
|
'user_id' => $user_id,
|
|
'ip_address' => $ip_address,
|
|
'expires' => $expiration,
|
|
'site_id' => get_current_blog_id(),
|
|
'username' => $username,
|
|
'created' => $login_time
|
|
)
|
|
);
|
|
}
|
|
|
|
if (false === $result) {
|
|
$generic_error_message = $existing_record ? "Error updating record in " . $logged_in_users_table : "Error inserting record into ".$logged_in_users_table;
|
|
$error_message = empty($wpdb->last_error) ? $generic_error_message : $wpdb->last_error;
|
|
$aio_wp_security->debug_logger->log_debug($error_message, 4);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles the data coming from the 'set_auth_cookie' hook
|
|
*
|
|
* @param string $auth_cookie - the generated auth_cookie
|
|
* @param int $expire - expiration timestamp of cookie if remember is marked
|
|
* @param int $expiration - expiration timestamp of cookie
|
|
* @param int $user_id - id of user logging in
|
|
*
|
|
* @return void
|
|
*/
|
|
public function handle_logged_in_user($auth_cookie, $expire, $expiration, $user_id) {
|
|
|
|
if (empty($auth_cookie)) return; //check if auth cookie is empty, meaning login was not successful
|
|
$expiration = $expire > 0 ? $expire : $expiration;
|
|
|
|
if (is_multisite() && !is_super_admin()) {
|
|
$user_blog = get_active_blog_for_user($user_id);
|
|
switch_to_blog($user_blog->blog_id); // switch to user blog incase they try to log in from wrong subsite
|
|
|
|
$this->store_logged_in_user($user_id, $expiration);
|
|
restore_current_blog();
|
|
} else {
|
|
$this->store_logged_in_user($user_id, $expiration);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes logged-in user from the logged_in_user table
|
|
*
|
|
* @param int $user_id
|
|
* @return bool
|
|
*/
|
|
public function delete_logged_in_user($user_id) {
|
|
global $wpdb, $aio_wp_security;
|
|
|
|
$logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS;
|
|
|
|
if (empty($user_id)) return true;
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Ignore.
|
|
$result = $wpdb->delete(
|
|
$logged_in_users_table,
|
|
array('user_id' => $user_id)
|
|
);
|
|
|
|
|
|
if (false === $result) {
|
|
$error_message = empty($wpdb->last_error) ? "Error deleting record from " . $logged_in_users_table : $wpdb->last_error;
|
|
$aio_wp_security->debug_logger->log_debug($error_message, 4);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Cron job function for removing data with expired session from the logged-in user table
|
|
*
|
|
* @return void
|
|
*/
|
|
public function delete_expired_logged_in_users() {
|
|
global $wpdb, $aio_wp_security;
|
|
$logged_in_users_table = AIOWPSEC_TBL_LOGGED_IN_USERS;
|
|
|
|
// Delete data with expired cookie
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Direct query required,
|
|
$result = $wpdb->query(
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- PCP error. Direct query required. Table name cannot be prepared pre WP 6.2.
|
|
$wpdb->prepare("DELETE FROM " . $logged_in_users_table . " WHERE expires < %d", time())
|
|
);
|
|
|
|
if (false === $result) {
|
|
$error_message = empty($wpdb->last_error) ? "Error deleting records from ".$logged_in_users_table : $wpdb->last_error;
|
|
$aio_wp_security->debug_logger->log_debug($error_message, 4);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function rewrites the password reset message
|
|
*
|
|
* @param string $message - The password reset email message to be edited
|
|
*
|
|
* @return string - Email message to be sent for password reset
|
|
*/
|
|
public function aiowps_retrieve_password_message($message) {
|
|
$ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user
|
|
|
|
// Find the position of the IP Address string in the message
|
|
$ip_string = isset($_SERVER['REMOTE_ADDR']) ? rest_is_ip_address(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
|
|
$ip_pos = strpos($message, $ip_string);
|
|
|
|
// If the IP Address string is found in the message and not the same as AIOWPS ip, replace it with the replacement string
|
|
if (false !== $ip_pos && $ip !== $ip_string) {
|
|
$replacement = "$ip.\r\n\r\n";
|
|
$message = substr_replace($message, $replacement, $ip_pos);
|
|
}
|
|
|
|
return $message;
|
|
}
|
|
}
|