From c5aeb80b05e7401e05c28c0928be92b78c0ebd1f Mon Sep 17 00:00:00 2001 From: Lai Wei Date: Wed, 27 Nov 2024 12:21:23 +0000 Subject: [PATCH] Log auth_oidc user out from all sessions when Front-channel logout URL is triggered --- auth/oidc/classes/loginflow/authcode.php | 9 +++- auth/oidc/classes/observers.php | 20 +++++++- auth/oidc/classes/task/cleanup_oidc_sid.php | 49 +++++++++++++++++++ auth/oidc/db/events.php | 6 +++ auth/oidc/db/install.xml | 14 +++++- auth/oidc/db/tasks.php | 9 ++++ auth/oidc/db/upgrade.php | 53 +++++++++++++++++++-- auth/oidc/lang/en/auth_oidc.php | 1 + auth/oidc/logout.php | 9 +++- auth/oidc/version.php | 2 +- 10 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 auth/oidc/classes/task/cleanup_oidc_sid.php diff --git a/auth/oidc/classes/loginflow/authcode.php b/auth/oidc/classes/loginflow/authcode.php index c81cc7d45..09bb85767 100644 --- a/auth/oidc/classes/loginflow/authcode.php +++ b/auth/oidc/classes/loginflow/authcode.php @@ -36,6 +36,7 @@ use moodle_exception; use moodle_url; use pix_icon; +use stdClass; defined('MOODLE_INTERNAL') || die(); @@ -395,7 +396,11 @@ protected function handleauthresponse(array $authparams) { // Otherwise it's a user logging in normally with OIDC. $this->handlelogin($oidcuniqid, $authparams, $tokenparams, $idtoken); if ($USER->id && $DB->record_exists('auth_oidc_token', ['userid' => $USER->id])) { - $DB->set_field('auth_oidc_token', 'sid', $sid, ['userid' => $USER->id]); + $authoidsidrecord = new stdClass(); + $authoidsidrecord->userid = $USER->id; + $authoidsidrecord->sid = $sid; + $authoidsidrecord->timecreated = time(); + $DB->insert_record('auth_oidc_sid', $authoidsidrecord); } redirect(core_login_get_return_url()); } @@ -792,7 +797,7 @@ protected function handlelogin(string $oidcuniqid, array $authparams, array $tok $tokenrec = $DB->get_record('auth_oidc_token', ['id' => $tokenrec->id]); // This should be already done in auth_plugin_oidc::user_authenticated_hook, but just in case... if (!empty($tokenrec) && empty($tokenrec->userid)) { - $updatedtokenrec = new \stdClass; + $updatedtokenrec = new stdClass; $updatedtokenrec->id = $tokenrec->id; $updatedtokenrec->userid = $user->id; $DB->update_record('auth_oidc_token', $updatedtokenrec); diff --git a/auth/oidc/classes/observers.php b/auth/oidc/classes/observers.php index a92cec86f..ad2e55c8b 100644 --- a/auth/oidc/classes/observers.php +++ b/auth/oidc/classes/observers.php @@ -25,6 +25,9 @@ namespace auth_oidc; +use core\event\user_deleted; +use core\event\user_loggedout; + defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot.'/lib/filelib.php'); @@ -36,13 +39,26 @@ class observers { /** * Handle user_deleted event - clean up calendar subscriptions. * - * @param \core\event\user_deleted $event The triggered event. + * @param user_deleted $event The triggered event. * @return bool Success/Failure. */ - public static function handle_user_deleted(\core\event\user_deleted $event) { + public static function handle_user_deleted(user_deleted $event) { global $DB; $userid = $event->objectid; $DB->delete_records('auth_oidc_token', ['userid' => $userid]); return true; } + + /** + * Handle user_loggedout event - clean up sid records. + * + * @param user_loggedout $event The triggered event. + * @return bool Success/Failure. + */ + public static function handle_user_loggedout(user_loggedout $event) { + global $DB; + $userid = $event->objectid; + $DB->delete_records('auth_oidc_sid', ['userid' => $userid]); + return true; + } } diff --git a/auth/oidc/classes/task/cleanup_oidc_sid.php b/auth/oidc/classes/task/cleanup_oidc_sid.php new file mode 100644 index 000000000..b6ed866a1 --- /dev/null +++ b/auth/oidc/classes/task/cleanup_oidc_sid.php @@ -0,0 +1,49 @@ +. + +/** + * A scheduled task to clean up oidc sid records. + * + * @package auth_oidc + * @author Lai Wei + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright (C) 2021 onwards Microsoft, Inc. (http://microsoft.com/) + */ + +namespace auth_oidc\task; + +use core\task\scheduled_task; + +/** + * A scheduled task that cleans up OIDC SID records. + */ +class cleanup_oidc_sid extends scheduled_task { + /** + * Get a descriptive name for the task. + */ + public function get_name() { + return get_string('task_cleanup_oidc_sid', 'auth_oidc'); + } + + /** + * Clean up OIDC SID records. + */ + public function execute() { + global $DB; + + $DB->delete_records_select('auth_oidc_sid', 'timecreated < ?', [strtotime('-1 day')]); + } +} diff --git a/auth/oidc/db/events.php b/auth/oidc/db/events.php index 376c05a7f..0013df416 100644 --- a/auth/oidc/db/events.php +++ b/auth/oidc/db/events.php @@ -32,4 +32,10 @@ 'priority' => 200, 'internal' => false, ], + [ + 'eventname' => '\core\event\user_loggedout', + 'callback' => '\auth_oidc\observers::handle_user_loggedout', + 'priority' => 200, + 'internal' => false, + ], ]; diff --git a/auth/oidc/db/install.xml b/auth/oidc/db/install.xml index a5c581b2e..93029ea81 100644 --- a/auth/oidc/db/install.xml +++ b/auth/oidc/db/install.xml @@ -1,5 +1,5 @@ - @@ -50,7 +50,6 @@ - @@ -61,5 +60,16 @@ + + + + + + + + + + +
diff --git a/auth/oidc/db/tasks.php b/auth/oidc/db/tasks.php index 8ab89042b..a254fceba 100644 --- a/auth/oidc/db/tasks.php +++ b/auth/oidc/db/tasks.php @@ -35,4 +35,13 @@ 'dayofweek' => '*', 'month' => '*', ], + [ + 'classname' => 'auth_oidc\task\cleanup_oidc_sid', + 'blocking' => 0, + 'minute' => '51', + 'hour' => '*', + 'day' => '*', + 'dayofweek' => '*', + 'month' => '*', + ], ]; diff --git a/auth/oidc/db/upgrade.php b/auth/oidc/db/upgrade.php index 3276e64c5..1f07b352e 100644 --- a/auth/oidc/db/upgrade.php +++ b/auth/oidc/db/upgrade.php @@ -94,7 +94,7 @@ function xmldb_auth_oidc_upgrade($oldversion) { // Populate token oidcusername. if (empty($user->oidcusername)) { - $updatedtoken = new \stdClass; + $updatedtoken = new stdClass; $updatedtoken->id = $user->tokenid; $updatedtoken->oidcusername = $oidcusername; $DB->update_record('auth_oidc_token', $updatedtoken); @@ -105,12 +105,12 @@ function xmldb_auth_oidc_upgrade($oldversion) { // Old username, update to upn/sub. if ($oidcusername != $user->username) { // Update username. - $updateduser = new \stdClass; + $updateduser = new stdClass; $updateduser->id = $user->userid; $updateduser->username = $oidcusername; $DB->update_record('user', $updateduser); - $updatedtoken = new \stdClass; + $updatedtoken = new stdClass; $updatedtoken->id = $user->tokenid; $updatedtoken->username = $oidcusername; $DB->update_record('auth_oidc_token', $updatedtoken); @@ -144,7 +144,7 @@ function xmldb_auth_oidc_upgrade($oldversion) { foreach ($authtokensrs as $authtokenrec) { $newusername = trim(\core_text::strtolower($authtokenrec->username)); if ($newusername !== $authtokenrec->username) { - $updatedrec = new \stdClass; + $updatedrec = new stdClass; $updatedrec->id = $authtokenrec->id; $updatedrec->username = $newusername; $DB->update_record('auth_oidc_token', $updatedrec); @@ -181,7 +181,7 @@ function xmldb_auth_oidc_upgrade($oldversion) { JOIN {user} u ON u.username = tok.username'; $records = $DB->get_recordset_sql($sql); foreach ($records as $record) { - $newrec = new \stdClass; + $newrec = new stdClass; $newrec->id = $record->id; $newrec->userid = $record->userid; $DB->update_record('auth_oidc_token', $newrec); @@ -504,5 +504,48 @@ function xmldb_auth_oidc_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2024042201, 'auth', 'oidc'); } + if ($oldversion < 2024100702) { + // Define table auth_oidc_sid to be created. + $table = new xmldb_table('auth_oidc_sid'); + + // Adding fields to table auth_oidc_sid. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, null); + $table->add_field('sid', XMLDB_TYPE_CHAR, '36', null, XMLDB_NOTNULL, null, null); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + + // Adding keys to table auth_oidc_sid. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Conditionally launch create table for auth_oidc_sid. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Migrate existing sid values from auth_oidc_tokens to auth_oidc_sid. + $tokenrecords = $DB->get_records('auth_oidc_token'); + foreach ($tokenrecords as $tokenrecord) { + if ($tokenrecord->sid) { + $sidrecord = new stdClass(); + $sidrecord->userid = $tokenrecord->userid; + $sidrecord->sid = $tokenrecord->sid; + $sidrecord->timecreated = time(); + $DB->insert_record('auth_oidc_sid', $sidrecord); + } + } + + // Define field sid to be dropped from auth_oidc_token. + $table = new xmldb_table('auth_oidc_token'); + $field = new xmldb_field('sid'); + + // Conditionally launch drop field sid. + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + + // Oidc savepoint reached. + upgrade_plugin_savepoint(true, 2024100702, 'auth', 'oidc'); + } + return true; } diff --git a/auth/oidc/lang/en/auth_oidc.php b/auth/oidc/lang/en/auth_oidc.php index 123bbf84e..1539773cc 100644 --- a/auth/oidc/lang/en/auth_oidc.php +++ b/auth/oidc/lang/en/auth_oidc.php @@ -207,6 +207,7 @@ $string['event_debug'] = 'Debug message'; $string['task_cleanup_oidc_state_and_token'] = 'Clean up OIDC state and invalid token'; +$string['task_cleanup_oidc_sid'] = 'Clean up OIDC SID records'; $string['errorauthdisconnectemptypassword'] = 'Password cannot be empty'; $string['errorauthdisconnectemptyusername'] = 'Username cannot be empty'; diff --git a/auth/oidc/logout.php b/auth/oidc/logout.php index be5dc718b..5ada78b3a 100644 --- a/auth/oidc/logout.php +++ b/auth/oidc/logout.php @@ -23,6 +23,8 @@ * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) */ +use core\session\manager; + // phpcs:ignore moodle.Files.RequireLogin.Missing require_once(__DIR__ . '/../../config.php'); @@ -32,8 +34,8 @@ $sid = optional_param('sid', '', PARAM_TEXT); if ($sid) { - if ($authoidctokenrecord = $DB->get_record('auth_oidc_token', ['sid' => $sid])) { - if ($authoidctokenrecord->userid == $USER->id) { + if ($authoidcsidrecord = $DB->get_record('auth_oidc_sid', ['sid' => $sid])) { + if ($authoidcsidrecord->userid == $USER->id) { $authsequence = get_enabled_auth_plugins(); // Auths, in sequence. foreach ($authsequence as $authname) { $authplugin = get_auth_plugin($authname); @@ -41,6 +43,9 @@ } require_logout(); + + // Log the user out from all sessions. + manager::destroy_user_sessions($authoidcsidrecord->userid); } } } diff --git a/auth/oidc/version.php b/auth/oidc/version.php index ff2197a2d..4f8a4edcd 100644 --- a/auth/oidc/version.php +++ b/auth/oidc/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024100700; +$plugin->version = 2024100702; $plugin->requires = 2024100700; $plugin->release = '4.5.0'; $plugin->component = 'auth_oidc';