Passed
Push — master ( 8ef52a...4d2ca2 )
by Patrick
02:00
created

IcingaApiBase::getLastJsonErrorMessage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
4
namespace Icinga\Module\Trapdirector\IcingaApi;
5
6
use RuntimeException;
7
use Exception;
8
9
class IcingaApiBase
10
{
11
    protected $version = 'v1';      //< icinga2 api version
12
    
13
    protected $host;                //< icinga2 host name or IP
14
    protected $port;                //< icinga2 api port
15
    
16
    protected $user;                //< user name
17
    protected $pass;                //< user password
18
    protected $usercert;            //< user key for certificate auth (NOT IMPLEMENTED)
19
    protected $authmethod='pass';   //< Authentication : 'pass' or 'cert'
20
21
    protected $queryURL=array(
22
        'host'  => 'objects/hosts',
23
        'hostgroup' => 'objects/hostgroups',
24
        'service' => 'objects/services'
25
    );
26
    
27
    protected $curl;
28
    // http://php.net/manual/de/function.json-last-error.php#119985
29
    protected $errorReference = [
30
        JSON_ERROR_NONE => 'No error has occurred.',
31
        JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded.',
32
        JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON.',
33
        JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded.',
34
        JSON_ERROR_SYNTAX => 'Syntax error.',
35
        JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded.',
36
        JSON_ERROR_RECURSION => 'One or more recursive references in the value to be encoded.',
37
        JSON_ERROR_INF_OR_NAN => 'One or more NAN or INF values in the value to be encoded.',
38
        JSON_ERROR_UNSUPPORTED_TYPE => 'A value of a type that cannot be encoded was given.',
39
    ];
40
    const JSON_UNKNOWN_ERROR = 'Unknown error.';
41
    
42
    /**
43
     * Creates Icinga2API object
44
     * 
45
     * @param string $host host name or IP
46
     * @param number $port API port
47
     */
48
    public function __construct($host, $port = 5665)
49
    {
50
        $this->host=$host;
51
        $this->port=$port;
52
    }
53
    /**
54
     * Set user & pass
55
     * @param string $user
56
     * @param string $pass
57
     */
58
    public function setCredentials($user,$pass)
59
    {
60
        $this->user=$user;
61
        $this->pass=$pass;
62
        $this->authmethod='pass';
63
    }
64
    
65
    /**
66
     * Set user & certificate (NOT IMPLEMENTED @throws RuntimeException)
67
     * @param string $user
68
     * @param string $usercert
69
     */
70
    public function setCredentialskey($user,$usercert)
71
    {
72
        $this->user=$user;
73
        $this->usercert=$usercert;
74
        $this->authmethod='cert';
75
        throw new RuntimeException('Certificate auth not implemented');
76
    }
77
78
    public function test(array $permissions)
79
    {
80
       try
81
        {
82
            $result=$this->request('GET', "", NULL, NULL);
83
        } 
84
        catch (Exception $e)
85
        {
86
            return array(true, 'Error with API : '.$e->getMessage());
87
        }
88
        //var_dump($result);
89
        $permOk=1;
90
        $permMissing='';
91
        if ($permissions === NULL || count($permissions) == 0) // If no permission check return OK after connexion
92
        {
93
            return array(false,'OK');
94
        }
95
        if (property_exists($result, 'results') && property_exists($result->results[0], 'permissions'))
96
        {
97
            
98
            foreach ( $permissions as $mustPermission)
99
            {
100
                $curPermOK=0;
101
                foreach ( $result->results[0]->permissions as $curPermission)
102
                {
103
                    $curPermission=preg_replace('/\*/','.*',$curPermission); // put * as .* to created a regexp
104
                    if (preg_match('#'.$curPermission.'#',$mustPermission))
105
                    {
106
                        $curPermOK=1;
107
                        break;
108
                    }
109
                }
110
                if ($curPermOK == 0)
111
                {
112
                    $permOk=0;
113
                    $permMissing=$mustPermission;
114
                    break;
115
                }
116
            }
117
            if ($permOk == 0)
118
            {
119
                return array(true,'API connection OK, but missing permission : '.$permMissing);
120
            }
121
            return array(false,'API connection OK');
122
            
123
        }
124
        return array(true,'API connection OK, but cannot get permissions');
125
    }
126
    
127
    
128
    protected function url($url) {
129
        return sprintf('https://%s:%d/%s/%s', $this->host, $this->port, $this->version, $url);
130
    }
131
    
132
    /**
133
     * Create or return curl ressource
134
     * @throws Exception
135
     * @return resource
136
     */
137
    protected function curl() {
138
        if ($this->curl === null) {
139
            $this->curl = curl_init(sprintf('https://%s:%d', $this->host, $this->port));
140
            if ($this->curl === false) {
141
                throw new Exception('CURL INIT ERROR');
142
            }
143
        }
144
        return $this->curl;
145
    }
146
147
    /**
148
     * Send a passive service check
149
     * @param string $host : host name 
150
     * @param string $service : service name
151
     * @param int $state : state of service
152
     * @param string $display : service passive check output
153
     * @param string $perfdata : performance data as string
154
     * @return array (status = true (oK) or false (nok), string message)
155
     */
156
    public function serviceCheckResult($host,$service,$state,$display,$perfdata='')
157
    {
158
        //Send a POST request to the URL endpoint /v1/actions/process-check-result
159
        //actions/process-check-result?service=example.localdomain!passive-ping6
160
        $url='actions/process-check-result';
161
        $body=array(
162
            "filter"        => 'service.name=="'.$service.'" && service.host_name=="'.$host.'"',
163
            'type'          => 'Service',
164
            "exit_status"   => $state,
165
            "plugin_output" => $display,
166
            "performance_data" => $perfdata
167
        );
168
        try 
169
        {
170
            $result=$this->request('POST', $url, null, $body);
171
        } catch (Exception $e) 
172
        {
173
            return array(false, $e->getMessage());
174
        }
175
        if (property_exists($result,'error') )
176
        {
177
            if (property_exists($result,'status'))
178
            {
179
                $message=$result->status;
180
            }
181
            else 
182
            {
183
                $message="Unkown status";
184
            }
185
            return array(false , 'Ret code ' .$result->error.' : '.$message);
186
        }
187
        if (property_exists($result, 'results'))
188
        {
189
            if (isset($result->results[0]))
190
            {
191
                return array(true,'code '.$result->results[0]->code.' : '.$result->results[0]->status);
192
            }
193
            else
194
            {
195
                return array(false,'Service not found');
196
            }
197
            
198
        }
199
        return array(false,'Unkown result, open issue with this : '.print_r($result,true));
200
    }
201
  
202
    /**
203
     * Check 'result' exists and check for errors/status
204
     * @param \stdClass $result
205
     * @throws Exception 
206
     */
207
    public function checkResultStatus(\stdClass $result) 
208
    {
209
        if (property_exists($result,'error') )
210
        {
211
            if (property_exists($result,'status'))
212
            {
213
                throw new Exception('Ret code ' .$result->error.' : ' . $result->status);
214
            }
215
            else
216
            {
217
                throw new Exception('Ret code ' .$result->error.' : Unkown status');
218
            }
219
        }
220
        if ( ! property_exists($result, 'results'))
221
        {
222
            throw new Exception('Unkown result');
223
        }
224
    }
225
    
226
    /**
227
     * Send request to API
228
     * @param string $method get/post/...
229
     * @param string $url (after /v1/ )
230
     * @param array $headers
231
     * @param array $body 
232
     * @throws Exception
233
     * @return array
234
     */
235
    public function request($method, $url, $headers, $body) {
236
        $auth = sprintf('%s:%s', $this->user, $this->pass);
237
        $curlHeaders = array("Accept: application/json");
238
        if ($body !== null) {
0 ignored issues
show
introduced by
The condition $body !== null is always true.
Loading history...
239
            $body = json_encode($body);
240
            array_push($curlHeaders, 'Content-Type: application/json');
241
            //array_push($curlHeaders, 'X-HTTP-Method-Override: GET');
242
        }
243
        //var_dump($body);
244
        //var_dump($this->url($url));
245
        if ($headers !== null) {
0 ignored issues
show
introduced by
The condition $headers !== null is always true.
Loading history...
246
            $curlFinalHeaders = array_merge($curlHeaders, $headers);
247
        } else 
248
        {
249
            $curlFinalHeaders=$curlHeaders;
250
        }
251
        $curl = $this->curl();
252
        $opts = array(
253
            CURLOPT_URL		=> $this->url($url),
254
            CURLOPT_HTTPHEADER 	=> $curlFinalHeaders,
255
            CURLOPT_USERPWD		=> $auth,
256
            CURLOPT_CUSTOMREQUEST	=> strtoupper($method),
257
            CURLOPT_RETURNTRANSFER 	=> true,
258
            CURLOPT_CONNECTTIMEOUT 	=> 10,
259
            CURLOPT_SSL_VERIFYHOST 	=> false,
260
            CURLOPT_SSL_VERIFYPEER 	=> false,
261
        );
262
        if ($body !== null) {
263
            $opts[CURLOPT_POSTFIELDS] = $body;
264
        }
265
        curl_setopt_array($curl, $opts);
266
        $res = curl_exec($curl);
267
        if ($res === false) {
268
            throw new Exception('CURL ERROR: ' . curl_error($curl));
269
        }
270
        $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
271
        if ($statusCode === 401) {
272
            throw new Exception('Unable to authenticate, please check your API credentials');
273
        }
274
        return $this->fromJsonResult($res);
0 ignored issues
show
Bug introduced by
It seems like $res can also be of type true; however, parameter $json of Icinga\Module\Trapdirect...iBase::fromJsonResult() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

274
        return $this->fromJsonResult(/** @scrutinizer ignore-type */ $res);
Loading history...
275
    }
276
    
277
    /**
278
     * 
279
     * @param string $json json encoded 
280
     * @throws Exception
281
     * @return array json decoded
282
     */
283
    protected function fromJsonResult($json) {
284
        $result = @json_decode($json);
285
        //var_dump($json);
286
        if ($result === null) {
287
            throw new Exception('Parsing JSON failed: '.$this->getLastJsonErrorMessage(json_last_error()));
288
        }
289
        return $result;
290
    }
291
    
292
    /**
293
     * Return text error no json error
294
     * @param string $errorCode
295
     * @return string
296
     */
297
    protected function getLastJsonErrorMessage($errorCode) {
298
        if (!array_key_exists($errorCode, $this->errorReference)) {
299
            return self::JSON_UNKNOWN_ERROR;
300
        }
301
        return $this->errorReference[$errorCode];
302
    }
303
304
    public function standardQuery(string $urlType , string $filter, array $attributes)
305
    {
306
        /*
307
         *  curl -k -s -u  trapdirector:trapdirector -H 'X-HTTP-Method-Override: GET' -X POST 'https://localhost:5665/v1/objects/hosts'
308
         *  -d '{"filter":"host.group==\"test_trap\"","attrs": ["address" ,"address6"]}'
309
         
310
         {"results":[{"attrs":{"__name":"Icinga host","address":"127.0.0.1","display_name":"Icinga host","name":"Icinga host"},"joins":{},"meta":{},"name":"Icinga host","type":"Host"}]}
311
         */
312
        
313
        if (! isset($this->queryURL[$urlType] ))
314
        {
315
            throw new Exception("Unkown object type");
316
        }
317
        $url=$this->queryURL[$urlType];
318
        $body=array();
319
        if ($filter !== NULL)
0 ignored issues
show
introduced by
The condition $filter !== NULL is always true.
Loading history...
320
        {
321
            $body['filter'] = $filter;
322
        }
323
        if (count($attributes) != 0)
324
        {
325
            $body['attrs'] = $attributes;
326
        }
327
328
        try
329
        {
330
            $result=$this->request('POST', $url, array('X-HTTP-Method-Override: GET'), $body);
331
        } catch (Exception $e)
332
        {
333
            throw new Exception($e->getMessage());
334
        }
335
        
336
        $this->checkResultStatus($result);
0 ignored issues
show
Bug introduced by
$result of type array is incompatible with the type stdClass expected by parameter $result of Icinga\Module\Trapdirect...se::checkResultStatus(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

336
        $this->checkResultStatus(/** @scrutinizer ignore-type */ $result);
Loading history...
337
        
338
        $numHost=0;
339
        $hostArray=array();
340
        while (isset($result->results[$numHost]) && property_exists ($result->results[$numHost],'attrs'))
341
        {
342
            $hostArray[$numHost] = $result->results[$numHost]->attrs;
343
            $numHost++;
344
        }
345
        return $hostArray;
346
    }
347
    
348
}
349
350