See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [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 namespace tool_mobile; 18 19 use externallib_advanced_testcase; 20 21 defined('MOODLE_INTERNAL') || die(); 22 23 global $CFG; 24 25 require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 26 require_once($CFG->dirroot . '/admin/tool/mobile/tests/fixtures/output/mobile.php'); 27 require_once($CFG->dirroot . '/webservice/lib.php'); 28 29 /** 30 * Moodle Mobile admin tool external functions tests. 31 * 32 * @package tool_mobile 33 * @copyright 2016 Juan Leyva 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 * @since Moodle 3.1 36 */ 37 class externallib_test extends externallib_advanced_testcase { 38 39 /** 40 * Test get_plugins_supporting_mobile. 41 * This is a very basic test because currently there aren't plugins supporting Mobile in core. 42 */ 43 public function test_get_plugins_supporting_mobile() { 44 $result = external::get_plugins_supporting_mobile(); 45 $result = \external_api::clean_returnvalue(external::get_plugins_supporting_mobile_returns(), $result); 46 $this->assertCount(0, $result['warnings']); 47 $this->assertArrayHasKey('plugins', $result); 48 $this->assertTrue(is_array($result['plugins'])); 49 } 50 51 public function test_get_public_config() { 52 global $CFG, $SITE, $OUTPUT; 53 54 $this->resetAfterTest(true); 55 $result = external::get_public_config(); 56 $result = \external_api::clean_returnvalue(external::get_public_config_returns(), $result); 57 58 // Test default values. 59 $context = \context_system::instance(); 60 list($authinstructions, $notusedformat) = external_format_text($CFG->auth_instructions, FORMAT_MOODLE, $context->id); 61 list($maintenancemessage, $notusedformat) = external_format_text($CFG->maintenance_message, FORMAT_MOODLE, $context->id); 62 63 $expected = array( 64 'wwwroot' => $CFG->wwwroot, 65 'httpswwwroot' => $CFG->wwwroot, 66 'sitename' => external_format_string($SITE->fullname, $context->id, true), 67 'guestlogin' => $CFG->guestloginbutton, 68 'rememberusername' => $CFG->rememberusername, 69 'authloginviaemail' => $CFG->authloginviaemail, 70 'registerauth' => $CFG->registerauth, 71 'forgottenpasswordurl' => $CFG->forgottenpasswordurl, 72 'authinstructions' => $authinstructions, 73 'authnoneenabled' => (int) is_enabled_auth('none'), 74 'enablewebservices' => $CFG->enablewebservices, 75 'enablemobilewebservice' => $CFG->enablemobilewebservice, 76 'maintenanceenabled' => $CFG->maintenance_enabled, 77 'maintenancemessage' => $maintenancemessage, 78 'typeoflogin' => api::LOGIN_VIA_APP, 79 'mobilecssurl' => '', 80 'tool_mobile_disabledfeatures' => '', 81 'launchurl' => "$CFG->wwwroot/$CFG->admin/tool/mobile/launch.php", 82 'country' => $CFG->country, 83 'agedigitalconsentverification' => \core_auth\digital_consent::is_age_digital_consent_verification_enabled(), 84 'autolang' => $CFG->autolang, 85 'lang' => $CFG->lang, 86 'langmenu' => $CFG->langmenu, 87 'langlist' => $CFG->langlist, 88 'locale' => $CFG->locale, 89 'tool_mobile_minimumversion' => '', 90 'tool_mobile_iosappid' => get_config('tool_mobile', 'iosappid'), 91 'tool_mobile_androidappid' => get_config('tool_mobile', 'androidappid'), 92 'tool_mobile_setuplink' => get_config('tool_mobile', 'setuplink'), 93 'tool_mobile_qrcodetype' => get_config('tool_mobile', 'qrcodetype'), 94 'supportpage' => $CFG->supportpage, 95 'supportavailability' => $CFG->supportavailability, 96 'warnings' => array() 97 ); 98 $this->assertEquals($expected, $result); 99 100 $this->setAdminUser(); 101 // Change some values. 102 set_config('registerauth', 'email'); 103 $authinstructions = 'Something with <b>html tags</b>'; 104 set_config('auth_instructions', $authinstructions); 105 set_config('typeoflogin', api::LOGIN_VIA_BROWSER, 'tool_mobile'); 106 set_config('logo', 'mock.png', 'core_admin'); 107 set_config('logocompact', 'mock.png', 'core_admin'); 108 set_config('forgottenpasswordurl', 'mailto:fake@email.zy'); // Test old hack. 109 set_config('agedigitalconsentverification', 1); 110 set_config('autolang', 1); 111 set_config('lang', 'a_b'); // Set invalid lang. 112 set_config('disabledfeatures', 'myoverview', 'tool_mobile'); 113 set_config('minimumversion', '3.8.0', 'tool_mobile'); 114 set_config('supportemail', 'test@test.com'); 115 set_config('supportavailability', CONTACT_SUPPORT_ANYONE); 116 117 // Enable couple of issuers. 118 $issuer = \core\oauth2\api::create_standard_issuer('google'); 119 $irecord = $issuer->to_record(); 120 $irecord->clientid = 'mock'; 121 $irecord->clientsecret = 'mock'; 122 \core\oauth2\api::update_issuer($irecord); 123 124 set_config('hostname', 'localhost', 'auth_cas'); 125 set_config('auth_logo', 'http://invalidurl.com//invalid/', 'auth_cas'); 126 set_config('auth_name', 'CAS', 'auth_cas'); 127 set_config('auth', 'oauth2,cas'); 128 129 list($authinstructions, $notusedformat) = external_format_text($authinstructions, FORMAT_MOODLE, $context->id); 130 $expected['registerauth'] = 'email'; 131 $expected['authinstructions'] = $authinstructions; 132 $expected['typeoflogin'] = api::LOGIN_VIA_BROWSER; 133 $expected['forgottenpasswordurl'] = ''; // Expect empty when it's not an URL. 134 $expected['agedigitalconsentverification'] = true; 135 $expected['supportname'] = $CFG->supportname; 136 $expected['supportemail'] = $CFG->supportemail; 137 $expected['supportavailability'] = $CFG->supportavailability; 138 $expected['autolang'] = '1'; 139 $expected['lang'] = ''; // Expect empty because it was set to an invalid lang. 140 $expected['tool_mobile_disabledfeatures'] = 'myoverview'; 141 $expected['tool_mobile_minimumversion'] = '3.8.0'; 142 143 if ($logourl = $OUTPUT->get_logo_url()) { 144 $expected['logourl'] = $logourl->out(false); 145 } 146 if ($compactlogourl = $OUTPUT->get_compact_logo_url()) { 147 $expected['compactlogourl'] = $compactlogourl->out(false); 148 } 149 150 $result = external::get_public_config(); 151 $result = \external_api::clean_returnvalue(external::get_public_config_returns(), $result); 152 // First check providers. 153 $identityproviders = $result['identityproviders']; 154 unset($result['identityproviders']); 155 156 $this->assertEquals('Google', $identityproviders[0]['name']); 157 $this->assertEquals($irecord->image, $identityproviders[0]['iconurl']); 158 $this->assertStringContainsString($CFG->wwwroot, $identityproviders[0]['url']); 159 160 $this->assertEquals('CAS', $identityproviders[1]['name']); 161 $this->assertEmpty($identityproviders[1]['iconurl']); 162 $this->assertStringContainsString($CFG->wwwroot, $identityproviders[1]['url']); 163 164 $this->assertEquals($expected, $result); 165 166 // Change providers img. 167 $newurl = 'validimage.png'; 168 set_config('auth_logo', $newurl, 'auth_cas'); 169 $result = external::get_public_config(); 170 $result = \external_api::clean_returnvalue(external::get_public_config_returns(), $result); 171 $this->assertStringContainsString($newurl, $result['identityproviders'][1]['iconurl']); 172 } 173 174 /** 175 * Test get_config 176 */ 177 public function test_get_config() { 178 global $CFG, $SITE; 179 require_once($CFG->dirroot . '/course/format/lib.php'); 180 181 $this->resetAfterTest(true); 182 183 $mysitepolicy = 'http://mysite.is/policy/'; 184 set_config('sitepolicy', $mysitepolicy); 185 set_config('supportemail', 'test@test.com'); 186 187 $result = external::get_config(); 188 $result = \external_api::clean_returnvalue(external::get_config_returns(), $result); 189 190 // SITE summary is null in phpunit which gets transformed to an empty string by format_text. 191 list($sitesummary, $unused) = external_format_text($SITE->summary, $SITE->summaryformat, \context_system::instance()->id); 192 193 // Test default values. 194 $context = \context_system::instance(); 195 $expected = array( 196 array('name' => 'fullname', 'value' => $SITE->fullname), 197 array('name' => 'shortname', 'value' => $SITE->shortname), 198 array('name' => 'summary', 'value' => $sitesummary), 199 array('name' => 'summaryformat', 'value' => FORMAT_HTML), 200 array('name' => 'frontpage', 'value' => $CFG->frontpage), 201 array('name' => 'frontpageloggedin', 'value' => $CFG->frontpageloggedin), 202 array('name' => 'maxcategorydepth', 'value' => $CFG->maxcategorydepth), 203 array('name' => 'frontpagecourselimit', 'value' => $CFG->frontpagecourselimit), 204 array('name' => 'numsections', 'value' => course_get_format($SITE)->get_last_section_number()), 205 array('name' => 'newsitems', 'value' => $SITE->newsitems), 206 array('name' => 'commentsperpage', 'value' => $CFG->commentsperpage), 207 array('name' => 'sitepolicy', 'value' => $mysitepolicy), 208 array('name' => 'sitepolicyhandler', 'value' => ''), 209 array('name' => 'disableuserimages', 'value' => $CFG->disableuserimages), 210 array('name' => 'mygradesurl', 'value' => user_mygrades_url()->out(false)), 211 array('name' => 'tool_mobile_forcelogout', 'value' => 0), 212 array('name' => 'tool_mobile_customlangstrings', 'value' => ''), 213 array('name' => 'tool_mobile_disabledfeatures', 'value' => ''), 214 array('name' => 'tool_mobile_filetypeexclusionlist', 'value' => ''), 215 array('name' => 'tool_mobile_custommenuitems', 'value' => ''), 216 array('name' => 'tool_mobile_apppolicy', 'value' => ''), 217 array('name' => 'tool_mobile_autologinmintimebetweenreq', 'value' => 6 * MINSECS), 218 array('name' => 'calendartype', 'value' => $CFG->calendartype), 219 array('name' => 'calendar_site_timeformat', 'value' => $CFG->calendar_site_timeformat), 220 array('name' => 'calendar_startwday', 'value' => $CFG->calendar_startwday), 221 array('name' => 'calendar_adminseesall', 'value' => $CFG->calendar_adminseesall), 222 array('name' => 'calendar_lookahead', 'value' => $CFG->calendar_lookahead), 223 array('name' => 'calendar_maxevents', 'value' => $CFG->calendar_maxevents), 224 ); 225 $colornumbers = range(1, 10); 226 foreach ($colornumbers as $number) { 227 $expected[] = [ 228 'name' => 'core_admin_coursecolor' . $number, 229 'value' => get_config('core_admin', 'coursecolor' . $number) 230 ]; 231 } 232 $expected[] = ['name' => 'supportavailability', 'value' => $CFG->supportavailability]; 233 $expected[] = ['name' => 'supportname', 'value' => $CFG->supportname]; 234 $expected[] = ['name' => 'supportemail', 'value' => $CFG->supportemail]; 235 $expected[] = ['name' => 'supportpage', 'value' => $CFG->supportpage]; 236 237 $expected[] = ['name' => 'coursegraceperiodafter', 'value' => $CFG->coursegraceperiodafter]; 238 $expected[] = ['name' => 'coursegraceperiodbefore', 'value' => $CFG->coursegraceperiodbefore]; 239 240 $expected[] = ['name' => 'enabledashboard', 'value' => $CFG->enabledashboard]; 241 $expected[] = ['name' => 'customusermenuitems', 'value' => $CFG->customusermenuitems]; 242 243 $this->assertCount(0, $result['warnings']); 244 $this->assertEquals($expected, $result['settings']); 245 246 // Change a value and retrieve filtering by section. 247 set_config('commentsperpage', 1); 248 $expected[10]['value'] = 1; 249 // Remove not expected elements. 250 array_splice($expected, 11); 251 252 $result = external::get_config('frontpagesettings'); 253 $result = \external_api::clean_returnvalue(external::get_config_returns(), $result); 254 $this->assertCount(0, $result['warnings']); 255 $this->assertEquals($expected, $result['settings']); 256 } 257 258 /* 259 * Test get_autologin_key. 260 */ 261 public function test_get_autologin_key() { 262 global $DB, $CFG, $USER; 263 264 $this->resetAfterTest(true); 265 266 $user = $this->getDataGenerator()->create_user(); 267 $this->setUser($user); 268 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE)); 269 270 $token = external_generate_token_for_current_user($service); 271 272 // Check we got the private token. 273 $this->assertTrue(isset($token->privatetoken)); 274 275 // Enable requeriments. 276 $_GET['wstoken'] = $token->token; // Mock parameters. 277 278 // Fake the app. 279 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 280 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 281 282 // Even if we force the password change for the current user we should be able to retrieve the key. 283 set_user_preference('auth_forcepasswordchange', 1, $user->id); 284 285 $this->setCurrentTimeStart(); 286 $result = external::get_autologin_key($token->privatetoken); 287 $result = \external_api::clean_returnvalue(external::get_autologin_key_returns(), $result); 288 // Validate the key. 289 $this->assertEquals(32, \core_text::strlen($result['key'])); 290 $key = $DB->get_record('user_private_key', array('value' => $result['key'])); 291 $this->assertEquals($USER->id, $key->userid); 292 $this->assertTimeCurrent($key->validuntil - api::LOGIN_KEY_TTL); 293 294 // Now, try with an invalid private token. 295 set_user_preference('tool_mobile_autologin_request_last', time() - HOURSECS, $USER); 296 297 $this->expectException('moodle_exception'); 298 $this->expectExceptionMessage(get_string('invalidprivatetoken', 'tool_mobile')); 299 $result = external::get_autologin_key(random_string('64')); 300 } 301 302 /** 303 * Test get_autologin_key missing ws. 304 */ 305 public function test_get_autologin_key_missing_ws() { 306 global $CFG; 307 $this->resetAfterTest(true); 308 309 // Fake the app. 310 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 311 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 312 313 // Need to disable webservices to verify that's checked. 314 $CFG->enablewebservices = 0; 315 $CFG->enablemobilewebservice = 0; 316 317 $this->setAdminUser(); 318 $this->expectException('moodle_exception'); 319 $this->expectExceptionMessage(get_string('enablewsdescription', 'webservice')); 320 $result = external::get_autologin_key(''); 321 } 322 323 /** 324 * Test get_autologin_key missing https. 325 */ 326 public function test_get_autologin_key_missing_https() { 327 global $CFG; 328 329 // Fake the app. 330 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 331 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 332 333 // Need to simulate a non HTTPS site here. 334 $CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot); 335 336 $this->resetAfterTest(true); 337 $this->setAdminUser(); 338 339 $this->expectException('moodle_exception'); 340 $this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile')); 341 $result = external::get_autologin_key(''); 342 } 343 344 /** 345 * Test get_autologin_key missing admin. 346 */ 347 public function test_get_autologin_key_missing_admin() { 348 global $CFG; 349 350 $this->resetAfterTest(true); 351 $this->setAdminUser(); 352 353 // Fake the app. 354 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 355 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 356 357 $this->expectException('moodle_exception'); 358 $this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile')); 359 $result = external::get_autologin_key(''); 360 } 361 362 /** 363 * Test get_autologin_key locked. 364 */ 365 public function test_get_autologin_key_missing_locked() { 366 global $CFG, $DB, $USER; 367 368 $this->resetAfterTest(true); 369 $user = $this->getDataGenerator()->create_user(); 370 $this->setUser($user); 371 372 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE)); 373 374 $token = external_generate_token_for_current_user($service); 375 $_GET['wstoken'] = $token->token; // Mock parameters. 376 377 // Fake the app. 378 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 379 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 380 381 $result = external::get_autologin_key($token->privatetoken); 382 $result = \external_api::clean_returnvalue(external::get_autologin_key_returns(), $result); 383 384 // Mock last time request. 385 $mocktime = time() - 7 * MINSECS; 386 set_user_preference('tool_mobile_autologin_request_last', $mocktime, $USER); 387 $result = external::get_autologin_key($token->privatetoken); 388 $result = \external_api::clean_returnvalue(external::get_autologin_key_returns(), $result); 389 390 // Change min time between requests to 3 minutes. 391 set_config('autologinmintimebetweenreq', 3 * MINSECS, 'tool_mobile'); 392 393 // Mock a previous request, 4 minutes ago. 394 $mocktime = time() - (4 * MINSECS); 395 set_user_preference('tool_mobile_autologin_request_last', $mocktime, $USER); 396 $result = external::get_autologin_key($token->privatetoken); 397 $result = \external_api::clean_returnvalue(external::get_autologin_key_returns(), $result); 398 399 // We just requested one token, we must wait. 400 $this->expectException('moodle_exception'); 401 $this->expectExceptionMessage(get_string('autologinkeygenerationlockout', 'tool_mobile', 3)); 402 $result = external::get_autologin_key($token->privatetoken); 403 } 404 405 /** 406 * Test get_autologin_key missing app_request. 407 */ 408 public function test_get_autologin_key_missing_app_request() { 409 global $CFG; 410 411 $this->resetAfterTest(true); 412 $this->setAdminUser(); 413 414 $this->expectException('moodle_exception'); 415 $this->expectExceptionMessage(get_string('apprequired', 'tool_mobile')); 416 $result = external::get_autologin_key(''); 417 } 418 419 /** 420 * Test get_content. 421 */ 422 public function test_get_content() { 423 424 $paramval = 16; 425 $result = external::get_content('tool_mobile', 'test_view', array(array('name' => 'param1', 'value' => $paramval))); 426 $result = \external_api::clean_returnvalue(external::get_content_returns(), $result); 427 $this->assertCount(1, $result['templates']); 428 $this->assertCount(1, $result['otherdata']); 429 $this->assertCount(2, $result['restrict']['users']); 430 $this->assertCount(2, $result['restrict']['courses']); 431 $this->assertEquals('alert();', $result['javascript']); 432 $this->assertEquals('main', $result['templates'][0]['id']); 433 $this->assertEquals('The HTML code', $result['templates'][0]['html']); 434 $this->assertEquals('otherdata1', $result['otherdata'][0]['name']); 435 $this->assertEquals($paramval, $result['otherdata'][0]['value']); 436 $this->assertEquals(array(1, 2), $result['restrict']['users']); 437 $this->assertEquals(array(3, 4), $result['restrict']['courses']); 438 $this->assertEmpty($result['files']); 439 $this->assertFalse($result['disabled']); 440 } 441 442 /** 443 * Test get_content disabled. 444 */ 445 public function test_get_content_disabled() { 446 447 $paramval = 16; 448 $result = external::get_content('tool_mobile', 'test_view_disabled', 449 array(array('name' => 'param1', 'value' => $paramval))); 450 $result = \external_api::clean_returnvalue(external::get_content_returns(), $result); 451 $this->assertTrue($result['disabled']); 452 } 453 454 /** 455 * Test get_content non existent function in valid component. 456 */ 457 public function test_get_content_non_existent_function() { 458 459 $this->expectException('coding_exception'); 460 $result = external::get_content('tool_mobile', 'test_blahblah'); 461 } 462 463 /** 464 * Test get_content incorrect component. 465 */ 466 public function test_get_content_invalid_component() { 467 468 $this->expectException('moodle_exception'); 469 $result = external::get_content('tool_mobile\hack', 'test_view'); 470 } 471 472 /** 473 * Test get_content non existent component. 474 */ 475 public function test_get_content_non_existent_component() { 476 477 $this->expectException('moodle_exception'); 478 $result = external::get_content('tool_blahblahblah', 'test_view'); 479 } 480 481 public function test_call_external_functions() { 482 global $SESSION; 483 484 $this->resetAfterTest(true); 485 486 $category = self::getDataGenerator()->create_category(array('name' => 'Category 1')); 487 $course = self::getDataGenerator()->create_course([ 488 'category' => $category->id, 489 'shortname' => 'c1', 490 'summary' => '<span lang="en" class="multilang">Course summary</span>' 491 . '<span lang="eo" class="multilang">Kurso resumo</span>' 492 . '@@PLUGINFILE@@/filename.txt' 493 . '<!-- Comment stripped when formatting text -->', 494 'summaryformat' => FORMAT_MOODLE 495 ]); 496 $user1 = self::getDataGenerator()->create_user(['username' => 'user1', 'lastaccess' => time()]); 497 $user2 = self::getDataGenerator()->create_user(['username' => 'user2', 'lastaccess' => time()]); 498 499 self::setUser($user1); 500 501 // Setup WS token. 502 $webservicemanager = new \webservice; 503 $service = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE); 504 $token = external_generate_token_for_current_user($service); 505 $_POST['wstoken'] = $token->token; 506 507 // Workaround for \external_api::call_external_function requiring sesskey. 508 $_POST['sesskey'] = sesskey(); 509 510 // Call some functions. 511 512 $requests = [ 513 [ 514 'function' => 'core_course_get_courses_by_field', 515 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]) 516 ], 517 [ 518 'function' => 'core_user_get_users_by_field', 519 'arguments' => json_encode(['field' => 'id', 'values' => [$user1->id]]) 520 ], 521 [ 522 'function' => 'core_user_get_user_preferences', 523 'arguments' => json_encode(['name' => 'some_setting', 'userid' => $user2->id]) 524 ], 525 [ 526 'function' => 'core_course_get_courses_by_field', 527 'arguments' => json_encode(['field' => 'shortname', 'value' => $course->shortname]) 528 ], 529 ]; 530 $result = external::call_external_functions($requests); 531 532 // We need to execute the return values cleaning process to simulate the web service server. 533 $result = \external_api::clean_returnvalue(external::call_external_functions_returns(), $result); 534 535 // Only 3 responses, the 4th request is not executed because the 3rd throws an exception. 536 $this->assertCount(3, $result['responses']); 537 538 $this->assertFalse($result['responses'][0]['error']); 539 $coursedata = \external_api::clean_returnvalue( 540 \core_course_external::get_courses_by_field_returns(), 541 \core_course_external::get_courses_by_field('id', $course->id)); 542 $this->assertEquals(json_encode($coursedata), $result['responses'][0]['data']); 543 544 $this->assertFalse($result['responses'][1]['error']); 545 $userdata = \external_api::clean_returnvalue( 546 \core_user_external::get_users_by_field_returns(), 547 \core_user_external::get_users_by_field('id', [$user1->id])); 548 $this->assertEquals(json_encode($userdata), $result['responses'][1]['data']); 549 550 $this->assertTrue($result['responses'][2]['error']); 551 $exception = json_decode($result['responses'][2]['exception'], true); 552 $this->assertEquals('nopermissions', $exception['errorcode']); 553 554 // Call a function not included in the external service. 555 556 $_POST['wstoken'] = $token->token; 557 $functions = $webservicemanager->get_not_associated_external_functions($service->id); 558 $requests = [['function' => current($functions)->name]]; 559 $result = external::call_external_functions($requests); 560 561 $this->assertTrue($result['responses'][0]['error']); 562 $exception = json_decode($result['responses'][0]['exception'], true); 563 $this->assertEquals('accessexception', $exception['errorcode']); 564 $this->assertEquals('webservice', $exception['module']); 565 566 // Call a function with different external settings. 567 568 filter_set_global_state('multilang', TEXTFILTER_ON); 569 $_POST['wstoken'] = $token->token; 570 $SESSION->lang = 'eo'; // Change default language, so we can test changing it to "en". 571 $requests = [ 572 [ 573 'function' => 'core_course_get_courses_by_field', 574 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]), 575 ], 576 [ 577 'function' => 'core_course_get_courses_by_field', 578 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]), 579 'settingraw' => '1' 580 ], 581 [ 582 'function' => 'core_course_get_courses_by_field', 583 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]), 584 'settingraw' => '1', 585 'settingfileurl' => '0' 586 ], 587 [ 588 'function' => 'core_course_get_courses_by_field', 589 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]), 590 'settingfilter' => '1', 591 'settinglang' => 'en' 592 ], 593 ]; 594 $result = external::call_external_functions($requests); 595 596 $this->assertCount(4, $result['responses']); 597 598 $context = \context_course::instance($course->id); 599 $pluginfile = 'webservice/pluginfile.php'; 600 601 $this->assertFalse($result['responses'][0]['error']); 602 $data = json_decode($result['responses'][0]['data']); 603 $expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null); 604 $expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => false]); 605 $this->assertEquals($expected, $data->courses[0]->summary); 606 607 $this->assertFalse($result['responses'][1]['error']); 608 $data = json_decode($result['responses'][1]['data']); 609 $expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null); 610 $this->assertEquals($expected, $data->courses[0]->summary); 611 612 $this->assertFalse($result['responses'][2]['error']); 613 $data = json_decode($result['responses'][2]['data']); 614 $this->assertEquals($course->summary, $data->courses[0]->summary); 615 616 $this->assertFalse($result['responses'][3]['error']); 617 $data = json_decode($result['responses'][3]['data']); 618 $expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null); 619 $SESSION->lang = 'en'; // We expect filtered text in english. 620 $expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => true]); 621 $this->assertEquals($expected, $data->courses[0]->summary); 622 } 623 624 /* 625 * Test get_tokens_for_qr_login. 626 */ 627 public function test_get_tokens_for_qr_login() { 628 global $DB, $CFG, $USER; 629 630 $this->resetAfterTest(true); 631 632 $user = $this->getDataGenerator()->create_user(); 633 $this->setUser($user); 634 635 $mobilesettings = get_config('tool_mobile'); 636 $mobilesettings->qrsameipcheck = 1; 637 $qrloginkey = api::get_qrlogin_key($mobilesettings); 638 639 // Generate new tokens, the ones we expect to receive. 640 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE)); 641 $token = external_generate_token_for_current_user($service); 642 643 // Fake the app. 644 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 645 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 646 647 $result = external::get_tokens_for_qr_login($qrloginkey, $USER->id); 648 $result = \external_api::clean_returnvalue(external::get_tokens_for_qr_login_returns(), $result); 649 650 $this->assertEmpty($result['warnings']); 651 $this->assertEquals($token->token, $result['token']); 652 $this->assertEquals($token->privatetoken, $result['privatetoken']); 653 654 // Now, try with an invalid key. 655 $this->expectException('moodle_exception'); 656 $this->expectExceptionMessage(get_string('invalidkey', 'error')); 657 $result = external::get_tokens_for_qr_login(random_string('64'), $user->id); 658 } 659 660 /* 661 * Test get_tokens_for_qr_login ignore ip check. 662 */ 663 public function test_get_tokens_for_qr_login_ignore_ip_check() { 664 global $DB, $CFG, $USER; 665 666 $this->resetAfterTest(true); 667 668 $user = $this->getDataGenerator()->create_user(); 669 $this->setUser($user); 670 671 $mobilesettings = get_config('tool_mobile'); 672 $mobilesettings->qrsameipcheck = 0; 673 $qrloginkey = api::get_qrlogin_key($mobilesettings); 674 675 $key = $DB->get_record('user_private_key', ['value' => $qrloginkey]); 676 $this->assertNull($key->iprestriction); 677 678 // Generate new tokens, the ones we expect to receive. 679 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE)); 680 $token = external_generate_token_for_current_user($service); 681 682 // Fake the app. 683 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 684 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 685 686 $result = external::get_tokens_for_qr_login($qrloginkey, $USER->id); 687 $result = \external_api::clean_returnvalue(external::get_tokens_for_qr_login_returns(), $result); 688 689 $this->assertEmpty($result['warnings']); 690 $this->assertEquals($token->token, $result['token']); 691 $this->assertEquals($token->privatetoken, $result['privatetoken']); 692 693 // Now, try with an invalid key. 694 $this->expectException('moodle_exception'); 695 $this->expectExceptionMessage(get_string('invalidkey', 'error')); 696 $result = external::get_tokens_for_qr_login(random_string('64'), $user->id); 697 } 698 699 /* 700 * Test get_tokens_for_qr_login ip check fails. 701 */ 702 public function test_get_tokens_for_qr_login_ip_check_mismatch() { 703 global $DB, $CFG, $USER; 704 705 $this->resetAfterTest(true); 706 707 $user = $this->getDataGenerator()->create_user(); 708 $this->setUser($user); 709 710 $mobilesettings = get_config('tool_mobile'); 711 $mobilesettings->qrsameipcheck = 1; 712 $qrloginkey = api::get_qrlogin_key($mobilesettings); 713 714 // Alter expected ip. 715 $DB->set_field('user_private_key', 'iprestriction', '6.6.6.6', ['value' => $qrloginkey]); 716 717 // Generate new tokens, the ones we expect to receive. 718 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE)); 719 $token = external_generate_token_for_current_user($service); 720 721 // Fake the app. 722 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 723 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 724 725 $this->expectException('moodle_exception'); 726 $this->expectExceptionMessage(get_string('ipmismatch', 'error')); 727 $result = external::get_tokens_for_qr_login($qrloginkey, $USER->id); 728 } 729 730 /** 731 * Test get_tokens_for_qr_login missing QR code enabled. 732 */ 733 public function test_get_tokens_for_qr_login_missing_enableqr() { 734 global $CFG, $USER; 735 $this->resetAfterTest(true); 736 $this->setAdminUser(); 737 738 set_config('qrcodetype', api::QR_CODE_DISABLED, 'tool_mobile'); 739 740 $this->expectExceptionMessage(get_string('qrcodedisabled', 'tool_mobile')); 741 $result = external::get_tokens_for_qr_login('', $USER->id); 742 } 743 744 /** 745 * Test get_tokens_for_qr_login missing ws. 746 */ 747 public function test_get_tokens_for_qr_login_missing_ws() { 748 global $CFG; 749 $this->resetAfterTest(true); 750 751 $user = $this->getDataGenerator()->create_user(); 752 $this->setUser($user); 753 754 // Fake the app. 755 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 756 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 757 758 // Need to disable webservices to verify that's checked. 759 $CFG->enablewebservices = 0; 760 $CFG->enablemobilewebservice = 0; 761 762 $this->setAdminUser(); 763 $this->expectException('moodle_exception'); 764 $this->expectExceptionMessage(get_string('enablewsdescription', 'webservice')); 765 $result = external::get_tokens_for_qr_login('', $user->id); 766 } 767 768 /** 769 * Test get_tokens_for_qr_login missing https. 770 */ 771 public function test_get_tokens_for_qr_login_missing_https() { 772 global $CFG, $USER; 773 774 // Fake the app. 775 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 776 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 777 778 // Need to simulate a non HTTPS site here. 779 $CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot); 780 781 $this->resetAfterTest(true); 782 $this->setAdminUser(); 783 784 $this->expectException('moodle_exception'); 785 $this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile')); 786 $result = external::get_tokens_for_qr_login('', $USER->id); 787 } 788 789 /** 790 * Test get_tokens_for_qr_login missing admin. 791 */ 792 public function test_get_tokens_for_qr_login_missing_admin() { 793 global $CFG, $USER; 794 795 $this->resetAfterTest(true); 796 $this->setAdminUser(); 797 798 // Fake the app. 799 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 800 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 801 802 $this->expectException('moodle_exception'); 803 $this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile')); 804 $result = external::get_tokens_for_qr_login('', $USER->id); 805 } 806 807 /** 808 * Test get_tokens_for_qr_login missing app_request. 809 */ 810 public function test_get_tokens_for_qr_login_missing_app_request() { 811 global $CFG, $USER; 812 813 $this->resetAfterTest(true); 814 $this->setAdminUser(); 815 816 $this->expectException('moodle_exception'); 817 $this->expectExceptionMessage(get_string('apprequired', 'tool_mobile')); 818 $result = external::get_tokens_for_qr_login('', $USER->id); 819 } 820 821 /** 822 * Test validate subscription key. 823 */ 824 public function test_validate_subscription_key_valid() { 825 $this->resetAfterTest(true); 826 827 $sitesubscriptionkey = ['validuntil' => time() + MINSECS, 'key' => complex_random_string(32)]; 828 set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile'); 829 830 $result = external::validate_subscription_key($sitesubscriptionkey['key']); 831 $result = \external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result); 832 $this->assertEmpty($result['warnings']); 833 $this->assertTrue($result['validated']); 834 } 835 836 /** 837 * Test validate subscription key invalid first and then a valid one. 838 */ 839 public function test_validate_subscription_key_invalid_key_first() { 840 $this->resetAfterTest(true); 841 842 $sitesubscriptionkey = ['validuntil' => time() + MINSECS, 'key' => complex_random_string(32)]; 843 set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile'); 844 845 $result = external::validate_subscription_key('fakekey'); 846 $result = \external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result); 847 $this->assertEmpty($result['warnings']); 848 $this->assertFalse($result['validated']); 849 850 // The valid one has been invalidated because the previous attempt. 851 $result = external::validate_subscription_key($sitesubscriptionkey['key']); 852 $result = \external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result); 853 $this->assertEmpty($result['warnings']); 854 $this->assertFalse($result['validated']); 855 } 856 857 /** 858 * Test validate subscription key invalid. 859 */ 860 public function test_validate_subscription_key_invalid_key() { 861 $this->resetAfterTest(true); 862 863 $result = external::validate_subscription_key('fakekey'); 864 $result = \external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result); 865 $this->assertEmpty($result['warnings']); 866 $this->assertFalse($result['validated']); 867 } 868 869 /** 870 * Test validate subscription key invalid. 871 */ 872 public function test_validate_subscription_key_outdated() { 873 $this->resetAfterTest(true); 874 875 $sitesubscriptionkey = ['validuntil' => time() - MINSECS, 'key' => complex_random_string(32)]; 876 set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile'); 877 878 $result = external::validate_subscription_key($sitesubscriptionkey['key']); 879 $result = \external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result); 880 $this->assertEmpty($result['warnings']); 881 $this->assertFalse($result['validated']); 882 } 883 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body