aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--endpoints/rest.php159
-rw-r--r--engine/lib/api.php436
2 files changed, 216 insertions, 379 deletions
diff --git a/endpoints/rest.php b/endpoints/rest.php
index e00d27555..6f9efc1e8 100644
--- a/endpoints/rest.php
+++ b/endpoints/rest.php
@@ -11,10 +11,68 @@
* @link http://elgg.org/
*/
+
+
+
+
+ /*
+
+ Elgg API system
+A brief specification: internal only
+
+NB: this is a loose specification, and as such some holes or shortcomings may become evident in
+implementation. Therefore, feel free to adjust as necessary, bearing in mind the goals, which
+are unmovable ...
+
+Goals: an extensible, two-way API that can be used to easily code secure client applications
+on a variety of networked systems, whether web or application-based. The results should be available,
+at the very least, in JSON, serialised PHP, XML and CSV, but the output formats should also be
+extensible by plugins in a documented way. Similarly, plugins must be able to add new function calls,
+in a similar way to how they register events or enable actions.
+
+
+
+
+
+
+On release, we will need to provide simple client libraries for PHP, .NET, C, Java and (although this
+can hopefully be outsourced to Kevin or similar) Ruby on Rails. Additionally, Django, vanilla Python
+and Perl libraries would be a bonus, although not required.
+
+Brief implementation requirements: A set of procedural functions. If possible, the output should
+use the existing views system, creating a new base view set for xml, json, csv and php. That way other
+output formats can be specified simply by changing the &view URL parameter, and added / extended by plugins.
+(It would also allow RSS output pretty much for free for certain types of data.) On failure, a friendly
+message should be returned in a way that can be read by the client software.
+
+These functions should be made available in a simple api.php module within engine/lib.php, without the use of
+any external libraries. If an external library really must be used, ensure that it has a compatible license
+and can be used on all systems where Elgg can be installed, including Apache for Windows and Apache-compatible
+web servers.
+
+When a plugin or core software module registers an API call, it should reference a function name, the
+parameters it requires, and an English description of the call. A special API call – and internal function -
+should return a list of enabled calls, for the use of client applications and internal help pages respectively.
+
+As one application of the API is as a back-end for AJAX applications, the API endpoint should check $_SESSION
+for logged in user information before checking for any other kind of login data. This way the browser can
+simply make an asynchronous callback request, allowing for many very interesting Javascript applications.
+In an ideal world, client applications should not need a special API key. This is because an application would
+have to install a new key for each installed Elgg site, which is not preferable, as it has a serious user
+experience hit (before the user can use a new client software on a particular install, they have to go to
+their account settings and obtain something that to them looks like a string of gobbledygook). If possible,
+all the client application should need is a valid username and password.
+
+Using a $CONFIG configuration option, site admins should be able to shut down the entire API system if
+required, or disallow the $_SESSION authentication method.
+
+ */
+
+
// Include required files
require_once('../engine/start.php');
- global $CONFIG, $ApiEnvironment;
-
+ global $CONFIG;
+
// Register the error handler
error_reporting(E_ALL);
set_error_handler('__php_api_error_handler');
@@ -22,12 +80,18 @@
// 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 ConfigurationException("Sorry, API access has been disabled by the administrator.");
+
+
+
+
// Get parameter variables
$format = get_input('format', 'php');
$method = get_input('method');
$result = null;
-
// See if we have a session
/**
* If we have a session then we can assume that this is being called by AJAX from
@@ -37,7 +101,68 @@
*/
if (!isloggedin())
{
- // Get api header
+ //$CONFIG->api_header = get_and_validate_api_headers(); // Get api header
+ //$CONFIG->api_user = get_api_user($CONFIG->api_header->api_key); // Pull API user details
+
+
+
+
+
+
+
+
+
+
+
+ }
+ else
+ {
+ // User is logged in, just execute
+
+
+
+
+ }
+
+ // Finally output
+ if (!($result instanceof GenericResult))
+ throw new APIException("API Result is of an unknown type, this should never happen.");
+
+ // Output the result
+ echo output_result($result, $format);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ // See if we have a session
+ /**
+ * If we have a session then we can assume that this is being called by AJAX from
+ * within an already logged on browser.
+ *
+ * NB. This may be a gaping security hole, but hey ho.
+ */
+// if (!isloggedin())
+// {
+/* // Get api header
$api_header = get_and_validate_api_headers();
$ApiEnvironment->api_header = $api_header;
@@ -94,22 +219,18 @@
}
else
throw new SecurityException("Invalid or missing API Key.",ErrorResult::$RESULT_FAIL_APIKEY_INVALID);
- }
- else
- {
- // Set site environment
- $ApiEnvironment->site_id = $CONFIG->site_id;
-
- // User is logged in, just execute
- if (isset($params['auth_token'])) $token = $params['auth_token'];
- $result = execute_method($method, $params, $token);
- }
+ }*/
+// else
+// {
+// // Set site environment
+// $ApiEnvironment->site_id = $CONFIG->site_id;
+//
+// // User is logged in, just execute
+// if (isset($params['auth_token'])) $token = $params['auth_token'];
+// $result = execute_method($method, $params, $token);
+// }
- // Finally output
- if (!($result instanceof GenericResult))
- throw new APIException("API Result is of an unknown type, this should never happen.");
-
- output_result($result, $format);
+
?> \ No newline at end of file
diff --git a/engine/lib/api.php b/engine/lib/api.php
index aedc6fd4e..53c548b19 100644
--- a/engine/lib/api.php
+++ b/engine/lib/api.php
@@ -158,325 +158,10 @@
}
}
- // API functions //////////////////////////////////////////////////////////////////////////
- /** Create the environment for API Calls */
- $ApiEnvironment = new stdClass;
- /**
- * An array holding methods.
- * The structure of this is
- * $METHODS = array (
- * "api.method" => array (
- * "function" = 'my_function_callback'
- * "parameters" = array (
- * "variable" = array ( // NB, the order is the same as defined by your function callback
- * type => 'int' | 'bool' | 'float' | 'string'
- * required => true (default) | false
- * )
- * )
- * "require_auth" => true (default) | false
- * )
- * )
- */
- $METHODS = array();
-
- /**
- * 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 int $site The ID of the site
- * @param string $token The Token.
- * @return mixed The user id attached to the token or false.
- */
- function validate_user_token($site, $token)
- {
- global $CONFIG;
-
- $site = (int)$site;
- $token = sanitise_string($token);
-
- if (!$site) throw new ConfigurationException("No site ID has been specified.");
- if (!$token) throw new APIException("User token not specified.");
-
- $time = time();
-
- $user = get_data_row("SELECT * from {$CONFIG->dbprefix}users_apisessions where token='$token' and site_id=$site and expires>$time");
- if ($user)
- return $user->user_id;
-
- return false;
- }
-
- /**
- * Expose an arbitrary function as an api call.
- *
- * Limitations: Currently can not expose functions which expect objects or arrays.
- *
- * @param string $method The api name to expose this as, eg "myapi.dosomething"
- * @param string $function Your function callback.
- * @param array $parameters Optional list of parameters in the same order as in your function, with optional parameters last.
- * @param bool $require_auth Whether this requires a user authentication token or not (default is true)
- * @return bool
- */
- function expose_function($method, $function, array $parameters = NULL, $require_auth = true)
- {
- global $METHODS;
-
- if (
- ($method!="") &&
- ($function!="")
- )
- {
- $METHODS[$method] = array();
-
- $METHODS[$method]["function"] = $function;
-
- if ($parameters!=NULL)
- $METHODS[$method]["parameters"] = $parameters;
-
- $METHODS[$method]["require_auth"] = $require_auth;
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Executes a method.
- * A method is a function which you have previously exposed using expose_function.
- *
- * @param string $method Method, e.g. "foo.bar"
- * @param array $parameters Array of parameters in the format "variable" => "value", thse will be sanitised before being fed to your handler.
- * @param string $token The authentication token to authorise this method call.
- * @return GenericResult The result of the execution.
- * @throws APIException, SecurityException
- */
- function execute_method($method, array $parameters, $token = "")
- {
- global $METHODS, $ApiEnvironment;
-
- // Sanity check
- $method = trim($method);
- $token = trim($token);
-
- // See if we can find the method handler
- if (is_callable($METHODS[$method]["function"]))
- {
- $serialised_parameters = "";
-
- $validated_userid = validate_user_token($ApiEnvironment->site_id, $token);
-
- if ((!$METHODS[$method]["require_auth"]) || ($validated_userid) || (isloggedin()))
- {
- // If we have parameters then we need to sanitise the parameters.
- if ((isset($METHODS[$method]["parameters"])) && (is_array($METHODS[$method]["parameters"])))
- {
- foreach ($METHODS[$method]["parameters"] as $key => $value)
- {
- if (
- (is_array($value)) // Check that this is an array
- && (isset($value['type'])) // Check we have a type defined
- )
- {
- // Check that the variable is present in the request
-
- if (
- (!isset($parameters[$key])) && // No parameter
- ((!isset($value['required'])) || ($value['required']!=true)) // and not optional
- )
- throw new APIException("Missing parameter $key in method $method");
- else
- {
- // Avoid debug error
- if (isset($parameters[$key]))
- {
- // Set variables casting to type.
- switch (strtolower($value['type']))
- {
- case 'int':
- case 'integer' : $serialised_parameters .= "," . (int)trim($parameters[$key]); break;
- case 'bool':
- case 'boolean':
- if (strcasecmp(trim($parameters[$key]), "false")==0)
- $parameters[$key]='';
-
- $serialised_parameters .= "," . (bool)trim($parameters[$key]);
- break;
- case 'string': $serialised_parameters .= ",'" . (string)mysql_real_escape_string(trim($parameters[$key])) . "'";
- break;
- case 'float': $serialised_parameters .= "," . (float)trim($parameters[$key]);
- break;
-
- default : throw new APIException("Unrecognised type in cast {$value['type']} for variable '$key' in method '$method'");
- }
- }
- }
- }
- else
- throw new APIException("Invalid parameter found for '$key' in method '$method'.");
- }
- }
-
- // Execute function: Construct function and calling parameters
- $function = $METHODS[$method]["function"];
- $serialised_parameters = trim($serialised_parameters, ", ");
-
- $result = eval("return $function($serialised_parameters);");
-
- // Sanity check result
- if ($result instanceof GenericResult) // If this function returns an api result itself, just return it
- return $result;
-
- if ($result === FALSE)
- throw new APIException("$function($serialised_parameters) has a parsing error.");
-
- if ($result === NULL)
- throw new APIException("$function($serialised_parameters) returned no value."); // If no value
-
- return SuccessResult::getInstance($result); // Otherwise assume that the call was successful and return it as a success object.
- }
- else
- throw new SecurityException("Authentication token either missing, invalid or expired.", GenericResult::$RESULT_FAIL_AUTHTOKEN);
- }
-
- // Return an error if not found
- throw new APIException("Method call '$method' has not been implemented.");
- }
- /**
- * This function looks at the super-global variable $_SERVER and extracts the various
- * header variables needed to pass to the validation functions after performing basic validation.
- *
- * @return stdClass Containing all the values.
- * @throws APIException Detailing the error.
- */
- function get_and_validate_api_headers()
- {
- $result = new stdClass;
-
- $result->method = trim($_SERVER['REQUEST_METHOD']);
- if (($result->method != "GET") && ($result->method!= "POST")) // Only allow these methods
- throw new APIException("Request method must be GET or POST");
-
- $result->api_key = trim($_SERVER['HTTP_X_ELGG_APIKEY']);
- if ($result->api_key == "")
- throw new APIException("Missing X-Elgg-apikey HTTP header");
-
- $result->hmac = trim($_SERVER['HTTP_X_ELGG_HMAC']);
- if ($result->hmac == "")
- throw new APIException("Missing X-Elgg-hmac header");
-
- $result->hmac_algo = trim($_SERVER['HTTP_X_ELGG_HMAC_ALGO']);
- if ($result->hmac_algo == "")
- throw new APIException("Missing X-Elgg-hmac-algo header");
-
- $result->time = trim($_SERVER['HTTP_X_ELGG_TIME']);
- if ($result->time == "")
- throw new APIException("Missing X-Elgg-time header");
- if (($result->time<(microtime(true)-86400.00)) || ($result->time>(microtime(true)+86400.00))) // Basic timecheck, think about making this smaller if we get loads of users and the cache gets really big.
- throw new APIException("X-Elgg-time is too far in the past or future");
-
- $result->get_variables = trim($_SERVER['QUERY_STRING']);
- if ($result->get_variables == "")
- throw new APIException("No data on the query string");
-
- if ($result->method=="POST")
- {
- $result->posthash = trim($_SERVER['HTTP_X_ELGG_POSTHASH']);
- if ($result->posthash == "")
- throw new APIException("Missing X-Elgg-posthash header");
-
- $result->posthash_algo = trim($_SERVER['HTTP_X_ELGG_POSTHASH_ALGO']);
- if ($result->posthash_algo == "")
- throw new APIException("Missing X-Elgg-posthash_algo header");
-
- $result->content_type = trim($_SERVER['CONTENT_TYPE']);
- if ($result->content_type == "")
- throw new APIException("Missing content type for post data");
- }
-
- return $result;
- }
-
- /**
- * Find an API User's details based on the provided public api key.
- *
- * @param string $api_key The API Key
- * @return mixed stdClass representing the database row or false.
- */
- function get_api_user($api_key)
- {
- global $CONFIG;
-
- $api_key = sanitise_string($api_key);
-
- return get_data_row("SELECT * from {$CONFIG->dbprefix}api_users where api_key='$api_key'");
- }
-
- /**
- * Calculate the HMAC for the query.
- * This function signs an api request using the information provided and is then verified by
- * searunner.
- *
- * @param $algo string The HMAC algorithm used as stored in X-Searunner-hmac-algo.
- * @param $time string String representation of unix time as stored in X-Searunner-time.
- * @param $api_key string Your api key.
- * @param $secret string Your secret key.
- * @param $get_variables string URLEncoded string representation of the get variable parameters, eg "format=php&method=searunner.test".
- * @param $post_hash string Optional sha1 hash of the post data.
- * @return string The HMAC string.
- */
- function calculate_hmac($algo, $time, $api_key, $secret_key, $get_variables, $post_hash = "")
- {
- $ctx = hash_init($algo, HASH_HMAC, $secret_key);
-
- hash_update($ctx, trim($time));
- hash_update($ctx, trim($api_key));
- hash_update($ctx, trim($get_variables));
- if (trim($post_hash)!="") hash_update($ctx, trim($post_hash));
-
- return hash_final($ctx);
- }
-
- /**
- * Calculate a hash for some post data.
- *
- * TODO: Work out how to handle really large bits of data.
- *
- * @param $postdata string The post data.
- * @param $algo string The algorithm used.
- * @return string The hash.
- */
- function calculate_posthash($postdata, $algo)
- {
- $ctx = hash_init($algo);
-
- hash_update($ctx, $postdata);
-
- return hash_final($ctx);
- }
- /**
- * This function will do two things. Firstly it verifys that a $hmac hasn't been seen before, and
- * secondly it will add the given hmac to the cache.
- *
- * TODO : REWRITE TO NOT USE ZEND
- *
- * @param $hmac The hmac string.
- * @return bool True if replay detected, false if not.
- */
- function cache_hmac_check_replay($hmac)
- {
- global $CONFIG;
-
- throw new NotImplementedException("Writeme!");
-
- return true;
- }
// XML functions //////////////////////////////////////////////////////////////////////////
@@ -557,50 +242,94 @@
// Output functions ///////////////////////////////////////////////////////////////////////
+ $API_OUTPUT_FUNCTIONS = array();
+
/**
- * Get output for a result in one of a number of formats.
- *
- * @param GenericResult $result
- * @param string $format Optional format, if not specified or invalid, PHP is assumed.
- * @return mixed The serialised output, or false.
+ * Register an API output handler.
+ * This function is used by the system and the plugins to register an output encoding method for
+ * returning API results.
+ *
+ * @param string $form The format string, eg 'xml' or 'php'
+ * @param string $function The function, which must be in the format function_name(stdClass $result) and return a string.
+ * @return bool
*/
- function get_serialised_result(GenericResult $result, $format = "php")
+ function register_api_outputhandler($form, $function)
{
- $format = trim(strtolower($format));
+ global $API_OUTPUT_FUNCTIONS;
- if ($result)
+ if ( ($form!="") && ($function!=""))
{
- // Echo
- switch ($format)
- {
- case 'xml' : return serialise_object_to_xml($result->toStdClass(), "Elgg");
-
- case 'json' : return json_encode($result->toStdClass());
-
- case 'php' :
- default: return serialize($result->toStdClass());
- }
+ $API_OUTPUT_FUNCTIONS[$form] = $function;
+
+ return true;
}
return false;
}
-
+
/**
- * Output a result, altering headers and mime-types as necessary.
- *
- * @param GenericResult $result
- * @param string $format Optional format, if not specified or invalid, PHP is assumed.
+ * Output the result, with the given fault.
+ *
+ * @param GenericResult $result Result object.
+ * @param string $format The format
+ * @return string
*/
- function output_result(GenericResult $result, $format = 'php')
+ function output_result(GenericResult $result, $format)
{
- switch ($format)
- {
- case 'xml' : header('Content-Type: text/xml');
- }
-
- echo get_serialised_result($result, $format);
+ global $API_OUTPUT_FUNCTIONS;
+
+ if (
+ (array_key_exists($format, $API_OUTPUT_FUNCTIONS)) &&
+ (is_callable($API_OUTPUT_FUNCTIONS[$format]))
+ )
+ return $API_OUTPUT_FUNCTIONS[$format]($result->toStdClass());
+
+ // We got here, so no output format was found. Output an error
+ $result = print_r($result, true);
+
+ return <<< END
+<html>
+<head><title>Something went wrong...</title></head>
+<body>
+ <h1>API Output Error</h1>
+ <p>Something went badly wrong while outputting the result of your request to '$format'. The result and any errors are displayed in
+ raw text below.</p>
+ <pre>
+$result
+</pre>
+</body>
+</html>
+END;
+ }
+
+
+ function xml_result_handler(stdClass $result)
+ {
+ header("Content-Type: text/xml");
+ return serialise_object_to_xml($result, "elgg");
+ }
+
+ function php_result_handler(stdClass $result)
+ {
+ return serialize($result);
}
-
+
+ function json_result_handler(stdClass $result)
+ {
+ return json_encode($result);
+ }
+
+ function cvs_result_handler(stdClass $result)
+ {
+ throw new NotImplementedException("CVS View currently not implemented");
+ }
+
+ // Register some format handlers
+ register_api_outputhandler('xml', 'xml_result_handler');
+ register_api_outputhandler('php', 'php_result_handler');
+ register_api_outputhandler('json', 'json_result_handler');
+ register_api_outputhandler('cvs', 'cvs_result_handler');
+
// Error handler functions ////////////////////////////////////////////////////////////////
@@ -656,7 +385,7 @@
error_log("*** FATAL EXCEPTION (API) *** : " . $exception);
- output_result(
+ echo output_result(
ErrorResult::getInstance(
$exception->getMessage(),
$exception->getCode() == 0 ? ErrorResult::$RESULT_FAIL : $exception->getCode(),
@@ -665,18 +394,5 @@
get_input('format','php') // Attempt to get the requested format if passed.
);
}
-
- // System functions ///////////////////////////////////////////////////////////////////////
-
- /**
- * Simple api to return a list of all api's installed on the system.
- */
- function list_all_apis()
- {
- global $METHODS;
- return $METHODS;
- }
- // Expose some system api functions
- expose_function("system.api.list", "list_all_apis", NULL, false);
?> \ No newline at end of file