plugin = $plugin; } /** * Registers the REST API endpoint for the live feed. */ public function register_api_endpoint() { register_rest_route('advaipbl/v1', '/live-attacks', [ 'methods' => 'GET', 'callback' => [$this, 'get_live_attacks'], 'permission_callback' => '__return_true', ]); } /** * API Callback. Returns the latest attacks. * * @param WP_REST_Request $request * @return WP_REST_Response */ public function get_live_attacks(WP_REST_Request $request) { $token = $request->get_param('token'); $stored_token = get_option('advaipbl_live_feed_token'); if (empty($token) || empty($stored_token) || !hash_equals($stored_token, $token)) { return new WP_REST_Response(['message' => 'Unauthorized or Live Feed inactive.'], 403); } global $wpdb; $since_id = $request->get_param('since'); $since_id = isset($since_id) ? (int) $since_id : 0; $table_name = $wpdb->prefix . 'advaipbl_activity_log'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) != $table_name) { return new WP_REST_Response(['attacks' => [], 'last_id' => 0], 200); } $table_name = $wpdb->prefix . 'advaipbl_logs'; $sql = "SELECT log_id, ip, timestamp, log_type, message, details FROM {$table_name} WHERE level = 'critical' AND log_type != 'general'"; $params = []; if ($since_id) { $sql .= " AND log_id > %d"; $params[] = $since_id; } $sql .= " ORDER BY log_id DESC LIMIT 20"; if (!empty($params)) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $query = $wpdb->prepare($sql, $params); } else { $query = $sql; } // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $results = $wpdb->get_results($query, ARRAY_A); $attacks = []; $last_id = 0; if (!empty($results)) { $last_id = (int) $results[0]['log_id']; foreach ($results as $row) { $details_data = json_decode($row['details'], true) ?: []; $location = 'Unknown Location'; if (!empty($details_data['city']) && !empty($details_data['country'])) { $location = $details_data['city'] . ', ' . $details_data['country']; } elseif (!empty($details_data['country'])) { $location = $details_data['country']; } $details_message = $row['message']; $log_type = $row['log_type']; switch ($log_type) { case 'abuseipdb': $score = $details_data['abuse_score'] ?? 'N/A'; /* translators: %s is a placeholder */ $details_message = sprintf(__('Blocked by AbuseIPDB with a score of %d%%.', 'advanced-ip-blocker'), $score); break; case 'advanced_rule': case 'advanced_rule_challenge': // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $block_entry = $wpdb->get_row($wpdb->prepare("SELECT reason FROM {$wpdb->prefix}advaipbl_blocked_ips WHERE ip_range = %s", $row['ip']), ARRAY_A); if ($block_entry && !empty($block_entry['reason'])) { $details_message = $block_entry['reason']; } break; case 'waf': $rule_triggered = $details_data['rule'] ?? 'Unknown Rule'; /* translators: %s is a placeholder */ $details_message = sprintf(__('Triggered WAF rule: %s', 'advanced-ip-blocker'), $rule_triggered); break; case 'asn': $asn_number = $details_data['asn_number'] ?? 'N/A'; /* translators: %s is a placeholder */ $details_message = sprintf(__('Blocked ASN: %s', 'advanced-ip-blocker'), $asn_number); break; } $obfuscated_ip = $this->obfuscate_ip($row['ip']); $attacks[] = [ 'id' => (int) $row['log_id'], 'time' => human_time_diff(strtotime($row['timestamp'])) . ' ago', 'ip' => esc_html($obfuscated_ip), 'type' => esc_html(ucwords(str_replace('_', ' ', $log_type))), 'location' => esc_html($location), 'method' => esc_html($details_data['method'] ?? 'N/A'), 'details' => esc_html($details_message) ]; } } return new WP_REST_Response([ 'attacks' => $attacks, 'last_id' => $last_id, ], 200); } /** * Obfuscates an IP address for public display (GDPR compliance). * IPv4: 192.168.1.55 -> 192.168.1.* * IPv6: 2001:0db8:85a3:0000:0000:8a2e:0370:7334 -> 2001:0db8:85a3:****:****:****:****:**** * * @param string $ip * @return string */ private function obfuscate_ip($ip) { if (empty($ip)) { return ''; } if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { $parts = explode('.', $ip); if (count($parts) === 4) { return $parts[0] . '.' . $parts[1] . '.' . $parts[2] . '.*'; } } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $expanded = inet_ntop(inet_pton($ip)); if ($expanded !== false) { $parts = explode(':', $expanded); if (count($parts) === 8) { return $parts[0] . ':' . $parts[1] . ':' . $parts[2] . ':**::**'; } } } $len = strlen($ip); return substr($ip, 0, (int)($len / 2)) . '***'; } /** * Shortcode renderer [advaipbl_live_feed] */ public function render_shortcode($atts) { $root_url = plugins_url('/', dirname(__FILE__)); $token = get_option('advaipbl_live_feed_token'); if (empty($token)) { $token = wp_generate_password(32, false); update_option('advaipbl_live_feed_token', $token); } wp_enqueue_script('advaipbl-live-feed-js', $root_url . 'js/advaipbl-live-feed.js', ['jquery'], ADVAIPBL_VERSION, true); wp_localize_script('advaipbl-live-feed-js', 'advaipbl_feed_data', [ 'api_url' => get_rest_url(null, 'advaipbl/v1/live-attacks'), 'token' => $token, 'text' => [ 'blocked_from' => __('blocked from', 'advanced-ip-blocker'), 'type' => __('Type', 'advanced-ip-blocker'), 'method' => __('Method', 'advanced-ip-blocker'), 'details' => __('Details', 'advanced-ip-blocker'), 'uri' => __('URI', 'advanced-ip-blocker'), 'user_agent' => __('User Agent', 'advanced-ip-blocker'), ] ]); $logo_url_desktop = $root_url . 'assets/img/logo-ligth.png'; $logo_url_mobile = $root_url . 'assets/img/icon-256x256.png'; $css = ''; $html = '
'; return $css . $html; } }