mirror of
https://gitlab.com/ArkHost/WP-Security-Pack.git
synced 2026-09-19 17:37:30 +02:00
279 lines
7.5 KiB
PHP
279 lines
7.5 KiB
PHP
<?php
|
|
/**
|
|
* Activity logging for WP Security Pack.
|
|
*
|
|
* @package WP_Security_Pack
|
|
*/
|
|
|
|
// Prevent direct access.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Activity log class.
|
|
*/
|
|
class WPSP_Activity_Log {
|
|
|
|
/**
|
|
* Event types.
|
|
*/
|
|
const EVENT_LOGIN_SUCCESS = 'login_success';
|
|
const EVENT_LOGIN_FAILED = 'login_failed';
|
|
const EVENT_LOCKOUT = 'lockout';
|
|
const EVENT_IP_BLOCKED = 'ip_blocked';
|
|
const EVENT_GEO_BLOCKED = 'geo_blocked';
|
|
const EVENT_LOCKOUT_LIFTED = 'lockout_lifted';
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function __construct() {
|
|
// Hooks are set up by Login Protection class.
|
|
}
|
|
|
|
/**
|
|
* Log an event.
|
|
*
|
|
* @param string $event_type Event type.
|
|
* @param string|null $ip_address IP address (auto-detected if null).
|
|
* @param string|null $username Username.
|
|
* @param string|null $details Additional details.
|
|
* @return int|false Insert ID or false on failure.
|
|
*/
|
|
public static function log( $event_type, $ip_address = null, $username = null, $details = null ) {
|
|
global $wpdb;
|
|
|
|
if ( null === $ip_address ) {
|
|
$ip_address = WPSP_Helper::get_client_ip();
|
|
}
|
|
|
|
// Get user agent.
|
|
$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] )
|
|
? substr( sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ), 0, 255 )
|
|
: '';
|
|
|
|
// Get country code if geo-blocking is available.
|
|
$country_code = null;
|
|
$geo_blocking = wpsp()->get_component( 'geo_blocking' );
|
|
if ( $geo_blocking && $ip_address ) {
|
|
$country_code = $geo_blocking->get_country_code( $ip_address );
|
|
}
|
|
|
|
$table = WPSP_DB::get_log_table();
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
|
$result = $wpdb->insert(
|
|
$table,
|
|
array(
|
|
'event_type' => $event_type,
|
|
'ip_address' => $ip_address ? $ip_address : '',
|
|
'username' => $username,
|
|
'user_agent' => $user_agent,
|
|
'country_code' => $country_code,
|
|
'details' => $details,
|
|
'created_at' => current_time( 'mysql' ),
|
|
),
|
|
array( '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
|
|
);
|
|
|
|
return $result ? $wpdb->insert_id : false;
|
|
}
|
|
|
|
/**
|
|
* Get recent logs.
|
|
*
|
|
* @param array $args Query arguments.
|
|
* @return array
|
|
*/
|
|
public static function get_logs( $args = array() ) {
|
|
global $wpdb;
|
|
|
|
$defaults = array(
|
|
'limit' => 50,
|
|
'offset' => 0,
|
|
'event_type' => '',
|
|
'ip_address' => '',
|
|
'orderby' => 'created_at',
|
|
'order' => 'DESC',
|
|
);
|
|
|
|
$args = wp_parse_args( $args, $defaults );
|
|
$table = WPSP_DB::get_log_table();
|
|
|
|
$where_clauses = array( '1=1' );
|
|
$where_values = array();
|
|
|
|
if ( ! empty( $args['event_type'] ) ) {
|
|
$where_clauses[] = 'event_type = %s';
|
|
$where_values[] = $args['event_type'];
|
|
}
|
|
|
|
if ( ! empty( $args['ip_address'] ) ) {
|
|
$where_clauses[] = 'ip_address = %s';
|
|
$where_values[] = $args['ip_address'];
|
|
}
|
|
|
|
$where_sql = implode( ' AND ', $where_clauses );
|
|
|
|
// Sanitize orderby.
|
|
$allowed_orderby = array( 'id', 'event_type', 'ip_address', 'username', 'created_at' );
|
|
$orderby = in_array( $args['orderby'], $allowed_orderby, true ) ? $args['orderby'] : 'created_at';
|
|
$order = 'ASC' === strtoupper( $args['order'] ) ? 'ASC' : 'DESC';
|
|
|
|
$limit = absint( $args['limit'] );
|
|
$offset = absint( $args['offset'] );
|
|
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
$sql = "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d";
|
|
|
|
$where_values[] = $limit;
|
|
$where_values[] = $offset;
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.NotPrepared
|
|
return $wpdb->get_results( $wpdb->prepare( $sql, $where_values ) );
|
|
}
|
|
|
|
/**
|
|
* Get total log count.
|
|
*
|
|
* @param array $args Query arguments.
|
|
* @return int
|
|
*/
|
|
public static function get_log_count( $args = array() ) {
|
|
global $wpdb;
|
|
|
|
$table = WPSP_DB::get_log_table();
|
|
|
|
$where_clauses = array( '1=1' );
|
|
$where_values = array();
|
|
|
|
if ( ! empty( $args['event_type'] ) ) {
|
|
$where_clauses[] = 'event_type = %s';
|
|
$where_values[] = $args['event_type'];
|
|
}
|
|
|
|
if ( ! empty( $args['ip_address'] ) ) {
|
|
$where_clauses[] = 'ip_address = %s';
|
|
$where_values[] = $args['ip_address'];
|
|
}
|
|
|
|
$where_sql = implode( ' AND ', $where_clauses );
|
|
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
$sql = "SELECT COUNT(*) FROM {$table} WHERE {$where_sql}";
|
|
|
|
if ( ! empty( $where_values ) ) {
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.NotPrepared
|
|
return (int) $wpdb->get_var( $wpdb->prepare( $sql, $where_values ) );
|
|
}
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.NotPrepared
|
|
return (int) $wpdb->get_var( $sql );
|
|
}
|
|
|
|
/**
|
|
* Get log statistics.
|
|
*
|
|
* @param int $days Number of days to look back.
|
|
* @return array
|
|
*/
|
|
public static function get_stats( $days = 30 ) {
|
|
global $wpdb;
|
|
|
|
$table = WPSP_DB::get_log_table();
|
|
$cutoff = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days} days" ) );
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
|
$results = $wpdb->get_results(
|
|
$wpdb->prepare(
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
"SELECT event_type, COUNT(*) as count FROM {$table} WHERE created_at >= %s GROUP BY event_type",
|
|
$cutoff
|
|
)
|
|
);
|
|
|
|
$stats = array(
|
|
'login_success' => 0,
|
|
'login_failed' => 0,
|
|
'lockout' => 0,
|
|
'ip_blocked' => 0,
|
|
'geo_blocked' => 0,
|
|
'total' => 0,
|
|
);
|
|
|
|
foreach ( $results as $row ) {
|
|
$stats[ $row->event_type ] = (int) $row->count;
|
|
$stats['total'] += (int) $row->count;
|
|
}
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Cleanup old logs (cron job).
|
|
*/
|
|
public static function cleanup_old_logs() {
|
|
global $wpdb;
|
|
|
|
$retention_days = WP_Security_Pack::get_setting( 'log_retention_days', 30 );
|
|
$cutoff = gmdate( 'Y-m-d H:i:s', strtotime( "-{$retention_days} days" ) );
|
|
|
|
$log_table = WPSP_DB::get_log_table();
|
|
$lockout_table = WPSP_DB::get_lockout_table();
|
|
|
|
// Delete old logs.
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
|
$wpdb->query(
|
|
$wpdb->prepare(
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
"DELETE FROM {$log_table} WHERE created_at < %s",
|
|
$cutoff
|
|
)
|
|
);
|
|
|
|
// Delete expired lockouts (lockout_until stores Unix timestamp as integer).
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
|
$wpdb->query(
|
|
$wpdb->prepare(
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
"DELETE FROM {$lockout_table} WHERE lockout_until IS NOT NULL AND lockout_until > 0 AND lockout_until < %d",
|
|
time()
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Clear all logs.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function clear_all_logs() {
|
|
global $wpdb;
|
|
|
|
$table = WPSP_DB::get_log_table();
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
return false !== $wpdb->query( "TRUNCATE TABLE {$table}" );
|
|
}
|
|
|
|
/**
|
|
* Get event type label.
|
|
*
|
|
* @param string $event_type Event type.
|
|
* @return string
|
|
*/
|
|
public static function get_event_label( $event_type ) {
|
|
$labels = array(
|
|
self::EVENT_LOGIN_SUCCESS => __( 'Login Success', 'wp-security-pack' ),
|
|
self::EVENT_LOGIN_FAILED => __( 'Login Failed', 'wp-security-pack' ),
|
|
self::EVENT_LOCKOUT => __( 'Lockout', 'wp-security-pack' ),
|
|
self::EVENT_IP_BLOCKED => __( 'IP Blocked', 'wp-security-pack' ),
|
|
self::EVENT_GEO_BLOCKED => __( 'Geo Blocked', 'wp-security-pack' ),
|
|
self::EVENT_LOCKOUT_LIFTED => __( 'Lockout Lifted', 'wp-security-pack' ),
|
|
);
|
|
|
|
return isset( $labels[ $event_type ] ) ? $labels[ $event_type ] : $event_type;
|
|
}
|
|
}
|