1<?php
2
3namespace Icinga\Module\Director\RestApi;
4
5use Exception;
6use Icinga\Exception\IcingaException;
7use Icinga\Exception\NotFoundError;
8use Icinga\Exception\ProgrammingError;
9use Icinga\Module\Director\Core\CoreApi;
10use Icinga\Module\Director\Exception\DuplicateKeyException;
11use Icinga\Module\Director\Objects\IcingaObject;
12use Icinga\Module\Director\Util;
13
14class IcingaObjectHandler extends RequestHandler
15{
16    /** @var IcingaObject */
17    protected $object;
18
19    /** @var CoreApi */
20    protected $api;
21
22    public function setObject(IcingaObject $object)
23    {
24        $this->object = $object;
25        return $this;
26    }
27
28    public function setApi(CoreApi $api)
29    {
30        $this->api = $api;
31        return $this;
32    }
33
34    /**
35     * @return IcingaObject
36     * @throws ProgrammingError
37     */
38    protected function requireObject()
39    {
40        if ($this->object === null) {
41            throw new ProgrammingError('Object is required');
42        }
43
44        return $this->object;
45    }
46
47    /**
48     * @return IcingaObject
49     */
50    protected function eventuallyLoadObject()
51    {
52        return $this->object;
53    }
54
55    protected function requireJsonBody()
56    {
57        $data = json_decode($this->request->getRawBody());
58
59        if ($data === null) {
60            $this->response->setHttpResponseCode(400);
61            throw new IcingaException(
62                'Invalid JSON: %s',
63                $this->getLastJsonError()
64            );
65        }
66
67        return $data;
68    }
69
70    protected function getType()
71    {
72        return $this->request->getControllerName();
73    }
74
75    protected function processApiRequest()
76    {
77        try {
78            $this->handleApiRequest();
79        } catch (NotFoundError $e) {
80            $this->sendJsonError($e, 404);
81            return;
82        } catch (DuplicateKeyException $e) {
83            $this->sendJsonError($e, 422);
84            return;
85        } catch (Exception $e) {
86            $this->sendJsonError($e);
87        }
88
89        if ($this->request->getActionName() !== 'index') {
90            throw new NotFoundError('Not found');
91        }
92    }
93
94    protected function handleApiRequest()
95    {
96        $request = $this->request;
97        $response = $this->response;
98        $db = $this->db;
99
100        // TODO: I hate doing this:
101        if ($this->request->getActionName() === 'ticket') {
102            $host = $this->requireObject();
103
104            if ($host->getResolvedProperty('has_agent') !== 'y') {
105                throw new NotFoundError('The host "%s" is not an agent', $host->getObjectName());
106            }
107
108            $this->sendJson(
109                Util::getIcingaTicket(
110                    $host->getObjectName(),
111                    $this->api->getTicketSalt()
112                )
113            );
114
115            // TODO: find a better way to shut down. Currently, this avoids
116            //       "not found" errors:
117            exit;
118        }
119
120        switch ($request->getMethod()) {
121            case 'DELETE':
122                $object = $this->requireObject();
123                $object->delete();
124                $this->sendJson($object->toPlainObject(false, true));
125                break;
126
127            case 'POST':
128            case 'PUT':
129                $data = (array) $this->requireJsonBody();
130                $type = $this->getType();
131                if ($object = $this->eventuallyLoadObject()) {
132                    if ($request->getMethod() === 'POST') {
133                        $object->setProperties($data);
134                    } else {
135                        $data = array_merge([
136                            'object_type' => $object->get('object_type'),
137                            'object_name' => $object->getObjectName()
138                        ], $data);
139                        $object->replaceWith(
140                            IcingaObject::createByType($type, $data, $db)
141                        );
142                    }
143                } else {
144                    $object = IcingaObject::createByType($type, $data, $db);
145                }
146
147                if ($object->hasBeenModified()) {
148                    $status = $object->hasBeenLoadedFromDb() ? 200 : 201;
149                    $object->store();
150                    $response->setHttpResponseCode($status);
151                } else {
152                    $response->setHttpResponseCode(304);
153                }
154
155                $this->sendJson($object->toPlainObject(false, true));
156                break;
157
158            case 'GET':
159                $params = $this->request->getUrl()->getParams();
160                $this->requireObject();
161                $properties = $params->shift('properties');
162                if (strlen($properties)) {
163                    $properties = preg_split('/\s*,\s*/', $properties, -1, PREG_SPLIT_NO_EMPTY);
164                } else {
165                    $properties = null;
166                }
167
168                $this->sendJson(
169                    $this->requireObject()->toPlainObject(
170                        $params->shift('resolved'),
171                        ! $params->shift('withNull'),
172                        $properties
173                    )
174                );
175                break;
176
177            default:
178                $request->getResponse()->setHttpResponseCode(400);
179                throw new IcingaException('Unsupported method ' . $request->getMethod());
180        }
181    }
182}
183