Skip to content
Browse files

Pulls out web services into a plugin

  • Loading branch information...
1 parent 64668db commit 9d6a8679c78a2d6098c16375075aabbe4ff75326 @cash cash committed Feb 9, 2013
View
1 .gitignore
@@ -36,6 +36,7 @@
!/mod/twitter/
!/mod/twitter_api/
!/mod/uservalidationbyemail/
+!/mod/web_services/
!/mod/zaudio/
# ignore IDE/hidden/OS cache files
View
18 engine/handlers/service_handler.php
@@ -2,26 +2,14 @@
/**
* Services handler.
*
- * This file dispatches requests to web services. It is called via a URL rewrite
- * in .htaccess from http://site/services/api/handler/response_format/request.
- * The first element after 'services/api/' is the service handler name as
- * registered by {@link register_service_handler()}.
- *
- * The remaining string is then passed to the {@link service_handler()}
- * which explodes by /, extracts the first element as the response format
- * (viewtype), and then passes the remaining array to the service handler
- * function registered by {@link register_service_handler()}.
- *
- * If a service handler isn't found, a 404 header is sent.
- *
- * @package Elgg.Core
- * @subpackage WebServices
- * @link http://docs.elgg.org/Tutorials/WebServices
+ * @deprecated 1.9 Update your .htaccess to remove the service handler
*/
require_once(dirname(dirname(__FILE__)) . "/start.php");
$handler = get_input('handler');
$request = get_input('request');
+elgg_deprecated_notice("Update your .htaccess file to remove the service handler", 1.9);
+
service_handler($handler, $request);
View
1 engine/start.php
@@ -108,7 +108,6 @@
'users.php',
'upgrade.php',
'views.php',
- 'web_services.php',
'widgets.php',
'xml.php',
View
2 htaccess_dist
@@ -110,8 +110,6 @@ RewriteRule ^action\/([A-Za-z0-9\_\-\/]+)$ engine/handlers/action_handler.php?ac
RewriteRule ^cache\/(.*)$ engine/handlers/cache_handler.php?request=$1&%{QUERY_STRING} [L]
-RewriteRule ^services\/api\/([A-Za-z0-9\_\-]+)\/(.*)$ engine/handlers/service_handler.php?handler=$1&request=$2&%{QUERY_STRING} [L]
-
RewriteRule ^export\/([A-Za-z]+)\/([0-9]+)\/?$ engine/handlers/export_handler.php?view=$1&guid=$2 [L]
RewriteRule ^export\/([A-Za-z]+)\/([0-9]+)\/([A-Za-z]+)\/([A-Za-z0-9\_]+)\/$ engine/handlers/export_handler.php?view=$1&guid=$2&type=$3&idname=$4 [L]
View
2 install/ElggInstaller.php
@@ -799,7 +799,7 @@ protected function finishBootstraping($step) {
'pam.php', 'plugins.php',
'private_settings.php', 'relationships.php', 'river.php',
'sites.php', 'statistics.php', 'tags.php', 'user_settings.php',
- 'users.php', 'upgrade.php', 'web_services.php',
+ 'users.php', 'upgrade.php',
'widgets.php', 'xml.php', 'deprecated-1.7.php',
'deprecated-1.8.php', 'deprecated-1.9.php'
);
View
7 mod/web_services/README.txt
@@ -0,0 +1,7 @@
+Web Services
+=========================
+This plugin provides the web services API that was packaged with Elgg core from
+version 1.0-1.8. Other plugins can expose functions as web services for
+integration with other platforms or applications.
+
+Documentation is available at http://docs.elgg.org/wiki/Web_Services
View
0 engine/classes/ErrorResult.php → mod/web_services/classes/ErrorResult.php
File renamed without changes.
View
0 engine/classes/GenericResult.php → mod/web_services/classes/GenericResult.php
File renamed without changes.
View
0 engine/classes/SuccessResult.php → mod/web_services/classes/SuccessResult.php
File renamed without changes.
View
24 mod/web_services/languages/en.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * The core language file is in /languages/en.php and each plugin has its
+ * language files in a languages directory. To change a string, copy the
+ * mapping into this file.
+ *
+ * For example, to change the blog Tools menu item
+ * from "Blog" to "Rantings", copy this pair:
+ * 'blog' => "Blog",
+ * into the $mapping array so that it looks like:
+ * 'blog' => "Rantings",
+ *
+ * Follow this pattern for any other string you want to change. Make sure this
+ * plugin is lower in the plugin list than any plugin that it is modifying.
+ *
+ * If you want to add languages other than English, name the file according to
+ * the language's ISO 639-1 code: http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+ */
+
+$mapping = array(
+ 'string:here' => 'Display string here',
+);
+
+add_translation('en', $mapping);
View
76 mod/web_services/lib/api_user.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * A library for managing users of the web services API
+ */
+
+// API key functions /////////////////////////////////////////////////////////////////////
+
+/**
+ * Generate a new API user for a site, returning a new keypair on success.
+ *
+ * @param int $site_guid The GUID of the site. (default is current site)
+ *
+ * @return stdClass object or false
+ */
+function create_api_user($site_guid) {
+ global $CONFIG;
+
+ if (!isset($site_guid)) {
+ $site_guid = $CONFIG->site_id;
+ }
+
+ $site_guid = (int)$site_guid;
+
+ $public = sha1(rand() . $site_guid . microtime());
+ $secret = sha1(rand() . $site_guid . microtime() . $public);
+
+ $insert = insert_data("INSERT into {$CONFIG->dbprefix}api_users
+ (site_guid, api_key, secret) values
+ ($site_guid, '$public', '$secret')");
+
+ if ($insert) {
+ return get_api_user($site_guid, $public);
+ }
+
+ return false;
+}
+
+/**
+ * Find an API User's details based on the provided public api key.
+ * These users are not users in the traditional sense.
+ *
+ * @param int $site_guid The GUID of the site.
+ * @param string $api_key The API Key
+ *
+ * @return mixed stdClass representing the database row or false.
+ */
+function get_api_user($site_guid, $api_key) {
+ global $CONFIG;
+
+ $api_key = sanitise_string($api_key);
+ $site_guid = (int)$site_guid;
+
+ $query = "SELECT * from {$CONFIG->dbprefix}api_users"
+ . " where api_key='$api_key' and site_guid=$site_guid and active=1";
+
+ return get_data_row($query);
+}
+
+/**
+ * Revoke an api user key.
+ *
+ * @param int $site_guid The GUID of the site.
+ * @param string $api_key The API Key (public).
+ *
+ * @return bool
+ */
+function remove_api_user($site_guid, $api_key) {
+ global $CONFIG;
+
+ $keypair = get_api_user($site_guid, $api_key);
+ if ($keypair) {
+ return delete_data("DELETE from {$CONFIG->dbprefix}api_users where id={$keypair->id}");
+ }
+
+ return false;
+}
View
162 mod/web_services/lib/client.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * A library for building an API client
+ *
+ * Load the library 'elgg:ws:client' to use the functions in this library
+ */
+
+/**
+ * Send a raw API call to an elgg api endpoint.
+ *
+ * @param array $keys The api keys.
+ * @param string $url URL of the endpoint.
+ * @param array $call Associated array of "variable" => "value"
+ * @param string $method GET or POST
+ * @param string $post_data The post data
+ * @param string $content_type The content type
+ *
+ * @return string
+ */
+function send_api_call(array $keys, $url, array $call, $method = 'GET', $post_data = '',
+$content_type = 'application/octet-stream') {
+
+ global $CONFIG;
+
+ $headers = array();
+ $encoded_params = array();
+
+ $method = strtoupper($method);
+ switch (strtoupper($method)) {
+ case 'GET' :
+ case 'POST' :
+ break;
+ default:
+ $msg = elgg_echo('NotImplementedException:CallMethodNotImplemented', array($method));
+ throw new NotImplementedException($msg);
+ }
+
+ // Time
+ $time = time();
+
+ // Nonce
+ $nonce = uniqid('');
+
+ // URL encode all the parameters
+ foreach ($call as $k => $v) {
+ $encoded_params[] = urlencode($k) . '=' . urlencode($v);
+ }
+
+ $params = implode('&', $encoded_params);
+
+ // Put together the query string
+ $url = $url . "?" . $params;
+
+ // Construct headers
+ $posthash = "";
+ if ($method == 'POST') {
+ $posthash = calculate_posthash($post_data, 'md5');
+ }
+
+ if ((isset($keys['public'])) && (isset($keys['private']))) {
+ $headers['X-Elgg-apikey'] = $keys['public'];
+ $headers['X-Elgg-time'] = $time;
+ $headers['X-Elgg-nonce'] = $nonce;
+ $headers['X-Elgg-hmac-algo'] = 'sha1';
+ $headers['X-Elgg-hmac'] = calculate_hmac('sha1',
+ $time,
+ $nonce,
+ $keys['public'],
+ $keys['private'],
+ $params,
+ $posthash
+ );
+ }
+ if ($method == 'POST') {
+ $headers['X-Elgg-posthash'] = $posthash;
+ $headers['X-Elgg-posthash-algo'] = 'md5';
+
+ $headers['Content-type'] = $content_type;
+ $headers['Content-Length'] = strlen($post_data);
+ }
+
+ // Opt array
+ $http_opts = array(
+ 'method' => $method,
+ 'header' => serialise_api_headers($headers)
+ );
+ if ($method == 'POST') {
+ $http_opts['content'] = $post_data;
+ }
+
+ $opts = array('http' => $http_opts);
+
+ // Send context
+ $context = stream_context_create($opts);
+
+ // Send the query and get the result and decode.
+ elgg_log("APICALL: $url");
+ $results = file_get_contents($url, false, $context);
+
+ return $results;
+}
+
+/**
+ * Send a GET call
+ *
+ * @param string $url URL of the endpoint.
+ * @param array $call Associated array of "variable" => "value"
+ * @param array $keys The keys dependant on chosen authentication method
+ *
+ * @return string
+ */
+function send_api_get_call($url, array $call, array $keys) {
+ return send_api_call($keys, $url, $call);
+}
+
+/**
+ * Send a GET call
+ *
+ * @param string $url URL of the endpoint.
+ * @param array $call Associated array of "variable" => "value"
+ * @param array $keys The keys dependant on chosen authentication method
+ * @param string $post_data The post data
+ * @param string $content_type The content type
+ *
+ * @return string
+ */
+function send_api_post_call($url, array $call, array $keys, $post_data,
+$content_type = 'application/octet-stream') {
+
+ return send_api_call($keys, $url, $call, 'POST', $post_data, $content_type);
+}
+
+/**
+ * Return a key array suitable for the API client using the standard
+ * authentication method based on api-keys and secret keys.
+ *
+ * @param string $secret_key Your secret key
+ * @param string $api_key Your api key
+ *
+ * @return array
+ */
+function get_standard_api_key_array($secret_key, $api_key) {
+ return array('public' => $api_key, 'private' => $secret_key);
+}
+
+/**
+ * Utility function to serialise a header array into its text representation.
+ *
+ * @param array $headers The array of headers "key" => "value"
+ *
+ * @return string
+ * @access private
+ */
+function serialise_api_headers(array $headers) {
+ $headers_str = "";
+
+ foreach ($headers as $k => $v) {
+ $headers_str .= trim($k) . ": " . trim($v) . "\r\n";
+ }
+
+ return trim($headers_str);
+}
View
158 mod/web_services/lib/tokens.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Library for managing user tokens
+ */
+
+/**
+ * Obtain a token for a user.
+ *
+ * @param string $username The username
+ * @param int $expire Minutes until token expires (default is 60 minutes)
+ *
+ * @return bool
+ */
+function create_user_token($username, $expire = 60) {
+ global $CONFIG;
+
+ $site_guid = $CONFIG->site_id;
+ $user = get_user_by_username($username);
+ $time = time();
+ $time += 60 * $expire;
+ $token = md5(rand() . microtime() . $username . $time . $site_guid);
+
+ if (!$user) {
+ return false;
+ }
+
+ if (insert_data("INSERT into {$CONFIG->dbprefix}users_apisessions
+ (user_guid, site_guid, token, expires) values
+ ({$user->guid}, $site_guid, '$token', '$time')
+ on duplicate key update token='$token', expires='$time'")) {
+ return $token;
+ }
+
+ return false;
+}
+
+/**
+ * Get all tokens attached to a user
+ *
+ * @param int $user_guid The user GUID
+ * @param int $site_guid The ID of the site (default is current site)
+ *
+ * @return false if none available or array of stdClass objects
+ * (see users_apisessions schema for available variables in objects)
+ * @since 1.7.0
+ */
+function get_user_tokens($user_guid, $site_guid) {
+ global $CONFIG;
+
+ if (!isset($site_guid)) {
+ $site_guid = $CONFIG->site_id;
+ }
+
+ $site_guid = (int)$site_guid;
+ $user_guid = (int)$user_guid;
+
+ $tokens = get_data("SELECT * from {$CONFIG->dbprefix}users_apisessions
+ where user_guid=$user_guid and site_guid=$site_guid");
+
+ return $tokens;
+}
+
+/**
+ * Validate a token against a given site.
+ *
+ * A token registered with one site can not be used from a
+ * different apikey(site), so be aware of this during development.
+ *
+ * @param string $token The Token.
+ * @param int $site_guid The ID of the site (default is current site)
+ *
+ * @return mixed The user id attached to the token if not expired or false.
+ */
+function validate_user_token($token, $site_guid) {
+ global $CONFIG;
+
+ if (!isset($site_guid)) {
+ $site_guid = $CONFIG->site_id;
+ }
+
+ $site_guid = (int)$site_guid;
+ $token = sanitise_string($token);
+
+ $time = time();
+
+ $user = get_data_row("SELECT * from {$CONFIG->dbprefix}users_apisessions
+ where token='$token' and site_guid=$site_guid and $time < expires");
+
+ if ($user) {
+ return $user->user_guid;
+ }
+
+ return false;
+}
+
+/**
+ * Remove user token
+ *
+ * @param string $token The toekn
+ * @param int $site_guid The ID of the site (default is current site)
+ *
+ * @return bool
+ * @since 1.7.0
+ */
+function remove_user_token($token, $site_guid) {
+ global $CONFIG;
+
+ if (!isset($site_guid)) {
+ $site_guid = $CONFIG->site_id;
+ }
+
+ $site_guid = (int)$site_guid;
+ $token = sanitise_string($token);
+
+ return delete_data("DELETE from {$CONFIG->dbprefix}users_apisessions
+ where site_guid=$site_guid and token='$token'");
+}
+
+/**
+ * Remove expired tokens
+ *
+ * @return bool
+ * @since 1.7.0
+ */
+function remove_expired_user_tokens() {
+ global $CONFIG;
+
+ $site_guid = $CONFIG->site_id;
+
+ $time = time();
+
+ return delete_data("DELETE from {$CONFIG->dbprefix}users_apisessions
+ where site_guid=$site_guid and expires < $time");
+}
+
+/**
+ * The auth.gettoken API.
+ * This API call lets a user log in, returning an authentication token which can be used
+ * to authenticate a user for a period of time. It is passed in future calls as the parameter
+ * auth_token.
+ *
+ * @param string $username Username
+ * @param string $password Clear text password
+ *
+ * @return string Token string or exception
+ * @throws SecurityException
+ * @access private
+ */
+function auth_gettoken($username, $password) {
+ if (true === elgg_authenticate($username, $password)) {
+ $token = create_user_token($username);
+ if ($token) {
+ return $token;
+ }
+ }
+
+ throw new SecurityException(elgg_echo('SecurityException:authenticationfailed'));
+}
View
710 engine/lib/web_services.php → mod/web_services/lib/web_services.php
@@ -1,152 +1,10 @@
<?php
/**
- * Elgg web services API
+ * Elgg web services API library
* Functions and objects for exposing custom web services.
*
- * @package Elgg.Core
- * @subpackage WebServicesAPI
*/
-// Primary Services API Server functions
-
-/**
- * A global array holding API methods.
- * The structure of this is
- * $API_METHODS = array (
- * $method => array (
- * "description" => "Some human readable description"
- * "function" = 'my_function_callback'
- * "parameters" = array (
- * "variable" = array ( // the order should be the same as the function callback
- * type => 'int' | 'bool' | 'float' | 'string'
- * required => true (default) | false
- * default => value // optional
- * )
- * )
- * "call_method" = 'GET' | 'POST'
- * "require_api_auth" => true | false (default)
- * "require_user_auth" => true | false (default)
- * )
- * )
- */
-global $API_METHODS;
-$API_METHODS = array();
-
-/**
- * Expose a function as a services api call.
- *
- * Limitations: Currently cannot expose functions which expect objects.
- * It also cannot handle arrays of bools or arrays of arrays.
- * Also, input will be filtered to protect against XSS attacks through the API.
- *
- * @param string $method The api name to expose - for example "myapi.dosomething"
- * @param string $function Your function callback.
- * @param array $parameters (optional) List of parameters in the same order as in
- * your function. Default values may be set for parameters which
- * allow REST api users flexibility in what parameters are passed.
- * Generally, optional parameters should be after required
- * parameters.
- *
- * This array should be in the format
- * "variable" = array (
- * type => 'int' | 'bool' | 'float' | 'string' | 'array'
- * required => true (default) | false
- * default => value (optional)
- * )
- * @param string $description (optional) human readable description of the function.
- * @param string $call_method (optional) Define what http method must be used for
- * this function. Default: GET
- * @param bool $require_api_auth (optional) (default is false) Does this method
- * require API authorization? (example: API key)
- * @param bool $require_user_auth (optional) (default is false) Does this method
- * require user authorization?
- *
- * @return bool
- * @throws InvalidParameterException
- */
-function expose_function($method, $function, array $parameters = NULL, $description = "",
-$call_method = "GET", $require_api_auth = false, $require_user_auth = false) {
-
- global $API_METHODS;
-
- if (($method == "") || ($function == "")) {
- $msg = elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet');
- throw new InvalidParameterException($msg);
- }
-
- // does not check whether this method has already been exposed - good idea?
- $API_METHODS[$method] = array();
-
- $API_METHODS[$method]["description"] = $description;
-
- // does not check whether callable - done in execute_method()
- $API_METHODS[$method]["function"] = $function;
-
- if ($parameters != NULL) {
- if (!is_array($parameters)) {
- $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method));
- throw new InvalidParameterException($msg);
- }
-
- // catch common mistake of not setting up param array correctly
- $first = current($parameters);
- if (!is_array($first)) {
- $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method));
- throw new InvalidParameterException($msg);
- }
- }
-
- if ($parameters != NULL) {
- // ensure the required flag is set correctly in default case for each parameter
- foreach ($parameters as $key => $value) {
- // check if 'required' was specified - if not, make it true
- if (!array_key_exists('required', $value)) {
- $parameters[$key]['required'] = true;
- }
- }
-
- $API_METHODS[$method]["parameters"] = $parameters;
- }
-
- $call_method = strtoupper($call_method);
- switch ($call_method) {
- case 'POST' :
- $API_METHODS[$method]["call_method"] = 'POST';
- break;
- case 'GET' :
- $API_METHODS[$method]["call_method"] = 'GET';
- break;
- default :
- $msg = elgg_echo('InvalidParameterException:UnrecognisedHttpMethod',
- array($call_method, $method));
-
- throw new InvalidParameterException($msg);
- }
-
- $API_METHODS[$method]["require_api_auth"] = $require_api_auth;
-
- $API_METHODS[$method]["require_user_auth"] = $require_user_auth;
-
- return true;
-}
-
-/**
- * Unregister an API method
- *
- * @param string $method The api name that was exposed
- *
- * @since 1.7.0
- *
- * @return void
- */
-function unexpose_function($method) {
- global $API_METHODS;
-
- if (isset($API_METHODS[$method])) {
- unset($API_METHODS[$method]);
- }
-}
-
/**
* Check that the method call has the proper API and user authentication
*
@@ -719,81 +577,6 @@ function cache_hmac_check_replay($hmac) {
return true;
}
-// API key functions /////////////////////////////////////////////////////////////////////
-
-/**
- * Generate a new API user for a site, returning a new keypair on success.
- *
- * @param int $site_guid The GUID of the site. (default is current site)
- *
- * @return stdClass object or false
- */
-function create_api_user($site_guid) {
- global $CONFIG;
-
- if (!isset($site_guid)) {
- $site_guid = $CONFIG->site_id;
- }
-
- $site_guid = (int)$site_guid;
-
- $public = sha1(rand() . $site_guid . microtime());
- $secret = sha1(rand() . $site_guid . microtime() . $public);
-
- $insert = insert_data("INSERT into {$CONFIG->dbprefix}api_users
- (site_guid, api_key, secret) values
- ($site_guid, '$public', '$secret')");
-
- if ($insert) {
- return get_api_user($site_guid, $public);
- }
-
- return false;
-}
-
-/**
- * Find an API User's details based on the provided public api key.
- * These users are not users in the traditional sense.
- *
- * @param int $site_guid The GUID of the site.
- * @param string $api_key The API Key
- *
- * @return mixed stdClass representing the database row or false.
- */
-function get_api_user($site_guid, $api_key) {
- global $CONFIG;
-
- $api_key = sanitise_string($api_key);
- $site_guid = (int)$site_guid;
-
- $query = "SELECT * from {$CONFIG->dbprefix}api_users"
- . " where api_key='$api_key' and site_guid=$site_guid and active=1";
-
- return get_data_row($query);
-}
-
-/**
- * Revoke an api user key.
- *
- * @param int $site_guid The GUID of the site.
- * @param string $api_key The API Key (public).
- *
- * @return bool
- */
-function remove_api_user($site_guid, $api_key) {
- global $CONFIG;
-
- $keypair = get_api_user($site_guid, $api_key);
- if ($keypair) {
- return delete_data("DELETE from {$CONFIG->dbprefix}api_users where id={$keypair->id}");
- }
-
- return false;
-}
-
-
-// User Authorization functions
-
/**
* Check the user token
* This examines whether an authentication token is present and returns true if
@@ -852,342 +635,6 @@ function pam_auth_session() {
return elgg_is_logged_in();
}
-// user token functions
-
-/**
- * Obtain a token for a user.
- *
- * @param string $username The username
- * @param int $expire Minutes until token expires (default is 60 minutes)
- *
- * @return bool
- */
-function create_user_token($username, $expire = 60) {
- global $CONFIG;
-
- $site_guid = $CONFIG->site_id;
- $user = get_user_by_username($username);
- $time = time();
- $time += 60 * $expire;
- $token = md5(rand() . microtime() . $username . $time . $site_guid);
-
- if (!$user) {
- return false;
- }
-
- if (insert_data("INSERT into {$CONFIG->dbprefix}users_apisessions
- (user_guid, site_guid, token, expires) values
- ({$user->guid}, $site_guid, '$token', '$time')
- on duplicate key update token='$token', expires='$time'")) {
- return $token;
- }
-
- return false;
-}
-
-/**
- * Get all tokens attached to a user
- *
- * @param int $user_guid The user GUID
- * @param int $site_guid The ID of the site (default is current site)
- *
- * @return false if none available or array of stdClass objects
- * (see users_apisessions schema for available variables in objects)
- * @since 1.7.0
- */
-function get_user_tokens($user_guid, $site_guid) {
- global $CONFIG;
-
- if (!isset($site_guid)) {
- $site_guid = $CONFIG->site_id;
- }
-
- $site_guid = (int)$site_guid;
- $user_guid = (int)$user_guid;
-
- $tokens = get_data("SELECT * from {$CONFIG->dbprefix}users_apisessions
- where user_guid=$user_guid and site_guid=$site_guid");
-
- return $tokens;
-}
-
-/**
- * Validate a token against a given site.
- *
- * A token registered with one site can not be used from a
- * different apikey(site), so be aware of this during development.
- *
- * @param string $token The Token.
- * @param int $site_guid The ID of the site (default is current site)
- *
- * @return mixed The user id attached to the token if not expired or false.
- */
-function validate_user_token($token, $site_guid) {
- global $CONFIG;
-
- if (!isset($site_guid)) {
- $site_guid = $CONFIG->site_id;
- }
-
- $site_guid = (int)$site_guid;
- $token = sanitise_string($token);
-
- $time = time();
-
- $user = get_data_row("SELECT * from {$CONFIG->dbprefix}users_apisessions
- where token='$token' and site_guid=$site_guid and $time < expires");
-
- if ($user) {
- return $user->user_guid;
- }
-
- return false;
-}
-
-/**
- * Remove user token
- *
- * @param string $token The toekn
- * @param int $site_guid The ID of the site (default is current site)
- *
- * @return bool
- * @since 1.7.0
- */
-function remove_user_token($token, $site_guid) {
- global $CONFIG;
-
- if (!isset($site_guid)) {
- $site_guid = $CONFIG->site_id;
- }
-
- $site_guid = (int)$site_guid;
- $token = sanitise_string($token);
-
- return delete_data("DELETE from {$CONFIG->dbprefix}users_apisessions
- where site_guid=$site_guid and token='$token'");
-}
-
-/**
- * Remove expired tokens
- *
- * @return bool
- * @since 1.7.0
- */
-function remove_expired_user_tokens() {
- global $CONFIG;
-
- $site_guid = $CONFIG->site_id;
-
- $time = time();
-
- return delete_data("DELETE from {$CONFIG->dbprefix}users_apisessions
- where site_guid=$site_guid and expires < $time");
-}
-
-// Client api functions
-
-/**
- * Utility function to serialise a header array into its text representation.
- *
- * @param array $headers The array of headers "key" => "value"
- *
- * @return string
- * @access private
- */
-function serialise_api_headers(array $headers) {
- $headers_str = "";
-
- foreach ($headers as $k => $v) {
- $headers_str .= trim($k) . ": " . trim($v) . "\r\n";
- }
-
- return trim($headers_str);
-}
-
-/**
- * Send a raw API call to an elgg api endpoint.
- *
- * @param array $keys The api keys.
- * @param string $url URL of the endpoint.
- * @param array $call Associated array of "variable" => "value"
- * @param string $method GET or POST
- * @param string $post_data The post data
- * @param string $content_type The content type
- *
- * @return string
- */
-function send_api_call(array $keys, $url, array $call, $method = 'GET', $post_data = '',
-$content_type = 'application/octet-stream') {
-
- global $CONFIG;
-
- $headers = array();
- $encoded_params = array();
-
- $method = strtoupper($method);
- switch (strtoupper($method)) {
- case 'GET' :
- case 'POST' :
- break;
- default:
- $msg = elgg_echo('NotImplementedException:CallMethodNotImplemented', array($method));
- throw new NotImplementedException($msg);
- }
-
- // Time
- $time = time();
-
- // Nonce
- $nonce = uniqid('');
-
- // URL encode all the parameters
- foreach ($call as $k => $v) {
- $encoded_params[] = urlencode($k) . '=' . urlencode($v);
- }
-
- $params = implode('&', $encoded_params);
-
- // Put together the query string
- $url = $url . "?" . $params;
-
- // Construct headers
- $posthash = "";
- if ($method == 'POST') {
- $posthash = calculate_posthash($post_data, 'md5');
- }
-
- if ((isset($keys['public'])) && (isset($keys['private']))) {
- $headers['X-Elgg-apikey'] = $keys['public'];
- $headers['X-Elgg-time'] = $time;
- $headers['X-Elgg-nonce'] = $nonce;
- $headers['X-Elgg-hmac-algo'] = 'sha1';
- $headers['X-Elgg-hmac'] = calculate_hmac('sha1',
- $time,
- $nonce,
- $keys['public'],
- $keys['private'],
- $params,
- $posthash
- );
- }
- if ($method == 'POST') {
- $headers['X-Elgg-posthash'] = $posthash;
- $headers['X-Elgg-posthash-algo'] = 'md5';
-
- $headers['Content-type'] = $content_type;
- $headers['Content-Length'] = strlen($post_data);
- }
-
- // Opt array
- $http_opts = array(
- 'method' => $method,
- 'header' => serialise_api_headers($headers)
- );
- if ($method == 'POST') {
- $http_opts['content'] = $post_data;
- }
-
- $opts = array('http' => $http_opts);
-
- // Send context
- $context = stream_context_create($opts);
-
- // Send the query and get the result and decode.
- elgg_log("APICALL: $url");
- $results = file_get_contents($url, false, $context);
-
- return $results;
-}
-
-/**
- * Send a GET call
- *
- * @param string $url URL of the endpoint.
- * @param array $call Associated array of "variable" => "value"
- * @param array $keys The keys dependant on chosen authentication method
- *
- * @return string
- */
-function send_api_get_call($url, array $call, array $keys) {
- return send_api_call($keys, $url, $call);
-}
-
-/**
- * Send a GET call
- *
- * @param string $url URL of the endpoint.
- * @param array $call Associated array of "variable" => "value"
- * @param array $keys The keys dependant on chosen authentication method
- * @param string $post_data The post data
- * @param string $content_type The content type
- *
- * @return string
- */
-function send_api_post_call($url, array $call, array $keys, $post_data,
-$content_type = 'application/octet-stream') {
-
- return send_api_call($keys, $url, $call, 'POST', $post_data, $content_type);
-}
-
-/**
- * Return a key array suitable for the API client using the standard
- * authentication method based on api-keys and secret keys.
- *
- * @param string $secret_key Your secret key
- * @param string $api_key Your api key
- *
- * @return array
- */
-function get_standard_api_key_array($secret_key, $api_key) {
- return array('public' => $api_key, 'private' => $secret_key);
-}
-
-// System functions
-
-/**
- * Simple api to return a list of all api's installed on the system.
- *
- * @return array
- * @access private
- */
-function list_all_apis() {
- global $API_METHODS;
-
- // sort first
- ksort($API_METHODS);
-
- return $API_METHODS;
-}
-
-/**
- * The auth.gettoken API.
- * This API call lets a user log in, returning an authentication token which can be used
- * to authenticate a user for a period of time. It is passed in future calls as the parameter
- * auth_token.
- *
- * @param string $username Username
- * @param string $password Clear text password
- *
- * @return string Token string or exception
- * @throws SecurityException
- * @access private
- */
-function auth_gettoken($username, $password) {
- if (true === elgg_authenticate($username, $password)) {
- $token = create_user_token($username);
- if ($token) {
- return $token;
- }
- }
-
- throw new SecurityException(elgg_echo('SecurityException:authenticationfailed'));
-}
-
-// Error handler functions
-
-/** Define a global array of errors */
-$ERRORS = array();
-
/**
* API PHP Error handler function.
* This function acts as a wrapper to catch and report PHP error messages.
@@ -1254,8 +701,6 @@ function _php_api_exception_handler($exception) {
}
-// Services handler
-
/**
* Services handler - turns request over to the registered handler
* If no handler is found, this returns a 404 error
@@ -1272,12 +717,13 @@ function service_handler($handler, $request) {
elgg_set_context('api');
$request = explode('/', $request);
+ var_dump($request);
// after the handler, the first identifier is response format
// ex) http://example.org/services/api/rest/xml/?method=test
$response_format = array_shift($request);
// Which view - xml, json, ...
- if ($response_format && elgg_is_valid_view_type($response_format)) {
+ if ($response_format && elgg_is_registered_viewtype($response_format)) {
elgg_set_viewtype($response_format);
} else {
// default to xml
@@ -1297,153 +743,3 @@ function service_handler($handler, $request) {
exit;
}
}
-
-/**
- * Registers a web services handler
- *
- * @param string $handler Web services type
- * @param string $function Your function name
- *
- * @return bool Depending on success
- * @since 1.7.0
- */
-function register_service_handler($handler, $function) {
- global $CONFIG;
-
- if (!isset($CONFIG->servicehandler)) {
- $CONFIG->servicehandler = array();
- }
- if (is_callable($function, true)) {
- $CONFIG->servicehandler[$handler] = $function;
- return true;
- }
-
- return false;
-}
-
-/**
- * Remove a web service
- * To replace a web service handler, register the desired handler over the old on
- * with register_service_handler().
- *
- * @param string $handler web services type
- *
- * @return void
- * @since 1.7.0
- */
-function unregister_service_handler($handler) {
- global $CONFIG;
-
- if (isset($CONFIG->servicehandler, $CONFIG->servicehandler[$handler])) {
- unset($CONFIG->servicehandler[$handler]);
- }
-}
-
-/**
- * REST API handler
- *
- * @return void
- * @access private
- *
- * @throws SecurityException|APIException
- */
-function rest_handler() {
- global $CONFIG;
-
- // Register the error handler
- error_reporting(E_ALL);
- set_error_handler('_php_api_error_handler');
-
- // Register a default exception handler
- set_exception_handler('_php_api_exception_handler');
-
- // Check to see if the api is available
- if ((isset($CONFIG->disable_api)) && ($CONFIG->disable_api == true)) {
- throw new SecurityException(elgg_echo('SecurityException:APIAccessDenied'));
- }
-
- // plugins should return true to control what API and user authentication handlers are registered
- if (elgg_trigger_plugin_hook('rest', 'init', null, false) == false) {
- // for testing from a web browser, you can use the session PAM
- // do not use for production sites!!
- //register_pam_handler('pam_auth_session');
-
- // user token can also be used for user authentication
- register_pam_handler('pam_auth_usertoken');
-
- // simple API key check
- register_pam_handler('api_auth_key', "sufficient", "api");
- // hmac
- register_pam_handler('api_auth_hmac', "sufficient", "api");
- }
-
- // Get parameter variables
- $method = get_input('method');
- $result = null;
-
- // this will throw an exception if authentication fails
- authenticate_method($method);
-
- $result = execute_method($method);
-
-
- if (!($result instanceof GenericResult)) {
- throw new APIException(elgg_echo('APIException:ApiResultUnknown'));
- }
-
- // Output the result
- echo elgg_view_page($method, elgg_view("api/output", array("result" => $result)));
-}
-
-// Initialization
-
-/**
- * Unit tests for API
- *
- * @param string $hook unit_test
- * @param string $type system
- * @param mixed $value Array of tests
- * @param mixed $params Params
- *
- * @return array
- * @access private
- */
-function api_unit_test($hook, $type, $value, $params) {
- global $CONFIG;
- $value[] = $CONFIG->path . 'engine/tests/ElggCoreServicesApiTest.php';
- return $value;
-}
-
-/**
- * Initialise the API subsystem.
- *
- * @return void
- * @access private
- */
-function api_init() {
- // Register a page handler, so we can have nice URLs
- register_service_handler('rest', 'rest_handler');
-
- elgg_register_plugin_hook_handler('unit_test', 'system', 'api_unit_test');
-
- // expose the list of api methods
- expose_function("system.api.list", "list_all_apis", NULL,
- elgg_echo("system.api.list"), "GET", false, false);
-
- // The authentication token api
- expose_function(
- "auth.gettoken",
- "auth_gettoken",
- array(
- 'username' => array ('type' => 'string'),
- 'password' => array ('type' => 'string'),
- ),
- elgg_echo('auth.gettoken'),
- 'POST',
- false,
- false
- );
-}
-
-
-elgg_register_event_handler('init', 'system', 'api_init');
View
18 mod/web_services/manifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8">
+ <name>Web services</name>
+ <author>Core developers</author>
+ <version>1.9</version>
+ <description>Provides a framework for building RPC web services.</description>
+ <website>http://www.elgg.org/</website>
+ <copyright>(C) Elgg Foundation 2013</copyright>
+ <license>GNU General Public License version 2</license>
+
+ <requires>
+ <type>elgg_release</type>
+ <version>1.9</version>
+ </requires>
+
+ <category>web_services</category>
+
+</plugin_manifest>
View
350 mod/web_services/start.php
@@ -0,0 +1,350 @@
+<?php
+/**
+ * Elgg web services API plugin
+ */
+
+elgg_register_event_handler('init', 'system', 'ws_init');
+
+function ws_init() {
+ $lib_dir = elgg_get_plugins_path() . "web_services/lib";
+ elgg_register_library('elgg:ws', "$lib_dir/web_services.php");
+ elgg_register_library('elgg:ws:api_user', "$lib_dir/api_user.php");
+ elgg_register_library('elgg:ws:client', "$lib_dir/client.php");
+ elgg_register_library('elgg:ws:tokens', "$lib_dir/tokens.php");
+
+ elgg_load_library('elgg:ws:api_user');
+ elgg_load_library('elgg:ws:tokens');
+
+ elgg_register_page_handler('services', 'ws_page_handler');
+
+ // Register a service handler for the default web services
+ // The name name is a misnomer as they are not RESTful
+ register_service_handler('rest', 'rest_handler');
+
+ // expose the list of api methods
+ expose_function("system.api.list", "list_all_apis", NULL,
+ elgg_echo("system.api.list"), "GET", false, false);
+
+ // The authentication token api
+ expose_function(
+ "auth.gettoken",
+ "auth_gettoken",
+ array(
+ 'username' => array ('type' => 'string'),
+ 'password' => array ('type' => 'string'),
+ ),
+ elgg_echo('auth.gettoken'),
+ 'POST',
+ false,
+ false
+ );
+
+ elgg_register_plugin_hook_handler('unit_test', 'system', 'api_unit_test');
+}
+
+/**
+ * Handle a web service request
+ *
+ * Handles requests of format: http://site/services/api/handler/response_format/request
+ * The first element after 'services/api/' is the service handler name as
+ * registered by {@link register_service_handler()}.
+ *
+ * The remaining string is then passed to the {@link service_handler()}
+ * which explodes by /, extracts the first element as the response format
+ * (viewtype), and then passes the remaining array to the service handler
+ * function registered by {@link register_service_handler()}.
+ *
+ * If a service handler isn't found, a 404 header is sent.
+ *
+ * @param array $segments URL segments
+ * @return bool
+ */
+function ws_page_handler($segments) {
+ elgg_load_library('elgg:ws');
+
+ if (!isset($segments[0]) || $segments[0] != 'api') {
+ return false;
+ }
+ array_shift($segments);
+
+ $handler = array_shift($segments);
+ $request = implode('/', $segments);
+ var_dump($handler);
+
+ service_handler($handler, $request);
+
+ return true;
+}
+
+/**
+ * A global array holding API methods.
+ * The structure of this is
+ * $API_METHODS = array (
+ * $method => array (
+ * "description" => "Some human readable description"
+ * "function" = 'my_function_callback'
+ * "parameters" = array (
+ * "variable" = array ( // the order should be the same as the function callback
+ * type => 'int' | 'bool' | 'float' | 'string'
+ * required => true (default) | false
+ * default => value // optional
+ * )
+ * )
+ * "call_method" = 'GET' | 'POST'
+ * "require_api_auth" => true | false (default)
+ * "require_user_auth" => true | false (default)
+ * )
+ * )
+ */
+global $API_METHODS;
+$API_METHODS = array();
+
+/** Define a global array of errors */
+global $ERRORS;
+$ERRORS = array();
+
+/**
+ * Expose a function as a services api call.
+ *
+ * Limitations: Currently cannot expose functions which expect objects.
+ * It also cannot handle arrays of bools or arrays of arrays.
+ * Also, input will be filtered to protect against XSS attacks through the API.
+ *
+ * @param string $method The api name to expose - for example "myapi.dosomething"
+ * @param string $function Your function callback.
+ * @param array $parameters (optional) List of parameters in the same order as in
+ * your function. Default values may be set for parameters which
+ * allow REST api users flexibility in what parameters are passed.
+ * Generally, optional parameters should be after required
+ * parameters.
+ *
+ * This array should be in the format
+ * "variable" = array (
+ * type => 'int' | 'bool' | 'float' | 'string' | 'array'
+ * required => true (default) | false
+ * default => value (optional)
+ * )
+ * @param string $description (optional) human readable description of the function.
+ * @param string $call_method (optional) Define what http method must be used for
+ * this function. Default: GET
+ * @param bool $require_api_auth (optional) (default is false) Does this method
+ * require API authorization? (example: API key)
+ * @param bool $require_user_auth (optional) (default is false) Does this method
+ * require user authorization?
+ *
+ * @return bool
+ * @throws InvalidParameterException
+ */
+function expose_function($method, $function, array $parameters = NULL, $description = "",
+$call_method = "GET", $require_api_auth = false, $require_user_auth = false) {
+
+ global $API_METHODS;
+
+ if (($method == "") || ($function == "")) {
+ $msg = elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet');
+ throw new InvalidParameterException($msg);
+ }
+
+ // does not check whether this method has already been exposed - good idea?
+ $API_METHODS[$method] = array();
+
+ $API_METHODS[$method]["description"] = $description;
+
+ // does not check whether callable - done in execute_method()
+ $API_METHODS[$method]["function"] = $function;
+
+ if ($parameters != NULL) {
+ if (!is_array($parameters)) {
+ $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method));
+ throw new InvalidParameterException($msg);
+ }
+
+ // catch common mistake of not setting up param array correctly
+ $first = current($parameters);
+ if (!is_array($first)) {
+ $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method));
+ throw new InvalidParameterException($msg);
+ }
+ }
+
+ if ($parameters != NULL) {
+ // ensure the required flag is set correctly in default case for each parameter
+ foreach ($parameters as $key => $value) {
+ // check if 'required' was specified - if not, make it true
+ if (!array_key_exists('required', $value)) {
+ $parameters[$key]['required'] = true;
+ }
+ }
+
+ $API_METHODS[$method]["parameters"] = $parameters;
+ }
+
+ $call_method = strtoupper($call_method);
+ switch ($call_method) {
+ case 'POST' :
+ $API_METHODS[$method]["call_method"] = 'POST';
+ break;
+ case 'GET' :
+ $API_METHODS[$method]["call_method"] = 'GET';
+ break;
+ default :
+ $msg = elgg_echo('InvalidParameterException:UnrecognisedHttpMethod',
+ array($call_method, $method));
+
+ throw new InvalidParameterException($msg);
+ }
+
+ $API_METHODS[$method]["require_api_auth"] = $require_api_auth;
+
+ $API_METHODS[$method]["require_user_auth"] = $require_user_auth;
+
+ return true;
+}
+
+/**
+ * Unregister an API method
+ *
+ * @param string $method The api name that was exposed
+ *
+ * @since 1.7.0
+ *
+ * @return void
+ */
+function unexpose_function($method) {
+ global $API_METHODS;
+
+ if (isset($API_METHODS[$method])) {
+ unset($API_METHODS[$method]);
+ }
+}
+
+/**
+ * Simple api to return a list of all api's installed on the system.
+ *
+ * @return array
+ * @access private
+ */
+function list_all_apis() {
+ global $API_METHODS;
+
+ // sort first
+ ksort($API_METHODS);
+
+ return $API_METHODS;
+}
+
+/**
+ * Registers a web services handler
+ *
+ * @param string $handler Web services type
+ * @param string $function Your function name
+ *
+ * @return bool Depending on success
+ * @since 1.7.0
+ */
+function register_service_handler($handler, $function) {
+ global $CONFIG;
+
+ if (!isset($CONFIG->servicehandler)) {
+ $CONFIG->servicehandler = array();
+ }
+ if (is_callable($function, true)) {
+ $CONFIG->servicehandler[$handler] = $function;
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Remove a web service
+ * To replace a web service handler, register the desired handler over the old on
+ * with register_service_handler().
+ *
+ * @param string $handler web services type
+ *
+ * @return void
+ * @since 1.7.0
+ */
+function unregister_service_handler($handler) {
+ global $CONFIG;
+
+ if (isset($CONFIG->servicehandler, $CONFIG->servicehandler[$handler])) {
+ unset($CONFIG->servicehandler[$handler]);
+ }
+}
+
+/**
+ * REST API handler
+ *
+ * @return void
+ * @access private
+ *
+ * @throws SecurityException|APIException
+ */
+function rest_handler() {
+ global $CONFIG;
+
+ elgg_load_library('elgg:ws');
+
+ // Register the error handler
+ error_reporting(E_ALL);
+ set_error_handler('_php_api_error_handler');
+
+ // Register a default exception handler
+ set_exception_handler('_php_api_exception_handler');
+
+ // Check to see if the api is available
+ if ((isset($CONFIG->disable_api)) && ($CONFIG->disable_api == true)) {
+ throw new SecurityException(elgg_echo('SecurityException:APIAccessDenied'));
+ }
+
+ // plugins should return true to control what API and user authentication handlers are registered
+ if (elgg_trigger_plugin_hook('rest', 'init', null, false) == false) {
+ // for testing from a web browser, you can use the session PAM
+ // do not use for production sites!!
+ //register_pam_handler('pam_auth_session');
+
+ // user token can also be used for user authentication
+ register_pam_handler('pam_auth_usertoken');
+
+ // simple API key check
+ register_pam_handler('api_auth_key', "sufficient", "api");
+ // hmac
+ register_pam_handler('api_auth_hmac', "sufficient", "api");
+ }
+
+ // Get parameter variables
+ $method = get_input('method');
+ $result = null;
+
+ // this will throw an exception if authentication fails
+ authenticate_method($method);
+
+ $result = execute_method($method);
+
+
+ if (!($result instanceof GenericResult)) {
+ throw new APIException(elgg_echo('APIException:ApiResultUnknown'));
+ }
+
+ // Output the result
+ echo elgg_view_page($method, elgg_view("api/output", array("result" => $result)));
+}
+
+/**
+ * Unit tests for API
+ *
+ * @param string $hook unit_test
+ * @param string $type system
+ * @param mixed $value Array of tests
+ * @param mixed $params Params
+ *
+ * @return array
+ * @access private
+ */
+function api_unit_test($hook, $type, $value, $params) {
+ global $CONFIG;
+ $value[] = $CONFIG->path . 'engine/tests/ElggCoreServicesApiTest.php';
+ return $value;
+}

0 comments on commit 9d6a867

Please sign in to comment.
Something went wrong with that request. Please try again.