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