Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions config/defaults.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,15 @@
// 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the code the default value is 0.


// Session domain: .example.org
$config['session_domain'] = '';

Expand Down
12 changes: 12 additions & 0 deletions program/include/rcmail_output_html.php
Original file line number Diff line number Diff line change
Expand Up @@ -2355,6 +2355,18 @@ protected function login_form($attrib)
'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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hidden field isn't used, is it?


// 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]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the checkbox is checked by default, it shouldn't.

];
}

if (is_array($default_host) && count($default_host) > 1) {
$input_host = new html_select(['name' => '_host', 'id' => 'rcmloginhost', 'class' => 'custom-select']);

Expand Down
15 changes: 15 additions & 0 deletions program/lib/Roundcube/rcube_config.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class rcube_config
private $userprefs = [];
private $immutable = [];
private $client_tz;
private $session_lifetime_extension_days;

/**
* Renamed options
Expand Down Expand Up @@ -926,4 +927,18 @@ public static function resolve_timezone_alias($tzname)

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;
}
}
23 changes: 21 additions & 2 deletions program/lib/Roundcube/rcube_session.php
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,11 @@ public function check_auth()
$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;
Expand All @@ -725,6 +730,10 @@ public function check_auth()
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;
Expand All @@ -733,10 +742,20 @@ public function check_auth()
/**
* 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;
}

Expand Down
1 change: 1 addition & 0 deletions program/localization/en_US/labels.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions public_html/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@
$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();
Expand Down
5 changes: 5 additions & 0 deletions skins/elastic/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we return here the tr will not get "form-group row" class, which makes it looking better (adds margin). Also, it would be good to align this row content to the center, imo.

}

if (icon_name) {
icon.addClass(icon_name);
}
Expand Down