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

View file

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

View file

@ -8,4 +8,5 @@ class StorageType
const CONFIG = 2; const CONFIG = 2;
const NOBODY = 3; const NOBODY = 3;
const SIGN_ME = 4; 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)) { if ($sFileName && \file_exists($sFileName)) {
$mValue = \file_get_contents($sFileName); $mValue = \file_get_contents($sFileName);
} }
return false === $mValue ? $mDefault : $mValue; 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 public function Clear($mAccount, int $iStorageType, string $sKey) : bool
{ {
$mResult = true;
$sFileName = $this->generateFileName($mAccount, $iStorageType, $sKey); $sFileName = $this->generateFileName($mAccount, $iStorageType, $sKey);
if ($sFileName && \file_exists($sFileName)) { return $sFileName && \file_exists($sFileName) && \unlink($sFileName);
$mResult = \unlink($sFileName);
}
return $mResult;
} }
/** /**
@ -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 protected function generateFileName($mAccount, int $iStorageType, string $sKey, bool $bMkDir = false, bool $bForDeleteAction = false) : string
{ {
$sEmail = $sSubEmail = ''; $sEmail = $sSubFolder = '';
if (null === $mAccount) { if (null === $mAccount) {
$iStorageType = StorageType::NOBODY; $iStorageType = StorageType::NOBODY;
} else if ($mAccount instanceof \RainLoop\Model\Account) { } else if ($mAccount instanceof \RainLoop\Model\MainAccount) {
$sEmail = $mAccount instanceof \RainLoop\Model\AdditionalAccount ? $mAccount->ParentEmail() : $mAccount->Email(); $sEmail = $mAccount->Email();
if ($this->bLocal && $mAccount instanceof \RainLoop\Model\AdditionalAccount && !$bForDeleteAction) if (StorageType::SIGN_ME === $iStorageType) {
{ $sSubFolder = '.sign_me';
$sSubEmail = $mAccount->Email(); } 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)) { } else if (\is_string($mAccount) && empty($sEmail)) {
$sEmail = $mAccount; $sEmail = $mAccount;
@ -110,7 +110,7 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
$sFilePath = $this->sDataPath.'/__nobody__/'.\sha1($sKey ?: \time()); $sFilePath = $this->sDataPath.'/__nobody__/'.\sha1($sKey ?: \time());
break; break;
case StorageType::SIGN_ME: case StorageType::SIGN_ME:
$sSubEmail = '.sign_me'; case StorageType::SESSION:
case StorageType::CONFIG: case StorageType::CONFIG:
if (empty($sEmail)) { if (empty($sEmail)) {
return ''; return '';
@ -123,7 +123,7 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
$sFilePath = $this->sDataPath $sFilePath = $this->sDataPath
.'/'.\RainLoop\Utils::fixName($sDomain ?: 'unknown.tld') .'/'.\RainLoop\Utils::fixName($sDomain ?: 'unknown.tld')
.'/'.\RainLoop\Utils::fixName(\implode('@', $aEmail) ?: '.unknown') .'/'.\RainLoop\Utils::fixName(\implode('@', $aEmail) ?: '.unknown')
.'/'.($sSubEmail ? \RainLoop\Utils::fixName($sSubEmail).'/' : '') .'/'.($sSubFolder ? \RainLoop\Utils::fixName($sSubFolder).'/' : '')
.($sKey ? \RainLoop\Utils::fixName($sKey) : ''); .($sKey ? \RainLoop\Utils::fixName($sKey) : '');
break; break;
default: 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)) { 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 \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; return $sFilePath;
} }

View file

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