Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
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 4
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
 * Web services init
8
 *
9
 * @return void
10
 */
11
function ws_init() {
12 31
	$lib_dir = __DIR__ . "/lib";
13 31
	elgg_register_library('elgg:ws', "$lib_dir/web_services.php");
14 31
	elgg_register_library('elgg:ws:api_user', "$lib_dir/api_user.php");
15 31
	elgg_register_library('elgg:ws:client', "$lib_dir/client.php");
16 31
	elgg_register_library('elgg:ws:tokens', "$lib_dir/tokens.php");
17
18 31
	elgg_load_library('elgg:ws:api_user');
19 31
	elgg_load_library('elgg:ws:tokens');
20
21 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

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

77
	service_handler($handler, /** @scrutinizer ignore-type */ $request);
Loading history...
78
79
	return true;
80
}
81
82
/**
83
 * A global array holding API methods.
84
 * The structure of this is
85
 * 	$API_METHODS = array (
86
 * 		$method => array (
87
 * 			"description" => "Some human readable description"
88
 * 			"function" = 'my_function_callback'
89
 * 			"parameters" = array (
90
 * 				"variable" = array ( // the order should be the same as the function callback
91
 * 					type => 'int' | 'bool' | 'float' | 'string'
92
 * 					required => true (default) | false
93
 *					default => value // optional
94
 * 				)
95
 * 			)
96
 * 			"call_method" = 'GET' | 'POST'
97
 * 			"require_api_auth" => true | false (default)
98
 * 			"require_user_auth" => true | false (default)
99
 * 		)
100
 *  )
101
 */
102
global $API_METHODS;
103
$API_METHODS = [];
104
105
/** Define a global array of errors */
106
global $ERRORS;
107
$ERRORS = [];
108
109
/**
110
 * Expose a function as a web service.
111
 *
112
 * Limitations: Currently cannot expose functions which expect objects.
113
 * It also cannot handle arrays of bools or arrays of arrays.
114
 * Also, input will be filtered to protect against XSS attacks through the web services.
115
 *
116
 * @param string   $method            The api name to expose - for example "myapi.dosomething"
117
 * @param callable $function          Callable to handle API call
118
 * @param array    $parameters        (optional) List of parameters in the same order as in
119
 *                                    your function. Default values may be set for parameters which
120
 *                                    allow REST api users flexibility in what parameters are passed.
121
 *                                    Generally, optional parameters should be after required
122
 *                                    parameters. If an optional parameter is not set and has no default,
123
 *                                    the API callable will receive null.
124
 *
125
 *                                    This array should be in the format
126
 *                                      "variable" = array (
127
 *                                          type => 'int' | 'bool' | 'float' | 'string' | 'array'
128
 *                                          required => true (default) | false
129
 *                                  	    default => value (optional)
130
 *                                  	 )
131
 * @param string   $description       (optional) human readable description of the function.
132
 * @param string   $call_method       (optional) Define what http method must be used for
133
 *                                    this function. Default: GET
134
 * @param bool     $require_api_auth  (optional) (default is false) Does this method
135
 *                                    require API authorization? (example: API key)
136
 * @param bool     $require_user_auth (optional) (default is false) Does this method
137
 *                                    require user authorization?
138
 * @param bool     $assoc             (optional) If set to true, the callback function will receive a single argument
139
 *                                    that contains an associative array of parameter => input pairs for the method.
140
 *
141
 * @return bool
142
 * @throws InvalidParameterException
143
 */
144
function elgg_ws_expose_function(
145
	$method,
146
	$function,
147
	$parameters = null,
148
	$description = "",
149
	$call_method = "GET",
150
	$require_api_auth = false,
151
	$require_user_auth = false,
152
	$assoc = false
153
) {
154
155 50
	global $API_METHODS;
156
157 50
	if (empty($method) || empty($function)) {
158 2
		$msg = elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet');
159 2
		throw new InvalidParameterException($msg);
160
	}
161
162
	// does not check whether this method has already been exposed - good idea?
163 48
	$API_METHODS[$method] = [];
164
165 48
	$API_METHODS[$method]["description"] = $description;
166
167
	// does not check whether callable - done in execute_method()
168 48
	$API_METHODS[$method]["function"] = $function;
169
170 48
	if ($parameters != null) {
171 46
		if (!is_array($parameters)) {
172 1
			$msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', [$method]);
173 1
			throw new InvalidParameterException($msg);
174
		}
175
176
		// catch common mistake of not setting up param array correctly
177 45
		$first = current($parameters);
178 45
		if (!is_array($first)) {
179 1
			$msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', [$method]);
180 1
			throw new InvalidParameterException($msg);
181
		}
182
	}
183
184 46
	if ($parameters != null) {
185
		// ensure the required flag is set correctly in default case for each parameter
186 44
		foreach ($parameters as $key => $value) {
187
			// check if 'required' was specified - if not, make it true
188 44
			if (!array_key_exists('required', $value)) {
189 44
				$parameters[$key]['required'] = true;
190
			}
191
		}
192
193 44
		$API_METHODS[$method]["parameters"] = $parameters;
194
	}
195
196 46
	$call_method = strtoupper($call_method);
197 46
	switch ($call_method) {
198
		case 'POST' :
199 39
			$API_METHODS[$method]["call_method"] = 'POST';
200 39
			break;
201
		case 'GET' :
202 37
			$API_METHODS[$method]["call_method"] = 'GET';
203 37
			break;
204
		default :
205 1
			$msg = elgg_echo('InvalidParameterException:UnrecognisedHttpMethod',
206 1
			[$call_method, $method]);
207
208 1
			throw new InvalidParameterException($msg);
209
	}
210
211 45
	$API_METHODS[$method]["require_api_auth"] = $require_api_auth;
212
213 45
	$API_METHODS[$method]["require_user_auth"] = $require_user_auth;
214
215 45
	$API_METHODS[$method]["assoc"] = (bool) $assoc;
216
217 45
	return true;
218
}
219
220
/**
221
 * Unregister a web services method
222
 *
223
 * @param string $method The api name that was exposed
224
 * @return void
225
 */
226
function elgg_ws_unexpose_function($method) {
227 1
	global $API_METHODS;
228
229 1
	if (isset($API_METHODS[$method])) {
230 1
		unset($API_METHODS[$method]);
231
	}
232 1
}
233
234
/**
235
 * Simple api to return a list of all api's installed on the system.
236
 *
237
 * @return array
238
 * @access private
239
 */
240
function list_all_apis() {
241
	global $API_METHODS;
242
243
	// sort first
244
	ksort($API_METHODS);
245
246
	return $API_METHODS;
247
}
248
249
/**
250
 * Registers a web services handler
251
 *
252
 * @param string $handler  Web services type
253
 * @param string $function Your function name
254
 *
255
 * @return bool Depending on success
256
 */
257
function elgg_ws_register_service_handler($handler, $function) {
258 31
	$servicehandler = _elgg_config()->servicehandler;
259 31
	if (!$servicehandler) {
260 18
		$servicehandler = [];
261
	}
262 31
	if (is_callable($function, true)) {
263 31
		$servicehandler[$handler] = $function;
264 31
		_elgg_config()->servicehandler = $servicehandler;
265 31
		return true;
266
	}
267
268
	return false;
269
}
270
271
/**
272
 * Remove a web service
273
 * To replace a web service handler, register the desired handler over the old on
274
 * with register_service_handler().
275
 *
276
 * @param string $handler web services type
277
 * @return void
278
 */
279
function elgg_ws_unregister_service_handler($handler) {
280
	$servicehandler = _elgg_config()->servicehandler;
281
282
	if (isset($servicehandler, $servicehandler[$handler])) {
283
		unset($servicehandler[$handler]);
284
		_elgg_config()->servicehandler = $servicehandler;
285
	}
286
}
287
288
/**
289
 * REST API handler
290
 *
291
 * @return void
292
 * @access private
293
 *
294
 * @throws SecurityException|APIException
295
 */
296
function ws_rest_handler() {
297
298
	$viewtype = elgg_get_viewtype();
299
300
	if (!elgg_view_exists('api/output', $viewtype)) {
301
		header("HTTP/1.0 400 Bad Request");
302
		header("Content-type: text/plain");
303
		echo "Missing view 'api/output' in viewtype '$viewtype'.";
304
		if (in_array($viewtype, ['xml', 'php'])) {
305
			echo "\nEnable the 'data_views' plugin to add this view.";
306
		}
307
		exit;
0 ignored issues
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...
308
	}
309
310
	elgg_load_library('elgg:ws');
311
312
	// Register the error handler
313
	error_reporting(E_ALL);
314
	set_error_handler('_php_api_error_handler');
315
316
	// Register a default exception handler
317
	set_exception_handler('_php_api_exception_handler');
318
319
	// plugins should return true to control what API and user authentication handlers are registered
320
	if (elgg_trigger_plugin_hook('rest', 'init', null, false) == false) {
0 ignored issues
show
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...
321
		// for testing from a web browser, you can use the session PAM
322
		// do not use for production sites!!
323
		//register_pam_handler('pam_auth_session');
324
325
		// user token can also be used for user authentication
326
		register_pam_handler('pam_auth_usertoken');
327
328
		// simple API key check
329
		register_pam_handler('api_auth_key', "sufficient", "api");
330
		// hmac
331
		register_pam_handler('api_auth_hmac', "sufficient", "api");
332
	}
333
334
	// Get parameter variables
335
	$method = get_input('method');
336
	$result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
337
338
	// this will throw an exception if authentication fails
339
	authenticate_method($method);
340
341
	$result = execute_method($method);
342
343
344
	if (!($result instanceof GenericResult)) {
345
		throw new APIException(elgg_echo('APIException:ApiResultUnknown'));
346
	}
347
348
	// Output the result
349
	echo elgg_view_page($method, elgg_view("api/output", ["result" => $result]));
350
}
351
352
/**
353
 * Filters system API list to remove PHP internal function names
354
 *
355
 * @param string $hook   "rest:output"
356
 * @param string $type   "system.api.list"
357
 * @param array  $return API list
358
 * @param array  $params Method params
359
 * @return array
360
 */
361
function ws_system_api_list_hook($hook, $type, $return, $params) {
362
363
	if (!empty($return) && is_array($return)) {
364
		foreach ($return as $method => $settings) {
365
			unset($return[$method]['function']);
366
		}
367
	}
368
369
	return $return;
370
}
371
372
return function() {
373 18
	elgg_register_event_handler('init', 'system', 'ws_init');
374
};
375