diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 9a27682c6c3..c06715f3c50 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -2076,8 +2076,8 @@ $string['showtheselogs'] = 'Show these logs'; $string['showthishelpinlanguage'] = 'Show this help in language: {$a}'; $string['schedule'] = 'Schedule'; $string['sidepanel'] = 'Side panel'; -$string['signoutofotherservices'] = 'Sign out everywhere'; -$string['signoutofotherservices_help'] = 'If ticked, the account will be signed out of all devices and systems which use web services, such as the mobile app.'; +$string['signoutofotherservices'] = 'Log out of all web apps'; +$string['signoutofotherservices_help'] = 'Log out of all devices and systems that use web services, such as the mobile app. Browser sessions will remain active.'; $string['since'] = 'Since'; $string['sincelast'] = 'since last login'; $string['site'] = 'Site'; diff --git a/login/change_password.php b/login/change_password.php index 560884e6a98..98d107d8ad1 100644 --- a/login/change_password.php +++ b/login/change_password.php @@ -117,7 +117,8 @@ if ($mform->is_cancelled()) { user_add_password_history($USER->id, $data->newpassword1); - if (!empty($CFG->passwordchangelogout)) { + // Log out all other sessions if mandated by admin, or if set by the user. + if (!empty($CFG->passwordchangelogout) || !empty($data->logoutothersessions)) { \core\session\manager::kill_user_sessions($USER->id, session_id()); } diff --git a/login/change_password_form.php b/login/change_password_form.php index ce2b4c3b9ff..b6a01a1d9db 100644 --- a/login/change_password_form.php +++ b/login/change_password_form.php @@ -70,10 +70,20 @@ class login_change_password_form extends moodleform { $mform->addRule('newpassword2', get_string('required'), 'required', null, 'client'); $mform->setType('newpassword2', PARAM_RAW); - if (empty($CFG->passwordchangetokendeletion) and !empty(webservice::get_active_tokens($USER->id))) { + $mform->addElement('checkbox', 'logoutothersessions', get_string('logoutothersessions', 'report_usersessions')); + $mform->addHelpButton('logoutothersessions', 'logoutothersessions', 'report_usersessions'); + $mform->setDefault('logoutothersessions', 1); + if (!empty($CFG->passwordchangelogout)) { + $mform->getElement('logoutothersessions')->freeze(); + } + + if (!empty(webservice::get_active_tokens($USER->id))) { $mform->addElement('advcheckbox', 'signoutofotherservices', get_string('signoutofotherservices')); $mform->addHelpButton('signoutofotherservices', 'signoutofotherservices'); $mform->setDefault('signoutofotherservices', 1); + if (!empty($CFG->passwordchangetokendeletion)) { + $mform->getElement('signoutofotherservices')->freeze(); + } } // hidden optional params diff --git a/login/lib.php b/login/lib.php index e30e178369e..5cec753feb2 100644 --- a/login/lib.php +++ b/login/lib.php @@ -287,7 +287,7 @@ function core_login_process_password_set($token) { throw new \moodle_exception('errorpasswordupdate', 'auth'); } user_add_password_history($user->id, $data->password); - if (!empty($CFG->passwordchangelogout)) { + if (!empty($CFG->passwordchangelogout) || !empty($data->logoutothersessions)) { \core\session\manager::kill_user_sessions($user->id, session_id()); } // Reset login lockout (if present) before a new password is set. diff --git a/login/set_password_form.php b/login/set_password_form.php index 89221e70c5b..1c9d2124d43 100644 --- a/login/set_password_form.php +++ b/login/set_password_form.php @@ -83,6 +83,13 @@ class login_set_password_form extends moodleform { $mform->addRule('password2', get_string('required'), 'required', null, 'client'); $mform->setType('password2', PARAM_RAW); + $mform->addElement('checkbox', 'logoutothersessions', get_string('logoutothersessions', 'report_usersessions')); + $mform->addHelpButton('logoutothersessions', 'logoutothersessions', 'report_usersessions'); + $mform->setDefault('logoutothersessions', 1); + if (!empty($CFG->passwordchangelogout)) { + $mform->getElement('logoutothersessions')->freeze(); + } + // Hook for plugins to extend form definition. $user = $this->_customdata; core_login_extend_set_password_form($mform, $user); diff --git a/report/usersessions/lang/en/report_usersessions.php b/report/usersessions/lang/en/report_usersessions.php index 1fbc317a408..14cde6675e4 100644 --- a/report/usersessions/lang/en/report_usersessions.php +++ b/report/usersessions/lang/en/report_usersessions.php @@ -23,8 +23,12 @@ * @author Petr Skoda */ +$string['logoutothersessions'] = 'Log out all other browser sessions'; +$string['logoutothersessions_help'] = 'Log out of all browser sessions, except for this one. This does not affect web apps.'; +$string['logoutothersessionssuccess'] = 'You have been logged out of all your other sessions'; +$string['logoutsinglesessionsuccess'] = 'You have been logged out of the session at {$a}'; +$string['mysessions'] = 'My active browser sessions'; $string['navigationlink'] = 'Browser sessions'; -$string['mysessions'] = 'My active sessions'; $string['pluginname'] = 'User sessions report'; $string['thissession'] = 'Current session'; $string['usersessions:manageownsessions'] = 'Manage own browser sessions'; diff --git a/report/usersessions/user.php b/report/usersessions/user.php index 52e2d856a7f..e295d901269 100644 --- a/report/usersessions/user.php +++ b/report/usersessions/user.php @@ -43,6 +43,8 @@ $context = context_user::instance($USER->id); require_capability('report/usersessions:manageownsessions', $context); $delete = optional_param('delete', 0, PARAM_INT); +$deleteall = optional_param('deleteall', false, PARAM_BOOL); +$lastip = cleanremoteaddr(optional_param('lastip', '', PARAM_TEXT)); $PAGE->set_url('/report/usersessions/user.php'); $PAGE->set_context($context); @@ -50,9 +52,24 @@ $PAGE->set_title(get_string('navigationlink', 'report_usersessions')); $PAGE->set_heading(fullname($USER)); $PAGE->set_pagelayout('admin'); -if ($delete and confirm_sesskey()) { +// Delete a specific session. +if ($delete && confirm_sesskey()) { report_usersessions_kill_session($delete); - redirect($PAGE->url); + redirect( + url: $PAGE->url, + message: get_string('logoutsinglesessionsuccess', 'report_usersessions', $lastip), + messagetype: \core\output\notification::NOTIFY_SUCCESS, + ); +} + +// Delete all sessions except current. +if ($deleteall && confirm_sesskey()) { + \core\session\manager::kill_user_sessions($USER->id, session_id()); + redirect( + url: $PAGE->url, + message: get_string('logoutothersessionssuccess', 'report_usersessions'), + messagetype: \core\output\notification::NOTIFY_SUCCESS, + ); } // Create the breadcrumb. @@ -79,7 +96,7 @@ foreach ($sessions as $session) { } else { $lastaccess = report_usersessions_format_duration(time() - $session->timemodified); - $url = new moodle_url($PAGE->url, array('delete' => $session->id, 'sesskey' => sesskey())); + $url = new moodle_url($PAGE->url, ['delete' => $session->id, 'sesskey' => sesskey(), 'lastip' => $session->lastip]); $deletelink = html_writer::link($url, get_string('logout')); } $data[] = array(userdate($session->timecreated), $lastaccess, report_usersessions_format_ip($session->lastip), $deletelink); @@ -91,5 +108,10 @@ $table->align = array('left', 'left', 'left', 'right'); $table->data = $data; echo html_writer::table($table); -echo $OUTPUT->footer(); +// Provide button to log out all other sessions. +if (count($sessions) > 1) { + $url = new moodle_url($PAGE->url, ['deleteall' => true]); + echo $OUTPUT->single_button($url, get_string('logoutothersessions', 'report_usersessions')); +} +echo $OUTPUT->footer(); diff --git a/user/tests/behat/edituserpassword.feature b/user/tests/behat/edituserpassword.feature index 29831592cd6..ae90f8c5d9f 100644 --- a/user/tests/behat/edituserpassword.feature +++ b/user/tests/behat/edituserpassword.feature @@ -16,14 +16,14 @@ Feature: Edit a users password # We need to cancel/submit a form that has been modified. And I press "Create user" - Scenario: Sign out everywhere field is not present if user doesn't have active token + Scenario: Log out web apps field is not present if user doesn't have active token Given the following "users" exist: | username | firstname | lastname | email | | user01 | User | One | user01@example.com | When I am on the "user01" "user > editing" page logged in as "admin" - Then "Sign out everywhere" "field" should not exist + Then "Log out of all web apps" "field" should not exist - Scenario Outline: Sign out everywhere field is present based on expiry of active token + Scenario Outline: Log out web apps field is present based on expiry of active token Given the following "users" exist: | username | firstname | lastname | email | | user01 | User | One | user01@example.com | @@ -34,7 +34,7 @@ Feature: Edit a users password | user | service | validuntil | | user01 | mytestservice | | When I am on the "user01" "user > editing" page logged in as "admin" - Then "Sign out everywhere" "field" exist + Then "Log out of all web apps" "field" exist Examples: | validuntil | shouldornot | | ## -1 month ## | should not |