[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Plugin 5 * @subpackage Multifactorauth.totp 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\Plugin\Multifactorauth\Totp\Extension; 12 13 use Joomla\CMS\Encrypt\Totp as TotpHelper; 14 use Joomla\CMS\Event\MultiFactor\Captive; 15 use Joomla\CMS\Event\MultiFactor\GetMethod; 16 use Joomla\CMS\Event\MultiFactor\GetSetup; 17 use Joomla\CMS\Event\MultiFactor\SaveSetup; 18 use Joomla\CMS\Event\MultiFactor\Validate; 19 use Joomla\CMS\Factory; 20 use Joomla\CMS\Language\Text; 21 use Joomla\CMS\Plugin\CMSPlugin; 22 use Joomla\CMS\Uri\Uri; 23 use Joomla\CMS\User\User; 24 use Joomla\CMS\User\UserFactoryInterface; 25 use Joomla\Component\Users\Administrator\DataShape\CaptiveRenderOptions; 26 use Joomla\Component\Users\Administrator\DataShape\MethodDescriptor; 27 use Joomla\Component\Users\Administrator\DataShape\SetupRenderOptions; 28 use Joomla\Component\Users\Administrator\Table\MfaTable; 29 use Joomla\Event\SubscriberInterface; 30 use Joomla\Input\Input; 31 use RuntimeException; 32 33 // phpcs:disable PSR1.Files.SideEffects 34 \defined('_JEXEC') or die; 35 // phpcs:enable PSR1.Files.SideEffects 36 37 /** 38 * Joomla! Multi-factor Authentication using Google Authenticator TOTP Plugin 39 * 40 * @since 3.2 41 */ 42 class Totp extends CMSPlugin implements SubscriberInterface 43 { 44 /** 45 * Affects constructor behavior. If true, language files will be loaded automatically. 46 * 47 * @var boolean 48 * @since 3.2 49 */ 50 protected $autoloadLanguage = true; 51 52 /** 53 * The MFA Method name handled by this plugin 54 * 55 * @var string 56 * @since 4.2.0 57 */ 58 private $mfaMethodName = 'totp'; 59 60 /** 61 * Should I try to detect and register legacy event listeners? 62 * 63 * @var boolean 64 * @since 4.2.0 65 * 66 * @deprecated 67 */ 68 protected $allowLegacyListeners = false; 69 70 /** 71 * Returns an array of events this subscriber will listen to. 72 * 73 * @return array 74 * 75 * @since 4.2.0 76 */ 77 public static function getSubscribedEvents(): array 78 { 79 return [ 80 'onUserMultifactorGetMethod' => 'onUserMultifactorGetMethod', 81 'onUserMultifactorCaptive' => 'onUserMultifactorCaptive', 82 'onUserMultifactorGetSetup' => 'onUserMultifactorGetSetup', 83 'onUserMultifactorSaveSetup' => 'onUserMultifactorSaveSetup', 84 'onUserMultifactorValidate' => 'onUserMultifactorValidate', 85 ]; 86 } 87 88 /** 89 * Gets the identity of this MFA Method 90 * 91 * @param GetMethod $event The event we are handling 92 * 93 * @return void 94 * @since 4.2.0 95 */ 96 public function onUserMultifactorGetMethod(GetMethod $event): void 97 { 98 $event->addResult( 99 new MethodDescriptor( 100 [ 101 'name' => $this->mfaMethodName, 102 'display' => Text::_('PLG_MULTIFACTORAUTH_TOTP_METHOD_TITLE'), 103 'shortinfo' => Text::_('PLG_MULTIFACTORAUTH_TOTP_SHORTINFO'), 104 'image' => 'media/plg_multifactorauth_totp/images/totp.svg', 105 ] 106 ) 107 ); 108 } 109 110 /** 111 * Returns the information which allows Joomla to render the Captive MFA page. This is the page 112 * which appears right after you log in and asks you to validate your login with MFA. 113 * 114 * @param Captive $event The event we are handling 115 * 116 * @return void 117 * @since 4.2.0 118 */ 119 public function onUserMultifactorCaptive(Captive $event): void 120 { 121 /** 122 * @var MfaTable $record The record currently selected by the user. 123 */ 124 $record = $event['record']; 125 126 // Make sure we are actually meant to handle this Method 127 if ($record->method !== $this->mfaMethodName) { 128 return; 129 } 130 131 $event->addResult( 132 new CaptiveRenderOptions( 133 [ 134 // Custom HTML to display above the MFA form 135 'pre_message' => Text::_('PLG_MULTIFACTORAUTH_TOTP_CAPTIVE_PROMPT'), 136 // How to render the MFA code field. "input" (HTML input element) or "custom" (custom HTML) 137 'field_type' => 'input', 138 // The type attribute for the HTML input box. Typically "text" or "password". Use any HTML5 input type. 139 'input_type' => 'text', 140 // The attributes for the HTML input box. 141 'input_attributes' => [ 142 'pattern' => "{0,9}", 'maxlength' => "6", 'inputmode' => "numeric" 143 ], 144 // Placeholder text for the HTML input box. Leave empty if you don't need it. 145 'placeholder' => '', 146 // Label to show above the HTML input box. Leave empty if you don't need it. 147 'label' => Text::_('PLG_MULTIFACTORAUTH_TOTP_LBL_LABEL'), 148 // Custom HTML. Only used when field_type = custom. 149 'html' => '', 150 // Custom HTML to display below the MFA form 151 'post_message' => '', 152 ] 153 ) 154 ); 155 } 156 157 /** 158 * Returns the information which allows Joomla to render the MFA setup page. This is the page 159 * which allows the user to add or modify a MFA Method for their user account. If the record 160 * does not correspond to your plugin return an empty array. 161 * 162 * @param GetSetup $event The event we are handling 163 * 164 * @return void 165 * @since 4.2.0 166 */ 167 public function onUserMultifactorGetSetup(GetSetup $event): void 168 { 169 /** 170 * @var MfaTable $record The record currently selected by the user. 171 */ 172 $record = $event['record']; 173 174 // Make sure we are actually meant to handle this Method 175 if ($record->method !== $this->mfaMethodName) { 176 return; 177 } 178 179 $totp = new TotpHelper(); 180 181 // Load the options from the record (if any) 182 $options = $this->decodeRecordOptions($record); 183 $key = $options['key'] ?? ''; 184 $session = $this->getApplication()->getSession(); 185 $isConfigured = !empty($key); 186 187 // If there's a key in the session use that instead. 188 $sessionKey = $session->get('com_users.totp.key', null); 189 190 if (!empty($sessionKey)) { 191 $key = $sessionKey; 192 } 193 194 // If there's still no key in the options, generate one and save it in the session 195 if (empty($key)) { 196 $key = $totp->generateSecret(); 197 $session->set('com_users.totp.key', $key); 198 } 199 200 // Generate a QR code for the key 201 $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($record->user_id); 202 $hostname = Uri::getInstance()->toString(['host']); 203 $otpURL = sprintf("otpauth://totp/%s@%s?secret=%s", $user->username, $hostname, $key); 204 $document = $this->getApplication()->getDocument(); 205 $wam = $document->getWebAssetManager(); 206 207 $document->addScriptOptions('plg_multifactorauth_totp.totp.qr', $otpURL); 208 209 $wam->getRegistry()->addExtensionRegistryFile('plg_multifactorauth_totp'); 210 $wam->useScript('plg_multifactorauth_totp.setup'); 211 212 $event->addResult( 213 new SetupRenderOptions( 214 [ 215 'default_title' => Text::_('PLG_MULTIFACTORAUTH_TOTP_METHOD_TITLE'), 216 'pre_message' => Text::_('PLG_MULTIFACTORAUTH_TOTP_LBL_SETUP_INSTRUCTIONS'), 217 'table_heading' => Text::_('PLG_MULTIFACTORAUTH_TOTP_LBL_SETUP_TABLE_HEADING'), 218 'tabular_data' => [ 219 '' => Text::_('PLG_MULTIFACTORAUTH_TOTP_LBL_SETUP_TABLE_SUBHEAD'), 220 Text::_('PLG_MULTIFACTORAUTH_TOTP_LBL_SETUP_TABLE_KEY') => $key, 221 Text::_('PLG_MULTIFACTORAUTH_TOTP_LBL_SETUP_TABLE_QR') => "<span id=\"users-mfa-totp-qrcode\" />", 222 Text::_('PLG_MULTIFACTORAUTH_TOTP_LBL_SETUP_TABLE_LINK') 223 => Text::sprintf('PLG_MULTIFACTORAUTH_TOTP_LBL_SETUP_TABLE_LINK_TEXT', $otpURL) . 224 '<br/><small>' . Text::_('PLG_MULTIFACTORAUTH_TOTP_LBL_SETUP_TABLE_LINK_NOTE') . '</small>', 225 ], 226 'hidden_data' => [ 227 'key' => $key, 228 ], 229 'input_type' => $isConfigured ? 'hidden' : 'text', 230 'input_attributes' => [ 231 'pattern' => "{0,9}", 'maxlength' => "6", 'inputmode' => "numeric" 232 ], 233 'input_value' => '', 234 'placeholder' => Text::_('PLG_MULTIFACTORAUTH_TOTP_LBL_SETUP_PLACEHOLDER'), 235 'label' => Text::_('PLG_MULTIFACTORAUTH_TOTP_LBL_LABEL'), 236 ] 237 ) 238 ); 239 } 240 241 /** 242 * Parse the input from the MFA setup page and return the configuration information to be saved to the database. If 243 * the information is invalid throw a RuntimeException to signal the need to display the editor page again. The 244 * message of the exception will be displayed to the user. If the record does not correspond to your plugin return 245 * an empty array. 246 * 247 * @param SaveSetup $event The event we are handling 248 * 249 * @return void The configuration data to save to the database 250 * @since 4.2.0 251 */ 252 public function onUserMultifactorSaveSetup(SaveSetup $event): void 253 { 254 /** 255 * @var MfaTable $record The record currently selected by the user. 256 * @var Input $input The user input you are going to take into account. 257 */ 258 $record = $event['record']; 259 $input = $event['input']; 260 261 // Make sure we are actually meant to handle this Method 262 if ($record->method != $this->mfaMethodName) { 263 return; 264 } 265 266 // Load the options from the record (if any) 267 $options = $this->decodeRecordOptions($record); 268 $optionsKey = $options['key'] ?? ''; 269 $key = $optionsKey; 270 $session = $this->getApplication()->getSession(); 271 272 // If there is no key in the options fetch one from the session 273 if (empty($key)) { 274 $key = $session->get('com_users.totp.key', null); 275 } 276 277 // If there is still no key in the options throw an error 278 if (empty($key)) { 279 throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403); 280 } 281 282 /** 283 * If the code is empty but the key already existed in $options someone is simply changing the title / default 284 * Method status. We can allow this and stop checking anything else now. 285 */ 286 $code = $input->getInt('code'); 287 288 if (empty($code) && !empty($optionsKey)) { 289 $event->addResult($options); 290 291 return; 292 } 293 294 // In any other case validate the submitted code 295 $totp = new TotpHelper(); 296 $isValid = $totp->checkCode($key, $code); 297 298 if (!$isValid) { 299 throw new RuntimeException(Text::_('PLG_MULTIFACTORAUTH_TOTP_ERR_VALIDATIONFAILED'), 500); 300 } 301 302 // The code is valid. Unset the key from the session. 303 $session->set('com_users.totp.key', null); 304 305 // Return the configuration to be serialized 306 $event->addResult( 307 [ 308 'key' => $key, 309 ] 310 ); 311 } 312 313 /** 314 * Validates the Multi-factor Authentication code submitted by the user in the Multi-Factor 315 * Authentication page. If the record does not correspond to your plugin return FALSE. 316 * 317 * @param Validate $event The event we are handling 318 * 319 * @return void 320 * @since 4.2.0 321 */ 322 public function onUserMultifactorValidate(Validate $event): void 323 { 324 /** 325 * @var MfaTable $record The MFA Method's record you're validating against 326 * @var User $user The user record 327 * @var string $code The submitted code 328 */ 329 $record = $event['record']; 330 $user = $event['user']; 331 $code = $event['code']; 332 333 // Make sure we are actually meant to handle this Method 334 if ($record->method !== $this->mfaMethodName) { 335 $event->addResult(false); 336 337 return; 338 } 339 340 // Double check the MFA Method is for the correct user 341 if ($user->id != $record->user_id) { 342 $event->addResult(false); 343 344 return; 345 } 346 347 // Load the options from the record (if any) 348 $options = $this->decodeRecordOptions($record); 349 $key = $options['key'] ?? ''; 350 351 // If there is no key in the options throw an error 352 if (empty($key)) { 353 $event->addResult(false); 354 355 return; 356 } 357 358 // Check the MFA code for validity 359 $event->addResult((new TotpHelper())->checkCode($key, $code)); 360 } 361 362 /** 363 * Decodes the options from a record into an options object. 364 * 365 * @param MfaTable $record The record to decode options for 366 * 367 * @return array 368 * @since 4.2.0 369 */ 370 private function decodeRecordOptions(MfaTable $record): array 371 { 372 $options = [ 373 'key' => '', 374 ]; 375 376 if (!empty($record->options)) { 377 $recordOptions = $record->options; 378 379 $options = array_merge($options, $recordOptions); 380 } 381 382 return $options; 383 } 384 }
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 |