From 2776d215e23f815cb4f34c02cd1b4c7bb6bb0219 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 3 Aug 2019 18:37:10 +0200 Subject: [PATCH] * Login: RememberMe token for either automatic login or as 2. factor for 2-Factor-Auth --- admin/templates/default/config.xet | 135 ++++++++----- api/lang/egw_de.lang | 4 +- api/lang/egw_en.lang | 2 + api/src/Framework/Login.php | 33 +++- api/src/Session.php | 285 ++++++++++++++++++++++++++-- login.php | 104 ++++------ logout.php | 7 +- pixelegg/css/mobile.css | 14 +- pixelegg/css/monochrome.css | 14 +- pixelegg/css/pixelegg.css | 14 +- pixelegg/less/layout_loginPage.less | 12 +- pixelegg/login.tpl | 2 +- pixelegg/mobile/fw_mobile.css | 14 +- setup/templates/default/config.tpl | 10 - 14 files changed, 478 insertions(+), 172 deletions(-) diff --git a/admin/templates/default/config.xet b/admin/templates/default/config.xet index 7e263f4585..1f1f8044fd 100644 --- a/admin/templates/default/config.xet +++ b/admin/templates/default/config.xet @@ -156,6 +156,9 @@ + + + - - + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -195,48 +240,19 @@ - - + + - - - - - - - - - - - - - - - - - - - + @@ -278,6 +294,37 @@ + + + + + + + + + + + + + diff --git a/api/lang/egw_de.lang b/api/lang/egw_de.lang index 8078f46a68..005ab8e42c 100644 --- a/api/lang/egw_de.lang +++ b/api/lang/egw_de.lang @@ -418,6 +418,7 @@ distribution lists as groups groupdav de Verteilerlisten als Gruppen djibouti common de DSCHIBUTI do not notify common de Nicht benachrichtigen do not notify of these changes common de Es werden keine Benachrichtigungen beim Anlegen oder Ändern versendet. +do not use on public computers! common de NICHT auf öffentlichen Computern verwenden! do you also want to delete all subcategories ? common de Sollen alle Unterkategorien gelöscht werden ? do you want to save the changes you made in table %s? common de Wollen Sie die Änderungen in der Tabelle '%s' speichern? do you want to send the message to all selected entries, without further editing? common de Wollen Sie die Nachricht an alle ausgewählten Einträge OHNE weitere Bearbeitung versenden? @@ -1082,7 +1083,8 @@ refresh common de Aktualisierung register common de Registrieren regular common de Normal reject common de Zurückweisen -remember me common de Mich erinnern +remember me common de An mich erinnern +remember me for %1 common de An mich erinnern für %1 remove row (can not be undone!!!) common de löscht eine Zeile (NICHT rückgängig zu machen) remove selected accounts common de Ausgewählte Benutzer entfernen remove shortcut common de Abkürzung entfernen diff --git a/api/lang/egw_en.lang b/api/lang/egw_en.lang index 701b748ef8..3a7c42e822 100644 --- a/api/lang/egw_en.lang +++ b/api/lang/egw_en.lang @@ -418,6 +418,7 @@ distribution lists as groups groupdav en Distribution lists as groups djibouti common en DJIBOUTI do not notify common en Do not notify do not notify of these changes common en Do not send notifications when creating or changing +do not use on public computers! common en Do NOT use on public computers! do you also want to delete all subcategories ? common en Do you also want to delete all sub categories? do you want to save the changes you made in table %s? common en Do you want to save the changes you made in table %s? do you want to send the message to all selected entries, without further editing? common en Do you want to send the message to all selected entries, WITHOUT further editing? @@ -1083,6 +1084,7 @@ register common en Register regular common en Regular reject common en Reject remember me common en Remember me +remember me for %1 common en Remember me for %1 remove row (can not be undone!!!) common en Remove row remove selected accounts common en Remove selected accounts remove shortcut common en Remove shortcut diff --git a/api/src/Framework/Login.php b/api/src/Framework/Login.php index d21e2ebb71..ccb2889e36 100644 --- a/api/src/Framework/Login.php +++ b/api/src/Framework/Login.php @@ -223,18 +223,31 @@ class Login * and place a time selectbox, how long cookie is valid * \********************************************************/ - if($GLOBALS['egw_info']['server']['allow_cookie_auth']) + if ($GLOBALS['egw_info']['server']['remember_me_token'] === 'always' || + ($GLOBALS['egw_info']['server']['2fa_required'] !== 'disabled' && + $GLOBALS['egw_info']['server']['remember_me_token'] !== 'disabled')) { $tmpl->set_block('login_form','remember_me_selection'); - $tmpl->set_var('lang_remember_me',lang('Remember me')); - $tmpl->set_var('select_remember_me',Api\Html::select('remember_me', '', array( - '' => lang('not'), - '1hour' => lang('1 Hour'), - '1day' => lang('1 Day'), - '1week'=> lang('1 Week'), - '1month' => lang('1 Month'), - 'forever' => lang('Forever'), - ),true,'tabindex="3"',0,false)); + $help = htmlspecialchars(lang('Do NOT use on public computers!')); + $tmpl->set_var('lang_remember_me_help', $help); + if ($GLOBALS['egw_info']['server']['remember_me_lifetime'] === 'user') + { + $tmpl->set_var('lang_remember_me', ''); + $tmpl->set_var('select_remember_me',Api\Html::select('remember_me', '', array( + '' => lang('Do not remember me'), + 'P1W'=> lang('Remember me for %1', lang('1 Week')), + 'P2W'=> lang('Remember me for %1', lang('2 Weeks')), + 'P1M' => lang('Remember me for %1', lang('1 Month')), + 'P3M' => lang('Remember me for %1', lang('3 Month')), + 'P1Y' => lang('Remember me for %1', lang('1 Year')), + ), true, 'tabindex="3" title="'.$help.'"', 0, false)); + } + else + { + $tmpl->set_var('lang_remember_me',lang('Remember me')); + $tmpl->set_var('select_remember_me', + Api\Html::checkbox('remember_me', false, 'True', ' id="remember_me" tabindex="3" title="'.$help.'"')); + } } else { diff --git a/api/src/Session.php b/api/src/Session.php index 746eda02e8..a094e81946 100644 --- a/api/src/Session.php +++ b/api/src/Session.php @@ -23,6 +23,8 @@ namespace EGroupware\Api; use PragmaRX\Google2FA; use EGroupware\Api\Mail\Credentials; +use EGroupware\OpenID; +use League\OAuth2\Server\Exception\OAuthServerException; /** * Create, verifies or destroys an EGroupware session @@ -75,6 +77,11 @@ class Session */ const EGW_SESSION_NAME = 'sessionid'; + /** + * Name of cookie with remember me token + */ + const REMEMBER_ME_COOKIE = 'eGW_remember'; + /** * current user login (account_lid@domain) * @@ -449,9 +456,10 @@ class Session * @param boolean $auth_check =true if false, the user is loged in without checking his password (eg. for single sign on), default = true * @param boolean $fail_on_forced_password_change =false true: do NOT create session, if password change requested * @param string|boolean $check_2fa =false string: 2fa-code to check (only if exists) and fail if wrong, false: do NOT check 2fa + * @param string $remember_me =null "True" for checkbox checked, or periode for user-choice select-box eg. "P1W" or "" for NOT remember * @return string|boolean session id or false if session was not created, $this->(cd_)reason contains cause */ - function create($login,$passwd = '',$passwd_type = '',$no_session=false,$auth_check=true,$fail_on_forced_password_change=false,$check_2fa=false) + function create($login,$passwd = '',$passwd_type = '',$no_session=false,$auth_check=true,$fail_on_forced_password_change=false,$check_2fa=false,$remember_me=null) { try { if (is_array($login)) @@ -498,6 +506,12 @@ class Session $this->account_id = $GLOBALS['egw']->accounts->name2id($this->account_lid,'account_lid','u'); + // do we need to check 'remember me' token (to bypass authentication) + if ($auth_check && !empty($_COOKIE[self::REMEMBER_ME_COOKIE])) + { + $auth_check = !$this->skipPasswordAuth($_COOKIE[self::REMEMBER_ME_COOKIE], $this->account_id); + } + if (($blocked = $this->login_blocked($login,$user_ip)) || // too many unsuccessful attempts $GLOBALS['egw_info']['server']['global_denied_users'][$this->account_lid] || $auth_check && !$GLOBALS['egw']->auth->authenticate($this->account_lid, $this->passwd, $this->passwd_type) || @@ -564,23 +578,10 @@ class Session Cache::setSession('phpgwapi', 'password', base64_encode($this->passwd)); // if we have a second factor, check it before forced password change - if ($check_2fa !== false && - $GLOBALS['egw_info']['server']['2fa_required'] !== 'disabled' && - (($creds = Credentials::read(0, Credentials::TWOFA, $this->account_id)) || - $GLOBALS['egw_info']['server']['2fa_required'] === 'strict')) + if ($check_2fa !== false) { - $google2fa = new Google2FA\Google2FA(); try { - if (empty($check_2fa) || empty($creds)) - { - throw new \Exception(Framework\Login::check_logoutcode(self::CD_SECOND_FACTOR_REQUIRED), self::CD_SECOND_FACTOR_REQUIRED); - } - if (!$google2fa->verify($check_2fa, $creds['2fa_password'])) - { - // we log the missing factor, but externally only show "Bad Login or Password" - // to give no indication that the password was already correct - throw new \Exception('Invalid 2-Factor Authentication code', self::CD_BAD_LOGIN_OR_PASSWORD); - } + $this->checkMultifactorAuth($check_2fa, $_COOKIE[self::REMEMBER_ME_COOKIE]); } catch(\Exception $e) { $this->cd_reason = $e->getCode(); @@ -649,6 +650,14 @@ class Session self::egw_setcookie('last_loginid', $this->account_lid ,$now+1209600); /* For 2 weeks */ self::egw_setcookie('last_domain',$this->account_domain,$now+1209600); } + + // set new remember me token/cookie, if requested and necessary + $expiration = null; + if (($token = $this->checkSetRememberMeToken($remember_me, $_COOKIE[self::REMEMBER_ME_COOKIE], $expiration))) + { + self::egw_setcookie(self::REMEMBER_ME_COOKIE, $token, $expiration); + } + if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."($this->login,$this->passwd,$this->passwd_type,$no_session,$auth_check) successfull sessionid=$this->sessionid"); // hook called once session is created @@ -670,7 +679,9 @@ class Session } // catch all exceptions, as their (allways logged) trace (eg. on a database error) would contain the user password catch(Exception $e) { - $this->reason = $this->cd_reason = $e->getMessage(); + $this->reason = $this->cd_reason = is_a($e, Db\Exception::class) ? + // do not output specific database error, eg. invalid SQL statement + lang('Database Error!') : $e->getMessage(); error_log(__METHOD__."('$login', ".array2string(str_repeat('*', strlen($passwd))). ", '$passwd_type', no_session=".array2string($no_session). ", auth_check=".array2string($auth_check). @@ -680,6 +691,246 @@ class Session } } + /** + * Check if password authentication is required or given token is sufficient + * + * Token is only checked for 'remember_me_token' === 'always', not for default of only for 2FA! + * + * Password auth is also required if 2FA is not disabled and either required or configured by user. + * + * @param string $token value of token + * @param int& $account_id =null account_id of token-owner to limit check on that user, on return account_id of token owner + * @return boolean false: if further auth check is required, true: if token is sufficient for authentication + */ + public function skipPasswordAuth($token, &$account_id=null) + { + // if token is empty or disabled --> password authentication required + if (empty($token) || $GLOBALS['egw_info']['server']['remember_me_token'] !== 'always' || + !($client = $this->checkOpenIDconfigured())) + { + return false; + } + + // check if token exists and is (still) valid + $tokenRepo = new OpenID\Repositories\AccessTokenRepository(); + if (!($access_token = $tokenRepo->findToken($client, $account_id, 'PT1S', $token))) + { + return false; + } + $account_id = $access_token->getUserIdentifier(); + + // check if we need a second factor + if ($GLOBALS['egw_info']['server']['2fa_required'] !== 'disabled' && + (($creds = Credentials::read(0, Credentials::TWOFA, $account_id)) || + $GLOBALS['egw_info']['server']['2fa_required'] === 'strict')) + { + return false; + } + + // access-token is sufficient + return true; + } + + /** + * Check multifcator authemtication + * + * @param string $code 2fa-code + * @param string $token remember me token + * @throws \Exception with error-message if NOT successful + */ + protected function checkMultifactorAuth($code, $token) + { + $errors = $factors = []; + + if ($GLOBALS['egw_info']['server']['2fa_required'] === 'disabled') + { + return; // nothing to check + } + + // check if token exists and is (still) valid + if (!empty($token) && $GLOBALS['egw_info']['server']['remember_me_token'] !== 'disabled' && + ($client = $this->checkOpenIDconfigured())) + { + $tokenRepo = new OpenID\Repositories\AccessTokenRepository(); + if ($tokenRepo->findToken($client, $this->account_id, 'PT1S', $token)) + { + $factors['remember_me_token'] = true; + } + else + { + $errors['remember_me_token'] = lang("Invalid or expired 'remember me' token"); + } + } + + // if 2fa is configured by user, check it + if (($creds = Credentials::read(0, Credentials::TWOFA, $this->account_id))) + { + if (empty($code)) + { + $errors['2fa_code'] = lang('2-Factor Authentication code required'); + } + else + { + $google2fa = new Google2FA\Google2FA(); + if (!empty($code) && $google2fa->verify($code, $creds['2fa_password'])) + { + $factors['2fa_code'] = true; + } + else + { + $errors['2fa_code'] = lang('Invalid 2-Factor Authentication code'); + } + } + } + + // check for more factors and/or policies + // hook can add factors, errors or throw \Exception with error-message and -code + Hooks::process([ + 'location' => 'multifactor_policy', + 'factors' => &$factors, + 'errors' => &$errors, + '2fa_code' => $code, + 'remember_me_token' => $token, + ], [], true); + + if (!count($factors) && (isset($errors['2fa_code']) || + $GLOBALS['egw_info']['server']['2fa_required'] === 'strict')) + { + if (!empty($code) && isset($errors['2fa_code'])) + { + // we log the missing factor, but externally only show "Bad Login or Password" + // to give no indication that the password was already correct + throw new \Exception(implode(', ', $errors), self::CD_BAD_LOGIN_OR_PASSWORD); + } + else + { + throw new \Exception(implode(', $errors'), self::CD_SECOND_FACTOR_REQUIRED); + } + } + } + + /** + * Check if we need to set a remember me token/cookie + * + * @param string $remember_me =null "True" for checkbox checked, or periode for user-choice select-box eg. "P1W" or "" for NOT remember + * @param string $token current remember me token + * @param int& $expriation on return expiration time of new cookie + * @return string new token to set as Cookieor null to not set a new one + */ + protected function checkSetRememberMeToken($remember_me, $token, &$expiration) + { + // do we need a new token + if (!empty($remember_me) && $GLOBALS['egw_info']['server']['remember_me_token'] !== 'disabled' && + ($client = $this->checkOpenIDconfigured())) + { + if (!empty($token)) + { + // check if token exists and is (still) valid + $tokenRepo = new OpenID\Repositories\AccessTokenRepository(); + if ($tokenRepo->findToken($client, $this->account_id, 'PT1S', $token)) + { + return null; // token still valid, no need to set it again + } + } + $lifetime = $this->rememberMeTokenLifetime(is_string($remember_me) ? $remember_me : null); + $expiration = $this->rememberMeTokenLifetime(is_string($remember_me) ? $remember_me : null, true); + + $tokenFactory = new OpenID\Token(); + if (($token = $tokenFactory->accessToken(self::OPENID_REMEMBER_ME_CLIENT_ID, [], $lifetime, false, $lifetime, false))) + { + return $token->getIdentifier(); + } + } + return null; + } + + /** + * Check if 'remember me' token should be deleted on explict logout + * + * @return boolean false: if 2FA is enabeld for user, true: otherwise + */ + public function removeRememberMeTokenOnLogout() + { + return $GLOBALS['egw_info']['server']['2fa_required'] === 'disabled' || + $GLOBALS['egw_info']['server']['2fa_required'] !== 'strict' && + !($creds = Credentials::read(0, Credentials::TWOFA, $this->account_id)); + } + + /** + * OpenID Client ID for remember me token + */ + const OPENID_REMEMBER_ME_CLIENT_ID = 'login-remember-me'; + + /** + * Check and if not configure OpenID app to generate 'remember me' tokens + * + * @return OpenID\Entities\ClientEntity|null null if OpenID Server app is not installed + */ + protected function checkOpenIDconfigured() + { + // OpenID app not installed --> password authentication required + if (!isset($GLOBALS['egw_info']['apps'])) + { + $GLOBALS['egw']->applications->read_installed_apps(); + } + if (empty($GLOBALS['egw_info']['apps']['openid'])) + { + return null; + } + + $clients = new OpenID\Repositories\ClientRepository(); + try { + $client = $clients->getClientEntity(self::OPENID_REMEMBER_ME_CLIENT_ID, null, null, false); // false = do NOT check client-secret + } + catch (OAuthServerException $e) + { + unset($e); + $client = new OpenID\Entities\ClientEntity(); + $client->setIdentifier(self::OPENID_REMEMBER_ME_CLIENT_ID); + $client->setSecret(Auth::randomstring(24)); // must not be unset + $client->setName(lang('Remember me token')); + $client->setAccessTokenTTL($this->rememberMeTokenLifetime()); + $client->setRefreshTokenTTL('P0S'); // no refresh token + $client->setRedirectUri($GLOBALS['egw_info']['server']['webserver_url'].'/'); + $clients->persistNewClient($client); + } + return $client; + } + + /** + * Return lifetime for remember me token + * + * @param string $user user choice, if allowed + * @param boolean $ts =false false: return periode string, true: return integer timestamp + * @return string periode spec eg. 'P1M' + */ + protected function rememberMeTokenLifetime($user=null, $ts=false) + { + switch ((string)$GLOBALS['egw_info']['server']['remember_me_lifetime']) + { + case 'user': + if (!empty($user)) + { + $lifetime = $user; + break; + } + // fall-through for default lifetime + case '': // default lifetime + $lifetime = 'P1M'; + break; + default: + $lifetime = $GLOBALS['egw_info']['server']['remember_me_lifetime']; + break; + } + if ($ts) + { + $expiration = new DateTime('now', DateTime::$server_timezone); + $expiration->add(new \DateInterval($lifetime)); + return $expiration->format('ts'); + } + return $lifetime; + } + /** * Store eGW specific session-vars * diff --git a/login.php b/login.php index b49815f5a1..5ddf40c810 100755 --- a/login.php +++ b/login.php @@ -136,44 +136,27 @@ else // some apache mod_auth_* modules use REMOTE_USER instead of PHP_AUTH_USER, thanks to Sylvain Beucler if ($GLOBALS['egw_info']['server']['auth_type'] == 'http' && !isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['REMOTE_USER'])) { - $_SERVER['PHP_AUTH_USER'] = $_SERVER['REMOTE_USER']; + $_SERVER['PHP_AUTH_USER'] = $_SERVER['REMOTE_USER']; } - if($GLOBALS['egw_info']['server']['auth_type'] == 'http' && isset($_SERVER['PHP_AUTH_USER'])) + $passwd = get_magic_quotes_gpc() ? stripslashes($_POST['passwd']) : $_POST['passwd']; + $passwd_type = $_POST['passwd_type']; + + // forced password change + if($GLOBALS['egw']->session->cd_reason != Api\Session::CD_FORCE_PASSWORD_CHANGE) + { + // no automatic login + } + // authentication via Apache + elseif ($GLOBALS['egw_info']['server']['auth_type'] == 'http' && isset($_SERVER['PHP_AUTH_USER'])) { $submit = True; $login = $_SERVER['PHP_AUTH_USER']; $passwd = $_SERVER['PHP_AUTH_PW']; $passwd_type = 'text'; } - else - { - $passwd = get_magic_quotes_gpc() ? stripslashes($_POST['passwd']) : $_POST['passwd']; - $passwd_type = $_POST['passwd_type']; - - if($GLOBALS['egw_info']['server']['allow_cookie_auth']) - { - $eGW_remember = explode('::::',get_magic_quotes_gpc() ? stripslashes($_COOKIE['eGW_remember']) : $_COOKIE['eGW_remember']); - - if($eGW_remember[0] && $eGW_remember[1] && $eGW_remember[2]) - { - $_SERVER['PHP_AUTH_USER'] = $login = $eGW_remember[0]; - $_SERVER['PHP_AUTH_PW'] = $passwd = $eGW_remember[1]; - $passwd_type = $eGW_remember[2]; - $submit = True; - } - } - if(!$passwd && ($GLOBALS['egw_info']['server']['auto_anon_login']) && !$_GET['cd']) - { - $_SERVER['PHP_AUTH_USER'] = $login = 'anonymous'; - $_SERVER['PHP_AUTH_PW'] = $passwd = 'anonymous'; - $passwd_type = 'text'; - $submit = True; - } - } - # Apache + mod_ssl style SSL certificate authentication # Certificate (chain) verification occurs inside mod_ssl - if($GLOBALS['egw_info']['server']['auth_type'] == 'sqlssl' && isset($_SERVER['SSL_CLIENT_S_DN']) && !isset($_GET['cd'])) + elseif($GLOBALS['egw_info']['server']['auth_type'] == 'sqlssl' && isset($_SERVER['SSL_CLIENT_S_DN']) && !isset($_GET['cd'])) { // an X.509 subject looks like: // CN=john.doe/OU=Department/O=Company/C=xx/Email=john@comapy.tld/L=City/ @@ -203,20 +186,33 @@ else unset($val); unset($sslattributes); } - - if(isset($passwd_type) || $_POST['submitit_x'] || $_POST['submitit_y'] || $submit) + else { - if(getenv('REQUEST_METHOD') != 'POST' && $_SERVER['REQUEST_METHOD'] != 'POST' && + // check if we have a sufficient access-token as cookie and no forced password change + if ($GLOBALS['egw']->session->cd_reason != Api\Session::CD_FORCE_PASSWORD_CHANGE && + $GLOBALS['egw']->session->skipPasswordAuth($_COOKIE[Api\Session::REMEMBER_ME_COOKIE], $account_id)) + { + $_SERVER['PHP_AUTH_USER'] = $login = Api\Accounts::id2name($account_id); + $submit = true; + } + + if(!$passwd && ($GLOBALS['egw_info']['server']['auto_anon_login']) && !$_GET['cd']) + { + $_SERVER['PHP_AUTH_USER'] = $login = 'anonymous'; + $_SERVER['PHP_AUTH_PW'] = $passwd = 'anonymous'; + $passwd_type = 'text'; + $submit = True; + } + } + + + if (isset($passwd_type) || $submit) + { + if($_SERVER['REQUEST_METHOD'] != 'POST' && !isset($_SERVER['PHP_AUTH_USER']) && !isset($_SERVER['SSL_CLIENT_S_DN'])) { - $GLOBALS['egw']->session->egw_setcookie('eGW_remember','',0,'/'); Egw::redirect_link('/login.php','cd=5'); } - /* cookie enabled check comment out, as it seems to cause a redirect loop under certain conditions and browsers :-( - if ($_COOKIE['eGW_cookie_test'] !== 'enabled') - { - Egw::redirect_link('/login.php','cd=4'); - }*/ // don't get login data again when $submit is true if($submit == false) @@ -252,7 +248,7 @@ else } } $GLOBALS['sessionid'] = $GLOBALS['egw']->session->create($login, $passwd, - $passwd_type, false, true, true, $_POST['2fa_code']); // true = let session fail on forced password change + $passwd_type, false, true, true, $_POST['2fa_code'], $_POST['remember_me']); // true = let session fail on forced password change if (!$GLOBALS['sessionid'] && $GLOBALS['egw']->session->cd_reason == Api\Session::CD_FORCE_PASSWORD_CHANGE) { @@ -278,40 +274,10 @@ else } elseif (!isset($GLOBALS['sessionid']) || ! $GLOBALS['sessionid']) { - Api\Session::egw_setcookie('eGW_remember','',0,'/'); Egw::redirect_link('/login.php?cd=' . $GLOBALS['egw']->session->cd_reason); } else { - /* set auth_cookie */ - if($GLOBALS['egw_info']['server']['allow_cookie_auth'] && $_POST['remember_me'] && $_POST['passwd']) - { - switch ($_POST['remember_me']) - { - case '1hour' : - $remember_time = time()+60*60; - break; - case '1day' : - $remember_time = time()+60*60*24; - break; - case '1week' : - $remember_time = time()+60*60*24*7; - break; - case '1month' : - $remember_time = time()+60*60*24*30; - break; - case 'forever' : - default: - $remember_time = 2147483647; - break; - } - $GLOBALS['egw']->session->egw_setcookie('eGW_remember',implode('::::',array( - 'login' => $login, - 'passwd' => $passwd, - 'passwd_type' => $passwd_type)), - $remember_time,'/'); // make the cookie valid for the whole site (incl. sitemgr) and not only the eGW install-dir - } - if ($_POST['lang'] && preg_match('/^[a-z]{2}(-[a-z]{2})?$/',$_POST['lang']) && $_POST['lang'] != $GLOBALS['egw_info']['user']['preferences']['common']['lang']) { diff --git a/logout.php b/logout.php index 00eddd94e1..d74fdb3642 100755 --- a/logout.php +++ b/logout.php @@ -37,13 +37,18 @@ elseif(strpos($redirectTarget, '[?&]cd=') !== false) $redirectTarget = preg_replace('/([?&])cd=[^&]+/', '$1cd=1', $redirectTarget); } +// remove remember me cookie on explicit logout, unless it is a second factor +if ($GLOBALS['egw']->session->removeRememberMeTokenOnLogout()) +{ + Api\Session::egw_setcookie('eGW_remember','',0,'/'); +} + if($verified) { Api\Hooks::process('logout'); $GLOBALS['egw']->session->destroy($GLOBALS['sessionid'],$GLOBALS['kp3']); } -Api\Session::egw_setcookie('eGW_remember','',0,'/'); Api\Session::egw_setcookie('sessionid'); Api\Session::egw_setcookie('kp3'); Api\Session::egw_setcookie('domain'); diff --git a/pixelegg/css/mobile.css b/pixelegg/css/mobile.css index f32d86a781..b99cf203a7 100644 --- a/pixelegg/css/mobile.css +++ b/pixelegg/css/mobile.css @@ -1919,8 +1919,9 @@ body { background-color: transparent; } #loginMainDiv div#centerBox form table.divLoginbox select[name="remember_me"] { - text-indent: 60%; - background-color: transparent; + background-image: url(../images/task.png); + background-repeat: no-repeat; + background-position-x: 0; } #loginMainDiv div#centerBox form table.divLoginbox select:focus, #loginMainDiv div#centerBox form table.divLoginbox select:hover { @@ -1958,9 +1959,9 @@ body { background-image: url(../images/password.png); } #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.remember_me { - background-image: url(../images/task.png); + background-image: none; z-index: 0; - width: 130px; + width: 230px; padding-left: 27px; } #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.domain { @@ -1969,6 +1970,11 @@ body { #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.language { background-image: url(../images/language.png); } +#loginMainDiv div#centerBox form table.divLoginbox input[type="checkbox"] { + height: 25px; + margin-top: 7px; + width: auto; +} #loginMainDiv div#centerBox form table.divLoginbox input[type="submit"] { background-color: #0a5ca5; color: #ffffff; diff --git a/pixelegg/css/monochrome.css b/pixelegg/css/monochrome.css index 9764a853ac..86ae6da2aa 100644 --- a/pixelegg/css/monochrome.css +++ b/pixelegg/css/monochrome.css @@ -1908,8 +1908,9 @@ body { background-color: transparent; } #loginMainDiv div#centerBox form table.divLoginbox select[name="remember_me"] { - text-indent: 60%; - background-color: transparent; + background-image: url(../images/task.png); + background-repeat: no-repeat; + background-position-x: 0; } #loginMainDiv div#centerBox form table.divLoginbox select:focus, #loginMainDiv div#centerBox form table.divLoginbox select:hover { @@ -1947,9 +1948,9 @@ body { background-image: url(../images/password.png); } #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.remember_me { - background-image: url(../images/task.png); + background-image: none; z-index: 0; - width: 130px; + width: 230px; padding-left: 27px; } #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.domain { @@ -1958,6 +1959,11 @@ body { #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.language { background-image: url(../images/language.png); } +#loginMainDiv div#centerBox form table.divLoginbox input[type="checkbox"] { + height: 25px; + margin-top: 7px; + width: auto; +} #loginMainDiv div#centerBox form table.divLoginbox input[type="submit"] { background-color: #0a5ca5; color: #ffffff; diff --git a/pixelegg/css/pixelegg.css b/pixelegg/css/pixelegg.css index 13993a3129..f0ca3b2b91 100644 --- a/pixelegg/css/pixelegg.css +++ b/pixelegg/css/pixelegg.css @@ -1919,8 +1919,9 @@ body { background-color: transparent; } #loginMainDiv div#centerBox form table.divLoginbox select[name="remember_me"] { - text-indent: 60%; - background-color: transparent; + background-image: url(../images/task.png); + background-repeat: no-repeat; + background-position-x: 0; } #loginMainDiv div#centerBox form table.divLoginbox select:focus, #loginMainDiv div#centerBox form table.divLoginbox select:hover { @@ -1958,9 +1959,9 @@ body { background-image: url(../images/password.png); } #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.remember_me { - background-image: url(../images/task.png); + background-image: none; z-index: 0; - width: 130px; + width: 230px; padding-left: 27px; } #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.domain { @@ -1969,6 +1970,11 @@ body { #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.language { background-image: url(../images/language.png); } +#loginMainDiv div#centerBox form table.divLoginbox input[type="checkbox"] { + height: 25px; + margin-top: 7px; + width: auto; +} #loginMainDiv div#centerBox form table.divLoginbox input[type="submit"] { background-color: #0a5ca5; color: #ffffff; diff --git a/pixelegg/less/layout_loginPage.less b/pixelegg/less/layout_loginPage.less index 1aad1b9d5f..0b61d656e2 100644 --- a/pixelegg/less/layout_loginPage.less +++ b/pixelegg/less/layout_loginPage.less @@ -189,8 +189,9 @@ background-color: transparent; } select[name="remember_me"] { - text-indent: 60%; - background-color: transparent; + background-image: url(../images/task.png); + background-repeat: no-repeat; + background-position-x: 0; } select:focus, select:hover {box-shadow:none;} input { @@ -221,9 +222,14 @@ } span.field_icons.username {background-image: url(../images/personal.png);} span.field_icons.password {background-image: url(../images/password.png);} - span.field_icons.remember_me {background-image: url(../images/task.png);z-index:0;width: 130px;padding-left: 27px;} + span.field_icons.remember_me {background-image: none;z-index:0;width: 230px;padding-left: 27px;} span.field_icons.domain {background-image: url(../images/internet.png);} span.field_icons.language {background-image: url(../images/language.png);} + input[type="checkbox"] { + height: 25px; + margin-top: 7px; + width: auto; + } input[type="submit"] { background-color: #0a5ca5; .color_0_gray; diff --git a/pixelegg/login.tpl b/pixelegg/login.tpl index c07f5acf57..b30c588b38 100644 --- a/pixelegg/login.tpl +++ b/pixelegg/login.tpl @@ -51,7 +51,7 @@ - {lang_remember_me}: + {select_remember_me} diff --git a/pixelegg/mobile/fw_mobile.css b/pixelegg/mobile/fw_mobile.css index f57e673026..223483a862 100644 --- a/pixelegg/mobile/fw_mobile.css +++ b/pixelegg/mobile/fw_mobile.css @@ -1930,8 +1930,9 @@ body { background-color: transparent; } #loginMainDiv div#centerBox form table.divLoginbox select[name="remember_me"] { - text-indent: 60%; - background-color: transparent; + background-image: url(../images/task.png); + background-repeat: no-repeat; + background-position-x: 0; } #loginMainDiv div#centerBox form table.divLoginbox select:focus, #loginMainDiv div#centerBox form table.divLoginbox select:hover { @@ -1969,9 +1970,9 @@ body { background-image: url(../images/password.png); } #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.remember_me { - background-image: url(../images/task.png); + background-image: none; z-index: 0; - width: 130px; + width: 230px; padding-left: 27px; } #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.domain { @@ -1980,6 +1981,11 @@ body { #loginMainDiv div#centerBox form table.divLoginbox span.field_icons.language { background-image: url(../images/language.png); } +#loginMainDiv div#centerBox form table.divLoginbox input[type="checkbox"] { + height: 25px; + margin-top: 7px; + width: auto; +} #loginMainDiv div#centerBox form table.divLoginbox input[type="submit"] { background-color: #0a5ca5; color: #ffffff; diff --git a/setup/templates/default/config.tpl b/setup/templates/default/config.tpl index 7f003bdafe..ff28e6a4f9 100644 --- a/setup/templates/default/config.tpl +++ b/setup/templates/default/config.tpl @@ -192,16 +192,6 @@ - - {lang_Allow_authentication_via_cookie}: - - - - - {lang_Auto_login_anonymous_user}: