store user-password encrypted in the session, managed by session-class via $GLOBALS['egw']->session->passwd

This commit is contained in:
ralf 2025-02-10 20:56:20 +01:00
parent 7000326f40
commit 424ce562a7
8 changed files with 99 additions and 27 deletions

View File

@ -397,8 +397,7 @@ class Auth
if ($account_id == $GLOBALS['egw']->session->account_id)
{
// need to change current users password in session
Cache::setSession('phpgwapi', 'password', base64_encode($new_passwd));
$GLOBALS['egw_info']['user']['passwd'] = $new_passwd;
$GLOBALS['egw']->session->passwd = $new_passwd;
$GLOBALS['egw_info']['user']['account_lastpwd_change'] = DateTime::to('now','ts');
// invalidate EGroupware session, as password is stored in egw_info in session
Egw::invalidate_session_cache();

View File

@ -499,6 +499,11 @@ class Cache
if (Session::ERROR_LOG_DEBUG) error_log(__METHOD__.' called after session was encrypted --> ignored!');
return false; // can no longer store something in the session, eg. because commit_session() was called
}
// user password is no longer stored (unencrypted) in session, but encrypted by session-class
if ($app === 'phpgwapi' && $location === 'password')
{
return false;
}
$_SESSION[Session::EGW_APPSESSION_VAR][$app][$location] = $data;
if ($expiration > 0)
@ -529,6 +534,13 @@ class Cache
$ret = null; // can no longer store something in the session, eg. because commit_session() was called
return $ret;
}
// user password is no longer stored (unencrypted) in session, but encrypted by session-class
if ($app === 'phpgwapi' && $location === 'password')
{
$passwd = isset($GLOBALS['egw']->session) && $GLOBALS['egw']->session->__isset('passwd') ?
base64_encode($GLOBALS['egw']->session->__get('passwd')) : null;
return $passwd;
}
// check if entry is expired and clean it up in that case
if (isset($_SESSION[Session::EGW_APPSESSION_VAR][self::SESSION_EXPIRATION_PREFIX.$app][$location]) &&
$_SESSION[Session::EGW_APPSESSION_VAR][self::SESSION_EXPIRATION_PREFIX.$app][$location] < time())
@ -897,4 +909,4 @@ if (is_null(Cache::$default_provider))
'EGroupware\Api\Cache\Files'));
}
//error_log('Cache::$default_provider='.array2string(Cache::$default_provider));
//error_log('Cache::$default_provider='.array2string(Cache::$default_provider));

View File

@ -425,7 +425,7 @@ class Ldap
$this->ds = Api\Ldap::factory(true,
$this->ldap_config['ldap_contact_host'],
$GLOBALS['egw_info']['user']['account_dn'],
$GLOBALS['egw_info']['user']['passwd'],
$GLOBALS['egw']->session->passwd,
$reconnect
);
}

View File

@ -297,8 +297,7 @@ class Credentials
if (isset($data[self::$type2prefix[self::SSO_PASSWORD].'password']))
{
Api\Cache::setSession('phpgwapi', 'password',
base64_encode($GLOBALS['egw']->session->passwd = $data[self::$type2prefix[self::SSO_PASSWORD].'password']));
$GLOBALS['egw']->session->passwd = $data[self::$type2prefix[self::SSO_PASSWORD].'password'];
return $data[self::$type2prefix[self::SSO_PASSWORD].'password'];
}
@ -391,7 +390,7 @@ class Credentials
default:
throw new Api\Exception\WrongParameter("Unknown data[acc_imap_logintype]=".array2string($data['acc_imap_logintype']).'!');
}
$password = base64_decode(Api\Cache::getSession('phpgwapi', 'password'));
$password = $GLOBALS['egw']->session->passwd;
// if session password is a token, do NOT use it, but also do NOT throw, just return NULL for the password(s)
if (Api\Auth\Token::isToken($password))
{
@ -580,7 +579,7 @@ class Credentials
if (empty($key))
{
if ($use_system !== true && $account_id > 0 && $account_id == $GLOBALS['egw_info']['user']['account_id'] &&
($key = Api\Cache::getSession('phpgwapi', 'password')) &&
($key = $GLOBALS['egw']->session->passwd) &&
// do NOT encrypt password if (optional) SAML or OpenIdConnect auth is enabled
!array_filter(array_keys(Api\Config::read('phpgwapi')), static function($name)
{
@ -736,7 +735,7 @@ class Credentials
{
if (self::isUser($pw_enc))
{
$session_key = Api\Cache::getSession('phpgwapi', 'password');
$session_key = $GLOBALS['egw']->session->passwd;
if (empty($session_key))
{
throw new NoSessionPassword();
@ -868,7 +867,7 @@ class Credentials
if (empty($data['old_passwd'])) return;
// as self::encrypt will use password in session, check it is identical to given new password
if ($data['new_passwd'] !== base64_decode(Api\Cache::getSession('phpgwapi', 'password')))
if ($data['new_passwd'] !== $GLOBALS['egw']->session->passwd)
{
throw new Api\Exception\AssertionFailed('Password in session !== password given in $data[new_password]!');
}
@ -915,7 +914,7 @@ class Credentials
}
elseif ($user)
{
$session_key = Api\Cache::getSession('phpgwapi', 'password');
$session_key = $GLOBALS['egw']->session->passwd;
if (empty($session_key))
{
error_log(__METHOD__."() no session password available!");

View File

@ -35,6 +35,8 @@ use League\OAuth2\Server\Exception\OAuthServerException;
* {
* // switch that on to analyse memory usage in the session
* //self::log_session_usage($_SESSION[self::EGW_APPSESSION_VAR],'_SESSION['.self::EGW_APPSESSION_VAR.']',true,5000);
*
* @var passwd user-password from session (it's stored encrypted in the session)
*/
class Session
{
@ -83,11 +85,17 @@ class Session
var $login;
/**
* current user password
* current encrypted user password
*
* @var string
*/
var $passwd;
public $password_encrypted;
/**
* @var string decrypted password, do NOT use direct, set or read $this->passwd (__sleep() method ensures it never gets serialized into session)
*/
private $_password;
public $passwd_type;
/**
* current user db/ldap account id
@ -151,7 +159,7 @@ class Session
*
* @var array
*/
private $egw_domains;
public $egw_domains;
/**
* Nummeric code why session creation failed
@ -279,6 +287,62 @@ class Session
$this->action = null;
}
/**
* Make sure to NOT serialize unencrypted user password ($this->_password) to session
*
* @return string[]
*/
function __sleep()
{
unset($this->_password); // not really necessary, as get_object_vars() only returns public attributes
return array_keys(get_object_vars($this));
}
/**
* Magic getter: currently used to decrypt encrypted user password from session ($this->password_encrypted)
* @param $name
* @return mixed
*/
public function __get($name)
{
if ($name === 'passwd')
{
if (!isset($this->_password) && isset($this->password_encrypted))
{
$this->_password = Mail\Credentials::decrypt([
'cred_password' => $this->password_encrypted,
'cred_pw_enc' => Mail\Credentials::SYSTEM_AES, // use system secret aka. DB password
]);
}
return $this->_password;
}
}
/**
* Magic setter: currently only used to store user password encrypted in the session
*
* @param $name
* @param $value
* @return void
*/
public function __set($name, $value)
{
if ($name === 'passwd')
{
$this->password_encrypted = Mail\Credentials::encrypt($value, $this->account_id, $pw_enc, true);
$this->_password = $value;
}
}
public function __isset($name)
{
if ($name === 'passwd')
{
return isset($this->password_encrypted);
}
}
/**
* Destructor: update access-log and encrypt session
*/
@ -578,8 +642,6 @@ class Session
return false;
}
Cache::setSession('phpgwapi', 'password', base64_encode($this->passwd));
// if we have a second factor, check it before forced password change
if ($check_2fa !== false)
{
@ -758,7 +820,7 @@ class Session
}
/**
* Check multifcator authemtication
* Check multifactor authentication
*
* @param string $code 2fa-code
* @param string $token remember me token
@ -980,6 +1042,7 @@ class Session
// we need to preserve the limits and if authenticated via token
'session_limits' => $this->limits,
'session_token_auth' => $this->token_auth,
'session_password' => $this->password_encrypted,
);
}
@ -1375,11 +1438,10 @@ class Session
if (self::ERROR_LOG_DEBUG) error_log("*** Session::verify($sessionid) accounts is expired");
return false;
}
$this->passwd = base64_decode(Cache::getSession('phpgwapi', 'password'));
$this->password_encrypted = $session['session_password'];
if ($fill_egw_info_and_repositories)
{
$GLOBALS['egw_info']['user']['session_ip'] = $session['session_ip'];
$GLOBALS['egw_info']['user']['passwd'] = $this->passwd;
}
if ($this->account_domain != $GLOBALS['egw_info']['user']['domain'])
{
@ -2017,7 +2079,7 @@ class Session
{
$probable_domain = array_pop($parts);
//Last part of login string, when separated by @, is a domain name
if (in_array($probable_domain,$this->egw_domains))
if ($this->egw_domains && in_array($probable_domain, $this->egw_domains))
{
$got_login = true;
$domain = $probable_domain;
@ -2230,4 +2292,4 @@ class Session
'notification_heartbeat > '.self::heartbeat_limit(),
), __LINE__, __FILE__)->fetchColumn();
}
}
}

View File

@ -87,7 +87,7 @@ class Base
{
$check_url = strtr($url, [
'$user' => $GLOBALS['egw_info']['user']['account_lid'],
'$pass' => urlencode($GLOBALS['egw_info']['user']['passwd']),
'$pass' => urlencode($GLOBALS['egw']->session->passwd),
'$host' => $GLOBALS['egw_info']['user']['domain'],
'$home' => str_replace(array('\\\\', '\\'), array('', '/'), $GLOBALS['egw_info']['user']['homedirectory']),
]);
@ -291,12 +291,12 @@ class Base
// setting default user, passwd and domain, if it's not contained int the url
$defaults = array(
'user' => $GLOBALS['egw_info']['user']['account_lid'],
'pass' => urlencode($GLOBALS['egw_info']['user']['passwd'] ?? ''),
'pass' => urlencode($GLOBALS['egw']->session->passwd ?? ''),
'host' => $GLOBALS['egw_info']['user']['domain'],
'home' => str_replace(array('\\\\', '\\'), array('', '/'), $GLOBALS['egw_info']['user']['homedirectory'] ?? ''),
);
// check if we have a (base64 encoded) fallback-auth GET parameter and need to use it
if (empty($GLOBALS['egw_info']['user']['passwd']) && ($query=Vfs::parse_url($_path, PHP_URL_QUERY)) &&
if (empty($GLOBALS['egw']->session->passwd) && ($query=Vfs::parse_url($_path, PHP_URL_QUERY)) &&
strpos($query, 'fallback-auth=') !== false)
{
parse_str($query, $query_params);

View File

@ -2180,7 +2180,7 @@ class calendar_bo
elseif ($GLOBALS['egw_info']['user']['preferences']['calendar']['freebusy'] == 2)
{
$credentials = $GLOBALS['egw_info']['user']['account_lid']
. ':' . $GLOBALS['egw_info']['user']['passwd'];
. ':' . $GLOBALS['egw']->session->passwd;
$credentials = '&cred=' . base64_encode($credentials);
}
return Api\Framework::getUrl($GLOBALS['egw_info']['server']['webserver_url']).

View File

@ -311,8 +311,8 @@ class preferences_password
}
$errors = array();
if (isset($GLOBALS['egw_info']['user']['passwd']) &&
$old_passwd !== $GLOBALS['egw_info']['user']['passwd'])
if (isset($GLOBALS['egw']->session->passwd) &&
$old_passwd !== $GLOBALS['egw']->session->passwd)
{
$errors[] = lang('The old password is not correct');
}