[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/administrator/components/com_users/src/Controller/ -> CaptiveController.php (source)

   1  <?php
   2  
   3  /**
   4   * @package     Joomla.Administrator
   5   * @subpackage  com_users
   6   *
   7   * @copyright   (C) 2022 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\Administrator\Controller;
  12  
  13  use Exception;
  14  use Joomla\CMS\Application\CMSApplication;
  15  use Joomla\CMS\Date\Date;
  16  use Joomla\CMS\Event\MultiFactor\NotifyActionLog;
  17  use Joomla\CMS\Event\MultiFactor\Validate;
  18  use Joomla\CMS\Factory;
  19  use Joomla\CMS\Language\Text;
  20  use Joomla\CMS\MVC\Controller\BaseController;
  21  use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
  22  use Joomla\CMS\Router\Route;
  23  use Joomla\CMS\Uri\Uri;
  24  use Joomla\CMS\User\UserFactoryInterface;
  25  use Joomla\Component\Users\Administrator\Model\BackupcodesModel;
  26  use Joomla\Component\Users\Administrator\Model\CaptiveModel;
  27  use Joomla\Input\Input;
  28  use RuntimeException;
  29  
  30  // phpcs:disable PSR1.Files.SideEffects
  31  \defined('_JEXEC') or die;
  32  // phpcs:enable PSR1.Files.SideEffects
  33  
  34  /**
  35   * Captive Multi-factor Authentication page controller
  36   *
  37   * @since 4.2.0
  38   */
  39  class CaptiveController extends BaseController
  40  {
  41      /**
  42       * Public constructor
  43       *
  44       * @param   array                     $config   Plugin configuration
  45       * @param   MVCFactoryInterface|null  $factory  MVC Factory for the com_users component
  46       * @param   CMSApplication|null       $app      CMS application object
  47       * @param   Input|null                $input    Joomla CMS input object
  48       *
  49       * @since 4.2.0
  50       */
  51      public function __construct(array $config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null)
  52      {
  53          parent::__construct($config, $factory, $app, $input);
  54  
  55          $this->registerTask('captive', 'display');
  56      }
  57  
  58      /**
  59       * Displays the captive login page
  60       *
  61       * @param   boolean        $cachable   Ignored. This page is never cached.
  62       * @param   boolean|array  $urlparams  Ignored. This page is never cached.
  63       *
  64       * @return  void
  65       * @throws  Exception
  66       * @since   4.2.0
  67       */
  68      public function display($cachable = false, $urlparams = false): void
  69      {
  70          $user = $this->app->getIdentity()
  71              ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
  72  
  73          // Only allow logged in Users
  74          if ($user->guest) {
  75              throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
  76          }
  77  
  78          // Get the view object
  79          $viewLayout = $this->input->get('layout', 'default', 'string');
  80          $view       = $this->getView(
  81              'Captive',
  82              'html',
  83              '',
  84              [
  85                  'base_path' => $this->basePath,
  86                  'layout'    => $viewLayout,
  87              ]
  88          );
  89  
  90          $view->document = $this->app->getDocument();
  91  
  92          // If we're already logged in go to the site's home page
  93          if ((int) $this->app->getSession()->get('com_users.mfa_checked', 0) === 1) {
  94              $url = Route::_('index.php?option=com_users&task=methods.display', false);
  95  
  96              $this->setRedirect($url);
  97          }
  98  
  99          // Pass the model to the view
 100          /** @var CaptiveModel $model */
 101          $model = $this->getModel('Captive');
 102          $view->setModel($model, true);
 103  
 104          /** @var BackupcodesModel $codesModel */
 105          $codesModel = $this->getModel('Backupcodes');
 106          $view->setModel($codesModel, false);
 107  
 108          try {
 109              // Suppress all modules on the page except those explicitly allowed
 110              $model->suppressAllModules();
 111          } catch (Exception $e) {
 112              // If we can't kill the modules we can still survive.
 113          }
 114  
 115          // Pass the MFA record ID to the model
 116          $recordId = $this->input->getInt('record_id', null);
 117          $model->setState('record_id', $recordId);
 118  
 119          // Do not go through $this->display() because it overrides the model.
 120          $view->display();
 121      }
 122  
 123      /**
 124       * Validate the MFA code entered by the user
 125       *
 126       * @param   bool   $cachable         Ignored. This page is never cached.
 127       * @param   array  $urlparameters    Ignored. This page is never cached.
 128       *
 129       * @return  void
 130       * @throws  Exception
 131       * @since   4.2.0
 132       */
 133      public function validate($cachable = false, $urlparameters = [])
 134      {
 135          // CSRF Check
 136          $this->checkToken($this->input->getMethod());
 137  
 138          // Get the MFA parameters from the request
 139          $recordId  = $this->input->getInt('record_id', null);
 140          $code      = $this->input->get('code', null, 'raw');
 141          /** @var CaptiveModel $model */
 142          $model = $this->getModel('Captive');
 143  
 144          // Validate the MFA record
 145          $model->setState('record_id', $recordId);
 146          $record = $model->getRecord();
 147  
 148          if (empty($record)) {
 149              $event = new NotifyActionLog('onComUsersCaptiveValidateInvalidMethod');
 150              $this->app->getDispatcher()->dispatch($event->getName(), $event);
 151  
 152              throw new RuntimeException(Text::_('COM_USERS_MFA_INVALID_METHOD'), 500);
 153          }
 154  
 155          // Validate the code
 156          $user = $this->app->getIdentity()
 157              ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
 158  
 159          $event   = new Validate($record, $user, $code);
 160          $results = $this->app
 161              ->getDispatcher()
 162              ->dispatch($event->getName(), $event)
 163              ->getArgument('result', []);
 164  
 165          $isValidCode = false;
 166  
 167          if ($record->method === 'backupcodes') {
 168              /** @var BackupcodesModel $codesModel */
 169              $codesModel = $this->getModel('Backupcodes');
 170              $results    = [$codesModel->isBackupCode($code, $user)];
 171              /**
 172               * This is required! Do not remove!
 173               *
 174               * There is a store() call below. It saves the in-memory MFA record to the database. That includes the
 175               * options key which contains the configuration of the Method. For backup codes, these are the actual codes
 176               * you can use. When we check for a backup code validity we also "burn" it, i.e. we remove it from the
 177               * options table and save that to the database. However, this DOES NOT update the $record here. Therefore
 178               * the call to saveRecord() would overwrite the database contents with a record that _includes_ the backup
 179               * code we had just burned. As a result the single use backup codes end up being multiple use.
 180               *
 181               * By doing a getRecord() here, right after we have "burned" any correct backup codes, we resolve this
 182               * issue. The loaded record will reflect the database contents where the options DO NOT include the code we
 183               * just used. Therefore the call to store() will result in the correct database state, i.e. the used backup
 184               * code being removed.
 185               */
 186              $record = $model->getRecord();
 187          }
 188  
 189          $isValidCode = array_reduce(
 190              $results,
 191              function (bool $carry, $result) {
 192                  return $carry || boolval($result);
 193              },
 194              false
 195          );
 196  
 197          if (!$isValidCode) {
 198              // The code is wrong. Display an error and go back.
 199              $captiveURL = Route::_('index.php?option=com_users&view=captive&record_id=' . $recordId, false);
 200              $message    = Text::_('COM_USERS_MFA_INVALID_CODE');
 201              $this->setRedirect($captiveURL, $message, 'error');
 202  
 203              $event = new NotifyActionLog('onComUsersCaptiveValidateFailed', [$record->title]);
 204              $this->app->getDispatcher()->dispatch($event->getName(), $event);
 205  
 206              return;
 207          }
 208  
 209          // Update the Last Used, UA and IP columns
 210          $jNow = Date::getInstance();
 211  
 212          $record->last_used = $jNow->toSql();
 213          $record->store();
 214  
 215          // Flag the user as fully logged in
 216          $session = $this->app->getSession();
 217          $session->set('com_users.mfa_checked', 1);
 218          $session->set('com_users.mandatory_mfa_setup', 0);
 219  
 220          // Get the return URL stored by the plugin in the session
 221          $returnUrl = $session->get('com_users.return_url', '');
 222  
 223          // If the return URL is not set or not internal to this site redirect to the site's front page
 224          if (empty($returnUrl) || !Uri::isInternal($returnUrl)) {
 225              $returnUrl = Uri::base();
 226          }
 227  
 228          $this->setRedirect($returnUrl);
 229  
 230          $event = new NotifyActionLog('onComUsersCaptiveValidateSuccess', [$record->title]);
 231          $this->app->getDispatcher()->dispatch($event->getName(), $event);
 232      }
 233  }


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