[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |