[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
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\Event\MultiFactor\NotifyActionLog; 16 use Joomla\CMS\Event\MultiFactor\SaveSetup; 17 use Joomla\CMS\Factory; 18 use Joomla\CMS\Language\Text; 19 use Joomla\CMS\MVC\Controller\BaseController as BaseControllerAlias; 20 use Joomla\CMS\MVC\Factory\MVCFactoryInterface; 21 use Joomla\CMS\Router\Route; 22 use Joomla\CMS\User\User; 23 use Joomla\CMS\User\UserFactoryInterface; 24 use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper; 25 use Joomla\Component\Users\Administrator\Model\BackupcodesModel; 26 use Joomla\Component\Users\Administrator\Model\MethodModel; 27 use Joomla\Component\Users\Administrator\Table\MfaTable; 28 use Joomla\Input\Input; 29 use RuntimeException; 30 31 // phpcs:disable PSR1.Files.SideEffects 32 \defined('_JEXEC') or die; 33 // phpcs:enable PSR1.Files.SideEffects 34 35 /** 36 * Multi-factor Authentication method controller 37 * 38 * @since 4.2.0 39 */ 40 class MethodController extends BaseControllerAlias 41 { 42 /** 43 * Public constructor 44 * 45 * @param array $config Plugin configuration 46 * @param MVCFactoryInterface|null $factory MVC Factory for the com_users component 47 * @param CMSApplication|null $app CMS application object 48 * @param Input|null $input Joomla CMS input object 49 * 50 * @since 4.2.0 51 */ 52 public function __construct(array $config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null) 53 { 54 // We have to tell Joomla what is the name of the view, otherwise it defaults to the name of the *component*. 55 $config['default_view'] = 'method'; 56 $config['default_task'] = 'add'; 57 58 parent::__construct($config, $factory, $app, $input); 59 } 60 61 /** 62 * Execute a task by triggering a Method in the derived class. 63 * 64 * @param string $task The task to perform. If no matching task is found, the '__default' task is executed, if 65 * defined. 66 * 67 * @return mixed The value returned by the called Method. 68 * 69 * @throws Exception 70 * @since 4.2.0 71 */ 72 public function execute($task) 73 { 74 if (empty($task) || $task === 'display') { 75 $task = 'add'; 76 } 77 78 return parent::execute($task); 79 } 80 81 /** 82 * Add a new MFA Method 83 * 84 * @param boolean $cachable Ignored. This page is never cached. 85 * @param boolean|array $urlparams Ignored. This page is never cached. 86 * 87 * @return void 88 * @throws Exception 89 * @since 4.2.0 90 */ 91 public function add($cachable = false, $urlparams = []): void 92 { 93 $this->assertLoggedInUser(); 94 95 // Make sure I am allowed to edit the specified user 96 $userId = $this->input->getInt('user_id', null); 97 $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId); 98 99 $this->assertCanEdit($user); 100 101 // Also make sure the Method really does exist 102 $method = $this->input->getCmd('method'); 103 $this->assertMethodExists($method); 104 105 /** @var MethodModel $model */ 106 $model = $this->getModel('Method'); 107 $model->setState('method', $method); 108 109 // Pass the return URL to the view 110 $returnURL = $this->input->getBase64('returnurl'); 111 $viewLayout = $this->input->get('layout', 'default', 'string'); 112 $view = $this->getView('Method', 'html'); 113 $view->setLayout($viewLayout); 114 $view->returnURL = $returnURL; 115 $view->user = $user; 116 $view->document = $this->app->getDocument(); 117 118 $view->setModel($model, true); 119 120 $event = new NotifyActionLog('onComUsersControllerMethodBeforeAdd', [$user, $method]); 121 $this->app->getDispatcher()->dispatch($event->getName(), $event); 122 123 $view->display(); 124 } 125 126 /** 127 * Edit an existing MFA Method 128 * 129 * @param boolean $cachable Ignored. This page is never cached. 130 * @param boolean|array $urlparams Ignored. This page is never cached. 131 * 132 * @return void 133 * @throws Exception 134 * @since 4.2.0 135 */ 136 public function edit($cachable = false, $urlparams = []): void 137 { 138 $this->assertLoggedInUser(); 139 140 // Make sure I am allowed to edit the specified user 141 $userId = $this->input->getInt('user_id', null); 142 $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId); 143 144 $this->assertCanEdit($user); 145 146 // Also make sure the Method really does exist 147 $id = $this->input->getInt('id'); 148 $record = $this->assertValidRecordId($id, $user); 149 150 if ($id <= 0) { 151 throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403); 152 } 153 154 /** @var MethodModel $model */ 155 $model = $this->getModel('Method'); 156 $model->setState('id', $id); 157 158 // Pass the return URL to the view 159 $returnURL = $this->input->getBase64('returnurl'); 160 $viewLayout = $this->input->get('layout', 'default', 'string'); 161 $view = $this->getView('Method', 'html'); 162 $view->setLayout($viewLayout); 163 $view->returnURL = $returnURL; 164 $view->user = $user; 165 $view->document = $this->app->getDocument(); 166 167 $view->setModel($model, true); 168 169 $event = new NotifyActionLog('onComUsersControllerMethodBeforeEdit', [$id, $user]); 170 $this->app->getDispatcher()->dispatch($event->getName(), $event); 171 172 $view->display(); 173 } 174 175 /** 176 * Regenerate backup codes 177 * 178 * @param boolean $cachable Ignored. This page is never cached. 179 * @param boolean|array $urlparams Ignored. This page is never cached. 180 * 181 * @return void 182 * @throws Exception 183 * @since 4.2.0 184 */ 185 public function regenerateBackupCodes($cachable = false, $urlparams = []): void 186 { 187 $this->assertLoggedInUser(); 188 189 $this->checkToken($this->input->getMethod()); 190 191 // Make sure I am allowed to edit the specified user 192 $userId = $this->input->getInt('user_id', null); 193 $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId); 194 $this->assertCanEdit($user); 195 196 /** @var BackupcodesModel $model */ 197 $model = $this->getModel('Backupcodes'); 198 $model->regenerateBackupCodes($user); 199 200 $backupCodesRecord = $model->getBackupCodesRecord($user); 201 202 // Redirect 203 $redirectUrl = 'index.php?option=com_users&task=method.edit&user_id=' . $userId . '&id=' . $backupCodesRecord->id; 204 $returnURL = $this->input->getBase64('returnurl'); 205 206 if (!empty($returnURL)) { 207 $redirectUrl .= '&returnurl=' . $returnURL; 208 } 209 210 $this->setRedirect(Route::_($redirectUrl, false)); 211 212 $event = new NotifyActionLog('onComUsersControllerMethodAfterRegenerateBackupCodes'); 213 $this->app->getDispatcher()->dispatch($event->getName(), $event); 214 } 215 216 /** 217 * Delete an existing MFA Method 218 * 219 * @param boolean $cachable Ignored. This page is never cached. 220 * @param boolean|array $urlparams Ignored. This page is never cached. 221 * 222 * @return void 223 * @since 4.2.0 224 */ 225 public function delete($cachable = false, $urlparams = []): void 226 { 227 $this->assertLoggedInUser(); 228 229 $this->checkToken($this->input->getMethod()); 230 231 // Make sure I am allowed to edit the specified user 232 $userId = $this->input->getInt('user_id', null); 233 $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId); 234 $this->assertCanDelete($user); 235 236 // Also make sure the Method really does exist 237 $id = $this->input->getInt('id'); 238 $record = $this->assertValidRecordId($id, $user); 239 240 if ($id <= 0) { 241 throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403); 242 } 243 244 $type = null; 245 $message = null; 246 247 $event = new NotifyActionLog('onComUsersControllerMethodBeforeDelete', [$id, $user]); 248 $this->app->getDispatcher()->dispatch($event->getName(), $event); 249 250 try { 251 $record->delete(); 252 } catch (Exception $e) { 253 $message = $e->getMessage(); 254 $type = 'error'; 255 } 256 257 // Redirect 258 $url = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $userId, false); 259 $returnURL = $this->input->getBase64('returnurl'); 260 261 if (!empty($returnURL)) { 262 $url = base64_decode($returnURL); 263 } 264 265 $this->setRedirect($url, $message, $type); 266 } 267 268 /** 269 * Save the MFA Method 270 * 271 * @param boolean $cachable Ignored. This page is never cached. 272 * @param boolean|array $urlparams Ignored. This page is never cached. 273 * 274 * @return void 275 * @since 4.2.0 276 */ 277 public function save($cachable = false, $urlparams = []): void 278 { 279 $this->assertLoggedInUser(); 280 281 $this->checkToken($this->input->getMethod()); 282 283 // Make sure I am allowed to edit the specified user 284 $userId = $this->input->getInt('user_id', null); 285 $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId); 286 $this->assertCanEdit($user); 287 288 // Redirect 289 $url = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $userId, false); 290 $returnURL = $this->input->getBase64('returnurl'); 291 292 if (!empty($returnURL)) { 293 $url = base64_decode($returnURL); 294 } 295 296 // The record must either be new (ID zero) or exist 297 $id = $this->input->getInt('id', 0); 298 $record = $this->assertValidRecordId($id, $user); 299 300 // If it's a new record we need to read the Method from the request and update the (not yet created) record. 301 if ($record->id == 0) { 302 $methodName = $this->input->getCmd('method'); 303 $this->assertMethodExists($methodName); 304 $record->method = $methodName; 305 } 306 307 /** @var MethodModel $model */ 308 $model = $this->getModel('Method'); 309 310 // Ask the plugin to validate the input by calling onUserMultifactorSaveSetup 311 $result = []; 312 $input = $this->app->input; 313 314 $event = new NotifyActionLog('onComUsersControllerMethodBeforeSave', [$id, $user]); 315 $this->app->getDispatcher()->dispatch($event->getName(), $event); 316 317 try { 318 $event = new SaveSetup($record, $input); 319 $pluginResults = $this->app 320 ->getDispatcher() 321 ->dispatch($event->getName(), $event) 322 ->getArgument('result', []); 323 324 foreach ($pluginResults as $pluginResult) { 325 $result = array_merge($result, $pluginResult); 326 } 327 } catch (RuntimeException $e) { 328 // Go back to the edit page 329 $nonSefUrl = 'index.php?option=com_users&task=method.'; 330 331 if ($id) { 332 $nonSefUrl .= 'edit&id=' . (int) $id; 333 } else { 334 $nonSefUrl .= 'add&method=' . $record->method; 335 } 336 337 $nonSefUrl .= '&user_id=' . $userId; 338 339 if (!empty($returnURL)) { 340 $nonSefUrl .= '&returnurl=' . urlencode($returnURL); 341 } 342 343 $url = Route::_($nonSefUrl, false); 344 $this->setRedirect($url, $e->getMessage(), 'error'); 345 346 return; 347 } 348 349 // Update the record's options with the plugin response 350 $title = $this->input->getString('title', null); 351 $title = trim($title); 352 353 if (empty($title)) { 354 $method = $model->getMethod($record->method); 355 $title = $method['display']; 356 } 357 358 // Update the record's "default" flag 359 $default = $this->input->getBool('default', false); 360 $record->title = $title; 361 $record->options = $result; 362 $record->default = $default ? 1 : 0; 363 364 // Ask the model to save the record 365 $saved = $record->store(); 366 367 if (!$saved) { 368 // Go back to the edit page 369 $nonSefUrl = 'index.php?option=com_users&task=method.'; 370 371 if ($id) { 372 $nonSefUrl .= 'edit&id=' . (int) $id; 373 } else { 374 $nonSefUrl .= 'add'; 375 } 376 377 $nonSefUrl .= '&user_id=' . $userId; 378 379 if (!empty($returnURL)) { 380 $nonSefUrl .= '&returnurl=' . urlencode($returnURL); 381 } 382 383 $url = Route::_($nonSefUrl, false); 384 $this->setRedirect($url, $record->getError(), 'error'); 385 386 return; 387 } 388 389 $this->setRedirect($url); 390 } 391 392 /** 393 * Assert that the provided ID is a valid record identified for the given user 394 * 395 * @param int $id Record ID to check 396 * @param User|null $user User record. Null to use current user. 397 * 398 * @return MfaTable The loaded record 399 * @since 4.2.0 400 */ 401 private function assertValidRecordId($id, ?User $user = null): MfaTable 402 { 403 if (is_null($user)) { 404 $user = $this->app->getIdentity() 405 ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0); 406 } 407 408 /** @var MethodModel $model */ 409 $model = $this->getModel('Method'); 410 411 $model->setState('id', $id); 412 413 $record = $model->getRecord($user); 414 415 if (is_null($record) || ($record->id != $id) || ($record->user_id != $user->id)) { 416 throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403); 417 } 418 419 return $record; 420 } 421 422 /** 423 * Assert that the user can add / edit MFA methods. 424 * 425 * @param User|null $user User record. Null to use current user. 426 * 427 * @return void 428 * @throws RuntimeException|Exception 429 * @since 4.2.0 430 */ 431 private function assertCanEdit(?User $user = null): void 432 { 433 if (!MfaHelper::canAddEditMethod($user)) { 434 throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403); 435 } 436 } 437 438 /** 439 * Assert that the user can delete MFA records / disable MFA. 440 * 441 * @param User|null $user User record. Null to use current user. 442 * 443 * @return void 444 * @throws RuntimeException|Exception 445 * @since 4.2.0 446 */ 447 private function assertCanDelete(?User $user = null): void 448 { 449 if (!MfaHelper::canDeleteMethod($user)) { 450 throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403); 451 } 452 } 453 454 /** 455 * Assert that the specified MFA Method exists, is activated and enabled for the current user 456 * 457 * @param string|null $method The Method to check 458 * 459 * @return void 460 * @since 4.2.0 461 */ 462 private function assertMethodExists(?string $method): void 463 { 464 /** @var MethodModel $model */ 465 $model = $this->getModel('Method'); 466 467 if (empty($method) || !$model->methodExists($method)) { 468 throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403); 469 } 470 } 471 472 /** 473 * Assert that there is a logged in user. 474 * 475 * @return void 476 * @since 4.2.0 477 */ 478 private function assertLoggedInUser(): void 479 { 480 $user = $this->app->getIdentity() 481 ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0); 482 483 if ($user->guest) { 484 throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403); 485 } 486 } 487 }
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 |