Revamp Issue #51 to make the whole "change password" thing a plugin

This commit is contained in:
djmaze 2021-03-01 00:52:46 +01:00
parent 3426921c9d
commit fb03687528
17 changed files with 207 additions and 297 deletions

View file

@ -98,11 +98,6 @@ export const Notification = {
AccountTwoFactorAuthRequired: 120,
AccountTwoFactorAuthError: 121,
CouldNotSaveNewPassword: 130,
CurrentPasswordIncorrect: 131,
NewPasswordShort: 132,
NewPasswordWeak: 133,
ContactsSyncError: 140,
CantGetMessageList: 201,

View file

@ -337,6 +337,7 @@ function decorateKoCommands(thisArg, commands) {
thisArg[key] = fn;
});
}
ko.decorateCommands = decorateKoCommands;
/**
* @param {miced} $items

View file

@ -594,18 +594,6 @@ class RemoteUserFetch extends AbstractFetchRemote {
};
}
/**
* @param {?Function} fCallback
* @param {string} prevPassword
* @param {string} newPassword
*/
changePassword(fCallback, prevPassword, newPassword) {
this.defaultRequest(fCallback, 'ChangePassword', {
'PrevPassword': prevPassword,
'NewPassword': newPassword
});
}
/**
* @param {?Function} fCallback
* @param {string} sFolderFullNameRaw

View file

@ -18,7 +18,6 @@ import { TemplatesUserSettings } from 'Settings/User/Templates';
import { FoldersUserSettings } from 'Settings/User/Folders';
import { ThemesUserSettings } from 'Settings/User/Themes';
import { OpenPgpUserSettings } from 'Settings/User/OpenPgp';
import { ChangePasswordUserSettings } from 'Settings/User/ChangePassword';
import { SystemDropDownSettingsUserView } from 'View/User/Settings/SystemDropDown';
import { MenuSettingsUserView } from 'View/User/Settings/Menu';
@ -78,15 +77,6 @@ export class SettingsUserScreen extends AbstractSettingsScreen {
);
}
if (Settings.get('ChangePasswordIsAllowed')) {
settingsAddViewModel(
ChangePasswordUserSettings,
'SettingsChangePassword',
'GLOBAL/PASSWORD',
'change-password'
);
}
if (Settings.capa(Capa.Folders)) {
settingsAddViewModel(FoldersUserSettings, 'SettingsFolders', 'SETTINGS_LABELS/LABEL_FOLDERS_NAME', 'folders');
}

View file

@ -1,90 +0,0 @@
import ko from 'ko';
import { StorageResultType, Notification } from 'Common/Enums';
import { getNotificationFromResponse, i18n } from 'Common/Translator';
import { Settings } from 'Common/Globals';
import Remote from 'Remote/User/Fetch';
import { decorateKoCommands } from 'Knoin/Knoin';
export class ChangePasswordUserSettings {
constructor() {
ko.addObservablesTo(this, {
changeProcess: false,
errorDescription: '',
passwordMismatch: false,
passwordUpdateError: false,
passwordUpdateSuccess: false,
currentPassword: '',
currentPasswordError: false,
newPassword: '',
newPassword2: '',
});
this.currentPassword.subscribe(() => this.resetUpdate(true));
this.newPassword.subscribe(() => this.resetUpdate());
this.newPassword2.subscribe(() => this.resetUpdate());
decorateKoCommands(this, {
saveNewPasswordCommand: self => !self.changeProcess()
&& '' !== self.currentPassword()
&& '' !== self.newPassword()
&& '' !== self.newPassword2()
});
}
saveNewPasswordCommand() {
if (this.newPassword() !== this.newPassword2()) {
this.passwordMismatch(true);
this.errorDescription(i18n('SETTINGS_CHANGE_PASSWORD/ERROR_PASSWORD_MISMATCH'));
} else {
this.reset(true);
Remote.changePassword(this.onChangePasswordResponse.bind(this), this.currentPassword(), this.newPassword());
}
}
reset(change) {
this.changeProcess(change);
this.resetUpdate();
this.currentPasswordError(false);
this.errorDescription('');
}
resetUpdate(current) {
this.passwordUpdateError(false);
this.passwordUpdateSuccess(false);
current ? this.currentPasswordError(false) : this.passwordMismatch(false);
}
onHide() {
this.reset(false);
this.currentPassword('');
this.newPassword('');
this.newPassword2('');
}
onChangePasswordResponse(result, data) {
this.reset(false);
if (StorageResultType.Success === result && data && data.Result) {
this.currentPassword('');
this.newPassword('');
this.newPassword2('');
this.passwordUpdateSuccess(true);
rl.hash.set();
Settings.set('AuthAccountHash', data.Result);
} else {
if (data && Notification.CurrentPasswordIncorrect === data.ErrorCode) {
this.currentPasswordError(true);
}
this.passwordUpdateError(true);
this.errorDescription(getNotificationFromResponse(data, Notification.CouldNotSaveNewPassword));
}
}
}

View file

@ -1,36 +0,0 @@
<?php
class ChangePasswordExampleDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
{
/**
* @var string
*/
private $sAllowedEmails = '';
/**
* @param string $sAllowedEmails
*
* @return \ChangePasswordExampleDriver
*/
public function SetAllowedEmails($sAllowedEmails)
{
$this->sAllowedEmails = $sAllowedEmails;
return $this;
}
public function isPossible(\RainLoop\Model\Account $oAccount) : bool
{
$sFoundedValue = '';
return $oAccount && $oAccount->Email() &&
\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails, $sFoundedValue);
}
public function ChangePassword(\RainLoop\Model\Account $oAccount, string $sPrevPassword, string $sNewPassword) : bool
{
$bResult = false;
// TODO
return $bResult;
}
}

View file

@ -1,49 +0,0 @@
<?php
class ChangePasswordExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
{
const
NAME = 'Change Password Example',
VERSION = '2.0',
RELEASE = '2021-03-01',
REQUIRED = '2.3.4',
CATEGORY = 'Security',
DESCRIPTION = 'Plugin that adds functionality to change the email account password';
public function Init() : void
{
$this->addHook('main.fabrica', 'MainFabrica');
}
/**
* @param string $sName
* @param mixed $oProvider
*/
public function MainFabrica($sName, &$oProvider)
{
switch ($sName)
{
case 'change-password':
include_once __DIR__.'/ChangePasswordExampleDriver.php';
$oProvider = new ChangePasswordExampleDriver();
$oProvider->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', ''))));
break;
}
}
/**
* @return array
*/
public function configMapping() : array
{
return array(
\RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net')
->SetDefaultValue('*')
);
}
}

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013 RainLoop Team
Copyright (c) 2015 RainLoop Team
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View file

@ -0,0 +1,76 @@
<?php
use \RainLoop\Exceptions\ClientException;
class ChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{
const
NAME = 'Change Password',
CATEGORY = 'Security',
DESCRIPTION = '';
// \RainLoop\Notifications\
const CouldNotSaveNewPassword = 130;
const CurrentPasswordIncorrect = 131;
const NewPasswordShort = 132;
const NewPasswordWeak = 133;
public function Init() : void
{
$this->UseLangs(true); // start use langs folder
$this->addJs('js/ChangePasswordUserSettings.js'); // add js file
$this->addJsonHook('ChangePassword', 'ChangePassword');
$this->addTemplate('templates/SettingsChangePassword.html');
/**
* Admin
*/
/*
$this->addJs('js/ChangePasswordAdminSettings.js', true); // add js file
$this->addJsonHook('AdminChangePassword', 'AdminChangePassword');
$this->addTemplate('templates/ChangePasswordAdminSettings.html', true);
*/
}
public function ChangePassword()
{
$sPrevPassword = $this->jsonParam('PrevPassword');
$sNewPassword = $this->jsonParam('NewPassword');
$oActions = $this->Manager()->Actions();
$oAccount = $oActions->GetAccount();
/*
if (!$this->isPossible($oAccount)) {
throw new ClientException(static::CouldNotSaveNewPassword);
}
*/
if ($sPrevPassword !== $oAccount->Password()) {
throw new ClientException(static::CurrentPasswordIncorrect, null, $oActions->StaticI18N('NOTIFICATIONS/CURRENT_PASSWORD_INCORRECT'));
}
$sPasswordForCheck = \trim($sNewPassword);
if (10 > \strlen($sPasswordForCheck)) {
throw new ClientException(static::NewPasswordShort, null, $oActions->StaticI18N('NOTIFICATIONS/NEW_PASSWORD_SHORT'));
}
if (!\MailSo\Base\Utils::PasswordWeaknessCheck($sPasswordForCheck)) {
throw new ClientException(static::NewPasswordWeak, null, $oActions->StaticI18N('NOTIFICATIONS/NEW_PASSWORD_WEAK'));
}
/*
if (!$this->oDriver->ChangePassword($oAccount, $sPrevPassword, $sNewPassword)) {
throw new ClientException(static::CouldNotSaveNewPassword);
}
$oAccount->SetPassword($sNewPassword);
$oActions->SetAuthToken($oAccount);
*/
return $oActions->GetSpecAuthToken();
// return $this->jsonResponse(__FUNCTION__, $oActions->GetSpecAuthToken());
}
public function AdminChangePassword()
{
}
}

View file

@ -0,0 +1,110 @@
(rl => {
if (!rl)
{
return;
}
class ChangePasswordUserSettings
{
constructor() {
ko.addObservablesTo(this, {
changeProcess: false,
errorDescription: '',
passwordMismatch: false,
passwordUpdateError: false,
passwordUpdateSuccess: false,
currentPassword: '',
currentPasswordError: false,
newPassword: '',
newPassword2: '',
});
this.currentPassword.subscribe(() => this.resetUpdate(true));
this.newPassword.subscribe(() => this.resetUpdate());
this.newPassword2.subscribe(() => this.resetUpdate());
ko.decorateCommands(this, {
saveNewPasswordCommand: self => !self.changeProcess()
&& '' !== self.currentPassword()
&& '' !== self.newPassword()
&& '' !== self.newPassword2()
});
}
saveNewPasswordCommand() {
if (this.newPassword() !== this.newPassword2()) {
this.passwordMismatch(true);
this.errorDescription(rl.i18n('SETTINGS_CHANGE_PASSWORD/ERROR_PASSWORD_MISMATCH'));
} else {
this.reset(true);
rl.pluginRemoteRequest(
(...args) => {
console.dir(...args);
this.onChangePasswordResponse(...args);
},
'ChangePassword',
{
'PrevPassword': this.currentPassword(),
'NewPassword': this.newPassword()
}
);
}
}
reset(change) {
this.changeProcess(change);
this.resetUpdate();
this.currentPasswordError(false);
this.errorDescription('');
}
resetUpdate(current) {
this.passwordUpdateError(false);
this.passwordUpdateSuccess(false);
current ? this.currentPasswordError(false) : this.passwordMismatch(false);
}
onHide() {
this.reset(false);
this.currentPassword('');
this.newPassword('');
this.newPassword2('');
}
onChangePasswordResponse(result, data) {
this.reset(false);
if (rl.Enums.StorageResultType.Success === result && data && data.Result) {
this.currentPassword('');
this.newPassword('');
this.newPassword2('');
this.passwordUpdateSuccess(true);
rl.hash.set();
rl.settings.set('AuthAccountHash', data.Result);
} else {
this.passwordUpdateError(true);
this.errorDescription(rl.i18n('NOTIFICATIONS/COULD_NOT_SAVE_NEW_PASSWORD'));
if (data) {
if (131 === data.ErrorCode) {
// Notification.CurrentPasswordIncorrect
this.currentPasswordError(true);
}
if (data.ErrorMessageAdditional) {
this.errorDescription(data.ErrorMessageAdditional);
}
}
}
}
}
rl.addSettingsViewModel(
ChangePasswordUserSettings,
'SettingsChangePassword',
'GLOBAL/PASSWORD',
'change-password'
);
})(window.rl);

View file

@ -0,0 +1,12 @@
[SETTINGS_CHANGE_PASSWORD]
LEGEND_CHANGE_PASSWORD = "Change Password"
LABEL_CURRENT_PASSWORD = "Current password"
LABEL_NEW_PASSWORD = "New password"
LABEL_REPEAT_PASSWORD = "Confirm New Password"
BUTTON_UPDATE_PASSWORD = "Set New Password"
ERROR_PASSWORD_MISMATCH = "Passwords do not match, please try again"
[NOTIFICATIONS]
COULD_NOT_SAVE_NEW_PASSWORD = "Could not save new password"
CURRENT_PASSWORD_INCORRECT = "Current password incorrect"
NEW_PASSWORD_SHORT = "Password is too short"
NEW_PASSWORD_WEAK = "Password is too easy"

View file

@ -12,7 +12,6 @@ class Actions
use Actions\User;
use Actions\Raw;
use Actions\Response;
use Actions\ChangePassword;
const AUTH_TFA_SIGN_ME_TOKEN_KEY = 'rltfasmauth';
const AUTH_SIGN_ME_TOKEN_KEY = 'rlsmauth';
@ -1065,7 +1064,6 @@ class Actions
'StartupUrl' => \trim(\ltrim(\trim($oConfig->Get('labs', 'startup_url', '')), '#/')),
'SieveAllowFileintoInbox' => (bool)$oConfig->Get('labs', 'sieve_allow_fileinto_inbox', false),
'ContactsIsAllowed' => false,
'ChangePasswordIsAllowed' => false,
'RequireTwoFactor' => false,
'Admin' => array(),
'Capa' => array(),
@ -1117,7 +1115,6 @@ class Actions
$aResult['OutLogin'] = $oAccount->OutLogin();
$aResult['AccountHash'] = $oAccount->Hash();
$aResult['AccountSignMe'] = $oAccount->SignMe();
$aResult['ChangePasswordIsAllowed'] = $this->ChangePasswordProvider()->isPossible($oAccount);
$aResult['ContactsIsAllowed'] = $oAddressBookProvider->IsActive();
$aResult['ContactsSyncIsAllowed'] = (bool)$oConfig->Get('contacts', 'allow_sync', false);
$aResult['ContactsSyncInterval'] = (int)$oConfig->Get('contacts', 'sync_interval', 20);

View file

@ -13,11 +13,6 @@ class Notifications
const AccountTwoFactorAuthRequired = 120;
const AccountTwoFactorAuthError = 121;
const CouldNotSaveNewPassword = 130;
const CurrentPasswordIncorrect = 131;
const NewPasswordShort = 132;
const NewPasswordWeak = 133;
const ContactsSyncError = 140;
const CantGetMessageList = 201;

View file

@ -1,71 +0,0 @@
<?php
namespace RainLoop\Providers;
use \RainLoop\Model\Account;
use \RainLoop\Exceptions\ClientException;
use \RainLoop\Notifications;
class ChangePassword extends AbstractProvider
{
/**
* @var \RainLoop\Actions
*/
private $oActions;
/**
* @var \RainLoop\Providers\ChangePassword\ChangePasswordInterface
*/
private $oDriver;
/**
* @var bool
*/
private $bCheckWeak;
public function __construct(\RainLoop\Actions $oActions, ?ChangePassword\ChangePasswordInterface $oDriver = null, bool $bCheckWeak = true)
{
$this->oActions = $oActions;
$this->oDriver = $oDriver;
$this->bCheckWeak = $bCheckWeak;
}
public function isPossible(Account $oAccount) : bool
{
return $this->IsActive() && $this->oDriver->isPossible($oAccount);
}
public function ChangePassword(Account $oAccount, string $sPrevPassword, string $sNewPassword) : bool
{
if (!$this->isPossible($oAccount)) {
throw new ClientException(Notifications::CouldNotSaveNewPassword);
}
if ($sPrevPassword !== $oAccount->Password()) {
throw new ClientException(Notifications::CurrentPasswordIncorrect);
}
$sPasswordForCheck = \trim($sNewPassword);
if (10 > \strlen($sPasswordForCheck)) {
throw new ClientException(Notifications::NewPasswordShort);
}
if ($this->bCheckWeak && !\MailSo\Base\Utils::PasswordWeaknessCheck($sPasswordForCheck)) {
throw new ClientException(Notifications::NewPasswordWeak);
}
if (!$this->oDriver->ChangePassword($oAccount, $sPrevPassword, $sNewPassword)) {
throw new ClientException(Notifications::CouldNotSaveNewPassword);
}
$oAccount->SetPassword($sNewPassword);
$this->oActions->SetAuthToken($oAccount);
return $this->oActions->GetSpecAuthToken();
}
public function IsActive() : bool
{
return $this->oDriver instanceof ChangePassword\ChangePasswordInterface;
}
}

View file

@ -1,10 +0,0 @@
<?php
namespace RainLoop\Providers\ChangePassword;
interface ChangePasswordInterface
{
public function isPossible(\RainLoop\Model\Account $oAccount) : bool;
public function ChangePassword(\RainLoop\Model\Account $oAccount, string $sPrevPassword, string $sNewPassword) : bool;
}

View file

@ -111,6 +111,12 @@ class ServiceActions
if ($aPost)
{
$this->oActions->SetActionParams($aPost, $sMethodName);
foreach ($aPost as $key => $value) {
if (false !== \stripos($key, 'Password')) {
$aPost[$key] = '*******';
}
}
/*
switch ($sMethodName)
{
case 'DoLogin':
@ -118,12 +124,8 @@ class ServiceActions
case 'DoAccountAdd':
$this->Logger()->AddSecret($this->oActions->GetActionParam('Password', ''));
break;
case 'DoChangePassword':
$this->Logger()->AddSecret($this->oActions->GetActionParam('PrevPassword', ''));
$this->Logger()->AddSecret($this->oActions->GetActionParam('NewPassword', ''));
break;
}
*/
$this->Logger()->Write(\MailSo\Base\Utils::Php2js($aPost, $this->Logger()),
\MailSo\Log\Enumerations\Type::INFO, 'POST', true);
}