From 0656dbc5acffa5e05b228428d82ad3b2d3cbcbe8 Mon Sep 17 00:00:00 2001 From: cash Date: Sun, 25 Oct 2009 21:37:21 +0000 Subject: cleaned up the web services unit tests git-svn-id: http://code.elgg.org/elgg/trunk@3581 36083f99-b078-4883-b0ff-0f9b5a30f544 --- engine/lib/api.php | 133 ++++++++++++++++++--------- engine/tests/services/api.php | 202 ++++++++++++++++++++++++++++++++---------- languages/en.php | 3 +- 3 files changed, 245 insertions(+), 93 deletions(-) diff --git a/engine/lib/api.php b/engine/lib/api.php index 63826cf74..38b93cd3a 100644 --- a/engine/lib/api.php +++ b/engine/lib/api.php @@ -348,8 +348,9 @@ function expose_function($method, $function, array $parameters = NULL, $descript } if ($parameters != NULL) { - // ensure the required flag is set correctly in default case + // 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; } @@ -627,14 +628,13 @@ function verify_parameters($method, $parameters) { // check that the parameters were registered correctly and all required ones are there foreach ($API_METHODS[$method]['parameters'] as $key => $value) { - // must be array to describe parameter in expose and type must be defined + // 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 - $is_param_required = !isset($value['required']) || $value['required']; - if ($is_param_required && !array_key_exists($key, $parameters)) { + if ($value['required'] && !array_key_exists($key, $parameters)) { throw new APIException(sprintf(elgg_echo('APIException:MissingParameterInMethod'), $key, $method)); } } @@ -739,9 +739,12 @@ function api_auth_key() { // check that it is active $api_user = get_api_user($CONFIG->site_id, $api_key); if (!$api_user) { - throw new APIException(elgg_echo('APIException:MissingAPIKey')); + // 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); } @@ -956,6 +959,34 @@ function cache_hmac_check_replay($hmac) { // 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. * @@ -989,29 +1020,6 @@ function remove_api_user($site_guid, $api_key) { 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); - } - - return false; -} // User Authorization functions //////////////////////////////////////////////////////////////// @@ -1029,7 +1037,7 @@ function pam_auth_usertoken($credentials = NULL) { $token = get_input('auth_token'); - $validated_userid = validate_user_token($CONFIG->site_id, $token); + $validated_userid = validate_user_token($token); if ($validated_userid) { $u = get_entity($validated_userid); @@ -1071,16 +1079,16 @@ function pam_auth_session($credentials = NULL) { * Obtain a token for a user. * * @param string $username The username - * @param string $password The password + * @param int $expire minutes until token expires (default is 60 minutes) */ -function obtain_user_token($username, $password) { +function create_user_token($username, $expire = 60) { global $CONFIG; - $site = $CONFIG->site_id; + $site_guid = $CONFIG->site_id; $user = get_user_by_username($username); $time = time(); - $time += 60*60; // token is good for one hour - $token = md5(rand(). microtime() . $username . $password . $time . $site); + $time += 60 * $expire; + $token = md5(rand(). microtime() . $username . $time . $site_guid); if (!$user) { return false; @@ -1088,7 +1096,7 @@ function obtain_user_token($username, $password) { 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'")) { + ({$user->guid}, $site_guid, '$token', '$time') on duplicate key update token='$token', expires='$time'")) { return $token; } @@ -1101,24 +1109,24 @@ function obtain_user_token($username, $password) { * 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. + * @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($site, $token) { +function validate_user_token($token, $site_guid) { global $CONFIG; - $site = (int)$site; - $token = sanitise_string($token); - - if (!$site) { - throw new ConfigurationException(elgg_echo('ConfigurationException:NoSiteID')); + 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 and $time < expires"); + where token='$token' and site_guid=$site_guid and $time < expires"); if ($user) { return $user->user_guid; @@ -1127,6 +1135,43 @@ function validate_user_token($site, $token) { 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 /////////////////////////////////////////////////////////////////// /** @@ -1298,7 +1343,7 @@ function list_all_apis() { */ function auth_gettoken($username, $password) { if (authenticate($username, $password)) { - $token = obtain_user_token($username, $password); + $token = create_user_token($username); if ($token) { return $token; } diff --git a/engine/tests/services/api.php b/engine/tests/services/api.php index cad28a452..66fff268d 100644 --- a/engine/tests/services/api.php +++ b/engine/tests/services/api.php @@ -20,34 +20,67 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { // expose_function public function testExposeFunctionNoMethod() { - - $this->expectException('InvalidParameterException'); - expose_function(); + try { + expose_function(); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'InvalidParameterException'); + $this->assertIdentical($e->getMessage(), elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet')); + } } public function testExposeFunctionNoFunction() { - $this->expectException('InvalidParameterException'); - expose_function('test'); + try { + expose_function('test'); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'InvalidParameterException'); + $this->assertIdentical($e->getMessage(), elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet')); + } } public function testExposeFunctionBadParameters() { - $this->expectException('InvalidParameterException'); - expose_function('test', 'test', 'BAD'); + try { + expose_function('test', 'test', 'BAD'); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'InvalidParameterException'); + $this->assertIdentical($e->getMessage(), sprintf(elgg_echo('InvalidParameterException:APIParametersArrayStructure'), 'test')); + } } - public function testExposeFunctionParametersNotArray() { - $this->expectException('InvalidParameterException'); - expose_function('test', 'test', array('param1' => 'string')); + public function testExposeFunctionParametersBadArray() { + try { + expose_function('test', 'test', array('param1' => 'string')); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'InvalidParameterException'); + $this->assertIdentical($e->getMessage(), sprintf(elgg_echo('InvalidParameterException:APIParametersArrayStructure'), 'test')); + } } public function testExposeFunctionBadHttpMethod() { - $this->expectException('InvalidParameterException'); - expose_function('test', 'test', null, '', 'BAD'); + try { + expose_function('test', 'test', null, '', 'BAD'); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'InvalidParameterException'); + $this->assertIdentical($e->getMessage(), sprintf(elgg_echo('InvalidParameterException:UnrecognisedHttpMethod'), 'BAD', 'test')); + } } public function testExposeFunctionSuccess() { global $API_METHODS; - $parameters = array('param1' => array('type' => 'int', 'required' => true)); + // this is a general test but also tests specifically for setting 'required' correctly + $parameters = array('param1' => array('type' => 'int', 'required' => true), + 'param2' => array('type' => 'bool'), + 'param3' => array('type' => 'string', 'required' => false), ); + + $this->assertTrue(expose_function('test', 'foo', $parameters)); + + $parameters = array('param1' => array('type' => 'int', 'required' => true), + 'param2' => array('type' => 'bool', 'required' => true), + 'param3' => array('type' => 'string', 'required' => false), ); $method['function'] = 'foo'; $method['parameters'] = $parameters; $method['call_method'] = 'GET'; @@ -55,7 +88,6 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { $method['require_api_auth'] = false; $method['require_user_auth'] = false; - $this->assertTrue(expose_function('test', 'foo', $parameters)); $this->assertIdentical($method, $API_METHODS['test']); } @@ -70,26 +102,36 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { } // authenticate_method - public function testApiMethodNotImplemented() { - global $CONFIG; - - $results = send_api_get_call($CONFIG->wwwroot . 'pg/api/rest/json/', array('method' => 'bad.method')); - $obj = json_decode($results); - $this->assertIdentical(sprintf(elgg_echo('APIException:MethodCallNotImplemented'), 'bad.method'), $obj->api[0]->message); + public function testAuthenticateMethodNotImplemented() { + try { + authenticate_method('BAD'); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'APIException'); + $this->assertIdentical($e->getMessage(), sprintf(elgg_echo('APIException:MethodCallNotImplemented'), 'BAD')); + } } - - public function testAuthenticateForApi() { - $this->registerFunction(true, false); - - $this->expectException('APIException'); - authenticate_method('test'); + + public function testAuthenticateMethodApiAuth() { + $this->registerFunction(true); + try { + authenticate_method('test'); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'APIException'); + $this->assertIdentical($e->getMessage(), elgg_echo('APIException:APIAuthenticationFailed')); + } } - - public function testAuthenticateForUser() { + + public function testAuthenticateMethodUserAuth() { $this->registerFunction(false, true); - - $this->expectException('APIException'); - authenticate_method('test'); + try { + authenticate_method('test'); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'APIException'); + $this->assertIdentical($e->getMessage(), elgg_echo('APIException:UserAuthenticationFailed')); + } } public function testAuthenticateMethod() { @@ -98,27 +140,67 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { $this->assertTrue(authenticate_method('test')); } -// api_authenticate - public function testApiAuthenticate() { - $this->registerFunction(true, false); - - $this->assertFalse(api_authenticate()); - } - // execute_method + public function testExecuteMethodNotImplemented() { + try { + execute_method('BAD'); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'APIException'); + $this->assertIdentical($e->getMessage(), sprintf(elgg_echo('APIException:MethodCallNotImplemented'), 'BAD')); + } + } + public function testExecuteMethodNonCallable() { expose_function('test', 'foo'); - $this->expectException('ApiException'); - execute_method('test'); + try { + execute_method('test'); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'APIException'); + $this->assertIdentical($e->getMessage(), sprintf(elgg_echo('APIException:FunctionDoesNotExist'), 'test')); + } } public function testExecuteMethodWrongMethod() { $this->registerFunction(); - // get when it should be a post - $this->expectException('CallException'); - execute_method('test'); + try { + // GET when it should be a POST + execute_method('test'); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'CallException'); + $this->assertIdentical($e->getMessage(), sprintf(elgg_echo('CallException:InvalidCallMethod'), 'test', 'POST')); + } + } + +// verify parameters + public function testVerifyParametersTypeNotSet() { + $params = array('param1' => array('required' => true)); + expose_function('test', 'elgg_echo', $params); + + try { + verify_parameters('test', array()); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'APIException'); + $this->assertIdentical($e->getMessage(), sprintf(elgg_echo('APIException:InvalidParameter'), 'param1', 'test')); + } + } + + public function testVerifyParametersMissing() { + $params = array('param1' => array('type' => 'int', 'required' => true)); + expose_function('test', 'elgg_echo', $params); + + try { + verify_parameters('test', array()); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'APIException'); + $this->assertIdentical($e->getMessage(), sprintf(elgg_echo('APIException:MissingParameterInMethod'), 'param1', 'test')); + } } public function testVerifyParameters() { @@ -126,13 +208,9 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { $parameters = array('param1' => 0); $this->assertTrue(verify_parameters('test', $parameters)); - - $parameters = array('param2' => true); - $this->expectException('APIException'); - $this->assertTrue(verify_parameters('test', $parameters)); } - public function testserialise_parameters() { + public function testSerialiseParameters() { // int and bool $this->registerFunction(); @@ -177,6 +255,34 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { $s = serialise_parameters('test', $parameters); } +// api key methods + public function testApiAuthenticate() { + $this->assertFalse(api_authenticate()); + } + + public function testApiAuthKeyNoKey() { + try { + api_auth_key(); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'APIException'); + $this->assertIdentical($e->getMessage(), elgg_echo('APIException:MissingAPIKey')); + } + } + + public function testApiAuthKeyBadKey() { + global $CONFIG; + + $CONFIG->input['api_key'] = 'BAD'; + try { + api_auth_key(); + $this->assertTrue(FALSE); + } catch (Exception $e) { + $this->assertIsA($e, 'APIException'); + $this->assertIdentical($e->getMessage(), elgg_echo('APIException:BadAPIKey')); + } + } + protected function registerFunction($api_auth = false, $user_auth = false, $params = null) { $parameters = array('param1' => array('type' => 'int', 'required' => true), 'param2' => array('type' => 'bool', 'required' => false), ); diff --git a/languages/en.php b/languages/en.php index 78310b9f7..5c562431f 100644 --- a/languages/en.php +++ b/languages/en.php @@ -138,7 +138,8 @@ $english = array( 'APIException:AlgorithmNotSupported' => "Algorithm '%s' is not supported or has been disabled.", 'ConfigurationException:CacheDirNotSet' => "Cache directory 'cache_path' not set.", 'APIException:NotGetOrPost' => "Request method must be GET or POST", - 'APIException:MissingAPIKey' => "Missing X-Elgg-apikey HTTP header", + 'APIException:MissingAPIKey' => "Missing API key", + 'APIException:BadAPIKey' => "Bad API key", 'APIException:MissingHmac' => "Missing X-Elgg-hmac header", 'APIException:MissingHmacAlgo' => "Missing X-Elgg-hmac-algo header", 'APIException:MissingTime' => "Missing X-Elgg-time header", -- cgit v1.2.3