Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [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' => 'tool_mobile_autologout', 'value' => get_config('tool_mobile', 'autologout')), 234 array('name' => 'tool_mobile_autologouttime', 'value' => get_config('tool_mobile', 'autologouttime')), 235 array('name' => 'calendartype', 'value' => $CFG->calendartype), 236 array('name' => 'calendar_site_timeformat', 'value' => $CFG->calendar_site_timeformat), 237 array('name' => 'calendar_startwday', 'value' => $CFG->calendar_startwday), 238 array('name' => 'calendar_adminseesall', 'value' => $CFG->calendar_adminseesall), 239 array('name' => 'calendar_lookahead', 'value' => $CFG->calendar_lookahead), 240 array('name' => 'calendar_maxevents', 'value' => $CFG->calendar_maxevents), 241 ); 242 $colornumbers = range(1, 10); 243 foreach ($colornumbers as $number) { 244 $expected[] = [ 245 'name' => 'core_admin_coursecolor' . $number, 246 'value' => get_config('core_admin', 'coursecolor' . $number) 247 ]; 248 } 249 $expected[] = ['name' => 'supportavailability', 'value' => $CFG->supportavailability]; 250 $expected[] = ['name' => 'supportname', 'value' => $CFG->supportname]; 251 $expected[] = ['name' => 'supportemail', 'value' => $CFG->supportemail]; 252 $expected[] = ['name' => 'supportpage', 'value' => $CFG->supportpage]; 253 254 $expected[] = ['name' => 'coursegraceperiodafter', 'value' => $CFG->coursegraceperiodafter]; 255 $expected[] = ['name' => 'coursegraceperiodbefore', 'value' => $CFG->coursegraceperiodbefore]; 256 257 $expected[] = ['name' => 'enabledashboard', 'value' => $CFG->enabledashboard]; 258 $expected[] = ['name' => 'customusermenuitems', 'value' => $CFG->customusermenuitems]; 259 $expected[] = ['name' => 'timezone', 'value' => $CFG->timezone]; 260 $expected[] = ['name' => 'forcetimezone', 'value' => $CFG->forcetimezone]; 261 262 $expected[] = ['name' => 'searchengine', 'value' => $CFG->searchengine]; 263 $expected[] = ['name' => 'searchenablecategories', 'value' => $CFG->searchenablecategories]; 264 $expected[] = ['name' => 'searchdefaultcategory', 'value' => $CFG->searchdefaultcategory]; 265 $expected[] = ['name' => 'searchhideallcategory', 'value' => $CFG->searchhideallcategory]; 266 $expected[] = ['name' => 'searchmaxtopresults', 'value' => $CFG->searchmaxtopresults]; 267 $expected[] = ['name' => 'searchbannerenable', 'value' => $CFG->searchbannerenable]; 268 $expected[] = ['name' => 'searchbanner', 'value' => $CFG->searchbanner]; 269 270 $this->assertCount(0, $result['warnings']); 271 $this->assertEquals($expected, $result['settings']); 272 273 // Change a value and retrieve filtering by section. 274 set_config('commentsperpage', 1); 275 $expected[10]['value'] = 1; 276 // Remove not expected elements. 277 array_splice($expected, 11); 278 279 $result = external::get_config('frontpagesettings'); 280 $result = external_api::clean_returnvalue(external::get_config_returns(), $result); 281 $this->assertCount(0, $result['warnings']); 282 $this->assertEquals($expected, $result['settings']); 283 } 284 285 /* 286 * Test get_autologin_key. 287 */ 288 public function test_get_autologin_key() { 289 global $DB, $CFG, $USER; 290 291 $this->resetAfterTest(true); 292 293 $user = $this->getDataGenerator()->create_user(); 294 $this->setUser($user); 295 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE)); 296 297 $token = \core_external\util::generate_token_for_current_user($service); 298 299 // Check we got the private token. 300 $this->assertTrue(isset($token->privatetoken)); 301 302 // Enable requeriments. 303 $_GET['wstoken'] = $token->token; // Mock parameters. 304 305 // Fake the app. 306 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 307 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 308 309 // Even if we force the password change for the current user we should be able to retrieve the key. 310 set_user_preference('auth_forcepasswordchange', 1, $user->id); 311 312 $this->setCurrentTimeStart(); 313 $result = external::get_autologin_key($token->privatetoken); 314 $result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result); 315 // Validate the key. 316 $this->assertEquals(32, \core_text::strlen($result['key'])); 317 $key = $DB->get_record('user_private_key', array('value' => $result['key'])); 318 $this->assertEquals($USER->id, $key->userid); 319 $this->assertTimeCurrent($key->validuntil - api::LOGIN_KEY_TTL); 320 321 // Now, try with an invalid private token. 322 set_user_preference('tool_mobile_autologin_request_last', time() - HOURSECS, $USER); 323 324 $this->expectException('moodle_exception'); 325 $this->expectExceptionMessage(get_string('invalidprivatetoken', 'tool_mobile')); 326 $result = external::get_autologin_key(random_string('64')); 327 } 328 329 /** 330 * Test get_autologin_key missing ws. 331 */ 332 public function test_get_autologin_key_missing_ws() { 333 global $CFG; 334 $this->resetAfterTest(true); 335 336 // Fake the app. 337 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 338 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 339 340 // Need to disable webservices to verify that's checked. 341 $CFG->enablewebservices = 0; 342 $CFG->enablemobilewebservice = 0; 343 344 $this->setAdminUser(); 345 $this->expectException('moodle_exception'); 346 $this->expectExceptionMessage(get_string('enablewsdescription', 'webservice')); 347 $result = external::get_autologin_key(''); 348 } 349 350 /** 351 * Test get_autologin_key missing https. 352 */ 353 public function test_get_autologin_key_missing_https() { 354 global $CFG; 355 356 // Fake the app. 357 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 358 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 359 360 // Need to simulate a non HTTPS site here. 361 $CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot); 362 363 $this->resetAfterTest(true); 364 $this->setAdminUser(); 365 366 $this->expectException('moodle_exception'); 367 $this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile')); 368 $result = external::get_autologin_key(''); 369 } 370 371 /** 372 * Test get_autologin_key missing admin. 373 */ 374 public function test_get_autologin_key_missing_admin() { 375 global $CFG; 376 377 $this->resetAfterTest(true); 378 $this->setAdminUser(); 379 380 // Fake the app. 381 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 382 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 383 384 $this->expectException('moodle_exception'); 385 $this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile')); 386 $result = external::get_autologin_key(''); 387 } 388 389 /** 390 * Test get_autologin_key locked. 391 */ 392 public function test_get_autologin_key_missing_locked() { 393 global $CFG, $DB, $USER; 394 395 $this->resetAfterTest(true); 396 $user = $this->getDataGenerator()->create_user(); 397 $this->setUser($user); 398 399 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE)); 400 401 $token = \core_external\util::generate_token_for_current_user($service); 402 $_GET['wstoken'] = $token->token; // Mock parameters. 403 404 // Fake the app. 405 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 406 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 407 408 $result = external::get_autologin_key($token->privatetoken); 409 $result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result); 410 411 // Mock last time request. 412 $mocktime = time() - 7 * MINSECS; 413 set_user_preference('tool_mobile_autologin_request_last', $mocktime, $USER); 414 $result = external::get_autologin_key($token->privatetoken); 415 $result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result); 416 417 // Change min time between requests to 3 minutes. 418 set_config('autologinmintimebetweenreq', 3 * MINSECS, 'tool_mobile'); 419 420 // Mock a previous request, 4 minutes ago. 421 $mocktime = time() - (4 * MINSECS); 422 set_user_preference('tool_mobile_autologin_request_last', $mocktime, $USER); 423 $result = external::get_autologin_key($token->privatetoken); 424 $result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result); 425 426 // We just requested one token, we must wait. 427 $this->expectException('moodle_exception'); 428 $this->expectExceptionMessage(get_string('autologinkeygenerationlockout', 'tool_mobile', 3)); 429 $result = external::get_autologin_key($token->privatetoken); 430 } 431 432 /** 433 * Test get_autologin_key missing app_request. 434 */ 435 public function test_get_autologin_key_missing_app_request() { 436 global $CFG; 437 438 $this->resetAfterTest(true); 439 $this->setAdminUser(); 440 441 $this->expectException('moodle_exception'); 442 $this->expectExceptionMessage(get_string('apprequired', 'tool_mobile')); 443 $result = external::get_autologin_key(''); 444 } 445 446 /** 447 * Test get_content. 448 */ 449 public function test_get_content() { 450 451 $paramval = 16; 452 $result = external::get_content('tool_mobile', 'test_view', array(array('name' => 'param1', 'value' => $paramval))); 453 $result = external_api::clean_returnvalue(external::get_content_returns(), $result); 454 $this->assertCount(1, $result['templates']); 455 $this->assertCount(1, $result['otherdata']); 456 $this->assertCount(2, $result['restrict']['users']); 457 $this->assertCount(2, $result['restrict']['courses']); 458 $this->assertEquals('alert();', $result['javascript']); 459 $this->assertEquals('main', $result['templates'][0]['id']); 460 $this->assertEquals('The HTML code', $result['templates'][0]['html']); 461 $this->assertEquals('otherdata1', $result['otherdata'][0]['name']); 462 $this->assertEquals($paramval, $result['otherdata'][0]['value']); 463 $this->assertEquals(array(1, 2), $result['restrict']['users']); 464 $this->assertEquals(array(3, 4), $result['restrict']['courses']); 465 $this->assertEmpty($result['files']); 466 $this->assertFalse($result['disabled']); 467 } 468 469 /** 470 * Test get_content disabled. 471 */ 472 public function test_get_content_disabled() { 473 474 $paramval = 16; 475 $result = external::get_content('tool_mobile', 'test_view_disabled', 476 array(array('name' => 'param1', 'value' => $paramval))); 477 $result = external_api::clean_returnvalue(external::get_content_returns(), $result); 478 $this->assertTrue($result['disabled']); 479 } 480 481 /** 482 * Test get_content non existent function in valid component. 483 */ 484 public function test_get_content_non_existent_function() { 485 486 $this->expectException('coding_exception'); 487 $result = external::get_content('tool_mobile', 'test_blahblah'); 488 } 489 490 /** 491 * Test get_content incorrect component. 492 */ 493 public function test_get_content_invalid_component() { 494 495 $this->expectException('moodle_exception'); 496 $result = external::get_content('tool_mobile\hack', 'test_view'); 497 } 498 499 /** 500 * Test get_content non existent component. 501 */ 502 public function test_get_content_non_existent_component() { 503 504 $this->expectException('moodle_exception'); 505 $result = external::get_content('tool_blahblahblah', 'test_view'); 506 } 507 508 public function test_call_external_functions() { 509 global $SESSION; 510 511 $this->resetAfterTest(true); 512 513 $category = self::getDataGenerator()->create_category(array('name' => 'Category 1')); 514 $course = self::getDataGenerator()->create_course([ 515 'category' => $category->id, 516 'shortname' => 'c1', 517 'summary' => '<span lang="en" class="multilang">Course summary</span>' 518 . '<span lang="eo" class="multilang">Kurso resumo</span>' 519 . '@@PLUGINFILE@@/filename.txt' 520 . '<!-- Comment stripped when formatting text -->', 521 'summaryformat' => FORMAT_MOODLE 522 ]); 523 $user1 = self::getDataGenerator()->create_user(['username' => 'user1', 'lastaccess' => time()]); 524 $user2 = self::getDataGenerator()->create_user(['username' => 'user2', 'lastaccess' => time()]); 525 526 self::setUser($user1); 527 528 // Setup WS token. 529 $webservicemanager = new \webservice; 530 $service = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE); 531 $token = \core_external\util::generate_token_for_current_user($service); 532 $_POST['wstoken'] = $token->token; 533 534 // Workaround for external_api::call_external_function requiring sesskey. 535 $_POST['sesskey'] = sesskey(); 536 537 // Call some functions. 538 $requests = [ 539 [ 540 'function' => 'core_course_get_courses_by_field', 541 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]) 542 ], 543 [ 544 'function' => 'core_user_get_users_by_field', 545 'arguments' => json_encode(['field' => 'id', 'values' => [$user1->id]]) 546 ], 547 [ 548 'function' => 'core_user_get_user_preferences', 549 'arguments' => json_encode(['name' => 'some_setting', 'userid' => $user2->id]) 550 ], 551 [ 552 'function' => 'core_course_get_courses_by_field', 553 'arguments' => json_encode(['field' => 'shortname', 'value' => $course->shortname]) 554 ], 555 ]; 556 $result = external::call_external_functions($requests); 557 558 // We need to execute the return values cleaning process to simulate the web service server. 559 $result = external_api::clean_returnvalue(external::call_external_functions_returns(), $result); 560 561 // Only 3 responses, the 4th request is not executed because the 3rd throws an exception. 562 $this->assertCount(3, $result['responses']); 563 564 $this->assertFalse($result['responses'][0]['error']); 565 $coursedata = external_api::clean_returnvalue( 566 \core_course_external::get_courses_by_field_returns(), 567 \core_course_external::get_courses_by_field('id', $course->id)); 568 $this->assertEquals(json_encode($coursedata), $result['responses'][0]['data']); 569 570 $this->assertFalse($result['responses'][1]['error']); 571 $userdata = external_api::clean_returnvalue( 572 \core_user_external::get_users_by_field_returns(), 573 \core_user_external::get_users_by_field('id', [$user1->id])); 574 $this->assertEquals(json_encode($userdata), $result['responses'][1]['data']); 575 576 $this->assertTrue($result['responses'][2]['error']); 577 $exception = json_decode($result['responses'][2]['exception'], true); 578 $this->assertEquals('nopermissions', $exception['errorcode']); 579 580 // Call a function not included in the external service. 581 582 $_POST['wstoken'] = $token->token; 583 $functions = $webservicemanager->get_not_associated_external_functions($service->id); 584 $requests = [['function' => current($functions)->name]]; 585 $result = external::call_external_functions($requests); 586 587 $this->assertTrue($result['responses'][0]['error']); 588 $exception = json_decode($result['responses'][0]['exception'], true); 589 $this->assertEquals('accessexception', $exception['errorcode']); 590 $this->assertEquals('webservice', $exception['module']); 591 592 // Call a function with different external settings. 593 594 filter_set_global_state('multilang', TEXTFILTER_ON); 595 $_POST['wstoken'] = $token->token; 596 $SESSION->lang = 'eo'; // Change default language, so we can test changing it to "en". 597 $requests = [ 598 [ 599 'function' => 'core_course_get_courses_by_field', 600 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]), 601 ], 602 [ 603 'function' => 'core_course_get_courses_by_field', 604 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]), 605 'settingraw' => '1' 606 ], 607 [ 608 'function' => 'core_course_get_courses_by_field', 609 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]), 610 'settingraw' => '1', 611 'settingfileurl' => '0' 612 ], 613 [ 614 'function' => 'core_course_get_courses_by_field', 615 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]), 616 'settingfilter' => '1', 617 'settinglang' => 'en' 618 ], 619 ]; 620 $result = external::call_external_functions($requests); 621 622 $this->assertCount(4, $result['responses']); 623 624 $context = \context_course::instance($course->id); 625 $pluginfile = 'webservice/pluginfile.php'; 626 627 $this->assertFalse($result['responses'][0]['error']); 628 $data = json_decode($result['responses'][0]['data']); 629 $expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null); 630 $expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => false]); 631 $this->assertEquals($expected, $data->courses[0]->summary); 632 633 $this->assertFalse($result['responses'][1]['error']); 634 $data = json_decode($result['responses'][1]['data']); 635 $expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null); 636 $this->assertEquals($expected, $data->courses[0]->summary); 637 638 $this->assertFalse($result['responses'][2]['error']); 639 $data = json_decode($result['responses'][2]['data']); 640 $this->assertEquals($course->summary, $data->courses[0]->summary); 641 642 $this->assertFalse($result['responses'][3]['error']); 643 $data = json_decode($result['responses'][3]['data']); 644 $expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null); 645 $SESSION->lang = 'en'; // We expect filtered text in english. 646 $expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => true]); 647 $this->assertEquals($expected, $data->courses[0]->summary); 648 } 649 650 /* 651 * Test get_tokens_for_qr_login. 652 */ 653 public function test_get_tokens_for_qr_login() { 654 global $DB, $CFG, $USER; 655 656 $this->resetAfterTest(true); 657 658 $user = $this->getDataGenerator()->create_user(); 659 $this->setUser($user); 660 661 $mobilesettings = get_config('tool_mobile'); 662 $mobilesettings->qrsameipcheck = 1; 663 $qrloginkey = api::get_qrlogin_key($mobilesettings); 664 665 // Generate new tokens, the ones we expect to receive. 666 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE)); 667 $token = \core_external\util::generate_token_for_current_user($service); 668 669 // Fake the app. 670 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 671 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 672 673 $result = external::get_tokens_for_qr_login($qrloginkey, $USER->id); 674 $result = external_api::clean_returnvalue(external::get_tokens_for_qr_login_returns(), $result); 675 676 $this->assertEmpty($result['warnings']); 677 $this->assertEquals($token->token, $result['token']); 678 $this->assertEquals($token->privatetoken, $result['privatetoken']); 679 680 // Now, try with an invalid key. 681 $this->expectException('moodle_exception'); 682 $this->expectExceptionMessage(get_string('invalidkey', 'error')); 683 $result = external::get_tokens_for_qr_login(random_string('64'), $user->id); 684 } 685 686 /* 687 * Test get_tokens_for_qr_login ignore ip check. 688 */ 689 public function test_get_tokens_for_qr_login_ignore_ip_check() { 690 global $DB, $CFG, $USER; 691 692 $this->resetAfterTest(true); 693 694 $user = $this->getDataGenerator()->create_user(); 695 $this->setUser($user); 696 697 $mobilesettings = get_config('tool_mobile'); 698 $mobilesettings->qrsameipcheck = 0; 699 $qrloginkey = api::get_qrlogin_key($mobilesettings); 700 701 $key = $DB->get_record('user_private_key', ['value' => $qrloginkey]); 702 $this->assertNull($key->iprestriction); 703 704 // Generate new tokens, the ones we expect to receive. 705 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE)); 706 $token = \core_external\util::generate_token_for_current_user($service); 707 708 // Fake the app. 709 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 710 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 711 712 $result = external::get_tokens_for_qr_login($qrloginkey, $USER->id); 713 $result = external_api::clean_returnvalue(external::get_tokens_for_qr_login_returns(), $result); 714 715 $this->assertEmpty($result['warnings']); 716 $this->assertEquals($token->token, $result['token']); 717 $this->assertEquals($token->privatetoken, $result['privatetoken']); 718 719 // Now, try with an invalid key. 720 $this->expectException('moodle_exception'); 721 $this->expectExceptionMessage(get_string('invalidkey', 'error')); 722 $result = external::get_tokens_for_qr_login(random_string('64'), $user->id); 723 } 724 725 /* 726 * Test get_tokens_for_qr_login ip check fails. 727 */ 728 public function test_get_tokens_for_qr_login_ip_check_mismatch() { 729 global $DB, $CFG, $USER; 730 731 $this->resetAfterTest(true); 732 733 $user = $this->getDataGenerator()->create_user(); 734 $this->setUser($user); 735 736 $mobilesettings = get_config('tool_mobile'); 737 $mobilesettings->qrsameipcheck = 1; 738 $qrloginkey = api::get_qrlogin_key($mobilesettings); 739 740 // Alter expected ip. 741 $DB->set_field('user_private_key', 'iprestriction', '6.6.6.6', ['value' => $qrloginkey]); 742 743 // Generate new tokens, the ones we expect to receive. 744 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE)); 745 $token = \core_external\util::generate_token_for_current_user($service); 746 747 // Fake the app. 748 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 749 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 750 751 $this->expectException('moodle_exception'); 752 $this->expectExceptionMessage(get_string('ipmismatch', 'error')); 753 $result = external::get_tokens_for_qr_login($qrloginkey, $USER->id); 754 } 755 756 /** 757 * Test get_tokens_for_qr_login missing QR code enabled. 758 */ 759 public function test_get_tokens_for_qr_login_missing_enableqr() { 760 global $CFG, $USER; 761 $this->resetAfterTest(true); 762 $this->setAdminUser(); 763 764 set_config('qrcodetype', api::QR_CODE_DISABLED, 'tool_mobile'); 765 766 $this->expectExceptionMessage(get_string('qrcodedisabled', 'tool_mobile')); 767 $result = external::get_tokens_for_qr_login('', $USER->id); 768 } 769 770 /** 771 * Test get_tokens_for_qr_login missing ws. 772 */ 773 public function test_get_tokens_for_qr_login_missing_ws() { 774 global $CFG; 775 $this->resetAfterTest(true); 776 777 $user = $this->getDataGenerator()->create_user(); 778 $this->setUser($user); 779 780 // Fake the app. 781 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 782 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 783 784 // Need to disable webservices to verify that's checked. 785 $CFG->enablewebservices = 0; 786 $CFG->enablemobilewebservice = 0; 787 788 $this->setAdminUser(); 789 $this->expectException('moodle_exception'); 790 $this->expectExceptionMessage(get_string('enablewsdescription', 'webservice')); 791 $result = external::get_tokens_for_qr_login('', $user->id); 792 } 793 794 /** 795 * Test get_tokens_for_qr_login missing https. 796 */ 797 public function test_get_tokens_for_qr_login_missing_https() { 798 global $CFG, $USER; 799 800 // Fake the app. 801 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 802 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 803 804 // Need to simulate a non HTTPS site here. 805 $CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot); 806 807 $this->resetAfterTest(true); 808 $this->setAdminUser(); 809 810 $this->expectException('moodle_exception'); 811 $this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile')); 812 $result = external::get_tokens_for_qr_login('', $USER->id); 813 } 814 815 /** 816 * Test get_tokens_for_qr_login missing admin. 817 */ 818 public function test_get_tokens_for_qr_login_missing_admin() { 819 global $CFG, $USER; 820 821 $this->resetAfterTest(true); 822 $this->setAdminUser(); 823 824 // Fake the app. 825 \core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' . 826 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'); 827 828 $this->expectException('moodle_exception'); 829 $this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile')); 830 $result = external::get_tokens_for_qr_login('', $USER->id); 831 } 832 833 /** 834 * Test get_tokens_for_qr_login missing app_request. 835 */ 836 public function test_get_tokens_for_qr_login_missing_app_request() { 837 global $CFG, $USER; 838 839 $this->resetAfterTest(true); 840 $this->setAdminUser(); 841 842 $this->expectException('moodle_exception'); 843 $this->expectExceptionMessage(get_string('apprequired', 'tool_mobile')); 844 $result = external::get_tokens_for_qr_login('', $USER->id); 845 } 846 847 /** 848 * Test validate subscription key. 849 */ 850 public function test_validate_subscription_key_valid() { 851 $this->resetAfterTest(true); 852 853 $sitesubscriptionkey = ['validuntil' => time() + MINSECS, 'key' => complex_random_string(32)]; 854 set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile'); 855 856 $result = external::validate_subscription_key($sitesubscriptionkey['key']); 857 $result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result); 858 $this->assertEmpty($result['warnings']); 859 $this->assertTrue($result['validated']); 860 } 861 862 /** 863 * Test validate subscription key invalid first and then a valid one. 864 */ 865 public function test_validate_subscription_key_invalid_key_first() { 866 $this->resetAfterTest(true); 867 868 $sitesubscriptionkey = ['validuntil' => time() + MINSECS, 'key' => complex_random_string(32)]; 869 set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile'); 870 871 $result = external::validate_subscription_key('fakekey'); 872 $result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result); 873 $this->assertEmpty($result['warnings']); 874 $this->assertFalse($result['validated']); 875 876 // The valid one has been invalidated because the previous attempt. 877 $result = external::validate_subscription_key($sitesubscriptionkey['key']); 878 $result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result); 879 $this->assertEmpty($result['warnings']); 880 $this->assertFalse($result['validated']); 881 } 882 883 /** 884 * Test validate subscription key invalid. 885 */ 886 public function test_validate_subscription_key_invalid_key() { 887 $this->resetAfterTest(true); 888 889 $result = external::validate_subscription_key('fakekey'); 890 $result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result); 891 $this->assertEmpty($result['warnings']); 892 $this->assertFalse($result['validated']); 893 } 894 895 /** 896 * Test validate subscription key invalid. 897 */ 898 public function test_validate_subscription_key_outdated() { 899 $this->resetAfterTest(true); 900 901 $sitesubscriptionkey = ['validuntil' => time() - MINSECS, 'key' => complex_random_string(32)]; 902 set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile'); 903 904 $result = external::validate_subscription_key($sitesubscriptionkey['key']); 905 $result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result); 906 $this->assertEmpty($result['warnings']); 907 $this->assertFalse($result['validated']); 908 } 909 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body