Passed
Branch master (739723)
by Ismayil
05:58
created

ws_system_api_list_hook()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 4
nc 2
nop 4
dl 0
loc 9
ccs 0
cts 1
cp 0
crap 20
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * Elgg web services API plugin
4
 */
5
6
7
/**
8
 * Web services init
9
 *
10
 * @return void
11
 */
12 31
function ws_init() {
13 31
14 31
	\Elgg\Includer::requireFileOnce(__DIR__ . "/lib/web_services.php");
15 31
	\Elgg\Includer::requireFileOnce(__DIR__ . "/lib/api_user.php");
16 31
	\Elgg\Includer::requireFileOnce(__DIR__ . "/lib/client.php");
17
	\Elgg\Includer::requireFileOnce(__DIR__ . "/lib/tokens.php");
18 31
19 31
	elgg_register_page_handler('services', 'ws_page_handler');
0 ignored issues
show
Deprecated Code introduced by
The function elgg_register_page_handler() has been deprecated: 3.0 ( Ignorable by Annotation )

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

19
	/** @scrutinizer ignore-deprecated */ elgg_register_page_handler('services', 'ws_page_handler');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
20
21 31
	// Register a service handler for the default web services
22
	// The name rest is a misnomer as they are not RESTful
23
	elgg_ws_register_service_handler('rest', 'ws_rest_handler');
24
25 31
	// expose the list of api methods
26
	elgg_ws_expose_function("system.api.list", "list_all_apis", null,
27
		elgg_echo("system.api.list"), "GET", false, false);
28 31
29 31
	// The authentication token api
30
	elgg_ws_expose_function(
31
		"auth.gettoken",
32 31
		"auth_gettoken",
33 31
		[
34 31
			'username' =>  ['type' => 'string'],
35
			'password' =>  ['type' => 'string'],
36 31
		],
37
		elgg_echo('auth.gettoken'),
38
		'POST',
39 31
		false,
40 31
		false
41 31
	);
42 31
43
	elgg_register_plugin_hook_handler('rest:output', 'system.api.list', 'ws_system_api_list_hook');
44
}
45 31
46 31
/**
47
 * Handle a web service request
48
 *
49
 * Handles requests of format: http://site/services/api/handler/response_format/request
50
 * The first element after 'services/api/' is the service handler name as
51
 * registered by {@link register_service_handler()}.
52
 *
53
 * The remaining string is then passed to the {@link service_handler()}
54
 * which explodes by /, extracts the first element as the response format
55
 * (viewtype), and then passes the remaining array to the service handler
56
 * function registered by {@link register_service_handler()}.
57
 *
58
 * If a service handler isn't found, a 404 header is sent.
59
 *
60
 * @param array $segments URL segments
61
 *
62
 * @return bool
63
 */
64
function ws_page_handler($segments) {
65
	if (!isset($segments[0]) || $segments[0] != 'api') {
66
		return false;
67
	}
68
	array_shift($segments);
69
70
	$handler = array_shift($segments);
71
	$request = implode('/', $segments);
72
73
	service_handler($handler, $request);
0 ignored issues
show
Bug introduced by
$request of type string is incompatible with the type array expected by parameter $request of service_handler(). ( Ignorable by Annotation )

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

73
	service_handler($handler, /** @scrutinizer ignore-type */ $request);
Loading history...
74
75
	return true;
76
}
77
78
/**
79
 * A global array holding API methods.
80
 * The structure of this is
81
 * 	$API_METHODS = array (
82
 * 		$method => array (
83
 * 			"description" => "Some human readable description"
84
 * 			"function" = 'my_function_callback'
85
 * 			"parameters" = array (
86
 * 				"variable" = array ( // the order should be the same as the function callback
87
 * 					type => 'int' | 'bool' | 'float' | 'string'
88
 * 					required => true (default) | false
89
 *					default => value // optional
90
 * 				)
91
 * 			)
92
 * 			"call_method" = 'GET' | 'POST'
93
 * 			"require_api_auth" => true | false (default)
94
 * 			"require_user_auth" => true | false (default)
95
 * 		)
96
 *  )
97
 */
98
global $API_METHODS;
99
$API_METHODS = [];
100
101
/** Define a global array of errors */
102
global $ERRORS;
103
$ERRORS = [];
104
105
/**
106
 * Expose a function as a web service.
107
 *
108
 * Limitations: Currently cannot expose functions which expect objects.
109
 * It also cannot handle arrays of bools or arrays of arrays.
110
 * Also, input will be filtered to protect against XSS attacks through the web services.
111
 *
112
 * @param string   $method            The api name to expose - for example "myapi.dosomething"
113
 * @param callable $function          Callable to handle API call
114
 * @param array    $parameters        (optional) List of parameters in the same order as in
115
 *                                    your function. Default values may be set for parameters which
116
 *                                    allow REST api users flexibility in what parameters are passed.
117
 *                                    Generally, optional parameters should be after required
118
 *                                    parameters. If an optional parameter is not set and has no default,
119
 *                                    the API callable will receive null.
120
 *
121
 *                                    This array should be in the format
122
 *                                      "variable" = array (
123
 *                                          type => 'int' | 'bool' | 'float' | 'string' | 'array'
124
 *                                          required => true (default) | false
125
 *                                  	    default => value (optional)
126
 *                                  	 )
127
 * @param string   $description       (optional) human readable description of the function.
128
 * @param string   $call_method       (optional) Define what http method must be used for
129
 *                                    this function. Default: GET
130
 * @param bool     $require_api_auth  (optional) (default is false) Does this method
131
 *                                    require API authorization? (example: API key)
132
 * @param bool     $require_user_auth (optional) (default is false) Does this method
133
 *                                    require user authorization?
134
 * @param bool     $assoc             (optional) If set to true, the callback function will receive a single argument
135
 *                                    that contains an associative array of parameter => input pairs for the method.
136
 *
137
 * @return bool
138
 * @throws InvalidParameterException
139
 */
140
function elgg_ws_expose_function(
141
	$method,
142
	$function,
143
	$parameters = null,
144
	$description = "",
145
	$call_method = "GET",
146
	$require_api_auth = false,
147
	$require_user_auth = false,
148
	$assoc = false
149
) {
150
151
	global $API_METHODS;
152
153
	if (empty($method) || empty($function)) {
154
		$msg = elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet');
155 50
		throw new InvalidParameterException($msg);
156
	}
157 50
158 2
	// does not check whether this method has already been exposed - good idea?
159 2
	$API_METHODS[$method] = [];
160
161
	$API_METHODS[$method]["description"] = $description;
162
163 48
	// does not check whether callable - done in execute_method()
164
	$API_METHODS[$method]["function"] = $function;
165 48
166
	if ($parameters != null) {
167
		if (!is_array($parameters)) {
0 ignored issues
show
introduced by
The condition ! is_array($parameters) can never be true.
Loading history...
168 48
			$msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', [$method]);
169
			throw new InvalidParameterException($msg);
170 48
		}
171 46
172 1
		// catch common mistake of not setting up param array correctly
173 1
		$first = current($parameters);
174
		if (!is_array($first)) {
175
			$msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', [$method]);
176
			throw new InvalidParameterException($msg);
177 45
		}
178 45
	}
179 1
180 1
	if ($parameters != null) {
181
		// ensure the required flag is set correctly in default case for each parameter
182
		foreach ($parameters as $key => $value) {
183
			// check if 'required' was specified - if not, make it true
184 46
			if (!array_key_exists('required', $value)) {
185
				$parameters[$key]['required'] = true;
186 44
			}
187
		}
188 44
189 44
		$API_METHODS[$method]["parameters"] = $parameters;
190
	}
191
192
	$call_method = strtoupper($call_method);
193 44
	switch ($call_method) {
194
		case 'POST' :
195
			$API_METHODS[$method]["call_method"] = 'POST';
196 46
			break;
197 46
		case 'GET' :
198
			$API_METHODS[$method]["call_method"] = 'GET';
199 39
			break;
200 39
		default :
201
			$msg = elgg_echo('InvalidParameterException:UnrecognisedHttpMethod',
202 37
			[$call_method, $method]);
203 37
204
			throw new InvalidParameterException($msg);
205 1
	}
206 1
207
	$API_METHODS[$method]["require_api_auth"] = $require_api_auth;
208 1
209
	$API_METHODS[$method]["require_user_auth"] = $require_user_auth;
210
211 45
	$API_METHODS[$method]["assoc"] = (bool) $assoc;
212
213 45
	return true;
214
}
215 45
216
/**
217 45
 * Unregister a web services method
218
 *
219
 * @param string $method The api name that was exposed
220
 * @return void
221
 */
222
function elgg_ws_unexpose_function($method) {
223
	global $API_METHODS;
224
225
	if (isset($API_METHODS[$method])) {
226
		unset($API_METHODS[$method]);
227 1
	}
228
}
229 1
230 1
/**
231
 * Simple api to return a list of all api's installed on the system.
232 1
 *
233
 * @return array
234
 * @access private
235
 */
236
function list_all_apis() {
237
	global $API_METHODS;
238
239
	// sort first
240
	ksort($API_METHODS);
241
242
	return $API_METHODS;
243
}
244
245
/**
246
 * Registers a web services handler
247
 *
248
 * @param string $handler  Web services type
249
 * @param string $function Your function name
250
 *
251
 * @return bool Depending on success
252
 */
253
function elgg_ws_register_service_handler($handler, $function) {
254
	$servicehandler = _elgg_config()->servicehandler;
255
	if (!$servicehandler) {
256
		$servicehandler = [];
257
	}
258 31
	if (is_callable($function, true)) {
259 31
		$servicehandler[$handler] = $function;
260 18
		_elgg_config()->servicehandler = $servicehandler;
261
		return true;
262 31
	}
263 31
264 31
	return false;
265 31
}
266
267
/**
268
 * Remove a web service
269
 * To replace a web service handler, register the desired handler over the old on
270
 * with register_service_handler().
271
 *
272
 * @param string $handler web services type
273
 * @return void
274
 */
275
function elgg_ws_unregister_service_handler($handler) {
276
	$servicehandler = _elgg_config()->servicehandler;
277
278
	if (isset($servicehandler, $servicehandler[$handler])) {
279
		unset($servicehandler[$handler]);
280
		_elgg_config()->servicehandler = $servicehandler;
281
	}
282
}
283
284
/**
285
 * REST API handler
286
 *
287
 * @return void
288
 * @access private
289
 *
290
 * @throws SecurityException|APIException
291
 */
292
function ws_rest_handler() {
293
294
	$viewtype = elgg_get_viewtype();
295
296
	if (!elgg_view_exists('api/output', $viewtype)) {
297
		header("HTTP/1.0 400 Bad Request");
298
		header("Content-type: text/plain");
299
		echo "Missing view 'api/output' in viewtype '$viewtype'.";
300
		if (in_array($viewtype, ['xml', 'php'])) {
301
			echo "\nEnable the 'data_views' plugin to add this view.";
302
		}
303
		exit;
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
304
	}
305
306
	// Register the error handler
307
	error_reporting(E_ALL);
308
	set_error_handler('_php_api_error_handler');
309
310
	// Register a default exception handler
311
	set_exception_handler('_php_api_exception_handler');
312
313
	// plugins should return true to control what API and user authentication handlers are registered
314
	if (elgg_trigger_plugin_hook('rest', 'init', null, false) == false) {
0 ignored issues
show
introduced by
The condition elgg_trigger_plugin_hook..., null, false) == false can never be false.
Loading history...
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
315
		// for testing from a web browser, you can use the session PAM
316
		// do not use for production sites!!
317
		//register_pam_handler('pam_auth_session');
318
319
		// user token can also be used for user authentication
320
		register_pam_handler('pam_auth_usertoken');
321
322
		// simple API key check
323
		register_pam_handler('api_auth_key', "sufficient", "api");
324
		// hmac
325
		register_pam_handler('api_auth_hmac', "sufficient", "api");
326
	}
327
328
	// Get parameter variables
329
	$method = get_input('method');
330
	$result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
331
332
	// this will throw an exception if authentication fails
333
	authenticate_method($method);
334
335
	$result = execute_method($method);
336
337
338
	if (!($result instanceof GenericResult)) {
0 ignored issues
show
introduced by
The condition ! $result instanceof GenericResult can never be true.
Loading history...
339
		throw new APIException(elgg_echo('APIException:ApiResultUnknown'));
340
	}
341
342
	// Output the result
343
	echo elgg_view_page($method, elgg_view("api/output", ["result" => $result]));
344
}
345
346
/**
347
 * Filters system API list to remove PHP internal function names
348
 *
349
 * @param string $hook   "rest:output"
350
 * @param string $type   "system.api.list"
351
 * @param array  $return API list
352
 * @param array  $params Method params
353
 * @return array
354
 */
355
function ws_system_api_list_hook($hook, $type, $return, $params) {
356
357
	if (!empty($return) && is_array($return)) {
358
		foreach ($return as $method => $settings) {
359
			unset($return[$method]['function']);
360
		}
361
	}
362
363
	return $return;
364
}
365
366
return function() {
367
	elgg_register_event_handler('init', 'system', 'ws_init');
368
};
369