File manager - Edit - /home/xfekoga/grenier/wp-content/plugins/defender-security/src/component/class-session-protection.php
Back
<?php /** * Handles session protection functionality within the WP Defender plugin. * * @package WP_Defender\Component * @since 5.2.0 */ namespace WP_Defender\Component; use WP_Defender\Component; use WP_Defender\Traits\IP; use WP_Defender\Traits\User; use WP_User_Meta_Session_Tokens; use WP_Defender\Model\Setting\Session_Protection as Settings; /** * This class handles session protection functionality within the WP Defender plugin. */ class Session_Protection extends Component { use IP; use User; /** * Key to lock the session to the browser. */ const LOCK_BROWSER = 'browser'; /** * Key to lock the session to the hostname. */ const LOCK_HOSTNAME = 'hostname'; /** * Key to lock the session to the IP address. */ const LOCK_IP_ADDRESS = 'ip_address'; /** * The name of the log file. * * @var string */ const LOG_FILE_NAME = 'session-protection'; /** * Transient key to show custom logout message in the login modal. * * @var string */ const LOGOUT_MSG_TRANSIENT_KEY = 'wpdef_session_logout_msg'; /** * The session protection settings. * * @var Settings|null */ protected ?Settings $settings; /** * Initializes the model and service, registers routes, and sets up scheduled events if the model is active. * * @return void */ public function __construct() { $this->settings = wd_di()->get( Settings::class ); } /** * Retrieve an array of default session lock properties. * * @return array An array of session lock properties. */ public static function session_lock_properties(): array { return array( self::LOCK_BROWSER => esc_html__( 'Browser', 'defender-security' ), self::LOCK_HOSTNAME => esc_html__( 'Hostname', 'defender-security' ), self::LOCK_IP_ADDRESS => esc_html__( 'IP Address', 'defender-security' ), ); } /** * Prepare details of the Session Lock property for MP event. * No need to translate. * * @return string */ public function get_session_lock_string(): string { if ( ! $this->settings->has_properties() ) { return 'Not Set'; } $checked_properties = array(); if ( $this->settings->is_property_locked( self::LOCK_IP_ADDRESS ) ) { $checked_properties[] = 'IP'; } if ( $this->settings->is_property_locked( self::LOCK_HOSTNAME ) ) { $checked_properties[] = 'Hostname'; } if ( $this->settings->is_property_locked( self::LOCK_BROWSER ) ) { $checked_properties[] = 'Browser'; } return implode( '/', $checked_properties ); } /** * Handles session timeout logic on init hook. * * @return void */ public function handle_session_timeout() { if ( ! is_user_logged_in() || wp_doing_ajax() || wp_doing_cron() || defender_is_rest_api_request() ) { return; } if ( ! $this->can_apply_session_protection() ) { $this->log( 'Session Protection is not applied for the current user role.', self::LOG_FILE_NAME ); return; } if ( $this->settings->has_properties() ) { $this->handle_session_lock(); return; } $idle_timeout = $this->get_idle_timeout(); $last_activity = $this->get_last_activity(); if ( $last_activity && ( defender_get_current_time() - $last_activity > $idle_timeout ) ) { $this->log( sprintf( 'User session timed out due to inactivity during %s hours', $this->settings->idle_timeout ), self::LOG_FILE_NAME ); /** * Fires during session timeout. * * @param int $user_id */ do_action( 'wpdef_session_timeout', get_current_user_id() ); $this->logout(); return; } $this->update_last_activity(); } /** * Retrieves the last activity timestamp. * * @return int|null Last activity timestamp or null if not set. */ private function get_last_activity() { // Check for client-side cookie first to avoid unnecessary database queries. if ( isset( $_COOKIE['wpdef_last_activity'] ) && is_numeric( $_COOKIE['wpdef_last_activity'] ) ) { return (int) $_COOKIE['wpdef_last_activity']; } return null; } /** * Logs out the current user due to inactivity. * * @return void */ public function logout() { if ( is_user_logged_in() ) { $user_id = get_current_user_id(); $current_session_token = wp_get_session_token(); // Get current session token. if ( $current_session_token ) { // Get session manager for the user. $session_manager = \WP_Session_Tokens::get_instance( $user_id ); // Destroy ONLY the current session. $session_manager->destroy( $current_session_token ); } else { $manager = \WP_Session_Tokens::get_instance( $user_id ); $manager->destroy_all(); } // Reset last activity cookie. setcookie( 'wpdef_last_activity', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true ); // Set transient to show custom message in the login modal. set_site_transient( self::LOGOUT_MSG_TRANSIENT_KEY, 1, 30 ); // Redirect to login page after session destruction. wp_safe_redirect( wp_login_url() ); } } /** * Show custom message in the login modal when session is expired. * * @param \WP_Error $errors WP Error object. * * @return \WP_Error */ public function login_modal_message( $errors ) { if ( empty( defender_get_data_from_request( 'interim-login', 'g' ) ) || ! get_site_transient( self::LOGOUT_MSG_TRANSIENT_KEY ) ) { return $errors; } delete_site_transient( self::LOGOUT_MSG_TRANSIENT_KEY ); if ( ! ( $errors instanceof \WP_Error ) ) { return $errors; } $error_keys = array( 'expired', 'expired_session' ); foreach ( $error_keys as $key ) { if ( $errors->get_error_message( $key ) ) { $errors->remove( $key ); $errors->add( $key, __( 'You\'ve been logged out due to inactivity. Please sign in to continue.', 'defender-security' ), 'message' ); break; } } return $errors; } /** * Styles for the custom message in the login modal. * * @return void */ public function login_modal_message_styles(): void { if ( get_site_transient( self::LOGOUT_MSG_TRANSIENT_KEY ) ) { // Don't delete the transient here, it will be deleted in the login_modal_message() method. echo '<style> #login_error { border-left-color: #72aee6; } </style>'; } } /** * Enqueues JavaScript for client-side idle detection. * * @return void */ public function enqueue_idle_scripts() { if ( is_user_logged_in() ) { wp_enqueue_script( 'wpdef-session-idle', plugins_url( 'assets/js/idle-session.js', WP_DEFENDER_FILE ), array( 'jquery' ), DEFENDER_VERSION, true ); wp_localize_script( 'wpdef-session-idle', 'wpdef_idle_params', array( 'timeout' => $this->get_idle_timeout(), 'ajax_url' => admin_url( 'admin-ajax.php' ), 'login_url' => wp_login_url(), ) ); } } /** * Retrieves the idle timeout duration. * * @return int The idle timeout duration in seconds. */ public function get_idle_timeout() { /** * Filter the idle timeout duration in seconds. * * @param int $idle_timeout The idle timeout duration in seconds. * * @return int The idle timeout duration in seconds. */ return (int) apply_filters( 'wpdef_idle_timeout', $this->settings->idle_timeout * HOUR_IN_SECONDS ); } /** * Updates the last activity timestamp efficiently. * * @return void */ public function update_last_activity() { $current_time = time(); $last_activity = $this->get_last_activity(); // Only update the cookie if minutes have passed since the last update. if ( ! empty( $last_activity ) || ( $current_time - $last_activity > MINUTE_IN_SECONDS ) ) { setcookie( 'wpdef_last_activity', $current_time, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true ); } } /** * Attach session information to the current user session. * * @param array $session Array of extra data. * @param int $user_id User ID. * * @return array Array of extra data. */ public function attach_session_information( $session, $user_id ) { if ( $this->settings->is_property_locked( self::LOCK_IP_ADDRESS ) ) { $session['defender_ip'] = implode( '|', $this->get_user_ip() ); } if ( $this->settings->is_property_locked( self::LOCK_HOSTNAME ) ) { $session['defender_hostname'] = defender_get_domain(); } if ( $this->settings->is_property_locked( self::LOCK_BROWSER ) ) { $session['defender_browser'] = defender_get_user_agent(); } return $session; } /** * Handles session lock logic. * * @return void */ public function handle_session_lock() { $token = wp_get_session_token(); $user_id = get_current_user_id(); $manager = WP_User_Meta_Session_Tokens::get_instance( $user_id ); $session_data = $manager->get( $token ); if ( ! is_array( $session_data ) ) { $this->log( 'Session data is missing or malformed. Destroying all sessions.', self::LOG_FILE_NAME ); $this->logout(); return; } $user_ips = $this->get_user_ip(); // Exclude Loopback case. If condition returns true, then there is no Loopback intersection. if ( ! empty( array_intersect( $user_ips, $this->get_localhost_ips() ) ) ) { return; } // Work with the Session Lock options. if ( $this->settings->is_property_locked( self::LOCK_IP_ADDRESS ) && isset( $session_data['defender_ip'] ) ) { $ips = implode( '|', $user_ips ); $this->is_ip_allowed( $ips, $session_data['defender_ip'], $user_id ); } if ( $this->settings->is_property_locked( self::LOCK_HOSTNAME ) && isset( $session_data['defender_hostname'] ) ) { $hostname = defender_get_domain(); $this->is_hostname_allowed( $hostname, $session_data['defender_hostname'], $user_id ); } if ( $this->settings->is_property_locked( self::LOCK_BROWSER ) && isset( $session_data['defender_browser'] ) ) { $browser = defender_get_user_agent(); $this->is_browser_allowed( $browser, $session_data['defender_browser'], $user_id ); } } /** * Verifies if the given IP is allowed based on the session information. * * @param string $ip IP addresses to check. * @param string $session_ip Session IP address. * @param int $user_id User ID. * * @return void */ private function is_ip_allowed( $ip, $session_ip, $user_id ) { if ( empty( $ip ) || empty( $session_ip ) || $ip !== $session_ip ) { $this->log( sprintf( 'IP address mismatch detected. Detected: %s, Expected: %s.', $ip, $session_ip ), self::LOG_FILE_NAME ); /** * Fires during session lock. * * @param int $user_id * @param string $session_lock_type */ do_action( 'wpdef_session_lock', $user_id, 'IP address' ); $this->logout(); } } /** * Verifies if the given hostname is allowed based on the session information. * * @param string $hostname Hostname to check. * @param string $session_hostname Session hostname. * @param int $user_id User ID. * * @return void */ private function is_hostname_allowed( $hostname, $session_hostname, $user_id ) { if ( empty( $hostname ) || empty( $session_hostname ) || strcasecmp( $hostname, $session_hostname ) !== 0 ) { $this->log( sprintf( 'Hostname mismatch detected. Detected: %s, Expected: %s.', $hostname, $session_hostname ), self::LOG_FILE_NAME ); do_action( 'wpdef_session_lock', $user_id, 'hostname' ); $this->logout(); } } /** * Verifies if the given browser is allowed based on the session information. * * @param string $browser Browser to check. * @param string $session_browser Session browser. * @param int $user_id User ID. * * @return void */ private function is_browser_allowed( $browser, $session_browser, $user_id ) { if ( empty( $browser ) || empty( $session_browser ) || strcasecmp( $browser, $session_browser ) !== 0 ) { $this->log( sprintf( 'Browser mismatch detected. Detected: %s, Expected: %s.', $browser, $session_browser ), self::LOG_FILE_NAME ); do_action( 'wpdef_session_lock', $user_id, 'browser' ); $this->logout(); } } /** * Should we apply this feature for the current user? * * @return bool */ public function can_apply_session_protection(): bool { $current_user = $this->get_user( get_current_user_id() ); /** * Filter whether to apply the feature for the current user. * * @param bool $apply Whether to apply the feature. * @param \WP_User $current_user The current user object. * * @return bool */ return (bool) apply_filters( 'wpdef_toggle_session_protection', $this->should_enforce_for_user( $current_user ), $current_user ); } }
| ver. 1.4 |
Github
|
.
| PHP 8.0.30 | Generation time: 0 |
proxy
|
phpinfo
|
Settings