Skip to content
Snippets Groups Projects
MarkController.php 9.68 KiB
<?php

namespace App\Http\Controllers;

use Cylab\Mark\Client;

use App\Mark;
use App\Feedback;

use Illuminate\Support\Facades\Auth;

class MarkController extends Controller
{
    private function server() : Client
    {
        return Mark::get();
    }

    public function error()
    {
        return view('app.error');
    }

    public function status()
    {
        $status = $this->server()->status();
        $status_history = $this->server()->history();

        $history_memory = $this->extractPoints($status_history, "memory_used");
        $history_load = $this->extractPoints($status_history, "load");

        $history_jobs_executed = $this->extractPoints($status_history, "executor_jobs_executed");
        $history_jobs_execution_rate = $this->computeExecutionRate($history_jobs_executed);

        $history_db_records = $this->extractPoints($status_history, "db_data_count");
        $history_db_records_rate = $this->computeExecutionRate($history_db_records);

        $history_db_evidences = $this->extractPoints($status_history, "db_evidence_count");
        $history_db_evidences_rate = $this->computeExecutionRate($history_db_evidences);

        return view("app.status", [
            "status" => $status,
            "history_memory" => $history_memory,
            "history_load" => $history_load,
            "history_jobs_execution_rate" => $history_jobs_execution_rate,
            "history_db_records_rate" => $history_db_records_rate,
            "history_db_evidences_rate" => $history_db_evidences_rate]);
    }

    public function lastData()
    {
        return view("app.lastdata", ["data" => $this->server()->findLastData()]);
    }

    public function lastEvidences()
    {
        return view(
            "app.lastevidences",
            ["evidences" => $this->server()->findLastEvidences()]
        );
    }

    public function pause()
    {
        $this->server()->pause();
        return redirect(action('MarkController@status'));
    }

    public function resume()
    {
        $this->server()->resume();
        return redirect(action('MarkController@status'));
    }

    public function reload()
    {
        $this->server()->reload();
        return redirect(action('MarkController@status'));
    }

    /**
     * Find
     * @param string $label
     * @return type the detector corresponding to this label.
     * Otherwize redirect to the detectors graph.
     */
    private function findDetector(string $label)
    {
        $detectors = $this->server()->activation();
        $detector = null;
        foreach ($detectors as $d) {
            if ($d->label == $label) {
                $detector = $d;
                break;
            }
        }

        if ($detector === null) {
            session()->flash(
                'error',
                $label . ' is not a detector and does not produce a ranking'
            );
            abort(redirect(action('MarkController@rankingHome')));
        }

        return $detector;
    }

    public function ranking(string $label)
    {

        $detector = $this->findDetector($label);
        $evidences = $this->server()->findEvidence($label);
        $now = \Carbon\Carbon::now();

        return view(
            "app.ranking",
            [
                    "label" => $label,
                    "evidences" => $evidences,
                    "detector" => $detector,
            "now" => $now]
        );
    }

    public function rankingCSV(string $label)
    {
        $detector = $this->findDetector($label);
        $evidences = $this->server()->findEvidence($label);
        $now = \Carbon\Carbon::now();

        $file_name = $now->toISOString() . "_$label.csv";
        $headers = array(
            "Content-type"        => "text/csv",
            "Content-Disposition" => "attachment; filename=$file_name",
            "Pragma"              => "no-cache",
            "Cache-Control"       => "must-revalidate, post-check=0, pre-check=0",
            "Expires"             => "0"
        );
        $columns = ['id', 'subject', 'score', 'time'];

        $callback = function () use ($evidences, $columns) {
            $file = fopen('php://output', 'w');
            fputcsv($file, $columns);

            foreach ($evidences as $ev) {
                fputcsv($file, [
                    $ev->id,
                    $ev->subject(),
                    $ev->score,
                    $ev->time
                ]);
            }

            fclose($file);
        };

        return response()->stream($callback, 200, $headers);
    }

    public function rankingHome()
    {
        $activation_graph_elements = [];
        $detectors = $this->server()->activation();

        // nodes
        foreach ($detectors as $detector) {
            // node
            $activation_graph_elements[] = ["data" => ["id" => $detector->label]];

            // edges
            $sources = $this->findSources($detectors, $detector);
            foreach ($sources as $source) {
                $activation_graph_elements[] = ["data" => [
                    "id" => rand(),
                    "source" => $source,
                    "target" => $detector->label]];
            }

            if (count($sources) == 0) {
                // the trigger_label for this detector is a data source
                // => create node + edge
                $activation_graph_elements[] = ["data" => ["id" => $detector->trigger_label]];
                $activation_graph_elements[] = ["data" => [
                    "id" => rand(),
                    "source" => $detector->trigger_label,
                    "target" => $detector->label]];
            }
        }

        return view("app.detectors", ["activation_graph_elements" => $activation_graph_elements]);
    }

    private function findSources(array $all_detectors, $detector)
    {

        $pattern = '/' . $detector->trigger_label . '/';
        $sources = [];
        foreach ($all_detectors as $d) {
            if (preg_match($pattern, $d->label)) {
                $sources[] = $d->label;
            }
        }

        return $sources;
    }

    public function extractPoints(array $evidences, string $field) : array
    {
        $points = [];
        foreach ($evidences as $evidence) {
            // used by status history
            // this is dirty :-(
            // status history should return an array of objects
            if (is_array($evidence)) {
                $points[] = new \App\TimePoint(
                    $evidence["time"],
                    $evidence[$field]
                );
            } else {
                $points[] = new \App\TimePoint(
                    $evidence->time,
                    $evidence->$field
                );
            }
        }
        return $points;
    }

    public function evidence(string $id)
    {
        $time_window = 3600; // in seconds

        $ev = $this->server()->findEvidenceById($id);
        $since = $ev->time - $time_window * 1000;

        $references = [];
        foreach ($ev->references as $id) {
            $references[] = $this->server()->findEvidenceById($id);
        }

        $history = $this->server()->findEvidenceSince($ev->label, $ev->subject, $since);
        $feedback = Feedback::findByReportId($ev->id);

        return view(
            'app.evidence',
            [
                    "evidence" => $ev,
                    "history" => $history,
                    "history_points" => $this->extractPoints($history, "score"),
                    "feedback" => $feedback,
                    "references" => $references]
        );
    }

    /**
     * Mark an evidence as false alarm (whitelist).
     * @param string $id
     */
    public function falseAlarm(string $id)
    {
        $feedback = new Feedback();
        $feedback->report_id = $id;
        $feedback->is_true_alert = false;
        $feedback->user_id = Auth::id();
        $feedback->save();

        return redirect(action('MarkController@evidence', ['id' => $id]));
    }

    /**
     * Mark an evidence as false alarm (whitelist).
     * @param string $id
     */
    public function trueDetection(string $id)
    {
        $feedback = new Feedback();
        $feedback->report_id = $id;
        $feedback->is_true_alert = true;
        $feedback->user_id = Auth::id();
        $feedback->save();

        return redirect(action('MarkController@evidence', ['id' => $id]));
    }

    public function evidenceData(string $id, $data_id)
    {
        $ev = $this->server()->findEvidenceById($id);
        $query = $ev->requests[$data_id];
        $data = $this->server->findDataByQuery($query);

        return view('app.data', [
            "evidence" => $ev,
            "query" => $query,
            "data" => $data
        ]);
    }

    /**
     * Compute the number of jobs executed per minute.
     *
     * @param array $history_jobs_executed
     * @return array
     */
    private function computeExecutionRate(array $history_jobs_executed) : array
    {

        if (count($history_jobs_executed) < 2) {
            // not enough points to compute execution rate...
            return [];
        }

        $points = [];
        $last_point = $history_jobs_executed[0];

        foreach ($history_jobs_executed as $point) {
            $delta_t_minutes = ($point->t - $last_point->t) / 60000;

            // this point is less than a minute after the last computed point
            if ($delta_t_minutes < 1) {
                continue;
            }

            $delta_value = $point->y - $last_point->y;
            if ($delta_value < 0) {
                $delta_value = 0;
            }

            $points[] = new \App\TimePoint(
                $point->t,
                round($delta_value / $delta_t_minutes)
            );
            $last_point = $point;
        }

        return $points;
    }
}