Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | Ultima modificación | Ver Log |

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