mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-09 14:38:27 +03:00
Improved Auth functions as trait
and use JSON instead of serialize for encoding/decoding data
This commit is contained in:
parent
98b5989b44
commit
f92810069a
7 changed files with 411 additions and 381 deletions
|
|
@ -10,13 +10,14 @@ class Actions
|
|||
{
|
||||
use Actions\Admin;
|
||||
use Actions\User;
|
||||
use Actions\UserAuth;
|
||||
use Actions\Raw;
|
||||
use Actions\Response;
|
||||
use Actions\Localization;
|
||||
use Actions\Themes;
|
||||
|
||||
const AUTH_SIGN_ME_TOKEN_KEY = 'rlsmauth';
|
||||
const AUTH_MAILTO_TOKEN_KEY = 'rlmailtoauth';
|
||||
const AUTH_SIGN_ME_TOKEN_KEY = 'rlsmauth';
|
||||
const AUTH_SPEC_TOKEN_KEY = 'rlspecauth';
|
||||
const AUTH_SPEC_LOGOUT_TOKEN_KEY = 'rlspeclogout';
|
||||
const AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY = 'rlspeclogoutcmk';
|
||||
|
|
@ -106,11 +107,6 @@ class Actions
|
|||
*/
|
||||
private $oConfig;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sSpecAuthToken;
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
|
|
@ -143,13 +139,6 @@ class Actions
|
|||
$this->Logger()->Ping();
|
||||
}
|
||||
|
||||
public function SetSpecAuthToken(string $sSpecAuthToken): self
|
||||
{
|
||||
$this->sSpecAuthToken = $sSpecAuthToken;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function SetIsJson(bool $bIsJson): self
|
||||
{
|
||||
$this->bIsJson = $bIsJson;
|
||||
|
|
@ -157,27 +146,11 @@ class Actions
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function GetSpecAuthToken(): string
|
||||
{
|
||||
return $this->sSpecAuthToken;
|
||||
}
|
||||
|
||||
public function GetIsJson(): bool
|
||||
{
|
||||
return $this->bIsJson;
|
||||
}
|
||||
|
||||
public function GetShortLifeSpecAuthToken(int $iLife = 60): string
|
||||
{
|
||||
$aAccountHash = Utils::DecodeKeyValues($this->getLocalAuthToken());
|
||||
if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0]) {
|
||||
$aAccountHash[10] = \time() + $iLife;
|
||||
return Utils::EncodeKeyValues($aAccountHash);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function Config(): Config\Application
|
||||
{
|
||||
if (null === $this->oConfig) {
|
||||
|
|
@ -321,30 +294,6 @@ class Actions
|
|||
return $sQuery;
|
||||
}
|
||||
|
||||
// rlspecauth / AuthAccountHash
|
||||
public function getAuthAccountHash() : string
|
||||
{
|
||||
if ('' === $this->sSpecAuthToken && !\strlen($this->GetSpecAuthLogoutTokenWithDeletion())) {
|
||||
$sAuthAccountHash = $this->GetSpecAuthTokenCookie() ?: $this->GetSpecAuthToken();
|
||||
if (empty($sAuthAccountHash)) {
|
||||
$oAccount = $this->GetAccountFromSignMeToken();
|
||||
if ($oAccount) try
|
||||
{
|
||||
$this->CheckMailConnection($oAccount);
|
||||
$this->AuthToken($oAccount);
|
||||
$sAuthAccountHash = $this->GetSpecAuthToken();
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$oException = null;
|
||||
$this->ClearSignMeData($oAccount);
|
||||
}
|
||||
}
|
||||
$this->SetSpecAuthToken($sAuthAccountHash);
|
||||
}
|
||||
return $this->GetSpecAuthToken();
|
||||
}
|
||||
|
||||
private function compileLogParams(string $sLine, ?Model\Account $oAccount = null, bool $bUrlEncode = false, array $aAdditionalParams = array()): string
|
||||
{
|
||||
$aClear = array();
|
||||
|
|
@ -472,75 +421,6 @@ class Actions
|
|||
return $sFileName;
|
||||
}
|
||||
|
||||
public function SetAuthLogoutToken(): void
|
||||
{
|
||||
\header('X-RainLoop-Action: Logout');
|
||||
Utils::SetCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY, \md5($_SERVER['REQUEST_TIME_FLOAT']), 0);
|
||||
}
|
||||
|
||||
public function SetAuthToken(Model\Account $oAccount): void
|
||||
{
|
||||
$sSpecAuthToken = '_' . $oAccount->GetAuthTokenQ();
|
||||
|
||||
$this->SetSpecAuthToken($sSpecAuthToken);
|
||||
Utils::SetCookie(self::AUTH_SPEC_TOKEN_KEY, $sSpecAuthToken);
|
||||
|
||||
if ($oAccount->SignMe() && \strlen($oAccount->SignMeToken())) {
|
||||
Utils::SetCookie(self::AUTH_SIGN_ME_TOKEN_KEY,
|
||||
Utils::EncodeKeyValuesQ(array(
|
||||
'e' => $oAccount->Email(),
|
||||
't' => $oAccount->SignMeToken()
|
||||
)),
|
||||
\time() + 60 * 60 * 24 * 30);
|
||||
|
||||
$this->StorageProvider()->Put($oAccount,
|
||||
Providers\Storage\Enumerations\StorageType::CONFIG,
|
||||
'sign_me',
|
||||
Utils::EncodeKeyValuesQ(array(
|
||||
'Time' => \time(),
|
||||
'AuthToken' => $oAccount->GetAuthTokenQ(),
|
||||
'SignMetToken' => $oAccount->SignMeToken()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetSpecAuthTokenCookie(): string
|
||||
{
|
||||
return Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY, '');
|
||||
}
|
||||
|
||||
public function GetSpecAuthLogoutTokenWithDeletion(): string
|
||||
{
|
||||
$sResult = Utils::GetCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY, '');
|
||||
if (0 < strlen($sResult)) {
|
||||
Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY);
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
public function GetSpecLogoutCustomMgsWithDeletion(): string
|
||||
{
|
||||
$sResult = Utils::GetCookie(self::AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY, '');
|
||||
if (0 < strlen($sResult)) {
|
||||
Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY);
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
public function SetSpecLogoutCustomMgsWithDeletion(string $sMessage): string
|
||||
{
|
||||
Utils::SetCookie(self::AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY, $sMessage, 0);
|
||||
}
|
||||
|
||||
private function getLocalAuthToken(): string
|
||||
{
|
||||
$sToken = $this->GetSpecAuthToken();
|
||||
return !empty($sToken) && '_' === \substr($sToken, 0, 1) ? \substr($sToken, 1) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
|
|
@ -858,109 +738,6 @@ class Actions
|
|||
}
|
||||
}
|
||||
|
||||
protected function LoginProvide(string $sEmail, string $sLogin, string $sPassword, string $sSignMeToken = '', string $sClientCert = '', bool $bThrowProvideException = false): ?Model\Account
|
||||
{
|
||||
$oAccount = null;
|
||||
if (\strlen($sEmail) && \strlen($sLogin) && \strlen($sPassword)) {
|
||||
$oDomain = $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
|
||||
if ($oDomain) {
|
||||
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) {
|
||||
$oAccount = new Model\Account($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken, '', '', $sClientCert);
|
||||
$this->Plugins()->RunHook('filter.account', array($oAccount));
|
||||
|
||||
if ($bThrowProvideException && !$oAccount) {
|
||||
throw new Exceptions\ClientException(Notifications::AuthError);
|
||||
}
|
||||
} else if ($bThrowProvideException) {
|
||||
throw new Exceptions\ClientException(Notifications::AccountNotAllowed);
|
||||
}
|
||||
} else if ($bThrowProvideException) {
|
||||
throw new Exceptions\ClientException(Notifications::DomainNotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
return $oAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
public function GetAccountFromCustomToken(string $sToken, bool $bThrowExceptionOnFalse = true, bool $bValidateShortToken = true, bool $bQ = false): ?Model\Account
|
||||
{
|
||||
$oResult = null;
|
||||
if (!empty($sToken)) {
|
||||
$aAccountHash = $bQ ? Utils::DecodeKeyValuesQ($sToken) : Utils::DecodeKeyValues($sToken);
|
||||
if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && // simple token validation
|
||||
8 <= \count($aAccountHash) && // length checking
|
||||
!empty($aAccountHash[7]) && // does short token exist
|
||||
(!$bValidateShortToken || Utils::GetShortToken() === $aAccountHash[7] || // check short token if needed
|
||||
(isset($aAccountHash[10]) && 0 < $aAccountHash[10] && \time() < $aAccountHash[10]))
|
||||
) {
|
||||
$oAccount = $this->LoginProvide($aAccountHash[1], $aAccountHash[2], $aAccountHash[3],
|
||||
empty($aAccountHash[5]) ? '' : $aAccountHash[5], empty($aAccountHash[11]) ? '' : $aAccountHash[11], $bThrowExceptionOnFalse);
|
||||
|
||||
if ($oAccount) {
|
||||
// init proxy user/password
|
||||
if (!empty($aAccountHash[8]) && !empty($aAccountHash[9])) {
|
||||
$oAccount->SetProxyAuthUser($aAccountHash[8]);
|
||||
$oAccount->SetProxyAuthPassword($aAccountHash[9]);
|
||||
}
|
||||
|
||||
$this->Logger()->AddSecret($oAccount->Password());
|
||||
$this->Logger()->AddSecret($oAccount->ProxyAuthPassword());
|
||||
|
||||
$oAccount->SetParentEmail($aAccountHash[6]);
|
||||
$oResult = $oAccount;
|
||||
}
|
||||
} else if ($bThrowExceptionOnFalse) {
|
||||
throw new Exceptions\ClientException(Notifications::AuthError);
|
||||
}
|
||||
}
|
||||
|
||||
if ($bThrowExceptionOnFalse && !$oResult) {
|
||||
throw new Exceptions\ClientException(Notifications::AuthError);
|
||||
}
|
||||
|
||||
return $oResult;
|
||||
}
|
||||
|
||||
public function GetAccountFromSignMeToken(): ?Model\Account
|
||||
{
|
||||
$oAccount = null;
|
||||
|
||||
$sSignMeToken = Utils::GetCookie(self::AUTH_SIGN_ME_TOKEN_KEY, '');
|
||||
if (!empty($sSignMeToken)) {
|
||||
$aTokenData = Utils::DecodeKeyValuesQ($sSignMeToken);
|
||||
if (!empty($aTokenData['e']) && !empty($aTokenData['t'])) {
|
||||
$sTokenSettings = $this->StorageProvider()->Get($aTokenData['e'],
|
||||
Providers\Storage\Enumerations\StorageType::CONFIG,
|
||||
'sign_me'
|
||||
);
|
||||
|
||||
if (!empty($sTokenSettings)) {
|
||||
$aSignMeData = Utils::DecodeKeyValuesQ($sTokenSettings);
|
||||
if (!empty($aSignMeData['AuthToken']) &&
|
||||
!empty($aSignMeData['SignMetToken']) &&
|
||||
$aSignMeData['SignMetToken'] === $aTokenData['t']) {
|
||||
$oAccount = $this->GetAccountFromCustomToken($aSignMeData['AuthToken'], false, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||
}
|
||||
|
||||
return $oAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
public function getAccountFromToken(bool $bThrowExceptionOnFalse = true): ?Model\Account
|
||||
{
|
||||
return $this->GetAccountFromCustomToken($this->getLocalAuthToken(), $bThrowExceptionOnFalse, true, true);
|
||||
}
|
||||
|
||||
public function AppDataSystem(bool $bAdmin = false): array
|
||||
{
|
||||
$oConfig = $this->Config();
|
||||
|
|
@ -1341,124 +1118,6 @@ class Actions
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
public function LoginProcess(string &$sEmail, string &$sPassword, string $sSignMeToken = ''): Model\Account
|
||||
{
|
||||
$sInputEmail = $sEmail;
|
||||
|
||||
$this->Plugins()->RunHook('login.credentials.step-1', array(&$sEmail));
|
||||
|
||||
$sEmail = \MailSo\Base\Utils::Trim($sEmail);
|
||||
if ($this->Config()->Get('login', 'login_lowercase', true)) {
|
||||
$sEmail = \MailSo\Base\Utils::StrToLowerIfAscii($sEmail);
|
||||
}
|
||||
|
||||
if (false === \strpos($sEmail, '@')) {
|
||||
$this->Logger()->Write('The email address "' . $sEmail . '" is not complete', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
|
||||
if (false === \strpos($sEmail, '@') && !!$this->Config()->Get('login', 'determine_user_domain', false)) {
|
||||
$sUserHost = \trim($this->Http()->GetHost(false, true, true));
|
||||
$this->Logger()->Write('Determined user domain: ' . $sUserHost, \MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
|
||||
$bAdded = false;
|
||||
|
||||
$iLimit = 14;
|
||||
$aDomainParts = \explode('.', $sUserHost);
|
||||
|
||||
$oDomainProvider = $this->DomainProvider();
|
||||
while (\count($aDomainParts) && 0 < $iLimit) {
|
||||
$sLine = \trim(\implode('.', $aDomainParts), '. ');
|
||||
|
||||
$oDomain = $oDomainProvider->Load($sLine, false);
|
||||
if ($oDomain) {
|
||||
$bAdded = true;
|
||||
$this->Logger()->Write('Check "' . $sLine . '": OK (' . $sEmail . ' > ' . $sEmail . '@' . $sLine . ')',
|
||||
\MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
|
||||
$sEmail = $sEmail . '@' . $sLine;
|
||||
break;
|
||||
} else {
|
||||
$this->Logger()->Write('Check "' . $sLine . '": NO', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
}
|
||||
|
||||
\array_shift($aDomainParts);
|
||||
$iLimit--;
|
||||
}
|
||||
|
||||
if (!$bAdded) {
|
||||
$sLine = $sUserHost;
|
||||
$oDomain = $oDomainProvider->Load($sLine, true);
|
||||
if ($oDomain && $oDomain) {
|
||||
$bAdded = true;
|
||||
$this->Logger()->Write('Check "' . $sLine . '" with wildcard: OK (' . $sEmail . ' > ' . $sEmail . '@' . $sLine . ')',
|
||||
\MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
|
||||
$sEmail = $sEmail . '@' . $sLine;
|
||||
} else {
|
||||
$this->Logger()->Write('Check "' . $sLine . '" with wildcard: NO', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bAdded) {
|
||||
$this->Logger()->Write('Domain was not found!', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
}
|
||||
}
|
||||
|
||||
$sDefDomain = \trim($this->Config()->Get('login', 'default_domain', ''));
|
||||
if (false === \strpos($sEmail, '@') && \strlen($sDefDomain)) {
|
||||
$this->Logger()->Write('Default domain "' . $sDefDomain . '" was used. (' . $sEmail . ' > ' . $sEmail . '@' . $sDefDomain . ')',
|
||||
\MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
|
||||
$sEmail = $sEmail . '@' . $sDefDomain;
|
||||
}
|
||||
}
|
||||
|
||||
$this->Plugins()->RunHook('login.credentials.step-2', array(&$sEmail, &$sPassword));
|
||||
|
||||
if (false === \strpos($sEmail, '@') || !\strlen($sPassword)) {
|
||||
$this->loginErrorDelay();
|
||||
|
||||
throw new Exceptions\ClientException(Notifications::InvalidInputArgument);
|
||||
}
|
||||
|
||||
$this->Logger()->AddSecret($sPassword);
|
||||
|
||||
$sLogin = $sEmail;
|
||||
if ($this->Config()->Get('login', 'login_lowercase', true)) {
|
||||
$sLogin = \MailSo\Base\Utils::StrToLowerIfAscii($sLogin);
|
||||
}
|
||||
|
||||
$this->Plugins()->RunHook('login.credentials', array(&$sEmail, &$sLogin, &$sPassword));
|
||||
|
||||
$this->Logger()->AddSecret($sPassword);
|
||||
|
||||
$oAccount = null;
|
||||
$sClientCert = \trim($this->Config()->Get('ssl', 'client_cert', ''));
|
||||
try {
|
||||
$oAccount = $this->LoginProvide($sEmail, $sLogin, $sPassword, $sSignMeToken, $sClientCert, true);
|
||||
|
||||
if (!$oAccount) {
|
||||
throw new Exceptions\ClientException(Notifications::AuthError);
|
||||
}
|
||||
} catch (\Throwable $oException) {
|
||||
$this->loginErrorDelay();
|
||||
$this->LoggerAuthHelper($oAccount, $this->getAdditionalLogParamsByUserLogin($sInputEmail));
|
||||
throw $oException;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->CheckMailConnection($oAccount, true);
|
||||
} catch (\Throwable $oException) {
|
||||
$this->loginErrorDelay();
|
||||
|
||||
throw $oException;
|
||||
}
|
||||
|
||||
return $oAccount;
|
||||
}
|
||||
|
||||
public function GetAccounts(Model\Account $oAccount): array
|
||||
{
|
||||
if ($this->GetCapa(false, Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) {
|
||||
|
|
@ -1871,7 +1530,7 @@ class Actions
|
|||
$this->Config()->Get('labs', 'allow_message_append', false) &&
|
||||
isset($_FILES, $_FILES['AppendFile'], $_FILES['AppendFile']['name'],
|
||||
$_FILES['AppendFile']['tmp_name'], $_FILES['AppendFile']['size'])) {
|
||||
if (is_string($_FILES['AppendFile']['tmp_name']) && 0 < strlen($_FILES['AppendFile']['tmp_name'])) {
|
||||
if (is_string($_FILES['AppendFile']['tmp_name']) && \strlen($_FILES['AppendFile']['tmp_name'])) {
|
||||
if (\UPLOAD_ERR_OK === (int)$_FILES['AppendFile']['error'] && !empty($sFolderFullNameRaw)) {
|
||||
$sSavedName = 'append-post-' . md5($sFolderFullNameRaw . $_FILES['AppendFile']['name'] . $_FILES['AppendFile']['tmp_name']);
|
||||
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ trait Response
|
|||
$mResult['References'] = $mResponse->References();
|
||||
|
||||
$fAdditionalExternalFilter = null;
|
||||
if (!!$this->Config()->Get('labs', 'use_local_proxy_for_external_images', false))
|
||||
if ($this->Config()->Get('labs', 'use_local_proxy_for_external_images', false))
|
||||
{
|
||||
$fAdditionalExternalFilter = function ($sUrl) {
|
||||
return './?/ProxyExternal/'.Utils::EncodeKeyValuesQ(array(
|
||||
|
|
|
|||
|
|
@ -515,17 +515,6 @@ trait User
|
|||
$this->SettingsProvider()->Save($oAccount, $oSettings) : false);
|
||||
}
|
||||
|
||||
protected function ClearSignMeData(\RainLoop\Model\Account $oAccount) : void
|
||||
{
|
||||
if ($oAccount) {
|
||||
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||
$this->StorageProvider()->Clear($oAccount,
|
||||
\RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
|
||||
'sign_me'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function generateSignMeToken(string $sEmail) : string
|
||||
{
|
||||
return \MailSo\Base\Utils::Sha1Rand(APP_SALT.$sEmail);
|
||||
|
|
|
|||
371
snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php
Normal file
371
snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
<?php
|
||||
|
||||
namespace RainLoop\Actions;
|
||||
|
||||
use \RainLoop\Utils;
|
||||
use \RainLoop\Model\Account;
|
||||
use \RainLoop\Providers\Storage\Enumerations\StorageType;
|
||||
use \RainLoop\Exceptions\ClientException;
|
||||
|
||||
trait UserAuth
|
||||
{
|
||||
/*
|
||||
const AUTH_SIGN_ME_TOKEN_KEY = 'rlsmauth';
|
||||
const AUTH_SPEC_TOKEN_KEY = 'rlspecauth';
|
||||
const AUTH_SPEC_LOGOUT_TOKEN_KEY = 'rlspeclogout';
|
||||
const AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY = 'rlspeclogoutcmk';
|
||||
*/
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sSpecAuthToken;
|
||||
|
||||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
public function LoginProcess(string &$sEmail, string &$sPassword, string $sSignMeToken = ''): Account
|
||||
{
|
||||
$sInputEmail = $sEmail;
|
||||
|
||||
$this->Plugins()->RunHook('login.credentials.step-1', array(&$sEmail));
|
||||
|
||||
$sEmail = \MailSo\Base\Utils::Trim($sEmail);
|
||||
if ($this->Config()->Get('login', 'login_lowercase', true)) {
|
||||
$sEmail = \MailSo\Base\Utils::StrToLowerIfAscii($sEmail);
|
||||
}
|
||||
|
||||
if (false === \strpos($sEmail, '@')) {
|
||||
$this->Logger()->Write('The email address "' . $sEmail . '" is not complete', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
|
||||
if (false === \strpos($sEmail, '@') && !!$this->Config()->Get('login', 'determine_user_domain', false)) {
|
||||
$sUserHost = \trim($this->Http()->GetHost(false, true, true));
|
||||
$this->Logger()->Write('Determined user domain: ' . $sUserHost, \MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
|
||||
$bAdded = false;
|
||||
|
||||
$iLimit = 14;
|
||||
$aDomainParts = \explode('.', $sUserHost);
|
||||
|
||||
$oDomainProvider = $this->DomainProvider();
|
||||
while (\count($aDomainParts) && 0 < $iLimit) {
|
||||
$sLine = \trim(\implode('.', $aDomainParts), '. ');
|
||||
|
||||
$oDomain = $oDomainProvider->Load($sLine, false);
|
||||
if ($oDomain) {
|
||||
$bAdded = true;
|
||||
$this->Logger()->Write('Check "' . $sLine . '": OK (' . $sEmail . ' > ' . $sEmail . '@' . $sLine . ')',
|
||||
\MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
|
||||
$sEmail = $sEmail . '@' . $sLine;
|
||||
break;
|
||||
} else {
|
||||
$this->Logger()->Write('Check "' . $sLine . '": NO', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
}
|
||||
|
||||
\array_shift($aDomainParts);
|
||||
$iLimit--;
|
||||
}
|
||||
|
||||
if (!$bAdded) {
|
||||
$sLine = $sUserHost;
|
||||
$oDomain = $oDomainProvider->Load($sLine, true);
|
||||
if ($oDomain && $oDomain) {
|
||||
$bAdded = true;
|
||||
$this->Logger()->Write('Check "' . $sLine . '" with wildcard: OK (' . $sEmail . ' > ' . $sEmail . '@' . $sLine . ')',
|
||||
\MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
|
||||
$sEmail = $sEmail . '@' . $sLine;
|
||||
} else {
|
||||
$this->Logger()->Write('Check "' . $sLine . '" with wildcard: NO', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bAdded) {
|
||||
$this->Logger()->Write('Domain was not found!', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
}
|
||||
}
|
||||
|
||||
$sDefDomain = \trim($this->Config()->Get('login', 'default_domain', ''));
|
||||
if (false === \strpos($sEmail, '@') && \strlen($sDefDomain)) {
|
||||
$this->Logger()->Write('Default domain "' . $sDefDomain . '" was used. (' . $sEmail . ' > ' . $sEmail . '@' . $sDefDomain . ')',
|
||||
\MailSo\Log\Enumerations\Type::INFO, 'LOGIN');
|
||||
|
||||
$sEmail = $sEmail . '@' . $sDefDomain;
|
||||
}
|
||||
}
|
||||
|
||||
$this->Plugins()->RunHook('login.credentials.step-2', array(&$sEmail, &$sPassword));
|
||||
|
||||
if (false === \strpos($sEmail, '@') || !\strlen($sPassword)) {
|
||||
$this->loginErrorDelay();
|
||||
|
||||
throw new ClientException(Notifications::InvalidInputArgument);
|
||||
}
|
||||
|
||||
$this->Logger()->AddSecret($sPassword);
|
||||
|
||||
$sLogin = $sEmail;
|
||||
if ($this->Config()->Get('login', 'login_lowercase', true)) {
|
||||
$sLogin = \MailSo\Base\Utils::StrToLowerIfAscii($sLogin);
|
||||
}
|
||||
|
||||
$this->Plugins()->RunHook('login.credentials', array(&$sEmail, &$sLogin, &$sPassword));
|
||||
|
||||
$this->Logger()->AddSecret($sPassword);
|
||||
|
||||
$oAccount = null;
|
||||
$sClientCert = \trim($this->Config()->Get('ssl', 'client_cert', ''));
|
||||
try {
|
||||
$oAccount = $this->LoginProvide($sEmail, $sLogin, $sPassword, $sSignMeToken, $sClientCert, true);
|
||||
|
||||
if (!$oAccount) {
|
||||
throw new ClientException(Notifications::AuthError);
|
||||
}
|
||||
} catch (\Throwable $oException) {
|
||||
$this->loginErrorDelay();
|
||||
$this->LoggerAuthHelper($oAccount, $this->getAdditionalLogParamsByUserLogin($sInputEmail));
|
||||
throw $oException;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->CheckMailConnection($oAccount, true);
|
||||
} catch (\Throwable $oException) {
|
||||
$this->loginErrorDelay();
|
||||
|
||||
throw $oException;
|
||||
}
|
||||
|
||||
return $oAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
public function GetAccountFromCustomToken(string $sToken, bool $bThrowExceptionOnFalse = true, bool $bValidateShortToken = true, bool $bQ = false): ?Account
|
||||
{
|
||||
$oResult = null;
|
||||
if (!empty($sToken)) {
|
||||
$aAccountHash = $bQ ? Utils::DecodeKeyValuesQ($sToken) : Utils::DecodeKeyValues($sToken);
|
||||
if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && // simple token validation
|
||||
8 <= \count($aAccountHash) && // length checking
|
||||
!empty($aAccountHash[7]) && // does short token exist
|
||||
(!$bValidateShortToken || Utils::GetShortToken() === $aAccountHash[7] || // check short token if needed
|
||||
(isset($aAccountHash[10]) && 0 < $aAccountHash[10] && \time() < $aAccountHash[10]))
|
||||
) {
|
||||
$oAccount = $this->LoginProvide($aAccountHash[1], $aAccountHash[2], $aAccountHash[3],
|
||||
empty($aAccountHash[5]) ? '' : $aAccountHash[5], empty($aAccountHash[11]) ? '' : $aAccountHash[11], $bThrowExceptionOnFalse);
|
||||
|
||||
if ($oAccount) {
|
||||
// init proxy user/password
|
||||
if (!empty($aAccountHash[8]) && !empty($aAccountHash[9])) {
|
||||
$oAccount->SetProxyAuthUser($aAccountHash[8]);
|
||||
$oAccount->SetProxyAuthPassword($aAccountHash[9]);
|
||||
}
|
||||
|
||||
$this->Logger()->AddSecret($oAccount->Password());
|
||||
$this->Logger()->AddSecret($oAccount->ProxyAuthPassword());
|
||||
|
||||
$oAccount->SetParentEmail($aAccountHash[6]);
|
||||
$oResult = $oAccount;
|
||||
}
|
||||
} else if ($bThrowExceptionOnFalse) {
|
||||
throw new ClientException(Notifications::AuthError);
|
||||
}
|
||||
}
|
||||
|
||||
if ($bThrowExceptionOnFalse && !$oResult) {
|
||||
throw new ClientException(Notifications::AuthError);
|
||||
}
|
||||
|
||||
return $oResult;
|
||||
}
|
||||
|
||||
protected function LoginProvide(string $sEmail, string $sLogin, string $sPassword, string $sSignMeToken = '', string $sClientCert = '', bool $bThrowProvideException = false): ?Account
|
||||
{
|
||||
$oAccount = null;
|
||||
if (\strlen($sEmail) && \strlen($sLogin) && \strlen($sPassword)) {
|
||||
$oDomain = $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
|
||||
if ($oDomain) {
|
||||
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) {
|
||||
$oAccount = new Account($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken, '', '', $sClientCert);
|
||||
$this->Plugins()->RunHook('filter.account', array($oAccount));
|
||||
|
||||
if ($bThrowProvideException && !$oAccount) {
|
||||
throw new ClientException(Notifications::AuthError);
|
||||
}
|
||||
} else if ($bThrowProvideException) {
|
||||
throw new ClientException(Notifications::AccountNotAllowed);
|
||||
}
|
||||
} else if ($bThrowProvideException) {
|
||||
throw new ClientException(Notifications::DomainNotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
return $oAccount;
|
||||
}
|
||||
|
||||
public function GetSpecAuthTokenCookie(): string
|
||||
{
|
||||
return Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY, '');
|
||||
}
|
||||
|
||||
public function GetAccountFromSignMeToken(): ?Account
|
||||
{
|
||||
$oAccount = null;
|
||||
|
||||
$sSignMeToken = Utils::GetCookie(self::AUTH_SIGN_ME_TOKEN_KEY, '');
|
||||
if (!empty($sSignMeToken)) {
|
||||
$aTokenData = Utils::DecodeKeyValuesQ($sSignMeToken);
|
||||
if (!empty($aTokenData['e']) && !empty($aTokenData['t'])) {
|
||||
$sTokenSettings = $this->StorageProvider()->Get($aTokenData['e'],
|
||||
StorageType::CONFIG,
|
||||
'sign_me'
|
||||
);
|
||||
|
||||
if (!empty($sTokenSettings)) {
|
||||
$aSignMeData = Utils::DecodeKeyValuesQ($sTokenSettings);
|
||||
if (!empty($aSignMeData['AuthToken']) &&
|
||||
!empty($aSignMeData['SignMeToken']) &&
|
||||
$aSignMeData['SignMeToken'] === $aTokenData['t']) {
|
||||
$oAccount = $this->GetAccountFromCustomToken($aSignMeData['AuthToken'], false, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||
}
|
||||
|
||||
return $oAccount;
|
||||
}
|
||||
|
||||
// rlspecauth / AuthAccountHash
|
||||
public function getAuthAccountHash() : string
|
||||
{
|
||||
if ('' === $this->sSpecAuthToken && !\strlen($this->GetSpecAuthLogoutTokenWithDeletion())) {
|
||||
$sAuthAccountHash = $this->GetSpecAuthTokenCookie() ?: $this->GetSpecAuthToken();
|
||||
if (empty($sAuthAccountHash)) {
|
||||
$oAccount = $this->GetAccountFromSignMeToken();
|
||||
if ($oAccount) try
|
||||
{
|
||||
$this->CheckMailConnection($oAccount);
|
||||
$this->AuthToken($oAccount);
|
||||
$sAuthAccountHash = $this->GetSpecAuthToken();
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$oException = null;
|
||||
$this->ClearSignMeData($oAccount);
|
||||
}
|
||||
}
|
||||
$this->SetSpecAuthToken($sAuthAccountHash);
|
||||
}
|
||||
return $this->GetSpecAuthToken();
|
||||
}
|
||||
|
||||
private function getLocalAuthToken(): string
|
||||
{
|
||||
$sToken = $this->GetSpecAuthToken();
|
||||
return !empty($sToken) && '_' === \substr($sToken, 0, 1) ? \substr($sToken, 1) : '';
|
||||
}
|
||||
|
||||
public function GetShortLifeSpecAuthToken(int $iLife = 60): string
|
||||
{
|
||||
$aAccountHash = Utils::DecodeKeyValues($this->getLocalAuthToken());
|
||||
if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0]) {
|
||||
$aAccountHash[10] = \time() + $iLife;
|
||||
return Utils::EncodeKeyValues($aAccountHash);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
public function getAccountFromToken(bool $bThrowExceptionOnFalse = true): ?Model\Account
|
||||
{
|
||||
return $this->GetAccountFromCustomToken($this->getLocalAuthToken(), $bThrowExceptionOnFalse, true, true);
|
||||
}
|
||||
|
||||
public function GetSpecAuthToken(): string
|
||||
{
|
||||
return $this->sSpecAuthToken;
|
||||
}
|
||||
|
||||
public function SetSpecAuthToken(string $sSpecAuthToken): self
|
||||
{
|
||||
$this->sSpecAuthToken = $sSpecAuthToken;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function SetAuthToken(Account $oAccount): void
|
||||
{
|
||||
$sSpecAuthToken = '_' . $oAccount->GetAuthTokenQ();
|
||||
|
||||
$this->SetSpecAuthToken($sSpecAuthToken);
|
||||
Utils::SetCookie(self::AUTH_SPEC_TOKEN_KEY, $sSpecAuthToken);
|
||||
|
||||
if ($oAccount->SignMe() && \strlen($oAccount->SignMeToken())) {
|
||||
Utils::SetCookie(self::AUTH_SIGN_ME_TOKEN_KEY,
|
||||
Utils::EncodeKeyValuesQ(array(
|
||||
'e' => $oAccount->Email(),
|
||||
't' => $oAccount->SignMeToken()
|
||||
)),
|
||||
\time() + 60 * 60 * 24 * 30);
|
||||
|
||||
$this->StorageProvider()->Put($oAccount,
|
||||
StorageType::CONFIG,
|
||||
'sign_me',
|
||||
Utils::EncodeKeyValuesQ(array(
|
||||
'Time' => \time(),
|
||||
'AuthToken' => $oAccount->GetAuthTokenQ(),
|
||||
'SignMeToken' => $oAccount->SignMeToken()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function ClearSignMeData(Account $oAccount) : void
|
||||
{
|
||||
if ($oAccount) {
|
||||
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||
$this->StorageProvider()->Clear($oAccount,
|
||||
StorageType::CONFIG,
|
||||
'sign_me'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetSpecAuthLogoutTokenWithDeletion(): string
|
||||
{
|
||||
$sResult = Utils::GetCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY, '');
|
||||
if (\strlen($sResult)) {
|
||||
Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY);
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
public function SetAuthLogoutToken(): void
|
||||
{
|
||||
\header('X-RainLoop-Action: Logout');
|
||||
Utils::SetCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY, \md5($_SERVER['REQUEST_TIME_FLOAT']));
|
||||
}
|
||||
|
||||
public function GetSpecLogoutCustomMgsWithDeletion(): string
|
||||
{
|
||||
$sResult = Utils::GetCookie(self::AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY, '');
|
||||
if (\strlen($sResult)) {
|
||||
Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY);
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
public function SetSpecLogoutCustomMgsWithDeletion(string $sMessage): string
|
||||
{
|
||||
Utils::SetCookie(self::AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY, $sMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -205,7 +205,7 @@ class Api
|
|||
|
||||
public static function LogoutCurrentLogginedUser() : bool
|
||||
{
|
||||
Utils::ClearCookie('rlsession');
|
||||
Utils::ClearCookie(Utils::SHORT_TOKEN);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ class Account
|
|||
|
||||
public function Hash() : string
|
||||
{
|
||||
return md5(APP_SALT.$this->Email().APP_SALT.$this->DomainIncHost().
|
||||
return \md5(APP_SALT.$this->Email().APP_SALT.$this->DomainIncHost().
|
||||
APP_SALT.$this->DomainIncPort().APP_SALT.$this->Password().APP_SALT.'0'.APP_SALT.$this->ParentEmail().APP_SALT);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,18 @@ class Utils
|
|||
*/
|
||||
static $CookieDefaultSecure = null;
|
||||
|
||||
const
|
||||
/**
|
||||
* Used by: ServiceProxyExternal, compileLogParams, GetCsrfToken
|
||||
* To preven CSRF attacks on all requests.
|
||||
*/
|
||||
CONNECTION_TOKEN = 'rltoken',
|
||||
|
||||
/**
|
||||
* Used by: GetAuthToken, GetAuthTokenQ, GetAccountFromCustomToken, EncryptStringQ, DecryptStringQ
|
||||
*/
|
||||
SHORT_TOKEN = 'rlsession';
|
||||
|
||||
public static function EncryptString(string $sString, string $sKey) : string
|
||||
{
|
||||
return \MailSo\Base\Crypt::Encrypt($sString, $sKey);
|
||||
|
|
@ -37,43 +49,46 @@ class Utils
|
|||
public static function EncodeKeyValues(array $aValues, string $sCustomKey = '') : string
|
||||
{
|
||||
return \MailSo\Base\Utils::UrlSafeBase64Encode(
|
||||
static::EncryptString(\serialize($aValues), \md5(APP_SALT.$sCustomKey)));
|
||||
static::EncryptString(\json_encode($aValues), \md5(APP_SALT.$sCustomKey)));
|
||||
}
|
||||
|
||||
public static function DecodeKeyValues(string $sEncodedValues, string $sCustomKey = '') : array
|
||||
{
|
||||
$aResult = \unserialize(
|
||||
static::DecryptString(
|
||||
\MailSo\Base\Utils::UrlSafeBase64Decode($sEncodedValues), \md5(APP_SALT.$sCustomKey)));
|
||||
|
||||
return \is_array($aResult) ? $aResult : array();
|
||||
return static::unserialize(
|
||||
static::DecryptString(\MailSo\Base\Utils::UrlSafeBase64Decode($sEncodedValues), \md5(APP_SALT.$sCustomKey))
|
||||
);
|
||||
}
|
||||
|
||||
public static function EncodeKeyValuesQ(array $aValues, string $sCustomKey = '') : string
|
||||
{
|
||||
return \MailSo\Base\Utils::UrlSafeBase64Encode(
|
||||
static::EncryptStringQ(
|
||||
\serialize($aValues), \md5(APP_SALT.$sCustomKey)));
|
||||
\json_encode($aValues), \md5(APP_SALT.$sCustomKey)));
|
||||
}
|
||||
|
||||
public static function DecodeKeyValuesQ(string $sEncodedValues, string $sCustomKey = '') : array
|
||||
{
|
||||
$aResult = \unserialize(
|
||||
static::DecryptStringQ(
|
||||
\MailSo\Base\Utils::UrlSafeBase64Decode($sEncodedValues), \md5(APP_SALT.$sCustomKey)));
|
||||
return static::unserialize(
|
||||
static::DecryptStringQ(\MailSo\Base\Utils::UrlSafeBase64Decode($sEncodedValues), \md5(APP_SALT.$sCustomKey))
|
||||
);
|
||||
}
|
||||
|
||||
return \is_array($aResult) ? $aResult : array();
|
||||
public static function unserialize(string $sDecodedValues) : array
|
||||
{
|
||||
try {
|
||||
return \json_decode($sDecodedValues, true, 512, JSON_THROW_ON_ERROR) ?: array();
|
||||
} catch (\Throwable $e) {
|
||||
return \unserialize($aResult) ?: array();
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetConnectionToken() : string
|
||||
{
|
||||
$sKey = 'rltoken';
|
||||
|
||||
$sToken = static::GetCookie($sKey, null);
|
||||
$sToken = static::GetCookie(self::CONNECTION_TOKEN, null);
|
||||
if (null === $sToken)
|
||||
{
|
||||
$sToken = \MailSo\Base\Utils::Sha1Rand(APP_SALT);
|
||||
static::SetCookie($sKey, $sToken, \time() + 60 * 60 * 24 * 30);
|
||||
static::SetCookie(self::CONNECTION_TOKEN, $sToken, \time() + 60 * 60 * 24 * 30);
|
||||
}
|
||||
|
||||
return \md5('Connection'.APP_SALT.$sToken.'Token'.APP_SALT);
|
||||
|
|
@ -86,13 +101,11 @@ class Utils
|
|||
|
||||
public static function GetShortToken() : string
|
||||
{
|
||||
$sKey = 'rlsession';
|
||||
|
||||
$sToken = static::GetCookie($sKey, null);
|
||||
$sToken = static::GetCookie(self::SHORT_TOKEN, null);
|
||||
if (!$sToken)
|
||||
{
|
||||
$sToken = \MailSo\Base\Utils::Sha1Rand(APP_SALT);
|
||||
static::SetCookie($sKey, $sToken, 0);
|
||||
static::SetCookie(self::SHORT_TOKEN, $sToken, 0);
|
||||
}
|
||||
|
||||
return \md5('Session'.APP_SALT.$sToken.'Token'.APP_SALT);
|
||||
|
|
@ -100,12 +113,10 @@ class Utils
|
|||
|
||||
public static function UpdateConnectionToken() : void
|
||||
{
|
||||
$sKey = 'rltoken';
|
||||
|
||||
$sToken = static::GetCookie($sKey, '');
|
||||
$sToken = static::GetCookie(self::CONNECTION_TOKEN, '');
|
||||
if (!empty($sToken))
|
||||
{
|
||||
static::SetCookie($sKey, $sToken, \time() + 60 * 60 * 24 * 30);
|
||||
static::SetCookie(self::CONNECTION_TOKEN, $sToken, \time() + 60 * 60 * 24 * 30);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue