1 | <?php |
||
2 | /** |
||
3 | * Elgg web services API library |
||
4 | * Functions and objects for exposing custom web services. |
||
5 | * |
||
6 | */ |
||
7 | |||
8 | /** |
||
9 | * Check that the method call has the proper API and user authentication |
||
10 | * |
||
11 | * @param string $method The api name that was exposed |
||
12 | * |
||
13 | * @return true or throws an exception |
||
14 | * @throws APIException |
||
15 | * @since 1.7.0 |
||
16 | * @access private |
||
17 | */ |
||
18 | function authenticate_method($method) { |
||
19 | 4 | global $API_METHODS; |
|
20 | |||
21 | // method must be exposed |
||
22 | 4 | if (!isset($API_METHODS[$method])) { |
|
23 | 1 | throw new APIException(elgg_echo('APIException:MethodCallNotImplemented', [$method])); |
|
24 | } |
||
25 | |||
26 | // check API authentication if required |
||
27 | 3 | if ($API_METHODS[$method]["require_api_auth"] == true) { |
|
28 | 1 | $api_pam = new ElggPAM('api'); |
|
29 | 1 | if ($api_pam->authenticate() !== true) { |
|
30 | 1 | throw new APIException(elgg_echo('APIException:APIAuthenticationFailed')); |
|
31 | } |
||
32 | } |
||
33 | |||
34 | 2 | $user_pam = new ElggPAM('user'); |
|
35 | 2 | $user_auth_result = $user_pam->authenticate([]); |
|
36 | |||
37 | // check if user authentication is required |
||
38 | 2 | if ($API_METHODS[$method]["require_user_auth"] == true) { |
|
39 | 1 | if (!$user_auth_result) { |
|
40 | 1 | throw new APIException($user_pam->getFailureMessage(), ErrorResult::$RESULT_FAIL_AUTHTOKEN); |
|
41 | } |
||
42 | } |
||
43 | |||
44 | 1 | return true; |
|
45 | } |
||
46 | |||
47 | /** |
||
48 | * Executes a method. |
||
49 | * A method is a function which you have previously exposed using expose_function. |
||
50 | * |
||
51 | * @param string $method Method, e.g. "foo.bar" |
||
52 | * |
||
53 | * @return GenericResult The result of the execution. |
||
54 | * @throws APIException|CallException |
||
55 | * @access private |
||
56 | */ |
||
57 | function execute_method($method) { |
||
58 | 5 | global $API_METHODS; |
|
59 | |||
60 | // method must be exposed |
||
61 | 5 | if (!isset($API_METHODS[$method])) { |
|
62 | 1 | $msg = elgg_echo('APIException:MethodCallNotImplemented', [$method]); |
|
63 | 1 | throw new APIException($msg); |
|
64 | } |
||
65 | |||
66 | // function must be callable |
||
67 | 4 | $function = elgg_extract('function', $API_METHODS[$method]); |
|
68 | 4 | if (!$function || !is_callable($function)) { |
|
69 | 1 | $msg = elgg_echo('APIException:FunctionDoesNotExist', [$method]); |
|
70 | 1 | throw new APIException($msg); |
|
71 | } |
||
72 | |||
73 | // check http call method |
||
74 | 3 | if (strcmp(get_call_method(), $API_METHODS[$method]["call_method"]) != 0) { |
|
75 | 1 | $msg = elgg_echo('CallException:InvalidCallMethod', [$method, |
|
76 | 1 | $API_METHODS[$method]["call_method"]]); |
|
77 | 1 | throw new CallException($msg); |
|
78 | } |
||
79 | |||
80 | 2 | $parameters = get_parameters_for_method($method); |
|
81 | |||
82 | // may throw exception, which is not caught here |
||
83 | 2 | verify_parameters($method, $parameters); |
|
84 | |||
85 | 2 | $serialised_parameters = serialise_parameters($method, $parameters); |
|
86 | |||
87 | // Execute function: Construct function and calling parameters |
||
88 | 2 | $serialised_parameters = trim($serialised_parameters, ", "); |
|
89 | |||
90 | // Sadly we probably can't get rid of this eval() in 2.x. Doing so would involve |
||
91 | // replacing serialise_parameters(), which does a bunch of weird stuff we need to |
||
92 | // stay BC with 2.x. There are tests for a lot of these quirks in ElggCoreWebServicesApiTest |
||
93 | // particularly in testSerialiseParametersCasting(). |
||
94 | 2 | $arguments = eval("return [$serialised_parameters];"); |
|
95 | |||
96 | 2 | if ($API_METHODS[$method]['assoc']) { |
|
97 | 1 | $argument = array_combine(_elgg_ws_get_parameter_names($method), $arguments); |
|
98 | 1 | $result = call_user_func($function, $argument); |
|
99 | } else { |
||
100 | 1 | $result = call_user_func_array($function, $arguments); |
|
101 | } |
||
102 | |||
103 | 2 | $result = elgg_trigger_plugin_hook('rest:output', $method, $parameters, $result); |
|
104 | |||
105 | // Sanity check result |
||
106 | // If this function returns an api result itself, just return it |
||
107 | 2 | if ($result instanceof GenericResult) { |
|
108 | return $result; |
||
109 | } |
||
110 | |||
111 | 2 | if ($result === false) { |
|
112 | $msg = elgg_echo('APIException:FunctionParseError', [$function, $serialised_parameters]); |
||
113 | throw new APIException($msg); |
||
114 | } |
||
115 | |||
116 | 2 | if ($result === null) { |
|
117 | // If no value |
||
118 | $msg = elgg_echo('APIException:FunctionNoReturn', [$function, $serialised_parameters]); |
||
119 | throw new APIException($msg); |
||
120 | } |
||
121 | |||
122 | // Otherwise assume that the call was successful and return it as a success object. |
||
123 | 2 | return SuccessResult::getInstance($result); |
|
124 | } |
||
125 | |||
126 | /** |
||
127 | * Get the request method. |
||
128 | * |
||
129 | * @return string HTTP request method |
||
130 | * @access private |
||
131 | */ |
||
132 | function get_call_method() { |
||
133 | 23 | return _elgg_services()->request->server->get('REQUEST_METHOD'); |
|
134 | } |
||
135 | |||
136 | /** |
||
137 | * This function analyses all expected parameters for a given method |
||
138 | * |
||
139 | * This function sanitizes the input parameters and returns them in |
||
140 | * an associated array. |
||
141 | * |
||
142 | * @param string $method The method |
||
143 | * |
||
144 | * @return array containing parameters as key => value |
||
145 | * @access private |
||
146 | */ |
||
147 | function get_parameters_for_method($method) { |
||
148 | 2 | global $API_METHODS; |
|
149 | |||
150 | 2 | $sanitised = []; |
|
151 | |||
152 | // if there are parameters, sanitize them |
||
153 | 2 | if (isset($API_METHODS[$method]['parameters'])) { |
|
154 | 2 | foreach ($API_METHODS[$method]['parameters'] as $k => $v) { |
|
155 | 2 | $param = get_input($k); // Make things go through the sanitiser |
|
156 | 2 | if ($param !== '' && $param !== null) { |
|
157 | 2 | $sanitised[$k] = $param; |
|
158 | } else { |
||
159 | // parameter wasn't passed so check for default |
||
160 | 2 | if (isset($v['default'])) { |
|
161 | 2 | $sanitised[$k] = $v['default']; |
|
162 | } |
||
163 | } |
||
164 | } |
||
165 | } |
||
166 | |||
167 | 2 | return $sanitised; |
|
168 | } |
||
169 | |||
170 | /** |
||
171 | * Get POST data |
||
172 | * Since this is called through a handler, we need to manually get the post data |
||
173 | * |
||
174 | * @return POST data as string encoded as multipart/form-data |
||
175 | * @access private |
||
176 | */ |
||
177 | function get_post_data() { |
||
178 | |||
179 | $postdata = file_get_contents('php://input'); |
||
180 | |||
181 | return $postdata; |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Verify that the required parameters are present |
||
186 | * |
||
187 | * @param string $method Method name |
||
188 | * @param array $parameters List of expected parameters |
||
189 | * |
||
190 | * @return true on success or exception |
||
191 | * @throws APIException |
||
192 | * @since 1.7.0 |
||
193 | * @access private |
||
194 | */ |
||
195 | function verify_parameters($method, $parameters) { |
||
196 | 5 | global $API_METHODS; |
|
197 | |||
198 | // are there any parameters for this method |
||
199 | 5 | if (!(isset($API_METHODS[$method]["parameters"]))) { |
|
200 | return true; // no so return |
||
201 | } |
||
202 | |||
203 | // check that the parameters were registered correctly and all required ones are there |
||
204 | 5 | foreach ($API_METHODS[$method]['parameters'] as $key => $value) { |
|
205 | // this tests the expose structure: must be array to describe parameter and type must be defined |
||
206 | 5 | if (!is_array($value) || !isset($value['type'])) { |
|
207 | 1 | $msg = elgg_echo('APIException:InvalidParameter', [$key, $method]); |
|
208 | 1 | throw new APIException($msg); |
|
209 | } |
||
210 | |||
211 | // Check that the variable is present in the request if required |
||
212 | 4 | if ($value['required'] && !array_key_exists($key, $parameters)) { |
|
213 | 1 | $msg = elgg_echo('APIException:MissingParameterInMethod', [$key, $method]); |
|
214 | 4 | throw new APIException($msg); |
|
215 | } |
||
216 | } |
||
217 | |||
218 | 3 | return true; |
|
219 | } |
||
220 | |||
221 | /** |
||
222 | * Get the names of a method's parameters |
||
223 | * |
||
224 | * @param string $method the api method to get the params for |
||
225 | * @return string[] |
||
226 | * @access private |
||
227 | */ |
||
228 | function _elgg_ws_get_parameter_names($method) { |
||
229 | 1 | global $API_METHODS; |
|
230 | |||
231 | 1 | if (!isset($API_METHODS[$method]["parameters"])) { |
|
232 | return []; |
||
233 | } |
||
234 | |||
235 | 1 | return array_keys($API_METHODS[$method]["parameters"]); |
|
236 | } |
||
237 | |||
238 | /** |
||
239 | * Serialize an array of parameters for an API method call, applying transformations |
||
240 | * to values depending on the declared parameter type, and returning a string of PHP |
||
241 | * code representing the contents of a PHP array literal. |
||
242 | * |
||
243 | * A leading comma needs to be removed from the output. |
||
244 | * |
||
245 | * @see \ElggCoreWebServicesApiTest::testSerialiseParametersCasting |
||
246 | * |
||
247 | * @param string $method API method name |
||
248 | * @param array $parameters Array of parameters |
||
249 | * |
||
250 | * @return string or exception E.g. ',"foo",2.1' |
||
251 | * @throws APIException |
||
252 | * @since 1.7.0 |
||
253 | * @access private |
||
254 | * |
||
255 | * @todo in 3.0 this should return an array of parameter values instead of a string of code. |
||
256 | */ |
||
257 | function serialise_parameters($method, $parameters) { |
||
258 | 4 | global $API_METHODS; |
|
259 | |||
260 | // are there any parameters for this method |
||
261 | 4 | if (!(isset($API_METHODS[$method]["parameters"]))) { |
|
262 | return ''; // if not, return |
||
263 | } |
||
264 | |||
265 | 4 | $serialised_parameters = ""; |
|
266 | 4 | foreach ($API_METHODS[$method]['parameters'] as $key => $value) { |
|
267 | // avoid warning on parameters that are not required and not present |
||
268 | 4 | if (!isset($parameters[$key])) { |
|
269 | 3 | $serialised_parameters .= ',null'; |
|
270 | 3 | continue; |
|
271 | } |
||
272 | |||
273 | // Set variables casting to type. |
||
274 | 4 | switch (strtolower($value['type'])) { |
|
275 | 4 | case 'int': |
|
276 | 4 | case 'integer' : |
|
277 | 4 | $serialised_parameters .= "," . (int) trim($parameters[$key]); |
|
278 | 4 | break; |
|
279 | 4 | case 'bool': |
|
280 | 2 | case 'boolean': |
|
281 | // change word false to boolean false |
||
282 | 4 | if (strcasecmp(trim($parameters[$key]), "false") == 0) { |
|
283 | 1 | $serialised_parameters .= ',false'; |
|
284 | 4 | } else if ($parameters[$key] == 0) { |
|
285 | 2 | $serialised_parameters .= ',false'; |
|
286 | } else { |
||
287 | 3 | $serialised_parameters .= ',true'; |
|
288 | } |
||
289 | |||
290 | 4 | break; |
|
291 | 2 | case 'string': |
|
292 | 2 | $serialised_parameters .= ',' . var_export(trim($parameters[$key]), true); |
|
293 | 2 | break; |
|
294 | 2 | case 'float': |
|
295 | 2 | $serialised_parameters .= "," . (float) trim($parameters[$key]); |
|
296 | 2 | break; |
|
297 | 2 | case 'array': |
|
298 | // we can handle an array of strings, maybe ints, definitely not booleans or other arrays |
||
299 | 2 | if (!is_array($parameters[$key])) { |
|
300 | $msg = elgg_echo('APIException:ParameterNotArray', [$key]); |
||
301 | throw new APIException($msg); |
||
302 | } |
||
303 | |||
304 | 2 | $array = "array("; |
|
305 | |||
306 | 2 | foreach ($parameters[$key] as $k => $v) { |
|
307 | // This is using sanitise_string() to escape characters to be inside a |
||
308 | // single-quoted string literal in PHP code. Not sure what we have to do |
||
309 | // to keep this safe in 3.0... |
||
310 | 2 | $k = sanitise_string($k); |
|
311 | 2 | $v = sanitise_string($v); |
|
312 | |||
313 | 2 | $array .= "'$k'=>'$v',"; |
|
314 | } |
||
315 | |||
316 | 2 | $array = trim($array, ","); |
|
317 | |||
318 | 2 | $array .= ")"; |
|
319 | 2 | $array = ",$array"; |
|
320 | |||
321 | 2 | $serialised_parameters .= $array; |
|
322 | 2 | break; |
|
323 | default: |
||
324 | 1 | $msg = elgg_echo('APIException:UnrecognisedTypeCast', [$value['type'], $key, $method]); |
|
325 | 4 | throw new APIException($msg); |
|
326 | } |
||
327 | } |
||
328 | |||
329 | 4 | return $serialised_parameters; |
|
330 | } |
||
331 | |||
332 | // API authorization handlers ///////////////////////////////////////////////////////////////////// |
||
333 | |||
334 | /** |
||
335 | * PAM: Confirm that the call includes a valid API key |
||
336 | * |
||
337 | * @return bool true if good API key - otherwise throws exception |
||
338 | * @throws APIException |
||
339 | * @since 1.7.0 |
||
340 | * @access private |
||
341 | */ |
||
342 | function api_auth_key() { |
||
343 | // check that an API key is present |
||
344 | 2 | $api_key = get_input('api_key'); |
|
345 | 2 | if ($api_key == "") { |
|
346 | 1 | throw new APIException(elgg_echo('APIException:MissingAPIKey')); |
|
347 | } |
||
348 | |||
349 | // check that it is active |
||
350 | 1 | $api_user = get_api_user($api_key); |
|
351 | 1 | if (!$api_user) { |
|
352 | // key is not active or does not exist |
||
353 | 1 | throw new APIException(elgg_echo('APIException:BadAPIKey')); |
|
354 | } |
||
355 | |||
356 | // can be used for keeping stats |
||
357 | // plugin can also return false to fail this authentication method |
||
358 | return elgg_trigger_plugin_hook('api_key', 'use', $api_key, true); |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * PAM: Confirm the HMAC signature |
||
363 | * |
||
364 | * @return true if success - otherwise throws exception |
||
365 | * |
||
366 | * @throws SecurityException |
||
367 | * @since 1.7.0 |
||
368 | * @access private |
||
369 | */ |
||
370 | function api_auth_hmac() { |
||
371 | // Get api header |
||
372 | $api_header = get_and_validate_api_headers(); |
||
373 | |||
374 | // Pull API user details |
||
375 | $api_user = get_api_user($api_header->api_key); |
||
376 | |||
377 | if (!$api_user) { |
||
378 | throw new SecurityException(elgg_echo('SecurityException:InvalidAPIKey'), |
||
379 | ErrorResult::$RESULT_FAIL_APIKEY_INVALID); |
||
380 | } |
||
381 | |||
382 | // Get the secret key |
||
383 | $secret_key = $api_user->secret; |
||
384 | |||
385 | // get the query string |
||
386 | $query = _elgg_services()->request->server->get('REQUEST_URI'); |
||
387 | $query = substr($query, strpos($query, '?') + 1); |
||
388 | |||
389 | // calculate expected HMAC |
||
390 | $hmac = calculate_hmac( $api_header->hmac_algo, |
||
391 | $api_header->time, |
||
392 | $api_header->nonce, |
||
393 | $api_header->api_key, |
||
394 | $secret_key, |
||
395 | $query, |
||
396 | $api_header->method == 'POST' ? $api_header->posthash : ""); |
||
397 | |||
398 | |||
399 | if ($api_header->hmac !== $hmac) { |
||
400 | throw new SecurityException("HMAC is invalid. {$api_header->hmac} != [calc]$hmac"); |
||
401 | } |
||
402 | |||
403 | // Now make sure this is not a replay |
||
404 | if (cache_hmac_check_replay($hmac)) { |
||
405 | throw new SecurityException(elgg_echo('SecurityException:DupePacket')); |
||
406 | } |
||
407 | |||
408 | // Validate post data |
||
409 | if ($api_header->method == "POST") { |
||
410 | $postdata = get_post_data(); |
||
411 | $calculated_posthash = calculate_posthash($postdata, $api_header->posthash_algo); |
||
412 | |||
413 | if (strcmp($api_header->posthash, $calculated_posthash) != 0) { |
||
414 | $msg = elgg_echo('SecurityException:InvalidPostHash', |
||
415 | [$calculated_posthash, $api_header->posthash]); |
||
416 | |||
417 | throw new SecurityException($msg); |
||
418 | } |
||
419 | } |
||
420 | |||
421 | return true; |
||
422 | } |
||
423 | |||
424 | // HMAC ///////////////////////////////////////////////////////////////////// |
||
425 | |||
426 | /** |
||
427 | * This function extracts the various header variables needed for the HMAC PAM |
||
428 | * |
||
429 | * @return stdClass Containing all the values. |
||
430 | * @throws APIException Detailing any error. |
||
431 | * @access private |
||
432 | */ |
||
433 | function get_and_validate_api_headers() { |
||
434 | $result = new stdClass; |
||
435 | |||
436 | $result->method = get_call_method(); |
||
437 | // Only allow these methods |
||
438 | if (($result->method != "GET") && ($result->method != "POST")) { |
||
439 | throw new APIException(elgg_echo('APIException:NotGetOrPost')); |
||
440 | } |
||
441 | |||
442 | $server = _elgg_services()->request->server; |
||
443 | |||
444 | $result->api_key = $server->get('HTTP_X_ELGG_APIKEY'); |
||
445 | if ($result->api_key == "") { |
||
446 | throw new APIException(elgg_echo('APIException:MissingAPIKey')); |
||
447 | } |
||
448 | |||
449 | $result->hmac = $server->get('HTTP_X_ELGG_HMAC'); |
||
450 | if ($result->hmac == "") { |
||
451 | throw new APIException(elgg_echo('APIException:MissingHmac')); |
||
452 | } |
||
453 | |||
454 | $result->hmac_algo = $server->get('HTTP_X_ELGG_HMAC_ALGO'); |
||
455 | if ($result->hmac_algo == "") { |
||
456 | throw new APIException(elgg_echo('APIException:MissingHmacAlgo')); |
||
457 | } |
||
458 | |||
459 | $result->time = $server->get('HTTP_X_ELGG_TIME'); |
||
460 | if ($result->time == "") { |
||
461 | throw new APIException(elgg_echo('APIException:MissingTime')); |
||
462 | } |
||
463 | |||
464 | // Must have been sent within 25 hour period. |
||
465 | // 25 hours is more than enough to handle server clock drift. |
||
466 | // This values determines how long the HMAC cache needs to store previous |
||
467 | // signatures. Heavy use of HMAC is better handled with a shorter sig lifetime. |
||
468 | // See cache_hmac_check_replay() |
||
469 | if (($result->time < (time() - 90000)) || ($result->time > (time() + 90000))) { |
||
470 | throw new APIException(elgg_echo('APIException:TemporalDrift')); |
||
471 | } |
||
472 | |||
473 | $result->nonce = $server->get('HTTP_X_ELGG_NONCE'); |
||
474 | if ($result->nonce == "") { |
||
475 | throw new APIException(elgg_echo('APIException:MissingNonce')); |
||
476 | } |
||
477 | |||
478 | if ($result->method == "POST") { |
||
479 | $result->posthash = $server->get('HTTP_X_ELGG_POSTHASH'); |
||
480 | if ($result->posthash == "") { |
||
481 | throw new APIException(elgg_echo('APIException:MissingPOSTHash')); |
||
482 | } |
||
483 | |||
484 | $result->posthash_algo = $server->get('HTTP_X_ELGG_POSTHASH_ALGO'); |
||
485 | if ($result->posthash_algo == "") { |
||
486 | throw new APIException(elgg_echo('APIException:MissingPOSTAlgo')); |
||
487 | } |
||
488 | |||
489 | $result->content_type = $server->get('CONTENT_TYPE'); |
||
490 | if ($result->content_type == "") { |
||
491 | throw new APIException(elgg_echo('APIException:MissingContentType')); |
||
492 | } |
||
493 | } |
||
494 | |||
495 | return $result; |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * Map various algorithms to their PHP equivs. |
||
500 | * This also gives us an easy way to disable algorithms. |
||
501 | * |
||
502 | * @param string $algo The algorithm |
||
503 | * |
||
504 | * @return string The php algorithm |
||
505 | * @throws APIException if an algorithm is not supported. |
||
506 | * @access private |
||
507 | */ |
||
508 | function map_api_hash($algo) { |
||
509 | $algo = strtolower(sanitise_string($algo)); |
||
510 | $supported_algos = [ |
||
511 | "md5" => "md5", // @todo Consider phasing this out |
||
512 | "sha" => "sha1", // alias for sha1 |
||
513 | "sha1" => "sha1", |
||
514 | "sha256" => "sha256" |
||
515 | ]; |
||
516 | |||
517 | if (array_key_exists($algo, $supported_algos)) { |
||
518 | return $supported_algos[$algo]; |
||
519 | } |
||
520 | |||
521 | throw new APIException(elgg_echo('APIException:AlgorithmNotSupported', [$algo])); |
||
522 | } |
||
523 | |||
524 | /** |
||
525 | * Calculate the HMAC for the http request. |
||
526 | * This function signs an api request using the information provided. The signature returned |
||
527 | * has been base64 encoded and then url encoded. |
||
528 | * |
||
529 | * @param string $algo The HMAC algorithm used |
||
530 | * @param string $time String representation of unix time |
||
531 | * @param string $nonce Nonce |
||
532 | * @param string $api_key Your api key |
||
533 | * @param string $secret_key Your private key |
||
534 | * @param string $get_variables URLEncoded string representation of the get variable parameters, |
||
535 | * eg "method=user&guid=2" |
||
536 | * @param string $post_hash Optional sha1 hash of the post data. |
||
537 | * |
||
538 | * @return string The HMAC signature |
||
539 | * @access private |
||
540 | */ |
||
541 | function calculate_hmac($algo, $time, $nonce, $api_key, $secret_key, |
||
542 | $get_variables, $post_hash = "") { |
||
543 | |||
544 | elgg_log("HMAC Parts: $algo, $time, $api_key, $secret_key, $get_variables, $post_hash"); |
||
545 | |||
546 | $ctx = hash_init(map_api_hash($algo), HASH_HMAC, $secret_key); |
||
547 | |||
548 | hash_update($ctx, trim($time)); |
||
549 | hash_update($ctx, trim($nonce)); |
||
550 | hash_update($ctx, trim($api_key)); |
||
551 | hash_update($ctx, trim($get_variables)); |
||
552 | if (trim($post_hash) != "") { |
||
553 | hash_update($ctx, trim($post_hash)); |
||
554 | } |
||
555 | |||
556 | return urlencode(base64_encode(hash_final($ctx, true))); |
||
557 | } |
||
558 | |||
559 | /** |
||
560 | * Calculate a hash for some post data. |
||
561 | * |
||
562 | * @todo Work out how to handle really large bits of data. |
||
563 | * |
||
564 | * @param string $postdata The post data. |
||
565 | * @param string $algo The algorithm used. |
||
566 | * |
||
567 | * @return string The hash. |
||
568 | * @access private |
||
569 | */ |
||
570 | function calculate_posthash($postdata, $algo) { |
||
571 | $ctx = hash_init(map_api_hash($algo)); |
||
572 | |||
573 | hash_update($ctx, $postdata); |
||
574 | |||
575 | return hash_final($ctx); |
||
576 | } |
||
577 | |||
578 | /** |
||
579 | * This function will do two things. Firstly it verifies that a HMAC signature |
||
580 | * hasn't been seen before, and secondly it will add the given hmac to the cache. |
||
581 | * |
||
582 | * @param string $hmac The hmac string. |
||
583 | * |
||
584 | * @return bool True if replay detected, false if not. |
||
585 | * @access private |
||
586 | */ |
||
587 | function cache_hmac_check_replay($hmac) { |
||
588 | // cache lifetime is 25 hours (this should be related to the time drift |
||
589 | // allowed in get_and_validate_headers |
||
590 | $cache = new ElggHMACCache(90000); |
||
591 | |||
592 | if (!$cache->load($hmac)) { |
||
593 | $cache->save($hmac, $hmac); |
||
594 | |||
595 | return false; |
||
596 | } |
||
597 | |||
598 | return true; |
||
599 | } |
||
600 | |||
601 | /** |
||
602 | * Check the user token |
||
603 | * This examines whether an authentication token is present and returns true if |
||
604 | * it is present and is valid. The user gets logged in so with the current |
||
605 | * session code of Elgg, that user will be logged out of all other sessions. |
||
606 | * |
||
607 | * @return bool |
||
608 | * @access private |
||
609 | */ |
||
610 | function pam_auth_usertoken() { |
||
611 | $token = get_input('auth_token'); |
||
612 | if (!$token) { |
||
613 | return false; |
||
614 | } |
||
615 | |||
616 | $validated_userid = validate_user_token($token); |
||
617 | |||
618 | if ($validated_userid) { |
||
619 | $u = get_entity($validated_userid); |
||
620 | |||
621 | // Could we get the user? |
||
622 | if (!$u) { |
||
623 | return false; |
||
624 | } |
||
625 | |||
626 | // Not an elgg user |
||
627 | if ((!$u instanceof ElggUser)) { |
||
628 | return false; |
||
629 | } |
||
630 | |||
631 | // User is banned |
||
632 | if ($u->isBanned()) { |
||
633 | return false; |
||
634 | } |
||
635 | |||
636 | // Fail if we couldn't log the user in |
||
637 | if (!login($u)) { |
||
638 | return false; |
||
639 | } |
||
640 | |||
641 | return true; |
||
642 | } |
||
643 | |||
644 | return false; |
||
645 | } |
||
646 | |||
647 | /** |
||
648 | * See if the user has a valid login sesson |
||
649 | * |
||
650 | * @return bool |
||
651 | * @access private |
||
652 | */ |
||
653 | function pam_auth_session() { |
||
654 | return elgg_is_logged_in(); |
||
655 | } |
||
656 | |||
657 | /** |
||
658 | * API PHP Error handler function. |
||
659 | * This function acts as a wrapper to catch and report PHP error messages. |
||
660 | * |
||
661 | * @see http://uk3.php.net/set-error-handler |
||
662 | * |
||
663 | * @param int $errno Error number |
||
664 | * @param string $errmsg Human readable message |
||
665 | * @param string $filename Filename |
||
666 | * @param int $linenum Line number |
||
667 | * @param array $vars Vars |
||
668 | * |
||
669 | * @return void |
||
670 | * @access private |
||
671 | * |
||
672 | * @throws Exception |
||
673 | */ |
||
674 | function _php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) { |
||
675 | global $ERRORS; |
||
676 | |||
677 | $error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file " |
||
678 | . $filename . " (line " . $linenum . ")"; |
||
679 | |||
680 | switch ($errno) { |
||
681 | case E_USER_ERROR: |
||
682 | error_log("ERROR: " . $error); |
||
683 | $ERRORS[] = "ERROR: " . $error; |
||
684 | |||
685 | // Since this is a fatal error, we want to stop any further execution but do so gracefully. |
||
686 | throw new Exception("ERROR: " . $error); |
||
687 | break; |
||
688 | |||
689 | case E_WARNING : |
||
690 | case E_USER_WARNING : |
||
691 | error_log("WARNING: " . $error); |
||
692 | $ERRORS[] = "WARNING: " . $error; |
||
693 | break; |
||
694 | |||
695 | default: |
||
696 | error_log("DEBUG: " . $error); |
||
697 | $ERRORS[] = "DEBUG: " . $error; |
||
698 | } |
||
699 | } |
||
700 | |||
701 | /** |
||
702 | * API PHP Exception handler. |
||
703 | * This is a generic exception handler for PHP exceptions. This will catch any |
||
704 | * uncaught exception, end API execution and return the result to the requestor |
||
705 | * as an ErrorResult in the requested format. |
||
706 | * |
||
707 | * @param Exception $exception Exception |
||
708 | * |
||
709 | * @return void |
||
710 | * @access private |
||
711 | */ |
||
712 | function _php_api_exception_handler($exception) { |
||
713 | |||
714 | error_log("*** FATAL EXCEPTION (API) *** : " . $exception); |
||
715 | |||
716 | $code = $exception->getCode() == 0 ? ErrorResult::$RESULT_FAIL : $exception->getCode(); |
||
717 | $result = new ErrorResult($exception->getMessage(), $code, null); |
||
718 | |||
719 | echo elgg_view_page($exception->getMessage(), elgg_view("api/output", ["result" => $result])); |
||
720 | } |
||
721 | |||
722 | |||
723 | /** |
||
724 | * Services handler - turns request over to the registered handler |
||
725 | * If no handler is found, this returns a 404 error |
||
726 | * |
||
727 | * @param string $handler Handler name |
||
728 | * @param array $request Request string |
||
729 | * |
||
730 | * @return void |
||
731 | * @access private |
||
732 | */ |
||
733 | function service_handler($handler, $request) { |
||
734 | elgg_set_context('api'); |
||
735 | |||
736 | $request = explode('/', $request); |
||
737 | |||
738 | // after the handler, the first identifier is response format |
||
739 | // ex) http://example.org/services/api/rest/json/?method=test |
||
740 | $response_format = array_shift($request); |
||
741 | if (!$response_format) { |
||
742 | $response_format = 'json'; |
||
743 | } |
||
744 | |||
745 | if (!ctype_alpha($response_format)) { |
||
746 | header("HTTP/1.0 400 Bad Request"); |
||
747 | header("Content-type: text/plain"); |
||
748 | echo "Invalid format."; |
||
749 | exit; |
||
750 | } |
||
751 | |||
752 | elgg_set_viewtype($response_format); |
||
753 | |||
754 | $servicehandler = _elgg_config()->servicehandler; |
||
755 | |||
756 | if (!isset($servicehandler) || empty($handler)) { |
||
757 | // no handlers set or bad url |
||
758 | header("HTTP/1.0 404 Not Found"); |
||
759 | exit; |
||
760 | } else if (isset($servicehandler[$handler]) && is_callable($servicehandler[$handler])) { |
||
761 | $function = $servicehandler[$handler]; |
||
762 | call_user_func($function, $request, $handler); |
||
763 | } else { |
||
764 | // no handler for this web service |
||
765 | header("HTTP/1.0 404 Not Found"); |
||
766 | exit; |
||
767 | } |
||
768 | } |
||
769 |