[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/plugins/user/joomla/ -> joomla.php (source)

   1  <?php
   2  
   3  /**
   4   * @package     Joomla.Plugin
   5   * @subpackage  User.joomla
   6   *
   7   * @copyright   (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
   8   * @license     GNU General Public License version 2 or later; see LICENSE.txt
   9  
  10   * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
  11   */
  12  
  13  use Joomla\CMS\Component\ComponentHelper;
  14  use Joomla\CMS\Factory;
  15  use Joomla\CMS\Language\LanguageFactoryInterface;
  16  use Joomla\CMS\Language\Text;
  17  use Joomla\CMS\Log\Log;
  18  use Joomla\CMS\Mail\MailTemplate;
  19  use Joomla\CMS\Plugin\CMSPlugin;
  20  use Joomla\CMS\Uri\Uri;
  21  use Joomla\CMS\User\User;
  22  use Joomla\CMS\User\UserHelper;
  23  use Joomla\Database\Exception\ExecutionFailureException;
  24  use Joomla\Database\ParameterType;
  25  use Joomla\Registry\Registry;
  26  
  27  // phpcs:disable PSR1.Files.SideEffects
  28  \defined('_JEXEC') or die;
  29  // phpcs:enable PSR1.Files.SideEffects
  30  
  31  /**
  32   * Joomla User plugin
  33   *
  34   * @since  1.5
  35   */
  36  class PlgUserJoomla extends CMSPlugin
  37  {
  38      /**
  39       * @var    \Joomla\CMS\Application\CMSApplication
  40       *
  41       * @since  3.2
  42       */
  43      protected $app;
  44  
  45      /**
  46       * @var    \Joomla\Database\DatabaseDriver
  47       *
  48       * @since  3.2
  49       */
  50      protected $db;
  51  
  52      /**
  53       * Set as required the passwords fields when mail to user is set to No
  54       *
  55       * @param   \Joomla\CMS\Form\Form  $form  The form to be altered.
  56       * @param   mixed                  $data  The associated data for the form.
  57       *
  58       * @return  boolean
  59       *
  60       * @since   4.0.0
  61       */
  62      public function onContentPrepareForm($form, $data)
  63      {
  64          // Check we are manipulating a valid user form before modifying it.
  65          $name = $form->getName();
  66  
  67          if ($name === 'com_users.user') {
  68              // In case there is a validation error (like duplicated user), $data is an empty array on save.
  69              // After returning from error, $data is an array but populated
  70              if (!$data) {
  71                  $data = Factory::getApplication()->input->get('jform', array(), 'array');
  72              }
  73  
  74              if (is_array($data)) {
  75                  $data = (object) $data;
  76              }
  77  
  78              // Passwords fields are required when mail to user is set to No
  79              if (empty($data->id) && !$this->params->get('mail_to_user', 1)) {
  80                  $form->setFieldAttribute('password', 'required', 'true');
  81                  $form->setFieldAttribute('password2', 'required', 'true');
  82              }
  83          }
  84  
  85          return true;
  86      }
  87  
  88      /**
  89       * Remove all sessions for the user name
  90       *
  91       * Method is called after user data is deleted from the database
  92       *
  93       * @param   array    $user     Holds the user data
  94       * @param   boolean  $success  True if user was successfully stored in the database
  95       * @param   string   $msg      Message
  96       *
  97       * @return  void
  98       *
  99       * @since   1.6
 100       */
 101      public function onUserAfterDelete($user, $success, $msg): void
 102      {
 103          if (!$success) {
 104              return;
 105          }
 106  
 107          $userId = (int) $user['id'];
 108  
 109          // Only execute this if the session metadata is tracked
 110          if ($this->app->get('session_metadata', true)) {
 111              UserHelper::destroyUserSessions($userId, true);
 112          }
 113  
 114          try {
 115              $this->db->setQuery(
 116                  $this->db->getQuery(true)
 117                      ->delete($this->db->quoteName('#__messages'))
 118                      ->where($this->db->quoteName('user_id_from') . ' = :userId')
 119                      ->bind(':userId', $userId, ParameterType::INTEGER)
 120              )->execute();
 121          } catch (ExecutionFailureException $e) {
 122              // Do nothing.
 123          }
 124  
 125          // Delete Multi-factor Authentication user profile records
 126          $profileKey = 'mfa.%';
 127          $query      = $this->db->getQuery(true)
 128              ->delete($this->db->quoteName('#__user_profiles'))
 129              ->where($this->db->quoteName('user_id') . ' = :userId')
 130              ->where($this->db->quoteName('profile_key') . ' LIKE :profileKey')
 131              ->bind(':userId', $userId, ParameterType::INTEGER)
 132              ->bind(':profileKey', $profileKey, ParameterType::STRING);
 133  
 134          try {
 135              $this->db->setQuery($query)->execute();
 136          } catch (Exception $e) {
 137              // Do nothing
 138          }
 139  
 140          // Delete Multi-factor Authentication records
 141          $query = $this->db->getQuery(true)
 142              ->delete($this->db->qn('#__user_mfa'))
 143              ->where($this->db->quoteName('user_id') . ' = :userId')
 144              ->bind(':userId', $userId, ParameterType::INTEGER);
 145  
 146          try {
 147              $this->db->setQuery($query)->execute();
 148          } catch (Exception $e) {
 149              // Do nothing
 150          }
 151      }
 152  
 153      /**
 154       * Utility method to act on a user after it has been saved.
 155       *
 156       * This method sends a registration email to new users created in the backend.
 157       *
 158       * @param   array    $user     Holds the new user data.
 159       * @param   boolean  $isnew    True if a new user is stored.
 160       * @param   boolean  $success  True if user was successfully stored in the database.
 161       * @param   string   $msg      Message.
 162       *
 163       * @return  void
 164       *
 165       * @since   1.6
 166       */
 167      public function onUserAfterSave($user, $isnew, $success, $msg): void
 168      {
 169          $mail_to_user = $this->params->get('mail_to_user', 1);
 170  
 171          if (!$isnew || !$mail_to_user) {
 172              return;
 173          }
 174  
 175          // @todo: Suck in the frontend registration emails here as well. Job for a rainy day.
 176          // The method check here ensures that if running as a CLI Application we don't get any errors
 177          if (method_exists($this->app, 'isClient') && ($this->app->isClient('site') || $this->app->isClient('cli'))) {
 178              return;
 179          }
 180  
 181          // Check if we have a sensible from email address, if not bail out as mail would not be sent anyway
 182          if (strpos($this->app->get('mailfrom'), '@') === false) {
 183              $this->app->enqueueMessage(Text::_('JERROR_SENDING_EMAIL'), 'warning');
 184  
 185              return;
 186          }
 187  
 188          $defaultLanguage = Factory::getLanguage();
 189          $defaultLocale   = $defaultLanguage->getTag();
 190  
 191          /**
 192           * Look for user language. Priority:
 193           *  1. User frontend language
 194           *  2. User backend language
 195           */
 196          $userParams = new Registry($user['params']);
 197          $userLocale = $userParams->get('language', $userParams->get('admin_language', $defaultLocale));
 198  
 199          // Temporarily set application language to user's language.
 200          if ($userLocale !== $defaultLocale) {
 201              Factory::$language = Factory::getContainer()
 202                  ->get(LanguageFactoryInterface::class)
 203                  ->createLanguage($userLocale, $this->app->get('debug_lang', false));
 204          }
 205  
 206          // Load plugin language files.
 207          $this->loadLanguage();
 208  
 209          // Collect data for mail
 210          $data = [
 211              'name' => $user['name'],
 212              'sitename' => $this->app->get('sitename'),
 213              'url' => Uri::root(),
 214              'username' => $user['username'],
 215              'password' => $user['password_clear'],
 216              'email' => $user['email'],
 217          ];
 218  
 219          $mailer = new MailTemplate('plg_user_joomla.mail', $userLocale);
 220          $mailer->addTemplateData($data);
 221          $mailer->addRecipient($user['email'], $user['name']);
 222  
 223          try {
 224              $res = $mailer->send();
 225          } catch (\Exception $exception) {
 226              try {
 227                  Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');
 228  
 229                  $res = false;
 230              } catch (\RuntimeException $exception) {
 231                  $this->app->enqueueMessage(Text::_($exception->errorMessage()), 'warning');
 232  
 233                  $res = false;
 234              }
 235          }
 236  
 237          if ($res === false) {
 238              $this->app->enqueueMessage(Text::_('JERROR_SENDING_EMAIL'), 'warning');
 239          }
 240  
 241          // Set application language back to default if we changed it
 242          if ($userLocale !== $defaultLocale) {
 243              Factory::$language = $defaultLanguage;
 244          }
 245      }
 246  
 247      /**
 248       * This method should handle any login logic and report back to the subject
 249       *
 250       * @param   array  $user     Holds the user data
 251       * @param   array  $options  Array holding options (remember, autoregister, group)
 252       *
 253       * @return  boolean  True on success
 254       *
 255       * @since   1.5
 256       */
 257      public function onUserLogin($user, $options = [])
 258      {
 259          $instance = $this->_getUser($user, $options);
 260  
 261          // If _getUser returned an error, then pass it back.
 262          if ($instance instanceof Exception) {
 263              return false;
 264          }
 265  
 266          // If the user is blocked, redirect with an error
 267          if ($instance->block == 1) {
 268              $this->app->enqueueMessage(Text::_('JERROR_NOLOGIN_BLOCKED'), 'warning');
 269  
 270              return false;
 271          }
 272  
 273          // Authorise the user based on the group information
 274          if (!isset($options['group'])) {
 275              $options['group'] = 'USERS';
 276          }
 277  
 278          // Check the user can login.
 279          $result = $instance->authorise($options['action']);
 280  
 281          if (!$result) {
 282              $this->app->enqueueMessage(Text::_('JERROR_LOGIN_DENIED'), 'warning');
 283  
 284              return false;
 285          }
 286  
 287          // Mark the user as logged in
 288          $instance->guest = 0;
 289  
 290          // Load the logged in user to the application
 291          $this->app->loadIdentity($instance);
 292  
 293          $session = $this->app->getSession();
 294  
 295          // Grab the current session ID
 296          $oldSessionId = $session->getId();
 297  
 298          // Fork the session
 299          $session->fork();
 300  
 301          // Register the needed session variables
 302          $session->set('user', $instance);
 303  
 304          // Update the user related fields for the Joomla sessions table if tracking session metadata.
 305          if ($this->app->get('session_metadata', true)) {
 306              $this->app->checkSession();
 307          }
 308  
 309          // Purge the old session
 310          $query = $this->db->getQuery(true)
 311              ->delete($this->db->quoteName('#__session'))
 312              ->where($this->db->quoteName('session_id') . ' = :sessionid')
 313              ->bind(':sessionid', $oldSessionId);
 314  
 315          try {
 316              $this->db->setQuery($query)->execute();
 317          } catch (RuntimeException $e) {
 318              // The old session is already invalidated, don't let this block logging in
 319          }
 320  
 321          // Hit the user last visit field
 322          $instance->setLastVisit();
 323  
 324          // Add "user state" cookie used for reverse caching proxies like Varnish, Nginx etc.
 325          if ($this->app->isClient('site')) {
 326              $this->app->input->cookie->set(
 327                  'joomla_user_state',
 328                  'logged_in',
 329                  0,
 330                  $this->app->get('cookie_path', '/'),
 331                  $this->app->get('cookie_domain', ''),
 332                  $this->app->isHttpsForced(),
 333                  true
 334              );
 335          }
 336  
 337          return true;
 338      }
 339  
 340      /**
 341       * This method should handle any logout logic and report back to the subject
 342       *
 343       * @param   array  $user     Holds the user data.
 344       * @param   array  $options  Array holding options (client, ...).
 345       *
 346       * @return  boolean  True on success
 347       *
 348       * @since   1.5
 349       */
 350      public function onUserLogout($user, $options = [])
 351      {
 352          $my      = Factory::getUser();
 353          $session = Factory::getSession();
 354  
 355          $userid = (int) $user['id'];
 356  
 357          // Make sure we're a valid user first
 358          if ($user['id'] === 0 && !$my->get('tmp_user')) {
 359              return true;
 360          }
 361  
 362          $sharedSessions = $this->app->get('shared_session', '0');
 363  
 364          // Check to see if we're deleting the current session
 365          if ($my->id == $userid && ($sharedSessions || (!$sharedSessions && $options['clientid'] == $this->app->getClientId()))) {
 366              // Hit the user last visit field
 367              $my->setLastVisit();
 368  
 369              // Destroy the php session for this user
 370              $session->destroy();
 371          }
 372  
 373          // Enable / Disable Forcing logout all users with same userid, but only if session metadata is tracked
 374          $forceLogout = $this->params->get('forceLogout', 1) && $this->app->get('session_metadata', true);
 375  
 376          if ($forceLogout) {
 377              $clientId = $sharedSessions ? null : (int) $options['clientid'];
 378              UserHelper::destroyUserSessions($user['id'], false, $clientId);
 379          }
 380  
 381          // Delete "user state" cookie used for reverse caching proxies like Varnish, Nginx etc.
 382          if ($this->app->isClient('site')) {
 383              $this->app->input->cookie->set('joomla_user_state', '', 1, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain', ''));
 384          }
 385  
 386          return true;
 387      }
 388  
 389      /**
 390       * Hooks on the Joomla! login event. Detects silent logins and disables the Multi-Factor
 391       * Authentication page in this case.
 392       *
 393       * Moreover, it will save the redirection URL and the Captive URL which is necessary in Joomla 4. You see, in Joomla
 394       * 4 having unified sessions turned on makes the backend login redirect you to the frontend of the site AFTER
 395       * logging in, something which would cause the Captive page to appear in the frontend and redirect you to the public
 396       * frontend homepage after successfully passing the Two Step verification process.
 397       *
 398       * @param   array  $options  Passed by Joomla. user: a User object; responseType: string, authentication response type.
 399       *
 400       * @return void
 401       * @since  4.2.0
 402       */
 403      public function onUserAfterLogin(array $options): void
 404      {
 405          if (!($this->app->isClient('administrator')) && !($this->app->isClient('site'))) {
 406              return;
 407          }
 408  
 409          $this->disableMfaOnSilentLogin($options);
 410      }
 411  
 412      /**
 413       * Detect silent logins and disable MFA if the relevant com_users option is set.
 414       *
 415       * @param   array  $options  The array of login options and login result
 416       *
 417       * @return  void
 418       * @since   4.2.0
 419       */
 420      private function disableMfaOnSilentLogin(array $options): void
 421      {
 422          $userParams         = ComponentHelper::getParams('com_users');
 423          $doMfaOnSilentLogin = $userParams->get('mfaonsilent', 0) == 1;
 424  
 425          // Should I show MFA even on silent logins? Default: 1 (yes, show)
 426          if ($doMfaOnSilentLogin) {
 427              return;
 428          }
 429  
 430          // Make sure I have a valid user
 431          /** @var User $user */
 432          $user = $options['user'];
 433  
 434          if (!is_object($user) || !($user instanceof User) || $user->guest) {
 435              return;
 436          }
 437  
 438          $silentResponseTypes = array_map(
 439              'trim',
 440              explode(',', $userParams->get('silentresponses', '') ?: '')
 441          );
 442          $silentResponseTypes = $silentResponseTypes ?: ['cookie', 'passwordless'];
 443  
 444          // Only proceed if this is not a silent login
 445          if (!in_array(strtolower($options['responseType'] ?? ''), $silentResponseTypes)) {
 446              return;
 447          }
 448  
 449          // Set the flag indicating that MFA is already checked.
 450          $this->app->getSession()->set('com_users.mfa_checked', 1);
 451      }
 452  
 453      /**
 454       * This method will return a user object
 455       *
 456       * If options['autoregister'] is true, if the user doesn't exist yet they will be created
 457       *
 458       * @param   array  $user     Holds the user data.
 459       * @param   array  $options  Array holding options (remember, autoregister, group).
 460       *
 461       * @return  User
 462       *
 463       * @since   1.5
 464       */
 465      protected function _getUser($user, $options = [])
 466      {
 467          $instance = User::getInstance();
 468          $id = (int) UserHelper::getUserId($user['username']);
 469  
 470          if ($id) {
 471              $instance->load($id);
 472  
 473              return $instance;
 474          }
 475  
 476          // @todo : move this out of the plugin
 477          $params = ComponentHelper::getParams('com_users');
 478  
 479          // Read the default user group option from com_users
 480          $defaultUserGroup = $params->get('new_usertype', $params->get('guest_usergroup', 1));
 481  
 482          $instance->id = 0;
 483          $instance->name = $user['fullname'];
 484          $instance->username = $user['username'];
 485          $instance->password_clear = $user['password_clear'];
 486  
 487          // Result should contain an email (check).
 488          $instance->email = $user['email'];
 489          $instance->groups = [$defaultUserGroup];
 490  
 491          // If autoregister is set let's register the user
 492          $autoregister = $options['autoregister'] ?? $this->params->get('autoregister', 1);
 493  
 494          if ($autoregister) {
 495              if (!$instance->save()) {
 496                  Log::add('Failed to automatically create account for user ' . $user['username'] . '.', Log::WARNING, 'error');
 497              }
 498          } else {
 499              // No existing user and autoregister off, this is a temporary user.
 500              $instance->set('tmp_user', true);
 501          }
 502  
 503          return $instance;
 504      }
 505  }


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