From 8a0a7414b296d1ae8915dcdbbd1c9e7e3588e9c0 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Mon, 25 Apr 2016 14:38:57 +0200 Subject: [PATCH 1/3] MDL-53777 tool_mobile: Move mobile settings to the admin tool --- admin/settings/plugins.php | 15 ----------- admin/tool/mobile/settings.php | 47 +++++++++++++++++++++++++++++++++ lib/classes/plugininfo/tool.php | 7 ++++- version.php | 2 +- 4 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 admin/tool/mobile/settings.php diff --git a/admin/settings/plugins.php b/admin/settings/plugins.php index 9223cab3e55..2dbff19d87e 100644 --- a/admin/settings/plugins.php +++ b/admin/settings/plugins.php @@ -320,22 +320,7 @@ if ($hassiteconfig) { /// Web services $ADMIN->add('modules', new admin_category('webservicesettings', new lang_string('webservices', 'webservice'))); - // Mobile - $temp = new admin_settingpage('mobile', new lang_string('mobile','admin'), 'moodle/site:config', false); - // We should wait to the installation to finish since we depend on some configuration values that are set once - // the admin user profile is configured. - if (!during_initial_install()) { - $enablemobiledocurl = new moodle_url(get_docs_url('Enable_mobile_web_services')); - $enablemobiledoclink = html_writer::link($enablemobiledocurl, new lang_string('documentation')); - $default = is_https() ? 1 : 0; - $temp->add(new admin_setting_enablemobileservice('enablemobilewebservice', - new lang_string('enablemobilewebservice', 'admin'), - new lang_string('configenablemobilewebservice', 'admin', $enablemobiledoclink), $default)); - } - - $temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'admin'), new lang_string('configmobilecssurl','admin'), '', PARAM_URL)); - $ADMIN->add('webservicesettings', $temp); /// overview page $temp = new admin_settingpage('webservicesoverview', new lang_string('webservicesoverview', 'webservice')); $temp->add(new admin_setting_webservicesoverview()); diff --git a/admin/tool/mobile/settings.php b/admin/tool/mobile/settings.php new file mode 100644 index 00000000000..11aea8516b9 --- /dev/null +++ b/admin/tool/mobile/settings.php @@ -0,0 +1,47 @@ +. + +/** + * Settings + * + * This file contains settings used by tool_mobile + * + * @package tool_mobile + * @copyright 2016 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +if ($hassiteconfig) { + + $temp = new admin_settingpage('mobile', new lang_string('mobile', 'admin'), 'moodle/site:config', false); + + // We should wait to the installation to finish since we depend on some configuration values that are set once + // the admin user profile is configured. + if (!during_initial_install()) { + $enablemobiledocurl = new moodle_url(get_docs_url('Enable_mobile_web_services')); + $enablemobiledoclink = html_writer::link($enablemobiledocurl, new lang_string('documentation')); + $default = is_https() ? 1 : 0; + $temp->add(new admin_setting_enablemobileservice('enablemobilewebservice', + new lang_string('enablemobilewebservice', 'admin'), + new lang_string('configenablemobilewebservice', 'admin', $enablemobiledoclink), $default)); + } + + $temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'admin'), + new lang_string('configmobilecssurl', 'admin'), '', PARAM_URL)); + $ADMIN->add('webservicesettings', $temp); +} diff --git a/lib/classes/plugininfo/tool.php b/lib/classes/plugininfo/tool.php index cc6ab3c5ad6..1594abffeb7 100644 --- a/lib/classes/plugininfo/tool.php +++ b/lib/classes/plugininfo/tool.php @@ -33,7 +33,12 @@ defined('MOODLE_INTERNAL') || die(); class tool extends base { public function is_uninstall_allowed() { - return true; + // Some mobile settings are used by the core. + if ($this->name === 'mobile') { + return false; + } else { + return true; + } } /** diff --git a/version.php b/version.php index 3c09cea3d01..0ab7567123c 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2016092900.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2016092900.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. From c951f1fe58ea5dd466e4547f02a13e2a99d55dde Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Mon, 25 Apr 2016 18:40:27 +0200 Subject: [PATCH 2/3] MDL-53777 tool_mobile: New setting typeoflogin, including WS --- admin/tool/mobile/classes/api.php | 21 ++++++++++++++++++++ admin/tool/mobile/classes/external.php | 2 ++ admin/tool/mobile/lang/en/tool_mobile.php | 7 +++++++ admin/tool/mobile/settings.php | 15 ++++++++++++++ admin/tool/mobile/tests/externallib_test.php | 7 ++++++- admin/tool/mobile/version.php | 2 +- 6 files changed, 52 insertions(+), 2 deletions(-) diff --git a/admin/tool/mobile/classes/api.php b/admin/tool/mobile/classes/api.php index c111010b2ff..9653d45fa90 100644 --- a/admin/tool/mobile/classes/api.php +++ b/admin/tool/mobile/classes/api.php @@ -27,6 +27,7 @@ namespace tool_mobile; use core_component; use core_plugin_manager; use context_system; +use moodle_url; /** * API exposed by tool_mobile @@ -37,6 +38,13 @@ use context_system; */ class api { + /** @var int to identify the login via app. */ + const LOGIN_VIA_APP = 1; + /** @var int to identify the login via browser. */ + const LOGIN_VIA_BROWSER = 2; + /** @var int to identify the login via an embedded browser. */ + const LOGIN_VIA_EMBEDDED_BROWSER = 3; + /** * Returns a list of Moodle plugins supporting the mobile app. * @@ -111,6 +119,19 @@ class api { 'maintenanceenabled' => $CFG->maintenance_enabled, 'maintenancemessage' => format_text($CFG->maintenance_message), ); + + $typeoflogin = get_config('tool_mobile', 'typeoflogin'); + // Not found, edge case. + if ($typeoflogin === false) { + $typeoflogin = self::LOGIN_VIA_APP; // Defaults to via app. + } + $settings['typeoflogin'] = $typeoflogin; + + if ($typeoflogin == self::LOGIN_VIA_BROWSER or + $typeoflogin == self::LOGIN_VIA_EMBEDDED_BROWSER) { + $url = new moodle_url("/$CFG->admin/tool/mobile/launch.php"); + $settings['launchurl'] = $url->out(false); + } return $settings; } diff --git a/admin/tool/mobile/classes/external.php b/admin/tool/mobile/classes/external.php index 148f8a48016..4ab5e930139 100644 --- a/admin/tool/mobile/classes/external.php +++ b/admin/tool/mobile/classes/external.php @@ -140,6 +140,8 @@ class external extends external_api { 'enablemobilewebservice' => new external_value(PARAM_INT, 'Whether the Mobile service is enabled.'), 'maintenanceenabled' => new external_value(PARAM_INT, 'Whether site maintenance is enabled.'), 'maintenancemessage' => new external_value(PARAM_RAW, 'Maintenance message.'), + 'typeoflogin' => new external_value(PARAM_INT, 'The type of login. 1 for app, 2 for browser, 3 for embedded.'), + 'launchurl' => new external_value(PARAM_URL, 'SSO login launch URL. Empty if it won\'t be used.', VALUE_OPTIONAL), 'warnings' => new external_warnings(), ) ); diff --git a/admin/tool/mobile/lang/en/tool_mobile.php b/admin/tool/mobile/lang/en/tool_mobile.php index 56a13605089..ac4eeea90ab 100644 --- a/admin/tool/mobile/lang/en/tool_mobile.php +++ b/admin/tool/mobile/lang/en/tool_mobile.php @@ -22,4 +22,11 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['forcedurlscheme'] = 'The URL scheme allows to open the mobile app from other apps like the browser. Use this setting if you want to allow only your custom branded app to be opened by the browser.'; +$string['forcedurlscheme_key'] = 'URL scheme'; +$string['loginintheapp'] = 'Via the app'; +$string['logininthebrowser'] = 'Via a browser window (for SSO plugins)'; +$string['loginintheembeddedbrowser'] = 'Via an embedded browser (for SSO plugins)'; $string['pluginname'] = 'Moodle Mobile tools'; +$string['typeoflogin'] = 'Type of login'; +$string['typeoflogin_desc'] = 'Choose the type of login.'; diff --git a/admin/tool/mobile/settings.php b/admin/tool/mobile/settings.php index 11aea8516b9..91bf63b18c1 100644 --- a/admin/tool/mobile/settings.php +++ b/admin/tool/mobile/settings.php @@ -43,5 +43,20 @@ if ($hassiteconfig) { $temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'admin'), new lang_string('configmobilecssurl', 'admin'), '', PARAM_URL)); + + // Type of login. + $options = array( + tool_mobile\api::LOGIN_VIA_APP => new lang_string('loginintheapp', 'tool_mobile'), + tool_mobile\api::LOGIN_VIA_BROWSER => new lang_string('logininthebrowser', 'tool_mobile'), + tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER => new lang_string('loginintheembeddedbrowser', 'tool_mobile'), + ); + $temp->add(new admin_setting_configselect('tool_mobile/typeoflogin', + new lang_string('typeoflogin', 'tool_mobile'), + new lang_string('typeoflogin_desc', 'tool_mobile'), 1, $options)); + + $temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme', + new lang_string('forcedurlscheme_key', 'tool_mobile'), + new lang_string('forcedurlscheme', 'tool_mobile'), '', PARAM_NOTAGS)); + $ADMIN->add('webservicesettings', $temp); } diff --git a/admin/tool/mobile/tests/externallib_test.php b/admin/tool/mobile/tests/externallib_test.php index 0ee8fc34b59..3d2c0edc517 100644 --- a/admin/tool/mobile/tests/externallib_test.php +++ b/admin/tool/mobile/tests/externallib_test.php @@ -31,6 +31,7 @@ global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); use tool_mobile\external; +use tool_mobile\api; /** * External learning plans webservice API tests. @@ -78,17 +79,21 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase { 'enablemobilewebservice' => $CFG->enablemobilewebservice, 'maintenanceenabled' => $CFG->maintenance_enabled, 'maintenancemessage' => format_text($CFG->maintenance_message), + 'typeoflogin' => api::LOGIN_VIA_APP, 'warnings' => array() ); $this->assertEquals($expected, $result); - // Change a value. + // Change some values. set_config('registerauth', 'email'); $authinstructions = 'Something with html tags'; set_config('auth_instructions', $authinstructions); + set_config('typeoflogin', api::LOGIN_VIA_BROWSER, 'tool_mobile'); $expected['registerauth'] = 'email'; $expected['authinstructions'] = format_text($authinstructions); + $expected['typeoflogin'] = api::LOGIN_VIA_BROWSER; + $expected['launchurl'] = "$CFG->wwwroot/$CFG->admin/tool/mobile/launch.php";; $result = external::get_site_public_settings(); $result = external_api::clean_returnvalue(external::get_site_public_settings_returns(), $result); diff --git a/admin/tool/mobile/version.php b/admin/tool/mobile/version.php index 0c57e237651..1be04c390ad 100644 --- a/admin/tool/mobile/version.php +++ b/admin/tool/mobile/version.php @@ -23,6 +23,6 @@ */ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2016052301; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2016052302; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2016051900; // Requires this Moodle version. $plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics). From 0a90f6247d66ac29b21a2eab58187a3ecb05fd96 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Thu, 28 Apr 2016 13:09:29 +0200 Subject: [PATCH 3/3] MDL-53777 tool_mobile: New script for launching the mobile app --- admin/tool/mobile/lang/en/tool_mobile.php | 1 + admin/tool/mobile/launch.php | 102 +++++++++++++++++++ lib/classes/useragent.php | 57 +++++++++++ lib/externallib.php | 116 ++++++++++++++++++++++ lib/tests/useragent_test.php | 22 ++++ login/token.php | 104 +------------------ 6 files changed, 301 insertions(+), 101 deletions(-) create mode 100644 admin/tool/mobile/launch.php diff --git a/admin/tool/mobile/lang/en/tool_mobile.php b/admin/tool/mobile/lang/en/tool_mobile.php index ac4eeea90ab..fa4b28843aa 100644 --- a/admin/tool/mobile/lang/en/tool_mobile.php +++ b/admin/tool/mobile/lang/en/tool_mobile.php @@ -22,6 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['clickheretolaunchtheapp'] = 'Please, click here if the app does not open automatically.'; $string['forcedurlscheme'] = 'The URL scheme allows to open the mobile app from other apps like the browser. Use this setting if you want to allow only your custom branded app to be opened by the browser.'; $string['forcedurlscheme_key'] = 'URL scheme'; $string['loginintheapp'] = 'Via the app'; diff --git a/admin/tool/mobile/launch.php b/admin/tool/mobile/launch.php new file mode 100644 index 00000000000..40da9bb7891 --- /dev/null +++ b/admin/tool/mobile/launch.php @@ -0,0 +1,102 @@ +. + +/** + * Launch page, launch the app using custom URL schemes. + * + * If the user is not logged when visiting this page, he will be redirected to the login page. + * Once he is logged, he will be redirected again to this page and the app launched via custom URL schemes. + * + * @package tool_mobile + * @copyright 2016 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__ . '/../../../config.php'); +require_once($CFG->libdir . '/externallib.php'); + +$serviceshortname = required_param('service', PARAM_ALPHANUMEXT); +$passport = required_param('passport', PARAM_RAW); // Passport send from the app to validate the response URL. +$urlscheme = optional_param('urlscheme', 'moodlemobile', PARAM_NOTAGS); // The URL scheme the app supports. + +// Check web services enabled. +if (!$CFG->enablewebservices) { + throw new moodle_exception('enablewsdescription', 'webservice'); +} + +// Check if the plugin is properly configured. +$typeoflogin = get_config('tool_mobile', 'typeoflogin'); +if ($typeoflogin != tool_mobile\api::LOGIN_VIA_BROWSER and + $typeoflogin != tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER) { + throw new moodle_exception('pluginnotenabledorconfigured', 'local_mobile'); +} + +// Check if the service exists and is enabled. +$service = $DB->get_record('external_services', array('shortname' => $serviceshortname, 'enabled' => 1)); +if (empty($service)) { + throw new moodle_exception('servicenotavailable', 'webservice'); +} + +require_login(0, false); + +// Require an active user: not guest, not suspended. +core_user::require_active_user($USER); + +// Get an existing token or create a new one. +$token = external_generate_token_for_current_user($service); + +// Log token access. +$DB->set_field('external_tokens', 'lastaccess', time(), array('id' => $token->id)); + +$params = array( + 'objectid' => $token->id, +); +$event = \core\event\webservice_token_sent::create($params); +$event->add_record_snapshot('external_tokens', $token); +$event->trigger(); + +// Passport is generated in the mobile app, so the app opening can be validated using that variable. +// Passports are valid only one time, it's deleted in the app once used. +$siteid = md5($CFG->wwwroot . $passport); +$apptoken = base64_encode($siteid . ':::' . $token->token); + +// Redirect using the custom URL scheme checking first if a URL scheme is forced in the site settings. +$forcedurlscheme = get_config('tool_mobile', 'forcedurlscheme'); +if (!empty($forcedurlscheme)) { + $urlscheme = $forcedurlscheme; +} + +$location = "$urlscheme://token=$apptoken"; + +// For iOS 10 onwards, we have to simulate a user click. +if (core_useragent::is_ios()) { + $PAGE->set_context(null); + $PAGE->set_url('/local/mobile/launch.php', array('service' => $serviceshortname, 'passport' => $passport, 'urlscheme' => $urlscheme)); + + echo $OUTPUT->header(); + $notice = get_string('clickheretolaunchtheapp', 'tool_mobile'); + echo html_writer::link($location, $notice, array('id' => 'launchapp')); + echo html_writer::script( + "window.onload = function() { + document.getElementById('launchapp').click(); + };" + ); + echo $OUTPUT->footer(); +} else { + // For Android a http redirect will do fine. + header('Location: ' . $location); + die; +} diff --git a/lib/classes/useragent.php b/lib/classes/useragent.php index b62cf419e70..effe291ab51 100644 --- a/lib/classes/useragent.php +++ b/lib/classes/useragent.php @@ -997,4 +997,61 @@ class core_useragent { $instance = self::instance(); return (bool) $instance->is_useragent_web_crawler(); } + + /** + * Returns true if the client appears to be a device using iOS (iPhone, iPad, iPod). + * + * @param scalar $version The version if we need to find out if it is equal to or greater than that specified. + * @return bool true if the client is using iOS + * @since Moodle 3.2 + */ + public static function is_ios($version = null) { + $useragent = self::get_user_agent_string(); + if ($useragent === false) { + return false; + } + if (strpos($useragent, 'AppleWebKit') === false) { + return false; + } + if (strpos($useragent, 'Windows')) { + // Reject Windows Safari. + return false; + } + if (strpos($useragent, 'Macintosh')) { + // Reject MacOS Safari. + return false; + } + // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices. + if (strpos($useragent, 'OmniWeb')) { + // Reject OmniWeb. + return false; + } + if (strpos($useragent, 'Shiira')) { + // Reject Shiira. + return false; + } + if (strpos($useragent, 'SymbianOS')) { + // Reject SymbianOS. + return false; + } + if (strpos($useragent, 'Android')) { + // Reject Androids too. + return false; + } + if (strpos($useragent, 'Chrome')) { + // Reject chrome browsers - it needs to be tested explicitly. + // This will also reject Edge, which pretends to be both Chrome, and Safari. + return false; + } + + if (empty($version)) { + return true; // No version specified. + } + if (preg_match("/AppleWebKit\/([0-9.]+)/i", $useragent, $match)) { + if (version_compare($match[1], $version) >= 0) { + return true; + } + } + return false; + } } diff --git a/lib/externallib.php b/lib/externallib.php index c05009da85e..8cd2c41b99c 100644 --- a/lib/externallib.php +++ b/lib/externallib.php @@ -954,6 +954,122 @@ function external_format_text($text, $textformat, $contextid, $component = null, return array($text, $textformat); } +/** + * Generate or return an existing token for the current authenticated user. + * This function is used for creating a valid token for users authenticathing via login/token.php or admin/tool/mobile/launch.php. + * + * @param stdClass $service external service object + * @return stdClass token object + * @since Moodle 3.2 + * @throws moodle_exception + */ +function external_generate_token_for_current_user($service) { + global $DB, $USER; + + core_user::require_active_user($USER, true, true); + + // Check if there is any required system capability. + if ($service->requiredcapability and !has_capability($service->requiredcapability, context_system::instance())) { + throw new moodle_exception('missingrequiredcapability', 'webservice', '', $service->requiredcapability); + } + + // Specific checks related to user restricted service. + if ($service->restrictedusers) { + $authoriseduser = $DB->get_record('external_services_users', + array('externalserviceid' => $service->id, 'userid' => $USER->id)); + + if (empty($authoriseduser)) { + throw new moodle_exception('usernotallowed', 'webservice', '', $service->shortname); + } + + if (!empty($authoriseduser->validuntil) and $authoriseduser->validuntil < time()) { + throw new moodle_exception('invalidtimedtoken', 'webservice'); + } + + if (!empty($authoriseduser->iprestriction) and !address_in_subnet(getremoteaddr(), $authoriseduser->iprestriction)) { + throw new moodle_exception('invalidiptoken', 'webservice'); + } + } + + // Check if a token has already been created for this user and this service. + $conditions = array( + 'userid' => $USER->id, + 'externalserviceid' => $service->id, + 'tokentype' => EXTERNAL_TOKEN_PERMANENT + ); + $tokens = $DB->get_records('external_tokens', $conditions, 'timecreated ASC'); + + // A bit of sanity checks. + foreach ($tokens as $key => $token) { + + // Checks related to a specific token. (script execution continue). + $unsettoken = false; + // If sid is set then there must be a valid associated session no matter the token type. + if (!empty($token->sid)) { + if (!\core\session\manager::session_exists($token->sid)) { + // This token will never be valid anymore, delete it. + $DB->delete_records('external_tokens', array('sid' => $token->sid)); + $unsettoken = true; + } + } + + // Remove token is not valid anymore. + if (!empty($token->validuntil) and $token->validuntil < time()) { + $DB->delete_records('external_tokens', array('token' => $token->token, 'tokentype' => EXTERNAL_TOKEN_PERMANENT)); + $unsettoken = true; + } + + // Remove token if its ip not in whitelist. + if (isset($token->iprestriction) and !address_in_subnet(getremoteaddr(), $token->iprestriction)) { + $unsettoken = true; + } + + if ($unsettoken) { + unset($tokens[$key]); + } + } + + // If some valid tokens exist then use the most recent. + if (count($tokens) > 0) { + $token = array_pop($tokens); + } else { + $context = context_system::instance(); + $isofficialservice = $service->shortname == MOODLE_OFFICIAL_MOBILE_SERVICE; + + if (($isofficialservice and has_capability('moodle/webservice:createmobiletoken', $context)) or + (!is_siteadmin($USER) && has_capability('moodle/webservice:createtoken', $context))) { + + // Create a new token. + $token = new stdClass; + $token->token = md5(uniqid(rand(), 1)); + $token->userid = $USER->id; + $token->tokentype = EXTERNAL_TOKEN_PERMANENT; + $token->contextid = context_system::instance()->id; + $token->creatorid = $USER->id; + $token->timecreated = time(); + $token->externalserviceid = $service->id; + // MDL-43119 Token valid for 3 months (12 weeks). + $token->validuntil = $token->timecreated + 12 * WEEKSECS; + $token->id = $DB->insert_record('external_tokens', $token); + + $params = array( + 'objectid' => $token->id, + 'relateduserid' => $USER->id, + 'other' => array( + 'auto' => true + ) + ); + $event = \core\event\webservice_token_created::create($params); + $event->add_record_snapshot('external_tokens', $token); + $event->trigger(); + } else { + throw new moodle_exception('cannotcreatetoken', 'webservice', '', $service->shortname); + } + } + return $token; +} + + /** * Singleton to handle the external settings. * diff --git a/lib/tests/useragent_test.php b/lib/tests/useragent_test.php index 5196395e5ab..c9b7c76b15d 100644 --- a/lib/tests/useragent_test.php +++ b/lib/tests/useragent_test.php @@ -890,6 +890,7 @@ class core_useragent_testcase extends advanced_testcase { array( // Note: We do *not* identify mobile Safari as Safari. 'is_safari_ios' => true, + 'is_ios' => true, 'check_safari_ios_version' => array( '527' => true, ), @@ -911,6 +912,7 @@ class core_useragent_testcase extends advanced_testcase { array( // Note: We do *not* identify mobile Safari as Safari. 'is_safari_ios' => true, + 'is_ios' => true, 'check_safari_ios_version' => array( '527' => true, '590' => true, @@ -934,6 +936,7 @@ class core_useragent_testcase extends advanced_testcase { array( // Note: We do *not* identify mobile Safari as Safari. 'is_safari_ios' => true, + 'is_ios' => true, 'check_safari_ios_version' => array( '527' => true, ), @@ -1222,6 +1225,7 @@ class core_useragent_testcase extends advanced_testcase { 'is_web_crawler' => true, 'is_webkit' => true, 'is_safari_ios' => true, + 'is_ios' => true, 'check_safari_ios_version' => array( '527' => true, ), @@ -1703,6 +1707,24 @@ class core_useragent_testcase extends advanced_testcase { } } + /** + * @dataProvider user_agents_providers + */ + public function test_useragent_ios($useragent, $tests) { + // Setup the core_useragent instance. + core_useragent::instance(true, $useragent); + + if (isset($tests['is_ios']) && $tests['is_ios']) { + $this->assertTrue(core_useragent::is_ios(), + "Browser was not identified as an iOS device browser"); + $this->assertTrue(core_useragent::check_safari_ios_version()); + } else { + $this->assertFalse(core_useragent::is_ios(), + "Browser was incorrectly identified as an iOS device browser"); + $this->assertFalse(core_useragent::check_safari_ios_version()); + } + } + /** * @dataProvider user_agents_providers */ diff --git a/login/token.php b/login/token.php index 2799ab9ac17..13ea7fcd07f 100644 --- a/login/token.php +++ b/login/token.php @@ -26,6 +26,7 @@ define('REQUIRE_CORRECT_ACCESS', true); define('NO_MOODLE_COOKIES', true); require_once(__DIR__ . '/../config.php'); +require_once($CFG->libdir . '/externallib.php'); // Allow CORS requests. header('Access-Control-Allow-Origin: *'); @@ -89,107 +90,8 @@ if (!empty($user)) { throw new moodle_exception('servicenotavailable', 'webservice'); } - //check if there is any required system capability - if ($service->requiredcapability and !has_capability($service->requiredcapability, context_system::instance(), $user)) { - throw new moodle_exception('missingrequiredcapability', 'webservice', '', $service->requiredcapability); - } - - //specific checks related to user restricted service - if ($service->restrictedusers) { - $authoriseduser = $DB->get_record('external_services_users', - array('externalserviceid' => $service->id, 'userid' => $user->id)); - - if (empty($authoriseduser)) { - throw new moodle_exception('usernotallowed', 'webservice', '', $serviceshortname); - } - - if (!empty($authoriseduser->validuntil) and $authoriseduser->validuntil < time()) { - throw new moodle_exception('invalidtimedtoken', 'webservice'); - } - - if (!empty($authoriseduser->iprestriction) and !address_in_subnet(getremoteaddr(), $authoriseduser->iprestriction)) { - throw new moodle_exception('invalidiptoken', 'webservice'); - } - } - - //Check if a token has already been created for this user and this service - //Note: this could be an admin created or an user created token. - // It does not really matter we take the first one that is valid. - $tokenssql = "SELECT t.id, t.sid, t.token, t.validuntil, t.iprestriction - FROM {external_tokens} t - WHERE t.userid = ? AND t.externalserviceid = ? AND t.tokentype = ? - ORDER BY t.timecreated ASC"; - $tokens = $DB->get_records_sql($tokenssql, array($user->id, $service->id, EXTERNAL_TOKEN_PERMANENT)); - - //A bit of sanity checks - foreach ($tokens as $key=>$token) { - - /// Checks related to a specific token. (script execution continue) - $unsettoken = false; - //if sid is set then there must be a valid associated session no matter the token type - if (!empty($token->sid)) { - if (!\core\session\manager::session_exists($token->sid)){ - //this token will never be valid anymore, delete it - $DB->delete_records('external_tokens', array('sid'=>$token->sid)); - $unsettoken = true; - } - } - - //remove token if no valid anymore - //Also delete this wrong token (similar logic to the web service servers - // /webservice/lib.php/webservice_server::authenticate_by_token()) - if (!empty($token->validuntil) and $token->validuntil < time()) { - $DB->delete_records('external_tokens', array('token'=>$token->token, 'tokentype'=> EXTERNAL_TOKEN_PERMANENT)); - $unsettoken = true; - } - - // remove token if its ip not in whitelist - if (isset($token->iprestriction) and !address_in_subnet(getremoteaddr(), $token->iprestriction)) { - $unsettoken = true; - } - - if ($unsettoken) { - unset($tokens[$key]); - } - } - - // if some valid tokens exist then use the most recent - if (count($tokens) > 0) { - $token = array_pop($tokens); - } else { - if ( ($serviceshortname == MOODLE_OFFICIAL_MOBILE_SERVICE and has_capability('moodle/webservice:createmobiletoken', context_system::instance())) - //Note: automatically token generation is not available to admin (they must create a token manually) - or (!is_siteadmin($user) && has_capability('moodle/webservice:createtoken', context_system::instance()))) { - // if service doesn't exist, dml will throw exception - $service_record = $DB->get_record('external_services', array('shortname'=>$serviceshortname, 'enabled'=>1), '*', MUST_EXIST); - - // Create a new token. - $token = new stdClass; - $token->token = md5(uniqid(rand(), 1)); - $token->userid = $user->id; - $token->tokentype = EXTERNAL_TOKEN_PERMANENT; - $token->contextid = context_system::instance()->id; - $token->creatorid = $user->id; - $token->timecreated = time(); - $token->externalserviceid = $service_record->id; - // MDL-43119 Token valid for 3 months (12 weeks). - $token->validuntil = $token->timecreated + 12 * WEEKSECS; - $token->id = $DB->insert_record('external_tokens', $token); - - $params = array( - 'objectid' => $token->id, - 'relateduserid' => $user->id, - 'other' => array( - 'auto' => true - ) - ); - $event = \core\event\webservice_token_created::create($params); - $event->add_record_snapshot('external_tokens', $token); - $event->trigger(); - } else { - throw new moodle_exception('cannotcreatetoken', 'webservice', '', $serviceshortname); - } - } + // Get an existing token or create a new one. + $token = external_generate_token_for_current_user($service); // log token access $DB->set_field('external_tokens', 'lastaccess', time(), array('id'=>$token->id));