Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Unit tests for the webservice component. 19 * 20 * @package core_webservice 21 * @category test 22 * @copyright 2016 Jun Pataleta <jun@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 namespace core_webservice; 26 27 use core_external\external_api; 28 use core_external\external_multiple_structure; 29 use core_external\external_single_structure; 30 use core_external\external_value; 31 use webservice; 32 33 defined('MOODLE_INTERNAL') || die(); 34 35 global $CFG; 36 require_once($CFG->dirroot . '/webservice/lib.php'); 37 38 /** 39 * Unit tests for the webservice component. 40 * 41 * @package core_webservice 42 * @category test 43 * @copyright 2016 Jun Pataleta <jun@moodle.com> 44 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 45 */ 46 class lib_test extends \advanced_testcase { 47 48 /** 49 * Setup. 50 */ 51 public function setUp(): void { 52 // Calling parent is good, always. 53 parent::setUp(); 54 55 // We always need enabled WS for this testcase. 56 set_config('enablewebservices', '1'); 57 } 58 59 /** 60 * Test init_service_class(). 61 */ 62 public function test_init_service_class() { 63 global $DB, $USER; 64 65 $this->resetAfterTest(true); 66 67 // Set current user. 68 $this->setAdminUser(); 69 70 // Add a web service. 71 $webservice = new \stdClass(); 72 $webservice->name = 'Test web service'; 73 $webservice->enabled = true; 74 $webservice->restrictedusers = false; 75 $webservice->component = 'moodle'; 76 $webservice->timecreated = time(); 77 $webservice->downloadfiles = true; 78 $webservice->uploadfiles = true; 79 $externalserviceid = $DB->insert_record('external_services', $webservice); 80 81 // Add token. 82 $externaltoken = new \stdClass(); 83 $externaltoken->token = 'testtoken'; 84 $externaltoken->tokentype = 0; 85 $externaltoken->userid = $USER->id; 86 $externaltoken->externalserviceid = $externalserviceid; 87 $externaltoken->contextid = 1; 88 $externaltoken->creatorid = $USER->id; 89 $externaltoken->timecreated = time(); 90 $externaltoken->name = \core_external\util::generate_token_name(); 91 $DB->insert_record('external_tokens', $externaltoken); 92 93 // Add a function to the service. 94 $wsmethod = new \stdClass(); 95 $wsmethod->externalserviceid = $externalserviceid; 96 $wsmethod->functionname = 'core_course_get_contents'; 97 $DB->insert_record('external_services_functions', $wsmethod); 98 99 // Initialise the dummy web service. 100 $dummy = new webservice_dummy(WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN); 101 // Set the token. 102 $dummy->set_token($externaltoken->token); 103 // Run the web service. 104 $dummy->run(); 105 // Get service methods and structs. 106 $servicemethods = $dummy->get_service_methods(); 107 $servicestructs = $dummy->get_service_structs(); 108 $this->assertNotEmpty($servicemethods); 109 // The function core_course_get_contents should be only the only web service function in the moment. 110 $this->assertEquals(1, count($servicemethods)); 111 // The function core_course_get_contents doesn't have a struct class, so the list of service structs should be empty. 112 $this->assertEmpty($servicestructs); 113 114 // Add other functions to the service. 115 // The function core_comment_get_comments has one struct class in its output. 116 $wsmethod->functionname = 'core_comment_get_comments'; 117 $DB->insert_record('external_services_functions', $wsmethod); 118 // The function core_grades_update_grades has one struct class in its input. 119 $wsmethod->functionname = 'core_grades_update_grades'; 120 $DB->insert_record('external_services_functions', $wsmethod); 121 122 // Run the web service again. 123 $dummy->run(); 124 // Get service methods and structs. 125 $servicemethods = $dummy->get_service_methods(); 126 $servicestructs = $dummy->get_service_structs(); 127 $this->assertEquals(3, count($servicemethods)); 128 $this->assertEquals(2, count($servicestructs)); 129 130 // Check the contents of service methods. 131 foreach ($servicemethods as $method) { 132 // Get the external function info. 133 $function = external_api::external_function_info($method->name); 134 135 // Check input params. 136 foreach ($function->parameters_desc->keys as $name => $keydesc) { 137 $this->check_params($method->inputparams[$name]['type'], $keydesc, $servicestructs); 138 } 139 140 // Check output params. 141 $this->check_params($method->outputparams['return']['type'], $function->returns_desc, $servicestructs); 142 143 // Check description. 144 $this->assertEquals($function->description, $method->description); 145 } 146 } 147 148 /** 149 * Tests update_token_lastaccess() function. 150 */ 151 public function test_update_token_lastaccess() { 152 global $DB, $USER; 153 154 $this->resetAfterTest(true); 155 156 // Set current user. 157 $this->setAdminUser(); 158 159 // Add a web service. 160 $webservice = new \stdClass(); 161 $webservice->name = 'Test web service'; 162 $webservice->enabled = true; 163 $webservice->restrictedusers = false; 164 $webservice->component = 'moodle'; 165 $webservice->timecreated = time(); 166 $webservice->downloadfiles = true; 167 $webservice->uploadfiles = true; 168 $DB->insert_record('external_services', $webservice); 169 170 // Add token. 171 $tokenstr = \core_external\util::generate_token( 172 EXTERNAL_TOKEN_EMBEDDED, 173 \core_external\util::get_service_by_name($webservice->name), 174 $USER->id, 175 \core\context\system::instance() 176 ); 177 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 178 179 // Trigger last access once (at current time). 180 webservice::update_token_lastaccess($token); 181 182 // Check last access. 183 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 184 $this->assertLessThan(5, abs(time() - $token->lastaccess)); 185 186 // Try setting it to +1 second. This should not update yet. 187 $before = (int)$token->lastaccess; 188 webservice::update_token_lastaccess($token, $before + 1); 189 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 190 $this->assertEquals($before, $token->lastaccess); 191 192 // To -1000 seconds. This should not update. 193 webservice::update_token_lastaccess($token, $before - 1000); 194 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 195 $this->assertEquals($before, $token->lastaccess); 196 197 // To +59 seconds. This should also not quite update. 198 webservice::update_token_lastaccess($token, $before + 59); 199 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 200 $this->assertEquals($before, $token->lastaccess); 201 202 // Finally to +60 seconds, where it should update. 203 webservice::update_token_lastaccess($token, $before + 60); 204 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 205 $this->assertEquals($before + 60, $token->lastaccess); 206 } 207 208 /** 209 * Tests for the {@see webservice::get_missing_capabilities_by_users()} implementation. 210 */ 211 public function test_get_missing_capabilities_by_users() { 212 global $DB; 213 214 $this->resetAfterTest(true); 215 $wsman = new webservice(); 216 217 $user1 = $this->getDataGenerator()->create_user(); 218 $user2 = $this->getDataGenerator()->create_user(); 219 $user3 = $this->getDataGenerator()->create_user(); 220 221 // Add a test web service. 222 $serviceid = $wsman->add_external_service((object)[ 223 'name' => 'Test web service', 224 'enabled' => 1, 225 'requiredcapability' => '', 226 'restrictedusers' => false, 227 'component' => 'moodle', 228 'downloadfiles' => false, 229 'uploadfiles' => false, 230 ]); 231 232 // Add a function to the service that does not declare any capability as required. 233 $wsman->add_external_function_to_service('core_webservice_get_site_info', $serviceid); 234 235 // Users can be provided as an array of objects, arrays or integers (ids). 236 $this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1, array($user2), $user3->id], $serviceid)); 237 238 // Add a function to the service that declares some capability as required, but that capability is common for 239 // any user. Here we use 'core_message_delete_conversation' which declares 'moodle/site:deleteownmessage' which 240 // in turn is granted to the authenticated user archetype by default. 241 $wsman->add_external_function_to_service('core_message_delete_conversation', $serviceid); 242 243 // So all three users should have this capability implicitly. 244 $this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1, $user2, $user3], $serviceid)); 245 246 // Add a function to the service that declares some non-common capability. Here we use 247 // 'core_group_add_group_members' that wants 'moodle/course:managegroups'. 248 $wsman->add_external_function_to_service('core_group_add_group_members', $serviceid); 249 250 // Make it so that the $user1 has the capability in some course. 251 $course1 = $this->getDataGenerator()->create_course(); 252 $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'editingteacher'); 253 254 // Check that no missing capability is reported for the $user1. We don't care at what actual context the 255 // external function call will evaluate the permission. We just check that there is a chance that the user has 256 // the capability somewhere. 257 $this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1], $serviceid)); 258 259 // But there is no place at the site where the capability would be granted to the other two users, so it is 260 // reported as missing. 261 $missing = $wsman->get_missing_capabilities_by_users([$user1, $user2, $user3], $serviceid); 262 $this->assertArrayNotHasKey($user1->id, $missing); 263 $this->assertContains('moodle/course:managegroups', $missing[$user2->id]); 264 $this->assertContains('moodle/course:managegroups', $missing[$user3->id]); 265 } 266 267 /** 268 * Data provider for {@see test_get_active_tokens} 269 * 270 * @return array 271 */ 272 public function get_active_tokens_provider(): array { 273 return [ 274 'No expiration' => [0, true], 275 'Active' => [time() + DAYSECS, true], 276 'Expired' => [time() - DAYSECS, false], 277 ]; 278 } 279 280 /** 281 * Test getting active tokens for a user 282 * 283 * @param int $validuntil 284 * @param bool $expectedactive 285 * 286 * @dataProvider get_active_tokens_provider 287 */ 288 public function test_get_active_tokens(int $validuntil, bool $expectedactive): void { 289 $this->resetAfterTest(); 290 291 $user = $this->getDataGenerator()->create_user(); 292 293 /** @var \core_webservice_generator $generator */ 294 $generator = $this->getDataGenerator()->get_plugin_generator('core_webservice'); 295 296 $service = $generator->create_service(['name' => 'My test service', 'shortname' => 'mytestservice']); 297 $generator->create_token(['userid' => $user->id, 'service' => $service->shortname, 'validuntil' => $validuntil]); 298 299 $tokens = webservice::get_active_tokens($user->id); 300 if ($expectedactive) { 301 $this->assertCount(1, $tokens); 302 $this->assertEquals($service->id, reset($tokens)->externalserviceid); 303 } else { 304 $this->assertEmpty($tokens); 305 } 306 } 307 308 /** 309 * Utility method that tests the parameter type of a method info's input/output parameter. 310 * 311 * @param string $type The parameter type that is being evaluated. 312 * @param mixed $methoddesc The method description of the WS function. 313 * @param array $servicestructs The list of generated service struct classes. 314 */ 315 private function check_params($type, $methoddesc, $servicestructs) { 316 if ($methoddesc instanceof external_value) { 317 // Test for simple types. 318 if (in_array($methoddesc->type, [PARAM_INT, PARAM_FLOAT, PARAM_BOOL])) { 319 $this->assertEquals($methoddesc->type, $type); 320 } else { 321 $this->assertEquals('string', $type); 322 } 323 } else if ($methoddesc instanceof external_single_structure) { 324 // Test that the class name of the struct class is in the array of service structs. 325 $structinfo = $this->get_struct_info($servicestructs, $type); 326 $this->assertNotNull($structinfo); 327 // Test that the properties of the struct info exist in the method description. 328 foreach ($structinfo->properties as $propname => $proptype) { 329 $this->assertTrue($this->in_keydesc($methoddesc, $propname)); 330 } 331 } else if ($methoddesc instanceof external_multiple_structure) { 332 // Test for array types. 333 $this->assertEquals('array', $type); 334 } 335 } 336 337 /** 338 * Gets the struct information from the list of struct classes based on the given struct class name. 339 * 340 * @param array $structarray The list of generated struct classes. 341 * @param string $structclass The name of the struct class. 342 * @return object|null The struct class info, or null if it's not found. 343 */ 344 private function get_struct_info($structarray, $structclass) { 345 foreach ($structarray as $struct) { 346 if ($struct->classname === $structclass) { 347 return $struct; 348 } 349 } 350 return null; 351 } 352 353 /** 354 * Searches the keys of the given external_single_structure object if it contains a certain property name. 355 * 356 * @param external_single_structure $keydesc 357 * @param string $propertyname The property name to be searched for. 358 * @return bool True if the property name is found in $keydesc. False, otherwise. 359 */ 360 private function in_keydesc(external_single_structure $keydesc, $propertyname) { 361 foreach ($keydesc->keys as $key => $desc) { 362 if ($key === $propertyname) { 363 return true; 364 } 365 } 366 return false; 367 } 368 } 369 370 /** 371 * Class webservice_dummy. 372 * 373 * Dummy webservice class for testing the \webservice_base_server class and enable us to expose variables we want to test. 374 * 375 * @package core_webservice 376 * @category test 377 * @copyright 2016 Jun Pataleta <jun@moodle.com> 378 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 379 */ 380 class webservice_dummy extends \webservice_base_server { 381 382 /** 383 * webservice_dummy constructor. 384 * 385 * @param int $authmethod The authentication method. 386 */ 387 public function __construct($authmethod) { 388 parent::__construct($authmethod); 389 390 // Arbitrarily naming this as REST in order not to have to register another WS protocol and set capabilities. 391 $this->wsname = 'rest'; 392 } 393 394 /** 395 * Token setter method. 396 * 397 * @param string $token The web service token. 398 */ 399 public function set_token($token) { 400 $this->token = $token; 401 } 402 403 /** 404 * This method parses the request input, it needs to get: 405 * 1/ user authentication - username+password or token 406 * 2/ function name 407 * 3/ function parameters 408 */ 409 protected function parse_request() { 410 // Just a method stub. No need to implement at the moment since it's not really being used for this test case for now. 411 } 412 413 /** 414 * Send the result of function call to the WS client. 415 */ 416 protected function send_response() { 417 // Just a method stub. No need to implement at the moment since it's not really being used for this test case for now. 418 } 419 420 /** 421 * Send the error information to the WS client. 422 * 423 * @param \Exception $ex 424 */ 425 protected function send_error($ex = null) { 426 // Just a method stub. No need to implement at the moment since it's not really being used for this test case for now. 427 } 428 429 /** 430 * run() method implementation. 431 */ 432 public function run() { 433 $this->authenticate_user(); 434 $this->init_service_class(); 435 } 436 437 /** 438 * Getter method of servicemethods array. 439 * 440 * @return array 441 */ 442 public function get_service_methods() { 443 return $this->servicemethods; 444 } 445 446 /** 447 * Getter method of servicestructs array. 448 * 449 * @return array 450 */ 451 public function get_service_structs() { 452 return $this->servicestructs; 453 } 454 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body