Completed
Push — master ( f4a3b3...4452de )
by Jeroen
67:28 queued 11:29
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 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 31
	\Elgg\Includer::requireFileOnce(__DIR__ . "/lib/tokens.php");
18
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
	// Register a service handler for the default web services
22
	// The name rest is a misnomer as they are not RESTful
23 31
	elgg_ws_register_service_handler('rest', 'ws_rest_handler');
24
25
	// expose the list of api methods
26 31
	elgg_ws_expose_function("system.api.list", "list_all_apis", null,
27 31
		elgg_echo("system.api.list"), "GET", false, false);
28
29
	// The authentication token api
30 31
	elgg_ws_expose_function(
31 31
		"auth.gettoken",
32 31
		"auth_gettoken",
33
		[
34 31
			'username' =>  ['type' => 'string'],
35
			'password' =>  ['type' => 'string'],
36
		],
37 31
		elgg_echo('auth.gettoken'),
38 31
		'POST',
39 31
		false,
40 31
		false
41
	);
42
43 31
	elgg_register_plugin_hook_handler('rest:output', 'system.api.list', 'ws_system_api_list_hook');
44 31
}
45
46
/**
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 50
	global $API_METHODS;
152
153 50
	if (empty($method) || empty($function)) {
154 2
		$msg = elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet');
155 2
		throw new InvalidParameterException($msg);
156
	}
157
158
	// does not check whether this method has already been exposed - good idea?
159 48
	$API_METHODS[$method] = [];
160
161 48
	$API_METHODS[$method]["description"] = $description;
162
163
	// does not check whether callable - done in execute_method()
164 48
	$API_METHODS[$method]["function"] = $function;
165
166 48
	if ($parameters != null) {
167 46
		if (!is_array($parameters)) {
168 1
			$msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', [$method]);
169 1
			throw new InvalidParameterException($msg);
170
		}
171
172
		// catch common mistake of not setting up param array correctly
173 45
		$first = current($parameters);
174 45
		if (!is_array($first)) {
175 1
			$msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', [$method]);
176 1
			throw new InvalidParameterException($msg);
177
		}
178
	}
179
180 46
	if ($parameters != null) {
181
		// ensure the required flag is set correctly in default case for each parameter
182 44
		foreach ($parameters as $key => $value) {
183
			// check if 'required' was specified - if not, make it true
184 44
			if (!array_key_exists('required', $value)) {
185 44
				$parameters[$key]['required'] = true;
186
			}
187
		}
188
189 44
		$API_METHODS[$method]["parameters"] = $parameters;
190
	}
191
192 46
	$call_method = strtoupper($call_method);
193 46
	switch ($call_method) {
194
		case 'POST' :
195 39
			$API_METHODS[$method]["call_method"] = 'POST';
196 39
			break;
197
		case 'GET' :
198 37
			$API_METHODS[$method]["call_method"] = 'GET';
199 37
			break;
200
		default :
201 1
			$msg = elgg_echo('InvalidParameterException:UnrecognisedHttpMethod',
202 1
			[$call_method, $method]);
203
204 1
			throw new InvalidParameterException($msg);
205
	}
206
207 45
	$API_METHODS[$method]["require_api_auth"] = $require_api_auth;
208
209 45
	$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
216
/**
217
 * 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 1
	global $API_METHODS;
224
225 1
	if (isset($API_METHODS[$method])) {
226 1
		unset($API_METHODS[$method]);
227
	}
228 1
}
229
230
/**
231
 * Simple api to return a list of all api's installed on the system.
232
 *
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 31
	$servicehandler = _elgg_config()->servicehandler;
255 31
	if (!$servicehandler) {
256 18
		$servicehandler = [];
257
	}
258 31
	if (is_callable($function, true)) {
259 31
		$servicehandler[$handler] = $function;
260 31
		_elgg_config()->servicehandler = $servicehandler;
261 31
		return true;
262
	}
263
264
	return false;
265
}
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;
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 18
	elgg_register_event_handler('init', 'system', 'ws_init');
368
};
369