mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Improved additional account management
This commit is contained in:
parent
b057c4083e
commit
6b8020b8f5
15 changed files with 161 additions and 161 deletions
|
|
@ -420,10 +420,10 @@ class AppUser extends AbstractApp {
|
||||||
|
|
||||||
if (!iError) {
|
if (!iError) {
|
||||||
const counts = {},
|
const counts = {},
|
||||||
sAccountEmail = AccountUserStore.email();
|
accounts = oData.Result.Accounts,
|
||||||
let parentEmail = SettingsGet('ParentEmail') || sAccountEmail;
|
mainEmail = SettingsGet('MainEmail');
|
||||||
|
|
||||||
if (isArray(oData.Result.Accounts)) {
|
if (isArray(accounts)) {
|
||||||
AccountUserStore.accounts.forEach(oAccount =>
|
AccountUserStore.accounts.forEach(oAccount =>
|
||||||
counts[oAccount.email] = oAccount.count()
|
counts[oAccount.email] = oAccount.count()
|
||||||
);
|
);
|
||||||
|
|
@ -431,10 +431,12 @@ class AppUser extends AbstractApp {
|
||||||
delegateRunOnDestroy(AccountUserStore.accounts());
|
delegateRunOnDestroy(AccountUserStore.accounts());
|
||||||
|
|
||||||
AccountUserStore.accounts(
|
AccountUserStore.accounts(
|
||||||
oData.Result.Accounts.map(
|
accounts.map(
|
||||||
sValue => new AccountModel(sValue, sValue !== parentEmail, counts[sValue] || 0)
|
sValue => new AccountModel(sValue, counts[sValue])
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
// accounts.length &&
|
||||||
|
AccountUserStore.accounts.unshift(new AccountModel(mainEmail, counts[mainEmail], false));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isArray(oData.Result.Identities)) {
|
if (isArray(oData.Result.Identities)) {
|
||||||
|
|
@ -442,9 +444,10 @@ class AppUser extends AbstractApp {
|
||||||
|
|
||||||
IdentityUserStore(
|
IdentityUserStore(
|
||||||
oData.Result.Identities.map(identityData => {
|
oData.Result.Identities.map(identityData => {
|
||||||
const id = pString(identityData.Id),
|
const identity = new IdentityModel(
|
||||||
email = pString(identityData.Email),
|
pString(identityData.Id),
|
||||||
identity = new IdentityModel(id, email);
|
pString(identityData.Email)
|
||||||
|
);
|
||||||
|
|
||||||
identity.name(pString(identityData.Name));
|
identity.name(pString(identityData.Name));
|
||||||
identity.replyTo(pString(identityData.ReplyTo));
|
identity.replyTo(pString(identityData.ReplyTo));
|
||||||
|
|
@ -715,7 +718,6 @@ class AppUser extends AbstractApp {
|
||||||
NotificationUserStore.enableDesktopNotification(!!SettingsGet('DesktopNotifications'));
|
NotificationUserStore.enableDesktopNotification(!!SettingsGet('DesktopNotifications'));
|
||||||
|
|
||||||
AccountUserStore.email(SettingsGet('Email'));
|
AccountUserStore.email(SettingsGet('Email'));
|
||||||
AccountUserStore.parentEmail(SettingsGet('ParentEmail'));
|
|
||||||
|
|
||||||
this.foldersReload(value => {
|
this.foldersReload(value => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,6 @@ export const
|
||||||
*/
|
*/
|
||||||
serverRequest = type => SERVER_PREFIX + '/' + type + '/' + SUB_QUERY_PREFIX + '/0/',
|
serverRequest = type => SERVER_PREFIX + '/' + type + '/' + SUB_QUERY_PREFIX + '/0/',
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} email
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
change = email => serverRequest('Change') + encodeURIComponent(email) + '/',
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} lang
|
* @param {string} lang
|
||||||
* @param {boolean} isAdmin
|
* @param {boolean} isAdmin
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { change } from 'Common/Links';
|
|
||||||
|
|
||||||
import { AbstractModel } from 'Knoin/AbstractModel';
|
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
|
|
||||||
export class AccountModel extends AbstractModel {
|
export class AccountModel extends AbstractModel {
|
||||||
|
|
@ -8,23 +6,16 @@ export class AccountModel extends AbstractModel {
|
||||||
* @param {boolean=} canBeDelete = true
|
* @param {boolean=} canBeDelete = true
|
||||||
* @param {number=} count = 0
|
* @param {number=} count = 0
|
||||||
*/
|
*/
|
||||||
constructor(email, canBeDelete = true, count = 0) {
|
constructor(email, count = 0, isAdditional = true) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.email = email;
|
this.email = email;
|
||||||
|
|
||||||
this.addObservables({
|
this.addObservables({
|
||||||
count: count,
|
count: count || 0,
|
||||||
deleteAccess: false,
|
deleteAccess: false,
|
||||||
canBeDeleted: !!canBeDelete
|
isAdditional: isAdditional
|
||||||
});
|
});
|
||||||
this.canBeEdit = this.canBeDeleted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
changeAccountLink() {
|
|
||||||
return change(this.email);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
import { Capa } from 'Common/Enums';
|
import { Capa } from 'Common/Enums';
|
||||||
import { Settings } from 'Common/Globals';
|
import { Settings, SettingsGet } from 'Common/Globals';
|
||||||
|
|
||||||
import { AccountUserStore } from 'Stores/User/Account';
|
import { AccountUserStore } from 'Stores/User/Account';
|
||||||
import { IdentityUserStore } from 'Stores/User/Identity';
|
import { IdentityUserStore } from 'Stores/User/Identity';
|
||||||
|
|
@ -20,6 +20,7 @@ export class AccountsUserSettings /*extends AbstractViewSettings*/ {
|
||||||
this.accounts = AccountUserStore.accounts;
|
this.accounts = AccountUserStore.accounts;
|
||||||
this.loading = AccountUserStore.loading;
|
this.loading = AccountUserStore.loading;
|
||||||
this.identities = IdentityUserStore;
|
this.identities = IdentityUserStore;
|
||||||
|
this.mainEmail = SettingsGet('MainEmail');
|
||||||
|
|
||||||
this.accountForDeletion = ko.observable(null).deleteAccessHelper();
|
this.accountForDeletion = ko.observable(null).deleteAccessHelper();
|
||||||
this.identityForDeletion = ko.observable(null).deleteAccessHelper();
|
this.identityForDeletion = ko.observable(null).deleteAccessHelper();
|
||||||
|
|
@ -30,7 +31,7 @@ export class AccountsUserSettings /*extends AbstractViewSettings*/ {
|
||||||
}
|
}
|
||||||
|
|
||||||
editAccount(account) {
|
editAccount(account) {
|
||||||
if (account && account.canBeEdit()) {
|
if (account && account.isAdditional()) {
|
||||||
showScreenPopup(AccountPopupView, [account]);
|
showScreenPopup(AccountPopupView, [account]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +82,10 @@ export class AccountsUserSettings /*extends AbstractViewSettings*/ {
|
||||||
}
|
}
|
||||||
|
|
||||||
accountsAndIdentitiesAfterMove() {
|
accountsAndIdentitiesAfterMove() {
|
||||||
Remote.accountsAndIdentitiesSortOrder(null, AccountUserStore.getEmailAddresses(), IdentityUserStore.getIDS());
|
Remote.accountsAndIdentitiesSortOrder(null,
|
||||||
|
AccountUserStore.getEmailAddresses().filter(v => v != SettingsGet('MainEmail')),
|
||||||
|
IdentityUserStore.getIDS()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onBuild(oDom) {
|
onBuild(oDom) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export const AccountUserStore = {
|
||||||
accounts: ko.observableArray(),
|
accounts: ko.observableArray(),
|
||||||
loading: ko.observable(false).extend({ debounce: 100 }),
|
loading: ko.observable(false).extend({ debounce: 100 }),
|
||||||
|
|
||||||
getEmailAddresses: () => AccountUserStore.accounts.map(item => item ? item.email : null).filter(v => v),
|
getEmailAddresses: () => AccountUserStore.accounts.map(item => item.email),
|
||||||
|
|
||||||
accountsUnreadCount: ko.computed(() => 0),
|
accountsUnreadCount: ko.computed(() => 0),
|
||||||
// accountsUnreadCount: ko.computed(() => {
|
// accountsUnreadCount: ko.computed(() => {
|
||||||
|
|
@ -21,6 +21,5 @@ export const AccountUserStore = {
|
||||||
|
|
||||||
addObservablesTo(AccountUserStore, {
|
addObservablesTo(AccountUserStore, {
|
||||||
email: '',
|
email: '',
|
||||||
parentEmail: '',
|
|
||||||
signature: ''
|
signature: ''
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,11 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
td + td {
|
td + td + td {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
td + td + td {
|
td:first-child,
|
||||||
|
td:last-child, {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ class AccountPopupView extends AbstractViewPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
onShow(account) {
|
onShow(account) {
|
||||||
if (account && account.canBeEdit()) {
|
if (account && account.isAdditional()) {
|
||||||
this.isNew(false);
|
this.isNew(false);
|
||||||
this.email(account.email);
|
this.email(account.email);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export class SystemDropDownUserView extends AbstractViewRight {
|
||||||
if (iError) {
|
if (iError) {
|
||||||
AccountUserStore.loading(false);
|
AccountUserStore.loading(false);
|
||||||
alert(getNotification(iError).replace('%EMAIL%', account.email));
|
alert(getNotification(iError).replace('%EMAIL%', account.email));
|
||||||
if (account.canBeEdit()) {
|
if (account.isAdditional()) {
|
||||||
showScreenPopup(AccountPopupView, [account]);
|
showScreenPopup(AccountPopupView, [account]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -852,7 +852,7 @@ class Actions
|
||||||
'ReplySameFolder' => (bool) $oConfig->Get('defaults', 'mail_reply_same_folder', false),
|
'ReplySameFolder' => (bool) $oConfig->Get('defaults', 'mail_reply_same_folder', false),
|
||||||
'ContactsAutosave' => (bool) $oConfig->Get('defaults', 'contacts_autosave', true),
|
'ContactsAutosave' => (bool) $oConfig->Get('defaults', 'contacts_autosave', true),
|
||||||
'HideUnsubscribed' => (bool) $oConfig->Get('labs', 'use_imap_list_subscribe', true),
|
'HideUnsubscribed' => (bool) $oConfig->Get('labs', 'use_imap_list_subscribe', true),
|
||||||
'ParentEmail' => '',
|
'MainEmail' => '',
|
||||||
'InterfaceAnimation' => true,
|
'InterfaceAnimation' => true,
|
||||||
'UserBackgroundName' => '',
|
'UserBackgroundName' => '',
|
||||||
'UserBackgroundHash' => ''
|
'UserBackgroundHash' => ''
|
||||||
|
|
@ -920,9 +920,7 @@ class Actions
|
||||||
$aResult['StartupUrl'] = $this->compileLogParams($aResult['StartupUrl'], $oAccount, true);
|
$aResult['StartupUrl'] = $this->compileLogParams($aResult['StartupUrl'], $oAccount, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($oAccount instanceof \RainLoop\Model\AdditionalAccount) {
|
$aResult['MainEmail'] = \MailSo\Base\Utils::IdnToUtf8($this->getMainAccountFromToken()->Email());
|
||||||
$aResult['ParentEmail'] = $oAccount->ParentEmail();
|
|
||||||
}
|
|
||||||
|
|
||||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||||
|
|
||||||
|
|
@ -1048,7 +1046,6 @@ class Actions
|
||||||
|
|
||||||
// IDN
|
// IDN
|
||||||
$aResult['Email'] = \MailSo\Base\Utils::IdnToUtf8($aResult['Email']);
|
$aResult['Email'] = \MailSo\Base\Utils::IdnToUtf8($aResult['Email']);
|
||||||
$aResult['ParentEmail'] = \MailSo\Base\Utils::IdnToUtf8($aResult['ParentEmail']);
|
|
||||||
$aResult['MailToEmail'] = \MailSo\Base\Utils::IdnToUtf8($aResult['MailToEmail']);
|
$aResult['MailToEmail'] = \MailSo\Base\Utils::IdnToUtf8($aResult['MailToEmail']);
|
||||||
$aResult['DevEmail'] = \MailSo\Base\Utils::IdnToUtf8($aResult['DevEmail']);
|
$aResult['DevEmail'] = \MailSo\Base\Utils::IdnToUtf8($aResult['DevEmail']);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ trait Accounts
|
||||||
StorageType::CONFIG,
|
StorageType::CONFIG,
|
||||||
'additionalaccounts'
|
'additionalaccounts'
|
||||||
);
|
);
|
||||||
$aAccounts = $sAccounts ? \json_decode($sAccounts, true) : $this->ConvertInsecureAccounts($oAccount);
|
$aAccounts = $sAccounts ? \json_decode($sAccounts, true) : \SnappyMail\Upgrade::ConvertInsecureAccounts($this, $oAccount);
|
||||||
if ($aAccounts && \is_array($aAccounts)) {
|
if ($aAccounts && \is_array($aAccounts)) {
|
||||||
return $aAccounts;
|
return $aAccounts;
|
||||||
}
|
}
|
||||||
|
|
@ -36,81 +36,6 @@ trait Accounts
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to convert the old less secure data into better secured data
|
|
||||||
*/
|
|
||||||
protected function ConvertInsecureAccounts(MainAccount $oMainAccount) : array
|
|
||||||
{
|
|
||||||
$sAccounts = $this->StorageProvider()->Get($oMainAccount, StorageType::CONFIG, 'accounts');
|
|
||||||
if (!$sAccounts || '{' !== $sAccounts[0]) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$aAccounts = \json_decode($sAccounts, true);
|
|
||||||
if (!$aAccounts || !\is_array($aAccounts)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$aNewAccounts = [];
|
|
||||||
if (1 < \count($aAccounts)) {
|
|
||||||
$sOrder = $this->StorageProvider()->Get($oMainAccount, StorageType::CONFIG, 'accounts_identities_order');
|
|
||||||
$aOrder = $sOrder ? \json_decode($sOrder, true) : [];
|
|
||||||
if (!empty($aOrder['Accounts']) && \is_array($aOrder['Accounts']) && 1 < \count($aOrder['Accounts'])) {
|
|
||||||
$aAccounts = \array_filter(\array_merge(
|
|
||||||
\array_fill_keys($aOrder['Accounts'], null),
|
|
||||||
$aAccounts
|
|
||||||
));
|
|
||||||
}
|
|
||||||
$sHash = $oMainAccount->CryptKey();
|
|
||||||
foreach ($aAccounts as $sEmail => $sToken) {
|
|
||||||
try {
|
|
||||||
$aNewAccounts[$sEmail] = [
|
|
||||||
'account',
|
|
||||||
$sEmail,
|
|
||||||
$sEmail,
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
$oMainAccount->Email(),
|
|
||||||
\hash_hmac('sha1', '', $sHash)
|
|
||||||
];
|
|
||||||
if (!$sToken) {
|
|
||||||
\error_log("ConvertInsecureAccount {$sEmail} no token");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$aAccountHash = \RainLoop\Utils::DecodeKeyValues($sToken);
|
|
||||||
if (empty($aAccountHash[0]) || 'token' !== $aAccountHash[0] // simple token validation
|
|
||||||
|| 8 > \count($aAccountHash) // length checking
|
|
||||||
) {
|
|
||||||
\error_log("ConvertInsecureAccount {$sEmail} invalid aAccountHash: " . print_r($aAccountHash,1));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$aAccountHash[3] = \SnappyMail\Crypt::EncryptUrlSafe($aAccountHash[3], $sHash);
|
|
||||||
$aNewAccounts[$sEmail] = [
|
|
||||||
'account',
|
|
||||||
$aAccountHash[1],
|
|
||||||
$aAccountHash[2],
|
|
||||||
$aAccountHash[3],
|
|
||||||
$aAccountHash[11],
|
|
||||||
$aAccountHash[8],
|
|
||||||
$aAccountHash[9],
|
|
||||||
$oMainAccount->Email(),
|
|
||||||
\hash_hmac('sha1', $aAccountHash[3], $sHash)
|
|
||||||
];
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
\error_log("ConvertInsecureAccount {$sEmail} failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->SetAccounts($oMainAccount, $aNewAccounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->StorageProvider()->Clear($oMainAccount, StorageType::CONFIG, 'accounts');
|
|
||||||
|
|
||||||
return $aNewAccounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function SetAccounts(MainAccount $oAccount, array $aAccounts = array()): void
|
protected function SetAccounts(MainAccount $oAccount, array $aAccounts = array()): void
|
||||||
{
|
{
|
||||||
$sParentEmail = $oAccount->Email();
|
$sParentEmail = $oAccount->Email();
|
||||||
|
|
@ -206,7 +131,7 @@ trait Accounts
|
||||||
$aResult['IncLogin'] = $oAccount->IncLogin();
|
$aResult['IncLogin'] = $oAccount->IncLogin();
|
||||||
$aResult['OutLogin'] = $oAccount->OutLogin();
|
$aResult['OutLogin'] = $oAccount->OutLogin();
|
||||||
$aResult['AccountHash'] = $oAccount->Hash();
|
$aResult['AccountHash'] = $oAccount->Hash();
|
||||||
$aResult['ParentEmail'] = ($oAccount instanceof \RainLoop\Model\AdditionalAccount)
|
$aResult['MainEmail'] = ($oAccount instanceof \RainLoop\Model\AdditionalAccount)
|
||||||
? $oAccount->ParentEmail() : '';
|
? $oAccount->ParentEmail() : '';
|
||||||
$aResult['ContactsIsAllowed'] = $this->AddressBookProvider($oAccount)->IsActive();
|
$aResult['ContactsIsAllowed'] = $this->AddressBookProvider($oAccount)->IsActive();
|
||||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||||
|
|
@ -301,19 +226,11 @@ trait Accounts
|
||||||
{
|
{
|
||||||
$oAccount = $this->getMainAccountFromToken();
|
$oAccount = $this->getMainAccountFromToken();
|
||||||
|
|
||||||
$aAccounts = false;
|
return $this->DefaultResponse(__FUNCTION__, array(
|
||||||
if ($this->GetCapa(false, Capa::ADDITIONAL_ACCOUNTS, $oAccount)) {
|
'Accounts' => \array_map(
|
||||||
$aAccounts = \array_map(
|
|
||||||
'MailSo\\Base\\Utils::IdnToUtf8',
|
'MailSo\\Base\\Utils::IdnToUtf8',
|
||||||
\array_keys($this->GetAccounts($oAccount))
|
\array_keys($this->GetAccounts($oAccount))
|
||||||
);
|
),
|
||||||
if ($aAccounts) {
|
|
||||||
\array_unshift($aAccounts, \MailSo\Base\Utils::IdnToUtf8($oAccount->Email()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->DefaultResponse(__FUNCTION__, array(
|
|
||||||
'Accounts' => $aAccounts,
|
|
||||||
'Identities' => $this->GetIdentities($oAccount)
|
'Identities' => $this->GetIdentities($oAccount)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -278,17 +278,7 @@ trait Contacts
|
||||||
return $aData;
|
return $aData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try the old
|
return \SnappyMail\Upgrade::ConvertInsecureContactsSync($this, $oAccount);
|
||||||
$aData = \RainLoop\Utils::DecodeKeyValues($sData);
|
|
||||||
if ($aData) {
|
|
||||||
$this->setContactsSyncData($oAccount, $aData);
|
|
||||||
return array(
|
|
||||||
'Enable' => isset($aData['Enable']) ? !!$aData['Enable'] : false,
|
|
||||||
'Url' => isset($aData['Url']) ? \trim($aData['Url']) : '',
|
|
||||||
'User' => isset($aData['User']) ? \trim($aData['User']) : '',
|
|
||||||
'Password' => isset($aData['Password']) ? $aData['Password'] : ''
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -811,22 +811,6 @@ class ServiceActions
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch to AdditionalAccount
|
|
||||||
*/
|
|
||||||
public function ServiceChange() : string
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->oActions->switchAccount(
|
|
||||||
empty($this->aPaths[2]) ? '' : \urldecode(\trim($this->aPaths[2]))
|
|
||||||
);
|
|
||||||
$this->oActions->Location('./');
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
exit($e->getMessage());
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
114
snappymail/v/0.0.0/app/libraries/snappymail/upgrade.php
Normal file
114
snappymail/v/0.0.0/app/libraries/snappymail/upgrade.php
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SnappyMail;
|
||||||
|
|
||||||
|
use RainLoop\Providers\Storage\Enumerations\StorageType;
|
||||||
|
|
||||||
|
abstract class Upgrade
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to convert the old less secure data into better secured data
|
||||||
|
*/
|
||||||
|
public function ConvertInsecureAccounts(\RainLoop\Actions $oActions, \RainLoop\Model\MainAccount $oMainAccount) : array
|
||||||
|
{
|
||||||
|
$oStorage = $oActions->StorageProvider();
|
||||||
|
$sAccounts = $oStorage->Get($oMainAccount, StorageType::CONFIG, 'accounts');
|
||||||
|
if (!$sAccounts || '{' !== $sAccounts[0]) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$aAccounts = \json_decode($sAccounts, true);
|
||||||
|
if (!$aAccounts || !\is_array($aAccounts)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$aNewAccounts = [];
|
||||||
|
if (1 < \count($aAccounts)) {
|
||||||
|
$sOrder = $oStorage->Get($oMainAccount, StorageType::CONFIG, 'accounts_identities_order');
|
||||||
|
$aOrder = $sOrder ? \json_decode($sOrder, true) : [];
|
||||||
|
if (!empty($aOrder['Accounts']) && \is_array($aOrder['Accounts']) && 1 < \count($aOrder['Accounts'])) {
|
||||||
|
$aAccounts = \array_filter(\array_merge(
|
||||||
|
\array_fill_keys($aOrder['Accounts'], null),
|
||||||
|
$aAccounts
|
||||||
|
));
|
||||||
|
}
|
||||||
|
$sHash = $oMainAccount->CryptKey();
|
||||||
|
foreach ($aAccounts as $sEmail => $sToken) {
|
||||||
|
try {
|
||||||
|
$aNewAccounts[$sEmail] = [
|
||||||
|
'account',
|
||||||
|
$sEmail,
|
||||||
|
$sEmail,
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
$oMainAccount->Email(),
|
||||||
|
\hash_hmac('sha1', '', $sHash)
|
||||||
|
];
|
||||||
|
if (!$sToken) {
|
||||||
|
\error_log("ConvertInsecureAccount {$sEmail} no token");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$aAccountHash = \RainLoop\Utils::DecodeKeyValues($sToken);
|
||||||
|
if (empty($aAccountHash[0]) || 'token' !== $aAccountHash[0] // simple token validation
|
||||||
|
|| 8 > \count($aAccountHash) // length checking
|
||||||
|
) {
|
||||||
|
\error_log("ConvertInsecureAccount {$sEmail} invalid aAccountHash: " . \print_r($aAccountHash,1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$aAccountHash[3] = Crypt::EncryptUrlSafe($aAccountHash[3], $sHash);
|
||||||
|
$aNewAccounts[$sEmail] = [
|
||||||
|
'account',
|
||||||
|
$aAccountHash[1],
|
||||||
|
$aAccountHash[2],
|
||||||
|
$aAccountHash[3],
|
||||||
|
$aAccountHash[11],
|
||||||
|
$aAccountHash[8],
|
||||||
|
$aAccountHash[9],
|
||||||
|
$oMainAccount->Email(),
|
||||||
|
\hash_hmac('sha1', $aAccountHash[3], $sHash)
|
||||||
|
];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\error_log("ConvertInsecureAccount {$sEmail} failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$oActions->SetAccounts($oMainAccount, $aNewAccounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$oStorage->Clear($oMainAccount, StorageType::CONFIG, 'accounts');
|
||||||
|
|
||||||
|
return $aNewAccounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to convert the old less secure data into better secured data
|
||||||
|
*/
|
||||||
|
public function ConvertInsecureContactsSync(\RainLoop\Actions $oActions, \RainLoop\Model\Account $oAccount) : ?array
|
||||||
|
{
|
||||||
|
$sData = $oActions->StorageProvider()->Get($oAccount,
|
||||||
|
\RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
|
||||||
|
'contacts_sync'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($sData)) {
|
||||||
|
$aData = \json_decode($sData);
|
||||||
|
if (!$aData) {
|
||||||
|
$aData = \RainLoop\Utils::DecodeKeyValues($sData);
|
||||||
|
if ($aData) {
|
||||||
|
$oActions->setContactsSyncData($oAccount, $aData);
|
||||||
|
return array(
|
||||||
|
'Enable' => isset($aData['Enable']) ? !!$aData['Enable'] : false,
|
||||||
|
'Url' => isset($aData['Url']) ? \trim($aData['Url']) : '',
|
||||||
|
'User' => isset($aData['User']) ? \trim($aData['User']) : '',
|
||||||
|
'Password' => isset($aData['Password']) ? $aData['Password'] : ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -9,20 +9,28 @@
|
||||||
<span data-i18n="SETTINGS_ACCOUNTS/BUTTON_ADD_ACCOUNT"></span>
|
<span data-i18n="SETTINGS_ACCOUNTS/BUTTON_ADD_ACCOUNT"></span>
|
||||||
</a>
|
</a>
|
||||||
<table class="table table-hover list-table accounts-list" data-bind="i18nUpdate: accounts">
|
<table class="table table-hover list-table accounts-list" data-bind="i18nUpdate: accounts">
|
||||||
|
<tbody>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="3" data-bind="text: mainEmail"></td>
|
||||||
|
</tbody>
|
||||||
<tbody data-bind="foreach: accounts">
|
<tbody data-bind="foreach: accounts">
|
||||||
|
<!-- ko if:isAdditional -->
|
||||||
<tr draggable="true" data-bind="sortableItem: { list: $root.accounts, afterMove: $root.accountsAndIdentitiesAfterMove }">
|
<tr draggable="true" data-bind="sortableItem: { list: $root.accounts, afterMove: $root.accountsAndIdentitiesAfterMove }">
|
||||||
<td class="e-action" data-bind="css: {'e-action': canBeEdit}">
|
<td>
|
||||||
<i class="fontastic drag-handle">⬍</i>
|
<i class="fontastic drag-handle">⬍</i>
|
||||||
|
</td>
|
||||||
|
<td class="e-action">
|
||||||
<span class="account-name" data-bind="text: email"></span>
|
<span class="account-name" data-bind="text: email"></span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-small btn-small-small btn-danger button-confirm-delete" data-bind="visible: canBeDeleted, css: {'delete-access': deleteAccess}, click: function(oAccount) { $root.deleteAccount(oAccount); }"
|
<a class="btn btn-small btn-small-small btn-danger button-confirm-delete" data-bind="css: {'delete-access': deleteAccess}, click: function(oAccount) { $root.deleteAccount(oAccount); }"
|
||||||
data-i18n="GLOBAL/ARE_YOU_SURE"></a>
|
data-i18n="GLOBAL/ARE_YOU_SURE"></a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="delete fontastic" data-bind="visible: !deleteAccess() && canBeDeleted(), click: function (oAccount) { $root.accountForDeletion(oAccount); }">🗑</span>
|
<span class="delete fontastic" data-bind="visible: !deleteAccess(), click: function (oAccount) { $root.accountForDeletion(oAccount); }">🗑</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<!-- /ko -->
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,7 @@
|
||||||
<!-- ko if: accounts().length -->
|
<!-- ko if: accounts().length -->
|
||||||
<!-- ko foreach: accounts -->
|
<!-- ko foreach: accounts -->
|
||||||
<li role="presentation">
|
<li role="presentation">
|
||||||
<a class="account-item" href="#" data-bind="click: $root.accountClick,
|
<a class="account-item" href="#" data-bind="click: $root.accountClick, css: {'current': $root.accountEmail() === email}">
|
||||||
attr: {'href': changeAccountLink()}, css: {'current': $root.accountEmail() === email}">
|
|
||||||
<!-- <b class="pull-right counter" data-bind="visible: 0 < count()">
|
<!-- <b class="pull-right counter" data-bind="visible: 0 < count()">
|
||||||
<span data-bind="text: count, visible: 100 > count()"></span>
|
<span data-bind="text: count, visible: 100 > count()"></span>
|
||||||
<span data-bind="visible: 99 < count()">99+</span>
|
<span data-bind="visible: 99 < count()">99+</span>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue