source: admin/WebConsole/rest/common.php @ b1ef135

Last change on this file since b1ef135 was 195753c, checked in by Ramón M. Gómez <ramongomez@…>, 5 years ago

#839: Fix a PHP 7 compatibility bug in REST private functions jsonResponseNow to avoid HTTP error 500 and redefine some log messages.

  • Property mode set to 100644
File size: 7.5 KB
Line 
1<?php
2/**
3 * @file    index.php
4 * @brief   OpenGnsys REST API: common functions and routes
5 * @warning All input and output messages are formatted in JSON.
6 * @note    Some ideas are based on article "How to create REST API for Android app using PHP, Slim and MySQL" by Ravi Tamada, thanx.
7 * @license GNU GPLv3+
8 * @author  Ramón M. Gómez, ETSII Univ. Sevilla
9 * @version 1.1.0 - First version
10 * @date    2016-11-17
11 */
12
13
14// Common constants.
15define('REST_LOGFILE', '/opt/opengnsys/log/rest.log');
16define('VERSION_FILE', '/opt/opengnsys/doc/VERSION.json');
17
18// Set time zone.
19if (function_exists("date_default_timezone_set")) {
20    if (exec("timedatectl status | awk '/Time zone/ {print $3}'", $out, $err)) {
21        date_default_timezone_set($out[0]);
22    }
23}
24
25// Common functions.
26
27/**
28 * @brief   Function to write a line into log file.
29 * @param   string message  Message to log.
30 * warning  Line format: "Date: ClientIP: UserId: Status: Method Route: Message"
31 */
32function writeRestLog($message = "") {
33        global $userid;
34        if (is_writable(REST_LOGFILE)) {
35                $app = \Slim\Slim::getInstance();
36                file_put_contents(REST_LOGFILE, date(DATE_ISO8601) .": " .
37                                                $_SERVER['REMOTE_ADDR'] . ": " .
38                                                (isset($userid) ? $userid : "-") . ": " .
39                                                $app->response->getStatus() . ": " .
40                                                $app->request->getMethod() . " " .
41                                                $app->request->getPathInfo() . ": $message\n",
42                                FILE_APPEND);
43        }
44}
45
46/**
47 * @brief   Compose JSON response.
48 * @param   int status      Status code for HTTP response.
49 * @param   array response  Response data.
50 * @param   int opts        Options to encode JSON data.
51 * @return  string          JSON response.
52 */
53function jsonResponse($status, $response, $opts=0) {
54        $app = \Slim\Slim::getInstance();
55        // HTTP status code.
56        $app->status($status);
57        // Content-type HTTP header.
58        $app->contentType('application/json; charset=utf-8');
59        // JSON response.
60        echo json_encode($response, $opts);
61}
62
63/**
64 * @brief   Print immediately JSON response to continue processing.
65 * @param   int status      Status code for HTTP response.
66 * @param   array response  Response data.
67 * @param   int opts        Options to encode JSON data.
68 * @return  string          JSON response.
69 */
70function jsonResponseNow($status, $response, $opts=0) {
71        // Compose headers and content.
72        ignore_user_abort();
73        http_response_code((int)$status);
74        header('Content-type: application/json; charset=utf-8');
75        ob_start();
76        echo json_encode($response, $opts);
77        $size = ob_get_length();
78        header("Content-Length: $size");
79        // Print content.
80        ob_end_flush();
81        flush();
82        session_write_close();
83}
84
85/**
86 * @brief    Validate API key included in "Authorization" HTTP header.
87 * @return   string  JSON response on error.
88 */
89function validateApiKey() {
90        global $cmd;
91        global $userid;
92        $response = [];
93        $app = \Slim\Slim::getInstance();
94        // Read Authorization HTTP header.
95        if (! empty($_SERVER['HTTP_AUTHORIZATION'])) {
96                // Assign user id. that match this key to global variable.
97                $apikey = htmlspecialchars($_SERVER['HTTP_AUTHORIZATION']);
98                $cmd->texto = "SELECT idusuario
99                                 FROM usuarios
100                                WHERE apikey='$apikey' LIMIT 1";
101                $rs=new Recordset;
102                $rs->Comando=&$cmd;
103                if ($rs->Abrir()) {
104                        $rs->Primero();
105                        if (!$rs->EOF){
106                                // Fetch user id.
107                                $userid = $rs->campos["idusuario"];
108                        } else {
109                                // Credentials error.
110                                $response['message'] = 'Login failed, incorrect credentials';
111                                jsonResponse(401, $response);
112                                $app->stop();
113                        }
114                        $rs->Cerrar();
115                } else {
116                        // Database error.
117                        $response['message'] = "An error occurred, please try again";
118                        jsonResponse(500, $response);
119                }
120        } else {
121                // Error: missing API key.
122                $response['message'] = 'Missing API key';
123                jsonResponse(400, $response);
124                $app->stop();
125        }
126}
127
128/**
129 * @brief    Check if parameter is set and print error messages if empty.
130 * @param    string param    Parameter to check.
131 * @return   boolean         "false" if parameter is null, otherwise "true".
132 */
133function checkParameter($param) {
134        $response = [];
135        if (isset($param)) {
136                return true;
137        } else {
138                // Print error message.
139                $response['message'] = 'Parameter not found';
140                jsonResponse(400, $response);
141                return false;
142        }
143}
144
145/**
146 * @brief    Check if all parameters are positive integer numbers.
147 * @param    int id ...      Identificators to check (variable number of parameters).
148 * @return   boolean         "true" if all ids are int>0, otherwise "false".
149 */
150function checkIds() {
151        $opts = ['options' => ['min_range' => 1]];      // Check for int>0
152        foreach (func_get_args() as $id) {
153                if (filter_var($id, FILTER_VALIDATE_INT, $opts) === false) {
154                        return false;
155                }
156        }
157        return true;
158}
159
160/**
161 * @brief   Show custom message for "not found" error (404).
162 */
163$app->notFound(
164    function() {
165        $response['message'] = 'REST route not found';
166        jsonResponse(404, $response);
167   }
168);
169
170/**
171 * @brief   Hook to write an error log message and a REST exit log message if debug is enabled.
172 * @warning Error message will be written in web server's error file.
173 * @warning REST message will be written in REST log file.
174 */
175$app->hook('slim.after', function() use ($app) {
176        if ($app->response->getStatus() != 200 ) {
177                // Compose error message (truncating long lines).
178                $app->log->error(date(DATE_ISO8601) . ': ' .
179                                 $app->getName() . ': ' .
180                                 $_SERVER['REMOTE_ADDR'] . ": " .
181                                 (isset($userid) ? $userid : "-") . ": " .
182                                 $app->response->getStatus() . ': ' .
183                                 $app->request->getMethod() . ' ' .
184                                 $app->request->getPathInfo() . ': ' .
185                                 substr($app->response->getBody(), 0, 100));
186        }
187        if ($app->settings['debug'])
188                writeRestLog(substr($app->response->getBody(), 0, 30));
189   }
190);
191
192
193// Common routes.
194
195/**
196 * @brief    Get general server information
197 * @note     Route: /info, Method: GET
198 * @return   string  JSON object with basic server information (version, services, etc.)
199 */
200$app->get('/info', function() {
201      $hasOglive = false;
202      $response = new \stdClass;
203      // Reading version file.
204      $data = json_decode(@file_get_contents(VERSION_FILE));
205      if (isset($data->project)) {
206          $response = $data;
207      } else {
208          $response->project = 'OpenGnsys';
209      }
210      // Getting actived services.
211      @$services = parse_ini_file('/etc/default/opengnsys');
212      $response->services = [];
213      if (@$services["RUN_OGADMSERVER"] === "yes") {
214          array_push($response->services, "server");
215          $hasOglive = true;
216      }
217      if (@$services["RUN_OGADMREPO"] === "yes")  array_push($response->services, "repository");
218      if (@$services["RUN_BTTRACKER"] === "yes")  array_push($response->services, "tracker");
219      // Reading installed ogLive information file.
220      if ($hasOglive === true) {
221          $data = json_decode(@file_get_contents('/opt/opengnsys/etc/ogliveinfo.json'));
222          if (isset($data->oglive)) {
223              $response->oglive = $data->oglive;
224          }
225      }
226      jsonResponse(200, $response);
227   }
228);
229
230/**
231 * @brief    Get the server status
232 * @note     Route: /status, Method: GET
233 * @return   string  JSON object with all data collected from server status (RAM, %CPU, etc.).
234 */
235$app->get('/status', function() {
236      $response = [];
237      // Getting memory and CPU information.
238      exec("awk '$1~/Mem/ {print $2}' /proc/meminfo",$memInfo);
239      $memInfo = array("total" => $memInfo[0], "used" => $memInfo[1]);
240      $cpuInfo = exec("awk '$1==\"cpu\" {printf \"%.2f\",($2+$4)*100/($2+$4+$5)}' /proc/stat");
241      $cpuModel = exec("awk -F: '$1~/model name/ {print $2}' /proc/cpuinfo");
242      $response["memInfo"] = $memInfo;
243      $response["cpu"] = array("model" => trim($cpuModel), "usage" => $cpuInfo);
244      jsonResponse(200, $response);
245   }
246);
247
Note: See TracBrowser for help on using the repository browser.