/** * Execute the command. * * @param array $incidents * @param int $evidenceID * * @return array */ public function save($incidents, $evidenceID) { $ticketCount = 0; $incidentCount = 0; $incidentsIgnored = 0; foreach ($incidents as $incident) { /* Here we will seek through all the incidents and look if there is an existing ticket. We will split * them up into two seperate arrays: $incidentsNew and $incidents$known. We can save all the known * incidents in the DB with a single incident saving loads of queries * * IP Owner is leading, as in most cases even if the domain is moved * The server might still have a problem. Next to the fact that domains * Arent transferred to a new owner 'internally' anyways. * * So we do a lookup based on the IP same as with the 3.x engine. After * the lookup we check wither the domain contact was changed, if so we UPDATE * the ticket and put a note somewhere about it. This way the IP owner does * not get a new ticket on this matter and the new domain owner is getting updates. * * As the ASH link is based on the contact code, the old domain owner will not * have any access to the ticket anymore. */ // If an incident is too old we are ignoring it if (config('main.reports.min_lastseen') !== false && strtotime(config('main.reports.min_lastseen')) !== false && strtotime(config('main.reports.min_lastseen') . ' ago') > $incident->timestamp) { Log::debug(get_class($this) . ': ' . 'is ignoring incident because its older then ' . config('main.reports.min_lastseen')); continue; } // Lookup the ip contact and if needed the domain contact too $findContact = new FindContact(); $ipContact = $findContact->byIP($incident->ip); if ($incident->domain != '') { $domainContact = $findContact->byDomain($incident->domain); } else { $domainContact = $findContact->undefined(); } /* * Ignore the incident if both ip and domain contacts are undefined and the resolving of an contact * was required. This is handy to ignore any reports that are not considered local, but use * with caution as it might just ignore anything if your IP/domains are not correctly configured */ if ($ipContact->reference == 'UNDEF' && $domainContact->reference == 'UNDEF' && config('main.reports.resolvable_only') === true) { if (!empty($domainContact) && $domainContact->reference == 'UNDEF' || empty($domainContact)) { Log::debug(get_class($this) . ': ' . 'is ignoring incident because there is no IP or Domain contact'); continue; } } /* * Search to see if there is an existing ticket for this incident classification */ $ticket = Ticket::where('ip', '=', $incident->ip)->where('class_id', '=', $incident->class, 'AND')->where('ip_contact_reference', '=', $ipContact->reference, 'AND')->where('status_id', '!=', 'CLOSED', 'AND')->get(); if ($ticket->count() === 0) { /* * If there are no search results then there is no existing ticket and we should create one */ $ticketCount++; $newTicket = new Ticket(); $newTicket->ip = $incident->ip; $newTicket->domain = empty($incident->domain) ? '' : $incident->domain; $newTicket->class_id = $incident->class; $newTicket->type_id = $incident->type; $newTicket->ip_contact_account_id = $ipContact->account_id; $newTicket->ip_contact_reference = $ipContact->reference; $newTicket->ip_contact_name = $ipContact->name; $newTicket->ip_contact_email = $ipContact->email; $newTicket->ip_contact_api_host = $ipContact->api_host; $newTicket->ip_contact_auto_notify = $ipContact->auto_notify; $newTicket->ip_contact_notified_count = 0; $newTicket->domain_contact_account_id = $domainContact->account_id; $newTicket->domain_contact_reference = $domainContact->reference; $newTicket->domain_contact_name = $domainContact->name; $newTicket->domain_contact_email = $domainContact->email; $newTicket->domain_contact_api_host = $domainContact->api_host; $newTicket->domain_contact_auto_notify = $domainContact->auto_notify; $newTicket->domain_contact_notified_count = 0; $newTicket->status_id = 'OPEN'; $newTicket->last_notify_count = 0; $newTicket->last_notify_timestamp = 0; // Validate the model before saving $validator = Validator::make(json_decode(json_encode($newTicket), true), Ticket::createRules()); if ($validator->fails()) { return $this->error('DevError: Internal validation failed when saving the Ticket object ' . implode(' ', $validator->messages()->all())); } $newTicket->save(); $newEvent = new Event(); $newEvent->evidence_id = $evidenceID; $newEvent->information = $incident->information; $newEvent->source = $incident->source; $newEvent->ticket_id = $newTicket->id; $newEvent->timestamp = $incident->timestamp; // Validate the model before saving $validator = Validator::make(json_decode(json_encode($newEvent), true), Event::createRules()); if ($validator->fails()) { return $this->error('DevError: Internal validation failed when saving the Event object ' . implode(' ', $validator->messages()->all())); } $newEvent->save(); } elseif ($ticket->count() === 1) { /* * There is an existing ticket, so we just need to add the incident to this ticket. If the * incident is an exact match we consider it a duplicate and will ignore it. */ $ticket = $ticket[0]; if (Event::where('information', '=', $incident->information)->where('source', '=', $incident->source)->where('ticket_id', '=', $ticket->id)->where('timestamp', '=', $incident->timestamp)->exists()) { $incidentsIgnored++; } else { // New unique incident, so we will save this $incidentCount++; $newEvent = new Event(); $newEvent->evidence_id = $evidenceID; $newEvent->information = $incident->information; $newEvent->source = $incident->source; $newEvent->ticket_id = $ticket->id; $newEvent->timestamp = $incident->timestamp; // Validate the model before saving $validator = Validator::make(json_decode(json_encode($newEvent), true), Event::createRules()); if ($validator->fails()) { return $this->error('DevError: Internal validation failed when saving the Event object ' . implode(' ', $validator->messages()->all())); } $newEvent->save(); /* * If the reference has changed for the domain owner, then we update the ticket with the new * domain owner. We not check if anything else then the reference has changed. If you change the * contact data you have the option to propogate it onto open tickets. */ if (!empty($incident->domain) && $domainContact !== false && $domainContact->reference !== $ticket->domain_contact_reference) { $ticket->domain_contact_reference = $domainContact->reference; $ticket->domain_contact_name = $domainContact->name; $ticket->domain_contact_email = $domainContact->email; $ticket->domain_contact_api_host = $domainContact->api_host; $ticket->domain_contact_auto_notify = $domainContact->auto_notify; $ticket->account_id = $domainContact->account->id; } /* * Upgrade the type if the received event has a higher priority type included */ $priority = ['INFO', 'ABUSE', 'ESCALATION']; if (array_search($ticket->type_id, $priority) < array_search($incident->type, $priority)) { $ticket->type_id = $incident->type; } /* * If the ticket was set to resolved, move it back to open */ if ($ticket->status_id == 'RESOLVED') { $ticket->status_id = 'OPEN'; } /* * Walk thru the escalation upgrade path, and upgrade if required */ //echo config("escalations.{$ticket->class_id}.abuse.enabled"); if (is_array(config("escalations.{$ticket->class_id}"))) { // There is a specific escalation path for this class $escalationPath = $ticket->class_id; } else { // Use the default escalation path $escalationPath = 'DEFAULT'; } // Check if all the values are set, or log a warning that were skipping escalation paths if (!is_bool(empty(config("escalations.{$escalationPath}.abuse.enabled"))) || !is_numeric(config("escalations.{$escalationPath}.abuse.threshold")) || !is_bool(empty(config("escalations.{$escalationPath}.escalation.enabled"))) || !is_numeric(config("escalations.{$escalationPath}.escalation.threshold"))) { Log::warning(get_class($this) . ': ' . 'Escalation path settings are missing or incomplete. Skipping this phase'); } else { // Now actually check if anything needs to be changed and if so, change it. if (!empty(config("escalations.{$escalationPath}.abuse.enabled")) && !empty(config("escalations.{$escalationPath}.abuse.threshold")) && $ticket->events->count() > config("escalations.{$escalationPath}.abuse.threshold") && $ticket->type_id == 'INFO') { // Upgrade to abuse Log::debug(get_class($this) . ': ' . "An escalation path threshold has been reached for ticket {$ticket->id}, " . 'threshold: ' . config("escalations.{$escalationPath}.abuse.threshold") . ', ' . 'setting: info -> abuse'); $ticket->type_id = 'ABUSE'; } if (!empty(config("escalations.{$escalationPath}.escalation.enabled")) && !empty(config("escalations.{$escalationPath}.escalation.threshold")) && $ticket->events->count() > config("escalations.{$escalationPath}.escalation.threshold") && $ticket->type_id == 'ABUSE') { // Upgrade to escalation Log::debug(get_class($this) . ': ' . "An escalation path threshold has been reached for ticket {$ticket->id}, " . 'threshold: ' . config("escalations.{$escalationPath}.escalation.threshold") . ', ' . 'setting: abuse -> escalation'); $ticket->type_id = 'ESCALATION'; } } // Validate the model before saving $validator = Validator::make(json_decode(json_encode($ticket), true), Ticket::createRules()); if ($validator->fails()) { return $this->error('DevError: Internal validation failed when saving the Ticket object ' . implode(' ', $validator->messages()->all())); } $ticket->save(); } } else { /* * We should not never have more then two open tickets for the same case. If this happens there is a * fault in the aggregator which must be resolved first. Until then we will permfail here. */ $this->error('Unable to link to ticket, multiple open tickets found for same incident type'); } } Log::debug(get_class($this) . ': ' . "has completed creating {$ticketCount} new tickets, " . "linking {$incidentCount} new incidents and ignored {$incidentsIgnored} duplicates"); return $this->success(''); }
/** * @param $ticket * @param $account */ private function replayTicket($ticket, $account) { // Create the ticket as usual $newTicket = $this->createTicket($ticket, $account); // Now this is the little magic, we need to calculate the offset and make sure that the first // and lastseen moments exactly match and the increments between those events are within that // same timeframe, but NOT duplicate. $firstSeen = (int) $ticket->FirstSeen; $lastSeen = (int) $ticket->LastSeen; $elapsed = $lastSeen - $firstSeen; $step = (int) round($elapsed / $ticket->ReportCount); $offset = ['first' => $firstSeen, 'last' => $lastSeen, 'elapsed' => $elapsed, 'step' => $step]; for ($counter = 0; $counter <= $ticket->ReportCount; $counter++) { $offset[$counter] = $firstSeen + $counter * $step; } // Make sure we end at the right point in time end($offset); $key = key($offset); $offset[$key] = $lastSeen; // Now recreate all the events for ($counter = 0; $counter <= $ticket->ReportCount; $counter++) { // Build new evidence file and write the evidence into the archive $evidence = new EvidenceSave(); $evidenceData = ['createdBy' => '*****@*****.**', 'receivedOn' => time(), 'submittedData' => json_decode(json_encode($ticket), true), 'attachments' => []]; if (!empty($attachment)) { $evidenceData['attachments'][0] = $attachment; } $evidenceFile = $evidence->save(json_encode($evidenceData)); // Save the file reference into the database $evidenceSave = new Evidence(); $evidenceSave->filename = $evidenceFile; $evidenceSave->sender = '*****@*****.**'; $evidenceSave->subject = 'Migrated evidence with a little magic'; $evidenceSave->save(); // Write the event $newEvent = new Event(); $newEvent->evidence_id = $evidenceSave->id; $newEvent->information = $ticket->Information; $newEvent->source = $ticket->Source; $newEvent->ticket_id = $newTicket->id; $newEvent->timestamp = $offset[$counter]; // Validate the model before saving $validator = Validator::make(json_decode(json_encode($newEvent), true), Event::createRules()); if ($validator->fails()) { $this->error('DevError: Internal validation failed when saving the Event object ' . implode(' ', $validator->messages()->all())); $this->exception(); } $newEvent->save(); } }