/** * @param Ticket $ticket * @param int|null $newstatus * * @return bool */ public static function status($ticket, $newstatus = null) { /* * status list is defined in config/types.php */ if (array_key_exists(strtoupper($newstatus), config('types.status.abusedesk'))) { $ticket->status_id = $newstatus; $ticket->save(); return true; } else { return false; } }
/** * Update IP contact for a ticket * * @param Ticket $ticket */ public static function ipContact($ticket) { $ipContact = FindContact::byIP($ticket['ip']); // Update IP Contact fields in ticket $ticket->ip_contact_account_id = $ipContact->account_id; $ticket->ip_contact_reference = $ipContact->reference; $ticket->ip_contact_name = $ipContact->name; $ticket->ip_contact_email = $ipContact->email; $ticket->ip_contact_api_host = $ipContact->api_host; $ticket->ip_contact_api_key = $ipContact->api_key; $ticket->ip_contact_auto_notify = $ipContact->auto_notify; $ticket->save(); }
/** * Method to add a note to a ticket * * @param integer $ticketID * @param string $token * @return \Illuminate\Http\Response */ public function addNote($ticketID, $token) { $brand = false; $submittor = false; $ticket = Ticket::find($ticketID); $AshAuthorisedBy = Request::get('AshAuthorisedBy'); if ($AshAuthorisedBy == 'TokenIP') { $brand = $ticket->accountIp->brand; $submittor = trans('ash.basic.ip') . ' ' . trans('ash.communication.contact'); } if ($AshAuthorisedBy == 'TokenDomain') { $brand = $ticket->accountDomain->brand; $submittor = trans('ash.basic.domain') . ' ' . trans('ash.communication.contact'); } if (empty($brand) || empty($submittor)) { abort(500); } $text = Input::get('text'); if (empty($text)) { $message = 'You cannot add an empty message!'; } else { $message = 'Note has been added.'; $note = new Note(); $note->ticket_id = $ticket->id; $note->submitter = $submittor; $note->text = $text; $note->save(); } return view('ash')->with('brand', $brand)->with('ticket', $ticket)->with('token', $token)->with('message', $message); }
/** * Method to add a note to a ticket. * * @param int $ticketID * @param string $token * * @return \Illuminate\Http\Response */ public function addNote($ticketID, $token) { $submittor = false; $ticket = Ticket::find($ticketID); $AshAuthorisedBy = Request::get('AshAuthorisedBy'); if ($AshAuthorisedBy == 'TokenIP') { $account = Account::find($ticket->ip_contact_account_ip); $submittor = trans('ash.basic.ip') . ' ' . trans('ash.communication.contact'); } if ($AshAuthorisedBy == 'TokenDomain') { $account = Account::find($ticket->domain_contact_account_id); $submittor = trans('ash.basic.domain') . ' ' . trans('ash.communication.contact'); } $brand = empty($account) ? Brand::getSystemBrand() : $account->brand; if (empty($brand) || empty($submittor)) { abort(500); } $changeStatus = Input::get('changeStatus'); if ($changeStatus == 'IGNORED' || $changeStatus == 'RESOLVED') { $ticket->contact_status_id = $changeStatus; $ticket->save(); } $text = Input::get('text'); if (empty($text) || strlen($text) < 1) { $message = 'noteEmpty'; } else { $message = 'noteAdded'; $note = new Note(); $note->ticket_id = $ticket->id; $note->submitter = $submittor; $note->text = $text; $note->save(); } return view('ash')->with('brand', $brand)->with('ticket', $ticket)->with('allowedChanges', $this->allowedStatusChanges($ticket))->with('token', $token)->with('message', $message); }
/** * @param $request * @param Closure $next * @return \BladeView|bool|\Illuminate\Contracts\Routing\ResponseFactory * \Illuminate\Contracts\View\Factory * \Illuminate\View\View * \Symfony\Component\HttpFoundation\Response * @throws \Exception */ public function handle($request, Closure $next) { /* * TODO: find how laravel passes $ticketID and $token from the controller to middleware * this will remove the need of parsing the URI ourselves */ $uri = explode('/', Request::path()); if ($uri[0] === 'ash' && $uri['1'] === 'collect') { $ticketID = $uri[2]; $token = $uri[3]; $ticket = Ticket::find($ticketID); if (!empty($ticket)) { // Prevent unauthorized access by UNDEF contact tokens(default) $validTokenIP = md5(Uuid::generate(4)); $validTokenDomain = md5(Uuid::generate(4)); if ($ticket->ip_contact_reference != 'UNDEF') { $validTokenIP = md5($ticket->id . $ticket->ip . $ticket->ip_contact_reference); } if ($token == $validTokenIP) { $request->merge(['AshAuthorisedBy' => 'TokenIP']); return $next($request); } if ($ticket->domain_contact_reference != 'UNDEF') { $request->merge(['AshAuthorisedBy' => 'TokenDomain']); $validTokenDomain = md5($ticket->id . $ticket->domain . $ticket->domain_contact_reference); } if ($token == $validTokenDomain) { return $next($request); } } } return $request->ajax ? response('Unauthorized.', 401) : view('errors.403'); }
/** * Export listing to CSV format. * * @return Response */ public function export() { $tickets = Ticket::all(); $columns = ['id' => 'Ticket ID']; $output = '"' . implode('", "', $columns) . '"' . PHP_EOL; foreach ($tickets as $ticket) { $row = [$ticket->id]; $output .= '"' . implode('", "', $row) . '"' . PHP_EOL; } return response(substr($output, 0, -1), 200)->header('Content-Type', 'text/csv')->header('Content-Disposition', 'attachment; filename="Tickets.csv"'); }
/** * Display a listing of the resource. * @return Response */ public function index($ticketID, $token) { $ticket = Ticket::find($ticketID); $validTokenIP = md5($ticket->id . $ticket->ip . $ticket->ip_contact_reference); $validTokenDomain = md5($ticket->id . $ticket->ip . $ticket->domain_contact_reference); if ($token == $validTokenIP || $token == $validTokenDomain) { return view('ash')->with('ticket', $ticket); } else { return view('errors.403'); } }
/** * Display a listing of the resource. * * @return Response */ public function index($ticketID, $token) { $ticket = Ticket::find($ticketID); // 6bb1aef09ea536260e3afe3fb9b432e4 // c1eee3ce87f1fd774eb8819c820fa5be $validTokenIP = md5($ticket->id . $ticket->ip . $ticket->ip_contact_reference); $validTokenDomain = md5($ticket->id . $ticket->ip . $ticket->domain_contact_reference); if ($token == $validTokenIP || $token == $validTokenDomain) { return view('ash')->with('ticket', $ticket); } else { return view('errors.403'); } }
/** * Export tickets to CSV format. * * @param string $format * @return \Illuminate\Http\Response */ public function export($format) { // TODO #AIO-?? ExportProvider - (mark) Move this into an ExportProvider or something s? $tickets = Ticket::all(); if ($format === 'csv') { $columns = ['id' => 'Ticket ID', 'ip' => 'IP address', 'class_id' => 'Classification', 'type_id' => 'Type', 'first_seen' => 'First seen', 'last_seen' => 'Last seen', 'event_count' => 'Events', 'status_id' => 'Ticket Status']; $output = '"' . implode('", "', $columns) . '"' . PHP_EOL; foreach ($tickets as $ticket) { $row = [$ticket->id, $ticket->ip, $ticket->class_id, $ticket->type_id, $ticket->firstEvent[0]->seen, $ticket->lastEvent[0]->seen, $ticket->events->count(), trans('types.status.' . $ticket->status_id . '.name')]; $output .= '"' . implode('", "', $row) . '"' . PHP_EOL; } return response(substr($output, 0, -1), 200)->header('Content-Type', 'text/csv')->header('Content-Disposition', 'attachment; filename="Tickets.csv"'); } return Redirect::route('admin.contacts.index')->with('message', "The requested format {$format} is not available for exports"); }
/** * Display a listing of the resource. * * @return \Illuminate\Http\RedirectResponse */ public function index() { $classCounts = []; foreach ((array) Lang::get('classifications') as $classID => $classInfo) { $classTotal = new \stdClass(); $tickets = Ticket::where('class_id', $classID); $ticketCount = $tickets->count(); $tickets = $tickets->get(); if ($ticketCount !== 0) { $classTotal->name = $classInfo['name']; $classTotal->tickets = $tickets->count(); $classCounts[] = $classTotal; } } return view('analytics')->with('classCounts', $classCounts)->with('auth_user', $this->auth_user); }
/** * Create a list of tickets that need outgoing notifications. * * @param int|bool $ticket Ticket ID * @param string|bool $reference ReferenceName of contact in ticket * @param bool|bool $force Force sending even when there are no new events * @param string $only Only send to ('ip', 'domain' or null (both)) * * @return array $notificationList List of notifications to send */ public function buildList($ticket = false, $reference = false, $force = false, $only = null) { /* * Select a list of tickets that are not closed(2) by default or add ticket / reference * conditions if options were passed along. */ $selection = []; $search = Ticket::where('id', '>', '0'); if (!$force) { $search = Ticket::where('status_id', '!=', 'CLOSED'); } if ($ticket !== false) { $search->where('id', '=', $ticket); } if ($reference !== false) { $search->where('ip_contact_reference', '=', $reference)->orwhere('domain_contact_reference', '=', $reference); } $tickets = $search->get(); $validator = Validator::make(['notification info_interval' => strtotime(config('main.notifications.info_interval') . ' ago'), 'notification abuse_interval' => strtotime(config('main.notifications.abuse_interval') . ' ago'), 'notification min_lastseen' => strtotime(config('main.notifications.min_lastseen') . ' ago')], ['notification info_interval' => 'required|timestamp', 'notification abuse_interval' => 'required|timestamp', 'notification min_lastseen' => 'required|timestamp']); if ($validator->fails()) { $messages = $validator->messages(); $message = ''; foreach ($messages->all() as $messagePart) { $message .= $messagePart . PHP_EOL; } return $message; } // If an invalid value for $only is given, set default (null = both) if (!in_array($only, ['ip', 'domain'])) { $only = null; } $sendInfoAfter = strtotime(config('main.notifications.info_interval') . ' ago'); $sendAbuseAfter = strtotime(config('main.notifications.abuse_interval') . ' ago'); $sendNotOlderThen = strtotime(config('main.notifications.min_lastseen') . ' ago'); foreach ($tickets as $ticket) { /* * Only send a notification if there is something new to report * or if this is the first notification. */ if ($ticket->last_notify_count == $ticket->events->count() && $ticket->last_notify_count != 0 && $force !== true) { continue; } else { /* * Filter outgoing notifications and aggregate them by reference so we can send out a single * notifications for multiple tickets if needed. */ if ($force !== true) { // Skip if status Ignored if ($ticket->status_id == 'IGNORED') { continue; } // Skip if type Info and contact status Ignored if ($ticket->type_id == 'INFO' && $ticket->contact_status_id == 'IGNORED') { continue; } // Skip if type Info (1) and last notification was send after info interval if ($ticket->last_notify_count != 0 && $ticket->type_id == 'INFO' && $ticket->last_notify_timestamp >= $sendInfoAfter) { continue; } // Skip if type Info (1) and last notification was send after abuse interval if ($ticket->last_notify_count != 0 && $ticket->type_id != 'INFO' && $ticket->last_notify_timestamp >= $sendAbuseAfter) { continue; } // Skip if the event received is older the minimal last seen if ($ticket->lastEvent[0]->timestamp <= $sendNotOlderThen) { continue; } // Conditions just for IP contacts if (!empty($ticket->ip_contact_reference) && $ticket->ip_contact_reference != 'UNDEF' && $ticket->ip_contact_auto_notify == true && $only != 'domain') { $selection[$ticket->ip_contact_reference]['ip'][] = $ticket; } // Conditions just for Domain contacts if (!empty($ticket->domain_contact_reference) && $ticket->domain_contact_reference != 'UNDEF' && $ticket->domain_contact_auto_notify == true && $ticket->domain_contact_reference != $ticket->ip_contact_reference && $only != 'ip') { $selection[$ticket->domain_contact_reference]['domain'][] = $ticket; } } else { /* * Notifications are forced, therefor we skip all the checks except empty/undef */ // Conditions just for IP contacts if (!empty($ticket->ip_contact_reference) && $ticket->ip_contact_reference != 'UNDEF' && $only != 'domain') { $selection[$ticket->ip_contact_reference]['ip'][] = $ticket; } // Conditions just for Domain contacts if (!empty($ticket->domain_contact_reference) && $ticket->domain_contact_reference != 'UNDEF' && $ticket->domain_contact_reference != $ticket->ip_contact_reference && $only != 'ip') { $selection[$ticket->domain_contact_reference]['domain'][] = $ticket; } } } } return $selection; }
/** * Walk thru all tickets to see which need closing. * * @return bool */ private function ticketsClosing() { Log::info(get_class($this) . ': Housekeeper is starting to close old tickets'); $closeOlderThen = strtotime(config('main.housekeeping.tickets_close_after') . ' ago'); $validator = Validator::make(['mailarchive_remove_after' => $closeOlderThen], ['mailarchive_remove_after' => 'required|timestamp']); if ($validator->fails()) { $messages = $validator->messages(); $message = ''; foreach ($messages->all() as $messagePart) { $message .= $messagePart . PHP_EOL; } echo $message; return false; } else { $tickets = Ticket::where('status_id', '!=', 'CLOSED')->get(); foreach ($tickets as $ticket) { if ($ticket->lastEvent[0]->timestamp <= $closeOlderThen && strtotime($ticket->created_at) <= $closeOlderThen) { $ticket->update(['status_id' => 'CLOSED']); } } } return true; }
/** * {@inheritdoc}. */ protected function findAll() { return Ticket::all(); }
/** * Execute the command. * * @param array $events * @param integer $evidenceID * @return array */ public function save($events, $evidenceID) { $ticketCount = 0; $eventCount = 0; $eventsIgnored = 0; foreach ($events as $event) { /* Here we will seek through all the events and look if there is an existing ticket. We will split them up * into two seperate arrays: $eventsNew and $events$known. We can save all the known events in the DB with * a single event 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 event 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') > $event['timestamp']) { Log::debug(get_class($this) . ': ' . "is ignoring event because its older then " . config('main.reports.min_lastseen')); continue; } // Start with building a classification lookup table and switch out name for ID foreach ((array) Lang::get('classifications') as $classID => $class) { if ($class['name'] == $event['class']) { $event['class'] = $classID; } } // Also build a types lookup table and switch out name for ID foreach ((array) Lang::get('types.type') as $typeID => $type) { if ($type['name'] == $event['type']) { $event['type'] = $typeID; } } // Lookup the ip contact and if needed the domain contact too $findContact = new FindContact(); $ipContact = $findContact->byIP($event['ip']); if ($event['domain'] != '') { $domainContact = $findContact->byDomain($event['domain']); } else { $domainContact = $findContact->undefined(); } /* * Ignore the event 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 event because there is no IP or Domain contact"); continue; } } /* * Search to see if there is an existing ticket for this event classification */ $ticket = Ticket::where('ip', '=', $event['ip'])->where('class_id', '=', $event['class'], 'AND')->where('type_id', '=', $event['type'], 'AND')->where('ip_contact_reference', '=', $ipContact->reference, 'AND')->where('status_id', '!=', 2, '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 = $event['ip']; $newTicket->domain = empty($event['domain']) ? '' : $event['domain']; $newTicket->class_id = $event['class']; $newTicket->type_id = $event['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_api_key = $ipContact->api_key; $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_api_key = $domainContact->api_key; $newTicket->domain_contact_auto_notify = $domainContact->auto_notify; $newTicket->domain_contact_notified_count = 0; $newTicket->status_id = 1; $newTicket->last_notify_count = 0; $newTicket->last_notify_timestamp = 0; $newTicket->save(); $newEvent = new Event(); $newEvent->evidence_id = $evidenceID; $newEvent->information = $event['information']; $newEvent->source = $event['source']; $newEvent->ticket_id = $newTicket->id; $newEvent->timestamp = $event['timestamp']; $newEvent->save(); } elseif ($ticket->count() === 1) { /* * There is an existing ticket, so we just need to add the event to this ticket. If the event is an * exact match we consider it a duplicate and will ignore it. */ $ticket = $ticket[0]; if (Event::where('information', '=', $event['information'])->where('source', '=', $event['source'])->where('ticket_id', '=', $ticket->id)->where('timestamp', '=', $event['timestamp'])->exists()) { $eventsIgnored++; } else { // New unique event, so we will save this $eventCount++; $newEvent = new Event(); $newEvent->evidence_id = $evidenceID; $newEvent->information = $event['information']; $newEvent->source = $event['source']; $newEvent->ticket_id = $ticket->id; $newEvent->timestamp = $event['timestamp']; $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($event['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_api_key = $domainContact->api_key; $ticket->domain_contact_auto_notify = $domainContact->auto_notify; $ticket->account_id = $domainContact->account->id; $ticket->save(); } // TODO: If this is an abuse/escalation ticket and currently 'resolved' then put status back to Open // TODO: Implement escalation triggers } } 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->failed('Unable to link to ticket, multiple open tickets found for same event type'); } } Log::debug(get_class($this) . ': ' . "has completed creating {$ticketCount} new tickets, " . "linking {$eventCount} new events and ignored {$eventsIgnored} duplicates"); $this->success(''); }
/** * Execute the command. * * @return array */ public function handle() { // Start with building a classification lookup table $classNames = []; foreach (Lang::get('classifications') as $classID => $class) { $classNames[$class['name']] = $classID; } // Also build a types lookup table $typeNames = []; foreach (Lang::get('types.type') as $typeID => $type) { $typeNames[$type['name']] = $typeID; } // Also build a status lookup table $statusNames = []; foreach (Lang::get('types.status') as $statusID => $status) { $statusNames[$status['name']] = $statusID; } foreach ($this->events as $event) { /* Here we will thru all the events and look if these is an existing ticket. We will split them up into * two seperate arrays: $eventsNew and $events$known. We can save all the known events in the DB with * a single event 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. */ // Lookup the ip contact and if needed the domain contact too $ipContact = FindContact::byIP($event['ip']); if ($event['domain'] != '') { $domainContact = FindContact::byDomain($event['domain']); } // Search to see if there is an existing ticket for this event classification // Todo: somehow add the domain too!!!! $search = Ticket::where('ip', '=', $event['ip'])->where('class_id', '=', $classNames[$event['class']], 'AND')->where('type_id', '=', $typeNames[$event['type']], 'AND')->where('ip_contact_reference', '=', $ipContact->reference, 'AND')->where('status_id', '!=', 2, 'AND')->get(); if ($search->count() === 0) { // Build an array with all new tickes and save it with its related event and evidence link. $newTicket = new Ticket(); $newTicket->ip = $event['ip']; $newTicket->domain = $event['domain']; $newTicket->class_id = $classNames[$event['class']]; $newTicket->type_id = $typeNames[$event['type']]; $newTicket->ip_contact_reference = $ipContact->reference; $newTicket->ip_contact_name = $ipContact->name; $newTicket->ip_contact_email = $ipContact->email; $newTicket->ip_contact_rpchost = $ipContact->rpc_host; $newTicket->ip_contact_rpckey = $ipContact->rpc_key; $newTicket->ip_contact_auto_notify = $ipContact->auto_notify; if ($event['domain'] != '') { $newTicket->domain_contact_reference = $domainContact->reference; $newTicket->domain_contact_name = $domainContact->name; $newTicket->domain_contact_email = $domainContact->email; $newTicket->domain_contact_rpchost = $domainContact->rpc_host; $newTicket->domain_contact_rpckey = $domainContact->rpc_key; $newTicket->domain_contact_auto_notify = $domainContact->auto_notify; } $newTicket->status_id = 1; $newTicket->notified_count = 0; $newTicket->last_notify_count = 0; $newTicket->last_notify_timestamp = 0; $newTicket->save(); $newEvent = new Event(); $newEvent->evidence_id = $this->evidenceID; $newEvent->information = $event['information']; $newEvent->source = $event['source']; $newEvent->ticket_id = $newTicket->id; $newEvent->timestamp = $event['timestamp']; $newEvent->save(); // Call notifier action handler, type new } elseif ($search->count() === 1) { $ticketID = $search[0]->id; if (Event::where('information', '=', $event['information'])->where('source', '=', $event['source'])->where('ticket_id', '=', $ticketID)->where('timestamp', '=', $event['timestamp'])->exists()) { // Exact duplicate match so we will ignore this event } else { // New unique event, so we will save this $newEvent = new Event(); $newEvent->evidence_id = $this->evidenceID; $newEvent->information = $event['information']; $newEvent->source = $event['source']; $newEvent->ticket_id = $ticketID; $newEvent->timestamp = $event['timestamp']; $newEvent->save(); // Call notifier action handler, type update } // This is an existing ticket } else { $this->failed('Unable to link to ticket, multiple open tickets found for same event type'); } } $this->success(''); }
/** * 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(''); }
/** * Export tickets to CSV format. * * @param string $format * * @return \Illuminate\Http\Response */ public function export($format) { // TODO #AIO-?? ExportProvider - (mark) Move this into an ExportProvider or something? // only export all tickets when we are in the systemaccount $auth_account = $this->auth_user->account; if ($auth_account->isSystemAccount()) { $tickets = Ticket::all(); } else { $tickets = Ticket::select('tickets.*')->where('ip_contact_account_id', $auth_account->id)->orWhere('domain_contact_account_id', $auth_account); } if ($format === 'csv') { $columns = ['id' => 'Ticket ID', 'ip' => 'IP address', 'class_id' => 'Classification', 'type_id' => 'Type', 'first_seen' => 'First seen', 'last_seen' => 'Last seen', 'event_count' => 'Events', 'status_id' => 'Ticket Status']; $output = '"' . implode('", "', $columns) . '"' . PHP_EOL; foreach ($tickets as $ticket) { $row = [$ticket->id, $ticket->ip, trans("classifications.{$ticket->class_id}.name"), trans("types.type.{$ticket->type_id}.name"), $ticket->firstEvent[0]->seen, $ticket->lastEvent[0]->seen, $ticket->events->count(), trans("types.status.abusedesk.{$ticket->status_id}.name")]; $output .= '"' . implode('", "', $row) . '"' . PHP_EOL; } return response(substr($output, 0, -1), 200)->header('Content-Type', 'text/csv')->header('Content-Disposition', 'attachment; filename="Tickets.csv"'); } return Redirect::route('admin.contacts.index')->with('message', "The requested format {$format} is not available for exports"); }
/** * {@inheritdoc}. */ protected function getObjectByArguments() { return Ticket::find($this->argument('id')); }
/** * Walk thru all tickets to see which need closing * * @return boolean */ private function ticketsClosing() { $closeOlderThen = strtotime(config('main.housekeeping.tickets_close_after') . " ago"); $validator = Validator::make(['mailarchive_remove_after' => $closeOlderThen], ['mailarchive_remove_after' => 'required|timestamp']); if ($validator->fails()) { $messages = $validator->messages(); $message = ''; foreach ($messages->all() as $messagePart) { $message .= $messagePart . PHP_EOL; } echo $message; return false; } else { $tickets = Ticket::where('status_id', '!=', '2')->get(); foreach ($tickets as $ticket) { if ($ticket->lastEvent[0]->timestamp <= $closeOlderThen) { $ticket->update(['status_id' => 2]); } } } return true; }
/** * @param $ticket * @param $account * * @return Ticket */ private function createTicket($ticket, $account) { // Start with building a classification lookup table and switch out name for ID // But first fix the names: $replaces = ['Possible DDOS sending NTP Server' => 'Possible DDoS sending Server', 'Possible DDOS sending DNS Server' => 'Possible DDoS sending Server']; $old = array_keys($replaces); $new = array_values($replaces); $ticket->Class = str_replace($old, $new, $ticket->Class); foreach ((array) Lang::get('classifications') as $classID => $class) { if ($class['name'] == $ticket->Class) { $ticket->Class = $classID; } } // Also build a types lookup table and switch out name for ID foreach ((array) Lang::get('types.type') as $typeID => $type) { // Consistancy fixes: $ticket->Type = strtoupper($ticket->Type); if ($type['name'] == $ticket->Type) { $ticket->Type = $typeID; } } // Create the ticket $newTicket = new Ticket(); $newTicket->id = $ticket->ID; $newTicket->ip = $ticket->IP; $newTicket->domain = empty($ticket->Domain) ? '' : $ticket->Domain; $newTicket->class_id = $ticket->Class; $newTicket->type_id = $ticket->Type; $newTicket->ip_contact_account_id = $account->id; $newTicket->ip_contact_reference = $ticket->CustomerCode; $newTicket->ip_contact_name = $ticket->CustomerName; $newTicket->ip_contact_email = $ticket->CustomerContact; $newTicket->ip_contact_api_host = ''; $newTicket->ip_contact_auto_notify = $ticket->AutoNotify; $newTicket->ip_contact_notified_count = $ticket->NotifiedCount; $domainContact = FindContact::undefined(); $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->last_notify_count = $ticket->LastNotifyReportCount; $newTicket->last_notify_timestamp = $ticket->LastNotifyTimestamp; $newTicket->created_at = Carbon::createFromTimestamp($ticket->FirstSeen); $newTicket->updated_at = Carbon::parse($ticket->LastModified); if ($ticket->Status == 'CLOSED') { $newTicket->status_id = 'CLOSED'; } elseif ($ticket->Status == 'OPEN') { $newTicket->status_id = 'OPEN'; } else { $this->error('Unknown ticket status'); $this->exception(); } if ($ticket->CustomerResolved == 1) { $newTicket->contact_status_id = 'RESOLVED'; } elseif ($ticket->CustomerIgnored == 1) { $newTicket->contact_status_id = 'IGNORED'; } else { $newTicket->contact_status_id = 'OPEN'; } // Validate the model before saving $validator = Validator::make(json_decode(json_encode($newTicket), true), Ticket::createRules()); if ($validator->fails()) { $this->error('DevError: Internal validation failed when saving the Ticket object ' . implode(' ', $validator->messages()->all())); var_dump($ticket); $this->exception(); } $newTicket->save(); return $newTicket; }
/** * Execute the command. * @return array */ public function handle() { $ticketCount = 0; $eventCount = 0; $eventsIgnored = 0; foreach ($this->events as $event) { /* Here we will thru all the events and look if these is an existing ticket. We will split them up into * two seperate arrays: $eventsNew and $events$known. We can save all the known events in the DB with * a single event 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. */ // Start with building a classification lookup table and switch out name for ID foreach ((array) Lang::get('classifications') as $classID => $class) { if ($class['name'] == $event['class']) { $event['class'] = $classID; } } // Also build a types lookup table and switch out name for ID foreach ((array) Lang::get('types.type') as $typeID => $type) { if ($type['name'] == $event['type']) { $event['type'] = $typeID; } } // Lookup the ip contact and if needed the domain contact too $ipContact = FindContact::byIP($event['ip']); if ($event['domain'] != '') { $domainContact = FindContact::byDomain($event['domain']); } else { $domainContact = false; } /* * Search to see if there is an existing ticket for this event classification */ $search = Ticket::where('ip', '=', $event['ip'])->where('class_id', '=', $event['class'], 'AND')->where('type_id', '=', $event['type'], 'AND')->where('ip_contact_reference', '=', $ipContact->reference, 'AND')->where('status_id', '!=', 2, 'AND')->get(); if ($search->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 = $event['ip']; $newTicket->domain = $event['domain']; $newTicket->class_id = $event['class']; $newTicket->type_id = $event['type']; $newTicket->ip_contact_reference = $ipContact->reference; $newTicket->ip_contact_name = $ipContact->name; $newTicket->ip_contact_email = $ipContact->email; $newTicket->ip_contact_rpchost = $ipContact->rpc_host; $newTicket->ip_contact_rpckey = $ipContact->rpc_key; $newTicket->ip_contact_auto_notify = $ipContact->auto_notify; if (!empty($event['domain']) && $domainContact !== false) { $newTicket->domain_contact_reference = $domainContact->reference; $newTicket->domain_contact_name = $domainContact->name; $newTicket->domain_contact_email = $domainContact->email; $newTicket->domain_contact_rpchost = $domainContact->rpc_host; $newTicket->domain_contact_rpckey = $domainContact->rpc_key; $newTicket->domain_contact_auto_notify = $domainContact->auto_notify; } $newTicket->status_id = 1; $newTicket->notified_count = 0; $newTicket->last_notify_count = 0; $newTicket->last_notify_timestamp = 0; $newTicket->save(); $newEvent = new Event(); $newEvent->evidence_id = $this->evidenceID; $newEvent->information = $event['information']; $newEvent->source = $event['source']; $newEvent->ticket_id = $newTicket->id; $newEvent->timestamp = $event['timestamp']; $newEvent->save(); // TODO - Call notifier action handler, type new } elseif ($search->count() === 1) { /* * There is an existing ticket, so we just need to add the event to this ticket. If the event is an * exact match we consider it a duplicate and will ignore it. */ $ticketID = $search[0]->id; if (Event::where('information', '=', $event['information'])->where('source', '=', $event['source'])->where('ticket_id', '=', $ticketID)->where('timestamp', '=', $event['timestamp'])->exists()) { $eventsIgnored++; } else { // New unique event, so we will save this $eventCount++; $newEvent = new Event(); $newEvent->evidence_id = $this->evidenceID; $newEvent->information = $event['information']; $newEvent->source = $event['source']; $newEvent->ticket_id = $ticketID; $newEvent->timestamp = $event['timestamp']; $newEvent->save(); // TODO - Update domain owner if changed based on the contactID (reference) // TODO - Call notifier action handler, type update } } 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->failed('Unable to link to ticket, multiple open tickets found for same event type'); } } Log::debug('(JOB ' . getmypid() . ') ' . get_class($this) . ': ' . "has completed creating {$ticketCount} new tickets, " . "linking {$eventCount} new events and ignored {$eventsIgnored} duplicates"); $this->success(''); }
/** * {@inherit docs}. */ protected function getCollectionWithArguments() { return Ticket::where('id', $this->argument('ticket')); }
/** * Retrieve a list of addresses based on open tickets and kick off scanAddress * * @return boolean */ private function scanTickets() { $tickets = Ticket::where('status_id', '!=', '2')->get(); foreach ($tickets as $ticket) { $this->scanAddress($ticket->ip); } return true; }