diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php index 6eb3a4d86..3534c7d8a 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php @@ -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; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Api.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Api.php index e40f9c7a1..f066d2fa4 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Api.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Api.php @@ -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; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/Enumerations/StorageType.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/Enumerations/StorageType.php index faae86558..fee728a75 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/Enumerations/StorageType.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/Enumerations/StorageType.php @@ -8,4 +8,5 @@ class StorageType const CONFIG = 2; const NOBODY = 3; const SIGN_ME = 4; + const SESSION = 5; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php index 51a8a809a..5db01b4ff 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php @@ -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; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Utils.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Utils.php index 25075cfbe..906404162 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Utils.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Utils.php @@ -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);