Extend handling login credentials by allowing to modify SMTP username

This commit is contained in:
the-djmaze 2024-04-17 01:09:07 +02:00
parent 3a6a5a6925
commit e554bcc27e
12 changed files with 220 additions and 183 deletions

View file

@ -132,8 +132,9 @@ $Plugin->addHook('hook.name', 'functionName');
### login.credentials ### login.credentials
params: params:
string &$sEmail string &$sEmail
string &$sLogin string &$sImapUser
string &$sPassword string &$sPassword
string &$sSmtpUser
### login.success ### login.success
params: params:

View file

@ -56,11 +56,11 @@ class LoginRemotePlugin extends \RainLoop\Plugins\AbstractPlugin
return true; return true;
} }
public function FilterLoginCredentials(&$sEmail, &$sLogin, &$sPassword) public function FilterLoginCredentials(&$sEmail, &$sImapUser, &$sPassword, &$sSmtpUser)
{ {
// cPanel https://github.com/the-djmaze/snappymail/issues/697 // cPanel https://github.com/the-djmaze/snappymail/issues/697
// && !empty($_ENV['CPANEL']) // && !empty($_ENV['CPANEL'])
if (static::$login/* && $sLogin == $_ENV['REMOTE_USER']*/) { if (static::$login/* && $sImapUser == $_ENV['REMOTE_USER']*/) {
if (empty($_ENV['REMOTE_TEMP_USER'])) { if (empty($_ENV['REMOTE_TEMP_USER'])) {
$iPos = \strpos($sPassword, '[::cpses::]'); $iPos = \strpos($sPassword, '[::cpses::]');
if ($iPos) { if ($iPos) {
@ -68,7 +68,8 @@ class LoginRemotePlugin extends \RainLoop\Plugins\AbstractPlugin
} }
} }
if (!empty($_ENV['REMOTE_TEMP_USER'])) { if (!empty($_ENV['REMOTE_TEMP_USER'])) {
$sLogin = $_ENV['REMOTE_USER'] . '/' . $_ENV['REMOTE_TEMP_USER']; $sImapUser = $_ENV['REMOTE_USER'] . '/' . $_ENV['REMOTE_TEMP_USER'];
$sSmtpUser = $_ENV['REMOTE_USER'] . '/' . $_ENV['REMOTE_TEMP_USER'];
} }
} }
} }

View file

@ -11,6 +11,8 @@
namespace MailSo\Mime; namespace MailSo\Mime;
use MailSo\Base\Utils;
/** /**
* @category MailSo * @category MailSo
* @package Mime * @package Mime
@ -38,9 +40,9 @@ class Email implements \JsonSerializable
throw new \ValueError; throw new \ValueError;
} }
$this->sEmail = \SnappyMail\IDN::emailToAscii(\MailSo\Base\Utils::Trim($sEmail)); $this->sEmail = \SnappyMail\IDN::emailToAscii(Utils::Trim($sEmail));
$this->sDisplayName = \MailSo\Base\Utils::Trim($sDisplayName); $this->sDisplayName = Utils::Trim($sDisplayName);
} }
/** /**
@ -48,8 +50,7 @@ class Email implements \JsonSerializable
*/ */
public static function Parse(string $sEmailAddress) : self public static function Parse(string $sEmailAddress) : self
{ {
$sEmailAddress = \MailSo\Base\Utils::DecodeHeaderValue($sEmailAddress); $sEmailAddress = Utils::Trim(Utils::DecodeHeaderValue($sEmailAddress));
$sEmailAddress = \MailSo\Base\Utils::Trim($sEmailAddress);
if (!\strlen(\trim($sEmailAddress))) { if (!\strlen(\trim($sEmailAddress))) {
throw new \ValueError; throw new \ValueError;
} }
@ -63,7 +64,6 @@ class Email implements \JsonSerializable
$bInComment = false; $bInComment = false;
$iStartIndex = 0; $iStartIndex = 0;
$iEndIndex = 0;
$iCurrentIndex = 0; $iCurrentIndex = 0;
while ($iCurrentIndex < \strlen($sEmailAddress)) { while ($iCurrentIndex < \strlen($sEmailAddress)) {
@ -76,10 +76,8 @@ class Email implements \JsonSerializable
$bInName = true; $bInName = true;
$iStartIndex = $iCurrentIndex; $iStartIndex = $iCurrentIndex;
} else if (!$bInAddress && !$bInComment) { } else if (!$bInAddress && !$bInComment) {
$iEndIndex = $iCurrentIndex; $sName = \substr($sEmailAddress, $iStartIndex + 1, $iCurrentIndex - $iStartIndex - 1);
$sName = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1); $sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iCurrentIndex - $iStartIndex + 1);
$sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
$iEndIndex = 0;
$iCurrentIndex = 0; $iCurrentIndex = 0;
$iStartIndex = 0; $iStartIndex = 0;
$bInName = false; $bInName = false;
@ -97,10 +95,8 @@ class Email implements \JsonSerializable
break; break;
case '>': case '>':
if ($bInAddress) { if ($bInAddress) {
$iEndIndex = $iCurrentIndex; $sEmail = \substr($sEmailAddress, $iStartIndex + 1, $iCurrentIndex - $iStartIndex - 1);
$sEmail = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1); $sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iCurrentIndex - $iStartIndex + 1);
$sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
$iEndIndex = 0;
$iCurrentIndex = 0; $iCurrentIndex = 0;
$iStartIndex = 0; $iStartIndex = 0;
$bInAddress = false; $bInAddress = false;
@ -114,10 +110,8 @@ class Email implements \JsonSerializable
break; break;
case ')': case ')':
if ($bInComment) { if ($bInComment) {
$iEndIndex = $iCurrentIndex; $sComment = \substr($sEmailAddress, $iStartIndex + 1, $iCurrentIndex - $iStartIndex - 1);
$sComment = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1); $sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iCurrentIndex - $iStartIndex + 1);
$sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
$iEndIndex = 0;
$iCurrentIndex = 0; $iCurrentIndex = 0;
$iStartIndex = 0; $iStartIndex = 0;
$bInComment = false; $bInComment = false;
@ -159,9 +153,9 @@ class Email implements \JsonSerializable
return new self($sEmail, $sName); return new self($sEmail, $sName);
} }
public function GetEmail(bool $bIdn = false) : string public function GetEmail(bool $bUtf8 = false) : string
{ {
return $bIdn ? \SnappyMail\IDN::emailToUtf8($this->sEmail) : $this->sEmail; return $bUtf8 ? \SnappyMail\IDN::emailToUtf8($this->sEmail) : $this->sEmail;
} }
public function GetDisplayName() : string public function GetDisplayName() : string
@ -171,12 +165,12 @@ class Email implements \JsonSerializable
public function getLocalPart() : string public function getLocalPart() : string
{ {
return \MailSo\Base\Utils::getEmailAddressLocalPart($this->sEmail); return Utils::getEmailAddressLocalPart($this->sEmail);
} }
public function GetDomain(bool $bIdn = false) : string public function GetDomain(bool $bIdn = false) : string
{ {
return \MailSo\Base\Utils::getEmailAddressDomain($this->GetEmail($bIdn)); return Utils::getEmailAddressDomain($this->GetEmail($bIdn));
} }
public function SetDkimStatus(string $sDkimStatus) public function SetDkimStatus(string $sDkimStatus)
@ -184,24 +178,21 @@ class Email implements \JsonSerializable
$this->sDkimStatus = Enumerations\DkimStatus::normalizeValue($sDkimStatus); $this->sDkimStatus = Enumerations\DkimStatus::normalizeValue($sDkimStatus);
} }
public function ToString(bool $bConvertSpecialsName = false, bool $bIdn = false) : string public function ToString(bool $bConvertSpecialsName = false, bool $bUtf8 = false) : string
{ {
$sReturn = ''; $sReturn = '';
$sDisplayName = \str_replace('"', '\"', $this->sDisplayName);
if ($bConvertSpecialsName) {
$sDisplayName = \strlen($sDisplayName) ? \MailSo\Base\Utils::EncodeHeaderValue($sDisplayName) : '';
}
$sDisplayName = \strlen($sDisplayName) ? '"'.$sDisplayName.'"' : '';
if (\strlen($this->sEmail)) { if (\strlen($this->sEmail)) {
$sReturn = $this->GetEmail($bIdn); $sReturn = $this->GetEmail($bUtf8);
$sDisplayName = $this->sDisplayName;
if (\strlen($sDisplayName)) { if (\strlen($sDisplayName)) {
$sReturn = $sDisplayName.' <'.$sReturn.'>'; $sDisplayName = \str_replace('"', '\"', $sDisplayName);
if ($bConvertSpecialsName) {
$sDisplayName = Utils::EncodeHeaderValue($sDisplayName);
}
$sReturn = '"'.$sDisplayName.'" <'.$sReturn.'>';
} }
} }
return $sReturn;
return \trim($sReturn);
} }
public function __toString() : string public function __toString() : string
@ -214,8 +205,8 @@ class Email implements \JsonSerializable
{ {
return array( return array(
'@Object' => 'Object/Email', '@Object' => 'Object/Email',
'name' => \MailSo\Base\Utils::Utf8Clear($this->GetDisplayName()), 'name' => Utils::Utf8Clear($this->sDisplayName),
'email' => \MailSo\Base\Utils::Utf8Clear($this->GetEmail(true)), 'email' => Utils::Utf8Clear($this->GetEmail(true)),
'dkimStatus' => $this->sDkimStatus 'dkimStatus' => $this->sDkimStatus
); );
} }

View file

@ -79,7 +79,7 @@ class ConnectSettings implements \JsonSerializable
$this->passphrase = \is_string($value) ? new SensitiveString($value) : $value; $this->passphrase = \is_string($value) ? new SensitiveString($value) : $value;
} }
if ('username' === $name || 'login' === $name) { if ('username' === $name || 'login' === $name) {
$this->username = $this->fixUsername($value); $this->username = $value;
} }
} }

View file

@ -73,35 +73,35 @@ trait Accounts
*/ */
public function DoAccountSetup(): array public function DoAccountSetup(): array
{ {
$oMainAccount = $this->getMainAccountFromToken();
if (!$this->GetCapa(Capa::ADDITIONAL_ACCOUNTS)) { if (!$this->GetCapa(Capa::ADDITIONAL_ACCOUNTS)) {
return $this->FalseResponse(); return $this->FalseResponse();
} }
$oMainAccount = $this->getMainAccountFromToken();
$aAccounts = $this->GetAccounts($oMainAccount); $aAccounts = $this->GetAccounts($oMainAccount);
$sEmail = \trim($this->GetActionParam('email', '')); $sEmail = \trim($this->GetActionParam('email', ''));
$oPassword = new \SnappyMail\SensitiveString($this->GetActionParam('password', '')); $oPassword = new \SnappyMail\SensitiveString($this->GetActionParam('password', ''));
$sName = \trim($this->GetActionParam('name', ''));
$bNew = !empty($this->GetActionParam('new', 1)); $bNew = !empty($this->GetActionParam('new', 1));
$sEmail = IDN::emailToAscii($sEmail);
if ($bNew && ($oMainAccount->Email() === $sEmail || isset($aAccounts[$sEmail]))) {
throw new ClientException(Notifications::AccountAlreadyExists);
} else if (!$bNew && !isset($aAccounts[$sEmail])) {
throw new ClientException(Notifications::AccountDoesNotExist);
}
if ($bNew || \strlen($oPassword)) { if ($bNew || \strlen($oPassword)) {
$oNewAccount = $this->LoginProcess($sEmail, $oPassword, false); $oNewAccount = $this->LoginProcess($sEmail, $oPassword, false);
$sEmail = $oNewAccount->Email();
$aAccounts[$sEmail] = $oNewAccount->asTokenArray($oMainAccount); $aAccounts[$sEmail] = $oNewAccount->asTokenArray($oMainAccount);
} else { } else {
$aAccounts[$sEmail] = \RainLoop\Model\AdditionalAccount::convertArray($aAccounts[$sEmail]); $aAccounts[$sEmail] = \RainLoop\Model\AdditionalAccount::convertArray($aAccounts[$sEmail]);
} }
if ($bNew) {
if ($oMainAccount->Email() === $sEmail || isset($aAccounts[$sEmail])) {
throw new ClientException(Notifications::AccountAlreadyExists);
}
} else if (!isset($aAccounts[$sEmail])) {
throw new ClientException(Notifications::AccountDoesNotExist);
}
if ($aAccounts[$sEmail]) { if ($aAccounts[$sEmail]) {
$aAccounts[$sEmail]['name'] = $sName; $aAccounts[$sEmail]['name'] = \trim($this->GetActionParam('name', ''));
$this->SetAccounts($oMainAccount, $aAccounts); $this->SetAccounts($oMainAccount, $aAccounts);
} }

View file

@ -63,18 +63,15 @@ trait AdminDomains
public function DoAdminDomainMatch() : array public function DoAdminDomainMatch() : array
{ {
$sEmail = $this->GetActionParam('username'); $sCredentials = $this->resolveLoginCredentials(
$oPassword = new \SnappyMail\SensitiveString('********'); $this->GetActionParam('username'),
$sLogin = ''; new \SnappyMail\SensitiveString('********')
$this->resolveLoginCredentials($sEmail, $oPassword, $sLogin); );
$oDomain = \str_contains($sEmail, '@')
? $this->DomainProvider()->Load(\MailSo\Base\Utils::getEmailAddressDomain($sEmail), true)
: null;
return $this->DefaultResponse(array( return $this->DefaultResponse(array(
'email' => $sEmail, 'email' => $sCredentials['email'],
'login' => $sLogin, 'login' => $sCredentials['imapUser'],
'domain' => $oDomain, 'domain' => $sCredentials['domain'],
'whitelist' => $oDomain ? $oDomain->ValidateWhiteList($sEmail, $sLogin) : null 'whitelist' => $sCredentials['domain'] ? $sCredentials['domain']->ValidateWhiteList($sEmail) : null
)); ));
} }

View file

@ -35,11 +35,11 @@ trait User
*/ */
public function DoLogin() : array public function DoLogin() : array
{ {
$sEmail = \MailSo\Base\Utils::Trim($this->GetActionParam('Email', ''));
$oPassword = new \SnappyMail\SensitiveString($this->GetActionParam('Password', ''));
try { try {
$oAccount = $this->LoginProcess($sEmail, $oPassword); $oAccount = $this->LoginProcess(
\MailSo\Base\Utils::Trim($this->GetActionParam('Email', '')),
new \SnappyMail\SensitiveString($this->GetActionParam('Password', ''))
);
} catch (\Throwable $oException) { } catch (\Throwable $oException) {
$this->loginErrorDelay(); $this->loginErrorDelay();
throw $oException; throw $oException;

View file

@ -11,6 +11,7 @@ use RainLoop\Model\AdditionalAccount;
use RainLoop\Providers\Storage\Enumerations\StorageType; use RainLoop\Providers\Storage\Enumerations\StorageType;
use RainLoop\Exceptions\ClientException; use RainLoop\Exceptions\ClientException;
use SnappyMail\Cookies; use SnappyMail\Cookies;
use SnappyMail\SensitiveString;
trait UserAuth trait UserAuth
{ {
@ -24,7 +25,7 @@ trait UserAuth
{ {
return $this->DefaultResponse( return $this->DefaultResponse(
$this->getMainAccountFromToken()->resealCryptKey( $this->getMainAccountFromToken()->resealCryptKey(
new \SnappyMail\SensitiveString($this->GetActionParam('passphrase', '')) new SensitiveString($this->GetActionParam('passphrase', ''))
) )
); );
} }
@ -32,7 +33,7 @@ trait UserAuth
/** /**
* @throws \RainLoop\Exceptions\ClientException * @throws \RainLoop\Exceptions\ClientException
*/ */
public function resolveLoginCredentials(string &$sEmail, \SnappyMail\SensitiveString $oPassword, string &$sLogin): void protected function resolveLoginCredentials(string $sEmail, SensitiveString $oPassword): array
{ {
$sEmail = \SnappyMail\IDN::emailToAscii(\MailSo\Base\Utils::Trim($sEmail)); $sEmail = \SnappyMail\IDN::emailToAscii(\MailSo\Base\Utils::Trim($sEmail));
@ -40,28 +41,31 @@ trait UserAuth
$oDomain = null; $oDomain = null;
$oDomainProvider = $this->DomainProvider(); $oDomainProvider = $this->DomainProvider();
// When email address is missing the domain, try to add it
if (!\str_contains($sEmail, '@')) { if (!\str_contains($sEmail, '@')) {
$this->logWrite("The email address '{$sEmail}' is incomplete", \LOG_INFO, 'LOGIN'); $this->logWrite("The email address '{$sEmail}' is incomplete", \LOG_INFO, 'LOGIN');
if ($this->Config()->Get('login', 'determine_user_domain', false)) { if ($this->Config()->Get('login', 'determine_user_domain', false)) {
// $sUserHost = \SnappyMail\IDN::toAscii($this->Http()->GetHost(false, true)); $sUserHost = \SnappyMail\IDN::toAscii($this->Http()->GetHost(false, true));
$sUserHost = \strtolower(\idn_to_ascii($this->Http()->GetHost(false, true)));
$this->logWrite("Determined user domain: {$sUserHost}", \LOG_INFO, 'LOGIN'); $this->logWrite("Determined user domain: {$sUserHost}", \LOG_INFO, 'LOGIN');
// Determine without wildcard
$aDomainParts = \explode('.', $sUserHost); $aDomainParts = \explode('.', $sUserHost);
$iLimit = \min(\count($aDomainParts), 14); $iLimit = \min(\count($aDomainParts), 14);
while (0 < $iLimit--) { while (0 < $iLimit--) {
$sLine = \implode('.', $aDomainParts); $sDomain = \implode('.', $aDomainParts);
$oDomain = $oDomainProvider->Load($sLine, false); $oDomain = $oDomainProvider->Load($sDomain, false);
if ($oDomain) { if ($oDomain) {
$sEmail .= '@' . $sLine; $sEmail .= '@' . $sDomain;
$this->logWrite("Check '{$sLine}': OK", \LOG_INFO, 'LOGIN'); $this->logWrite("Check '{$sDomain}': OK", \LOG_INFO, 'LOGIN');
break; break;
} else { } else {
$this->logWrite("Check '{$sLine}': NO", \LOG_INFO, 'LOGIN'); $this->logWrite("Check '{$sDomain}': NO", \LOG_INFO, 'LOGIN');
} }
\array_shift($aDomainParts); \array_shift($aDomainParts);
} }
// Else determine with wildcard
if (!$oDomain) { if (!$oDomain) {
$oDomain = $oDomainProvider->Load($sUserHost, true); $oDomain = $oDomainProvider->Load($sUserHost, true);
if ($oDomain) { if ($oDomain) {
@ -77,6 +81,7 @@ trait UserAuth
} }
} }
// Else try default domain
if (!$oDomain) { if (!$oDomain) {
$sDefDomain = \trim($this->Config()->Get('login', 'default_domain', '')); $sDefDomain = \trim($this->Config()->Get('login', 'default_domain', ''));
if (\strlen($sDefDomain)) { if (\strlen($sDefDomain)) {
@ -97,43 +102,57 @@ trait UserAuth
$this->Plugins()->RunHook('login.credentials.step-2', array(&$sEmail, &$sPassword)); $this->Plugins()->RunHook('login.credentials.step-2', array(&$sEmail, &$sPassword));
$this->logMask($sPassword); $this->logMask($sPassword);
$sLogin = $sEmail; $sImapUser = $sEmail;
$sSmtpUser = $sEmail;
if (\str_contains($sEmail, '@') if (\str_contains($sEmail, '@')
&& ($oDomain || ($oDomain = $oDomainProvider->Load(\MailSo\Base\Utils::getEmailAddressDomain($sEmail)))) && ($oDomain || ($oDomain = $oDomainProvider->Load(\MailSo\Base\Utils::getEmailAddressDomain($sEmail), true)))
) { ) {
$sEmail = $oDomain->ImapSettings()->fixUsername($sEmail, false); $sEmail = $oDomain->ImapSettings()->fixUsername($sEmail, false);
$sLogin = $oDomain->ImapSettings()->fixUsername($sLogin); $sImapUser = $oDomain->ImapSettings()->fixUsername($sImapUser);
$sSmtpUser = $oDomain->SmtpSettings()->fixUsername($sSmtpUser);
} }
$this->Plugins()->RunHook('login.credentials', array(&$sEmail, &$sLogin, &$sPassword)); $this->Plugins()->RunHook('login.credentials', array(&$sEmail, &$sImapUser, &$sPassword, &$sSmtpUser));
$oPassword->setValue($sPassword); $oPassword->setValue($sPassword);
return [
'email' => $sEmail,
'domain' => $oDomain,
'imapUser' => $sImapUser,
'smtpUser' => $sSmtpUser,
'pass' => $oPassword
];
} }
/** /**
* @throws \RainLoop\Exceptions\ClientException * @throws \RainLoop\Exceptions\ClientException
*/ */
public function LoginProcess(string &$sEmail, \SnappyMail\SensitiveString $oPassword, bool $bMainAccount = true): Account public function LoginProcess(string $sEmail, SensitiveString $oPassword, bool $bMainAccount = true): Account
{ {
$sInputEmail = $sEmail; $sCredentials = $this->resolveLoginCredentials($sEmail, $oPassword);
$sLogin = ''; if (!\str_contains($sCredentials['email'], '@') || !\strlen($oPassword)) {
$this->resolveLoginCredentials($sEmail, $oPassword, $sLogin);
if (!\str_contains($sEmail, '@') || !\strlen($oPassword)) {
throw new ClientException(Notifications::InvalidInputArgument); throw new ClientException(Notifications::InvalidInputArgument);
} }
$oAccount = null; $oAccount = null;
try { try {
$oAccount = $bMainAccount $oAccount = $bMainAccount ? new MainAccount : new AdditionalAccount;
? MainAccount::NewInstanceFromCredentials($this, $sEmail, $sLogin, $oPassword, true) $oAccount->setCredentials(
: AdditionalAccount::NewInstanceFromCredentials($this, $sEmail, $sLogin, $oPassword, true); $sCredentials['domain'],
$sCredentials['email'],
$sCredentials['imapUser'],
$oPassword,
$sCredentials['smtpUser']
// ,new SensitiveString($oPassword)
);
$this->Plugins()->RunHook('filter.account', array($oAccount));
if (!$oAccount) { if (!$oAccount) {
throw new ClientException(Notifications::AuthError); throw new ClientException(Notifications::AccountFilterError);
} }
} catch (\Throwable $oException) { } catch (\Throwable $oException) {
$this->LoggerAuthHelper($oAccount, $sInputEmail); $this->LoggerAuthHelper($oAccount, $sEmail);
throw $oException; throw $oException;
} }

View file

@ -15,7 +15,7 @@ abstract class Account implements \JsonSerializable
private string $sImapUser = ''; private string $sImapUser = '';
private ?SensitiveString $oPassword = null; private ?SensitiveString $oImapPass = null;
private string $sSmtpUser = ''; private string $sSmtpUser = '';
@ -33,29 +33,20 @@ abstract class Account implements \JsonSerializable
return $this->sName; return $this->sName;
} }
public function IncLogin() : string
{
return $this->ImapUser();
}
public function ImapUser() : string public function ImapUser() : string
{ {
// return $this->oDomain->ImapSettings()->fixUsername($this->sImapUser);
return $this->sImapUser; return $this->sImapUser;
} }
public function IncPassword() : string public function ImapPass() : string
{ {
return $this->oPassword ? $this->oPassword->getValue() : ''; return $this->oImapPass ? $this->oImapPass->getValue() : '';
} }
public function OutLogin() : string
{
return $this->SmtpUser();
}
public function SmtpUser() : string public function SmtpUser() : string
{ {
// return $this->oDomain->SmtpSettings()->fixUsername($this->sSmtpUser ?: $this->sEmail); return $this->sSmtpUser ?: $this->oDomain->SmtpSettings()->fixUsername($this->sEmail);
return $this->sSmtpUser ?: $this->sEmail; // return $this->sSmtpUser ?: $this->sEmail ?: $this->sImapUser;
} }
public function Domain() : Domain public function Domain() : Domain
@ -69,16 +60,26 @@ abstract class Account implements \JsonSerializable
$this->sEmail, $this->sEmail,
$this->sImapUser, $this->sImapUser,
// \json_encode($this->Domain()), // \json_encode($this->Domain()),
// $this->oPassword // $this->oImapPass
])); ]));
} }
public function SetPassword(SensitiveString $oPassword) : void public function setImapUser(string $sImapUser) : void
{ {
$this->oPassword = $oPassword; $this->sImapUser = $sImapUser;
} }
public function SetSmtpPassword(SensitiveString $oPassword) : void public function setImapPass(SensitiveString $oPassword) : void
{
$this->oImapPass = $oPassword;
}
public function setSmtpUser(string $sSmtpUser) : void
{
$this->sSmtpUser = $sSmtpUser;
}
public function setSmtpPass(SensitiveString $oPassword) : void
{ {
$this->oSmtpPass = $oPassword; $this->oSmtpPass = $oPassword;
} }
@ -89,49 +90,33 @@ abstract class Account implements \JsonSerializable
$result = [ $result = [
'email' => $this->sEmail, 'email' => $this->sEmail,
'login' => $this->sImapUser, 'login' => $this->sImapUser,
'pass' => $this->IncPassword(), 'pass' => $this->ImapPass(),
'name' => $this->sName 'name' => $this->sName,
'smtp' => []
]; ];
if ($this->sSmtpUser && $this->oSmtpPass) { if ($this->sSmtpUser) {
$result['smtp'] = [ $result['smtp']['user'] = $this->sSmtpUser;
'user' => $this->sSmtpUser, }
'pass' => $this->oSmtpPass->getValue() if ($this->oSmtpPass) {
]; $result['smtp']['pass'] = $this->oSmtpPass->getValue();
} }
return $result; return $result;
} }
public static function NewInstanceFromCredentials(\RainLoop\Actions $oActions, public function setCredentials(
string $sEmail, string $sLogin, Domain $oDomain,
SensitiveString $oPassword, string $sEmail,
bool $bThrowException = false): ?self string $sImapUser,
{ SensitiveString $oImapPass,
$oAccount = null; string $sSmtpUser = '',
if ($sEmail && $sLogin && \strlen($oPassword)) { ?SensitiveString $oSmtpPass = null
$oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::getEmailAddressDomain($sEmail), true); ) {
if ($oDomain) { $this->sEmail = $sEmail;
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) { $this->oDomain = $oDomain;
$oAccount = new static; $this->sImapUser = $sImapUser;
$this->oImapPass = $oImapPass;
$oAccount->sEmail = \SnappyMail\IDN::emailToAscii($sEmail); $this->sSmtpUser = $sSmtpUser;
$oAccount->sImapUser = \SnappyMail\IDN::emailToAscii($sLogin); $this->oSmtpPass = $oSmtpPass;
$oAccount->SetPassword($oPassword);
$oAccount->oDomain = $oDomain;
$oActions->Plugins()->RunHook('filter.account', array($oAccount));
if ($bThrowException && !$oAccount) {
throw new ClientException(Notifications::AccountFilterError);
}
} else if ($bThrowException) {
throw new ClientException(Notifications::AccountNotAllowed);
}
} else if ($bThrowException) {
throw new ClientException(Notifications::DomainNotAllowed);
}
}
return $oAccount;
} }
/** /**
@ -159,22 +144,37 @@ abstract class Account implements \JsonSerializable
{ {
$oAccount = null; $oAccount = null;
$aAccountHash = static::convertArray($aAccountHash); $aAccountHash = static::convertArray($aAccountHash);
if (!empty($aAccountHash['email']) && 3 <= \count($aAccountHash)) { if (!empty($aAccountHash['email']) && !empty($aAccountHash['login']) && !empty($aAccountHash['pass'])) {
$oAccount = static::NewInstanceFromCredentials( try {
$oActions, $oDomain = $oActions->DomainProvider()->getByEmailAddress($aAccountHash['email']);
$aAccountHash['email'], if ($oDomain) {
$aAccountHash['login'], // $aAccountHash['email'] = $oDomain->ImapSettings()->fixUsername($aAccountHash['email'], false);
new SensitiveString($aAccountHash['pass']), // $aAccountHash['login'] = $oDomain->ImapSettings()->fixUsername($aAccountHash['login']);
$bThrowExceptionOnFalse $oAccount = new static;
); $oAccount->sEmail = \SnappyMail\IDN::emailToAscii($aAccountHash['email']);
$oAccount->sImapUser = \SnappyMail\IDN::emailToAscii($aAccountHash['login']);
$oAccount->setImapPass(new SensitiveString($aAccountHash['pass']));
$oAccount->oDomain = $oDomain;
$oActions->Plugins()->RunHook('filter.account', array($oAccount));
if ($bThrowExceptionOnFalse && !$oAccount) {
throw new ClientException(Notifications::AccountFilterError);
}
}
} catch (\Throwable $e) {
if ($bThrowExceptionOnFalse) {
throw $e;
}
}
if ($oAccount) { if ($oAccount) {
if (isset($aAccountHash['name'])) { if (isset($aAccountHash['name'])) {
$oAccount->sName = $aAccountHash['name']; $oAccount->sName = $aAccountHash['name'];
} }
// init smtp user/password // init smtp user/password
if (isset($aAccountHash['smtp'])) { if (isset($aAccountHash['smtp']['user'])) {
$oAccount->sSmtpUser = $aAccountHash['smtp']['user']; $oAccount->sSmtpUser = $aAccountHash['smtp']['user'];
$oAccount->SetSmtpPassword(new SensitiveString($aAccountHash['smtp']['pass'])); }
if (isset($aAccountHash['smtp']['pass'])) {
$oAccount->setSmtpPass(new SensitiveString($aAccountHash['smtp']['pass']));
} }
} }
} }
@ -202,7 +202,7 @@ abstract class Account implements \JsonSerializable
$oImapClient->Connect($oSettings); $oImapClient->Connect($oSettings);
$oPlugins->RunHook('imap.after-connect', array($this, $oImapClient, $oSettings)); $oPlugins->RunHook('imap.after-connect', array($this, $oImapClient, $oSettings));
$oSettings->passphrase = $this->oPassword; $oSettings->passphrase = $this->oImapPass;
return $this->netClientLogin($oImapClient, $oPlugins); return $this->netClientLogin($oImapClient, $oPlugins);
} }
@ -226,7 +226,7 @@ abstract class Account implements \JsonSerializable
throw new RequireCredentialsException throw new RequireCredentialsException
} }
*/ */
$oSettings->passphrase = $this->oSmtpPass ?: $this->oPassword; $oSettings->passphrase = $this->oSmtpPass ?: $this->oImapPass;
return $this->netClientLogin($oSmtpClient, $oPlugins); return $this->netClientLogin($oSmtpClient, $oPlugins);
} }
@ -241,7 +241,7 @@ abstract class Account implements \JsonSerializable
$oSieveClient->Connect($oSettings); $oSieveClient->Connect($oSettings);
$oPlugins->RunHook('sieve.after-connect', array($this, $oSieveClient, $oSettings)); $oPlugins->RunHook('sieve.after-connect', array($this, $oSieveClient, $oSettings));
$oSettings->passphrase = $this->oPassword; $oSettings->passphrase = $this->oImapPass;
return $this->netClientLogin($oSieveClient, $oPlugins); return $this->netClientLogin($oSieveClient, $oPlugins);
} }
@ -273,4 +273,24 @@ abstract class Account implements \JsonSerializable
return \RainLoop\Api::Actions()->SettingsProvider(true)->Load($this); return \RainLoop\Api::Actions()->SettingsProvider(true)->Load($this);
} }
*/ */
/**
* @deprecated
*/
public function IncLogin() : string
{
return $this->ImapUser();
}
public function IncPassword() : string
{
return $this->ImapPass();
}
public function OutLogin() : string
{
return $this->SmtpUser();
}
public function SetPassword(SensitiveString $oPassword) : void
{
$this->oImapPass = $oPassword;
}
} }

View file

@ -104,27 +104,25 @@ class Domain implements \JsonSerializable
$this->aliasName = \strtolower(\idn_to_ascii($sAliasName)); $this->aliasName = \strtolower(\idn_to_ascii($sAliasName));
} }
public function ValidateWhiteList(string $sEmail, string $sLogin) : bool public function ValidateWhiteList(string $sEmail) : bool
{ {
$sW = \trim($this->whiteList); $sW = \trim($this->whiteList);
if ($sW) { if (!$sW) {
$sEmail = $this->IMAP->fixUsername($sEmail, false); return true;
$sLogin = $this->IMAP->fixUsername($sLogin);
$sUserPart = \MailSo\Base\Utils::getEmailAddressLocalPart($sLogin ?: $sEmail);
$sUserDomain = \MailSo\Base\Utils::getEmailAddressDomain($sEmail);
$sItem = \strtok($sW, " ;,\n");
while (false !== $sItem) {
$sItem = $this->IMAP->fixUsername(\trim($sItem), false);
if ($sLogin === $sItem || $sEmail === $sItem
|| $sUserPart === $sItem || "@{$sUserDomain}" === $sItem
) {
return true;
}
$sItem = \strtok(" ;,\n");
}
return false;
} }
return true; $sEmail = \SnappyMail\IDN::emailToAscii(\mb_strtolower($sEmail));
$iPos = \strrpos($sEmail, '@');
$sUserPart = \substr($sEmail, 0, $iPos);
$sUserDomain = \substr($sEmail, $iPos);
$sItem = \strtok($sW, " ;,\n");
while (false !== $sItem) {
$sItem = \SnappyMail\IDN::emailToAscii(\mb_strtolower(\trim($sItem)));
if ($sItem === $sEmail || $sItem === $sUserPart || $sItem === $sUserDomain) {
return true;
}
$sItem = \strtok(" ;,\n");
}
return false;
} }
public function ImapSettings() : \MailSo\Imap\Settings public function ImapSettings() : \MailSo\Imap\Settings

View file

@ -79,4 +79,16 @@ class Domain extends AbstractProvider
{ {
return $this->oDriver instanceof Domain\DomainInterface; return $this->oDriver instanceof Domain\DomainInterface;
} }
public function getByEmailAddress(string $sEmail) : \RainLoop\Model\Domain
{
$oDomain = $this->Load(\MailSo\Base\Utils::getEmailAddressDomain($sEmail), true);
if (!$oDomain) {
throw new ClientException(Notifications::DomainNotAllowed);
}
if (!$oDomain->ValidateWhiteList($sEmail)) {
throw new ClientException(Notifications::AccountNotAllowed);
}
return $oDomain;
}
} }

View file

@ -552,16 +552,14 @@ class ServiceActions
if (\is_array($aData) && !empty($aData['Email']) && isset($aData['Password'], $aData['Time']) && if (\is_array($aData) && !empty($aData['Email']) && isset($aData['Password'], $aData['Time']) &&
(0 === $aData['Time'] || \time() - 10 < $aData['Time'])) (0 === $aData['Time'] || \time() - 10 < $aData['Time']))
{ {
$sEmail = \trim($aData['Email']);
$oPassword = new \SnappyMail\SensitiveString($aData['Password']);
$aAdditionalOptions = (isset($aData['AdditionalOptions']) && \is_array($aData['AdditionalOptions'])) $aAdditionalOptions = (isset($aData['AdditionalOptions']) && \is_array($aData['AdditionalOptions']))
? $aData['AdditionalOptions'] : []; ? $aData['AdditionalOptions'] : [];
try try
{ {
$oAccount = $this->oActions->LoginProcess($sEmail, $oPassword); $oAccount = $this->oActions->LoginProcess(
\trim($aData['Email']),
new \SnappyMail\SensitiveString($aData['Password'])
);
if ($aAdditionalOptions) { if ($aAdditionalOptions) {
$bSaveSettings = false; $bSaveSettings = false;