Skip to content

Commit

Permalink
Revamp the whole accounts system for better management and control.
Browse files Browse the repository at this point in the history
This also solves RainLoop/RainLoop#2134
  • Loading branch information
djmaze committed Nov 11, 2021
1 parent ef4790d commit a18d393
Show file tree
Hide file tree
Showing 15 changed files with 432 additions and 392 deletions.
3 changes: 1 addition & 2 deletions snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -1164,8 +1164,7 @@ public static function UrlSafeBase64Encode(string $sValue) : string

public static function UrlSafeBase64Decode(string $sValue) : string
{
$sValue = \rtrim(\strtr($sValue, '-_.', '+/='), '=');
return static::Base64Decode(\str_pad($sValue, \strlen($sValue) + (\strlen($sValue) % 4), '=', STR_PAD_RIGHT));
return \base64_decode(\strtr($sValue, '-_', '+/'), '=');
}

public static function ParseFetchSequence(string $sSequence) : array
Expand Down
63 changes: 13 additions & 50 deletions snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ class Actions
*/
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_CUSTOM_MSG_KEY = 'smspeclogoutcmk';

Expand Down Expand Up @@ -553,7 +558,7 @@ public function Cacher(?Model\Account $oAccount = null, bool $bForceFile = false
{
$sKey = '';
if ($oAccount) {
$sKey = $oAccount->ParentEmailHelper();
$sKey = $this->GetMainEmail($oAccount);
}

$sIndexKey = empty($sKey) ? '_default_' : $sKey;
Expand Down Expand Up @@ -918,7 +923,9 @@ public function AppData(bool $bAdmin): array
$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);

Expand Down Expand Up @@ -1128,32 +1135,6 @@ public function GetIdentityByID(Model\Account $oAccount, string $sID, bool $bFir
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
{
$sValue = $this->GetActionParam($sParamName, '');
Expand Down Expand Up @@ -1420,28 +1401,10 @@ private function importContactsFromCsvFile(Model\Account $oAccount, /*resource*/

if (\count($aData)) {
$this->Logger()->Write('Import contacts from csv');
$iCount = $oAddressBookProvider->ImportCsvArray($oAccount->ParentEmailHelper(), $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);
$iCount = $oAddressBookProvider->ImportCsvArray(
$this->GetMainEmail($oAccount),
$aData
);
}
}
}
Expand Down
145 changes: 66 additions & 79 deletions snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,54 @@
use RainLoop\Model\Identity;
use RainLoop\Notifications;
use RainLoop\Providers\Storage\Enumerations\StorageType;
use RainLoop\Utils;

trait Accounts
{

protected function GetMainEmail(Account $oAccount)
{
return $oAccount instanceof \RainLoop\Model\AdditionalAccount ? $oAccount->ParentEmail() : $oAccount->Email();
}

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)) {
$sAccounts = $this->StorageProvider()->Get($oAccount,
StorageType::CONFIG,
'accounts'
'additionalaccounts'
);

$aAccounts = $sAccounts ? \json_decode($sAccounts, true) : array();

if (\is_array($aAccounts) && \count($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);
});
}
}

if ($aAccounts && \is_array($aAccounts)) {
return $aAccounts;
}
}

$aAccounts = array();
if (!$oAccount->IsAdditionalAccount()) {
$aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken();
}

return $aAccounts;
return array();
}

protected function SetAccounts(Account $oAccount, array $aAccounts = array()): void
{
$sParentEmail = $oAccount->ParentEmailHelper();
if (!$aAccounts ||
(1 === \count($aAccounts) && !empty($aAccounts[$sParentEmail]))) {
if (\is_subclass_of($oAccount, 'RainLoop\\Model\\Account')) {
throw new \LogicException('Only main account can have sub accounts');
}
$sParentEmail = $oAccount->Email();
if (!$aAccounts) {
$this->StorageProvider()->Clear(
$oAccount,
StorageType::CONFIG,
'accounts'
'additionalaccounts'
);
} else {
$this->StorageProvider()->Put(
$oAccount,
StorageType::CONFIG,
'accounts',
'additionalaccounts',
\json_encode($aAccounts)
);
}
Expand All @@ -79,36 +66,30 @@ protected function SetAccounts(Account $oAccount, array $aAccounts = array()): v
*/
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__);
}

$sParentEmail = $oAccount->ParentEmailHelper();

$aAccounts = $this->GetAccounts($oAccount);
$aAccounts = $this->GetAccounts($oMainAccount);

$sEmail = \trim($this->GetActionParam('Email', ''));
$sPassword = $this->GetActionParam('Password', '');
$bNew = '1' === (string)$this->GetActionParam('New', '1');

$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);
} else if (!$bNew && !isset($aAccounts[$sEmail])) {
throw new ClientException(Notifications::AccountDoesNotExist);
}

$oNewAccount = $this->LoginProcess($sEmail, $sPassword);
$oNewAccount->SetParentEmail($sParentEmail);
$oNewAccount = $this->LoginProcess($sEmail, $sPassword, false, $oMainAccount);

$aAccounts[$oNewAccount->Email()] = $oNewAccount->GetAuthToken();
if (!$oAccount->IsAdditionalAccount()) {
$aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken();
}
$aAccounts[$oNewAccount->Email()] = $oNewAccount->asTokenArray($oMainAccount);
$this->SetAccounts($oMainAccount, $aAccounts);

$this->SetAccounts($oAccount, $aAccounts);
return $this->TrueResponse(__FUNCTION__);
}

Expand All @@ -117,31 +98,29 @@ public function DoAccountSetup(): 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__);
}

$sParentEmail = $oAccount->ParentEmailHelper();
$sEmailToDelete = \trim($this->GetActionParam('EmailToDelete', ''));
$sEmailToDelete = \MailSo\Base\Utils::IdnToAscii($sEmailToDelete, true);

$aAccounts = $this->GetAccounts($oAccount);
$aAccounts = $this->GetAccounts($oMainAccount);

if (0 < \strlen($sEmailToDelete) && $sEmailToDelete !== $sParentEmail && isset($aAccounts[$sEmailToDelete])) {
unset($aAccounts[$sEmailToDelete]);

$oAccountToChange = null;
if ($oAccount->Email() === $sEmailToDelete && !empty($aAccounts[$sParentEmail])) {
$oAccountToChange = $this->GetAccountFromCustomToken($aAccounts[$sParentEmail]);
if ($oAccountToChange) {
$this->SetAuthToken($oAccountToChange);
}
if (\strlen($sEmailToDelete) && isset($aAccounts[$sEmailToDelete])) {
$bReload = false;
// $oAccount = $this->getAccountFromToken();
if ($oAccount->Email() === $sEmailToDelete) {
Utils::ClearCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
$bReload = true;
}

$this->SetAccounts($oAccount, $aAccounts);
return $this->TrueResponse(__FUNCTION__, array('Reload' => !!$oAccountToChange));
unset($aAccounts[$sEmailToDelete]);
$this->SetAccounts($oMainAccount, $aAccounts);

return $this->TrueResponse(__FUNCTION__, array('Reload' => $bReload));
}

return $this->FalseResponse(__FUNCTION__);
Expand Down Expand Up @@ -188,19 +167,27 @@ public function DoIdentityDelete(): array
*/
public function DoAccountsAndIdentitiesSortOrder(): array
{
$oAccount = $this->getAccountFromToken();

$aAccounts = $this->GetActionParam('Accounts', null);
$aIdentities = $this->GetActionParam('Identities', null);

if (!\is_array($aAccounts) && !\is_array($aIdentities)) {
return $this->FalseResponse(__FUNCTION__);
}

return $this->DefaultResponse(__FUNCTION__, $this->StorageProvider()->Put($oAccount,
StorageType::CONFIG, 'accounts_identities_order',
$oAccount = $this->getMainAccountFromToken();
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(
'Accounts' => \is_array($aAccounts) ? $aAccounts : array(),
'Identities' => \is_array($aIdentities) ? $aIdentities : array()
))
));
Expand All @@ -211,39 +198,39 @@ public function DoAccountsAndIdentitiesSortOrder(): array
*/
public function DoAccountsAndIdentities(): array
{
$oAccount = $this->getAccountFromToken();

$mAccounts = false;
$oAccount = $this->getMainAccountFromToken();

$aAccounts = false;
if ($this->GetCapa(false, Capa::ADDITIONAL_ACCOUNTS, $oAccount)) {
$mAccounts = $this->GetAccounts($oAccount);
$mAccounts = \array_keys($mAccounts);

foreach ($mAccounts as $iIndex => $sName) {
$mAccounts[$iIndex] = \MailSo\Base\Utils::IdnToUtf8($sName);
$aAccounts = \array_map(
'MailSo\\Base\\Utils::IdnToUtf8',
\array_keys($this->GetAccounts($oAccount))
);
if ($aAccounts) {
\array_unshift($aAccounts, \MailSo\Base\Utils::IdnToUtf8($oAccount->Email()));
}
}

return $this->DefaultResponse(__FUNCTION__, array(
'Accounts' => $mAccounts,
'Accounts' => $aAccounts,
'Identities' => $this->GetIdentities($oAccount)
));
}

/**
* @param Account $account
* @param Account $oAccount
* @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
$allowMultipleIdentities = $this->GetCapa(false, Capa::IDENTITIES, $account);
$allowMultipleIdentities = $this->GetCapa(false, Capa::IDENTITIES, $oAccount);

// Get all identities
$identities = $this->IdentitiesProvider()->GetIdentities($account, $allowMultipleIdentities);
$identities = $this->IdentitiesProvider()->GetIdentities($oAccount, $allowMultipleIdentities);

// 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) ?? [];
if (isset($order['Identities']) && \is_array($order['Identities']) && \count($order['Identities']) > 1) {
$list = \array_map(function ($item) {
Expand Down
Loading

0 comments on commit a18d393

Please sign in to comment.