aboutsummaryrefslogtreecommitdiff
path: root/engine/lib/api.php
diff options
context:
space:
mode:
Diffstat (limited to 'engine/lib/api.php')
-rw-r--r--engine/lib/api.php746
1 files changed, 0 insertions, 746 deletions
diff --git a/engine/lib/api.php b/engine/lib/api.php
deleted file mode 100644
index 11543da21..000000000
--- a/engine/lib/api.php
+++ /dev/null
@@ -1,746 +0,0 @@
-<?php
- /**
- * Elgg API
- * Functions and objects which make up the API engine.
- *
- * @package Elgg
- * @subpackage Core
- * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU Public License version 2
- * @author Marcus Povey <marcus@dushka.co.uk>
- * @copyright Curverider Ltd 2008
- * @link http://elgg.org/
- */
-
- // Result classes /////////////////////////////////////////////////////////////////////////
-
- /**
- * GenericResult Result superclass.
- *
- * @author Marcus Povey <marcus@dushka.co.uk>
- * @package Elgg
- * @subpackage Core
- */
- abstract class GenericResult
- {
- /**
- * The status of the result.
- * @var int
- */
- private $status_code;
-
- /**
- * Message returned along with the status which is almost always an error message.
- * This must be human readable, understandable and localised.
- * @var string
- */
- private $message;
-
- /**
- * Result store.
- * Attach result specific informaton here.
- *
- * @var mixed. Should probably be an object of some sort.
- */
- private $result;
-
- /**
- * Set a status code and optional message.
- *
- * @param int $status The status code.
- * @param string $message The message.
- */
- protected function setStatusCode($status, $message = "")
- {
- $this->status_code = $status;
- $this->message = $message;
- }
-
- /**
- * Set the result.
- *
- * @param mixed $result
- */
- protected function setResult($result) { $this->result = $result; }
-
- protected function getStatusCode() { return $this->status_code; }
- protected function getStatusMessage() { return $this->message; }
- protected function getResult() { return $this->result; }
-
- /**
- * Serialise to a standard class.
- *
- * DEVNOTE: The API is only interested in data, we can not easily serialise
- * custom classes without the need for 1) the other side being PHP, 2) you need to have the class
- * definition installed, 3) its the right version!
- *
- * Therefore, I'm not bothering.
- *
- * Override this to include any more specific information, however api results should be attached to the
- * class using setResult().
- *
- * if $CONFIG->debug is set then additional information about the runtime environment and authentication will be
- * returned.
- *
- * @return stdClass Object containing the serialised result.
- */
- public function export()
- {
- global $ERRORS, $CONFIG, $_PAM_HANDLERS_MSG;
-
- $result = new stdClass;
-
- $result->status = $this->getStatusCode();
- if ($this->getStatusMessage()!="") $result->message = $this->getStatusMessage();
-
- $resultdata = $this->getResult();
- if (isset($resultdata)) $result->result = $resultdata;
-
- if ((isset($CONFIG->debug)) && ($CONFIG->debug == true))
- {
- if (count($ERRORS))
- $result->runtime_errors = $ERRORS;
-
- if (count($_PAM_HANDLERS_MSG))
- $result->pam = $_PAM_HANDLERS_MSG;
- }
-
- return $result;
- }
- }
-
- /**
- * SuccessResult
- * Generic success result class, extend if you want to do something special.
- *
- * @author Marcus Povey <marcus@dushka.co.uk>
- * @package Elgg
- * @subpackage Core
- */
- class SuccessResult extends GenericResult
- {
- public static $RESULT_SUCCESS = 0; // Do not change this from 0
-
- public function SuccessResult($result)
- {
- $this->setResult($result);
- $this->setStatusCode(SuccessResult::$RESULT_SUCCESS);
- }
-
- public static function getInstance($result)
- {
- // Return a new error object.
- return new SuccessResult($result);
- }
- }
-
- /**
- * ErrorResult
- * The error result class.
- *
- * @author Marcus Povey <marcus@dushka.co.uk>
- * @package Elgg
- * @subpackage Core
- */
- class ErrorResult extends GenericResult
- {
- public static $RESULT_FAIL = -1 ; // Fail with no specific code
-
- public static $RESULT_FAIL_APIKEY_DISABLED = -30;
- public static $RESULT_FAIL_APIKEY_INACTIVE = -31;
- public static $RESULT_FAIL_APIKEY_INVALID = -32;
-
- public static $RESULT_FAIL_AUTHTOKEN = -20; // Invalid, expired or missing auth token
-
- public function ErrorResult($message, $code = "", Exception $exception = NULL)
- {
- if ($code == "")
- $code = GenericResult::$RESULT_FAIL;
-
- if ($exception!=NULL)
- $this->setResult($exception->__toString());
-
- $this->setStatusCode($code, $message);
- }
-
- /**
- * Get a new instance of the ErrorResult.
- *
- * @param string $message
- * @param int $code
- * @param Exception $exception Optional exception for generating a stack trace.
- */
- public static function getInstance($message, $code = "", Exception $exception = NULL)
- {
- // Return a new error object.
- return new ErrorResult($message, $code, $exception);
- }
- }
-
- // API Call functions /////////////////////////////////////////////////////////////////////
-
- /**
- * An array holding methods.
- * The structure of this is
- * $METHODS = array (
- * "api.method" => array (
- * "function" = 'my_function_callback'
- * "call_method" = 'GET' | 'POST'
- * "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
- * "description" => "Some human readable description"
- * )
- * )
- */
- $METHODS = array();
-
- /**
- * Get the request method.
- */
- function get_call_method()
- {
- return $_SERVER['REQUEST_METHOD'];
- }
-
- /**
- * 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(elgg_echo('ConfigurationException:NoSiteID'));
-
- $time = time();
-
- $user = get_data_row("SELECT * from {$CONFIG->dbprefix}users_apisessions where token='$token' and site_guid=$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.
- * This array should be in the format
- * "variable" = array (
- * type => 'int' | 'bool' | 'float' | 'string' | 'array'
- * required => true (default) | false
- * )
- * @param string $call_method Define what call method should be used for this function.
- * @param bool $require_auth Whether this requires a user authentication token or not (default is true)
- * @param string $description Optional human readable description of the function.
- * @return bool
- */
- function expose_function($method, $function, array $parameters = NULL, $call_method = "GET", $require_auth = true, $description = "")
- {
- global $METHODS;
-
- if (
- ($method!="") &&
- ($function!="")
- )
- {
- $METHODS[$method] = array();
-
- $METHODS[$method]["function"] = $function;
-
- if ($parameters!=NULL)
- $METHODS[$method]["parameters"] = $parameters;
-
- $call_method = strtoupper($call_method);
- switch ($call_method)
- {
- case 'POST' : $METHODS[$method]["call_method"] = 'POST'; break;
- case 'GET' : $METHODS[$method]["call_method"] = 'GET'; break;
- default :
- throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:UnrecognisedMethod'), $method));
- }
-
- $METHODS[$method]["description"] = $description;
-
- $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, $CONFIG;
-
- // Sanity check
- $method = sanitise_string($method);
- $token = sanitise_string($token);
-
- // See if we can find the method handler
- if ((isset($METHODS[$method]["function"])) && (is_callable($METHODS[$method]["function"])))
- {
- // See if this is being made with the right call method
- if (strcmp(get_call_method(), $METHODS[$method]["call_method"])==0)
- {
- $serialised_parameters = "";
-
- $validated_userid = validate_user_token($CONFIG->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(sprintf(elgg_echo('APIException:MissingParameterInMethod'), $key, $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;
- case 'array':
- $array = "array(";
-
- if (is_array($parameters[$key]))
- {
- foreach ($parameters[$key] as $k => $v)
- {
- $k = sanitise_string($k);
- $v = sanitise_string($v);
-
- $array .= "'$k'=>'$v',";
- }
-
- $array = trim($array,",");
- }
- else
- throw APIException(sprintf(elgg_echo('APIException:ParameterNotArray'), $key));
-
- $array .= ")";
-
- $serialised_parameters .= $array;
- break;
-
- default : throw new APIException(sprintf(elgg_echo('APIException:UnrecognisedTypeCast'), $value['type'], $key, $method));
- }
- }
- }
- }
- else
- throw new APIException(sprintf(elgg_echo('APIException:InvalidParameter'), $key, $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(sprintf(elgg_echo('APIException:FunctionParseError'), $function, $serialised_parameters));
-
- if ($result === NULL)
- throw new APIException(sprintf(elgg_echo('APIException:FunctionNoReturn'), $function, $serialised_parameters)); // 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(elgg_echo('SecurityException:AuthTokenExpired'), GenericResult::$RESULT_FAIL_AUTHTOKEN);
- }
- else
- throw new CallException(sprintf(elgg_echo('CallException:InvalidCallMethod'), $method, $METHODS[$method]["call_method"]));
- }
-
- // Return an error if not found
- throw new APIException(sprintf(elgg_echo('APIException:MethodCallNotImplemented'), $method));
- }
-
- // 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, "GET", false, "List all available API calls on the system.");
-
-
- // PAM AUTH HMAC functions ////////////////////////////////////////////////////////////////
-
- /**
- * Map various algorithms to their PHP equivs.
- * This also gives us an easy way to disable algorithms.
- *
- * @param string $algo The algorithm
- * @return string The php algorithm
- * @throws APIException if an algorithm is not supported.
- */
- function map_api_hash($algo)
- {
- $algo = strtolower(sanitise_string($algo));
- $supported_algos = array(
- "md5" => "md5", // TODO: Consider phasing this out
- "sha" => "sha1", // alias for sha1
- "sha1" => "sha1",
- "sha256" => "sha256"
- );
-
- if (array_key_exists($algo, $supported_algos))
- return $supported_algos[$algo];
-
- throw new APIException(sprintf(elgg_echo('APIException:AlgorithmNotSupported'), $algo));
- }
-
- /**
- * 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(map_api_hash($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(map_api_hash($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;
-
- $cache_dir = $CONFIG->cache_path;
- if (!$cache_dir)
- throw new ConfigurationException(elgg_echo('ConfigurationException:CacheDirNotSet'));
-
- $cache = new ElggFileCache($cache_dir, 90000); // cache lifetime is 25 hours (see time window in get_and_validate_api_headers() )
-
- if (!$result = $cache->load($hmac))
- {
- $cache->save($hmac, $hmac);
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Find an API User's details based on the provided public api key.
- *
- * @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;
-
- return get_data_row("SELECT * from {$CONFIG->dbprefix}api_users where api_key='$api_key' and site_guid=$site_guid and active=1");
- }
-
- /**
- * 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 any error.
- */
- function get_and_validate_api_headers()
- {
- $result = new stdClass;
-
- $result->method = $_SERVER['REQUEST_METHOD'];
- if (($result->method != "GET") && ($result->method!= "POST")) // Only allow these methods
- throw new APIException(elgg_echo('APIException:NotGetOrPost'));
-
- $result->api_key = $_SERVER['HTTP_X_ELGG_APIKEY'];
- if ($result->api_key == "")
- throw new APIException(elgg_echo('APIException:MissingAPIKey'));
-
- $result->hmac = $_SERVER['HTTP_X_ELGG_HMAC'];
- if ($result->hmac == "")
- throw new APIException(elgg_echo('APIException:MissingHmac'));
-
- $result->hmac_algo = $_SERVER['HTTP_X_ELGG_HMAC_ALGO'];
- if ($result->hmac_algo == "")
- throw new APIException(elgg_echo('APIException:MissingHmacAlgo'));
-
- $result->time = $_SERVER['HTTP_X_ELGG_TIME'];
- if ($result->time == "")
- throw new APIException(elgg_echo('APIException:MissingTime'));
- 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(elgg_echo('APIException:TemporalDrift'));
-
- $result->get_variables = $_SERVER['QUERY_STRING'];
- if ($result->get_variables == "")
- throw new APIException(elgg_echo('APIException:NoQueryString'));
-
- if ($result->method=="POST")
- {
- $result->posthash = $_SERVER['HTTP_X_ELGG_POSTHASH'];
- if ($result->posthash == "")
- throw new APIException(elgg_echo('APIException:MissingPOSTHash'));
-
- $result->posthash_algo = $_SERVER['HTTP_X_ELGG_POSTHASH_ALGO'];
- if ($result->posthash_algo == "")
- throw new APIException(elgg_echo('APIException:MissingPOSTAlgo'));
-
- $result->content_type = $_SERVER['CONTENT_TYPE'];
- if ($result->content_type == "")
- throw new APIException(elgg_echo('APIException:MissingContentType'));
- }
-
- return $result;
- }
-
- /**
- * Return a sanitised form of the POST data sent to the script
- *
- * @return string
- */
- function get_post_data()
- {
- global $GLOBALS;
-
- return $GLOBALS['HTTP_RAW_POST_DATA'];
- }
-
- // PAM functions //////////////////////////////////////////////////////////////////////////
-
- /**
- * See if the user has a valid login sesson.
- */
- function pam_auth_session($credentials = NULL)
- {
- return isloggedin();
- }
-
- /**
- * Secure authentication through headers and HMAC.
- */
- function pam_auth_hmac($credentials = NULL)
- {
- global $CONFIG;
-
- $api_header = get_and_validate_api_headers(); // Get api header
- $api_user = get_api_user($CONFIG->site_id, $api_header->api_key); // Pull API user details
-
- if ($api_user)
- {
- // Get the secret key
- $secret_key = $api_user->secret;
-
- // Validate HMAC
- $hmac = calculate_hmac($api_header->hmac_algo,
- $api_header->time,
- $api_header->api_key,
- $secret_key,
- $api_header->get_variables,
- $api_header->method == 'POST' ? $api_header->posthash : "");
-
- if (strcmp(
- $api_header->hmac,
- $hmac
- )==0)
- {
- // Now make sure this is not a replay
- if (!cache_hmac_check_replay($hmac))
- {
- // Validate post data
- if ($api_header->method=="POST")
- {
- $postdata = get_post_data();
- $calculated_posthash = calculate_posthash($postdata, $api_header->posthash_algo);
-
- if (strcmp($api_header->posthash, $calculated_posthash)!=0)
- throw new SecurityException(sprintf(elgg_echo('SecurityException:InvalidPostHash'), $calculated_posthash, $api_header->posthash));
- }
-
- // If we've passed all the checks so far then we can be reasonably certain that the request is authentic, so return this fact to the PAM engine.
- return true;
- }
- else
- throw new SecurityException(elgg_echo('SecurityException:DupePacket'));
- }
- else
- throw new SecurityException("HMAC is invalid. {$api_header->hmac} != [calc]$hmac = {$api_header->hmac_algo}(**SECRET KEY**, time:{$api_header->time}, apikey:{$api_header->api_key}, get_vars:{$api_header->get_variables}" . ($api_header->method=="POST"? "posthash:$api_header->posthash}" : ")"));
- }
- else
- throw new SecurityException(elgg_echo('SecurityException:InvalidAPIKey'),ErrorResult::$RESULT_FAIL_APIKEY_INVALID);
-
- return false;
- }
-
- // Error handler functions ////////////////////////////////////////////////////////////////
-
- /** Define a global array of errors */
- $ERRORS = array();
-
- /**
- * PHP Error handler function.
- * This function acts as a wrapper to catch and report PHP error messages.
- *
- * @see http://uk3.php.net/set-error-handler
- * @param unknown_type $errno
- * @param unknown_type $errmsg
- * @param unknown_type $filename
- * @param unknown_type $linenum
- * @param unknown_type $vars
- */
- function __php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars)
- {
- global $ERRORS;
-
- $error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file " . $filename . " (line " . $linenum . ")";
-
- switch ($errno) {
- case E_USER_ERROR:
- error_log("ERROR: " . $error);
- $ERRORS[] = "ERROR: " .$error;
-
- // Since this is a fatal error, we want to stop any further execution but do so gracefully.
- throw new Exception("ERROR: " . $error);
- break;
-
- case E_WARNING :
- case E_USER_WARNING :
- error_log("WARNING: " . $error);
- $ERRORS[] = "WARNING: " .$error;
- break;
-
- default:
- error_log("DEBUG: " . $error);
- $ERRORS[] = "DEBUG: " .$error;
- }
- }
-
- /**
- * PHP Exception handler.
- * This is a generic exception handler for PHP exceptions. This will catch any
- * uncaught exception and return it as an ErrorResult in the requested format.
- *
- * @param Exception $exception
- */
- function __php_api_exception_handler($exception) {
-
- error_log("*** FATAL EXCEPTION (API) *** : " . $exception);
-
- page_draw($exception->getMessage(), elgg_view("api/output",
- array('result' => ErrorResult::getInstance(
- $exception->getMessage(),
- $exception->getCode() == 0 ? ErrorResult::$RESULT_FAIL : $exception->getCode(),
- $exception)
- ))
- );
- }
-
-?> \ No newline at end of file