apiKey = config('services.cortex.key'); $this->apiKeyId = config('services.cortex.id'); $this->baseUrl = config('services.cortex.url'); } /** * Generate headers. * Switched to Standard Authentication (Direct Key) based on documentation screenshot. */ protected function getHeaders() { // STANDARD AUTHENTICATION return [ 'x-xdr-auth-id' => $this->apiKeyId, 'Authorization' => $this->apiKey, 'Content-Type' => 'application/json', 'Accept' => 'application/json', ]; } /** * Get Incidents * Using pagination to fetch up to $limit incidents. */ public function getIncidents($limit = 1000, $filters = []) { return $this->fetchAll('incidents/get_incidents', 'incidents', $limit, $filters); } /** * Get Endpoints * Using pagination to fetch up to $limit endpoints. */ public function getEndpoints($limit = 1000) { return $this->fetchAll('endpoints/get_endpoint', 'endpoints', $limit); } /** * Generic fetch method with pagination. */ private function fetchAll($endpoint, $dataKey, $limit, $filters = []) { if (empty($this->apiKey) || empty($this->apiKeyId) || empty($this->baseUrl)) { Log::warning('Cortex XDR credentials missing.'); return []; } $allResults = []; $batchSize = 100; // API Limit $offset = 0; try { while ($offset < $limit) { // Adjust batch size for last page if needed $currentBatchSize = min($batchSize, $limit - $offset); // API confusingly uses search_to as count? Or offset + count? // Documentation: "search_to: Integer representing the ending offset" ? // Error message: "0 < search_size <= 100". // Wait, request_data documentation usually says: // search_from: integer // search_to: integer (exclusive end index?) or count? // Let's assume standard Cortex: offset based. // If search_from=0, search_to=100 -> gets 0 to 99 (100 items). $response = Http::withHeaders($this->getHeaders()) ->post("{$this->baseUrl}/public_api/v1/{$endpoint}/", [ 'request_data' => array_merge([ 'search_from' => $offset, 'search_to' => $offset + $currentBatchSize, ], !empty($filters) ? ['filters' => $filters] : []) ]); if ($response->successful()) { $items = $response->json()['reply'][$dataKey] ?? []; if (empty($items)) { break; // No more items } $allResults = array_merge($allResults, $items); $offset += count($items); // If we got fewer items than requested, we are done if (count($items) < $currentBatchSize) { break; } } else { $err = "Cortex API Error ({$endpoint}): " . $response->status() . ' - ' . $response->body(); Log::error($err); throw new \Exception($err); } } return $allResults; } catch (\Exception $e) { throw $e; } } /** * Get Dashboard Summary */ public function getSummary() { try { // Filter to fetch only active incidents to improve performance $filters = [ [ 'field' => 'status', 'operator' => 'in', 'value' => ['new', 'under_investigation'] ] ]; $incidents = $this->getIncidents(1000, $filters); // Limit incidents to 1000 recent active $endpoints = $this->getEndpoints(1000); // Limit endpoints to 2000 } catch (\Exception $e) { // In dashboard context, we verify configuration separately. // Returning empty arrays here avoids crashing the page if just one call fails. $incidents = []; $endpoints = []; } // Calculations $endpointsTotal = count($endpoints); $endpointsConnected = collect($endpoints)->filter(fn($e) => strtolower($e['endpoint_status'] ?? '') === 'connected')->count(); $endpointsDisconnected = collect($endpoints)->filter(fn($e) => strtolower($e['endpoint_status'] ?? '') === 'disconnected')->count(); // Filter only Active Incidents (New or Under Investigation) // Since we now filter at API level, we can just use the result, // but keeping the collection filter adds a layer of safety if filters change. $activeIncidents = collect($incidents)->filter(fn($i) => in_array($i['status'] ?? '', ['new', 'under_investigation'])); //die(var_dump($activeIncidents)); $incidentsCritical = $activeIncidents->where('severity', 'critical')->count(); $incidentsHigh = $activeIncidents->where('severity', 'high')->count(); $incidentsMedium = $activeIncidents->where('severity', 'medium')->count(); $incidentsLow = $activeIncidents->where('severity', 'low')->count(); // Endpoint Types (Workstation, Server, etc.) $endpointTypes = collect($endpoints)->groupBy(function ($e) { return ucfirst(strtolower($e['endpoint_type'] ?? 'Unknown')); })->map->count(); return [ 'incidents_total' => $activeIncidents->count(), 'incidents_critical' => $incidentsCritical, 'incidents_high' => $incidentsHigh, 'incidents_medium' => $incidentsMedium, 'incidents_low' => $incidentsLow, 'endpoints_total' => $endpointsTotal, 'endpoints_connected' => $endpointsConnected, 'endpoints_disconnected' => $endpointsDisconnected, 'endpoints_types' => $endpointTypes, // New Data 'recent_incidents' => collect($incidents) ->filter(fn($i) => !in_array($i['status'] ?? '', ['resolved_true_positive', 'resolved_false_positive'])) ->sortByDesc('incident_id') ->slice(0, 10) ->values() ->all() ]; } }