Completed
Push — 3.1 ( d59679...4b8741 )
by Jeroen
62:38 queued 13s
created

mod/web_services/start.php (1 issue)

1
<?php
2
/**
3
 * Elgg web services API plugin
4
 */
5
6
7
/**
8
 * Web services init
9
 *
10
 * @return void
11
 */
12
function ws_init() {
13
14 77
	\Elgg\Includer::requireFileOnce(__DIR__ . "/lib/web_services.php");
15 77
	\Elgg\Includer::requireFileOnce(__DIR__ . "/lib/api_user.php");
16 77
	\Elgg\Includer::requireFileOnce(__DIR__ . "/lib/client.php");
17 77
	\Elgg\Includer::requireFileOnce(__DIR__ . "/lib/tokens.php");
18
19
	// Register a service handler for the default web services
20
	// The name rest is a misnomer as they are not RESTful
21 77
	elgg_ws_register_service_handler('rest', 'ws_rest_handler');
22
23
	// expose the list of api methods
24 77
	elgg_ws_expose_function("system.api.list", "list_all_apis", null,
25 77
		elgg_echo("system.api.list"), "GET", false, false);
26
27
	// The authentication token api
28 77
	elgg_ws_expose_function(
29 77
		"auth.gettoken",
30 77
		"auth_gettoken",
31
		[
32 77
			'username' =>  ['type' => 'string'],
33
			'password' =>  ['type' => 'string'],
34
		],
35 77
		elgg_echo('auth.gettoken'),
36 77
		'POST',
37 77
		false,
38 77
		false
39
	);
40
41 77
	elgg_register_plugin_hook_handler('rest:output', 'system.api.list', 'ws_system_api_list_hook');
42 77
}
43
44
/**
45
 * Handle a web service request
46
 *
47
 * Handles requests of format: http://site/services/api/handler/response_format/request
48
 * The first element after 'services/api/' is the service handler name as
49
 * registered by {@link register_service_handler()}.
50
 *
51
 * The remaining string is then passed to the {@link service_handler()}
52
 * which explodes by /, extracts the first element as the response format
53
 * (viewtype), and then passes the remaining array to the service handler
54
 * function registered by {@link register_service_handler()}.
55
 *
56
 * If a service handler isn't found, a 404 header is sent.
57
 *
58
 * @param array $segments URL segments
59
 *
60
 * @return bool
61
 */
62
function ws_page_handler($segments) {
63
	if (!isset($segments[0]) || $segments[0] != 'api') {
64
		return false;
65
	}
66
	array_shift($segments);
67
68
	$handler = array_shift($segments);
69
	$request = implode('/', $segments);
70
71
	service_handler($handler, $request);
72
73
	return true;
74
}
75
76
/**
77
 * A global array holding API methods.
78
 * The structure of this is
79
 * 	$API_METHODS = array (
80
 * 		$method => array (
81
 * 			"description" => "Some human readable description"
82
 * 			"function" = 'my_function_callback'
83
 * 			"parameters" = array (
84
 * 				"variable" = array ( // the order should be the same as the function callback
85
 * 					type => 'int' | 'bool' | 'float' | 'string'
86
 * 					required => true (default) | false
87
 *					default => value // optional
88
 * 				)
89
 * 			)
90
 * 			"call_method" = 'GET' | 'POST'
91
 * 			"require_api_auth" => true | false (default)
92
 * 			"require_user_auth" => true | false (default)
93
 * 		)
94
 *  )
95
 */
96 1
global $API_METHODS;
97 1
$API_METHODS = [];
98
99
/** Define a global array of errors */
100 1
global $ERRORS;
101 1
$ERRORS = [];
102
103
/**
104
 * Expose a function as a web service.
105
 *
106
 * Limitations: Currently cannot expose functions which expect objects.
107
 * It also cannot handle arrays of bools or arrays of arrays.
108
 * Also, input will be filtered to protect against XSS attacks through the web services.
109
 *
110
 * @param string   $method            The api name to expose - for example "myapi.dosomething"
111
 * @param callable $function          Callable to handle API call
112
 * @param array    $parameters        (optional) List of parameters in the same order as in
113
 *                                    your function. Default values may be set for parameters which
114
 *                                    allow REST api users flexibility in what parameters are passed.
115
 *                                    Generally, optional parameters should be after required
116
 *                                    parameters. If an optional parameter is not set and has no default,
117
 *                                    the API callable will receive null.
118
 *
119
 *                                    This array should be in the format
120
 *                                      "variable" = array (
121
 *                                          type => 'int' | 'bool' | 'float' | 'string' | 'array'
122
 *                                          required => true (default) | false
123
 *                                  	    default => value (optional)
124
 *                                  	 )
125
 * @param string   $description       (optional) human readable description of the function.
126
 * @param string   $call_method       (optional) Define what http method must be used for
127
 *                                    this function. Default: GET
128
 * @param bool     $require_api_auth  (optional) (default is false) Does this method
129
 *                                    require API authorization? (example: API key)
130
 * @param bool     $require_user_auth (optional) (default is false) Does this method
131
 *                                    require user authorization?
132
 * @param bool     $assoc             (optional) If set to true, the callback function will receive a single argument
133
 *                                    that contains an associative array of parameter => input pairs for the method.
134
 *
135
 * @return bool
136
 * @throws InvalidParameterException
137
 */
138
function elgg_ws_expose_function(
139
	$method,
140
	$function,
141
	$parameters = null,
142
	$description = "",
143
	$call_method = "GET",
144
	$require_api_auth = false,
145
	$require_user_auth = false,
146
	$assoc = false
147
) {
148
149 96
	global $API_METHODS;
150
151 96
	if (empty($method) || empty($function)) {
152 2
		$msg = elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet');
153 2
		throw new InvalidParameterException($msg);
154
	}
155
156
	// does not check whether this method has already been exposed - good idea?
157 94
	$API_METHODS[$method] = [];
158
159 94
	$API_METHODS[$method]["description"] = $description;
160
161
	// does not check whether callable - done in execute_method()
162 94
	$API_METHODS[$method]["function"] = $function;
163
164 94
	if ($parameters != null) {
165 92
		if (!is_array($parameters)) {
166 1
			$msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', [$method]);
167 1
			throw new InvalidParameterException($msg);
168
		}
169
170
		// catch common mistake of not setting up param array correctly
171 91
		$first = current($parameters);
172 91
		if (!is_array($first)) {
173 1
			$msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', [$method]);
174 1
			throw new InvalidParameterException($msg);
175
		}
176
	}
177
178 92
	if ($parameters != null) {
179
		// ensure the required flag is set correctly in default case for each parameter
180 90
		foreach ($parameters as $key => $value) {
181
			// check if 'required' was specified - if not, make it true
182 90
			if (!array_key_exists('required', $value)) {
183 90
				$parameters[$key]['required'] = true;
184
			}
185
		}
186
187 90
		$API_METHODS[$method]["parameters"] = $parameters;
188
	}
189
190 92
	$call_method = strtoupper($call_method);
191 92
	switch ($call_method) {
192 92
		case 'POST' :
193 85
			$API_METHODS[$method]["call_method"] = 'POST';
194 85
			break;
195 84
		case 'GET' :
196 83
			$API_METHODS[$method]["call_method"] = 'GET';
197 83
			break;
198
		default :
199 1
			$msg = elgg_echo('InvalidParameterException:UnrecognisedHttpMethod',
200 1
			[$call_method, $method]);
201
202 1
			throw new InvalidParameterException($msg);
203
	}
204
205 91
	$API_METHODS[$method]["require_api_auth"] = $require_api_auth;
206
207 91
	$API_METHODS[$method]["require_user_auth"] = $require_user_auth;
208
209 91
	$API_METHODS[$method]["assoc"] = (bool) $assoc;
210
211 91
	return true;
212
}
213
214
/**
215
 * Unregister a web services method
216
 *
217
 * @param string $method The api name that was exposed
218
 * @return void
219
 */
220
function elgg_ws_unexpose_function($method) {
221 1
	global $API_METHODS;
222
223 1
	if (isset($API_METHODS[$method])) {
224 1
		unset($API_METHODS[$method]);
225
	}
226 1
}
227
228
/**
229
 * Simple api to return a list of all api's installed on the system.
230
 *
231
 * @return array
232
 * @internal
233
 */
234
function list_all_apis() {
235
	global $API_METHODS;
236
237
	// sort first
238
	ksort($API_METHODS);
239
240
	return $API_METHODS;
241
}
242
243
/**
244
 * Registers a web services handler
245
 *
246
 * @param string $handler  Web services type
247
 * @param string $function Your function name
248
 *
249
 * @return bool Depending on success
250
 */
251
function elgg_ws_register_service_handler($handler, $function) {
252 77
	$servicehandler = _elgg_config()->servicehandler;
253 77
	if (!$servicehandler) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $servicehandler of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
254 77
		$servicehandler = [];
255
	}
256 77
	if (is_callable($function, true)) {
257 77
		$servicehandler[$handler] = $function;
258 77
		_elgg_config()->servicehandler = $servicehandler;
259 77
		return true;
260
	}
261
262
	return false;
263
}
264
265
/**
266
 * Remove a web service
267
 * To replace a web service handler, register the desired handler over the old on
268
 * with register_service_handler().
269
 *
270
 * @param string $handler web services type
271
 * @return void
272
 */
273
function elgg_ws_unregister_service_handler($handler) {
274
	$servicehandler = _elgg_config()->servicehandler;
275
276
	if (isset($servicehandler, $servicehandler[$handler])) {
277
		unset($servicehandler[$handler]);
278
		_elgg_config()->servicehandler = $servicehandler;
279
	}
280
}
281
282
/**
283
 * REST API handler
284
 *
285
 * @return void
286
 * @internal
287
 *
288
 * @throws SecurityException|APIException
289
 */
290
function ws_rest_handler() {
291
292
	$viewtype = elgg_get_viewtype();
293
294
	if (!elgg_view_exists('api/output', $viewtype)) {
295
		header("HTTP/1.0 400 Bad Request");
296
		header("Content-type: text/plain");
297
		echo "Missing view 'api/output' in viewtype '$viewtype'.";
298
		if (in_array($viewtype, ['xml', 'php'])) {
299
			echo "\nEnable the 'data_views' plugin to add this view.";
300
		}
301
		exit;
302
	}
303
304
	// Register the error handler
305
	error_reporting(E_ALL);
306
	set_error_handler('_php_api_error_handler');
307
308
	// Register a default exception handler
309
	set_exception_handler('_php_api_exception_handler');
310
311
	// plugins should return true to control what API and user authentication handlers are registered
312
	if (elgg_trigger_plugin_hook('rest', 'init', null, false) == false) {
313
		// for testing from a web browser, you can use the session PAM
314
		// do not use for production sites!!
315
		//register_pam_handler('pam_auth_session');
316
317
		// user token can also be used for user authentication
318
		register_pam_handler('pam_auth_usertoken');
319
320
		// simple API key check
321
		register_pam_handler('api_auth_key', "sufficient", "api");
322
		// hmac
323
		register_pam_handler('api_auth_hmac', "sufficient", "api");
324
	}
325
326
	// Get parameter variables
327
	$method = get_input('method');
328
329
	// this will throw an exception if authentication fails
330
	authenticate_method($method);
331
332
	$result = execute_method($method);
333
334
335
	if (!($result instanceof GenericResult)) {
336
		throw new APIException(elgg_echo('APIException:ApiResultUnknown'));
337
	}
338
339
	// Output the result
340
	echo elgg_view_page($method, elgg_view("api/output", ["result" => $result]));
341
}
342
343
/**
344
 * Filters system API list to remove PHP internal function names
345
 *
346
 * @param \Elgg\Hook $hook "rest:output", "system.api.list"
347
 *
348
 * @return array
349
 */
350
function ws_system_api_list_hook(\Elgg\Hook $hook) {
351
	$return = $hook->getValue();
352
	if (!empty($return) && is_array($return)) {
353
		foreach ($return as $method => $settings) {
354
			unset($return[$method]['function']);
355
		}
356
	}
357
358
	return $return;
359
}
360
361
return function() {
362 77
	elgg_register_event_handler('init', 'system', 'ws_init');
363
};
364