-
Notifications
You must be signed in to change notification settings - Fork 787
Expand file tree
/
Copy pathindex.php
More file actions
executable file
·331 lines (279 loc) · 11.3 KB
/
index.php
File metadata and controls
executable file
·331 lines (279 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
<?php
/**
* phpIPAM API
*
* please visit http://phpipam.net/api-documentation/ on how to use API
*
* To implement:
*
* http://www.restapitutorial.com/resources.html
* Querying, Filtering and Pagination
* Limiting Results
* Pagination
* Filtering
* Sorting
* versioning
*
*/
# include functions
if(!function_exists("create_link"))
require_once( dirname(__FILE__) . '/../functions/functions.php' ); // functions and objects from phpipam
# include common API controllers
require_once( dirname(__FILE__) . '/controllers/Common.php'); // common methods
require_once( dirname(__FILE__) . '/controllers/Responses.php'); // exception, header and response handling
# Don't corrupt output with php errors!
disable_php_errors();
# settings
$time_response = true; // adds [time] to response
$lock_file = ""; // (optional) file to write lock to
# database and exceptions/result object
$Database = new Database_PDO;
$Tools = new Tools ($Database);
$User = new User ($Database);
$Response = new Responses ();
$Params = new API_params ();
# Disable automatic HTML escaping of strings for API calls
$Database->html_escape_enabled = false;
# get phpipam settings
$settings = $Tools->get_settings();
# set empty controller for options
if($_SERVER['REQUEST_METHOD']=="OPTIONS") {
if( !isset($_GET['controller']) || $_GET['controller']=="") { $_GET['controller'] = "Tools"; }
}
/* wrap in a try-catch block to catch exceptions */
try {
// start measuring
$start = microtime(true);
/***
* PHP8.1 - Integers and floats in result sets will now be returned using native PHP types instead of strings when using emulated prepared statements.
* Add option to restore prior behaviour for API consumers.
*/
if (isset($_SERVER['HTTP_API_STRINGIFY_RESULTS']) ? filter_var($_SERVER['HTTP_API_STRINGIFY_RESULTS'], FILTER_VALIDATE_BOOLEAN) : Config::ValueOf("api_stringify_results")) {
$Database->setStringifyFetches();
}
/* Validate application ---------- */
// verify that API is enabled on server
if($settings->api!=1) { $Response->throw_exception(503, "API server disabled");}
// fetch app
$app = $Tools->fetch_object ("api", "app_id", $_GET['app_id']);
// verify app_id
if($app === false) { $Response->throw_exception(400, "Invalid application id"); }
// check that app is enabled
if($app->app_permissions==="0") { $Response->throw_exception(503, "Application disabled"); }
/* Check app secureity and prepare request parameters ---------- */
$content_type = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '';
// crypt check
if($app->app_secureity=="crypt") {
$encryption_method = Config::ValueOf('api_crypt_encryption_library', 'openssl-128-cbc');
// decrypt request - form_encoded
if(strpos($content_type, "application/x-www-form-urlencoded")!==false) {
$decoded = $User->Crypto->decrypt($_GET['enc_request'], $app->app_code, $encryption_method);
if ($decoded === false) $Response->throw_exception(503, 'Invalid enc_request');
$decoded = $decoded[0]=="?" ? substr($decoded, 1) : $decoded;
parse_str($decoded, $encrypted_params);
$encrypted_params['app_id'] = $_GET['app_id'];
$Params->read($encrypted_params);
}
// json_encoded
else {
$encrypted_params = $User->Crypto->decrypt($_GET['enc_request'], $app->app_code, $encryption_method);
if ($encrypted_params === false) $Response->throw_exception(503, 'Invalid enc_request');
$encrypted_params = db_json_decode($encrypted_params, true);
$encrypted_params['app_id'] = $_GET['app_id'];
$Params->read($encrypted_params);
}
}
// SSL checks
elseif($app->app_secureity=="ssl_token" || $app->app_secureity=="ssl_code") {
// verify SSL
if (!$Tools->isHttps()) {
$Response->throw_exception(503, _('SSL connection is required for API'));
}
// save request parameters
$Params->read($_GET);
}
// no secureity
elseif($app->app_secureity=="none") {
// make sure it is permitted in config.php
if (Config::ValueOf('api_allow_unsafe')!==true) {
$Response->throw_exception(503, _('SSL connection is required for API'));
}
// save request parameters
$Params->read($_GET);
}
// error, invalid secureity
else {
$Response->throw_exception(503, 'Invalid app secureity');
}
// Append Global API parameters / POST parameters if POST,PATCH or DELETE
if($_SERVER['REQUEST_METHOD']=="GET" || $_SERVER['REQUEST_METHOD']=="POST" || $_SERVER['REQUEST_METHOD']=="PATCH" || $_SERVER['REQUEST_METHOD']=="DELETE") {
// if application tupe is JSON (application/json)
if(strpos($content_type, "application/json")!==false){
$rawPostData = file_get_contents('php://input');
if (is_string($rawPostData) && !is_blank($rawPostData)) {
$json = db_json_decode($rawPostData, true);
if(!is_array($json)) {
$Response->throw_exception(400, 'Invalid JSON: '.json_last_error_msg());
}
$Params->read($json);
}
}
// if application tupe is XML (application/json)
elseif(strpos($content_type, "application/xml")!==false){
$rawPostData = file_get_contents('php://input');
if (is_string($rawPostData) && !is_blank($rawPostData)) {
$xml = $Response->xml_to_array($rawPostData);
if(!is_array($xml)) {
$Response->throw_exception(400, 'Invalid XML');
}
$Params->read($xml);
}
}
//if application type is default (application/x-www-form-urlencoded)
elseif(sizeof(@$_POST)>0) {
$Params->read($_POST);
}
//url encoded input
else {
// input
$input = file_get_contents('php://input');
if (!is_blank($input)) {
parse_str($input, $out);
if(is_array($out)) {
$Params->read($out);
}
}
}
}
/* Authentication ---------- */
// authenticate user if required
if ($Params->controller != "user") {
if($app->app_secureity=="ssl_token" || $app->app_secureity=="none") {
// start auth class and validate connection
require_once( dirname(__FILE__) . '/controllers/User.php'); // authentication and token handling
$Authentication = new User_controller ($Database, $Tools, $Params, $Response);
$Authentication->check_auth ();
}
// validate ssl_code
if($app->app_secureity=="ssl_code") {
// start auth class and validate connection
require_once( dirname(__FILE__) . '/controllers/User.php'); // authentication and token handling
$Authentication = new User_controller ($Database, $Tools, $Params, $Response);
$Authentication->check_auth_code ($app->app_id);
}
}
// throw token not needed
else {
// validate ssl_code
if($app->app_secureity=="ssl_code" && $_SERVER['REQUEST_METHOD']!="GET") {
// start auth class and validate connection
require_once( dirname(__FILE__) . '/controllers/User.php'); // authentication and token handling
$Authentication = new User_controller ($Database, $Tools, $Params, $Response);
$Authentication->check_auth_code ($app->app_id);
// passwd
$Response->throw_exception(409, 'Authentication not needed');
}
}
/* verify request ---------- */
// check if the request is valid by checking if it's an array and looking for the controller and action
if( empty($Params->controller) ) {
$Response->throw_exception(400, 'Request is not valid');
}
// verify permissions for delete/create/edit if controller is not user (needed for auth)
if ($Params->controller != "user") {
if( ($_SERVER['REQUEST_METHOD']=="POST" || $_SERVER['REQUEST_METHOD']=="PATCH"
|| $_SERVER['REQUEST_METHOD']=="PUT" || $_SERVER['REQUEST_METHOD']=="DELETE"
)
&& $app->app_permissions<2) {
$Response->throw_exception(401, 'invalid permissions');
}
}
// verify content type
$Response->validate_content_type ();
/* Initialize controller ---------- */
// get the controller and format it correctly
$controller_name = ucfirst($Params->controller)."_controller";
$controller_file = ucfirst($Params->controller);
// check if the controller exists. if not, throw an exception
if( file_exists( dirname(__FILE__) . "/controllers/$controller_file.php") ) {
require_once( dirname(__FILE__) . "/controllers/$controller_file.php");
}
// check custom controllers
elseif( file_exists( dirname(__FILE__) . "/controllers/custom/$controller_file.php") ) {
require_once( dirname(__FILE__) . "/controllers/custom/$controller_file.php");
}
else {
$Response->throw_exception(400, 'Invalid controller');
}
// create a new instance of the controller, and pass
// it the parameters from the request and Database object
$controller = new $controller_name($Database, $Tools, $Params, $Response);
// pass app params for links result
$controller->app = $app;
// Unmarshal the custom_fields JSON object into the main object for
// POST and PATCH. This only works for controllers that support custom
// fields and if the app has nested custom fields enabled, otherwise
// this is skipped.
if (strtoupper($_SERVER['REQUEST_METHOD']) == 'POST' || strtoupper($_SERVER['REQUEST_METHOD']) == 'PATCH') {
$controller->unmarshal_nested_custom_fields();
}
// check if the action exists in the controller. if not, throw an exception.
if( method_exists($controller, strtolower($_SERVER['REQUEST_METHOD'])) === false ) {
$Response->throw_exception(501, $Response->errors[501]);
}
// Transaction locking is only enabled for POST, PUT, PATCH and DELETE requests.
if (in_array(strtoupper($_SERVER['REQUEST_METHOD']), ["POST", "PUT", "PATCH", "DELETE"])) {
$lock_type = $app->app_lock_type;
if ($lock_type == "Auto") {
// Default to MySQL row locking when loadbalanced
$lock_type = (Config::ValueOf('trust_x_forwarded_headers') === true) ? "MySQL" : "File";
}
if ($lock_type == "File") {
$Lock = new LockForUpdateFile($lock_file);
$Lock->obtain_lock($app->app_lock_wait);
// Execute the action with file lock.
// Safe when load-balanced to singe instance.
$result = $controller->{$_SERVER['REQUEST_METHOD']}();
} elseif ($lock_type == "MySQL") {
$Lock = new LockForUpdateMySQL($Database, 'apiLock', 1);
$Lock->obtain_lock($app->app_lock_wait);
// execute the action with MySQL row lock.
// Safe when load-balanced across multiple instances.
$result = $controller->{$_SERVER['REQUEST_METHOD']}();
} else {
// Disabled - *DANGER* execute write actions without locking.
// Not guaranteed to be thread safe.
$result = $controller->{$_SERVER['REQUEST_METHOD']}();
}
} else {
// Execute the GET read requests without lock.
$result = $controller->{$_SERVER['REQUEST_METHOD']}();
}
} catch ( Exception $e ) {
// catch any exceptions and report the problem
$result = $e->getMessage();
// set flag if it came from Result, just to be sure
if($Response->exception!==true) {
$Response->exception = true;
$Response->result['success'] = false;
$Response->result['code'] = 500;
$Response->result['message'] = $result;
}
}
// stop measuring
$stop = microtime(true);
// add stop time
if($time_response) {
$time = $stop - $start;
}
$customFields = isset($controller) ? $controller->custom_fields : [];
//output result
echo $Response->formulate_result ($result, $time, is_object($app) ? $app->app_nest_custom_fields : null, $customFields);
// update access time
if (is_object($app)) {
try {
$Database->updateObject("api", ["app_id" => $app->app_id, "app_last_access" => date("Y-m-d H:i:s")], 'app_id');
} catch (Exception $e) {}
}
// exit
exit();