diff options
Diffstat (limited to 'engine/lib')
| -rw-r--r-- | engine/lib/api.php | 2298 | 
1 files changed, 1167 insertions, 1131 deletions
diff --git a/engine/lib/api.php b/engine/lib/api.php index 2ec36388b..53af1af67 100644 --- a/engine/lib/api.php +++ b/engine/lib/api.php @@ -1,1227 +1,1263 @@  <?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 {  	/** -	 * 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/ +	 * The status of the result. +	 * @var int  	 */ +	private $status_code; -	// Result classes ///////////////////////////////////////////////////////////////////////// +	/** +	 * 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;  	/** -	 * GenericResult Result superclass. -	 *  -	 * @author Curverider Ltd <info@elgg.com> -	 * @package Elgg -	 * @subpackage Core +	 * Result store. +	 * Attach result specific informaton here. +	 * +	 * @var mixed. Should probably be an object of some sort.  	 */ -	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; -			} +	private $result; -			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 +	 * Set a status code and optional message. +	 * +	 * @param int $status The status code. +	 * @param string $message The message.  	 */ -	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); -		} +	protected function setStatusCode($status, $message = "") { +		$this->status_code = $status; +		$this->message = $message;  	} -	 +  	/** -	 * ErrorResult -	 * The error result class. -	 *  -	 * @author Curverider Ltd <info@elgg.com> -	 * @package Elgg -	 * @subpackage Core +	 * Set the result. +	 * +	 * @param mixed $result  	 */ -	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 = 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); -		} +	protected function setResult($result) { +		$this->result = $result; +	} + +	protected function getStatusCode() { +		return $this->status_code; +	} + +	protected function getStatusMessage() { +		return $this->message;  	} -	 -	// Caching of HMACs ///////////////////////////////////////////////////////////////////////	 -	 + +	protected function getResult() { +		return $this->result; +	} +  	/** -	 * ElggHMACCache -	 * Store cached data in a temporary database, only used by the HMAC stuff. -	 *  -	 * @author Curverider Ltd <info@elgg.com> -	 * @package Elgg -	 * @subpackage API +	 * 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.  	 */ -	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); +	public function export() { +		global $ERRORS, $CONFIG, $_PAM_HANDLERS_MSG; + +		$result = new stdClass; + +		$result->status = $this->getStatusCode(); +		if ($this->getStatusMessage()!="") { +			$result->message = $this->getStatusMessage();  		} -	 -		/** -		 * 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')"); + +		$resultdata = $this->getResult(); +		if (isset($resultdata)) { +			$result->result = $resultdata;  		} -		 -		/** -		 * 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; + +		if ((isset($CONFIG->debug)) && ($CONFIG->debug == true)) { +			if (count($ERRORS)) { +				$result->runtime_errors = $ERRORS; +			} + +			if (count($_PAM_HANDLERS_MSG)) { +				$result->pam = $_PAM_HANDLERS_MSG; +			}  		} -		 -		/** -		 * 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'"); + +		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;  		} -		 -		/** -		 * 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"); + +		if ($exception!=NULL) { +			$this->setResult($exception->__toString());  		} + +		$this->setStatusCode($code, $message);  	} -	// 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_token" => true (default) | false -	 * 			"description" => "Some human readable description" -	 * 		) -	 *  ) -	 */ -	$METHODS = array(); -	  	/** -	 * Get the request method. +	 * Get a new instance of the ErrorResult. +	 * +	 * @param string $message +	 * @param int $code +	 * @param Exception $exception Optional exception for generating a stack trace.  	 */ -	function get_call_method() -	{ -		return $_SERVER['REQUEST_METHOD']; +	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 {  	/** -	 * This function analyses all expected parameters for a given method, returning them in an associated array from -	 * input.  -	 *  -	 * This ensures that they are sanitised and that no superfluous commands are registered. It also means that  -	 * hmacs work through the page handler. +	 * Set the Elgg cache.  	 * -	 * @param string $method The method -	 * @return Array containing commands and values, including method and api +	 * @param int $max_age Maximum age in seconds, 0 if no limit.  	 */ -	function get_parameters_for_method($method) -	{ -		global $CONFIG, $METHODS; - -		$method = sanitise_string($method); -		$sanitised = array(); -		 -		foreach ($CONFIG->input as $k => $v) -		{ -			if ((isset($METHODS[$method]['parameters'][$k])) || ($k == 'auth_token') || ($k == 'method')) -				$sanitised[$k] = get_input($k); // Make things go through the sanitiser	 -		} -	 -		return $sanitised; +	function __construct($max_age = 0) { +		$this->set_variable("max_age", $max_age);  	} -	 +  	/** -	 * Obtain a token for a user. +	 * Save a key  	 * -	 * @param string $username The username -	 * @param string $password The password +	 * @param string $key +	 * @param string $data +	 * @return boolean  	 */ -	function obtain_user_token($username, $password) -	{ +	public function save($key, $data) {  		global $CONFIG; -		 -		$site = $CONFIG->site_id; -		$user = get_user_by_username($username); + +		$key = sanitise_string($key);  		$time = time(); -		$time += 60*60; -		$token = md5(rand(). microtime() . $username . $password . $time . $site); -		 -		if (!$user) return false;  -		 -		if (insert_data("INSERT into {$CONFIG->dbprefix}users_apisessions (user_guid, site_guid, token, expires) values ({$user->guid}, $site, '$token', '$time') on duplicate key update token='$token', expires='$time'")) -			return $token; -			 -		return false; + +		return insert_data("INSERT into {$CONFIG->dbprefix}hmac_cache (hmac, ts) VALUES ('$key', '$time')");  	} -	 +  	/** -	 * 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. +	 * Load a key +	 * +	 * @param string $key +	 * @param int $offset +	 * @param int $limit +	 * @return string  	 */ -	function validate_user_token($site, $token) -	{ +	public function load($key, $offset = 0, $limit = null) {  		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 $time < expires"); -		if ($user) -			return $user->user_guid; -		 + +		$key = sanitise_string($key); + +		$row = get_data_row("SELECT * from {$CONFIG->dbprefix}hmac_cache where hmac='$key'"); +		if ($row) { +			return $row->hmac; +		} +  		return false;  	} -	 +  	/** -	 * Expose an arbitrary function as an api call. -	 *  -	 * Limitations: Currently can not expose functions which expect objects. -	 *  -	 * @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 $description Optional human readable description of the function. -	 * @param string $call_method Define what call method should be used for this function. -	 * @param bool $require_auth_token Whether this requires a user authentication token or not (default is true). -	 * @param bool $anonymous Can anonymous (non-authenticated in any way) users execute this call.  +	 * Invalidate a given key. +	 * +	 * @param string $key  	 * @return bool  	 */ -	function expose_function($method, $function, array $parameters = NULL, $description = "", $call_method = "GET", $require_auth_token = true, $anonymous = false) -	{ -		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_token"] = $require_auth_token; -			 -			$METHODS[$method]["anonymous"] = $anonymous; -			 -			return true; -		} -		 -		return false;	 +	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; +	} +  	/** -	 * Executes a method. -	 * A method is a function which you have previously exposed using expose_function. +	 * Clean out old stuff.  	 * -	 * @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 = ""; -				 -				 -				// 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)) // Or not optional -							) +	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"); +	} +} + +// 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_token" => true (default) | false + * 			"description" => "Some human readable description" + * 		) + *  ) + */ +$METHODS = array(); + +/** + * Get the request method. + */ +function get_call_method() { +	return $_SERVER['REQUEST_METHOD']; +} + +/** + * This function analyses all expected parameters for a given method, returning them in an associated array from + * input. + * + * This ensures that they are sanitised and that no superfluous commands are registered. It also means that + * hmacs work through the page handler. + * + * @param string $method The method + * @return Array containing commands and values, including method and api + */ +function get_parameters_for_method($method) { +	global $CONFIG, $METHODS; + +	$method = sanitise_string($method); +	$sanitised = array(); + +	foreach ($CONFIG->input as $k => $v) { +		if ((isset($METHODS[$method]['parameters'][$k])) || ($k == 'auth_token') || ($k == 'method')) { +			// Make things go through the sanitiser +			$sanitised[$k] = get_input($k); +		} +	} + +	return $sanitised; +} + +/** + * Obtain a token for a user. + * + * @param string $username The username + * @param string $password The password + */ +function obtain_user_token($username, $password) { +	global $CONFIG; + +	$site = $CONFIG->site_id; +	$user = get_user_by_username($username); +	$time = time(); +	$time += 60*60; +	$token = md5(rand(). microtime() . $username . $password . $time . $site); + +	if (!$user) { +		return false; +	} + +	if (insert_data("INSERT into {$CONFIG->dbprefix}users_apisessions +	(user_guid, site_guid, token, expires) values +	({$user->guid}, $site, '$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 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 $time < expires"); + +	if ($user) { +		return $user->user_guid; +	} + +	return false; +} + +/** + * Expose an arbitrary function as an api call. + * + * Limitations: Currently can not expose functions which expect objects. + * + * @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 $description Optional human readable description of the function. + * @param string $call_method Define what call method should be used for this function. + * @param bool $require_auth_token Whether this requires a user authentication token or not (default is true). + * @param bool $anonymous Can anonymous (non-authenticated in any way) users execute this call. + * @return bool + */ +function expose_function($method, $function, array $parameters = NULL, $description = "", $call_method = "GET", $require_auth_token = true, $anonymous = false) { +	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_token"] = $require_auth_token; + +		$METHODS[$method]["anonymous"] = $anonymous; + +		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 = ""; + +			// 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)) && (isset($value['type']))) { +						// Check that the variable is present in the request +						if ((!isset($parameters[$key])) +						|| ((!isset($value['required'])) +						|| ($value['required']==true))) {  								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 { +							// 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)); +					} 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 CallException(sprintf(elgg_echo('CallException:InvalidCallMethod'), $method, $METHODS[$method]["call_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 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); + +		} 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, elgg_echo("system.api.list"), "GET", false); -	/** -	 * 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 = obtain_user_token($username, $password); -			if ($token) -				return $token; +	// 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, elgg_echo("system.api.list"), "GET", false); + +/** + * 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 = obtain_user_token($username, $password); +		if ($token) { +			return $token;  		} -		 -		throw new SecurityException(elgg_echo('SecurityException:authenticationfailed'));  	} -	 -	// The authentication token api -	expose_function("auth.gettoken", "auth_gettoken", array( -		"username" => array ( -  			'type' => 'string' -  		), -  		"password" => array ( -  			'type' => 'string' -		) -	), elgg_echo('auth.gettoken'), "GET", false, false); -	 -	 -	// 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)); + +	throw new SecurityException(elgg_echo('SecurityException:authenticationfailed')); +} + +// The authentication token api +expose_function("auth.gettoken", "auth_gettoken", array( +	"username" => array ( +		'type' => 'string' +	), +	"password" => array ( +		'type' => 'string' +	) +), elgg_echo('auth.gettoken'), "GET", false, false); + + +// 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];  	} -	 -	/** -	 * 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 = "") -	{ -		global $CONFIG; -		 -		if ((isset($CONFIG)) && ($CONFIG->debug)) -			error_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 hash_final($ctx); + +	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 = "") { +	global $CONFIG; + +	if ((isset($CONFIG)) && ($CONFIG->debug)) { +		error_log("HMAC Parts: $algo, $time, $api_key, $secret_key, $get_variables, $post_hash");  	} -	/** -	 * 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)); +	$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); -		hash_update($ctx, $postdata); +	return hash_final($ctx); +} -		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;  	} -	 -	/** -	 * 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 = new ElggHMACCache(90000); // cache lifetime is 25 hours (see time window in get_and_validate_api_headers() ) -	 -		if (!$cache->load($hmac)) -		{	 -			$cache->save($hmac, $hmac); -		 -			return false; -		} -				 -		return true; + +	return true; +} + +/** + * 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}");  	} -	 -	/** -	 * 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");	 + +	return false; +} + +/** + * Generate a new API user for a site, returning a new keypair on success. + * + * @param int $site_guid The GUID of the site. + */ +function create_api_user($site_guid) { +	global $CONFIG; + +	$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);  	} -	 -	/** -	 * 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; + +	return false; +} + +/** + * 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'));  	} -	 -	/** -	 * Generate a new API user for a site, returning a new keypair on success. -	 * -	 * @param int $site_guid The GUID of the site. -	 */ -	function create_api_user($site_guid) -	{ -		global $CONFIG; -		 -		$site_guid = (int)$site_guid; -		 -		$public = sha1(rand().$site_guid.microtime()); -		$secret = sha1(rand().$site_guid.microtime().$public); -		 -		if (insert_data("INSERT into {$CONFIG->dbprefix}api_users (site_guid, api_key, secret) values ($site_guid, '$public', '$secret')")) -			return get_api_user($site_guid, $public); -			 -		return false; + +	$result->api_key = $_SERVER['HTTP_X_ELGG_APIKEY']; +	if ($result->api_key == "") { +		throw new APIException(elgg_echo('APIException:MissingAPIKey'));  	} -	 -	/** -	 * 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();  -		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 = get_parameters_for_method(get_input('method')); //$_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; + +	$result->hmac = $_SERVER['HTTP_X_ELGG_HMAC']; +	if ($result->hmac == "") { +		throw new APIException(elgg_echo('APIException:MissingHmac'));  	} -	 -	/** -	 * Return a sanitised form of the POST data sent to the script -	 * -	 * @return string -	 */ -	function get_post_data() -	{ -		global $GLOBALS; -		 -		$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'); + +	$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')); +	} + +	// Basic timecheck, think about making this smaller if we get loads of users and the cache gets really big. +	if (($result->time<(microtime(true)-86400.00)) || ($result->time>(microtime(true)+86400.00))) { +		throw new APIException(elgg_echo('APIException:TemporalDrift')); +	} + +	//$_SERVER['QUERY_STRING']; +	$result->get_variables = get_parameters_for_method(get_input('method')); +	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 $postdata;  	} -	 -	// PAM functions ////////////////////////////////////////////////////////////////////////// -	/** -	 * Function that examines whether an authentication token is present returning true if it is, OR the requested  -	 * method doesn't require one. -	 *  -	 * If a token is present and a validated user id is returned, that user is logged in to the current session. -	 * -	 * @param unknown_type $credentials -	 */ -	function pam_auth_usertoken($credentials = NULL) -	{ -		global $METHODS, $CONFIG; -		 -		$method = get_input('method'); -		$token = get_input('auth_token'); -		 -		$validated_userid = validate_user_token($CONFIG->site_id, $token);  -		 -		if ($validated_userid) {			 -			$u = get_entity($validated_userid); -			if (!$u) return false; // Could we get the user? -			if ( (!$u instanceof ElggUser)) return false; // Not an elgg user -			if ($u->isBanned()) return false; // User is banned -			if (!login($u)) return false; // Fail if we couldn't log the user in  -			 +	return $result; +} + +/** + * Return a sanitised form of the POST data sent to the script + * + * @return string + */ +function get_post_data() { +	global $GLOBALS; + +	$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; +} + +// PAM functions ////////////////////////////////////////////////////////////////////////// + +/** + * Function that examines whether an authentication token is present returning true if it is, OR the requested + * method doesn't require one. + * + * If a token is present and a validated user id is returned, that user is logged in to the current session. + * + * @param unknown_type $credentials + */ +function pam_auth_usertoken($credentials = NULL) { +	global $METHODS, $CONFIG; + +	$method = get_input('method'); +	$token = get_input('auth_token'); + +	$validated_userid = validate_user_token($CONFIG->site_id, $token); + +	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;  		} -		 -		if ((!$METHODS[$method]["require_auth_token"]) || ($validated_userid) || (isloggedin())) { -			return true; -		} else -			throw new SecurityException(elgg_echo('SecurityException:AuthTokenExpired'), ErrorResult::$RESULT_FAIL_AUTHTOKEN); -		 -		return false;  	} -	 -	/** -	 * Test to see whether a given function has been declared as anonymous access (it doesn't require any auth token) -	 * -	 * @param unknown_type $credentials -	 */ -	function pam_auth_anonymous_method($credentials = NULL) -	{ -		global $METHODS, $CONFIG; -		 -		$method = get_input('method'); -		 -		if ((isset($METHODS[$method]["anonymous"])) && ($METHODS[$method]["anonymous"])) -			return true; -		 -		return false; + +	if ((!$METHODS[$method]["require_auth_token"]) || ($validated_userid) || (isloggedin())) { +		return true; +	} else { +		throw new SecurityException(elgg_echo('SecurityException:AuthTokenExpired'), ErrorResult::$RESULT_FAIL_AUTHTOKEN);  	} -	 -	/** -	 * See if the user has a valid login sesson. -	 */ -	function pam_auth_session($credentials = NULL) -	{ -		return isloggedin(); + +	return false; +} + +/** + * Test to see whether a given function has been declared as anonymous access (it doesn't require any auth token) + * + * @param unknown_type $credentials + */ +function pam_auth_anonymous_method($credentials = NULL) { +	global $METHODS, $CONFIG; + +	$method = get_input('method'); + +	if ((isset($METHODS[$method]["anonymous"])) && ($METHODS[$method]["anonymous"])) { +		return true;  	} -	 -	/** -	 * 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; -		 -			// Serialise parameters -			$encoded_params = array(); -			foreach ($api_header->get_variables as $k => $v) -				$encoded_params[] = urlencode($k).'='.urlencode($v); -			$params = implode('&', $encoded_params);		 -			 -			// Validate HMAC -			$hmac = calculate_hmac($api_header->hmac_algo,  -					$api_header->time,  -					$api_header->api_key,  -					$secret_key,  -					$params,  -					$api_header->method == 'POST' ? $api_header->posthash : ""); -		 -			if ((strcmp( -				$api_header->hmac, -				$hmac	 -			)==0) && ($api_header->hmac) && ($hmac)) -			{ -				// 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)); + +	return false; +} + +/** + * 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; + +	// 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) { +		// Get the secret key +		$secret_key = $api_user->secret; + +		// Serialise parameters +		$encoded_params = array(); +		foreach ($api_header->get_variables as $k => $v) { +			$encoded_params[] = urlencode($k).'='.urlencode($v); +		} +		$params = implode('&', $encoded_params); + +		// Validate HMAC +		$hmac = calculate_hmac($api_header->hmac_algo, +			$api_header->time, +			$api_header->api_key, +			$secret_key, +			$params, +			$api_header->method == 'POST' ? $api_header->posthash : ""); + +		if ((strcmp($api_header->hmac, $hmac) == 0) && ($api_header->hmac) && ($hmac)) { +			// 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')); + +				// 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:{$params}" . ($api_header->method=="POST"? "posthash:$api_header->posthash}" : ")")); +		} 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:{$params}" . ($api_header->method=="POST"? "posthash:$api_header->posthash}" : ")"));  		} -		else -			throw new SecurityException(elgg_echo('SecurityException:InvalidAPIKey'),ErrorResult::$RESULT_FAIL_APIKEY_INVALID); -			 -		return false; +	} else { +		throw new SecurityException(elgg_echo('SecurityException:InvalidAPIKey'),ErrorResult::$RESULT_FAIL_APIKEY_INVALID);  	} -	 -	/** -	 * A bit of a hack. Basically, this combines session and hmac, so that one of them must evaluate to true in order  -	 * to proceed. -	 *  -	 * This ensures that this and auth_token are evaluated separately. -	 * -	 * @param unknown_type $credentials -	 */ -	function pam_auth_session_or_hmac($credentials = NULL) -	{ -		if (pam_auth_session($credentials)) -			return true; -			 -		if (pam_auth_hmac($credentials)) -			return true; -			 -		return false; + +	return false; +} + +/** + * A bit of a hack. Basically, this combines session and hmac, so that one of them must evaluate to true in order + * to proceed. + * + * This ensures that this and auth_token are evaluated separately. + * + * @param unknown_type $credentials + */ +function pam_auth_session_or_hmac($credentials = NULL) { +	if (pam_auth_session($credentials)) { +		return true;  	} -	 -	// Client api functions /////////////////////////////////////////////////////////////////// -	 -	$APICLIENT_LAST_CALL = NULL;  -	$APICLIENT_LAST_CALL_RAW = "";  -	$APICLIENT_LAST_ERROR = NULL;  -	 -	/** -	 * 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"; +	if (pam_auth_hmac($credentials)) { +		return true; +	} + +	return false; +} + +// Client api functions /////////////////////////////////////////////////////////////////// -		return trim($headers_str);		 +$APICLIENT_LAST_CALL = NULL; +$APICLIENT_LAST_CALL_RAW = ""; +$APICLIENT_LAST_ERROR = NULL; + +/** + * 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";  	} -	 -	/** -	 * 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 $APICLIENT_LAST_CALL, $APICLIENT_LAST_CALL_RAW, $APICLIENT_LAST_ERROR, $CONFIG;  -		 -		$headers = array(); -		$encoded_params = array(); -		 -		$method = strtoupper($method); -		switch (strtoupper($method)) -		{ -			case 'GET' : -			case 'POST' :  break; -			default: throw new NotImplementedException(sprintf(elgg_echo('NotImplementedException:CallMethodNotImplemented'), $method)); -		} -		 -		// Time -		$time = microtime(true);  - -		// URL encode all the parameters, ensuring auth_token (if present) is at the end! -		foreach ($call as $k => $v){ -			if ($k!='auth_token') -				$encoded_params[] = urlencode($k).'='.urlencode($v); -		} -		if ($call['auth_token']) -			$encoded_params[] = urlencode('auth_token').'='.urlencode($call['auth_token']); -		$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. -		if ((isset($CONFIG->debug)) && ($CONFIG->debug)) -			error_log("APICALL: $url"); -		$APICLIENT_LAST_CALL_RAW = file_get_contents($url, false, $context); -	 -		$APICLIENT_LAST_CALL = unserialize($APICLIENT_LAST_CALL_RAW); -		 -		if (($APICLIENT_LAST_CALL) && ($APICLIENT_LAST_CALL->status!=0)) -			$APICLIENT_LAST_ERROR = $APICLIENT_LAST_CALL; -		 -		return $APICLIENT_LAST_CALL; +	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 $APICLIENT_LAST_CALL, $APICLIENT_LAST_CALL_RAW, $APICLIENT_LAST_ERROR, $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);  	} -	/** -	 * 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' => $api_key); } -	 -	// 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; +	// Time +	$time = microtime(true); + +	// URL encode all the parameters, ensuring auth_token (if present) is at the end! +	foreach ($call as $k => $v){ +		if ($k!='auth_token') { +			$encoded_params[] = urlencode($k).'='.urlencode($v);  		}  	} -	 -	/** -	 * 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) -			)) + +	if ($call['auth_token']) { +		$encoded_params[] = urlencode('auth_token').'='.urlencode($call['auth_token']); +	} + +	$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  		);  	} -	 -	// Initialisation & pagehandler /////////////////////////////////////////////////////////// -	 -	/** -	 * Initialise the API subsystem. -	 * -	 */ -	function api_init() -	{ -		// Register a page handler, so we can have nice URLs -		register_page_handler('api','api_endpoint_handler'); +	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);  	} -	 -	/** -	 * 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]); -			 + +	// 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. +	if ((isset($CONFIG->debug)) && ($CONFIG->debug)) { +		error_log("APICALL: $url"); +	} +	$APICLIENT_LAST_CALL_RAW = file_get_contents($url, false, $context); + +	$APICLIENT_LAST_CALL = unserialize($APICLIENT_LAST_CALL_RAW); + +	if (($APICLIENT_LAST_CALL) && ($APICLIENT_LAST_CALL->status!=0)) { +		$APICLIENT_LAST_ERROR = $APICLIENT_LAST_CALL; +	} + +	return $APICLIENT_LAST_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 + * @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' => $api_key); +} + +// 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) +		)) +	); +} + +// Initialisation & pagehandler /////////////////////////////////////////////////////////// + +/** + * 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 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");  		} -		 -		// Which endpoint -		if ($page[0]) -		{ -			switch ($page[0]) -			{ -				case 'rest' : -				default : include($CONFIG->path . "services/api/rest.php"); -			} -		}   	} -	 -	 -	register_elgg_event_handler('init','system','api_init'); -	 -?>
\ No newline at end of file +} + +register_elgg_event_handler('init','system','api_init');  | 
