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

mod/web_services/start.php (1 issue)

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