diff options
Diffstat (limited to 'engine/lib/api.php')
| -rw-r--r-- | engine/lib/api.php | 1411 |
1 files changed, 0 insertions, 1411 deletions
diff --git a/engine/lib/api.php b/engine/lib/api.php deleted file mode 100644 index 92d68475b..000000000 --- a/engine/lib/api.php +++ /dev/null @@ -1,1411 +0,0 @@ -<?php -/** - * Elgg API - * Functions and objects which make up the API engine. - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ - */ - -// Result classes ///////////////////////////////////////////////////////////////////////// - -/** - * GenericResult Result superclass. - * - * @author Curverider Ltd <info@elgg.com> - * @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)) { - 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 Curverider Ltd <info@elgg.com> - * @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 Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Core - */ -class ErrorResult extends GenericResult { - // Fail with no specific code - public static $RESULT_FAIL = -1 ; - - public static $RESULT_FAIL_APIKEY_DISABLED = -30; - public static $RESULT_FAIL_APIKEY_INACTIVE = -31; - public static $RESULT_FAIL_APIKEY_INVALID = -32; - - // Invalid, expired or missing auth token - public static $RESULT_FAIL_AUTHTOKEN = -20; - - public function ErrorResult($message, $code = "", Exception $exception = NULL) { - if ($code == "") { - $code = ErrorResult::$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); - } -} - -// Caching of HMACs /////////////////////////////////////////////////////////////////////// - -/** - * ElggHMACCache - * Store cached data in a temporary database, only used by the HMAC stuff. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage API - */ -class ElggHMACCache extends ElggCache { - /** - * Set the Elgg cache. - * - * @param int $max_age Maximum age in seconds, 0 if no limit. - */ - function __construct($max_age = 0) { - $this->set_variable("max_age", $max_age); - } - - /** - * Save a key - * - * @param string $key - * @param string $data - * @return boolean - */ - public function save($key, $data) { - global $CONFIG; - - $key = sanitise_string($key); - $time = time(); - - return insert_data("INSERT into {$CONFIG->dbprefix}hmac_cache (hmac, ts) VALUES ('$key', '$time')"); - } - - /** - * Load a key - * - * @param string $key - * @param int $offset - * @param int $limit - * @return string - */ - public function load($key, $offset = 0, $limit = null) { - global $CONFIG; - - $key = sanitise_string($key); - - $row = get_data_row("SELECT * from {$CONFIG->dbprefix}hmac_cache where hmac='$key'"); - if ($row) { - return $row->hmac; - } - - return false; - } - - /** - * Invalidate a given key. - * - * @param string $key - * @return bool - */ - public function delete($key) { - global $CONFIG; - - $key = sanitise_string($key); - - return delete_data("DELETE from {$CONFIG->dbprefix}hmac_cache where hmac='$key'"); - } - - /** - * Clear out all the contents of the cache. - * - * Not currently implemented in this cache type. - */ - public function clear() { - return true; - } - - /** - * Clean out old stuff. - * - */ - public function __destruct() { - global $CONFIG; - - $time = time(); - $age = (int)$this->get_variable("max_age"); - - $expires = $time-$age; - - delete_data("DELETE from {$CONFIG->dbprefix}hmac_cache where ts<$expires"); - } -} - -// Primary Services API Server functions ///////////////////////////////////////////////////////////////////// - -/** - * A global array holding API methods. - * The structure of this is - * $API_METHODS = array ( - * $method => array ( - * "function" = 'my_function_callback' - * "call_method" = 'GET' | 'POST' - * "parameters" = array ( - * "variable" = array ( // NB, the order should be the same as the function callback - * type => 'int' | 'bool' | 'float' | 'string' - * required => true (default) | false - * ) - * ) - * "require_api_auth" => true | false (default) - * "require_user_auth" => true | false (default) - * "description" => "Some human readable description" - * ) - * ) - */ -$API_METHODS = array(); - -/** - * Expose a function as a services api call. - * - * Limitations: Currently can not expose functions which expect objects. - * - * @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, 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 $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 - */ -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 == "")) { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet')); - } - - // does not check whether this method has already been exposed - good idea? - $API_METHODS[$method] = array(); - - // does not check whether callable - done in execute_method() - $API_METHODS[$method]["function"] = $function; - - if ($parameters != NULL) { - if (!is_array($parameters)) { - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:APIParametersArrayStructure'), $method)); - } - - // catch common mistake of not setting up param array correctly - $first = current($parameters); - if (!is_array($first)) { - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:APIParametersArrayStructure'), $method)); - } - } - - 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 : - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:UnrecognisedHttpMethod'), $call_method, $method)); - } - - $API_METHODS[$method]["description"] = $description; - - $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 $method The api name that was exposed - */ -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 - * @return bool - */ -function authenticate_method($method) { - global $API_METHODS; - - // method must be exposed - if (!isset($API_METHODS[$method])) { - throw new APIException(sprintf(elgg_echo('APIException:MethodCallNotImplemented'), $method)); - } - - // make sure that POST variables are available if relevant - if (get_call_method() === 'POST') { - include_post_data(); - } - - // check API authentication if required - if ($API_METHODS[$method]["require_api_auth"] == true) { - if (pam_authenticate(null, "api") == false) { - throw new APIException(elgg_echo('APIException:APIAuthenticationFailed')); - } - } - - // check user authentication if required - if ($API_METHODS[$method]["require_user_auth"] == true) { - if (pam_authenticate() == false) { - throw new APIException(elgg_echo('APIException:UserAuthenticationFailed')); - } - } - - return true; -} - -/** - * Executes a method. - * A method is a function which you have previously exposed using expose_function. - * - * @param string $method Method, e.g. "foo.bar" - * @return GenericResult The result of the execution. - * @throws APIException, SecurityException - */ -function execute_method($method) { - global $API_METHODS, $CONFIG; - - // method must be exposed - if (!isset($API_METHODS[$method])) { - throw new APIException(sprintf(elgg_echo('APIException:MethodCallNotImplemented'), $method)); - } - - // function must be callable - if (!(isset($API_METHODS[$method]["function"])) || !(is_callable($API_METHODS[$method]["function"]))) { - throw new APIException(sprintf(elgg_echo('APIException:FunctionDoesNotExist'), $method)); - } - - // check http call method - if (strcmp(get_call_method(), $API_METHODS[$method]["call_method"]) != 0) { - throw new CallException(sprintf(elgg_echo('CallException:InvalidCallMethod'), $method, $API_METHODS[$method]["call_method"])); - } - - $parameters = get_parameters_for_method($method); - - if (verify_parameters($method, $parameters) == false) { - // error - return false; - } - - $serialised_parameters = serialise_parameters($method, $parameters); - - // Execute function: Construct function and calling parameters - $function = $API_METHODS[$method]["function"]; - $serialised_parameters = trim($serialised_parameters, ", "); - - $result = eval("return $function($serialised_parameters);"); - - // Sanity check result - // If this function returns an api result itself, just return it - if ($result instanceof GenericResult) { - return $result; - } - - if ($result === false) { - throw new APIException(sprintf(elgg_echo('APIException:FunctionParseError'), $function, $serialised_parameters)); - } - - if ($result === NULL) { - // If no value - throw new APIException(sprintf(elgg_echo('APIException:FunctionNoReturn'), $function, $serialised_parameters)); - } - - // Otherwise assume that the call was successful and return it as a success object. - return SuccessResult::getInstance($result); -} - -/** - * Get the request method. - */ -function get_call_method() { - return $_SERVER['REQUEST_METHOD']; -} - -/** - * This function analyses all expected parameters for a given method - * - * This function sanitizes the input parameters and returns them in - * an associated array. - * - * @param string $method The method - * @return array containing parameters as key => value - */ -function get_parameters_for_method($method) { - global $API_METHODS; - - $sanitised = array(); - - // if there are parameters, sanitize them - if (isset($API_METHODS[$method]['parameters'])) { - foreach ($API_METHODS[$method]['parameters'] as $k => $v) { - $v = get_input($k); // Make things go through the sanitiser - if ($v !== '') { - $sanitised[$k] = $v; - } - } - } - - return $sanitised; -} - - -function get_post_data() { - global $GLOBALS; - - $postdata = ''; - if (isset($GLOBALS['HTTP_RAW_POST_DATA'])) - $postdata = $GLOBALS['HTTP_RAW_POST_DATA']; - - // Attempt another method to return post data (incase always_populate_raw_post_data is switched off) - if (!$postdata) { - $postdata = file_get_contents('php://input'); - } - - return $postdata; -} - -/** - * This fixes the post parameters that are munged due to page handler - */ -function include_post_data() { - - $postdata = get_post_data(); - - if (isset($postdata)) { - parse_str($postdata, $query_arr); - if (is_array($query_arr)) { - foreach($query_arr as $name => $val) { - set_input($name, $val); - } - } - } -} - -/** - * Verify that the required parameters are present - * @param $method - * @param $parameters - * @return true on success or exception - */ -function verify_parameters($method, $parameters) { - global $API_METHODS; - - // are there any parameters for this method - if (!(isset($API_METHODS[$method]["parameters"]))) { - return true; // no so return - } - - // check that the parameters were registered correctly and all required ones are there - foreach ($API_METHODS[$method]['parameters'] as $key => $value) { - // this tests the expose structure: must be array to describe parameter and type must be defined - if (!is_array($value) || !isset($value['type'])) { - throw new APIException(sprintf(elgg_echo('APIException:InvalidParameter'), $key, $method)); - } - - // Check that the variable is present in the request if required - if ($value['required'] && !array_key_exists($key, $parameters)) { - throw new APIException(sprintf(elgg_echo('APIException:MissingParameterInMethod'), $key, $method)); - } - } - - return true; -} - -/** - * Serialize an array of parameters for an API method call - * - * @param $method - * @param $parameters - * @return unknown_type - */ -function serialise_parameters($method, $parameters) { - global $API_METHODS; - - // are there any parameters for this method - if (!(isset($API_METHODS[$method]["parameters"]))) { - return ''; // if not, return - } - - $serialised_parameters = ""; - foreach ($API_METHODS[$method]['parameters'] as $key => $value) { - - // avoid warning on parameters that are not required and not present - if (!isset($parameters[$key])) { - continue; - } - - // Set variables casting to type. - switch (strtolower($value['type'])) - { - case 'int': - case 'integer' : - $serialised_parameters .= "," . (int)trim($parameters[$key]); - break; - case 'bool': - case 'boolean': - // change word false to boolean false - if (strcasecmp(trim($parameters[$key]), "false") == 0) { - $serialised_parameters .= ',false'; - } else if ($parameters[$key] == 0) { - $serialised_parameters .= ',false'; - } else { - $serialised_parameters .= ',true'; - } - - 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': - // we can handle an array of strings, maybe ints, definitely not booleans or other arrays - if (!is_array($parameters[$key])) - { - throw APIException(sprintf(elgg_echo('APIException:ParameterNotArray'), $key)); - } - - $array = "array("; - - foreach ($parameters[$key] as $k => $v) - { - $k = sanitise_string($k); - $v = sanitise_string($v); - - $array .= "'$k'=>'$v',"; - } - - $array = trim($array,","); - - $array .= ")"; - - $serialised_parameters .= $array; - break; - default: - throw new APIException(sprintf(elgg_echo('APIException:UnrecognisedTypeCast'), $value['type'], $key, $method)); - } - } - - return $serialised_parameters; -} - -// API authorization handlers ///////////////////////////////////////////////////////////////////// - -/** - * Confirm that the call includes a valid API key - * @return true if good API key - otherwise throws exception - */ -function api_auth_key() { - global $CONFIG; - - // check that an API key is present - $api_key = get_input('api_key'); - if ($api_key == "") { - throw new APIException(elgg_echo('APIException:MissingAPIKey')); - } - - // check that it is active - $api_user = get_api_user($CONFIG->site_id, $api_key); - if (!$api_user) { - // key is not active or does not exist - throw new APIException(elgg_echo('APIException:BadAPIKey')); - } - - // can be used for keeping stats - // plugin can also return false to fail this authentication method - return trigger_plugin_hook('api_key', 'use', $api_key, true); -} - - -/** - * - * @return true if success - otherwise throws exception - */ -function api_auth_hmac() { - global $CONFIG; - - // Get api header - $api_header = get_and_validate_api_headers(); - - // Pull API user details - $api_user = get_api_user($CONFIG->site_id, $api_header->api_key); - - if (!$api_user) { - throw new SecurityException(elgg_echo('SecurityException:InvalidAPIKey'), ErrorResult::$RESULT_FAIL_APIKEY_INVALID); - } - - // Get the secret key - $secret_key = $api_user->secret; - - // get the query string - $query = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], '?') + 1); - - // calculate expected HMAC - $hmac = calculate_hmac( $api_header->hmac_algo, - $api_header->time, - $api_header->api_key, - $secret_key, - $query, - $api_header->method == 'POST' ? $api_header->posthash : ""); - - - if ($api_header->hmac !== $hmac) { - throw new SecurityException("HMAC is invalid. {$api_header->hmac} != [calc]$hmac"); - } - - // Now make sure this is not a replay - if (cache_hmac_check_replay($hmac)) { - throw new SecurityException(elgg_echo('SecurityException:DupePacket')); - } - - // 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)); - } - } - - return true; -} - -// HMAC ///////////////////////////////////////////////////////////////////// - -/** - * 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 = get_call_method(); - // Only allow these methods - if (($result->method != "GET") && ($result->method != "POST")) { - 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')); - } - - // must have been sent in the last 10 minutes - if (($result->time<(time()-600)) || ($result->time>(time()+600))) { - throw new APIException(elgg_echo('APIException:TemporalDrift')); - } - - 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; -} - -/** - * 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 http request. - * This function signs an api request using the information provided. The signature returned - * has been base64 encoded and then url encoded. - * - * @param $algo string The HMAC algorithm used - * @param $time string String representation of unix time - * @param $api_key string Your api key - * @param $secret string Your private key - * @param $get_variables string URLEncoded string representation of the get variable parameters, eg "method=user&guid=2" - * @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 = "") { - global $CONFIG; - - elgg_log("HMAC Parts: $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 urlencode(base64_encode(hash_final($ctx, true))); -} - -/** - * 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. - * - * @param $hmac The hmac string. - * @return bool True if replay detected, false if not. - */ -function cache_hmac_check_replay($hmac) { - // cache lifetime is 25 hours (see time window in get_and_validate_api_headers() ) - $cache = new ElggHMACCache(90000); - - if (!$cache->load($hmac)) { - $cache->save($hmac, $hmac); - - return false; - } - - 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) - */ -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; - - return get_data_row("SELECT * from {$CONFIG->dbprefix}api_users where api_key='$api_key' and site_guid=$site_guid and active=1"); -} - -/** - * Revoke an api user key. - * - * @param int $site_guid The GUID of the site. - * @param string $api_key The API Key (public). - */ -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 - * it is present and is valid. The user gets logged in so with the current - * session code of Elgg, that user will be logged out of all other sessions. - * - * @param unknown_type $credentials - * @return bool - */ -function pam_auth_usertoken($credentials = NULL) { - global $CONFIG; - - $token = get_input('auth_token'); - if (!$token) { - return false; - } - - $validated_userid = validate_user_token($token, $CONFIG->site_id); - - if ($validated_userid) { - $u = get_entity($validated_userid); - - // Could we get the user? - if (!$u) { - return false; - } - - // Not an elgg user - if ( (!$u instanceof ElggUser)) { - return false; - } - - // User is banned - if ($u->isBanned()) { - return false; - } - - // Fail if we couldn't log the user in - if (!login($u)) { - return false; - } - - return true; - } - - return false; -} - -/** - * See if the user has a valid login sesson. - */ -function pam_auth_session($credentials = NULL) { - return isloggedin(); -} - -/** - * Obtain a token for a user. - * - * @param string $username The username - * @param int $expire minutes until token expires (default is 60 minutes) - */ -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; -} - -/** - * 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 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 - * @param int $site_guid The ID of the site (default is current site) - * @return bool - */ -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 - */ -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 $headers array The array of headers "key" => "value" - * @return string - */ -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 stdClass The unserialised response object - */ -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 = sprintf(elgg_echo('NotImplementedException:CallMethodNotImplemented'), $method); - throw new NotImplementedException($msg); - } - - // Time - $time = time(); - - // 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-hmac-algo'] = 'sha1'; - $headers['X-Elgg-hmac'] = calculate_hmac('sha1', - $time, - $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 stdClass The unserialised response object - */ -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 stdClass The unserialised response object - */ -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 - */ -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. - */ -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 - * in leu of a username and password login from then on. - * - * @param string username Username - * @param string password Clear text password - */ -function auth_gettoken($username, $password) { - if (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(); - -/** - * 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); - - $code = $exception->getCode() == 0 ? ErrorResult::$RESULT_FAIL : $exception->getCode(); - $result = new ErrorResult($exception->getMessage(), $code, NULL); - - page_draw($exception->getMessage(), elgg_view("api/output", array("result" => $result))); -} - -// Initialisation ///////////////////////////////////////////////////////////// - -/** - * Register a page handler for the various API endpoints. - * - * @param array $page - */ -function api_endpoint_handler($page) { - global $CONFIG; - - // Which view - if ($page[1]) { - elgg_set_viewtype($page[1]); - } - - // Which endpoint - if ($page[0]) { - switch ($page[0]) { - case 'rest' : - default : include($CONFIG->path . "services/api/rest.php"); - } - } -} - -/** - * Unit tests for API - */ -function api_unit_test($hook, $type, $value, $params) { - global $CONFIG; - $value[] = $CONFIG->path . 'engine/tests/services/api.php'; - return $value; -} - -/** - * Initialise the API subsystem. - * - */ -function api_init() { - // Register a page handler, so we can have nice URLs - register_page_handler('api','api_endpoint_handler'); - - register_plugin_hook('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); -} - - -register_elgg_event_handler('init','system','api_init'); |
