start.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. <?php
  2. /**
  3. * Elgg web services API plugin
  4. */
  5. elgg_register_event_handler('init', 'system', 'ws_init');
  6. function ws_init() {
  7. $lib_dir = elgg_get_plugins_path() . "web_services/lib";
  8. elgg_register_library('elgg:ws', "$lib_dir/web_services.php");
  9. elgg_register_library('elgg:ws:api_user', "$lib_dir/api_user.php");
  10. elgg_register_library('elgg:ws:client', "$lib_dir/client.php");
  11. elgg_register_library('elgg:ws:tokens', "$lib_dir/tokens.php");
  12. elgg_load_library('elgg:ws:api_user');
  13. elgg_load_library('elgg:ws:tokens');
  14. elgg_register_page_handler('services', 'ws_page_handler');
  15. // Register a service handler for the default web services
  16. // The name rest is a misnomer as they are not RESTful
  17. elgg_ws_register_service_handler('rest', 'ws_rest_handler');
  18. // expose the list of api methods
  19. elgg_ws_expose_function("system.api.list", "list_all_apis", null,
  20. elgg_echo("system.api.list"), "GET", false, false);
  21. // The authentication token api
  22. elgg_ws_expose_function(
  23. "auth.gettoken",
  24. "auth_gettoken",
  25. array(
  26. 'username' => array ('type' => 'string'),
  27. 'password' => array ('type' => 'string'),
  28. ),
  29. elgg_echo('auth.gettoken'),
  30. 'POST',
  31. false,
  32. false
  33. );
  34. elgg_register_plugin_hook_handler('unit_test', 'system', 'ws_unit_test');
  35. }
  36. /**
  37. * Handle a web service request
  38. *
  39. * Handles requests of format: http://site/services/api/handler/response_format/request
  40. * The first element after 'services/api/' is the service handler name as
  41. * registered by {@link register_service_handler()}.
  42. *
  43. * The remaining string is then passed to the {@link service_handler()}
  44. * which explodes by /, extracts the first element as the response format
  45. * (viewtype), and then passes the remaining array to the service handler
  46. * function registered by {@link register_service_handler()}.
  47. *
  48. * If a service handler isn't found, a 404 header is sent.
  49. *
  50. * @param array $segments URL segments
  51. * @return bool
  52. */
  53. function ws_page_handler($segments) {
  54. elgg_load_library('elgg:ws');
  55. if (!isset($segments[0]) || $segments[0] != 'api') {
  56. return false;
  57. }
  58. array_shift($segments);
  59. $handler = array_shift($segments);
  60. $request = implode('/', $segments);
  61. service_handler($handler, $request);
  62. return true;
  63. }
  64. /**
  65. * A global array holding API methods.
  66. * The structure of this is
  67. * $API_METHODS = array (
  68. * $method => array (
  69. * "description" => "Some human readable description"
  70. * "function" = 'my_function_callback'
  71. * "parameters" = array (
  72. * "variable" = array ( // the order should be the same as the function callback
  73. * type => 'int' | 'bool' | 'float' | 'string'
  74. * required => true (default) | false
  75. * default => value // optional
  76. * )
  77. * )
  78. * "call_method" = 'GET' | 'POST'
  79. * "require_api_auth" => true | false (default)
  80. * "require_user_auth" => true | false (default)
  81. * )
  82. * )
  83. */
  84. global $API_METHODS;
  85. $API_METHODS = array();
  86. /** Define a global array of errors */
  87. global $ERRORS;
  88. $ERRORS = array();
  89. /**
  90. * Expose a function as a web service.
  91. *
  92. * Limitations: Currently cannot expose functions which expect objects.
  93. * It also cannot handle arrays of bools or arrays of arrays.
  94. * Also, input will be filtered to protect against XSS attacks through the web services.
  95. *
  96. * @param string $method The api name to expose - for example "myapi.dosomething"
  97. * @param string $function Your function callback.
  98. * @param array $parameters (optional) List of parameters in the same order as in
  99. * your function. Default values may be set for parameters which
  100. * allow REST api users flexibility in what parameters are passed.
  101. * Generally, optional parameters should be after required
  102. * parameters.
  103. *
  104. * This array should be in the format
  105. * "variable" = array (
  106. * type => 'int' | 'bool' | 'float' | 'string' | 'array'
  107. * required => true (default) | false
  108. * default => value (optional)
  109. * )
  110. * @param string $description (optional) human readable description of the function.
  111. * @param string $call_method (optional) Define what http method must be used for
  112. * this function. Default: GET
  113. * @param bool $require_api_auth (optional) (default is false) Does this method
  114. * require API authorization? (example: API key)
  115. * @param bool $require_user_auth (optional) (default is false) Does this method
  116. * require user authorization?
  117. *
  118. * @return bool
  119. * @throws InvalidParameterException
  120. */
  121. function elgg_ws_expose_function($method, $function, array $parameters = NULL, $description = "",
  122. $call_method = "GET", $require_api_auth = false, $require_user_auth = false) {
  123. global $API_METHODS;
  124. if (($method == "") || ($function == "")) {
  125. $msg = elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet');
  126. throw new InvalidParameterException($msg);
  127. }
  128. // does not check whether this method has already been exposed - good idea?
  129. $API_METHODS[$method] = array();
  130. $API_METHODS[$method]["description"] = $description;
  131. // does not check whether callable - done in execute_method()
  132. $API_METHODS[$method]["function"] = $function;
  133. if ($parameters != NULL) {
  134. if (!is_array($parameters)) {
  135. $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method));
  136. throw new InvalidParameterException($msg);
  137. }
  138. // catch common mistake of not setting up param array correctly
  139. $first = current($parameters);
  140. if (!is_array($first)) {
  141. $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method));
  142. throw new InvalidParameterException($msg);
  143. }
  144. }
  145. if ($parameters != NULL) {
  146. // ensure the required flag is set correctly in default case for each parameter
  147. foreach ($parameters as $key => $value) {
  148. // check if 'required' was specified - if not, make it true
  149. if (!array_key_exists('required', $value)) {
  150. $parameters[$key]['required'] = true;
  151. }
  152. }
  153. $API_METHODS[$method]["parameters"] = $parameters;
  154. }
  155. $call_method = strtoupper($call_method);
  156. switch ($call_method) {
  157. case 'POST' :
  158. $API_METHODS[$method]["call_method"] = 'POST';
  159. break;
  160. case 'GET' :
  161. $API_METHODS[$method]["call_method"] = 'GET';
  162. break;
  163. default :
  164. $msg = elgg_echo('InvalidParameterException:UnrecognisedHttpMethod',
  165. array($call_method, $method));
  166. throw new InvalidParameterException($msg);
  167. }
  168. $API_METHODS[$method]["require_api_auth"] = $require_api_auth;
  169. $API_METHODS[$method]["require_user_auth"] = $require_user_auth;
  170. return true;
  171. }
  172. /**
  173. * Unregister a web services method
  174. *
  175. * @param string $method The api name that was exposed
  176. * @return void
  177. */
  178. function elgg_ws_unexpose_function($method) {
  179. global $API_METHODS;
  180. if (isset($API_METHODS[$method])) {
  181. unset($API_METHODS[$method]);
  182. }
  183. }
  184. /**
  185. * Simple api to return a list of all api's installed on the system.
  186. *
  187. * @return array
  188. * @access private
  189. */
  190. function list_all_apis() {
  191. global $API_METHODS;
  192. // sort first
  193. ksort($API_METHODS);
  194. return $API_METHODS;
  195. }
  196. /**
  197. * Registers a web services handler
  198. *
  199. * @param string $handler Web services type
  200. * @param string $function Your function name
  201. *
  202. * @return bool Depending on success
  203. */
  204. function elgg_ws_register_service_handler($handler, $function) {
  205. global $CONFIG;
  206. if (!isset($CONFIG->servicehandler)) {
  207. $CONFIG->servicehandler = array();
  208. }
  209. if (is_callable($function, true)) {
  210. $CONFIG->servicehandler[$handler] = $function;
  211. return true;
  212. }
  213. return false;
  214. }
  215. /**
  216. * Remove a web service
  217. * To replace a web service handler, register the desired handler over the old on
  218. * with register_service_handler().
  219. *
  220. * @param string $handler web services type
  221. * @return void
  222. */
  223. function elgg_ws_unregister_service_handler($handler) {
  224. global $CONFIG;
  225. if (isset($CONFIG->servicehandler, $CONFIG->servicehandler[$handler])) {
  226. unset($CONFIG->servicehandler[$handler]);
  227. }
  228. }
  229. /**
  230. * REST API handler
  231. *
  232. * @return void
  233. * @access private
  234. *
  235. * @throws SecurityException|APIException
  236. */
  237. function ws_rest_handler() {
  238. $viewtype = elgg_get_viewtype();
  239. if (!elgg_view_exists('api/output', $viewtype)) {
  240. header("HTTP/1.0 400 Bad Request");
  241. header("Content-type: text/plain");
  242. echo "Missing view 'api/output' in viewtype '$viewtype'.";
  243. if (in_array($viewtype, ['xml', 'php'])) {
  244. echo "\nEnable the 'data_views' plugin to add this view.";
  245. }
  246. exit;
  247. }
  248. elgg_load_library('elgg:ws');
  249. // Register the error handler
  250. error_reporting(E_ALL);
  251. set_error_handler('_php_api_error_handler');
  252. // Register a default exception handler
  253. set_exception_handler('_php_api_exception_handler');
  254. // plugins should return true to control what API and user authentication handlers are registered
  255. if (elgg_trigger_plugin_hook('rest', 'init', null, false) == false) {
  256. // for testing from a web browser, you can use the session PAM
  257. // do not use for production sites!!
  258. //register_pam_handler('pam_auth_session');
  259. // user token can also be used for user authentication
  260. register_pam_handler('pam_auth_usertoken');
  261. // simple API key check
  262. register_pam_handler('api_auth_key', "sufficient", "api");
  263. // hmac
  264. register_pam_handler('api_auth_hmac', "sufficient", "api");
  265. }
  266. // Get parameter variables
  267. $method = get_input('method');
  268. $result = null;
  269. // this will throw an exception if authentication fails
  270. authenticate_method($method);
  271. $result = execute_method($method);
  272. if (!($result instanceof GenericResult)) {
  273. throw new APIException(elgg_echo('APIException:ApiResultUnknown'));
  274. }
  275. // Output the result
  276. echo elgg_view_page($method, elgg_view("api/output", array("result" => $result)));
  277. }
  278. /**
  279. * Unit tests for web services
  280. *
  281. * @param string $hook unit_test
  282. * @param string $type system
  283. * @param mixed $value Array of tests
  284. * @param mixed $params Params
  285. *
  286. * @return array
  287. * @access private
  288. */
  289. function ws_unit_test($hook, $type, $value, $params) {
  290. elgg_load_library('elgg:ws');
  291. elgg_load_library('elgg:ws:client');
  292. $value[] = dirname(__FILE__) . '/tests/ElggCoreWebServicesApiTest.php';
  293. return $value;
  294. }