Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

mod/web_services/start.php (2 issues)

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');
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);
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
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;
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