* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ use PrestaShop\PrestaShop\Core\Security\PasswordPolicyConfiguration; use PrestaShop\PrestaShop\Core\Util\InternationalizedDomainNameConverter; class PasswordControllerCore extends FrontController { /** @var string */ public $php_self = 'password'; /** @var bool */ public $auth = false; /** @var bool */ public $ssl = true; /** * @var InternationalizedDomainNameConverter */ private $IDNConverter; public function __construct() { parent::__construct(); $this->IDNConverter = new InternationalizedDomainNameConverter(); } /** * Start forms process. * * @see FrontController::postProcess() */ public function postProcess() { $this->setTemplate('customer/password-email'); if (Tools::isSubmit('email')) { $this->sendRenewPasswordLink(); } elseif (Tools::getValue('token') && ($id_customer = (int) Tools::getValue('id_customer'))) { $this->changePassword(); } elseif (Tools::getValue('token') || Tools::getValue('id_customer')) { $this->errors[] = $this->trans('We cannot regenerate your password with the data you\'ve submitted', [], 'Shop.Notifications.Error'); } } protected function sendRenewPasswordLink() { if (!($email = $this->IDNConverter->emailToUtf8(trim(Tools::getValue('email')))) || !Validate::isEmail($email)) { $this->errors[] = $this->trans('Invalid email address.', [], 'Shop.Notifications.Error'); } else { $customer = new Customer(); $customer->getByEmail($email); if (null === $customer->email) { $customer->email = Tools::getValue('email'); } if (!Validate::isLoadedObject($customer)) { $this->success[] = $this->trans( 'If this email address has been registered in our store, you will receive a link to reset your password at %email%.', ['%email%' => $customer->email], 'Shop.Notifications.Success' ); $this->setTemplate('customer/password-infos'); } elseif (!$customer->active) { $this->errors[] = $this->trans('You cannot regenerate the password for this account.', [], 'Shop.Notifications.Error'); } elseif ((strtotime($customer->last_passwd_gen . '+' . ($minTime = (int) Configuration::get('PS_PASSWD_TIME_FRONT')) . ' minutes') - time()) > 0) { $this->errors[] = $this->trans('You can regenerate your password only every %d minute(s)', [(int) $minTime], 'Shop.Notifications.Error'); } else { if (!$customer->hasRecentResetPasswordToken()) { $customer->stampResetPasswordToken(); $customer->update(); } $mailParams = [ '{email}' => $customer->email, '{lastname}' => $customer->lastname, '{firstname}' => $customer->firstname, '{url}' => $this->context->link->getPageLink('password', null, null, 'token=' . $customer->secure_key . '&id_customer=' . (int) $customer->id . '&reset_token=' . $customer->reset_password_token), ]; if ( Mail::Send( $this->context->language->id, 'password_query', $this->trans( 'Password query confirmation', [], 'Emails.Subject' ), $mailParams, $customer->email, $customer->firstname . ' ' . $customer->lastname ) ) { $this->success[] = $this->trans('If this email address has been registered in our store, you will receive a link to reset your password at %email%.', ['%email%' => $customer->email], 'Shop.Notifications.Success'); $this->setTemplate('customer/password-infos'); } else { $this->errors[] = $this->trans('An error occurred while sending the email.', [], 'Shop.Notifications.Error'); } } } } protected function changePassword() { $token = Tools::getValue('token'); $id_customer = (int) Tools::getValue('id_customer'); $reset_token = Tools::getValue('reset_token'); $email = Db::getInstance()->getValue( 'SELECT `email` FROM ' . _DB_PREFIX_ . 'customer c WHERE c.`secure_key` = \'' . pSQL($token) . '\' AND c.id_customer = ' . $id_customer ); if ($email) { $customer = new Customer(); $customer->getByEmail($email); if (!Validate::isLoadedObject($customer)) { $this->errors[] = $this->trans('Customer account not found', [], 'Shop.Notifications.Error'); } elseif (!$customer->active) { $this->errors[] = $this->trans('You cannot regenerate the password for this account.', [], 'Shop.Notifications.Error'); } elseif ($customer->getValidResetPasswordToken() !== $reset_token) { $this->errors[] = $this->trans('The password change request expired. You should ask for a new one.', [], 'Shop.Notifications.Error'); } if ($this->errors) { return; } if ($isSubmit = Tools::isSubmit('passwd')) { // If password is submitted validate pass and confirmation if (!$passwd = Tools::getValue('passwd')) { $this->errors[] = $this->trans('The password is missing: please enter your new password.', [], 'Shop.Notifications.Error'); } if (!$confirmation = Tools::getValue('confirmation')) { $this->errors[] = $this->trans('The confirmation is empty: please fill in the password confirmation as well', [], 'Shop.Notifications.Error'); } if ($passwd && $confirmation) { if ($passwd !== $confirmation) { $this->errors[] = $this->trans('The confirmation password doesn\'t match.', [], 'Shop.Notifications.Error'); } if (!Validate::isPlaintextPassword($passwd)) { $this->errors[] = $this->trans('The password is not in a valid format.', [], 'Shop.Notifications.Error'); } } if (Validate::isAcceptablePasswordLength($passwd) === false) { $this->errors[] = $this->translator->trans( 'Password must be between %d and %d characters long', [ Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MINIMUM_LENGTH), Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MAXIMUM_LENGTH), ], 'Shop.Notifications.Error' ); } if (Validate::isAcceptablePasswordScore($passwd) === false) { $wordingsForScore = [ $this->translator->trans('Very weak', [], 'Shop.Theme.Global'), $this->translator->trans('Weak', [], 'Shop.Theme.Global'), $this->translator->trans('Average', [], 'Shop.Theme.Global'), $this->translator->trans('Strong', [], 'Shop.Theme.Global'), $this->translator->trans('Very strong', [], 'Shop.Theme.Global'), ]; $this->errors[] = $this->translator->trans( 'The minimum score must be: %s', [ $wordingsForScore[(int) Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MINIMUM_SCORE)], ], 'Shop.Notifications.Error' ); } } if (!$isSubmit || $this->errors) { // If password is NOT submitted OR there are errors, shows the form (and errors) $this->context->smarty->assign([ 'customer_email' => $customer->email, 'customer_token' => $token, 'id_customer' => $id_customer, 'reset_token' => $reset_token, ]); $this->setTemplate('customer/password-new'); } else { // Both password fields posted. Check if all is right and store new password properly. if (!$reset_token || (strtotime($customer->last_passwd_gen . '+' . (int) Configuration::get('PS_PASSWD_TIME_FRONT') . ' minutes') - time()) > 0) { Tools::redirect('index.php?controller=authentication&error_regen_pwd'); } else { $customer->passwd = $this->get('hashing')->hash($password = Tools::getValue('passwd'), _COOKIE_KEY_); $customer->last_passwd_gen = date('Y-m-d H:i:s', time()); if ($customer->update()) { Hook::exec('actionPasswordRenew', ['customer' => $customer, 'password' => $password]); $customer->removeResetPasswordToken(); $customer->update(); $mail_params = [ '{email}' => $customer->email, '{lastname}' => $customer->lastname, '{firstname}' => $customer->firstname, ]; if ( Mail::Send( $this->context->language->id, 'password', $this->trans( 'Your new password', [], 'Emails.Subject' ), $mail_params, $customer->email, $customer->firstname . ' ' . $customer->lastname ) ) { $this->context->smarty->assign([ 'customer_email' => $customer->email, ]); $this->success[] = $this->trans('Your password has been successfully reset and a confirmation has been sent to your email address: %s', [$customer->email], 'Shop.Notifications.Success'); $this->context->updateCustomer($customer); $this->redirectWithNotifications('index.php?controller=my-account'); } else { $this->errors[] = $this->trans('An error occurred while sending the email.', [], 'Shop.Notifications.Error'); } } else { $this->errors[] = $this->trans('An error occurred with your account, which prevents us from updating the new password. Please report this issue using the contact form.', [], 'Shop.Notifications.Error'); } } } } else { $this->errors[] = $this->trans('We cannot regenerate your password with the data you\'ve submitted', [], 'Shop.Notifications.Error'); } } /** * @return bool */ public function display() { $this->context->smarty->assign( [ 'layout' => $this->getLayout(), 'stylesheets' => $this->getStylesheets(), 'javascript' => $this->getJavascript(), 'js_custom_vars' => Media::getJsDef(), 'errors' => $this->getErrors(), 'successes' => $this->getSuccesses(), ] ); $this->smartyOutputContent($this->template); return true; } /** * @return array */ protected function getErrors() { $notifications = $this->prepareNotifications(); $errors = []; if (array_key_exists('error', $notifications)) { $errors = $notifications['error']; } return $errors; } /** * @return array */ protected function getSuccesses() { $notifications = $this->prepareNotifications(); $successes = []; if (array_key_exists('success', $notifications)) { $successes = $notifications['success']; } return $successes; } public function getBreadcrumbLinks() { $breadcrumb = parent::getBreadcrumbLinks(); $breadcrumb['links'][] = [ 'title' => $this->trans('Reset your password', [], 'Shop.Theme.Customeraccount'), 'url' => $this->context->link->getPageLink('password'), ]; return $breadcrumb; } /** * {@inheritdoc} */ public function getCanonicalURL() { return $this->context->link->getPageLink('password'); } }