[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/components/com_users/src/Model/ -> ResetModel.php (source)

   1  <?php
   2  
   3  /**
   4   * @package     Joomla.Site
   5   * @subpackage  com_users
   6   *
   7   * @copyright   (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
   8   * @license     GNU General Public License version 2 or later; see LICENSE.txt
   9   */
  10  
  11  namespace Joomla\Component\Users\Site\Model;
  12  
  13  use Joomla\CMS\Application\ApplicationHelper;
  14  use Joomla\CMS\Factory;
  15  use Joomla\CMS\Form\Form;
  16  use Joomla\CMS\Language\Text;
  17  use Joomla\CMS\Log\Log;
  18  use Joomla\CMS\Mail\MailTemplate;
  19  use Joomla\CMS\MVC\Model\FormModel;
  20  use Joomla\CMS\Router\Route;
  21  use Joomla\CMS\String\PunycodeHelper;
  22  use Joomla\CMS\User\User;
  23  use Joomla\CMS\User\UserHelper;
  24  
  25  // phpcs:disable PSR1.Files.SideEffects
  26  \defined('_JEXEC') or die;
  27  // phpcs:enable PSR1.Files.SideEffects
  28  
  29  /**
  30   * Reset model class for Users.
  31   *
  32   * @since  1.5
  33   */
  34  class ResetModel extends FormModel
  35  {
  36      /**
  37       * Method to get the password reset request form.
  38       *
  39       * The base form is loaded from XML and then an event is fired
  40       * for users plugins to extend the form with extra fields.
  41       *
  42       * @param   array    $data      An optional array of data for the form to interrogate.
  43       * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
  44       *
  45       * @return  Form  A Form object on success, false on failure
  46       *
  47       * @since   1.6
  48       */
  49      public function getForm($data = array(), $loadData = true)
  50      {
  51          // Get the form.
  52          $form = $this->loadForm('com_users.reset_request', 'reset_request', array('control' => 'jform', 'load_data' => $loadData));
  53  
  54          if (empty($form)) {
  55              return false;
  56          }
  57  
  58          return $form;
  59      }
  60  
  61      /**
  62       * Method to get the password reset complete form.
  63       *
  64       * @param   array    $data      Data for the form.
  65       * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
  66       *
  67       * @return  Form    A Form object on success, false on failure
  68       *
  69       * @since   1.6
  70       */
  71      public function getResetCompleteForm($data = array(), $loadData = true)
  72      {
  73          // Get the form.
  74          $form = $this->loadForm('com_users.reset_complete', 'reset_complete', $options = array('control' => 'jform'));
  75  
  76          if (empty($form)) {
  77              return false;
  78          }
  79  
  80          return $form;
  81      }
  82  
  83      /**
  84       * Method to get the password reset confirm form.
  85       *
  86       * @param   array    $data      Data for the form.
  87       * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
  88       *
  89       * @return  Form  A Form object on success, false on failure
  90       *
  91       * @since   1.6
  92       * @throws  \Exception
  93       */
  94      public function getResetConfirmForm($data = array(), $loadData = true)
  95      {
  96          // Get the form.
  97          $form = $this->loadForm('com_users.reset_confirm', 'reset_confirm', $options = array('control' => 'jform'));
  98  
  99          if (empty($form)) {
 100              return false;
 101          } else {
 102              $form->setValue('token', '', Factory::getApplication()->input->get('token'));
 103          }
 104  
 105          return $form;
 106      }
 107  
 108      /**
 109       * Override preprocessForm to load the user plugin group instead of content.
 110       *
 111       * @param   Form    $form   A Form object.
 112       * @param   mixed   $data   The data expected for the form.
 113       * @param   string  $group  The name of the plugin group to import (defaults to "content").
 114       *
 115       * @return  void
 116       *
 117       * @throws  \Exception if there is an error in the form event.
 118       *
 119       * @since   1.6
 120       */
 121      protected function preprocessForm(Form $form, $data, $group = 'user')
 122      {
 123          parent::preprocessForm($form, $data, $group);
 124      }
 125  
 126      /**
 127       * Method to auto-populate the model state.
 128       *
 129       * Note. Calling getState in this method will result in recursion.
 130       *
 131       * @return  void
 132       *
 133       * @since   1.6
 134       * @throws  \Exception
 135       */
 136      protected function populateState()
 137      {
 138          // Get the application object.
 139          $params = Factory::getApplication()->getParams('com_users');
 140  
 141          // Load the parameters.
 142          $this->setState('params', $params);
 143      }
 144  
 145      /**
 146       * Save the new password after reset is done
 147       *
 148       * @param   array  $data  The data expected for the form.
 149       *
 150       * @return  mixed  \Exception | boolean
 151       *
 152       * @since   1.6
 153       * @throws  \Exception
 154       */
 155      public function processResetComplete($data)
 156      {
 157          // Get the form.
 158          $form = $this->getResetCompleteForm();
 159  
 160          // Check for an error.
 161          if ($form instanceof \Exception) {
 162              return $form;
 163          }
 164  
 165          // Filter and validate the form data.
 166          $data = $form->filter($data);
 167          $return = $form->validate($data);
 168  
 169          // Check for an error.
 170          if ($return instanceof \Exception) {
 171              return $return;
 172          }
 173  
 174          // Check the validation results.
 175          if ($return === false) {
 176              // Get the validation messages from the form.
 177              foreach ($form->getErrors() as $formError) {
 178                  $this->setError($formError->getMessage());
 179              }
 180  
 181              return false;
 182          }
 183  
 184          // Get the token and user id from the confirmation process.
 185          $app = Factory::getApplication();
 186          $token = $app->getUserState('com_users.reset.token', null);
 187          $userId = $app->getUserState('com_users.reset.user', null);
 188  
 189          // Check the token and user id.
 190          if (empty($token) || empty($userId)) {
 191              return new \Exception(Text::_('COM_USERS_RESET_COMPLETE_TOKENS_MISSING'), 403);
 192          }
 193  
 194          // Get the user object.
 195          $user = User::getInstance($userId);
 196  
 197          // Check for a user and that the tokens match.
 198          if (empty($user) || $user->activation !== $token) {
 199              $this->setError(Text::_('COM_USERS_USER_NOT_FOUND'));
 200  
 201              return false;
 202          }
 203  
 204          // Make sure the user isn't blocked.
 205          if ($user->block) {
 206              $this->setError(Text::_('COM_USERS_USER_BLOCKED'));
 207  
 208              return false;
 209          }
 210  
 211          // Check if the user is reusing the current password if required to reset their password
 212          if ($user->requireReset == 1 && UserHelper::verifyPassword($data['password1'], $user->password)) {
 213              $this->setError(Text::_('JLIB_USER_ERROR_CANNOT_REUSE_PASSWORD'));
 214  
 215              return false;
 216          }
 217  
 218          // Prepare user data.
 219          $data['password']   = $data['password1'];
 220          $data['activation'] = '';
 221  
 222          // Update the user object.
 223          if (!$user->bind($data)) {
 224              return new \Exception($user->getError(), 500);
 225          }
 226  
 227          // Save the user to the database.
 228          if (!$user->save(true)) {
 229              return new \Exception(Text::sprintf('COM_USERS_USER_SAVE_FAILED', $user->getError()), 500);
 230          }
 231  
 232          // Destroy all active sessions for the user
 233          UserHelper::destroyUserSessions($user->id);
 234  
 235          // Flush the user data from the session.
 236          $app->setUserState('com_users.reset.token', null);
 237          $app->setUserState('com_users.reset.user', null);
 238  
 239          return true;
 240      }
 241  
 242      /**
 243       * Receive the reset password request
 244       *
 245       * @param   array  $data  The data expected for the form.
 246       *
 247       * @return  mixed  \Exception | boolean
 248       *
 249       * @since   1.6
 250       * @throws  \Exception
 251       */
 252      public function processResetConfirm($data)
 253      {
 254          // Get the form.
 255          $form = $this->getResetConfirmForm();
 256  
 257          // Check for an error.
 258          if ($form instanceof \Exception) {
 259              return $form;
 260          }
 261  
 262          // Filter and validate the form data.
 263          $data = $form->filter($data);
 264          $return = $form->validate($data);
 265  
 266          // Check for an error.
 267          if ($return instanceof \Exception) {
 268              return $return;
 269          }
 270  
 271          // Check the validation results.
 272          if ($return === false) {
 273              // Get the validation messages from the form.
 274              foreach ($form->getErrors() as $formError) {
 275                  $this->setError($formError->getMessage());
 276              }
 277  
 278              return false;
 279          }
 280  
 281          // Find the user id for the given token.
 282          $db = $this->getDatabase();
 283          $query = $db->getQuery(true)
 284              ->select($db->quoteName(['activation', 'id', 'block']))
 285              ->from($db->quoteName('#__users'))
 286              ->where($db->quoteName('username') . ' = :username')
 287              ->bind(':username', $data['username']);
 288  
 289          // Get the user id.
 290          $db->setQuery($query);
 291  
 292          try {
 293              $user = $db->loadObject();
 294          } catch (\RuntimeException $e) {
 295              return new \Exception(Text::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage()), 500);
 296          }
 297  
 298          // Check for a user.
 299          if (empty($user)) {
 300              $this->setError(Text::_('COM_USERS_USER_NOT_FOUND'));
 301  
 302              return false;
 303          }
 304  
 305          if (!$user->activation) {
 306              $this->setError(Text::_('COM_USERS_USER_NOT_FOUND'));
 307  
 308              return false;
 309          }
 310  
 311          // Verify the token
 312          if (!UserHelper::verifyPassword($data['token'], $user->activation)) {
 313              $this->setError(Text::_('COM_USERS_USER_NOT_FOUND'));
 314  
 315              return false;
 316          }
 317  
 318          // Make sure the user isn't blocked.
 319          if ($user->block) {
 320              $this->setError(Text::_('COM_USERS_USER_BLOCKED'));
 321  
 322              return false;
 323          }
 324  
 325          // Push the user data into the session.
 326          $app = Factory::getApplication();
 327          $app->setUserState('com_users.reset.token', $user->activation);
 328          $app->setUserState('com_users.reset.user', $user->id);
 329  
 330          return true;
 331      }
 332  
 333      /**
 334       * Method to start the password reset process.
 335       *
 336       * @param   array  $data  The data expected for the form.
 337       *
 338       * @return  mixed  \Exception | boolean
 339       *
 340       * @since   1.6
 341       * @throws  \Exception
 342       */
 343      public function processResetRequest($data)
 344      {
 345          $app = Factory::getApplication();
 346  
 347          // Get the form.
 348          $form = $this->getForm();
 349  
 350          $data['email'] = PunycodeHelper::emailToPunycode($data['email']);
 351  
 352          // Check for an error.
 353          if ($form instanceof \Exception) {
 354              return $form;
 355          }
 356  
 357          // Filter and validate the form data.
 358          $data = $form->filter($data);
 359          $return = $form->validate($data);
 360  
 361          // Check for an error.
 362          if ($return instanceof \Exception) {
 363              return $return;
 364          }
 365  
 366          // Check the validation results.
 367          if ($return === false) {
 368              // Get the validation messages from the form.
 369              foreach ($form->getErrors() as $formError) {
 370                  $this->setError($formError->getMessage());
 371              }
 372  
 373              return false;
 374          }
 375  
 376          // Find the user id for the given email address.
 377          $db = $this->getDatabase();
 378          $query = $db->getQuery(true)
 379              ->select($db->quoteName('id'))
 380              ->from($db->quoteName('#__users'))
 381              ->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
 382              ->bind(':email', $data['email']);
 383  
 384          // Get the user object.
 385          $db->setQuery($query);
 386  
 387          try {
 388              $userId = $db->loadResult();
 389          } catch (\RuntimeException $e) {
 390              $this->setError(Text::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage()));
 391  
 392              return false;
 393          }
 394  
 395          // Check for a user.
 396          if (empty($userId)) {
 397              $this->setError(Text::_('COM_USERS_INVALID_EMAIL'));
 398  
 399              return false;
 400          }
 401  
 402          // Get the user object.
 403          $user = User::getInstance($userId);
 404  
 405          // Make sure the user isn't blocked.
 406          if ($user->block) {
 407              $this->setError(Text::_('COM_USERS_USER_BLOCKED'));
 408  
 409              return false;
 410          }
 411  
 412          // Make sure the user isn't a Super Admin.
 413          if ($user->authorise('core.admin')) {
 414              $this->setError(Text::_('COM_USERS_REMIND_SUPERADMIN_ERROR'));
 415  
 416              return false;
 417          }
 418  
 419          // Make sure the user has not exceeded the reset limit
 420          if (!$this->checkResetLimit($user)) {
 421              $resetLimit = (int) Factory::getApplication()->getParams()->get('reset_time');
 422              $this->setError(Text::plural('COM_USERS_REMIND_LIMIT_ERROR_N_HOURS', $resetLimit));
 423  
 424              return false;
 425          }
 426  
 427          // Set the confirmation token.
 428          $token = ApplicationHelper::getHash(UserHelper::genRandomPassword());
 429          $hashedToken = UserHelper::hashPassword($token);
 430  
 431          $user->activation = $hashedToken;
 432  
 433          // Save the user to the database.
 434          if (!$user->save(true)) {
 435              return new \Exception(Text::sprintf('COM_USERS_USER_SAVE_FAILED', $user->getError()), 500);
 436          }
 437  
 438          // Assemble the password reset confirmation link.
 439          $mode = $app->get('force_ssl', 0) == 2 ? 1 : (-1);
 440          $link = 'index.php?option=com_users&view=reset&layout=confirm&token=' . $token;
 441  
 442          // Put together the email template data.
 443          $data = $user->getProperties();
 444          $data['sitename'] = $app->get('sitename');
 445          $data['link_text'] = Route::_($link, false, $mode);
 446          $data['link_html'] = Route::_($link, true, $mode);
 447          $data['token'] = $token;
 448  
 449          $mailer = new MailTemplate('com_users.password_reset', $app->getLanguage()->getTag());
 450          $mailer->addTemplateData($data);
 451          $mailer->addRecipient($user->email, $user->name);
 452  
 453          // Try to send the password reset request email.
 454          try {
 455              $return = $mailer->send();
 456          } catch (\Exception $exception) {
 457              try {
 458                  Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');
 459  
 460                  $return = false;
 461              } catch (\RuntimeException $exception) {
 462                  Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning');
 463  
 464                  $return = false;
 465              }
 466          }
 467  
 468          // Check for an error.
 469          if ($return !== true) {
 470              return new \Exception(Text::_('COM_USERS_MAIL_FAILED'), 500);
 471          } else {
 472              return true;
 473          }
 474      }
 475  
 476      /**
 477       * Method to check if user reset limit has been exceeded within the allowed time period.
 478       *
 479       * @param   User  $user  User doing the password reset
 480       *
 481       * @return  boolean true if user can do the reset, false if limit exceeded
 482       *
 483       * @since    2.5
 484       * @throws  \Exception
 485       */
 486      public function checkResetLimit($user)
 487      {
 488          $params = Factory::getApplication()->getParams();
 489          $maxCount = (int) $params->get('reset_count');
 490          $resetHours = (int) $params->get('reset_time');
 491          $result = true;
 492  
 493          $lastResetTime = strtotime($user->lastResetTime) ?: 0;
 494          $hoursSinceLastReset = (strtotime(Factory::getDate()->toSql()) - $lastResetTime) / 3600;
 495  
 496          if ($hoursSinceLastReset > $resetHours) {
 497              // If it's been long enough, start a new reset count
 498              $user->lastResetTime = Factory::getDate()->toSql();
 499              $user->resetCount = 1;
 500          } elseif ($user->resetCount < $maxCount) {
 501              // If we are under the max count, just increment the counter
 502              ++$user->resetCount;
 503          } else {
 504              // At this point, we know we have exceeded the maximum resets for the time period
 505              $result = false;
 506          }
 507  
 508          return $result;
 509      }
 510  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer