[ 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\Helper; 12 13 use Exception; 14 use Joomla\CMS\Application\CMSApplication; 15 use Joomla\CMS\Component\ComponentHelper; 16 use Joomla\CMS\Document\HtmlDocument; 17 use Joomla\CMS\Event\MultiFactor\GetMethod; 18 use Joomla\CMS\Factory; 19 use Joomla\CMS\MVC\Factory\MVCFactoryInterface; 20 use Joomla\CMS\Plugin\PluginHelper; 21 use Joomla\CMS\Uri\Uri; 22 use Joomla\CMS\User\User; 23 use Joomla\CMS\User\UserFactoryInterface; 24 use Joomla\Component\Users\Administrator\DataShape\MethodDescriptor; 25 use Joomla\Component\Users\Administrator\Model\BackupcodesModel; 26 use Joomla\Component\Users\Administrator\Model\MethodsModel; 27 use Joomla\Component\Users\Administrator\Table\MfaTable; 28 use Joomla\Component\Users\Administrator\View\Methods\HtmlView; 29 use Joomla\Database\DatabaseDriver; 30 use Joomla\Database\ParameterType; 31 32 // phpcs:disable PSR1.Files.SideEffects 33 \defined('_JEXEC') or die; 34 // phpcs:enable PSR1.Files.SideEffects 35 36 /** 37 * Helper functions for captive MFA handling 38 * 39 * @since 4.2.0 40 */ 41 abstract class Mfa 42 { 43 /** 44 * Cache of all currently active MFAs 45 * 46 * @var array|null 47 * @since 4.2.0 48 */ 49 protected static $allMFAs = null; 50 51 /** 52 * Are we inside the administrator application 53 * 54 * @var boolean 55 * @since 4.2.0 56 */ 57 protected static $isAdmin = null; 58 59 /** 60 * Get the HTML for the Multi-factor Authentication configuration interface for a user. 61 * 62 * This helper method uses a sort of primitive HMVC to display the com_users' Methods page which 63 * renders the MFA configuration interface. 64 * 65 * @param User $user The user we are going to show the configuration UI for. 66 * 67 * @return string|null The HTML of the UI; null if we cannot / must not show it. 68 * @throws Exception 69 * @since 4.2.0 70 */ 71 public static function getConfigurationInterface(User $user): ?string 72 { 73 // Check the conditions 74 if (!self::canShowConfigurationInterface($user)) { 75 return null; 76 } 77 78 /** @var CMSApplication $app */ 79 $app = Factory::getApplication(); 80 81 if (!$app->input->getCmd('option', '') === 'com_users') { 82 $app->getLanguage()->load('com_users'); 83 $app->getDocument() 84 ->getWebAssetManager() 85 ->getRegistry() 86 ->addExtensionRegistryFile('com_users'); 87 } 88 89 // Get a model 90 /** @var MVCFactoryInterface $factory */ 91 $factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory(); 92 93 /** @var MethodsModel $methodsModel */ 94 $methodsModel = $factory->createModel('Methods', 'Administrator'); 95 /** @var BackupcodesModel $methodsModel */ 96 $backupCodesModel = $factory->createModel('Backupcodes', 'Administrator'); 97 98 // Get a view object 99 $appRoot = $app->isClient('site') ? \JPATH_SITE : \JPATH_ADMINISTRATOR; 100 $prefix = $app->isClient('site') ? 'Site' : 'Administrator'; 101 /** @var HtmlView $view */ 102 $view = $factory->createView( 103 'Methods', 104 $prefix, 105 'Html', 106 [ 107 'base_path' => $appRoot . '/components/com_users', 108 ] 109 ); 110 $view->setModel($methodsModel, true); 111 /** @noinspection PhpParamsInspection */ 112 $view->setModel($backupCodesModel); 113 $view->document = $app->getDocument(); 114 $view->returnURL = base64_encode(Uri::getInstance()->toString()); 115 $view->user = $user; 116 $view->set('forHMVC', true); 117 118 @ob_start(); 119 120 try { 121 $view->display(); 122 } catch (\Throwable $e) { 123 @ob_end_clean(); 124 125 /** 126 * This is intentional! When you are developing a Multi-factor Authentication plugin you 127 * will inevitably mess something up and end up with an error. This would cause the 128 * entire MFA configuration page to disappear. No problem! Set Debug System to Yes in 129 * Global Configuration and you can see the error exception which will help you solve 130 * your problem. 131 */ 132 if (defined('JDEBUG') && JDEBUG) { 133 throw $e; 134 } 135 136 return null; 137 } 138 139 return @ob_get_clean(); 140 } 141 142 /** 143 * Get a list of all of the MFA Methods 144 * 145 * @return MethodDescriptor[] 146 * @since 4.2.0 147 */ 148 public static function getMfaMethods(): array 149 { 150 PluginHelper::importPlugin('multifactorauth'); 151 152 if (is_null(self::$allMFAs)) { 153 // Get all the plugin results 154 $event = new GetMethod(); 155 $temp = Factory::getApplication() 156 ->getDispatcher() 157 ->dispatch($event->getName(), $event) 158 ->getArgument('result', []); 159 160 // Normalize the results 161 self::$allMFAs = []; 162 163 foreach ($temp as $method) { 164 if (!is_array($method) && !($method instanceof MethodDescriptor)) { 165 continue; 166 } 167 168 $method = $method instanceof MethodDescriptor 169 ? $method : new MethodDescriptor($method); 170 171 if (empty($method['name'])) { 172 continue; 173 } 174 175 self::$allMFAs[$method['name']] = $method; 176 } 177 } 178 179 return self::$allMFAs; 180 } 181 182 /** 183 * Is the current user allowed to add/edit MFA methods for $user? 184 * 185 * This is only allowed if I am adding / editing methods for myself. 186 * 187 * If the target user is a member of any group disallowed to use MFA this will return false. 188 * 189 * @param User|null $user The user you want to know if we're allowed to edit 190 * 191 * @return boolean 192 * @throws Exception 193 * @since 4.2.0 194 */ 195 public static function canAddEditMethod(?User $user = null): bool 196 { 197 // Cannot do MFA operations on no user or a guest user. 198 if (is_null($user) || $user->guest) { 199 return false; 200 } 201 202 // If the user is in a user group which disallows MFA we cannot allow adding / editing methods. 203 $neverMFAGroups = ComponentHelper::getParams('com_users')->get('neverMFAUserGroups', []); 204 $neverMFAGroups = is_array($neverMFAGroups) ? $neverMFAGroups : []; 205 206 if (count(array_intersect($user->getAuthorisedGroups(), $neverMFAGroups))) { 207 return false; 208 } 209 210 // Check if this is the same as the logged-in user. 211 $myUser = Factory::getApplication()->getIdentity() 212 ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0); 213 214 return $myUser->id === $user->id; 215 } 216 217 /** 218 * Is the current user allowed to delete MFA methods / disable MFA for $user? 219 * 220 * This is allowed if: 221 * - The user being queried is the same as the logged-in user 222 * - The logged-in user is a Super User AND the queried user is NOT a Super User. 223 * 224 * Note that Super Users can be edited by their own user only for security reasons. If a Super 225 * User gets locked out they must use the Backup Codes to regain access. If that's not possible, 226 * they will need to delete their records from the `#__user_mfa` table. 227 * 228 * @param User|null $user The user being queried. 229 * 230 * @return boolean 231 * @throws Exception 232 * @since 4.2.0 233 */ 234 public static function canDeleteMethod(?User $user = null): bool 235 { 236 // Cannot do MFA operations on no user or a guest user. 237 if (is_null($user) || $user->guest) { 238 return false; 239 } 240 241 $myUser = Factory::getApplication()->getIdentity() 242 ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0); 243 244 return $myUser->id === $user->id 245 || ($myUser->authorise('core.admin') && !$user->authorise('core.admin')); 246 } 247 248 /** 249 * Return all MFA records for a specific user 250 * 251 * @param int|null $userId User ID. NULL for currently logged in user. 252 * 253 * @return MfaTable[] 254 * @throws Exception 255 * 256 * @since 4.2.0 257 */ 258 public static function getUserMfaRecords(?int $userId): array 259 { 260 if (empty($userId)) { 261 $user = Factory::getApplication()->getIdentity() ?: Factory::getUser(); 262 $userId = $user->id ?: 0; 263 } 264 265 /** @var DatabaseDriver $db */ 266 $db = Factory::getContainer()->get('DatabaseDriver'); 267 $query = $db->getQuery(true) 268 ->select($db->quoteName('id')) 269 ->from($db->quoteName('#__user_mfa')) 270 ->where($db->quoteName('user_id') . ' = :user_id') 271 ->bind(':user_id', $userId, ParameterType::INTEGER); 272 273 try { 274 $ids = $db->setQuery($query)->loadColumn() ?: []; 275 } catch (Exception $e) { 276 $ids = []; 277 } 278 279 if (empty($ids)) { 280 return []; 281 } 282 283 /** @var MVCFactoryInterface $factory */ 284 $factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory(); 285 286 // Map all results to MFA table objects 287 $records = array_map( 288 function ($id) use ($factory) { 289 /** @var MfaTable $record */ 290 $record = $factory->createTable('Mfa', 'Administrator'); 291 $loaded = $record->load($id); 292 293 return $loaded ? $record : null; 294 }, 295 $ids 296 ); 297 298 // Let's remove Methods we couldn't decrypt when reading from the database. 299 $hasBackupCodes = false; 300 301 $records = array_filter( 302 $records, 303 function ($record) use (&$hasBackupCodes) { 304 $isValid = !is_null($record) && (!empty($record->options)); 305 306 if ($isValid && ($record->method === 'backupcodes')) { 307 $hasBackupCodes = true; 308 } 309 310 return $isValid; 311 } 312 ); 313 314 // If the only Method is backup codes it's as good as having no records 315 if ((count($records) === 1) && $hasBackupCodes) { 316 return []; 317 } 318 319 return $records; 320 } 321 322 /** 323 * Are the conditions for showing the MFA configuration interface met? 324 * 325 * @param User|null $user The user to be configured 326 * 327 * @return boolean 328 * @throws Exception 329 * @since 4.2.0 330 */ 331 public static function canShowConfigurationInterface(?User $user = null): bool 332 { 333 // If I have no user to check against that's all the checking I can do. 334 if (empty($user)) { 335 return false; 336 } 337 338 // I need at least one MFA method plugin for the setup interface to make any sense. 339 $plugins = PluginHelper::getPlugin('multifactorauth'); 340 341 if (count($plugins) < 1) { 342 return false; 343 } 344 345 /** @var CMSApplication $app */ 346 $app = Factory::getApplication(); 347 348 // We can only show a configuration page in the front- or backend application. 349 if (!$app->isClient('site') && !$app->isClient('administrator')) { 350 return false; 351 } 352 353 // Only show the configuration page if we have an HTML document 354 if (!($app->getDocument() instanceof HtmlDocument)) { 355 return false; 356 } 357 358 // I must be able to add, edit or delete the user's MFA settings 359 return self::canAddEditMethod($user) || self::canDeleteMethod($user); 360 } 361 }
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 |