Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
/**
18
 * This is the external API for this tool.
19
 *
20
 * @package    tool_mobile
21
 * @copyright  2016 Juan Leyva
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace tool_mobile;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
require_once("$CFG->dirroot/webservice/lib.php");
29
 
30
use core_external\external_api;
31
use core_external\external_files;
32
use core_external\external_function_parameters;
33
use core_external\external_multiple_structure;
34
use core_external\external_single_structure;
35
use core_external\external_settings;
36
use core_external\external_value;
37
use core_external\external_warnings;
38
use context_system;
39
use moodle_exception;
40
use moodle_url;
41
use core_user;
42
use coding_exception;
43
 
44
/**
45
 * This is the external API for this tool.
46
 *
47
 * @copyright  2016 Juan Leyva
48
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49
 */
50
class external extends external_api {
51
 
52
    /**
53
     * Returns description of get_plugins_supporting_mobile() parameters.
54
     *
55
     * @return external_function_parameters
56
     * @since  Moodle 3.1
57
     */
58
    public static function get_plugins_supporting_mobile_parameters() {
59
        return new external_function_parameters(array());
60
    }
61
 
62
    /**
63
     * Returns a list of Moodle plugins supporting the mobile app.
64
     *
65
     * @return array an array of warnings and objects containing the plugin information
66
     * @since  Moodle 3.1
67
     */
68
    public static function get_plugins_supporting_mobile() {
69
        return array(
70
            'plugins' => api::get_plugins_supporting_mobile(),
71
            'warnings' => array(),
72
        );
73
    }
74
 
75
    /**
76
     * Returns description of get_plugins_supporting_mobile() result value.
77
     *
78
     * @return \core_external\external_description
79
     * @since  Moodle 3.1
80
     */
81
    public static function get_plugins_supporting_mobile_returns() {
82
        return new external_single_structure(
83
            array(
84
                'plugins' => new external_multiple_structure(
85
                    new external_single_structure(
86
                        array(
87
                            'component' => new external_value(PARAM_COMPONENT, 'The plugin component name.'),
88
                            'version' => new external_value(PARAM_NOTAGS, 'The plugin version number.'),
89
                            'addon' => new external_value(PARAM_COMPONENT, 'The Mobile addon (package) name.'),
90
                            'dependencies' => new external_multiple_structure(
91
                                                new external_value(PARAM_COMPONENT, 'Mobile addon name.'),
92
                                                'The list of Mobile addons this addon depends on.'
93
                                               ),
94
                            'fileurl' => new external_value(PARAM_URL, 'The addon package url for download
95
                                                            or empty if it doesn\'t exist.'),
96
                            'filehash' => new external_value(PARAM_RAW, 'The addon package hash or empty if it doesn\'t exist.'),
97
                            'filesize' => new external_value(PARAM_INT, 'The addon package size or empty if it doesn\'t exist.'),
98
                            'handlers' => new external_value(PARAM_RAW, 'Handlers definition (JSON)', VALUE_OPTIONAL),
99
                            'lang' => new external_value(PARAM_RAW, 'Language strings used by the handlers (JSON)', VALUE_OPTIONAL),
100
                        )
101
                    )
102
                ),
103
                'warnings' => new external_warnings(),
104
            )
105
        );
106
    }
107
 
108
    /**
109
     * Returns description of get_public_config() parameters.
110
     *
111
     * @return external_function_parameters
112
     * @since  Moodle 3.2
113
     */
114
    public static function get_public_config_parameters() {
115
        return new external_function_parameters(array());
116
    }
117
 
118
    /**
119
     * Returns a list of the site public settings, those not requiring authentication.
120
     *
121
     * @return array with the settings and warnings
122
     * @since  Moodle 3.2
123
     */
124
    public static function get_public_config() {
125
        $result = api::get_public_config();
126
        $result['warnings'] = array();
127
        return $result;
128
    }
129
 
130
    /**
131
     * Returns description of get_public_config() result value.
132
     *
133
     * @return \core_external\external_description
134
     * @since  Moodle 3.2
135
     */
136
    public static function get_public_config_returns() {
137
        return new external_single_structure(
138
            array(
139
                'wwwroot' => new external_value(PARAM_RAW, 'Site URL.'),
140
                'httpswwwroot' => new external_value(PARAM_RAW, 'Site https URL (if httpslogin is enabled).'),
141
                'sitename' => new external_value(PARAM_RAW, 'Site name.'),
142
                'guestlogin' => new external_value(PARAM_INT, 'Whether guest login is enabled.'),
143
                'rememberusername' => new external_value(PARAM_INT, 'Values: 0 for No, 1 for Yes, 2 for optional.'),
144
                'authloginviaemail' => new external_value(PARAM_INT, 'Whether log in via email is enabled.'),
145
                'registerauth' => new external_value(PARAM_PLUGIN, 'Authentication method for user registration.'),
146
                'forgottenpasswordurl' => new external_value(PARAM_URL, 'Forgotten password URL.'),
147
                'authinstructions' => new external_value(PARAM_RAW, 'Authentication instructions.'),
148
                'authnoneenabled' => new external_value(PARAM_INT, 'Whether auth none is enabled.'),
149
                'enablewebservices' => new external_value(PARAM_INT, 'Whether Web Services are enabled.'),
150
                'enablemobilewebservice' => new external_value(PARAM_INT, 'Whether the Mobile service is enabled.'),
151
                'maintenanceenabled' => new external_value(PARAM_INT, 'Whether site maintenance is enabled.'),
152
                'maintenancemessage' => new external_value(PARAM_RAW, 'Maintenance message.'),
153
                'logourl' => new external_value(PARAM_URL, 'The site logo URL', VALUE_OPTIONAL),
154
                'compactlogourl' => new external_value(PARAM_URL, 'The site compact logo URL', VALUE_OPTIONAL),
155
                'typeoflogin' => new external_value(PARAM_INT, 'The type of login. 1 for app, 2 for browser, 3 for embedded.'),
156
                'launchurl' => new external_value(PARAM_URL, 'SSO login launch URL.', VALUE_OPTIONAL),
157
                'mobilecssurl' => new external_value(PARAM_URL, 'Mobile custom CSS theme', VALUE_OPTIONAL),
158
                'tool_mobile_disabledfeatures' => new external_value(PARAM_RAW, 'Disabled features in the app', VALUE_OPTIONAL),
159
                'identityproviders' => new external_multiple_structure(
160
                    new external_single_structure(
161
                        array(
162
                            'name' => new external_value(PARAM_TEXT, 'The identity provider name.'),
163
                            'iconurl' => new external_value(PARAM_URL, 'The icon URL for the provider.'),
164
                            'url' => new external_value(PARAM_URL, 'The URL of the provider.'),
165
                        )
166
                    ),
167
                    'Identity providers', VALUE_OPTIONAL
168
                ),
169
                'country' => new external_value(PARAM_NOTAGS, 'Default site country', VALUE_OPTIONAL),
170
                'agedigitalconsentverification' => new external_value(PARAM_BOOL, 'Whether age digital consent verification
171
                    is enabled.', VALUE_OPTIONAL),
172
                'supportname' => new external_value(PARAM_NOTAGS, 'Site support contact name
173
                    (only if age verification is enabled).', VALUE_OPTIONAL),
174
                'supportemail' => new external_value(PARAM_EMAIL, 'Site support contact email
175
                    (only if age verification is enabled).', VALUE_OPTIONAL),
176
                'supportpage' => new external_value(PARAM_URL, 'Site support page link.', VALUE_OPTIONAL),
177
                'supportavailability' => new external_value(PARAM_INT, 'Determines who has access to contact site support.',
178
                    VALUE_OPTIONAL),
179
                'autolang' => new external_value(PARAM_INT, 'Whether to detect default language
180
                    from browser setting.', VALUE_OPTIONAL),
181
                'lang' => new external_value(PARAM_LANG, 'Default language for the site.', VALUE_OPTIONAL),
182
                'langmenu' => new external_value(PARAM_INT, 'Whether the language menu should be displayed.', VALUE_OPTIONAL),
183
                'langlist' => new external_value(PARAM_RAW, 'Languages on language menu.', VALUE_OPTIONAL),
184
                'locale' => new external_value(PARAM_RAW, 'Sitewide locale.', VALUE_OPTIONAL),
185
                'tool_mobile_minimumversion' => new external_value(PARAM_NOTAGS, 'Minimum required version to access.',
186
                    VALUE_OPTIONAL),
187
                'tool_mobile_iosappid' => new external_value(PARAM_ALPHANUM, 'iOS app\'s unique identifier.',
188
                    VALUE_OPTIONAL),
189
                'tool_mobile_androidappid' => new external_value(PARAM_NOTAGS, 'Android app\'s unique identifier.',
190
                    VALUE_OPTIONAL),
191
                'tool_mobile_setuplink' => new external_value(PARAM_URL, 'App download page.', VALUE_OPTIONAL),
192
                'tool_mobile_qrcodetype' => new external_value(PARAM_INT, 'QR login configuration.', VALUE_OPTIONAL),
193
                'warnings' => new external_warnings(),
1441 ariadna 194
                'showloginform' => new external_value(PARAM_INT, 'Display default login form.'),
1 efrain 195
            )
196
        );
197
    }
198
 
199
    /**
200
     * Returns description of get_config() parameters.
201
     *
202
     * @return external_function_parameters
203
     * @since  Moodle 3.2
204
     */
205
    public static function get_config_parameters() {
206
        return new external_function_parameters(
207
            array(
208
                'section' => new external_value(PARAM_ALPHANUMEXT, 'Settings section name.', VALUE_DEFAULT, ''),
209
            )
210
        );
211
    }
212
 
213
    /**
214
     * Returns a list of site settings, filtering by section.
215
     *
216
     * @param string $section settings section name
217
     * @return array with the settings and warnings
218
     * @since  Moodle 3.2
219
     */
220
    public static function get_config($section = '') {
221
 
222
        $params = self::validate_parameters(self::get_config_parameters(), array('section' => $section));
223
 
224
        $settings = api::get_config($params['section']);
225
        $result['settings'] = array();
226
        foreach ($settings as $name => $value) {
227
            $result['settings'][] = array(
228
                'name' => $name,
229
                'value' => $value,
230
            );
231
        }
232
 
233
        $result['warnings'] = array();
234
        return $result;
235
    }
236
 
237
    /**
238
     * Returns description of get_config() result value.
239
     *
240
     * @return \core_external\external_description
241
     * @since  Moodle 3.2
242
     */
243
    public static function get_config_returns() {
244
        return new external_single_structure(
245
            array(
246
                'settings' => new external_multiple_structure(
247
                    new external_single_structure(
248
                        array(
249
                            'name' => new external_value(PARAM_RAW, 'The name of the setting'),
250
                            'value' => new external_value(PARAM_RAW, 'The value of the setting'),
251
                        )
252
                    ),
253
                    'Settings'
254
                ),
255
                'warnings' => new external_warnings(),
256
            )
257
        );
258
    }
259
 
260
    /**
261
     * Returns description of get_autologin_key() parameters.
262
     *
263
     * @return external_function_parameters
264
     * @since  Moodle 3.2
265
     */
266
    public static function get_autologin_key_parameters() {
267
        return new external_function_parameters (
268
            array(
269
                'privatetoken' => new external_value(PARAM_ALPHANUM, 'Private token, usually generated by login/token.php'),
270
            )
271
        );
272
    }
273
 
274
    /**
275
     * Creates an auto-login key for the current user. Is created only in https sites and is restricted by time and ip address.
276
     *
277
     * Please note that it only works if the request comes from the Moodle mobile or desktop app.
278
     *
279
     * @param string $privatetoken the user private token for validating the request
280
     * @return array with the settings and warnings
281
     * @since  Moodle 3.2
282
     */
283
    public static function get_autologin_key($privatetoken) {
284
        global $CFG, $DB, $USER;
285
 
286
        $params = self::validate_parameters(self::get_autologin_key_parameters(), array('privatetoken' => $privatetoken));
287
        $privatetoken = $params['privatetoken'];
288
 
289
        $context = context_system::instance();
290
 
291
        // We must toletare these two exceptions: forcepasswordchangenotice and usernotfullysetup.
292
        try {
293
            self::validate_context($context);
294
        } catch (moodle_exception $e) {
295
            if ($e->errorcode != 'usernotfullysetup' && $e->errorcode != 'forcepasswordchangenotice') {
296
                // In case we receive a different exception, throw it.
297
                throw $e;
298
            }
299
        }
300
 
301
        // Only requests from the Moodle mobile or desktop app. This enhances security to avoid any type of XSS attack.
302
        // This code goes intentionally here and not inside the check_autologin_prerequisites() function because it
303
        // is used by other PHP scripts that can be opened in any browser.
304
        if (!\core_useragent::is_moodle_app()) {
305
            throw new moodle_exception('apprequired', 'tool_mobile');
306
        }
307
        api::check_autologin_prerequisites($USER->id);
308
 
309
        if (isset($_GET['privatetoken']) or empty($privatetoken)) {
310
            throw new moodle_exception('invalidprivatetoken', 'tool_mobile');
311
        }
312
 
313
        // Check the request counter, we must limit the number of times the privatetoken is sent.
314
        // Between each request 6 minutes are required.
315
        $last = get_user_preferences('tool_mobile_autologin_request_last', 0, $USER);
316
        // Check if we must reset the count.
317
        $mintimereq = get_config('tool_mobile', 'autologinmintimebetweenreq');
318
        $mintimereq = empty($mintimereq) ? 6 * MINSECS : $mintimereq;
319
        $timenow = time();
320
        if ($timenow - $last < $mintimereq) {
321
            $minutes = $mintimereq / MINSECS;
322
            throw new moodle_exception('autologinkeygenerationlockout', 'tool_mobile', '', $minutes);
323
        }
324
        set_user_preference('tool_mobile_autologin_request_last', $timenow, $USER);
325
 
326
        // We are expecting a privatetoken linked to the current token being used.
327
        // This WS is only valid when using mobile services via REST (this is intended).
328
        $currenttoken = required_param('wstoken', PARAM_ALPHANUM);
329
        $conditions = array(
330
            'userid' => $USER->id,
331
            'token' => $currenttoken,
332
            'privatetoken' => $privatetoken,
333
        );
334
        if (!$token = $DB->get_record('external_tokens', $conditions)) {
335
            throw new moodle_exception('invalidprivatetoken', 'tool_mobile');
336
        }
337
 
338
        $result = array();
339
        $result['key'] = api::get_autologin_key();
340
        $autologinurl = new moodle_url("/$CFG->admin/tool/mobile/autologin.php");
341
        $result['autologinurl'] = $autologinurl->out(false);
342
        $result['warnings'] = array();
343
        return $result;
344
    }
345
 
346
    /**
347
     * Returns description of get_autologin_key() result value.
348
     *
349
     * @return \core_external\external_description
350
     * @since  Moodle 3.2
351
     */
352
    public static function get_autologin_key_returns() {
353
        return new external_single_structure(
354
            array(
355
                'key' => new external_value(PARAM_ALPHANUMEXT, 'Auto-login key for a single usage with time expiration.'),
356
                'autologinurl' => new external_value(PARAM_URL, 'Auto-login URL.'),
357
                'warnings' => new external_warnings(),
358
            )
359
        );
360
    }
361
 
362
    /**
363
     * Returns description of get_content() parameters
364
     *
365
     * @return external_function_parameters
366
     * @since Moodle 3.5
367
     */
368
    public static function get_content_parameters() {
369
        return new external_function_parameters(
370
            array(
371
                'component' => new external_value(PARAM_COMPONENT, 'Component where the class is e.g. mod_assign.'),
372
                'method' => new external_value(PARAM_ALPHANUMEXT, 'Method to execute in class \$component\output\mobile.'),
373
                'args' => new external_multiple_structure(
374
                    new external_single_structure(
375
                        array(
376
                            'name' => new external_value(PARAM_ALPHANUMEXT, 'Param name.'),
377
                            'value' => new external_value(PARAM_RAW, 'Param value.')
378
                        )
379
                    ), 'Args for the method are optional.', VALUE_OPTIONAL
380
                )
381
            )
382
        );
383
    }
384
 
385
    /**
386
     * Returns a piece of content to be displayed in the Mobile app, it usually returns a template, javascript and
387
     * other structured data that will be used to render a view in the Mobile app.
388
     *
389
     * Callbacks (placed in \$component\output\mobile) that are called by this web service are responsible for doing the
390
     * appropriate security checks to access the information to be returned.
391
     *
392
     * @param string $component name of the component.
393
     * @param string $method function method name in class \$component\output\mobile.
394
     * @param array $args optional arguments for the method.
395
     * @return array HTML, JavaScript and other required data and information to create a view in the app.
396
     * @since Moodle 3.5
397
     * @throws coding_exception
398
     */
399
    public static function get_content($component, $method, $args = array()) {
400
        global $OUTPUT, $PAGE, $USER;
401
 
402
        $params = self::validate_parameters(self::get_content_parameters(),
403
            array(
404
                'component' => $component,
405
                'method' => $method,
406
                'args' => $args
407
            )
408
        );
409
 
410
        // Reformat arguments into something less unwieldy.
411
        $arguments = array();
412
        foreach ($params['args'] as $paramargument) {
413
            $arguments[$paramargument['name']] = $paramargument['value'];
414
        }
415
 
416
        // The component was validated via the PARAM_COMPONENT parameter type.
417
        $classname = '\\' . $params['component'] .'\output\mobile';
418
        if (!method_exists($classname, $params['method'])) {
419
            throw new coding_exception("Missing method in $classname");
420
        }
421
        $result = call_user_func_array(array($classname, $params['method']), array($arguments));
422
 
423
        // Populate otherdata.
424
        $otherdata = array();
425
        if (!empty($result['otherdata'])) {
426
            $result['otherdata'] = (array) $result['otherdata'];
427
            foreach ($result['otherdata'] as $name => $value) {
428
                $otherdata[] = array(
429
                    'name' => $name,
430
                    'value' => $value
431
                );
432
            }
433
        }
434
 
435
        return array(
436
            'templates'  => !empty($result['templates']) ? $result['templates'] : array(),
437
            'javascript' => !empty($result['javascript']) ? $result['javascript'] : '',
438
            'otherdata'  => $otherdata,
439
            'files'      => !empty($result['files']) ? $result['files'] : array(),
440
            'restrict'   => !empty($result['restrict']) ? $result['restrict'] : array(),
441
            'disabled'   => !empty($result['disabled']) ? true : false,
442
        );
443
    }
444
 
445
    /**
446
     * Returns description of get_content() result value
447
     *
448
     * @return array
449
     * @since Moodle 3.5
450
     */
451
    public static function get_content_returns() {
452
        return new external_single_structure(
453
            array(
454
                'templates' => new external_multiple_structure(
455
                    new external_single_structure(
456
                        array(
457
                            'id' => new external_value(PARAM_TEXT, 'ID of the template.'),
458
                            'html' => new external_value(PARAM_RAW, 'HTML code.'),
459
                        )
460
                    ),
461
                    'Templates required by the generated content.'
462
                ),
463
                'javascript' => new external_value(PARAM_RAW, 'JavaScript code.'),
464
                'otherdata' => new external_multiple_structure(
465
                    new external_single_structure(
466
                        array(
467
                            'name' => new external_value(PARAM_RAW, 'Field name.'),
468
                            'value' => new external_value(PARAM_RAW, 'Field value.')
469
                        )
470
                    ),
471
                    'Other data that can be used or manipulated by the template via 2-way data-binding.'
472
                ),
473
                'files' => new external_files('Files in the content.'),
474
                'restrict' => new external_single_structure(
475
                    array(
476
                        'users' => new external_multiple_structure(
477
                            new external_value(PARAM_INT, 'user id'), 'List of allowed users.', VALUE_OPTIONAL
478
                        ),
479
                        'courses' => new external_multiple_structure(
480
                            new external_value(PARAM_INT, 'course id'), 'List of allowed courses.', VALUE_OPTIONAL
481
                        ),
482
                    ),
483
                    'Restrict this content to certain users or courses.'
484
                ),
485
                'disabled' => new external_value(PARAM_BOOL, 'Whether we consider this disabled or not.', VALUE_OPTIONAL),
486
            )
487
        );
488
    }
489
 
490
    /**
491
     * Returns description of method parameters
492
     *
493
     * @return external_function_parameters
494
     * @since Moodle 3.7
495
     */
496
    public static function call_external_functions_parameters() {
497
        return new external_function_parameters([
498
            'requests' => new external_multiple_structure(
499
                new external_single_structure([
500
                    'function' => new external_value(PARAM_ALPHANUMEXT, 'Function name'),
501
                    'arguments' => new external_value(PARAM_RAW, 'JSON-encoded object with named arguments', VALUE_DEFAULT, '{}'),
502
                    'settingraw' => new external_value(PARAM_BOOL, 'Return raw text', VALUE_DEFAULT, false),
503
                    'settingfilter' => new external_value(PARAM_BOOL, 'Filter text', VALUE_DEFAULT, false),
504
                    'settingfileurl' => new external_value(PARAM_BOOL, 'Rewrite plugin file URLs', VALUE_DEFAULT, true),
505
                    'settinglang' => new external_value(PARAM_LANG, 'Session language', VALUE_DEFAULT, ''),
506
                ])
507
            )
508
        ]);
509
    }
510
 
511
    /**
512
     * Call multiple external functions and return all responses.
513
     *
514
     * @param array $requests List of requests.
515
     * @return array Responses.
516
     * @since Moodle 3.7
517
     */
518
    public static function call_external_functions($requests) {
519
        global $SESSION;
520
 
521
        $params = self::validate_parameters(self::call_external_functions_parameters(), ['requests' => $requests]);
522
 
523
        // We need to check if the functions being called are included in the service of the current token.
524
        // This function only works when using mobile services via REST (this is intended).
525
        $webservicemanager = new \webservice;
526
        $token = $webservicemanager->get_user_ws_token(required_param('wstoken', PARAM_ALPHANUM));
527
 
528
        $settings = external_settings::get_instance();
529
        $defaultlang = current_language();
530
        $responses = [];
531
 
532
        foreach ($params['requests'] as $request) {
533
            // Some external functions modify _GET or $_POST data, we need to restore the original data after each call.
534
            $originalget = fullclone($_GET);
535
            $originalpost = fullclone($_POST);
536
 
537
            // Set external settings and language.
538
            $settings->set_raw($request['settingraw']);
539
            $settings->set_filter($request['settingfilter']);
540
            $settings->set_fileurl($request['settingfileurl']);
541
            $settings->set_lang($request['settinglang']);
542
            $SESSION->lang = $request['settinglang'] ?: $defaultlang;
543
 
544
            // Parse arguments to an array, validation is done in external_api::call_external_function.
545
            $args = @json_decode($request['arguments'], true);
546
            if (!is_array($args)) {
547
                $args = [];
548
            }
549
 
550
            if ($webservicemanager->service_function_exists($request['function'], $token->externalserviceid)) {
551
                $response = external_api::call_external_function($request['function'], $args, false);
552
            } else {
553
                // Function not included in the service, return an access exception.
554
                $response = [
555
                    'error' => true,
556
                    'exception' => [
557
                        'errorcode' => 'accessexception',
558
                        'module' => 'webservice'
559
                    ]
560
                ];
561
                if (debugging('', DEBUG_DEVELOPER)) {
562
                    $response['exception']['debuginfo'] = 'Access to the function is not allowed.';
563
                }
564
            }
565
 
566
            if (isset($response['data'])) {
567
                $response['data'] = json_encode($response['data']);
568
            }
569
            if (isset($response['exception'])) {
570
                $response['exception'] = json_encode($response['exception']);
571
            }
572
            $responses[] = $response;
573
 
574
            // Restore original $_GET and $_POST.
575
            $_GET = $originalget;
576
            $_POST = $originalpost;
577
 
578
            if ($response['error']) {
579
                // Do not process the remaining requests.
580
                break;
581
            }
582
        }
583
 
584
        return ['responses' => $responses];
585
    }
586
 
587
    /**
588
     * Returns description of method result value
589
     *
590
     * @return external_single_structure
591
     * @since Moodle 3.7
592
     */
593
    public static function call_external_functions_returns() {
594
        return new external_function_parameters([
595
            'responses' => new external_multiple_structure(
596
                new external_single_structure([
597
                    'error' => new external_value(PARAM_BOOL, 'Whether an exception was thrown.'),
598
                    'data' => new external_value(PARAM_RAW, 'JSON-encoded response data', VALUE_OPTIONAL),
599
                    'exception' => new external_value(PARAM_RAW, 'JSON-encoed exception info', VALUE_OPTIONAL),
600
                ])
601
             )
602
        ]);
603
    }
604
 
605
    /**
606
     * Returns description of get_tokens_for_qr_login() parameters.
607
     *
608
     * @return external_function_parameters
609
     * @since  Moodle 3.9
610
     */
611
    public static function get_tokens_for_qr_login_parameters() {
612
        return new external_function_parameters (
613
            [
614
                'qrloginkey' => new external_value(PARAM_ALPHANUMEXT, 'The user key for validating the request.'),
615
                'userid' => new external_value(PARAM_INT, 'The user the key belongs to.'),
616
            ]
617
        );
618
    }
619
 
620
    /**
621
     * Returns a WebService token (and private token) for QR login
622
     *
623
     * @param string $qrloginkey the user key generated and embedded into the QR code for validating the request
624
     * @param int $userid the user the key belongs to
625
     * @return array with the tokens and warnings
626
     * @since  Moodle 3.9
627
     */
628
    public static function get_tokens_for_qr_login($qrloginkey, $userid) {
629
        global $PAGE, $DB;
630
 
631
        $params = self::validate_parameters(self::get_tokens_for_qr_login_parameters(),
632
            ['qrloginkey' => $qrloginkey, 'userid' => $userid]);
633
 
634
        $context = context_system::instance();
635
        // We need this to make work the format text functions.
636
        $PAGE->set_context($context);
637
 
638
        $qrcodetype = get_config('tool_mobile', 'qrcodetype');
639
        if ($qrcodetype != api::QR_CODE_LOGIN) {
640
            throw new moodle_exception('qrcodedisabled', 'tool_mobile');
641
        }
642
 
643
        // Only requests from the Moodle mobile or desktop app. This enhances security to avoid any type of XSS attack.
644
        // This code goes intentionally here and not inside the check_autologin_prerequisites() function because it
645
        // is used by other PHP scripts that can be opened in any browser.
646
        if (!\core_useragent::is_moodle_app()) {
647
            throw new moodle_exception('apprequired', 'tool_mobile');
648
        }
649
        api::check_autologin_prerequisites($params['userid']);  // Checks https, avoid site admins using this...
650
 
651
        // Validate and delete the key.
652
        $key = validate_user_key($params['qrloginkey'], 'tool_mobile/qrlogin', null);
653
        delete_user_key('tool_mobile/qrlogin', $params['userid']);
654
 
655
        // Double check key belong to user.
656
        if ($key->userid != $params['userid']) {
657
            throw new moodle_exception('invalidkey');
658
        }
659
 
660
        // Key validated, check user.
661
        $user = core_user::get_user($key->userid, '*', MUST_EXIST);
662
        core_user::require_active_user($user, true, true);
663
 
664
        // Generate WS tokens.
665
        \core\session\manager::set_user($user);
666
 
667
        // Check if the service exists and is enabled.
668
        $service = $DB->get_record('external_services', ['shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE, 'enabled' => 1]);
669
        if (empty($service)) {
670
            // will throw exception if no token found
671
            throw new moodle_exception('servicenotavailable', 'webservice');
672
        }
673
 
674
        // Get an existing token or create a new one.
675
        $token = \core_external\util::generate_token_for_current_user($service);
676
        $privatetoken = $token->privatetoken; // Save it here, the next function removes it.
677
        \core_external\util::log_token_request($token);
678
 
679
        $result = [
680
            'token' => $token->token,
681
            'privatetoken' => $privatetoken ?: '',
682
            'warnings' => [],
683
        ];
684
        return $result;
685
    }
686
 
687
    /**
688
     * Returns description of get_tokens_for_qr_login() result value.
689
     *
690
     * @return \core_external\external_description
691
     * @since  Moodle 3.9
692
     */
693
    public static function get_tokens_for_qr_login_returns() {
694
        return new external_single_structure(
695
            [
696
                'token' => new external_value(PARAM_ALPHANUM, 'A valid WebService token for the official mobile app service.'),
697
                'privatetoken' => new external_value(PARAM_ALPHANUM, 'Private token used for auto-login processes.'),
698
                'warnings' => new external_warnings(),
699
            ]
700
        );
701
    }
702
 
703
    /**
704
     * Returns description of validate_subscription_key() parameters.
705
     *
706
     * @return external_function_parameters
707
     * @since  Moodle 3.9
708
     */
709
    public static function validate_subscription_key_parameters() {
710
        return new external_function_parameters(
711
            [
712
                'key' => new external_value(PARAM_RAW, 'Site subscription temporary key.'),
713
            ]
714
        );
715
    }
716
 
717
    /**
718
     * Check if the given site subscription key is valid
719
     *
720
     * @param string $key subscriptiion temporary key
721
     * @return array with the settings and warnings
722
     * @since  Moodle 3.9
723
     */
724
    public static function validate_subscription_key(string $key): array {
725
        global $CFG, $PAGE;
726
 
727
        $params = self::validate_parameters(self::validate_subscription_key_parameters(), ['key' => $key]);
728
 
729
        $context = context_system::instance();
730
        $PAGE->set_context($context);
731
 
732
        $validated = false;
733
        $sitesubscriptionkey = get_config('tool_mobile', 'sitesubscriptionkey');
734
        if (!empty($sitesubscriptionkey) && $CFG->enablemobilewebservice && empty($CFG->disablemobileappsubscription)) {
735
            $sitesubscriptionkey = json_decode($sitesubscriptionkey);
736
            $validated = time() < $sitesubscriptionkey->validuntil && $params['key'] === $sitesubscriptionkey->key;
737
            // Delete existing, even if not validated to enforce security and attacks prevention.
738
            unset_config('sitesubscriptionkey', 'tool_mobile');
739
        }
740
 
741
        return [
742
            'validated' => $validated,
743
            'warnings' => [],
744
        ];
745
    }
746
 
747
    /**
748
     * Returns description of validate_subscription_key() result value.
749
     *
750
     * @return \core_external\external_description
751
     * @since  Moodle 3.9
752
     */
753
    public static function validate_subscription_key_returns() {
754
        return new external_single_structure(
755
            [
756
                'validated' => new external_value(PARAM_BOOL, 'Whether the key is validated or not.'),
757
                'warnings' => new external_warnings(),
758
            ]
759
        );
760
    }
761
}