diff --git a/admin/tool/mobile/classes/local/hooks/user/after_complete_login.php b/admin/tool/mobile/classes/local/hooks/user/after_complete_login.php new file mode 100644 index 00000000000..43823e7e4ac --- /dev/null +++ b/admin/tool/mobile/classes/local/hooks/user/after_complete_login.php @@ -0,0 +1,45 @@ +. + +namespace tool_mobile\local\hooks\user; + +/** + * Handles mobile app launches when a third-party auth plugin did not properly set $SESSION->wantsurl. + * + * @package tool_mobile + * @copyright 2024 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class after_complete_login { + + /** + * Callback to recover $SESSION->wantsurl. + * + * @param \core\hook\user\after_complete_login $hook + */ + public static function callback(\core\hook\user\after_complete_login $hook): void { + global $SESSION, $CFG; + + // Check if the user is doing a mobile app launch, if that's the case, ensure $SESSION->wantsurl is correctly set. + if (!NO_MOODLE_COOKIES && !empty($_COOKIE['tool_mobile_launch'])) { + if (empty($SESSION->wantsurl) || strpos($SESSION->wantsurl, '/tool/mobile/launch.php') === false) { + + $params = json_decode($_COOKIE['tool_mobile_launch'], true); + $SESSION->wantsurl = (new \moodle_url("/$CFG->admin/tool/mobile/launch.php", $params))->out(false); + } + } + } +} diff --git a/admin/tool/mobile/classes/local/hooks/user/after_user_passed_mfa.php b/admin/tool/mobile/classes/local/hooks/user/after_user_passed_mfa.php new file mode 100644 index 00000000000..098be081b5a --- /dev/null +++ b/admin/tool/mobile/classes/local/hooks/user/after_user_passed_mfa.php @@ -0,0 +1,51 @@ +. + +namespace tool_mobile\local\hooks\user; + +/** + * Handles mobile app launches when third-party auth plugins are put in front of MFA. + * + * @package tool_mobile + * @copyright 2024 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class after_user_passed_mfa { + + /** + * Callback to recover $SESSION->wantsurl. + * + * @param \tool_mfa\hook\after_user_passed_mfa $hook + */ + public static function callback(\tool_mfa\hook\after_user_passed_mfa $hook): void { + global $SESSION, $CFG; + + // Check if the user is doing a mobile app launch, if that's the case, ensure $SESSION->wantsurl is correctly set. + if (!NO_MOODLE_COOKIES && !empty($_COOKIE['tool_mobile_launch'])) { + if (empty($SESSION->wantsurl) || strpos($SESSION->wantsurl, '/tool/mobile/launch.php') === false) { + + $params = json_decode($_COOKIE['tool_mobile_launch'], true); + $SESSION->wantsurl = (new \moodle_url("/$CFG->admin/tool/mobile/launch.php", $params))->out(false); + $SESSION->tool_mfa_has_been_redirected = true; // Indicate MFA that they need to follow $SESSION->wantsurl. + } + // Invalidate cookie as we won't be needing it anymore. + unset($_COOKIE['tool_mobile_launch']); + if (!headers_sent()) { // Just be very cautios as this is a critical code. + setcookie('tool_mobile_launch', '', -1, $CFG->sessioncookiepath); + } + } + } +} diff --git a/admin/tool/mobile/db/hooks.php b/admin/tool/mobile/db/hooks.php new file mode 100644 index 00000000000..61bc9fac19f --- /dev/null +++ b/admin/tool/mobile/db/hooks.php @@ -0,0 +1,38 @@ +. + +/** + * Hook callbacks for Moodle app tools + * + * @package tool_mobile + * @copyright 2024 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$callbacks = [ + [ + 'hook' => core\hook\user\after_complete_login::class, + 'callback' => 'tool_mobile\local\hooks\user\after_complete_login::callback', + 'priority' => 500, + ], + [ + 'hook' => tool_mfa\hook\after_user_passed_mfa::class, + 'callback' => 'tool_mobile\local\hooks\user\after_user_passed_mfa::callback', + 'priority' => 500, + ], +]; diff --git a/admin/tool/mobile/launch.php b/admin/tool/mobile/launch.php index 3acfe218d84..346653a2fb0 100644 --- a/admin/tool/mobile/launch.php +++ b/admin/tool/mobile/launch.php @@ -44,6 +44,18 @@ if (!$CFG->enablewebservices) { throw new moodle_exception('enablewsdescription', 'webservice'); } +// Check if the service exists and is enabled. +$service = $DB->get_record('external_services', ['shortname' => $serviceshortname, 'enabled' => 1]); +if (empty($service)) { + throw new moodle_exception('servicenotavailable', 'webservice'); +} + +// Set a cookie indicating that there was a launch to authenticate via the site from the app. +$ldata = json_encode(['service' => $serviceshortname, 'passport' => $passport, + 'urlscheme' => $urlscheme, 'confirmed' => (int) $confirmed, 'oauthsso' => $oauthsso]); +$expires = time() + (15 * MINSECS); // 15 minutes for authentication should be enough. +setcookie('tool_mobile_launch', $ldata, $expires, $CFG->sessioncookiepath, $CFG->sessioncookiedomain); + // We have been requested to start a SSO process via OpenID. if (!empty($oauthsso) && is_enabled_auth('oauth2')) { $wantsurl = new moodle_url('/admin/tool/mobile/launch.php', @@ -63,17 +75,15 @@ if (empty($SESSION->justloggedin) && throw new moodle_exception('pluginnotenabledorconfigured', 'tool_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); +// Remove cookie. +unset($_COOKIE['tool_mobile_launch']); +setcookie('tool_mobile_launch', '', -1, $CFG->sessioncookiepath); + // Get an existing token or create a new one. $timenow = time(); $token = \core_external\util::generate_token_for_current_user($service); @@ -126,7 +136,7 @@ if ($confirmed or $isios) { } $notice = get_string('clickheretolaunchtheapp', 'tool_mobile'); - echo html_writer::link($location, $notice, array('id' => 'launchapp')); + echo $OUTPUT->box(html_writer::link($location, $notice, ['id' => 'launchapp']), 'generalbox warning centerpara'); echo html_writer::script( "window.onload = function() { document.getElementById('launchapp').click(); diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php index 6449b473a41..e3803dd942c 100644 --- a/lib/tests/component_test.php +++ b/lib/tests/component_test.php @@ -506,7 +506,7 @@ class component_test extends advanced_testcase { $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', 'output\\myprofile')); // Without namespace it returns classes/ classes. - $this->assertCount(5, core_component::get_component_classes_in_namespace('tool_mobile', '')); + $this->assertCount(7, core_component::get_component_classes_in_namespace('tool_mobile', '')); $this->assertCount(2, core_component::get_component_classes_in_namespace('tool_filetypes')); // When no component is specified, classes are returned for the namespace in all components.