phplib/REST/Alerts.php

Summary

Maintainability
D
2 days
Test Coverage
<?php

namespace FOO;

/**
 * Class Alerts_REST
 * REST endpoint for manipulating Alerts.
 * @package FOO
 */
class Alerts_REST extends Models_REST {
    const SLOG_TYPE = SLog::T_ALERT;

    public static $MODEL = 'Alert';
    public static $CREATABLE = [];
    public static $QUERYABLE = [
        'alert_date', 'assignee_type', 'assignee', 'search_id', 'state', 'escalated', 'from', 'to'
    ];
    public static $READABLE = null;
    public static $UPDATEABLE = [
        'renderer_data'
    ];

    /** @var ESClient Client wrapper for handling batch requests */
    private $client;

    public function __construct() {
        $this->client = new ESClient;
    }

    public function allowRead() {
        return Auth::isAuthenticated();
    }
    public function allowCreate() {
        return false;
    }
    public function allowUpdate() {
        return Auth::isAuthenticated();
    }
    public function allowDelete() {
        return Auth::isAuthenticated();
    }

    /**
     * Override authorization check.
     * The push endpoint can be accessed without auth, so we do that whitelisting here.
     */
    public function checkAuthorization() {
        if(!Auth::isAuthenticated()) {
            throw new UnauthorizedException('Authentication required');
        }
    }

    public function beforeStore($model, $data, $new, $delete) {
        if(!$delete) {
            return;
        }

        $this->client->delete($model);
    }
    public function afterStore($model, $data, $new, $delete) {
        if($delete) {
            return;
        }

        $this->client->update($model);
    }
    public function finalizeStore() {
        $this->client->finalize();
    }

    public function GET(array $get) {
        $action = Util::get($get, 'action');

        switch($action) {
            case 'ids':
                return self::format($this->getIds($get));
            case 'link':
                return self::format($this->getLink($get));
            case 'bootstrap':
                return self::format($this->bootstrapAlerts($get));
            case 'query':
                return self::format($this->queryAlerts($get));
            default:
                return self::format($this->read($get));
        }
    }

    public function POST(array $get, array $data) {
        $action = Util::get($get, 'action');

        switch($action) {
            case 'send':
                return self::format($this->sendAlerts($data));
            case 'whitelist':
                return self::format($this->whitelistAlerts($data));
            case 'push':
                return self::format($this->push($get, $data));
            default:
                return self::format($this->create($get, $data));
        }
    }

    public function PUT(array $get, array $data) {
        $action = Util::get($get, 'action');

        switch($action) {
            case 'escalate':
                return self::format($this->setAlertEscalation($data));
            case 'switch':
                return self::format($this->setAlertState($data));
            case 'assign':
                return self::format($this->setAlertAssignee($data));
            case 'note':
                return self::format($this->addAlertNote($data));
            default:
                return self::format($this->update($get, $data));
        }
    }

    public function getIds($get) {
        if(!$this->allowRead()) {
            throw new ForbiddenException;
        }

        $query = Util::get($get, 'query', '');
        $from = Util::get($get, 'from');
        $to = Util::get($get, 'to');

        return $this->client->getIds($query, $from, $to);
    }

    public function bootstrapAlerts($get) {
        if(!$this->allowRead()) {
            throw new ForbiddenException;
        }

        $query = Util::get($get, 'query', '');
        $from = Util::get($get, 'from');
        $to = Util::get($get, 'to');

        return $this->client->bootstrap($query, $from, $to);
    }

    public function queryAlerts($get) {
        if(!$this->allowRead()) {
            throw new ForbiddenException;
        }

        $query = Util::get($get, 'query', '');
        $from = Util::get($get, 'from');
        $to = Util::get($get, 'to');
        $offset = Util::get($get, 'offset');
        $count = Util::get($get, 'count');

        return $this->client->getAlerts($query, $from, $to, $offset, $count);
    }

    public function getLink($get) {
        if(!$this->allowRead()) {
            throw new ForbiddenException;
        }

        $id = Util::get($get, 'id');
        $alert = AlertFinder::getById($id);
        $search = SearchFinder::getById($alert['search_id']);

        return ['link' => $search->getLink($alert)];
    }

    public function whitelistAlerts($data) {
        if(!$this->allowUpdate()) {
            throw new ForbiddenException;
        }

        $lifetime = (int)Util::get($data, 'lifetime');
        $description = (string)Util::get($data, 'description', '');
        $ids = (array)Util::get($data, 'id');

        $ret = ['count' => 0];
        $hashes = [];
        foreach($ids as $id) {
            $alert = AlertFinder::getById($id);
            if(is_null($alert)) {
                continue;
            }
            $search_id = $alert['search_id'];

            if(!Util::exists($hashes, $search_id)) {
                $hashes[$search_id] = [];
            }
            $hashes[$search_id][$alert['content_hash']] = null;
            ++$ret['count'];
        }

        foreach($hashes as $search_id=>$list) {
            $filter = new Hash_Filter();
            $filter['search_id'] = $search_id;
            $filter['lifetime'] = $lifetime;
            $filter['description'] = $description;
            $filter['data']['list'] = array_keys($list);
            $filter->store();
        }

        return $ret;
    }

    public function sendAlerts($data) {
        if(!$this->allowUpdate()) {
            throw new ForbiddenException;
        }

        $target_data = Util::get($data, 'target');
        $type = Util::get($target_data, 'type');
        $ids = (array)Util::get($data, 'id');

        $ret = ['count' => 0];
        $target = Target::newTarget($type);
        $target['data'] = (array)Util::get($target_data, 'data');
        $target->validate();

        $errors = [];
        foreach($ids as $id) {
            $alert = AlertFinder::getById($id);
            if(!is_null($alert)) {
                try {
                    $target->process($alert, $_SERVER['REQUEST_TIME']);
                    $ret['count'] += 1;
                } catch(TargetException $e) {
                    $errors[] = sprintf('Target %s: %s', $target['type'], $e->getMessage());
                }
            }
        }
        try {
            $target->finalize($_SERVER['REQUEST_TIME']);
        } catch(TargetException $e) {
            $errors[] = sprintf('Target %s: %s', $target['type'], $e->getMessage());
        }

        return self::format($ret, !count($errors), $errors);
    }

    public function addAlertNote($data) {
        return $this->setAlertFields(
            $data,
            [],
            AlertLog::A_NOTE, SLOG::AA_NOTE
        );
    }

    public function setAlertEscalation($data) {
        return $this->setAlertFields(
            $data,
            ['escalated'],
            AlertLog::A_ESCALATE, SLog::AA_ESCALATE,
            (int) Util::get($data, 'escalated')
        );
    }

    public function setAlertState($data) {
        $fields = ['state'];
        if(Util::get($data, 'state', null) === 2) {
            $fields[] = 'resolution';
        }

        return $this->setAlertFields(
            $data,
            $fields,
            AlertLog::A_SWITCH, SLog::AA_SWITCH,
            (int) Util::get($data, 'state'), (int) Util::get($data, 'resolution')
        );
    }

    public function setAlertAssignee($data) {
        return $this->setAlertFields(
            $data,
            ['assignee_type', 'assignee'],
            AlertLog::A_ASSIGN, SLog::AA_ASSIGN,
            (int) Util::get($data, 'assignee_type'), (int) Util::get($data, 'assignee')
        );
    }

    private function setAlertFields($data, $fields, $alog_action, $slog_action, $a=0, $b=0) {
        if(!$this->allowUpdate()) {
            throw new ForbiddenException;
        }

        // All fields are required.
        foreach($fields as $field) {
            if(is_null(Util::get($data, $field))) {
                throw new InternalErrorException('Required field missing');
            }
        }

        $ids = (array) Util::get($data, 'id');
        $ret = ['count' => 0];
        $log = null;

        // Apply changes to the Alerts, and keep track of each Alert.
        $alert_groups = [[], []];
        foreach(AlertFinder::getByQuery(['id' => $ids]) as $alert) {
            foreach($fields as $field) {
                $alert[$field] = Util::get($data, $field);
            }
            $alert->store();

            // Generate a log entry for this change.
            $log = new AlertLog();
            $log['user_id'] = Auth::getUserId();
            $log['alert_id'] = $alert['id'];
            $log['note'] = Util::get($data, 'note', '');
            $log['action'] = $alog_action;
            $log['a'] = $a;
            $log['b'] = $b;
            $log->store();

            $this->slog($slog_action, $alert['id'], $a, $b);

            $this->afterStore($alert, [], false, false);

            $ret['count'] += 1;
            $assignee_type = $alert['assignee_type'];
            $assignee = $alert['assignee'];
            if(!Util::exists($alert_groups[$assignee_type], $assignee)) {
                $alert_groups[$assignee_type][$assignee] = [];
            }
            $alert_groups[$assignee_type][$assignee][] = $alert;
        }

        $this->finalizeStore();

        // Fire off notifications for each batch of Alerts that were updated.
        $searches = [];
        foreach($alert_groups as $assignee_type=>$alert_group) {
            foreach($alert_group as $assignee=>$alerts) {
                // Filter out Alerts that shouldn't generate a notification.
                $filtered_alerts = [];
                foreach($alerts as $alert) {
                    $search_id = $alert['search_id'];
                    if(!Util::exists($searches, $search_id)) {
                        $searches[$search_id] = SearchFinder::getById($search_id, true);
                    }
                    $search = $searches[$search_id];

                    // Only add to the list if escalated or the Search has on-demand notifs.
                    if($alert['escalated'] || $search['notif_type'] >= Search::NT_ONDEMAND) {
                        $filtered_alerts[] = $alert;
                    }
                }

                if(count($filtered_alerts)) {
                    $to = Assignee::getEmails($assignee_type, $assignee);
                    Notification::sendAlertActionEmail($to, $log, $searches, $filtered_alerts);
                }
            }
        }

        return $ret;
    }

    public function push($get, $data) {
        $search_id = Util::get($get, 'search_id', 0);
        $search = SearchFinder::getById($search_id);

        // Verify that we have a Push_Search and that it's enabled.
        if(is_null($search) || $search['type'] != Push_Search::$TYPE || !$search['enabled']) {
            throw new ForbiddenException;
        }

        $search->setResults($data);

        $searchjob = new Search_Job();
        $searchjob['target_date'] = $_SERVER['REQUEST_TIME'];
        list($alerts, $errors, $ignorable) = $searchjob->_run(true, $search, true);
        $this->slog(SLog::AS_EXECUTE, $search['id']);
        return self::format($alerts, is_null($alerts), $errors);
    }
}