mirror of
https://github.com/roundcube/roundcubemail.git
synced 2026-02-19 17:11:20 +01:00
Session lifetime extension
aka Persisted Login plugin functionality in core code. Allows admins to set `$config['session_lifetime_extension_days']`, which allows users to switch on an extended session lifetime in the login form. In effect, these user sessions are valid for the configured number of days after the last activity, even across network outages, closed browsers (as long as they keep their cookies), etc.
This commit is contained in:
@@ -646,6 +646,15 @@ $config['display_product_info'] = 1;
|
||||
// Session lifetime in minutes
|
||||
$config['session_lifetime'] = 10;
|
||||
|
||||
// Allow users to extend their session lifetime to up to X days by checking a
|
||||
// checkbox at the login. Practically this means that a login will survive
|
||||
// network changes, browser restarts (unless they delete cookies), etc, for up
|
||||
// to X days without activity.
|
||||
// Warning: This reduces the effectiveness of Roundcube's session highjacking
|
||||
// mitigation, since a stolen session cookie will be valid for much longer than
|
||||
// without this option.
|
||||
$config['session_lifetime_extension_days'] = 1;
|
||||
|
||||
// Session domain: .example.org
|
||||
$config['session_domain'] = '';
|
||||
|
||||
|
||||
@@ -2355,6 +2355,18 @@ class rcmail_output_html extends rcmail_output
|
||||
'buttons' => [],
|
||||
];
|
||||
|
||||
if ($this->config->session_lifetime_extension_days() > 0) {
|
||||
$session_lifetime_extension_hidden_field = new html_hiddenfield(['name' => '_session_lifetime_extension', 'value' => '0']);
|
||||
$form_content['hidden']['session_lifetime_extension'] = $session_lifetime_extension_hidden_field->show();
|
||||
|
||||
// Make sure the value is in the range 1..365.
|
||||
$session_lifetime_extension_text = str_replace('#', $this->config->session_lifetime_extension_days(), $this->app->gettext('session_lifetime_extension_switch_text'));
|
||||
$session_lifetime_extension_checkbox = new html_checkbox(['name' => '_session_lifetime_extension', 'id' => '_session_lifetime_extension', 'title' => $session_lifetime_extension_text]);
|
||||
$form_content['inputs']['session_lifetime_extension'] = [
|
||||
'content' => html::label(['for' => '_session_lifetime_extension'], [$session_lifetime_extension_checkbox->show(), $session_lifetime_extension_text]),
|
||||
];
|
||||
}
|
||||
|
||||
if (is_array($default_host) && count($default_host) > 1) {
|
||||
$input_host = new html_select(['name' => '_host', 'id' => 'rcmloginhost', 'class' => 'custom-select']);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ class rcube_config
|
||||
private $userprefs = [];
|
||||
private $immutable = [];
|
||||
private $client_tz;
|
||||
private $session_lifetime_extension_days;
|
||||
|
||||
/**
|
||||
* Renamed options
|
||||
@@ -926,4 +927,18 @@ class rcube_config
|
||||
|
||||
return $deprecated_timezones[$tzname] ?? $tzname;
|
||||
}
|
||||
|
||||
public function session_lifetime_extension_days(): int
|
||||
{
|
||||
if ($this->session_lifetime_extension_days === null) {
|
||||
$config_value = $this->get('session_lifetime_extension_days', 0);
|
||||
if (is_int($config_value) && $config_value > 0) {
|
||||
// Make sure the value is in the range 1..365.
|
||||
$this->session_lifetime_extension_days = min(max(1, $config_value), 365);
|
||||
} else {
|
||||
$this->session_lifetime_extension_days = 0;
|
||||
}
|
||||
}
|
||||
return $this->session_lifetime_extension_days;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,6 +707,11 @@ abstract class rcube_session implements \SessionHandlerInterface
|
||||
$this->log('IP check failed for ' . $this->key . '; expected ' . $this->ip . '; got ' . rcube_utils::remote_addr());
|
||||
}
|
||||
|
||||
// Use the lifetime from the session so the cookie-name matching succeeds.
|
||||
if ($_SESSION['session_lifetime_extension']) {
|
||||
$this->set_lifetime($_SESSION['session_lifetime_extension']);
|
||||
}
|
||||
|
||||
if ($result && $this->mkcookie($this->now) != $this->cookie) {
|
||||
$this->log('Session auth check failed for ' . $this->key . '; timeslot = ' . date('Y-m-d H:i:s', $this->now));
|
||||
$result = false;
|
||||
@@ -725,6 +730,10 @@ abstract class rcube_session implements \SessionHandlerInterface
|
||||
if (!$result) {
|
||||
$this->log('Session authentication failed for ' . $this->key
|
||||
. '; invalid auth cookie sent; timeslot = ' . date('Y-m-d H:i:s', $prev));
|
||||
} else {
|
||||
// Re-set the auth- and session-id-cookie, because in case of an extended session lifetime they can have an
|
||||
// expiry date in the browser, which we need to extend.
|
||||
$this->set_auth_cookie();
|
||||
}
|
||||
|
||||
return $result;
|
||||
@@ -733,10 +742,20 @@ abstract class rcube_session implements \SessionHandlerInterface
|
||||
/**
|
||||
* Set session authentication cookie
|
||||
*/
|
||||
public function set_auth_cookie()
|
||||
public function set_auth_cookie(bool $session_lifetime_extension = false): void
|
||||
{
|
||||
if ($session_lifetime_extension === true) {
|
||||
if ($this->config->session_lifetime_extension_days() > 0) {
|
||||
$lifetime_seconds = $this->config->session_lifetime_extension_days() * 24 * 60 * 60;
|
||||
$this->set_lifetime($lifetime_seconds);
|
||||
$_SESSION['session_lifetime_extension'] = $lifetime_seconds;
|
||||
$cookie_expiry = time() + $lifetime_seconds;
|
||||
// Set the sessid-cookie (again) to force/renew its expiration date.
|
||||
rcube_utils::setcookie(ini_get('session.name'), session_id(), $cookie_expiry);
|
||||
}
|
||||
}
|
||||
$this->cookie = $this->mkcookie($this->now);
|
||||
rcube_utils::setcookie($this->cookiename, $this->cookie, 0);
|
||||
rcube_utils::setcookie($this->cookiename, $this->cookie, $cookie_expiry ?? 0);
|
||||
$_COOKIE[$this->cookiename] = $this->cookie;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ $labels['password'] = 'Password';
|
||||
$labels['server'] = 'Server';
|
||||
$labels['login'] = 'Login';
|
||||
$labels['oauthlogin'] = 'Login with $provider';
|
||||
$labels['session_lifetime_extension_switch_text'] = 'Remember login for up to # days';
|
||||
|
||||
// taskbar
|
||||
$labels['menu'] = 'Menu';
|
||||
|
||||
@@ -123,8 +123,8 @@ if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') {
|
||||
$RCMAIL->session->remove('temp');
|
||||
$RCMAIL->session->regenerate_id(false);
|
||||
|
||||
// send auth cookie if necessary
|
||||
$RCMAIL->session->set_auth_cookie();
|
||||
$session_lifetime_extension = rcube_utils::get_input_string('_session_lifetime_extension', rcube_utils::INPUT_POST);
|
||||
$RCMAIL->session->set_auth_cookie($session_lifetime_extension === 'on');
|
||||
|
||||
// log successful login
|
||||
$RCMAIL->log_login();
|
||||
|
||||
@@ -1160,6 +1160,11 @@ function rcube_elastic_ui() {
|
||||
icon_name = input.data('icon'),
|
||||
icon = $('<i>').attr('class', 'input-group-text icon ' + input.attr('name').replace('_', ''));
|
||||
|
||||
// Ignore checkboxes, they are prettified well enough by pretty_checkbox() already.
|
||||
if (input.attr('type') === 'checkbox') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (icon_name) {
|
||||
icon.addClass(icon_name);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user