[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/User/ -> UserHelper.php (source)

   1  <?php
   2  
   3  /**
   4   * Joomla! Content Management System
   5   *
   6   * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
   7   * @license    GNU General Public License version 2 or later; see LICENSE.txt
   8   */
   9  
  10  namespace Joomla\CMS\User;
  11  
  12  use Joomla\Authentication\Password\Argon2idHandler;
  13  use Joomla\Authentication\Password\Argon2iHandler;
  14  use Joomla\Authentication\Password\BCryptHandler;
  15  use Joomla\CMS\Access\Access;
  16  use Joomla\CMS\Authentication\Password\ChainedHandler;
  17  use Joomla\CMS\Authentication\Password\CheckIfRehashNeededHandlerInterface;
  18  use Joomla\CMS\Authentication\Password\MD5Handler;
  19  use Joomla\CMS\Authentication\Password\PHPassHandler;
  20  use Joomla\CMS\Crypt\Crypt;
  21  use Joomla\CMS\Factory;
  22  use Joomla\CMS\Language\Text;
  23  use Joomla\CMS\Log\Log;
  24  use Joomla\CMS\Object\CMSObject;
  25  use Joomla\CMS\Plugin\PluginHelper;
  26  use Joomla\CMS\Session\SessionManager;
  27  use Joomla\CMS\Uri\Uri;
  28  use Joomla\Database\Exception\ExecutionFailureException;
  29  use Joomla\Database\ParameterType;
  30  use Joomla\Utilities\ArrayHelper;
  31  
  32  // phpcs:disable PSR1.Files.SideEffects
  33  \defined('JPATH_PLATFORM') or die;
  34  // phpcs:enable PSR1.Files.SideEffects
  35  
  36  /**
  37   * Authorisation helper class, provides static methods to perform various tasks relevant
  38   * to the Joomla user and authorisation classes
  39   *
  40   * This class has influences and some method logic from the Horde Auth package
  41   *
  42   * @since  1.7.0
  43   */
  44  abstract class UserHelper
  45  {
  46      /**
  47       * Constant defining the Argon2i password algorithm for use with password hashes
  48       *
  49       * Note: PHP's native `PASSWORD_ARGON2I` constant is not used as PHP may be compiled without this constant
  50       *
  51       * @var    string
  52       * @since  4.0.0
  53       */
  54      public const HASH_ARGON2I = 'argon2i';
  55  
  56      /**
  57       * B/C constant `PASSWORD_ARGON2I` for PHP < 7.4 (using integer)
  58       *
  59       * Note: PHP's native `PASSWORD_ARGON2I` constant is not used as PHP may be compiled without this constant
  60       *
  61       * @var    integer
  62       * @since  4.0.0
  63       * @deprecated 4.0.0  Use self::HASH_ARGON2I instead
  64       */
  65      public const HASH_ARGON2I_BC = 2;
  66  
  67      /**
  68       * Constant defining the Argon2id password algorithm for use with password hashes
  69       *
  70       * Note: PHP's native `PASSWORD_ARGON2ID` constant is not used as PHP may be compiled without this constant
  71       *
  72       * @var    string
  73       * @since  4.0.0
  74       */
  75      public const HASH_ARGON2ID = 'argon2id';
  76  
  77      /**
  78       * B/C constant `PASSWORD_ARGON2ID` for PHP < 7.4 (using integer)
  79       *
  80       * Note: PHP's native `PASSWORD_ARGON2ID` constant is not used as PHP may be compiled without this constant
  81       *
  82       * @var    integer
  83       * @since  4.0.0
  84       * @deprecated  4.0.0  Use self::HASH_ARGON2ID instead
  85       */
  86      public const HASH_ARGON2ID_BC = 3;
  87  
  88      /**
  89       * Constant defining the BCrypt password algorithm for use with password hashes
  90       *
  91       * @var    string
  92       * @since  4.0.0
  93       */
  94      public const HASH_BCRYPT = '2y';
  95  
  96      /**
  97       * B/C constant `PASSWORD_BCRYPT` for PHP < 7.4 (using integer)
  98       *
  99       * @var    integer
 100       * @since  4.0.0
 101       * @deprecated  4.0.0  Use self::HASH_BCRYPT instead
 102       */
 103      public const HASH_BCRYPT_BC = 1;
 104  
 105      /**
 106       * Constant defining the MD5 password algorithm for use with password hashes
 107       *
 108       * @var    string
 109       * @since  4.0.0
 110       * @deprecated  5.0  Support for MD5 hashed passwords will be removed
 111       */
 112      public const HASH_MD5 = 'md5';
 113  
 114      /**
 115       * Constant defining the PHPass password algorithm for use with password hashes
 116       *
 117       * @var    string
 118       * @since  4.0.0
 119       * @deprecated  5.0  Support for PHPass hashed passwords will be removed
 120       */
 121      public const HASH_PHPASS = 'phpass';
 122  
 123      /**
 124       * Mapping array for the algorithm handler
 125       *
 126       * @var array
 127       * @since  4.0.0
 128       */
 129      public const HASH_ALGORITHMS = [
 130          self::HASH_ARGON2I => Argon2iHandler::class,
 131          self::HASH_ARGON2I_BC => Argon2iHandler::class,
 132          self::HASH_ARGON2ID => Argon2idHandler::class,
 133          self::HASH_ARGON2ID_BC => Argon2idHandler::class,
 134          self::HASH_BCRYPT => BCryptHandler::class,
 135          self::HASH_BCRYPT_BC => BCryptHandler::class,
 136          self::HASH_MD5 => MD5Handler::class,
 137          self::HASH_PHPASS => PHPassHandler::class
 138      ];
 139  
 140      /**
 141       * Method to add a user to a group.
 142       *
 143       * @param   integer  $userId   The id of the user.
 144       * @param   integer  $groupId  The id of the group.
 145       *
 146       * @return  boolean  True on success
 147       *
 148       * @since   1.7.0
 149       * @throws  \RuntimeException
 150       */
 151      public static function addUserToGroup($userId, $groupId)
 152      {
 153          // Cast as integer until method is typehinted.
 154          $userId  = (int) $userId;
 155          $groupId = (int) $groupId;
 156  
 157          // Get the user object.
 158          $user = new User($userId);
 159  
 160          // Add the user to the group if necessary.
 161          if (!\in_array($groupId, $user->groups)) {
 162              // Check whether the group exists.
 163              $db = Factory::getDbo();
 164              $query = $db->getQuery(true)
 165                  ->select($db->quoteName('id'))
 166                  ->from($db->quoteName('#__usergroups'))
 167                  ->where($db->quoteName('id') . ' = :groupId')
 168                  ->bind(':groupId', $groupId, ParameterType::INTEGER);
 169              $db->setQuery($query);
 170  
 171              // If the group does not exist, return an exception.
 172              if ($db->loadResult() === null) {
 173                  throw new \RuntimeException('Access Usergroup Invalid');
 174              }
 175  
 176              // Add the group data to the user object.
 177              $user->groups[$groupId] = $groupId;
 178  
 179              // Reindex the array for prepared statements binding
 180              $user->groups = array_values($user->groups);
 181  
 182              // Store the user object.
 183              $user->save();
 184          }
 185  
 186          // Set the group data for any preloaded user objects.
 187          $temp         = User::getInstance($userId);
 188          $temp->groups = $user->groups;
 189  
 190          if (Factory::getSession()->getId()) {
 191              // Set the group data for the user object in the session.
 192              $temp = Factory::getUser();
 193  
 194              if ($temp->id == $userId) {
 195                  $temp->groups = $user->groups;
 196              }
 197          }
 198  
 199          return true;
 200      }
 201  
 202      /**
 203       * Method to get a list of groups a user is in.
 204       *
 205       * @param   integer  $userId  The id of the user.
 206       *
 207       * @return  array    List of groups
 208       *
 209       * @since   1.7.0
 210       */
 211      public static function getUserGroups($userId)
 212      {
 213          // Get the user object.
 214          $user = User::getInstance((int) $userId);
 215  
 216          return $user->groups ?? array();
 217      }
 218  
 219      /**
 220       * Method to remove a user from a group.
 221       *
 222       * @param   integer  $userId   The id of the user.
 223       * @param   integer  $groupId  The id of the group.
 224       *
 225       * @return  boolean  True on success
 226       *
 227       * @since   1.7.0
 228       */
 229      public static function removeUserFromGroup($userId, $groupId)
 230      {
 231          // Get the user object.
 232          $user = User::getInstance((int) $userId);
 233  
 234          // Remove the user from the group if necessary.
 235          $key = array_search($groupId, $user->groups);
 236  
 237          if ($key !== false) {
 238              unset($user->groups[$key]);
 239              $user->groups = array_values($user->groups);
 240  
 241              // Store the user object.
 242              $user->save();
 243          }
 244  
 245          // Set the group data for any preloaded user objects.
 246          $temp = Factory::getUser((int) $userId);
 247          $temp->groups = $user->groups;
 248  
 249          // Set the group data for the user object in the session.
 250          $temp = Factory::getUser();
 251  
 252          if ($temp->id == $userId) {
 253              $temp->groups = $user->groups;
 254          }
 255  
 256          return true;
 257      }
 258  
 259      /**
 260       * Method to set the groups for a user.
 261       *
 262       * @param   integer  $userId  The id of the user.
 263       * @param   array    $groups  An array of group ids to put the user in.
 264       *
 265       * @return  boolean  True on success
 266       *
 267       * @since   1.7.0
 268       */
 269      public static function setUserGroups($userId, $groups)
 270      {
 271          // Get the user object.
 272          $user = User::getInstance((int) $userId);
 273  
 274          // Set the group ids.
 275          $groups = ArrayHelper::toInteger($groups);
 276          $user->groups = $groups;
 277  
 278          // Get the titles for the user groups.
 279          $db = Factory::getDbo();
 280          $query = $db->getQuery(true)
 281              ->select($db->quoteName(['id', 'title']))
 282              ->from($db->quoteName('#__usergroups'))
 283              ->whereIn($db->quoteName('id'), $user->groups);
 284          $db->setQuery($query);
 285          $results = $db->loadObjectList();
 286  
 287          // Set the titles for the user groups.
 288          for ($i = 0, $n = \count($results); $i < $n; $i++) {
 289              $user->groups[$results[$i]->id] = $results[$i]->id;
 290          }
 291  
 292          // Store the user object.
 293          $user->save();
 294  
 295          // Set the group data for any preloaded user objects.
 296          $temp = Factory::getUser((int) $userId);
 297          $temp->groups = $user->groups;
 298  
 299          if (Factory::getSession()->getId()) {
 300              // Set the group data for the user object in the session.
 301              $temp = Factory::getUser();
 302  
 303              if ($temp->id == $userId) {
 304                  $temp->groups = $user->groups;
 305              }
 306          }
 307  
 308          return true;
 309      }
 310  
 311      /**
 312       * Gets the user profile information
 313       *
 314       * @param   integer  $userId  The id of the user.
 315       *
 316       * @return  object
 317       *
 318       * @since   1.7.0
 319       */
 320      public static function getProfile($userId = 0)
 321      {
 322          if ($userId == 0) {
 323              $user   = Factory::getUser();
 324              $userId = $user->id;
 325          }
 326  
 327          // Get the dispatcher and load the user's plugins.
 328          PluginHelper::importPlugin('user');
 329  
 330          $data = new CMSObject();
 331          $data->id = $userId;
 332  
 333          // Trigger the data preparation event.
 334          Factory::getApplication()->triggerEvent('onContentPrepareData', array('com_users.profile', &$data));
 335  
 336          return $data;
 337      }
 338  
 339      /**
 340       * Method to activate a user
 341       *
 342       * @param   string  $activation  Activation string
 343       *
 344       * @return  boolean  True on success
 345       *
 346       * @since   1.7.0
 347       */
 348      public static function activateUser($activation)
 349      {
 350          $db       = Factory::getDbo();
 351  
 352          // Let's get the id of the user we want to activate
 353          $query = $db->getQuery(true)
 354              ->select($db->quoteName('id'))
 355              ->from($db->quoteName('#__users'))
 356              ->where($db->quoteName('activation') . ' = :activation')
 357              ->where($db->quoteName('block') . ' = 1')
 358              ->where($db->quoteName('lastvisitDate') . ' IS NULL')
 359              ->bind(':activation', $activation);
 360          $db->setQuery($query);
 361          $id = (int) $db->loadResult();
 362  
 363          // Is it a valid user to activate?
 364          if ($id) {
 365              $user = User::getInstance($id);
 366  
 367              $user->set('block', '0');
 368              $user->set('activation', '');
 369  
 370              // Time to take care of business.... store the user.
 371              if (!$user->save()) {
 372                  Log::add($user->getError(), Log::WARNING, 'jerror');
 373  
 374                  return false;
 375              }
 376          } else {
 377              Log::add(Text::_('JLIB_USER_ERROR_UNABLE_TO_FIND_USER'), Log::WARNING, 'jerror');
 378  
 379              return false;
 380          }
 381  
 382          return true;
 383      }
 384  
 385      /**
 386       * Returns userid if a user exists
 387       *
 388       * @param   string  $username  The username to search on.
 389       *
 390       * @return  integer  The user id or 0 if not found.
 391       *
 392       * @since   1.7.0
 393       */
 394      public static function getUserId($username)
 395      {
 396          // Initialise some variables
 397          $db = Factory::getDbo();
 398          $query = $db->getQuery(true)
 399              ->select($db->quoteName('id'))
 400              ->from($db->quoteName('#__users'))
 401              ->where($db->quoteName('username') . ' = :username')
 402              ->bind(':username', $username)
 403              ->setLimit(1);
 404          $db->setQuery($query);
 405  
 406          return $db->loadResult();
 407      }
 408  
 409      /**
 410       * Hashes a password using the current encryption.
 411       *
 412       * @param   string          $password   The plaintext password to encrypt.
 413       * @param   string|integer  $algorithm  The hashing algorithm to use, represented by `HASH_*` class constants, or a container service ID.
 414       * @param   array           $options    The options for the algorithm to use.
 415       *
 416       * @return  string  The encrypted password.
 417       *
 418       * @since   3.2.1
 419       * @throws  \InvalidArgumentException when the algorithm is not supported
 420       */
 421      public static function hashPassword($password, $algorithm = self::HASH_BCRYPT, array $options = array())
 422      {
 423          $container = Factory::getContainer();
 424  
 425          // If the algorithm is a valid service ID, use that service to generate the hash
 426          if ($container->has($algorithm)) {
 427              return $container->get($algorithm)->hashPassword($password, $options);
 428          }
 429  
 430          // Try to load handler
 431          if (isset(self::HASH_ALGORITHMS[$algorithm])) {
 432              return $container->get(self::HASH_ALGORITHMS[$algorithm])->hashPassword($password, $options);
 433          }
 434  
 435          // Unsupported algorithm, sorry!
 436          throw new \InvalidArgumentException(sprintf('The %s algorithm is not supported for hashing passwords.', $algorithm));
 437      }
 438  
 439      /**
 440       * Formats a password using the current encryption. If the user ID is given
 441       * and the hash does not fit the current hashing algorithm, it automatically
 442       * updates the hash.
 443       *
 444       * @param   string   $password  The plaintext password to check.
 445       * @param   string   $hash      The hash to verify against.
 446       * @param   integer  $userId    ID of the user if the password hash should be updated
 447       *
 448       * @return  boolean  True if the password and hash match, false otherwise
 449       *
 450       * @since   3.2.1
 451       */
 452      public static function verifyPassword($password, $hash, $userId = 0)
 453      {
 454          $passwordAlgorithm = self::HASH_BCRYPT;
 455          $container         = Factory::getContainer();
 456  
 457          // Cheaply try to determine the algorithm in use otherwise fall back to the chained handler
 458          if (strpos($hash, '$P$') === 0) {
 459              /** @var PHPassHandler $handler */
 460              $handler = $container->get(PHPassHandler::class);
 461          } elseif (strpos($hash, '$argon2id') === 0) {
 462              // Check for Argon2id hashes
 463              /** @var Argon2idHandler $handler */
 464              $handler = $container->get(Argon2idHandler::class);
 465  
 466              $passwordAlgorithm = self::HASH_ARGON2ID;
 467          } elseif (strpos($hash, '$argon2i') === 0) {
 468              // Check for Argon2i hashes
 469              /** @var Argon2iHandler $handler */
 470              $handler = $container->get(Argon2iHandler::class);
 471  
 472              $passwordAlgorithm = self::HASH_ARGON2I;
 473          } elseif (strpos($hash, '$2') === 0) {
 474              // Check for bcrypt hashes
 475              /** @var BCryptHandler $handler */
 476              $handler = $container->get(BCryptHandler::class);
 477          } else {
 478              /** @var ChainedHandler $handler */
 479              $handler = $container->get(ChainedHandler::class);
 480          }
 481  
 482          $match  = $handler->validatePassword($password, $hash);
 483          $rehash = $handler instanceof CheckIfRehashNeededHandlerInterface ? $handler->checkIfRehashNeeded($hash) : false;
 484  
 485          // If we have a match and rehash = true, rehash the password with the current algorithm.
 486          if ((int) $userId > 0 && $match && $rehash) {
 487              $user = new User($userId);
 488              $user->password = static::hashPassword($password, $passwordAlgorithm);
 489              $user->save();
 490          }
 491  
 492          return $match;
 493      }
 494  
 495      /**
 496       * Generate a random password
 497       *
 498       * @param   integer  $length  Length of the password to generate
 499       *
 500       * @return  string  Random Password
 501       *
 502       * @since   1.7.0
 503       */
 504      public static function genRandomPassword($length = 8)
 505      {
 506          $salt = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
 507          $base = \strlen($salt);
 508          $makepass = '';
 509  
 510          /*
 511           * Start with a cryptographic strength random string, then convert it to
 512           * a string with the numeric base of the salt.
 513           * Shift the base conversion on each character so the character
 514           * distribution is even, and randomize the start shift so it's not
 515           * predictable.
 516           */
 517          $random = Crypt::genRandomBytes($length + 1);
 518          $shift = \ord($random[0]);
 519  
 520          for ($i = 1; $i <= $length; ++$i) {
 521              $makepass .= $salt[($shift + \ord($random[$i])) % $base];
 522              $shift += \ord($random[$i]);
 523          }
 524  
 525          return $makepass;
 526      }
 527  
 528      /**
 529       * Method to get a hashed user agent string that does not include browser version.
 530       * Used when frequent version changes cause problems.
 531       *
 532       * @return  string  A hashed user agent string with version replaced by 'abcd'
 533       *
 534       * @since   3.2
 535       */
 536      public static function getShortHashedUserAgent()
 537      {
 538          $ua = Factory::getApplication()->client;
 539          $uaString = $ua->userAgent;
 540          $browserVersion = $ua->browserVersion;
 541  
 542          if ($browserVersion) {
 543              $uaShort = str_replace($browserVersion, 'abcd', $uaString);
 544          } else {
 545              $uaShort = $uaString;
 546          }
 547  
 548          return md5(Uri::base() . $uaShort);
 549      }
 550  
 551      /**
 552       * Check if there is a super user in the user ids.
 553       *
 554       * @param   array  $userIds  An array of user IDs on which to operate
 555       *
 556       * @return  boolean  True on success, false on failure
 557       *
 558       * @since   3.6.5
 559       */
 560      public static function checkSuperUserInUsers(array $userIds)
 561      {
 562          foreach ($userIds as $userId) {
 563              foreach (static::getUserGroups($userId) as $userGroupId) {
 564                  if (Access::checkGroup($userGroupId, 'core.admin')) {
 565                      return true;
 566                  }
 567              }
 568          }
 569  
 570          return false;
 571      }
 572  
 573      /**
 574       * Destroy all active session for a given user id
 575       *
 576       * @param   int      $userId       Id of user
 577       * @param   boolean  $keepCurrent  Keep the session of the currently acting user
 578       * @param   int      $clientId     Application client id
 579       *
 580       * @return  boolean
 581       *
 582       * @since   3.9.28
 583       */
 584      public static function destroyUserSessions($userId, $keepCurrent = false, $clientId = null)
 585      {
 586          // Destroy all sessions for the user account if able
 587          if (!Factory::getApplication()->get('session_metadata', true)) {
 588              return false;
 589          }
 590  
 591          $db = Factory::getDbo();
 592  
 593          try {
 594              $userId = (int) $userId;
 595  
 596              $query = $db->getQuery(true)
 597                  ->select($db->quoteName('session_id'))
 598                  ->from($db->quoteName('#__session'))
 599                  ->where($db->quoteName('userid') . ' = :userid')
 600                  ->bind(':userid', $userId, ParameterType::INTEGER);
 601  
 602              if ($clientId !== null) {
 603                  $clientId = (int) $clientId;
 604  
 605                  $query->where($db->quoteName('client_id') . ' = :client_id')
 606                      ->bind(':client_id', $clientId, ParameterType::INTEGER);
 607              }
 608  
 609              $sessionIds = $db->setQuery($query)->loadColumn();
 610          } catch (ExecutionFailureException $e) {
 611              return false;
 612          }
 613  
 614          // Convert PostgreSQL Session IDs into strings (see GitHub #33822)
 615          foreach ($sessionIds as &$sessionId) {
 616              if (is_resource($sessionId) && get_resource_type($sessionId) === 'stream') {
 617                  $sessionId = stream_get_contents($sessionId);
 618              }
 619          }
 620  
 621          // If true, removes the current session id from the purge list
 622          if ($keepCurrent) {
 623              $sessionIds = array_diff($sessionIds, array(Factory::getSession()->getId()));
 624          }
 625  
 626          // If there aren't any active sessions then there's nothing to do here
 627          if (empty($sessionIds)) {
 628              return false;
 629          }
 630  
 631          /** @var SessionManager $sessionManager */
 632          $sessionManager = Factory::getContainer()->get('session.manager');
 633          $sessionManager->destroySessions($sessionIds);
 634  
 635          try {
 636              $db->setQuery(
 637                  $db->getQuery(true)
 638                      ->delete($db->quoteName('#__session'))
 639                      ->whereIn($db->quoteName('session_id'), $sessionIds, ParameterType::LARGE_OBJECT)
 640              )->execute();
 641          } catch (ExecutionFailureException $e) {
 642              // No issue, let things go
 643          }
 644      }
 645  }


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