table = $wpdb->prefix . 'ticket_tailor_security_log'; // Create table if needed $this->maybe_create_table(); } /** * Create security log table */ private function maybe_create_table() { global $wpdb; // Check if table already exists $table_exists = $wpdb->get_var($wpdb->prepare( "SHOW TABLES LIKE %s", $this->table )); if ($table_exists) { return; } $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS {$this->table} ( id bigint(20) NOT NULL AUTO_INCREMENT, event_type varchar(50) NOT NULL, user_id bigint(20) DEFAULT 0, ip_address varchar(45) NOT NULL, details longtext, timestamp datetime NOT NULL, PRIMARY KEY (id), KEY event_type (event_type), KEY timestamp (timestamp), KEY ip_address (ip_address) ) {$charset_collate};"; // Only require upgrade.php if we actually need to create the table if (!function_exists('dbDelta')) { require_once ABSPATH . 'wp-admin/includes/upgrade.php'; } dbDelta($sql); } /** * Log security event */ public function log_event($event_type, $details = array()) { global $wpdb; $wpdb->insert( $this->table, array( 'event_type' => sanitize_key($event_type), 'user_id' => get_current_user_id(), 'ip_address' => $this->get_ip(), 'details' => wp_json_encode($details), 'timestamp' => current_time('mysql', 1), ), array('%s', '%d', '%s', '%s', '%s') ); } /** * Log failed webhook */ public function log_failed_webhook($ip, $reason) { $this->log_event('webhook_failed', array( 'ip' => $ip, 'reason' => $reason, )); // Check for multiple failures $recent_failures = $this->count_recent_events('webhook_failed', 300); if ($recent_failures >= 5) { $this->send_security_alert('Multiple Failed Webhook Attempts', array( 'count' => $recent_failures, 'ip' => $ip, 'timeframe' => '5 minutes', )); } } /** * Log successful webhook */ public function log_successful_webhook($ip, $event_type) { $this->log_event('webhook_success', array( 'ip' => $ip, 'webhook_type' => $event_type, )); } /** * Log API key change */ public function log_api_key_change($old_key_hash, $new_key_hash) { $this->log_event('api_key_changed', array( 'old_hash' => substr($old_key_hash, 0, 10), 'new_hash' => substr($new_key_hash, 0, 10), 'user' => wp_get_current_user()->user_login, )); $this->send_security_alert('API Key Changed', array( 'changed_by' => wp_get_current_user()->user_login, 'ip' => $this->get_ip(), )); } /** * Log settings change */ public function log_settings_change($setting_name, $old_value = null, $new_value = null) { $this->log_event('settings_changed', array( 'setting' => $setting_name, 'changed_by' => wp_get_current_user()->user_login, 'has_old' => !is_null($old_value), 'has_new' => !is_null($new_value), )); } /** * Log failed login attempt */ public function log_failed_login($username) { $this->log_event('login_failed', array( 'username' => $username, )); // Check for brute force $recent_failures = $this->count_recent_events('login_failed', 900, $this->get_ip()); if ($recent_failures >= 5) { $this->send_security_alert('Multiple Failed Login Attempts', array( 'count' => $recent_failures, 'ip' => $this->get_ip(), 'username' => $username, )); } } /** * Log rate limit exceeded */ public function log_rate_limit_exceeded($ip, $context = 'webhook') { $this->log_event('rate_limit_exceeded', array( 'ip' => $ip, 'context' => $context, )); } /** * Log unauthorized access attempt */ public function log_unauthorized_access($resource) { $this->log_event('unauthorized_access', array( 'resource' => $resource, 'user_id' => get_current_user_id(), )); } /** * Count recent events */ private function count_recent_events($event_type, $seconds, $ip = null) { global $wpdb; $time_threshold = gmdate('Y-m-d H:i:s', time() - $seconds); if ($ip) { return $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$this->table} WHERE event_type = %s AND timestamp > %s AND ip_address = %s", $event_type, $time_threshold, $ip )); } else { return $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$this->table} WHERE event_type = %s AND timestamp > %s", $event_type, $time_threshold )); } } /** * Send security alert email */ public function send_security_alert($event_type, $details) { // Check if alerts are enabled if (!get_option('ticket_tailor_security_alerts', true)) { return; } $admin_email = get_option('admin_email'); $site_name = get_bloginfo('name'); $site_url = get_bloginfo('url'); $subject = sprintf( '[%s] Security Alert: %s', $site_name, $event_type ); $message = sprintf( "Security Event Detected on %s\n\n" . "Event Type: %s\n" . "Time: %s UTC\n" . "IP Address: %s\n" . "User: %s\n\n" . "Details:\n%s\n\n" . "---\n" . "This is an automated security alert from Ticket Tailor plugin.\n" . "To disable these alerts, go to: %s/wp-admin/admin.php?page=ticket-tailor-settings", $site_name, $event_type, current_time('Y-m-d H:i:s'), $this->get_ip(), wp_get_current_user()->user_login ?: 'Guest', $this->format_details($details), $site_url ); wp_mail($admin_email, $subject, $message); } /** * Format details for email */ private function format_details($details) { $formatted = ''; foreach ($details as $key => $value) { $formatted .= ' ' . ucfirst(str_replace('_', ' ', $key)) . ': ' . $value . "\n"; } return $formatted; } /** * Get client IP */ private function get_ip() { $ip_keys = array( 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR', ); foreach ($ip_keys as $key) { if (!empty($_SERVER[$key])) { $ip = $_SERVER[$key]; if (strpos($ip, ',') !== false) { $ip = trim(explode(',', $ip)[0]); } if (filter_var($ip, FILTER_VALIDATE_IP)) { return $ip; } } } return '0.0.0.0'; } /** * Get recent security events */ public function get_recent_events($limit = 50, $event_type = null) { global $wpdb; if ($event_type) { return $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$this->table} WHERE event_type = %s ORDER BY timestamp DESC LIMIT %d", $event_type, $limit )); } else { return $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$this->table} ORDER BY timestamp DESC LIMIT %d", $limit )); } } /** * Clean old logs (older than 90 days) */ public function cleanup_old_logs() { global $wpdb; $threshold = gmdate('Y-m-d H:i:s', time() - (90 * DAY_IN_SECONDS)); $wpdb->query($wpdb->prepare( "DELETE FROM {$this->table} WHERE timestamp < %s", $threshold )); } /** * Get security statistics */ public function get_statistics($days = 30) { global $wpdb; $threshold = gmdate('Y-m-d H:i:s', time() - ($days * DAY_IN_SECONDS)); $stats = array(); // Total events $stats['total_events'] = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$this->table} WHERE timestamp > %s", $threshold )); // Events by type $stats['by_type'] = $wpdb->get_results($wpdb->prepare( "SELECT event_type, COUNT(*) as count FROM {$this->table} WHERE timestamp > %s GROUP BY event_type ORDER BY count DESC", $threshold ), ARRAY_A); // Top IPs $stats['top_ips'] = $wpdb->get_results($wpdb->prepare( "SELECT ip_address, COUNT(*) as count FROM {$this->table} WHERE timestamp > %s GROUP BY ip_address ORDER BY count DESC LIMIT 10", $threshold ), ARRAY_A); return $stats; } }