From f20d31e956efa728e3a478c2ccbed41c38f7004a Mon Sep 17 00:00:00 2001 From: sander Date: Fri, 5 Mar 2021 11:33:51 +0100 Subject: [PATCH] Added option to let user generate password via email Tweaks from feedback and phpstorm hints --- core/lexicon/en/user.inc.php | 3 + .../Processors/Security/User/Create.php | 51 ++++++++++++ .../Processors/Security/User/Update.php | 82 +++++++++++++++++-- .../Processors/Security/User/Validation.php | 6 +- .../widgets/security/modx.panel.user.js | 7 ++ 5 files changed, 139 insertions(+), 10 deletions(-) diff --git a/core/lexicon/en/user.inc.php b/core/lexicon/en/user.inc.php index 63d2d6828c0..0829e697d47 100644 --- a/core/lexicon/en/user.inc.php +++ b/core/lexicon/en/user.inc.php @@ -25,6 +25,7 @@ $_lang['password_gen_specify'] = 'Let me specify the password:'; $_lang['password_method'] = 'Password notification method'; $_lang['password_method_screen'] = 'Show the new password on screen.'; +$_lang['password_gen_user_email_specify'] = 'Let the user choose their own password via email'; $_lang['notify_new_user'] = 'Email this user about their new login for this website.'; $_lang['password_new'] = 'New Password'; $_lang['password_notification'] = 'Password Notification'; @@ -196,3 +197,5 @@ $_lang['users'] = 'Users'; $_lang['user_createdon'] = 'Created On'; $_lang['user_createdon_desc'] = 'The date the user was created.'; +$_lang['user_password_email_subject'] = 'Set up your password'; +$_lang['user_password_email'] = '

Set up your password

We received a request to set up your MODX Revolution password. You can set up your password by clicking the button below and following the instructions on screen.

Set up my password

If you did not send this request, please ignore this email.

'; diff --git a/core/src/Revolution/Processors/Security/User/Create.php b/core/src/Revolution/Processors/Security/User/Create.php index b2bf95835b6..91063bb3dbb 100644 --- a/core/src/Revolution/Processors/Security/User/Create.php +++ b/core/src/Revolution/Processors/Security/User/Create.php @@ -12,6 +12,7 @@ use Exception; +use MODX\Revolution\Hashing\modHashing; use MODX\Revolution\Processors\Model\CreateProcessor; use MODX\Revolution\Processors\Processor; use MODX\Revolution\modUser; @@ -19,6 +20,8 @@ use MODX\Revolution\modUserGroupMember; use MODX\Revolution\modUserProfile; use MODX\Revolution\modX; +use MODX\Revolution\Registry\modRegister; +use MODX\Revolution\Registry\modRegistry; use MODX\Revolution\Smarty\modSmarty; /** @@ -232,6 +235,54 @@ public function sendNotificationEmail() { 'html' => true, ]); } + + if ( + $this->getProperty('passwordgenmethod') === 'user_email_specify' + ) { + $activationHash = bin2hex(random_bytes(32)); + + /** @var modRegistry $registry */ + $registry = $this->modx->getService('registry', 'registry.modRegistry'); + /** @var modRegister $register */ + $register = $registry->getRegister('user', 'registry.modDbRegister'); + $register->connect(); + $register->subscribe('/pwd/change/'); + $register->send('/pwd/change/', [$activationHash => $this->object->get('username')], ['ttl' => 86400]); + + // Send activation email + $message = $this->modx->lexicon('user_password_email'); + $placeholders = array_merge($this->modx->config, $this->object->toArray()); + $placeholders['hash'] = $activationHash; + + // Store previous placeholders + $ph = $this->modx->placeholders; + // now set those useful for modParser + $this->modx->setPlaceholders($placeholders); + $this->modx->getParser()->processElementTags('', $message, true, false, '[[', ']]', [], 10); + $this->modx->getParser()->processElementTags('', $message, true, true, '[[', ']]', [], 10); + // Then restore previous placeholders to prevent any breakage + $this->modx->placeholders = $ph; + + $this->modx->getService('smarty', 'smarty.modSmarty', '', ['template_dir' => $this->modx->getOption('manager_path') . 'templates/default/']); + + $this->modx->smarty->assign('_config', $this->modx->config); + $this->modx->smarty->assign('content', $message, true); + + $sent = $this->object->sendEmail( + $this->modx->smarty->fetch('email/default.tpl'), + [ + 'from' => $this->modx->getOption('emailsender'), + 'fromName' => $this->modx->getOption('site_name'), + 'sender' => $this->modx->getOption('emailsender'), + 'subject' => $this->modx->lexicon('user_password_email_subject'), + 'html' => true, + ] + ); + + if (!$sent) { + return $this->failure($this->modx->lexicon('error_sending_email_to') . $this->object->get('email')); + } + } } /** diff --git a/core/src/Revolution/Processors/Security/User/Update.php b/core/src/Revolution/Processors/Security/User/Update.php index 02e2cf4e761..2e6419728cd 100644 --- a/core/src/Revolution/Processors/Security/User/Update.php +++ b/core/src/Revolution/Processors/Security/User/Update.php @@ -11,6 +11,7 @@ namespace MODX\Revolution\Processors\Security\User; +use MODX\Revolution\Hashing\modHashing; use MODX\Revolution\Processors\Model\UpdateProcessor; use MODX\Revolution\Processors\Processor; use MODX\Revolution\modSystemEvent; @@ -19,6 +20,8 @@ use MODX\Revolution\modUserGroupMember; use MODX\Revolution\modUserProfile; use MODX\Revolution\modX; +use MODX\Revolution\Registry\modRegister; +use MODX\Revolution\Registry\modRegistry; /** * Update a user. @@ -285,6 +288,7 @@ public function setUserGroups() { */ public function afterSave() { $this->setUserGroups(); + $this->sendNotificationEmail(); if ($this->activeStatusChanged) { $this->fireAfterActiveStatusChange(); } @@ -305,6 +309,62 @@ public function fireAfterActiveStatusChange() { ); } + /** + * Send the password notification email, if specified + * + * @return void + * @throws Exception + */ + public function sendNotificationEmail() { + if ($this->getProperty('passwordgenmethod') === 'user_email_specify') { + $activationHash = bin2hex(random_bytes(32)); + + /** @var modRegistry $registry */ + $registry = $this->modx->getService('registry', 'registry.modRegistry'); + /** @var modRegister $register */ + $register = $registry->getRegister('user', 'registry.modDbRegister'); + $register->connect(); + $register->subscribe('/pwd/change/'); + $register->send('/pwd/change/', [$activationHash => $this->object->get('username')], ['ttl' => 86400]); + + $this->modx->lexicon->load('core:login'); + + // Send activation email + $message = $this->modx->lexicon('user_password_email'); + $placeholders = array_merge($this->modx->config, $this->object->toArray()); + $placeholders['hash'] = $activationHash; + + // Store previous placeholders + $ph = $this->modx->placeholders; + // now set those useful for modParser + $this->modx->setPlaceholders($placeholders); + $this->modx->getParser()->processElementTags('', $message, true, false, '[[', ']]', [], 10); + $this->modx->getParser()->processElementTags('', $message, true, true, '[[', ']]', [], 10); + // Then restore previous placeholders to prevent any breakage + $this->modx->placeholders = $ph; + + $this->modx->getService('smarty', 'smarty.modSmarty', '', ['template_dir' => $this->modx->getOption('manager_path') . 'templates/default/']); + + $this->modx->smarty->assign('_config', $this->modx->config); + $this->modx->smarty->assign('content', $message, true); + + $sent = $this->object->sendEmail( + $this->modx->smarty->fetch('email/default.tpl'), + [ + 'from' => $this->modx->getOption('emailsender'), + 'fromName' => $this->modx->getOption('site_name'), + 'sender' => $this->modx->getOption('emailsender'), + 'subject' => $this->modx->lexicon('user_password_email_subject'), + 'html' => true, + ] + ); + + if (!$sent) { + return $this->failure($this->modx->lexicon('error_sending_email_to') . $this->object->get('email')); + } + } + } + /** * {@inheritDoc} * @return array|string @@ -319,14 +379,20 @@ public function cleanup() unset($userArray['password'], $userArray['cachepwd'], $userArray['sessionid'], $userArray['salt']); $passwordNotifyMethod = $this->getProperty('passwordnotifymethod'); - if (!empty($passwordNotifyMethod) && !empty($this->newPassword) && $passwordNotifyMethod == 's') { - return $this->success($this->modx->lexicon('user_updated_password_message', - [ - 'password' => $this->newPassword, - ] - ), $this->object); - } else { - return $this->success('',$this->object); + if ( + !empty($passwordNotifyMethod) + && !empty($this->newPassword) + && $passwordNotifyMethod === 's' + ) { + return $this->success( + $this->modx->lexicon('user_updated_password_message', + array( + 'password' => $this->newPassword, + ) + ), + $this->object + ); } + return $this->success('', $this->object); } } diff --git a/core/src/Revolution/Processors/Security/User/Validation.php b/core/src/Revolution/Processors/Security/User/Validation.php index daa34885759..bb789ef763d 100644 --- a/core/src/Revolution/Processors/Security/User/Validation.php +++ b/core/src/Revolution/Processors/Security/User/Validation.php @@ -76,12 +76,14 @@ public function alreadyExists($name) { public function checkPassword() { $newPassword = $this->processor->getProperty('newpassword',null); $id = $this->processor->getProperty('id'); - if ($newPassword !== null && $newPassword != 'false' || empty($id)) { + + $passwordGenerationMethod = $this->processor->getProperty('passwordgenmethod','g'); + if ($passwordGenerationMethod !== 'user_email_specify' && ($newPassword !== null && $newPassword != 'false' || empty($id))) { $passwordNotifyMethod = $this->processor->getProperty('passwordnotifymethod',null); if (empty($passwordNotifyMethod)) { $this->processor->addFieldError('password_notify_method',$this->modx->lexicon('user_err_not_specified_notification_method')); } - $passwordGenerationMethod = $this->processor->getProperty('passwordgenmethod','g'); + if ($passwordGenerationMethod == 'g') { $autoPassword = $this->user->generatePassword(); $this->user->set('password', $autoPassword); diff --git a/manager/assets/modext/widgets/security/modx.panel.user.js b/manager/assets/modext/widgets/security/modx.panel.user.js index de41d251cb1..24c8ee737fe 100644 --- a/manager/assets/modext/widgets/security/modx.panel.user.js +++ b/manager/assets/modext/widgets/security/modx.panel.user.js @@ -483,6 +483,13 @@ Ext.extend(MODx.panel.User,MODx.FormPanel,{ ,xtype: 'radio' ,inputValue: 'spec' ,value: 'spec' + },{ + id: 'modx-user-password-genmethod-user-email-specify' + ,name: 'passwordgenmethod' + ,boxLabel: _('password_gen_user_email_specify') + ,xtype: 'radio' + ,inputValue: 'user_email_specify' + ,value: 'user_email_specify' }] },{ id: 'modx-user-panel-newpassword'