PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/admin/tool/mobile/classes/external.php

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