mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Revamp the whole accounts system for better management and control.
This also solves RainLoop/#2134
This commit is contained in:
parent
ef4790d04e
commit
a18d393f54
15 changed files with 441 additions and 401 deletions
|
|
@ -1164,8 +1164,7 @@ END;
|
||||||
|
|
||||||
public static function UrlSafeBase64Decode(string $sValue) : string
|
public static function UrlSafeBase64Decode(string $sValue) : string
|
||||||
{
|
{
|
||||||
$sValue = \rtrim(\strtr($sValue, '-_.', '+/='), '=');
|
return \base64_decode(\strtr($sValue, '-_', '+/'), '=');
|
||||||
return static::Base64Decode(\str_pad($sValue, \strlen($sValue) + (\strlen($sValue) % 4), '=', STR_PAD_RIGHT));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function ParseFetchSequence(string $sSequence) : array
|
public static function ParseFetchSequence(string $sSequence) : array
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,11 @@ class Actions
|
||||||
*/
|
*/
|
||||||
const AUTH_SPEC_TOKEN_KEY = 'smaccount';
|
const AUTH_SPEC_TOKEN_KEY = 'smaccount';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This session cookie optionally contains a \RainLoop\Model\AdditionalAccount
|
||||||
|
*/
|
||||||
|
const AUTH_ADDITIONAL_TOKEN_KEY = 'smadditional';
|
||||||
|
|
||||||
const AUTH_SPEC_LOGOUT_TOKEN_KEY = 'smspeclogout';
|
const AUTH_SPEC_LOGOUT_TOKEN_KEY = 'smspeclogout';
|
||||||
const AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY = 'smspeclogoutcmk';
|
const AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY = 'smspeclogoutcmk';
|
||||||
|
|
||||||
|
|
@ -553,7 +558,7 @@ class Actions
|
||||||
{
|
{
|
||||||
$sKey = '';
|
$sKey = '';
|
||||||
if ($oAccount) {
|
if ($oAccount) {
|
||||||
$sKey = $oAccount->ParentEmailHelper();
|
$sKey = $this->GetMainEmail($oAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sIndexKey = empty($sKey) ? '_default_' : $sKey;
|
$sIndexKey = empty($sKey) ? '_default_' : $sKey;
|
||||||
|
|
@ -918,7 +923,9 @@ class Actions
|
||||||
$aResult['StartupUrl'] = $this->compileLogParams($aResult['StartupUrl'], $oAccount, true);
|
$aResult['StartupUrl'] = $this->compileLogParams($aResult['StartupUrl'], $oAccount, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$aResult['ParentEmail'] = $oAccount->ParentEmail();
|
if ($oAccount instanceof \RainLoop\Model\AdditionalAccount) {
|
||||||
|
$aResult['ParentEmail'] = $oAccount->ParentEmail();
|
||||||
|
}
|
||||||
|
|
||||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||||
|
|
||||||
|
|
@ -1128,32 +1135,6 @@ class Actions
|
||||||
return $bFirstOnEmpty && isset($aIdentities[0]) ? $aIdentities[0] : null;
|
return $bFirstOnEmpty && isset($aIdentities[0]) ? $aIdentities[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \MailSo\Base\Exceptions\Exception
|
|
||||||
*/
|
|
||||||
public function getAccountUnreadCountFromHash(string $sHash): int
|
|
||||||
{
|
|
||||||
$iResult = 0;
|
|
||||||
|
|
||||||
$oAccount = $this->GetAccountFromCustomToken($sHash);
|
|
||||||
if ($oAccount) {
|
|
||||||
try {
|
|
||||||
$oMailClient = new \MailSo\Mail\MailClient();
|
|
||||||
$oMailClient->SetLogger($this->Logger());
|
|
||||||
|
|
||||||
$oAccount->IncConnectAndLoginHelper($this->Plugins(), $oMailClient, $this->Config());
|
|
||||||
|
|
||||||
$iResult = $oMailClient->InboxUnreadCount();
|
|
||||||
|
|
||||||
$oMailClient->Disconnect();
|
|
||||||
} catch (\Throwable $oException) {
|
|
||||||
$this->Logger()->WriteException($oException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $iResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setConfigFromParams(Config\Application $oConfig, string $sParamName, string $sConfigSector, string $sConfigName, string $sType = 'string', ?callable $mStringCallback = null): void
|
public function setConfigFromParams(Config\Application $oConfig, string $sParamName, string $sConfigSector, string $sConfigName, string $sType = 'string', ?callable $mStringCallback = null): void
|
||||||
{
|
{
|
||||||
$sValue = $this->GetActionParam($sParamName, '');
|
$sValue = $this->GetActionParam($sParamName, '');
|
||||||
|
|
@ -1420,28 +1401,10 @@ class Actions
|
||||||
|
|
||||||
if (\count($aData)) {
|
if (\count($aData)) {
|
||||||
$this->Logger()->Write('Import contacts from csv');
|
$this->Logger()->Write('Import contacts from csv');
|
||||||
$iCount = $oAddressBookProvider->ImportCsvArray($oAccount->ParentEmailHelper(), $aData);
|
$iCount = $oAddressBookProvider->ImportCsvArray(
|
||||||
}
|
$this->GetMainEmail($oAccount),
|
||||||
}
|
$aData
|
||||||
}
|
);
|
||||||
|
|
||||||
return $iCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function importContactsFromVcfFile(Model\Account $oAccount, /*resource*/ $rFile): int
|
|
||||||
{
|
|
||||||
$iCount = 0;
|
|
||||||
if ($oAccount && \is_resource($rFile)) {
|
|
||||||
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
|
||||||
if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) {
|
|
||||||
$sFile = \stream_get_contents($rFile);
|
|
||||||
if (\is_resource($rFile)) {
|
|
||||||
\fclose($rFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_string($sFile) && 5 < \strlen($sFile)) {
|
|
||||||
$this->Logger()->Write('Import contacts from vcf');
|
|
||||||
$iCount = $oAddressBookProvider->ImportVcfFile($oAccount->ParentEmailHelper(), $sFile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,67 +8,54 @@ use RainLoop\Model\Account;
|
||||||
use RainLoop\Model\Identity;
|
use RainLoop\Model\Identity;
|
||||||
use RainLoop\Notifications;
|
use RainLoop\Notifications;
|
||||||
use RainLoop\Providers\Storage\Enumerations\StorageType;
|
use RainLoop\Providers\Storage\Enumerations\StorageType;
|
||||||
|
use RainLoop\Utils;
|
||||||
|
|
||||||
trait Accounts
|
trait Accounts
|
||||||
{
|
{
|
||||||
|
|
||||||
|
protected function GetMainEmail(Account $oAccount)
|
||||||
|
{
|
||||||
|
return $oAccount instanceof \RainLoop\Model\AdditionalAccount ? $oAccount->ParentEmail() : $oAccount->Email();
|
||||||
|
}
|
||||||
|
|
||||||
public function GetAccounts(Account $oAccount): array
|
public function GetAccounts(Account $oAccount): array
|
||||||
{
|
{
|
||||||
|
if (\is_subclass_of($oAccount, 'RainLoop\\Model\\Account')) {
|
||||||
|
throw new \LogicException('Only main account can have sub accounts');
|
||||||
|
}
|
||||||
if ($this->GetCapa(false, Capa::ADDITIONAL_ACCOUNTS, $oAccount)) {
|
if ($this->GetCapa(false, Capa::ADDITIONAL_ACCOUNTS, $oAccount)) {
|
||||||
$sAccounts = $this->StorageProvider()->Get($oAccount,
|
$sAccounts = $this->StorageProvider()->Get($oAccount,
|
||||||
StorageType::CONFIG,
|
StorageType::CONFIG,
|
||||||
'accounts'
|
'additionalaccounts'
|
||||||
);
|
);
|
||||||
|
|
||||||
$aAccounts = $sAccounts ? \json_decode($sAccounts, true) : array();
|
$aAccounts = $sAccounts ? \json_decode($sAccounts, true) : array();
|
||||||
|
|
||||||
if (\is_array($aAccounts) && \count($aAccounts)) {
|
if ($aAccounts && \is_array($aAccounts)) {
|
||||||
if (1 === \count($aAccounts)) {
|
|
||||||
$this->SetAccounts($oAccount, array());
|
|
||||||
|
|
||||||
} else if (1 < \count($aAccounts)) {
|
|
||||||
$sOrder = $this->StorageProvider()->Get($oAccount,
|
|
||||||
StorageType::CONFIG,
|
|
||||||
'accounts_identities_order'
|
|
||||||
);
|
|
||||||
|
|
||||||
$aOrder = empty($sOrder) ? array() : \json_decode($sOrder, true);
|
|
||||||
if (isset($aOrder['Accounts']) && \is_array($aOrder['Accounts']) && 1 < \count($aOrder['Accounts'])) {
|
|
||||||
$aAccounts = \array_merge(\array_flip($aOrder['Accounts']), $aAccounts);
|
|
||||||
|
|
||||||
$aAccounts = \array_filter($aAccounts, function ($sHash) {
|
|
||||||
return 5 < \strlen($sHash);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aAccounts;
|
return $aAccounts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$aAccounts = array();
|
return array();
|
||||||
if (!$oAccount->IsAdditionalAccount()) {
|
|
||||||
$aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aAccounts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function SetAccounts(Account $oAccount, array $aAccounts = array()): void
|
protected function SetAccounts(Account $oAccount, array $aAccounts = array()): void
|
||||||
{
|
{
|
||||||
$sParentEmail = $oAccount->ParentEmailHelper();
|
if (\is_subclass_of($oAccount, 'RainLoop\\Model\\Account')) {
|
||||||
if (!$aAccounts ||
|
throw new \LogicException('Only main account can have sub accounts');
|
||||||
(1 === \count($aAccounts) && !empty($aAccounts[$sParentEmail]))) {
|
}
|
||||||
|
$sParentEmail = $oAccount->Email();
|
||||||
|
if (!$aAccounts) {
|
||||||
$this->StorageProvider()->Clear(
|
$this->StorageProvider()->Clear(
|
||||||
$oAccount,
|
$oAccount,
|
||||||
StorageType::CONFIG,
|
StorageType::CONFIG,
|
||||||
'accounts'
|
'additionalaccounts'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->StorageProvider()->Put(
|
$this->StorageProvider()->Put(
|
||||||
$oAccount,
|
$oAccount,
|
||||||
StorageType::CONFIG,
|
StorageType::CONFIG,
|
||||||
'accounts',
|
'additionalaccounts',
|
||||||
\json_encode($aAccounts)
|
\json_encode($aAccounts)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -79,36 +66,30 @@ trait Accounts
|
||||||
*/
|
*/
|
||||||
public function DoAccountSetup(): array
|
public function DoAccountSetup(): array
|
||||||
{
|
{
|
||||||
$oAccount = $this->getAccountFromToken();
|
$oMainAccount = $this->getMainAccountFromToken();
|
||||||
|
|
||||||
if (!$this->GetCapa(false, Capa::ADDITIONAL_ACCOUNTS, $oAccount)) {
|
if (!$this->GetCapa(false, Capa::ADDITIONAL_ACCOUNTS, $oMainAccount)) {
|
||||||
return $this->FalseResponse(__FUNCTION__);
|
return $this->FalseResponse(__FUNCTION__);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sParentEmail = $oAccount->ParentEmailHelper();
|
$aAccounts = $this->GetAccounts($oMainAccount);
|
||||||
|
|
||||||
$aAccounts = $this->GetAccounts($oAccount);
|
|
||||||
|
|
||||||
$sEmail = \trim($this->GetActionParam('Email', ''));
|
$sEmail = \trim($this->GetActionParam('Email', ''));
|
||||||
$sPassword = $this->GetActionParam('Password', '');
|
$sPassword = $this->GetActionParam('Password', '');
|
||||||
$bNew = '1' === (string)$this->GetActionParam('New', '1');
|
$bNew = '1' === (string)$this->GetActionParam('New', '1');
|
||||||
|
|
||||||
$sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
$sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
||||||
if ($bNew && ($oAccount->Email() === $sEmail || $sParentEmail === $sEmail || isset($aAccounts[$sEmail]))) {
|
if ($bNew && ($oMainAccount->Email() === $sEmail || isset($aAccounts[$sEmail]))) {
|
||||||
throw new ClientException(Notifications::AccountAlreadyExists);
|
throw new ClientException(Notifications::AccountAlreadyExists);
|
||||||
} else if (!$bNew && !isset($aAccounts[$sEmail])) {
|
} else if (!$bNew && !isset($aAccounts[$sEmail])) {
|
||||||
throw new ClientException(Notifications::AccountDoesNotExist);
|
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||||
}
|
}
|
||||||
|
|
||||||
$oNewAccount = $this->LoginProcess($sEmail, $sPassword);
|
$oNewAccount = $this->LoginProcess($sEmail, $sPassword, false, $oMainAccount);
|
||||||
$oNewAccount->SetParentEmail($sParentEmail);
|
|
||||||
|
|
||||||
$aAccounts[$oNewAccount->Email()] = $oNewAccount->GetAuthToken();
|
$aAccounts[$oNewAccount->Email()] = $oNewAccount->asTokenArray($oMainAccount);
|
||||||
if (!$oAccount->IsAdditionalAccount()) {
|
$this->SetAccounts($oMainAccount, $aAccounts);
|
||||||
$aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->SetAccounts($oAccount, $aAccounts);
|
|
||||||
return $this->TrueResponse(__FUNCTION__);
|
return $this->TrueResponse(__FUNCTION__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,31 +98,29 @@ trait Accounts
|
||||||
*/
|
*/
|
||||||
public function DoAccountDelete(): array
|
public function DoAccountDelete(): array
|
||||||
{
|
{
|
||||||
$oAccount = $this->getAccountFromToken();
|
$oMainAccount = $this->getMainAccountFromToken();
|
||||||
|
|
||||||
if (!$this->GetCapa(false, Capa::ADDITIONAL_ACCOUNTS, $oAccount)) {
|
if (!$this->GetCapa(false, Capa::ADDITIONAL_ACCOUNTS, $oMainAccount)) {
|
||||||
return $this->FalseResponse(__FUNCTION__);
|
return $this->FalseResponse(__FUNCTION__);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sParentEmail = $oAccount->ParentEmailHelper();
|
|
||||||
$sEmailToDelete = \trim($this->GetActionParam('EmailToDelete', ''));
|
$sEmailToDelete = \trim($this->GetActionParam('EmailToDelete', ''));
|
||||||
$sEmailToDelete = \MailSo\Base\Utils::IdnToAscii($sEmailToDelete, true);
|
$sEmailToDelete = \MailSo\Base\Utils::IdnToAscii($sEmailToDelete, true);
|
||||||
|
|
||||||
$aAccounts = $this->GetAccounts($oAccount);
|
$aAccounts = $this->GetAccounts($oMainAccount);
|
||||||
|
|
||||||
if (0 < \strlen($sEmailToDelete) && $sEmailToDelete !== $sParentEmail && isset($aAccounts[$sEmailToDelete])) {
|
if (\strlen($sEmailToDelete) && isset($aAccounts[$sEmailToDelete])) {
|
||||||
unset($aAccounts[$sEmailToDelete]);
|
$bReload = false;
|
||||||
|
// $oAccount = $this->getAccountFromToken();
|
||||||
$oAccountToChange = null;
|
if ($oAccount->Email() === $sEmailToDelete) {
|
||||||
if ($oAccount->Email() === $sEmailToDelete && !empty($aAccounts[$sParentEmail])) {
|
Utils::ClearCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
|
||||||
$oAccountToChange = $this->GetAccountFromCustomToken($aAccounts[$sParentEmail]);
|
$bReload = true;
|
||||||
if ($oAccountToChange) {
|
|
||||||
$this->SetAuthToken($oAccountToChange);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->SetAccounts($oAccount, $aAccounts);
|
unset($aAccounts[$sEmailToDelete]);
|
||||||
return $this->TrueResponse(__FUNCTION__, array('Reload' => !!$oAccountToChange));
|
$this->SetAccounts($oMainAccount, $aAccounts);
|
||||||
|
|
||||||
|
return $this->TrueResponse(__FUNCTION__, array('Reload' => $bReload));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->FalseResponse(__FUNCTION__);
|
return $this->FalseResponse(__FUNCTION__);
|
||||||
|
|
@ -188,8 +167,6 @@ trait Accounts
|
||||||
*/
|
*/
|
||||||
public function DoAccountsAndIdentitiesSortOrder(): array
|
public function DoAccountsAndIdentitiesSortOrder(): array
|
||||||
{
|
{
|
||||||
$oAccount = $this->getAccountFromToken();
|
|
||||||
|
|
||||||
$aAccounts = $this->GetActionParam('Accounts', null);
|
$aAccounts = $this->GetActionParam('Accounts', null);
|
||||||
$aIdentities = $this->GetActionParam('Identities', null);
|
$aIdentities = $this->GetActionParam('Identities', null);
|
||||||
|
|
||||||
|
|
@ -197,10 +174,20 @@ trait Accounts
|
||||||
return $this->FalseResponse(__FUNCTION__);
|
return $this->FalseResponse(__FUNCTION__);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->DefaultResponse(__FUNCTION__, $this->StorageProvider()->Put($oAccount,
|
$oAccount = $this->getMainAccountFromToken();
|
||||||
StorageType::CONFIG, 'accounts_identities_order',
|
if (1 < \count($aAccounts)) {
|
||||||
|
$aAccounts = \array_filter(\array_merge(
|
||||||
|
\array_fill_keys($aOrder['Accounts'], null),
|
||||||
|
$this->GetAccounts($oAccount)
|
||||||
|
));
|
||||||
|
$this->SetAccounts($oAccount, $aAccounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->DefaultResponse(__FUNCTION__, $this->StorageProvider()->Put(
|
||||||
|
$this->getMainAccountFromToken(),
|
||||||
|
StorageType::CONFIG,
|
||||||
|
'identities_order',
|
||||||
\json_encode(array(
|
\json_encode(array(
|
||||||
'Accounts' => \is_array($aAccounts) ? $aAccounts : array(),
|
|
||||||
'Identities' => \is_array($aIdentities) ? $aIdentities : array()
|
'Identities' => \is_array($aIdentities) ? $aIdentities : array()
|
||||||
))
|
))
|
||||||
));
|
));
|
||||||
|
|
@ -211,39 +198,39 @@ trait Accounts
|
||||||
*/
|
*/
|
||||||
public function DoAccountsAndIdentities(): array
|
public function DoAccountsAndIdentities(): array
|
||||||
{
|
{
|
||||||
$oAccount = $this->getAccountFromToken();
|
$oAccount = $this->getMainAccountFromToken();
|
||||||
|
|
||||||
$mAccounts = false;
|
|
||||||
|
|
||||||
|
$aAccounts = false;
|
||||||
if ($this->GetCapa(false, Capa::ADDITIONAL_ACCOUNTS, $oAccount)) {
|
if ($this->GetCapa(false, Capa::ADDITIONAL_ACCOUNTS, $oAccount)) {
|
||||||
$mAccounts = $this->GetAccounts($oAccount);
|
$aAccounts = \array_map(
|
||||||
$mAccounts = \array_keys($mAccounts);
|
'MailSo\\Base\\Utils::IdnToUtf8',
|
||||||
|
\array_keys($this->GetAccounts($oAccount))
|
||||||
foreach ($mAccounts as $iIndex => $sName) {
|
);
|
||||||
$mAccounts[$iIndex] = \MailSo\Base\Utils::IdnToUtf8($sName);
|
if ($aAccounts) {
|
||||||
|
\array_unshift($aAccounts, \MailSo\Base\Utils::IdnToUtf8($oAccount->Email()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->DefaultResponse(__FUNCTION__, array(
|
return $this->DefaultResponse(__FUNCTION__, array(
|
||||||
'Accounts' => $mAccounts,
|
'Accounts' => $aAccounts,
|
||||||
'Identities' => $this->GetIdentities($oAccount)
|
'Identities' => $this->GetIdentities($oAccount)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Account $account
|
* @param Account $oAccount
|
||||||
* @return Identity[]
|
* @return Identity[]
|
||||||
*/
|
*/
|
||||||
public function GetIdentities(Account $account): array
|
public function GetIdentities(Account $oAccount): array
|
||||||
{
|
{
|
||||||
// A custom name for a single identity is also stored in this system
|
// A custom name for a single identity is also stored in this system
|
||||||
$allowMultipleIdentities = $this->GetCapa(false, Capa::IDENTITIES, $account);
|
$allowMultipleIdentities = $this->GetCapa(false, Capa::IDENTITIES, $oAccount);
|
||||||
|
|
||||||
// Get all identities
|
// Get all identities
|
||||||
$identities = $this->IdentitiesProvider()->GetIdentities($account, $allowMultipleIdentities);
|
$identities = $this->IdentitiesProvider()->GetIdentities($oAccount, $allowMultipleIdentities);
|
||||||
|
|
||||||
// Sort identities
|
// Sort identities
|
||||||
$orderString = $this->StorageProvider()->Get($account, StorageType::CONFIG, 'accounts_identities_order');
|
$orderString = $this->StorageProvider()->Get($oAccount, StorageType::CONFIG, 'identities_order');
|
||||||
$order = \json_decode($orderString, true) ?? [];
|
$order = \json_decode($orderString, true) ?? [];
|
||||||
if (isset($order['Identities']) && \is_array($order['Identities']) && \count($order['Identities']) > 1) {
|
if (isset($order['Identities']) && \is_array($order['Identities']) && \count($order['Identities']) > 1) {
|
||||||
$list = \array_map(function ($item) {
|
$list = \array_map(function ($item) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ namespace RainLoop\Actions;
|
||||||
trait Contacts
|
trait Contacts
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private function GetAccountParentEmail()
|
||||||
|
{
|
||||||
|
// $oAccount instanceof \RainLoop\Model\AdditionalAccount ? $oAccount->ParentEmail() : $oAccount->Email()
|
||||||
|
}
|
||||||
|
|
||||||
public function DoSaveContactsSyncData() : array
|
public function DoSaveContactsSyncData() : array
|
||||||
{
|
{
|
||||||
$oAccount = $this->getAccountFromToken();
|
$oAccount = $this->getAccountFromToken();
|
||||||
|
|
@ -45,7 +50,7 @@ trait Contacts
|
||||||
if (isset($mData['Enable'], $mData['User'], $mData['Password'], $mData['Url']) && $mData['Enable'])
|
if (isset($mData['Enable'], $mData['User'], $mData['Password'], $mData['Url']) && $mData['Enable'])
|
||||||
{
|
{
|
||||||
$bResult = $oAddressBookProvider->Sync(
|
$bResult = $oAddressBookProvider->Sync(
|
||||||
$oAccount->ParentEmailHelper(),
|
$this->GetMainEmail($oAccount),
|
||||||
$mData['Url'], $mData['User'], $mData['Password']);
|
$mData['Url'], $mData['User'], $mData['Password']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +80,7 @@ trait Contacts
|
||||||
if ($oAbp->IsActive())
|
if ($oAbp->IsActive())
|
||||||
{
|
{
|
||||||
$iResultCount = 0;
|
$iResultCount = 0;
|
||||||
$mResult = $oAbp->GetContacts($oAccount->ParentEmailHelper(),
|
$mResult = $oAbp->GetContacts($this->GetMainEmail($oAccount),
|
||||||
$iOffset, $iLimit, $sSearch, $iResultCount);
|
$iOffset, $iLimit, $sSearch, $iResultCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,7 +103,7 @@ trait Contacts
|
||||||
$bResult = false;
|
$bResult = false;
|
||||||
if (0 < \count($aFilteredUids) && $this->AddressBookProvider($oAccount)->IsActive())
|
if (0 < \count($aFilteredUids) && $this->AddressBookProvider($oAccount)->IsActive())
|
||||||
{
|
{
|
||||||
$bResult = $this->AddressBookProvider($oAccount)->DeleteContacts($oAccount->ParentEmailHelper(), $aFilteredUids);
|
$bResult = $this->AddressBookProvider($oAccount)->DeleteContacts($this->GetMainEmail($oAccount), $aFilteredUids);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->DefaultResponse(__FUNCTION__, $bResult);
|
return $this->DefaultResponse(__FUNCTION__, $bResult);
|
||||||
|
|
@ -119,7 +124,7 @@ trait Contacts
|
||||||
$oContact = null;
|
$oContact = null;
|
||||||
if (0 < \strlen($sUid))
|
if (0 < \strlen($sUid))
|
||||||
{
|
{
|
||||||
$oContact = $oAddressBookProvider->GetContactByID($oAccount->ParentEmailHelper(), $sUid);
|
$oContact = $oAddressBookProvider->GetContactByID($this->GetMainEmail($oAccount), $sUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$oContact)
|
if (!$oContact)
|
||||||
|
|
@ -159,7 +164,7 @@ trait Contacts
|
||||||
|
|
||||||
$oContact->PopulateDisplayAndFullNameValue(true);
|
$oContact->PopulateDisplayAndFullNameValue(true);
|
||||||
|
|
||||||
$bResult = $oAddressBookProvider->ContactSave($oAccount->ParentEmailHelper(), $oContact);
|
$bResult = $oAddressBookProvider->ContactSave($this->GetMainEmail($oAccount), $oContact);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->DefaultResponse(__FUNCTION__, array(
|
return $this->DefaultResponse(__FUNCTION__, array(
|
||||||
|
|
@ -300,7 +305,7 @@ trait Contacts
|
||||||
$this->oHttp->ServerNoCache();
|
$this->oHttp->ServerNoCache();
|
||||||
|
|
||||||
return $this->AddressBookProvider($oAccount)->IsActive() ?
|
return $this->AddressBookProvider($oAccount)->IsActive() ?
|
||||||
$this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'vcf') : false;
|
$this->AddressBookProvider($oAccount)->Export($this->GetMainEmail($oAccount), 'vcf') : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function RawContactsCsv() : bool
|
public function RawContactsCsv() : bool
|
||||||
|
|
@ -315,7 +320,31 @@ trait Contacts
|
||||||
$this->oHttp->ServerNoCache();
|
$this->oHttp->ServerNoCache();
|
||||||
|
|
||||||
return $this->AddressBookProvider($oAccount)->IsActive() ?
|
return $this->AddressBookProvider($oAccount)->IsActive() ?
|
||||||
$this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'csv') : false;
|
$this->AddressBookProvider($oAccount)->Export($this->GetMainEmail($oAccount), 'csv') : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function importContactsFromVcfFile(Model\Account $oAccount, /*resource*/ $rFile): int
|
||||||
|
{
|
||||||
|
$iCount = 0;
|
||||||
|
if ($oAccount && \is_resource($rFile)) {
|
||||||
|
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
||||||
|
if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) {
|
||||||
|
$sFile = \stream_get_contents($rFile);
|
||||||
|
if (\is_resource($rFile)) {
|
||||||
|
\fclose($rFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($sFile) && 5 < \strlen($sFile)) {
|
||||||
|
$this->Logger()->Write('Import contacts from vcf');
|
||||||
|
$iCount = $oAddressBookProvider->ImportVcfFile(
|
||||||
|
$this->GetMainEmail($oAccount),
|
||||||
|
$sFile
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $iCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -324,7 +324,7 @@ trait Messages
|
||||||
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
||||||
|
|
||||||
$this->AddressBookProvider($oAccount)->IncFrec(
|
$this->AddressBookProvider($oAccount)->IncFrec(
|
||||||
$oAccount->ParentEmailHelper(), \array_values($aArrayToFrec),
|
$this->GetMainEmail($oAccount), \array_values($aArrayToFrec),
|
||||||
!!$oSettings->GetConf('ContactsAutosave',
|
!!$oSettings->GetConf('ContactsAutosave',
|
||||||
!!$oConfig->Get('defaults', 'contacts_autosave', true)));
|
!!$oConfig->Get('defaults', 'contacts_autosave', true)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ trait User
|
||||||
$oAccount = $this->getAccountFromToken(false);
|
$oAccount = $this->getAccountFromToken(false);
|
||||||
if ($oAccount)
|
if ($oAccount)
|
||||||
{
|
{
|
||||||
if (!$oAccount->IsAdditionalAccount())
|
if (!\is_subclass_of($oAccount, 'RainLoop\\Model\\Account'))
|
||||||
{
|
{
|
||||||
$this->ClearSignMeData();
|
$this->ClearSignMeData();
|
||||||
Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
|
Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
|
||||||
|
|
@ -393,7 +393,7 @@ trait User
|
||||||
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
||||||
if ($oAddressBookProvider && $oAddressBookProvider->IsActive())
|
if ($oAddressBookProvider && $oAddressBookProvider->IsActive())
|
||||||
{
|
{
|
||||||
$aSuggestions = $oAddressBookProvider->GetSuggestions($oAccount->ParentEmailHelper(), $sQuery, $iLimit);
|
$aSuggestions = $oAddressBookProvider->GetSuggestions($this->GetMainEmail($oAccount), $sQuery, $iLimit);
|
||||||
if (!\count($aResult))
|
if (!\count($aResult))
|
||||||
{
|
{
|
||||||
$aResult = $aSuggestions;
|
$aResult = $aSuggestions;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ namespace RainLoop\Actions;
|
||||||
use RainLoop\Notifications;
|
use RainLoop\Notifications;
|
||||||
use RainLoop\Utils;
|
use RainLoop\Utils;
|
||||||
use RainLoop\Model\Account;
|
use RainLoop\Model\Account;
|
||||||
|
use RainLoop\Model\AdditionalAccount;
|
||||||
use RainLoop\Providers\Storage\Enumerations\StorageType;
|
use RainLoop\Providers\Storage\Enumerations\StorageType;
|
||||||
use RainLoop\Exceptions\ClientException;
|
use RainLoop\Exceptions\ClientException;
|
||||||
|
|
||||||
|
|
@ -13,18 +14,13 @@ trait UserAuth
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $sSpecAuthToken = null;
|
private $oAdditionalAuthAccount = null;
|
||||||
private $oSpecAuthAccount = null;
|
private $oMainAuthAccount = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Or use 'aes-256-xts' ?
|
|
||||||
*/
|
|
||||||
private static $sCipher = 'aes-256-cbc-hmac-sha1';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws \RainLoop\Exceptions\ClientException
|
* @throws \RainLoop\Exceptions\ClientException
|
||||||
*/
|
*/
|
||||||
public function LoginProcess(string &$sEmail, string &$sPassword, bool $bSignMe = false): Account
|
public function LoginProcess(string &$sEmail, string &$sPassword, bool $bSignMe = false, Account $oMainAccount = null): Account
|
||||||
{
|
{
|
||||||
$sInputEmail = $sEmail;
|
$sInputEmail = $sEmail;
|
||||||
|
|
||||||
|
|
@ -117,7 +113,14 @@ trait UserAuth
|
||||||
$oAccount = null;
|
$oAccount = null;
|
||||||
$sClientCert = \trim($this->Config()->Get('ssl', 'client_cert', ''));
|
$sClientCert = \trim($this->Config()->Get('ssl', 'client_cert', ''));
|
||||||
try {
|
try {
|
||||||
$oAccount = Account::NewInstanceByLogin($this, $sEmail, $sLogin, $sPassword, $sClientCert, true);
|
if ($oMainAccount) {
|
||||||
|
$oAccount = AdditionalAccount::NewInstanceByLogin($this, $sEmail, $sLogin, $sPassword, $sClientCert, true);
|
||||||
|
if ($oAccount) {
|
||||||
|
$oAccount->SetParentEmail($oMainAccount->Email());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$oAccount = Account::NewInstanceByLogin($this, $sEmail, $sLogin, $sPassword, $sClientCert, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (!$oAccount) {
|
if (!$oAccount) {
|
||||||
throw new ClientException(Notifications::AuthError);
|
throw new ClientException(Notifications::AuthError);
|
||||||
|
|
@ -141,86 +144,103 @@ trait UserAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws \RainLoop\Exceptions\ClientException
|
* Returns RainLoop\Model\AdditionalAccount when it exists,
|
||||||
*/
|
* else returns RainLoop\Model\Account when it exists,
|
||||||
public function GetAccountFromCustomToken(string $sToken): ?Account
|
* else null
|
||||||
{
|
*
|
||||||
return empty($sToken) ? null : Account::NewInstanceFromAuthToken($sToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \RainLoop\Exceptions\ClientException
|
* @throws \RainLoop\Exceptions\ClientException
|
||||||
*/
|
*/
|
||||||
public function getAccountFromToken(bool $bThrowExceptionOnFalse = true): ?Account
|
public function getAccountFromToken(bool $bThrowExceptionOnFalse = true): ?Account
|
||||||
{
|
{
|
||||||
if (!$this->oSpecAuthAccount) {
|
if (\is_null($this->oAdditionalAuthAccount) && isset($_COOKIE[self::AUTH_ADDITIONAL_TOKEN_KEY])) {
|
||||||
if (!\is_string($this->sSpecAuthToken)) {
|
$aData = Utils::GetSecureCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
|
||||||
$this->sSpecAuthToken = '';
|
if ($aData) {
|
||||||
if (isset($_COOKIE[self::AUTH_SPEC_LOGOUT_TOKEN_KEY])) {
|
$this->oAdditionalAuthAccount = AdditionalAccount::NewInstanceFromTokenArray(
|
||||||
Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY);
|
|
||||||
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
|
||||||
// Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
|
|
||||||
} else {
|
|
||||||
$sAuthAccountHash = Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY);
|
|
||||||
if (empty($sAuthAccountHash)) {
|
|
||||||
$oAccount = $this->GetAccountFromSignMeToken();
|
|
||||||
if ($oAccount) {
|
|
||||||
$this->SetAuthToken($oAccount);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->sSpecAuthToken = $sAuthAccountHash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$aData = \SnappyMail\Crypt::DecryptFromJSON(\MailSo\Base\Utils::UrlSafeBase64Decode($this->sSpecAuthToken));
|
|
||||||
if (!empty($aData)) {
|
|
||||||
$this->oSpecAuthAccount = Account::NewInstanceFromTokenArray(
|
|
||||||
$this,
|
$this,
|
||||||
$aData,
|
$aData,
|
||||||
$bThrowExceptionOnFalse
|
$bThrowExceptionOnFalse
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!$this->oAdditionalAuthAccount) {
|
||||||
|
$this->oAdditionalAuthAccount = false;
|
||||||
|
Utils::ClearCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->oAdditionalAuthAccount ?: $this->getMainAccountFromToken($bThrowExceptionOnFalse);
|
||||||
|
}
|
||||||
|
|
||||||
if ($bThrowExceptionOnFalse && !$this->oSpecAuthAccount) {
|
/**
|
||||||
|
* Returns RainLoop\Model\Account when it exists, else null
|
||||||
|
*
|
||||||
|
* @throws \RainLoop\Exceptions\ClientException
|
||||||
|
*/
|
||||||
|
public function getMainAccountFromToken(bool $bThrowExceptionOnFalse = true): ?Account
|
||||||
|
{
|
||||||
|
if (!$this->oMainAuthAccount) {
|
||||||
|
if (isset($_COOKIE[self::AUTH_SPEC_LOGOUT_TOKEN_KEY])) {
|
||||||
|
Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY);
|
||||||
|
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||||
|
// Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
|
||||||
|
// Utils::ClearCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$aData = Utils::GetSecureCookie(self::AUTH_SPEC_TOKEN_KEY);
|
||||||
|
if ($aData) {
|
||||||
|
$this->oMainAuthAccount = Account::NewInstanceFromTokenArray(
|
||||||
|
$this,
|
||||||
|
$aData,
|
||||||
|
$bThrowExceptionOnFalse
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$oAccount = $this->GetAccountFromSignMeToken();
|
||||||
|
if ($oAccount) {
|
||||||
|
$this->SetAuthToken($oAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($bThrowExceptionOnFalse && !$this->oMainAuthAccount) {
|
||||||
throw new ClientException(\RainLoop\Notifications::AuthError);
|
throw new ClientException(\RainLoop\Notifications::AuthError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->oSpecAuthAccount;
|
return $this->oMainAuthAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function SetAuthToken(Account $oAccount): void
|
public function SetAuthToken(Account $oAccount): void
|
||||||
{
|
{
|
||||||
$sSpecAuthToken = \MailSo\Base\Utils::UrlSafeBase64Encode(\SnappyMail\Crypt::EncryptToJSON($oAccount));
|
if (\is_subclass_of($oAccount, 'RainLoop\\Model\\Account')) {
|
||||||
|
throw new \LogicException('Only main Account can be set as AuthToken');
|
||||||
|
}
|
||||||
|
$this->oAdditionalAuthAccount = false;
|
||||||
|
$this->oMainAuthAccount = $oAccount;
|
||||||
|
Utils::SetSecureCookie(self::AUTH_SPEC_TOKEN_KEY, $oAccount);
|
||||||
|
}
|
||||||
|
|
||||||
$this->sSpecAuthToken = $sSpecAuthToken;
|
public function SetAdditionalAuthToken(?AdditionalAccount $oAccount): void
|
||||||
$this->oSpecAuthAccount = null;
|
{
|
||||||
Utils::SetCookie(self::AUTH_SPEC_TOKEN_KEY, $sSpecAuthToken);
|
$this->oAdditionalAuthAccount = $oAccount ?: false;
|
||||||
|
if ($oAccount) {
|
||||||
if (isset($aAccounts[$oAccount->Email()])) {
|
Utils::SetSecureCookie(self::AUTH_ADDITIONAL_TOKEN_KEY, $oAccount);
|
||||||
$aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken();
|
} else {
|
||||||
$this->SetAccounts($oAccount, $aAccounts);
|
Utils::ClearCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SignMe methods used for the "remember me" cookie
|
||||||
|
*/
|
||||||
|
|
||||||
private static function GetSignMeToken(): ?array
|
private static function GetSignMeToken(): ?array
|
||||||
{
|
{
|
||||||
$sSignMeToken = Utils::GetCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
$sSignMeToken = Utils::GetCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||||
if (empty($sSignMeToken)) {
|
if ($sSignMeToken) {
|
||||||
return null;
|
$aResult = \SnappyMail\Crypt::DecryptUrlSafe($sSignMeToken);
|
||||||
|
if (isset($aResult['e'], $aResult['u']) && \SnappyMail\UUID::isValid($aResult['u'])) {
|
||||||
|
return $aResult;
|
||||||
|
}
|
||||||
|
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||||
}
|
}
|
||||||
$aResult = \SnappyMail\Crypt::DecryptUrlSafe($sSignMeToken);
|
return null;
|
||||||
return \is_array($aResult) ? $aResult : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function SetSignMeTokenCookie(array $aData): void
|
|
||||||
{
|
|
||||||
Utils::SetCookie(
|
|
||||||
self::AUTH_SIGN_ME_TOKEN_KEY,
|
|
||||||
\SnappyMail\Crypt::EncryptUrlSafe($aData),
|
|
||||||
\time() + 3600 * 24 * 30 // 30 days
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function SetSignMeToken(Account $oAccount): void
|
private function SetSignMeToken(Account $oAccount): void
|
||||||
|
|
@ -230,25 +250,15 @@ trait UserAuth
|
||||||
$uuid = \SnappyMail\UUID::generate();
|
$uuid = \SnappyMail\UUID::generate();
|
||||||
$data = \SnappyMail\Crypt::Encrypt($oAccount);
|
$data = \SnappyMail\Crypt::Encrypt($oAccount);
|
||||||
|
|
||||||
if ('xxtea' === $data[0]) {
|
Utils::SetCookie(
|
||||||
static::SetSignMeTokenCookie(array(
|
self::AUTH_SIGN_ME_TOKEN_KEY,
|
||||||
|
\SnappyMail\Crypt::EncryptUrlSafe([
|
||||||
'e' => $oAccount->Email(),
|
'e' => $oAccount->Email(),
|
||||||
'u' => $uuid,
|
'u' => $uuid,
|
||||||
'x' => \base64_encode($data[1])
|
$data[0] => \base64_encode($data[1])
|
||||||
));
|
]),
|
||||||
} else if ('sodium' === $data[0]) {
|
\time() + 3600 * 24 * 30 // 30 days
|
||||||
static::SetSignMeTokenCookie(array(
|
);
|
||||||
'e' => $oAccount->Email(),
|
|
||||||
'u' => $uuid,
|
|
||||||
's' => \base64_encode($data[1])
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
static::SetSignMeTokenCookie(array(
|
|
||||||
'e' => $oAccount->Email(),
|
|
||||||
'u' => $uuid,
|
|
||||||
'o' => \base64_encode($data[1])
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->StorageProvider()->Put(
|
$this->StorageProvider()->Put(
|
||||||
$oAccount,
|
$oAccount,
|
||||||
|
|
@ -261,58 +271,55 @@ trait UserAuth
|
||||||
public function GetAccountFromSignMeToken(): ?Account
|
public function GetAccountFromSignMeToken(): ?Account
|
||||||
{
|
{
|
||||||
$aTokenData = static::GetSignMeToken();
|
$aTokenData = static::GetSignMeToken();
|
||||||
if (!empty($aTokenData)) {
|
if ($aTokenData) {
|
||||||
$oAccount = null;
|
try
|
||||||
if (!empty($aTokenData['e']) && !empty($aTokenData['u']) && \SnappyMail\UUID::isValid($aTokenData['u'])) {
|
{
|
||||||
$sAuthToken = $this->StorageProvider()->Get(
|
$sAuthToken = $this->StorageProvider()->Get(
|
||||||
$aTokenData['e'],
|
$aTokenData['e'],
|
||||||
StorageType::SIGN_ME,
|
StorageType::SIGN_ME,
|
||||||
$aTokenData['u']
|
$aTokenData['u']
|
||||||
);
|
);
|
||||||
if (empty($sAuthToken)) {
|
if ($sAuthToken) {
|
||||||
return null;
|
$aAccountHash = \SnappyMail\Crypt::Decrypt([
|
||||||
}
|
\array_key_last($aTokenData),
|
||||||
if (!empty($aTokenData['x'])) {
|
\base64_decode(\end($aTokenData)),
|
||||||
$aAccountHash = \SnappyMail\Crypt::XxteaDecrypt($sAuthToken, \base64_decode($aTokenData['x']));
|
$sAuthToken
|
||||||
} else if (!empty($aTokenData['s'])) {
|
]);
|
||||||
$aAccountHash = \SnappyMail\Crypt::SodiumDecrypt($sAuthToken, \base64_decode($aTokenData['s']));
|
|
||||||
} else if (!empty($aTokenData['o'])) {
|
|
||||||
$aAccountHash = \SnappyMail\Crypt::OpenSSLDecrypt($sAuthToken, \base64_decode($aTokenData['o']));
|
|
||||||
}
|
|
||||||
if (!empty($aAccountHash) && \is_array($aAccountHash)) {
|
|
||||||
$oAccount = Account::NewInstanceFromTokenArray($this, $aAccountHash);
|
|
||||||
if ($oAccount) {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$this->CheckMailConnection($oAccount);
|
|
||||||
// Update lifetime
|
|
||||||
$this->SetSignMeToken($oAccount);
|
|
||||||
|
|
||||||
return $oAccount;
|
$oAccount = \is_array($aAccountHash)
|
||||||
}
|
? Account::NewInstanceFromTokenArray($this, $aAccountHash) : null;
|
||||||
catch (\Throwable $oException)
|
if ($oAccount) {
|
||||||
{
|
$this->CheckMailConnection($oAccount);
|
||||||
}
|
// Update lifetime
|
||||||
|
$this->SetSignMeToken($oAccount);
|
||||||
|
|
||||||
|
return $oAccount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (\Throwable $oException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->ClearSignMeData();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->ClearSignMeData();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function ClearSignMeData() : void
|
protected function ClearSignMeData() : void
|
||||||
{
|
{
|
||||||
if (isset($_COOKIE[self::AUTH_SIGN_ME_TOKEN_KEY])) {
|
$aTokenData = static::GetSignMeToken();
|
||||||
$aTokenData = static::GetSignMeToken();
|
if ($aTokenData) {
|
||||||
if (!empty($aTokenData['e']) && !empty($aTokenData['u']) && \SnappyMail\UUID::isValid($aTokenData['u'])) {
|
$this->StorageProvider()->Clear($aTokenData['e'], StorageType::SIGN_ME, $aTokenData['u']);
|
||||||
$this->StorageProvider()->Clear($aTokenData['e'], StorageType::SIGN_ME, $aTokenData['u']);
|
|
||||||
}
|
|
||||||
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout methods
|
||||||
|
*/
|
||||||
|
|
||||||
public function SetAuthLogoutToken(): void
|
public function SetAuthLogoutToken(): void
|
||||||
{
|
{
|
||||||
\header('X-RainLoop-Action: Logout');
|
\header('X-RainLoop-Action: Logout');
|
||||||
|
|
|
||||||
|
|
@ -42,21 +42,11 @@ class Account implements \JsonSerializable
|
||||||
*/
|
*/
|
||||||
private $oDomain;
|
private $oDomain;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $sParentEmail = '';
|
|
||||||
|
|
||||||
public function Email() : string
|
public function Email() : string
|
||||||
{
|
{
|
||||||
return $this->sEmail;
|
return $this->sEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ParentEmail() : string
|
|
||||||
{
|
|
||||||
return $this->sParentEmail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ProxyAuthUser() : string
|
public function ProxyAuthUser() : string
|
||||||
{
|
{
|
||||||
return $this->sProxyAuthUser;
|
return $this->sProxyAuthUser;
|
||||||
|
|
@ -67,16 +57,6 @@ class Account implements \JsonSerializable
|
||||||
return $this->sProxyAuthPassword;
|
return $this->sProxyAuthPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ParentEmailHelper() : string
|
|
||||||
{
|
|
||||||
return $this->sParentEmail ?: $this->sEmail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function IsAdditionalAccount() : bool
|
|
||||||
{
|
|
||||||
return !empty($this->sParentEmail);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function IncLogin() : string
|
public function IncLogin() : string
|
||||||
{
|
{
|
||||||
$sLogin = $this->sLogin;
|
$sLogin = $this->sLogin;
|
||||||
|
|
@ -135,8 +115,7 @@ class Account implements \JsonSerializable
|
||||||
$this->sEmail,
|
$this->sEmail,
|
||||||
$this->Domain()->IncHost(),
|
$this->Domain()->IncHost(),
|
||||||
$this->Domain()->IncPort(),
|
$this->Domain()->IncPort(),
|
||||||
$this->sPassword,
|
$this->sPassword
|
||||||
$this->sParentEmail
|
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,11 +124,6 @@ class Account implements \JsonSerializable
|
||||||
$this->sPassword = $sPassword;
|
$this->sPassword = $sPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function SetParentEmail(string $sParentEmail) : void
|
|
||||||
{
|
|
||||||
$this->sParentEmail = \trim(\MailSo\Base\Utils::IdnToAscii($sParentEmail, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function SetProxyAuthUser(string $sProxyAuthUser) : void
|
public function SetProxyAuthUser(string $sProxyAuthUser) : void
|
||||||
{
|
{
|
||||||
$this->sProxyAuthUser = $sProxyAuthUser;
|
$this->sProxyAuthUser = $sProxyAuthUser;
|
||||||
|
|
@ -168,29 +142,15 @@ class Account implements \JsonSerializable
|
||||||
$this->sLogin, // 2
|
$this->sLogin, // 2
|
||||||
$this->sPassword, // 3
|
$this->sPassword, // 3
|
||||||
$this->sClientCert, // 4
|
$this->sClientCert, // 4
|
||||||
$this->sParentEmail, // 5
|
$this->sProxyAuthUser, // 5
|
||||||
$this->sProxyAuthUser, // 6
|
$this->sProxyAuthPassword // 6
|
||||||
$this->sProxyAuthPassword // 7
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetAuthToken() : string
|
|
||||||
{
|
|
||||||
return Utils::EncodeKeyValues($this->jsonSerialize());
|
|
||||||
/*
|
|
||||||
return \MailSo\Base\Utils::UrlSafeBase64Encode(
|
|
||||||
\MailSo\Base\Crypt::Encrypt(
|
|
||||||
\json_encode($this),
|
|
||||||
\md5(APP_SALT.$sCustomKey)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws \RainLoop\Exceptions\ClientException
|
* @throws \RainLoop\Exceptions\ClientException
|
||||||
*/
|
*/
|
||||||
public static function NewInstanceFromAuthToken(\RainLoop\Actions $oActions, string $sToken): ?Account
|
public static function NewInstanceFromAuthToken(\RainLoop\Actions $oActions, string $sToken): ?self
|
||||||
{
|
{
|
||||||
return empty($sToken) ? null
|
return empty($sToken) ? null
|
||||||
: static::NewInstanceFromTokenArray(
|
: static::NewInstanceFromTokenArray(
|
||||||
|
|
@ -200,14 +160,16 @@ class Account implements \JsonSerializable
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function NewInstanceByLogin(\RainLoop\Actions $oActions, string $sEmail, string $sLogin, string $sPassword, string $sClientCert = '', bool $bThrowException = false): ?self
|
public static function NewInstanceByLogin(\RainLoop\Actions $oActions,
|
||||||
|
string $sEmail, string $sLogin, string $sPassword, string $sClientCert = '',
|
||||||
|
bool $bThrowException = false): ?self
|
||||||
{
|
{
|
||||||
$oAccount = null;
|
$oAccount = null;
|
||||||
if (\strlen($sEmail) && \strlen($sLogin) && \strlen($sPassword)) {
|
if (\strlen($sEmail) && \strlen($sLogin) && \strlen($sPassword)) {
|
||||||
$oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
|
$oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
|
||||||
if ($oDomain) {
|
if ($oDomain) {
|
||||||
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) {
|
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) {
|
||||||
$oAccount = new self;
|
$oAccount = new static;
|
||||||
|
|
||||||
$oAccount->sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
$oAccount->sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
||||||
$oAccount->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin);
|
$oAccount->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin);
|
||||||
|
|
@ -236,7 +198,7 @@ class Account implements \JsonSerializable
|
||||||
array $aAccountHash,
|
array $aAccountHash,
|
||||||
bool $bThrowExceptionOnFalse = false): ?self
|
bool $bThrowExceptionOnFalse = false): ?self
|
||||||
{
|
{
|
||||||
if (!empty($aAccountHash[0]) && 'account' === $aAccountHash[0] && 8 === \count($aAccountHash)) {
|
if (!empty($aAccountHash[0]) && 'account' === $aAccountHash[0] && 7 === \count($aAccountHash)) {
|
||||||
$oAccount = static::NewInstanceByLogin(
|
$oAccount = static::NewInstanceByLogin(
|
||||||
$oActions,
|
$oActions,
|
||||||
$aAccountHash[1],
|
$aAccountHash[1],
|
||||||
|
|
@ -248,15 +210,14 @@ class Account implements \JsonSerializable
|
||||||
|
|
||||||
if ($oAccount) {
|
if ($oAccount) {
|
||||||
// init proxy user/password
|
// init proxy user/password
|
||||||
if (!empty($aAccountHash[6]) && !empty($aAccountHash[7])) {
|
if (!empty($aAccountHash[5]) && !empty($aAccountHash[6])) {
|
||||||
$oAccount->SetProxyAuthUser($aAccountHash[6]);
|
$oAccount->SetProxyAuthUser($aAccountHash[5]);
|
||||||
$oAccount->SetProxyAuthPassword($aAccountHash[7]);
|
$oAccount->SetProxyAuthPassword($aAccountHash[6]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$oActions->Logger()->AddSecret($oAccount->Password());
|
$oActions->Logger()->AddSecret($oAccount->Password());
|
||||||
$oActions->Logger()->AddSecret($oAccount->ProxyAuthPassword());
|
$oActions->Logger()->AddSecret($oAccount->ProxyAuthPassword());
|
||||||
|
|
||||||
$oAccount->SetParentEmail($aAccountHash[5]);
|
|
||||||
return $oAccount;
|
return $oAccount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RainLoop\Model;
|
||||||
|
|
||||||
|
use RainLoop\Utils;
|
||||||
|
use RainLoop\Exceptions\ClientException;
|
||||||
|
|
||||||
|
class AdditionalAccount extends Account
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sParentEmail = '';
|
||||||
|
|
||||||
|
public function ParentEmail() : string
|
||||||
|
{
|
||||||
|
return $this->sParentEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SetParentEmail(string $sParentEmail) : void
|
||||||
|
{
|
||||||
|
$this->sParentEmail = \trim(\MailSo\Base\Utils::IdnToAscii($sParentEmail, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function PasswordHash() : string
|
||||||
|
{
|
||||||
|
throw new \LogicException('Not allowed on AdditionalAccount');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Hash() : string
|
||||||
|
{
|
||||||
|
return \md5(parent::Hash() . $this->sParentEmail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
$aData = parent::jsonSerialize();
|
||||||
|
$aData[] = $this->sParentEmail;
|
||||||
|
return $aData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asTokenArray(Account $oMainAccount) : array
|
||||||
|
{
|
||||||
|
$sHash = $oMainAccount->PasswordHash();
|
||||||
|
$aData = $this->jsonSerialize();
|
||||||
|
$aData[3] = \SnappyMail\Crypt::EncryptUrlSafe($aData[3], $sHash);
|
||||||
|
$aData[] = \hash_hmac('sha1', $aData[3], $sHash);
|
||||||
|
return $aData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function NewInstanceFromTokenArray(
|
||||||
|
\RainLoop\Actions $oActions,
|
||||||
|
array $aAccountHash,
|
||||||
|
bool $bThrowExceptionOnFalse = false) : ?Account /* PHP7.4: ?self*/
|
||||||
|
{
|
||||||
|
$iCount = \count($aAccountHash);
|
||||||
|
if (!empty($aAccountHash[0]) && 'account' === $aAccountHash[0] && 8 <= $iCount && 9 >= $iCount) {
|
||||||
|
$sHash = $oActions->getMainAccountFromToken()->PasswordHash();
|
||||||
|
$sPasswordHMAC = (8 < $iCount) ? \array_pop($aAccountHash) : null;
|
||||||
|
$sParentEmail = \array_pop($aAccountHash);
|
||||||
|
if ($sPasswordHMAC && $sPasswordHMAC === \hash_hmac('sha1', $aAccountHash[3], $sHash)) {
|
||||||
|
$aAccountHash[3] = \SnappyMail\Crypt::DecryptUrlSafe($aAccountHash[3], $sHash);
|
||||||
|
}
|
||||||
|
$oAccount = parent::NewInstanceFromTokenArray($oActions, $aAccountHash, $bThrowExceptionOnFalse);
|
||||||
|
if ($oAccount) {
|
||||||
|
$oAccount->SetParentEmail($sParentEmail);
|
||||||
|
return $oAccount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -142,8 +142,13 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
|
||||||
|
|
||||||
private function generateFullFileName(\RainLoop\Model\Account $oAccount, string $sKey, bool $bMkDir = false) : string
|
private function generateFullFileName(\RainLoop\Model\Account $oAccount, string $sKey, bool $bMkDir = false) : string
|
||||||
{
|
{
|
||||||
$sEmail = $oAccount->ParentEmailHelper() ?: 'nobody@unknown.tld';
|
if ($oAccount instanceof \RainLoop\Model\AdditionalAccount) {
|
||||||
$sSubEmail = $oAccount->IsAdditionalAccount() ? $oAccount->Email() : '';
|
$sEmail = $oAccount->ParentEmail();
|
||||||
|
$sSubEmail = $oAccount->Email();
|
||||||
|
} else {
|
||||||
|
$sEmail = $oAccount->Email() ?: 'nobody@unknown.tld';
|
||||||
|
$sSubEmail = '';
|
||||||
|
}
|
||||||
|
|
||||||
$aEmail = \explode('@', $sEmail ?: 'nobody@unknown.tld');
|
$aEmail = \explode('@', $sEmail ?: 'nobody@unknown.tld');
|
||||||
$sDomain = \trim(1 < \count($aEmail) ? \array_pop($aEmail) : '');
|
$sDomain = \trim(1 < \count($aEmail) ? \array_pop($aEmail) : '');
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,8 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
|
||||||
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\Account) {
|
||||||
$sEmail = $mAccount->ParentEmailHelper();
|
$sEmail = $mAccount instanceof \RainLoop\Model\AdditionalAccount ? $mAccount->ParentEmail() : $mAccount->Email();
|
||||||
if ($this->bLocal && $mAccount->IsAdditionalAccount() && !$bForDeleteAction)
|
if ($this->bLocal && $mAccount instanceof \RainLoop\Model\AdditionalAccount && !$bForDeleteAction)
|
||||||
{
|
{
|
||||||
$sSubEmail = $mAccount->Email();
|
$sSubEmail = $mAccount->Email();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -811,14 +811,16 @@ class ServiceActions
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to AdditionalAccount
|
||||||
|
*/
|
||||||
public function ServiceChange() : string
|
public function ServiceChange() : string
|
||||||
{
|
{
|
||||||
$this->oHttp->ServerNoCache();
|
$this->oHttp->ServerNoCache();
|
||||||
|
|
||||||
// $oAccount = $this->oActions->getAccountFromToken(false);
|
$oMainAccount = $this->oActions->getMainAccountFromToken(false);
|
||||||
$oAccount = $this->oActions->GetAccount();
|
|
||||||
|
|
||||||
if ($oAccount && $this->oActions->GetCapa(false, Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount))
|
if ($oMainAccount && $this->oActions->GetCapa(false, Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oMainAccount))
|
||||||
{
|
{
|
||||||
$oAccountToLogin = null;
|
$oAccountToLogin = null;
|
||||||
$sEmail = empty($this->aPaths[2]) ? '' : \urldecode(\trim($this->aPaths[2]));
|
$sEmail = empty($this->aPaths[2]) ? '' : \urldecode(\trim($this->aPaths[2]));
|
||||||
|
|
@ -826,17 +828,16 @@ class ServiceActions
|
||||||
{
|
{
|
||||||
$sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail);
|
$sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail);
|
||||||
|
|
||||||
$aAccounts = $this->oActions->GetAccounts($oAccount);
|
$aAccounts = $this->oActions->GetAccounts($oMainAccount);
|
||||||
if (isset($aAccounts[$sEmail]))
|
if (isset($aAccounts[$sEmail]))
|
||||||
{
|
{
|
||||||
$oAccountToLogin = $this->oActions->GetAccountFromCustomToken($aAccounts[$sEmail]);
|
$oAccountToLogin = \RainLoop\Model\AdditionalAccount::NewInstanceFromTokenArray(
|
||||||
|
$this->oActions,
|
||||||
|
$aAccounts[$sEmail]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$this->oActions->SetAdditionalAuthToken($oAccountToLogin);
|
||||||
if ($oAccountToLogin)
|
|
||||||
{
|
|
||||||
$this->oActions->SetAuthToken($oAccountToLogin);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->oActions->Location('./');
|
$this->oActions->Location('./');
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,13 @@ class Utils
|
||||||
return isset($_COOKIE[$sName]) ? $_COOKIE[$sName] : $mDefault;
|
return isset($_COOKIE[$sName]) ? $_COOKIE[$sName] : $mDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function GetSecureCookie(string $sName)
|
||||||
|
{
|
||||||
|
return isset($_COOKIE[$sName])
|
||||||
|
? \SnappyMail\Crypt::DecryptFromJSON(\MailSo\Base\Utils::UrlSafeBase64Decode($_COOKIE[$sName]))
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
public static function SetCookie(string $sName, string $sValue = '', int $iExpire = 0, bool $bHttpOnly = true)
|
public static function SetCookie(string $sName, string $sValue = '', int $iExpire = 0, bool $bHttpOnly = true)
|
||||||
{
|
{
|
||||||
$sPath = static::$CookieDefaultPath;
|
$sPath = static::$CookieDefaultPath;
|
||||||
|
|
@ -161,6 +168,16 @@ class Utils
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function SetSecureCookie(string $sName, $mValue, int $iExpire = 0, bool $bHttpOnly = true)
|
||||||
|
{
|
||||||
|
static::SetCookie(
|
||||||
|
$sName,
|
||||||
|
\MailSo\Base\Utils::UrlSafeBase64Encode(\SnappyMail\Crypt::EncryptToJSON($mValue)),
|
||||||
|
$iExpire,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static function ClearCookie(string $sName)
|
public static function ClearCookie(string $sName)
|
||||||
{
|
{
|
||||||
if (isset($_COOKIE[$sName])) {
|
if (isset($_COOKIE[$sName])) {
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ abstract class Crypt
|
||||||
public static function setCipher(string $cipher) : bool
|
public static function setCipher(string $cipher) : bool
|
||||||
{
|
{
|
||||||
if ($cipher) {
|
if ($cipher) {
|
||||||
$aCiphers = static::listCiphers();
|
$ciphers = static::listCiphers();
|
||||||
if (\in_array($cipher, $aCiphers)) {
|
if (\in_array($cipher, $ciphers)) {
|
||||||
static::$cipher = $cipher;
|
static::$cipher = $cipher;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -48,63 +48,60 @@ abstract class Crypt
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function DecryptUrlSafe($sData, string $key = null) /* : mixed */
|
|
||||||
{
|
|
||||||
return \json_decode(
|
|
||||||
\zlib_decode(
|
|
||||||
static::XxteaDecrypt(
|
|
||||||
\MailSo\Base\Utils::UrlSafeBase64Decode($sData),
|
|
||||||
static::Passphrase($key)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function EncryptUrlSafe($mData, string $key = null) : string
|
|
||||||
{
|
|
||||||
return \MailSo\Base\Utils::UrlSafeBase64Encode(
|
|
||||||
static::XxteaEncrypt(
|
|
||||||
\zlib_encode(
|
|
||||||
\json_encode($mData),
|
|
||||||
ZLIB_ENCODING_RAW,
|
|
||||||
9
|
|
||||||
),
|
|
||||||
static::Passphrase($key)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function Decrypt(array $data, string $key = null) /* : mixed */
|
public static function Decrypt(array $data, string $key = null) /* : mixed */
|
||||||
{
|
{
|
||||||
if (3 === \count($data) && isset($data[0], $data[1], $data[2])) {
|
if (3 === \count($data) && isset($data[0], $data[1], $data[2])) {
|
||||||
$fn = "{$data[0]}Decrypt";
|
try {
|
||||||
if (\method_exists(__CLASS__, $fn)) {
|
$fn = "{$data[0]}Decrypt";
|
||||||
return \SnappyMail\Crypt::{$fn}($data[2], $data[1], $key);
|
if (\method_exists(__CLASS__, $fn)) {
|
||||||
|
$result = static::{$fn}($data[2], $data[1], $key);
|
||||||
|
return \json_decode($result, true);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\trigger_error(__CLASS__ . "::{$fn}(): " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
\trigger_error(__CLASS__ . '::Decrypt() invalid $data');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function DecryptFromJSON(string $data, string $key = null) /* : mixed */
|
public static function DecryptFromJSON(string $data, string $key = null) /* : mixed */
|
||||||
{
|
{
|
||||||
$aData = \json_decode($data, true);
|
$data = \json_decode($data, true);
|
||||||
return \is_array($aData) ? static::Decrypt(\array_map('base64_decode', $aData), $key) : null;
|
if (!\is_array($data)) {
|
||||||
|
\trigger_error(__CLASS__ . '::DecryptFromJSON() invalid $data');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return static::Decrypt(\array_map('base64_decode', $data), $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function DecryptUrlSafe(string $data, string $key = null) /* : mixed */
|
||||||
|
{
|
||||||
|
$data = \explode('.', $data);
|
||||||
|
if (!\is_array($data)) {
|
||||||
|
\trigger_error(__CLASS__ . '::DecryptUrlSafe() invalid $data');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return static::Decrypt(\array_map('MailSo\\Base\\Utils::UrlSafeBase64Decode', $data), $key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function Encrypt($data, string $key = null) : array
|
public static function Encrypt($data, string $key = null) : array
|
||||||
{
|
{
|
||||||
|
$data = \json_encode($data);
|
||||||
if (\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
|
if (\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
|
||||||
$nonce = \random_bytes(24);
|
$nonce = \random_bytes(\SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
|
||||||
return ['sodium', $nonce, static::SodiumEncrypt($data, $nonce)];
|
$result = ['sodium', $nonce, static::SodiumEncrypt($data, $nonce, $key)];
|
||||||
}
|
} else if (static::$cipher && \is_callable('openssl_encrypt')) {
|
||||||
|
|
||||||
if (static::$cipher && \is_callable('openssl_encrypt')) {
|
|
||||||
$iv = \random_bytes(\openssl_cipher_iv_length(static::$cipher));
|
$iv = \random_bytes(\openssl_cipher_iv_length(static::$cipher));
|
||||||
return ['openssl', $iv, static::OpenSSLEncrypt($data, $iv)];
|
$result = ['openssl', $iv, static::OpenSSLEncrypt($data, $iv, $key)];
|
||||||
|
} else {
|
||||||
|
$salt = \random_bytes(16);
|
||||||
|
$result = ['xxtea', $salt, static::XxteaEncrypt($data, $salt, $key)];
|
||||||
}
|
}
|
||||||
|
if (static::{"{$result[0]}Decrypt"}($result[2], $result[1], $key) !== $data) {
|
||||||
$salt = \random_bytes(16);
|
throw new \Exception('Encrypt/Decrypt mismatch');
|
||||||
return ['xxtea', $salt, static::XxteaEncrypt($data, $salt)];
|
}
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function EncryptToJSON($data, string $key = null) : string
|
public static function EncryptToJSON($data, string $key = null) : string
|
||||||
|
|
@ -112,26 +109,31 @@ abstract class Crypt
|
||||||
return \json_encode(\array_map('base64_encode', static::Encrypt($data, $key)));
|
return \json_encode(\array_map('base64_encode', static::Encrypt($data, $key)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function EncryptUrlSafe($data, string $key = null) : string
|
||||||
|
{
|
||||||
|
return \implode('.', \array_map('MailSo\\Base\\Utils::UrlSafeBase64Encode', static::Encrypt($data, $key)));
|
||||||
|
}
|
||||||
|
|
||||||
public static function SodiumDecrypt(string $data, string $nonce, string $key = null) /* : mixed */
|
public static function SodiumDecrypt(string $data, string $nonce, string $key = null) /* : mixed */
|
||||||
{
|
{
|
||||||
if (!\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_decrypt')) {
|
if (!\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_decrypt')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return \json_decode(\zlib_decode(\sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
|
return \sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||||
$data,
|
$data,
|
||||||
APP_SALT,
|
APP_SALT,
|
||||||
$nonce,
|
$nonce,
|
||||||
\str_pad('', \SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, static::Passphrase($key))
|
\str_pad('', \SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, static::Passphrase($key))
|
||||||
)));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function SodiumEncrypt($data, string $nonce, string $key = null) : ?string
|
public static function SodiumEncrypt(string $data, string $nonce, string $key = null) : ?string
|
||||||
{
|
{
|
||||||
if (!\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
|
if (!\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return \sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
|
return \sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||||
\zlib_encode(\json_encode($data), ZLIB_ENCODING_RAW, 9),
|
$data,
|
||||||
APP_SALT,
|
APP_SALT,
|
||||||
$nonce,
|
$nonce,
|
||||||
\str_pad('', \SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, static::Passphrase($key))
|
\str_pad('', \SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, static::Passphrase($key))
|
||||||
|
|
@ -143,22 +145,22 @@ abstract class Crypt
|
||||||
if (!$data || !$iv || !static::$cipher || !\is_callable('openssl_decrypt')) {
|
if (!$data || !$iv || !static::$cipher || !\is_callable('openssl_decrypt')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return \json_decode(\zlib_decode(\openssl_decrypt(
|
return \openssl_decrypt(
|
||||||
$data,
|
$data,
|
||||||
static::$cipher,
|
static::$cipher,
|
||||||
static::Passphrase($key),
|
static::Passphrase($key),
|
||||||
OPENSSL_RAW_DATA,
|
OPENSSL_RAW_DATA,
|
||||||
$iv
|
$iv
|
||||||
)), true);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function OpenSSLEncrypt($data, string $iv, string $key = null) : ?string
|
public static function OpenSSLEncrypt(string $data, string $iv, string $key = null) : ?string
|
||||||
{
|
{
|
||||||
if (!$data || !$iv || !static::$cipher || !\is_callable('openssl_encrypt')) {
|
if (!$data || !$iv || !static::$cipher || !\is_callable('openssl_encrypt')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return \openssl_encrypt(
|
return \openssl_encrypt(
|
||||||
\zlib_encode(\json_encode($data), ZLIB_ENCODING_RAW, 9),
|
$data,
|
||||||
static::$cipher,
|
static::$cipher,
|
||||||
static::Passphrase($key),
|
static::Passphrase($key),
|
||||||
OPENSSL_RAW_DATA,
|
OPENSSL_RAW_DATA,
|
||||||
|
|
@ -172,18 +174,16 @@ abstract class Crypt
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$key = $salt . static::Passphrase($key);
|
$key = $salt . static::Passphrase($key);
|
||||||
return \json_decode(\zlib_decode(\is_callable('xxtea_decrypt')
|
return \is_callable('xxtea_decrypt')
|
||||||
? \xxtea_decrypt($data, $key)
|
? \xxtea_decrypt($data, $key)
|
||||||
: \MailSo\Base\Xxtea::decrypt($data, $key)
|
: \MailSo\Base\Xxtea::decrypt($data, $key);
|
||||||
), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function XxteaEncrypt($data, string $salt, string $key = null) : ?string
|
public static function XxteaEncrypt(string $data, string $salt, string $key = null) : ?string
|
||||||
{
|
{
|
||||||
if (!$data || !$salt) {
|
if (!$data || !$salt) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$data = \zlib_encode(\json_encode($data), ZLIB_ENCODING_RAW, 9);
|
|
||||||
$key = $salt . static::Passphrase($key);
|
$key = $salt . static::Passphrase($key);
|
||||||
return \is_callable('xxtea_encrypt')
|
return \is_callable('xxtea_encrypt')
|
||||||
? \xxtea_encrypt($data, $key)
|
? \xxtea_encrypt($data, $key)
|
||||||
|
|
|
||||||
|
|
@ -5,35 +5,33 @@
|
||||||
<span data-bind="visible: !isNew()" data-i18n="POPUPS_ADD_ACCOUNT/TITLE_UPDATE_ACCOUNT"></span>
|
<span data-bind="visible: !isNew()" data-i18n="POPUPS_ADD_ACCOUNT/TITLE_UPDATE_ACCOUNT"></span>
|
||||||
</h3>
|
</h3>
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<form class="modal-body form-horizontal" autocomplete="off">
|
||||||
<div class="form-horizontal">
|
<div class="alert" data-bind="visible: '' !== submitError()">
|
||||||
<div class="alert" data-bind="visible: '' !== submitError()">
|
<button type="button" class="close" data-bind="click: function () { submitError('') }">×</button>
|
||||||
<button type="button" class="close" data-bind="click: function () { submitError('') }">×</button>
|
<span data-bind="text: submitError"></span>
|
||||||
<span data-bind="text: submitError"></span>
|
<div data-bind="visible: submitErrorAdditional">
|
||||||
<div data-bind="visible: submitErrorAdditional">
|
<br />
|
||||||
<br />
|
<span data-bind="text: submitErrorAdditional"></span>
|
||||||
<span data-bind="text: submitErrorAdditional"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="control-group" data-bind="css: {'error': emailError}">
|
|
||||||
<label class="control-label" data-i18n="GLOBAL/EMAIL"></label>
|
|
||||||
<div class="controls">
|
|
||||||
<label style="margin-top: 5px;" data-bind="visible: !isNew()"><strong data-bind="text: email"></strong></label>
|
|
||||||
<input type="email" class="input-xlarge"
|
|
||||||
autofocus="" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
|
|
||||||
data-bind="visible: isNew, textInput: email, onEnter: addAccountCommand" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group" data-bind="css: {'error': passwordError}">
|
|
||||||
<label class="control-label" data-i18n="GLOBAL/PASSWORD"></label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="password" class="inputPassword input-xlarge" autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false"
|
|
||||||
data-bind="value: password, onEnter: addAccountCommand" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<br />
|
||||||
|
<div class="control-group" data-bind="css: {'error': emailError}">
|
||||||
|
<label class="control-label" data-i18n="GLOBAL/EMAIL"></label>
|
||||||
|
<div class="controls">
|
||||||
|
<label style="margin-top: 5px;" data-bind="visible: !isNew()"><strong data-bind="text: email"></strong></label>
|
||||||
|
<input type="email" class="input-xlarge"
|
||||||
|
autofocus="" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||||
|
data-bind="visible: isNew, textInput: email, onEnter: addAccountCommand" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group" data-bind="css: {'error': passwordError}">
|
||||||
|
<label class="control-label" data-i18n="GLOBAL/PASSWORD"></label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="password" class="inputPassword input-xlarge" autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||||
|
data-bind="value: password, onEnter: addAccountCommand" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
<footer>
|
<footer>
|
||||||
<a class="btn buttonAddAccount" data-bind="command: addAccountCommand">
|
<a class="btn buttonAddAccount" data-bind="command: addAccountCommand">
|
||||||
<i data-bind="visible: isNew, css: {'icon-user-add': !submitRequest(), 'icon-spinner': submitRequest()}"></i>
|
<i data-bind="visible: isNew, css: {'icon-user-add': !submitRequest(), 'icon-spinner': submitRequest()}"></i>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue