Server side control/kickout of logged in sessions
This commit is contained in:
djmaze 2021-12-09 14:26:55 +01:00
parent 40471957ee
commit 5719b63619
5 changed files with 62 additions and 27 deletions

View file

@ -131,7 +131,10 @@ trait UserAuth
try {
$this->CheckMailConnection($oAccount, true);
$bSignMe && $this->SetSignMeToken($oAccount);
if (!$oMainAccount) {
$bSignMe && $this->SetSignMeToken($oAccount);
$this->StorageProvider()->Put($oAccount, StorageType::SESSION, Utils::GetSessionToken(), 'true');
}
} catch (\Throwable $oException) {
$this->loginErrorDelay();
@ -177,6 +180,8 @@ trait UserAuth
*/
public function getAccountFromToken(bool $bThrowExceptionOnFalse = true): ?Account
{
$this->getMainAccountFromToken($bThrowExceptionOnFalse);
if (\is_null($this->oAdditionalAuthAccount) && isset($_COOKIE[self::AUTH_ADDITIONAL_TOKEN_KEY])) {
$aData = Utils::GetSecureCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
if ($aData) {
@ -191,7 +196,8 @@ trait UserAuth
Utils::ClearCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
}
}
return $this->oAdditionalAuthAccount ?: $this->getMainAccountFromToken($bThrowExceptionOnFalse);
return $this->oAdditionalAuthAccount ?: $this->oMainAuthAccount;
}
/**
@ -205,15 +211,34 @@ trait UserAuth
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
// Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
// Utils::ClearCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
Utils::ClearCookie(Utils::SESSION_TOKEN);
}
$aData = Utils::GetSecureCookie(self::AUTH_SPEC_TOKEN_KEY);
if ($aData) {
$this->oMainAuthAccount = MainAccount::NewInstanceFromTokenArray(
$this,
$aData,
$bThrowExceptionOnFalse
);
/**
* Server side control/kickout of logged in sessions
* https://github.com/the-djmaze/snappymail/issues/151
*/
if (isset($_COOKIE[Utils::SESSION_TOKEN])) {
$oMainAuthAccount = MainAccount::NewInstanceFromTokenArray(
$this,
$aData,
$bThrowExceptionOnFalse
);
$sToken = Utils::GetSessionToken();
if ($oMainAuthAccount && $this->StorageProvider()->Get($oMainAuthAccount, StorageType::SESSION, $sToken)) {
$this->oMainAuthAccount = $oMainAuthAccount;
} else {
$oMainAuthAccount && $this->StorageProvider()->Clear($oMainAuthAccount, StorageType::SESSION, $sToken);
Utils::ClearCookie(Utils::SESSION_TOKEN);
Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
Utils::ClearCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
}
} else {
Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
Utils::ClearCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
}
} else {
$oAccount = $this->GetAccountFromSignMeToken();
if ($oAccount) {
@ -224,6 +249,10 @@ trait UserAuth
if ($bThrowExceptionOnFalse && !$this->oMainAuthAccount) {
throw new ClientException(Notifications::AuthError);
}
if ($this->oMainAuthAccount) {
$this->StorageProvider()->Put($this->oMainAuthAccount, StorageType::SESSION, $sToken, 'true');
}
}
return $this->oMainAuthAccount;

View file

@ -208,7 +208,7 @@ class Api
public static function LogoutCurrentLogginedUser() : bool
{
// TODO: kill SignMe data to prevent automatic login?
Utils::ClearCookie(Utils::SHORT_TOKEN);
Utils::ClearCookie(Utils::SESSION_TOKEN);
return true;
}

View file

@ -8,4 +8,5 @@ class StorageType
const CONFIG = 2;
const NOBODY = 3;
const SIGN_ME = 4;
const SESSION = 5;
}

View file

@ -50,7 +50,6 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
if ($sFileName && \file_exists($sFileName)) {
$mValue = \file_get_contents($sFileName);
}
return false === $mValue ? $mDefault : $mValue;
}
@ -59,13 +58,8 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
*/
public function Clear($mAccount, int $iStorageType, string $sKey) : bool
{
$mResult = true;
$sFileName = $this->generateFileName($mAccount, $iStorageType, $sKey);
if ($sFileName && \file_exists($sFileName)) {
$mResult = \unlink($sFileName);
}
return $mResult;
return $sFileName && \file_exists($sFileName) && \unlink($sFileName);
}
/**
@ -90,14 +84,20 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
*/
protected function generateFileName($mAccount, int $iStorageType, string $sKey, bool $bMkDir = false, bool $bForDeleteAction = false) : string
{
$sEmail = $sSubEmail = '';
$sEmail = $sSubFolder = '';
if (null === $mAccount) {
$iStorageType = StorageType::NOBODY;
} else if ($mAccount instanceof \RainLoop\Model\Account) {
$sEmail = $mAccount instanceof \RainLoop\Model\AdditionalAccount ? $mAccount->ParentEmail() : $mAccount->Email();
if ($this->bLocal && $mAccount instanceof \RainLoop\Model\AdditionalAccount && !$bForDeleteAction)
{
$sSubEmail = $mAccount->Email();
} else if ($mAccount instanceof \RainLoop\Model\MainAccount) {
$sEmail = $mAccount->Email();
if (StorageType::SIGN_ME === $iStorageType) {
$sSubFolder = '.sign_me';
} else if (StorageType::SESSION === $iStorageType) {
$sSubFolder = '.sessions';
}
} else if ($mAccount instanceof \RainLoop\Model\AdditionalAccount) {
$sEmail = $mAccount->ParentEmail();
if ($this->bLocal && !$bForDeleteAction) {
$sSubFolder = $mAccount->Email();
}
} else if (\is_string($mAccount) && empty($sEmail)) {
$sEmail = $mAccount;
@ -110,7 +110,7 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
$sFilePath = $this->sDataPath.'/__nobody__/'.\sha1($sKey ?: \time());
break;
case StorageType::SIGN_ME:
$sSubEmail = '.sign_me';
case StorageType::SESSION:
case StorageType::CONFIG:
if (empty($sEmail)) {
return '';
@ -123,7 +123,7 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
$sFilePath = $this->sDataPath
.'/'.\RainLoop\Utils::fixName($sDomain ?: 'unknown.tld')
.'/'.\RainLoop\Utils::fixName(\implode('@', $aEmail) ?: '.unknown')
.'/'.($sSubEmail ? \RainLoop\Utils::fixName($sSubEmail).'/' : '')
.'/'.($sSubFolder ? \RainLoop\Utils::fixName($sSubFolder).'/' : '')
.($sKey ? \RainLoop\Utils::fixName($sKey) : '');
break;
default:
@ -138,11 +138,16 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
}
}
// CleanupSignMeData
// Cleanup SignMe
if (StorageType::SIGN_ME === $iStorageType && $sKey && 0 === \random_int(0, 25) && \is_dir($sFilePath)) {
\MailSo\Base\Utils::RecTimeDirRemove(\is_dir($sFilePath), 3600 * 24 * 30); // 30 days
}
// Cleanup sessions
if (StorageType::SESSION === $iStorageType && $sKey && 0 === \random_int(0, 25) && \is_dir($sFilePath)) {
\MailSo\Base\Utils::RecTimeDirRemove(\is_dir($sFilePath), 3600 * 3); // 3 hours
}
return $sFilePath;
}

View file

@ -25,7 +25,7 @@ class Utils
* Session cookie
* Used by: EncodeKeyValuesQ, DecodeKeyValuesQ
*/
SHORT_TOKEN = 'smsession';
SESSION_TOKEN = 'smsession';
public static function EncodeKeyValues(array $aValues, string $sCustomKey = '') : string
{
@ -74,10 +74,10 @@ class Utils
public static function GetSessionToken() : string
{
$sToken = static::GetCookie(self::SHORT_TOKEN, null);
$sToken = static::GetCookie(self::SESSION_TOKEN, null);
if (!$sToken) {
$sToken = \MailSo\Base\Utils::Sha1Rand(APP_SALT);
static::SetCookie(self::SHORT_TOKEN, $sToken, 0);
static::SetCookie(self::SESSION_TOKEN, $sToken, 0);
}
return \sha1('Session'.APP_SALT.$sToken.'Token'.APP_SALT);