primo commit
This commit is contained in:
		
							
								
								
									
										319
									
								
								plugins/multifactorauth/fixed/src/Extension/Fixed.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								plugins/multifactorauth/fixed/src/Extension/Fixed.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,319 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * @package     Joomla.Plugin | ||||
|  * @subpackage  Multifactorauth.fixed | ||||
|  * | ||||
|  * @copyright   (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> | ||||
|  * @license     GNU General Public License version 2 or later; see LICENSE.txt | ||||
|  */ | ||||
|  | ||||
| namespace Joomla\Plugin\Multifactorauth\Fixed\Extension; | ||||
|  | ||||
| use Joomla\CMS\Event\MultiFactor\Captive; | ||||
| use Joomla\CMS\Event\MultiFactor\GetMethod; | ||||
| use Joomla\CMS\Event\MultiFactor\GetSetup; | ||||
| use Joomla\CMS\Event\MultiFactor\SaveSetup; | ||||
| use Joomla\CMS\Event\MultiFactor\Validate; | ||||
| use Joomla\CMS\Language\Text; | ||||
| use Joomla\CMS\Plugin\CMSPlugin; | ||||
| use Joomla\CMS\User\User; | ||||
| use Joomla\Component\Users\Administrator\DataShape\CaptiveRenderOptions; | ||||
| use Joomla\Component\Users\Administrator\DataShape\MethodDescriptor; | ||||
| use Joomla\Component\Users\Administrator\DataShape\SetupRenderOptions; | ||||
| use Joomla\Component\Users\Administrator\Table\MfaTable; | ||||
| use Joomla\Event\SubscriberInterface; | ||||
| use Joomla\Input\Input; | ||||
|  | ||||
| // phpcs:disable PSR1.Files.SideEffects | ||||
| \defined('_JEXEC') or die; | ||||
| // phpcs:enable PSR1.Files.SideEffects | ||||
|  | ||||
| /** | ||||
|  * Joomla! Multi-factor Authentication using a fixed code. | ||||
|  * | ||||
|  * Requires a static string (password), different for each user. It effectively works as a second | ||||
|  * password. The fixed code is stored hashed, like a regular password. | ||||
|  * | ||||
|  * This is NOT to be used on production sites. It serves as a demonstration plugin and as a template | ||||
|  * for developers to create their own custom Multi-factor Authentication plugins. | ||||
|  * | ||||
|  * @since 4.2.0 | ||||
|  */ | ||||
| class Fixed extends CMSPlugin implements SubscriberInterface | ||||
| { | ||||
|     /** | ||||
|      * Affects constructor behavior. If true, language files will be loaded automatically. | ||||
|      * | ||||
|      * @var    boolean | ||||
|      * @since  4.2.0 | ||||
|      */ | ||||
|     protected $autoloadLanguage = true; | ||||
|  | ||||
|     /** | ||||
|      * The MFA Method name handled by this plugin | ||||
|      * | ||||
|      * @var   string | ||||
|      * @since 4.2.0 | ||||
|      */ | ||||
|     private $mfaMethodName = 'fixed'; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Should I try to detect and register legacy event listeners, i.e. methods which accept unwrapped arguments? While | ||||
|      * this maintains a great degree of backwards compatibility to Joomla! 3.x-style plugins it is much slower. You are | ||||
|      * advised to implement your plugins using proper Listeners, methods accepting an AbstractEvent as their sole | ||||
|      * parameter, for best performance. Also bear in mind that Joomla! 5.x onwards will only allow proper listeners, | ||||
|      * removing support for legacy Listeners. | ||||
|      * | ||||
|      * @var    boolean | ||||
|      * @since  4.2.0 | ||||
|      * | ||||
|      * @deprecated  4.3 will be removed in 6.0 | ||||
|      *              Implement your plugin methods accepting an AbstractEvent object | ||||
|      *              Example: | ||||
|      *              onEventTriggerName(AbstractEvent $event) { | ||||
|      *                  $context = $event->getArgument(...); | ||||
|      *              } | ||||
|      */ | ||||
|     protected $allowLegacyListeners = false; | ||||
|  | ||||
|     /** | ||||
|      * Returns an array of events this subscriber will listen to. | ||||
|      * | ||||
|      * @return  array | ||||
|      * | ||||
|      * @since   4.2.0 | ||||
|      */ | ||||
|     public static function getSubscribedEvents(): array | ||||
|     { | ||||
|         return [ | ||||
|             'onUserMultifactorGetMethod' => 'onUserMultifactorGetMethod', | ||||
|             'onUserMultifactorCaptive'   => 'onUserMultifactorCaptive', | ||||
|             'onUserMultifactorGetSetup'  => 'onUserMultifactorGetSetup', | ||||
|             'onUserMultifactorSaveSetup' => 'onUserMultifactorSaveSetup', | ||||
|             'onUserMultifactorValidate'  => 'onUserMultifactorValidate', | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the identity of this MFA Method | ||||
|      * | ||||
|      * @param   GetMethod  $event  The event we are handling | ||||
|      * | ||||
|      * @return  void | ||||
|      * @since   4.2.0 | ||||
|      */ | ||||
|     public function onUserMultifactorGetMethod(GetMethod $event): void | ||||
|     { | ||||
|         $event->addResult( | ||||
|             new MethodDescriptor( | ||||
|                 [ | ||||
|                     'name'      => $this->mfaMethodName, | ||||
|                     'display'   => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_DISPLAYEDAS'), | ||||
|                     'shortinfo' => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_SHORTINFO'), | ||||
|                     'image'     => 'media/plg_multifactorauth_fixed/images/fixed.svg', | ||||
|                 ] | ||||
|             ) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the information which allows Joomla to render the Captive MFA page. This is the page | ||||
|      * which appears right after you log in and asks you to validate your login with MFA. | ||||
|      * | ||||
|      * @param   Captive  $event  The event we are handling | ||||
|      * | ||||
|      * @return  void | ||||
|      * @since   4.2.0 | ||||
|      */ | ||||
|     public function onUserMultifactorCaptive(Captive $event): void | ||||
|     { | ||||
|         /** | ||||
|          * @var   MfaTable $record The record currently selected by the user. | ||||
|          */ | ||||
|         $record = $event['record']; | ||||
|  | ||||
|         // Make sure we are actually meant to handle this Method | ||||
|         if ($record->method != $this->mfaMethodName) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $event->addResult( | ||||
|             new CaptiveRenderOptions( | ||||
|                 [ | ||||
|                     // Custom HTML to display above the MFA form | ||||
|                     'pre_message' => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_PREMESSAGE'), | ||||
|                     // How to render the MFA code field. "input" (HTML input element) or "custom" (custom HTML) | ||||
|                     'field_type' => 'input', | ||||
|                     // The type attribute for the HTML input box. Typically "text" or "password". Use any HTML5 input type. | ||||
|                     'input_type' => 'password', | ||||
|                     // Placeholder text for the HTML input box. Leave empty if you don't need it. | ||||
|                     'placeholder' => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_PLACEHOLDER'), | ||||
|                     // Label to show above the HTML input box. Leave empty if you don't need it. | ||||
|                     'label' => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_LABEL'), | ||||
|                     // Custom HTML. Only used when field_type = custom. | ||||
|                     'html' => '', | ||||
|                     // Custom HTML to display below the MFA form | ||||
|                     'post_message' => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_POSTMESSAGE'), | ||||
|                     // Override the autocomplete attribute for the HTML input box. | ||||
|                     'autocomplete' => 'off', | ||||
|                 ] | ||||
|             ) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the information which allows Joomla to render the MFA setup page. This is the page | ||||
|      * which allows the user to add or modify a MFA Method for their user account. If the record | ||||
|      * does not correspond to your plugin return an empty array. | ||||
|      * | ||||
|      * @param   GetSetup  $event  The event we are handling | ||||
|      * | ||||
|      * @return  void | ||||
|      * @since   4.2.0 | ||||
|      */ | ||||
|     public function onUserMultifactorGetSetup(GetSetup $event): void | ||||
|     { | ||||
|         /** @var MfaTable $record The record currently selected by the user. */ | ||||
|         $record = $event['record']; | ||||
|  | ||||
|         // Make sure we are actually meant to handle this Method | ||||
|         if ($record->method != $this->mfaMethodName) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Load the options from the record (if any) | ||||
|         $options = $this->decodeRecordOptions($record); | ||||
|  | ||||
|         /** | ||||
|          * Return the parameters used to render the GUI. | ||||
|          * | ||||
|          * Some MFA Methods need to display a different interface before and after the setup. For example, when setting | ||||
|          * up Google Authenticator or a hardware OTP dongle you need the user to enter a MFA code to verify they are in | ||||
|          * possession of a correctly configured device. After the setup is complete you don't want them to see that | ||||
|          * field again. In the first state you could use the tabular_data to display the setup values, pre_message to | ||||
|          * display the QR code and field_type=input to let the user enter the MFA code. In the second state do the same | ||||
|          * BUT set field_type=custom, set html='' and show_submit=false to effectively hide the setup form from the | ||||
|          * user. | ||||
|          */ | ||||
|         $event->addResult( | ||||
|             new SetupRenderOptions( | ||||
|                 [ | ||||
|                     'default_title' => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_DEFAULTTITLE'), | ||||
|                     'pre_message'   => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_SETUP_PREMESSAGE'), | ||||
|                     'field_type'    => 'input', | ||||
|                     'input_type'    => 'password', | ||||
|                     'input_value'   => $options->fixed_code, | ||||
|                     'placeholder'   => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_PLACEHOLDER'), | ||||
|                     'label'         => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_LABEL'), | ||||
|                     'post_message'  => Text::_('PLG_MULTIFACTORAUTH_FIXED_LBL_SETUP_POSTMESSAGE'), | ||||
|                 ] | ||||
|             ) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Parse the input from the MFA setup page and return the configuration information to be saved to the database. If | ||||
|      * the information is invalid throw a RuntimeException to signal the need to display the editor page again. The | ||||
|      * message of the exception will be displayed to the user. If the record does not correspond to your plugin return | ||||
|      * an empty array. | ||||
|      * | ||||
|      * @param   SaveSetup  $event  The event we are handling | ||||
|      * | ||||
|      * @return  void The configuration data to save to the database | ||||
|      * @since   4.2.0 | ||||
|      */ | ||||
|     public function onUserMultifactorSaveSetup(SaveSetup $event): void | ||||
|     { | ||||
|         /** | ||||
|          * @var MfaTable $record The record currently selected by the user. | ||||
|          * @var Input    $input  The user input you are going to take into account. | ||||
|          */ | ||||
|         $record = $event['record']; | ||||
|         $input  = $event['input']; | ||||
|  | ||||
|         // Make sure we are actually meant to handle this Method | ||||
|         if ($record->method != $this->mfaMethodName) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Load the options from the record (if any) | ||||
|         $options = $this->decodeRecordOptions($record); | ||||
|  | ||||
|         // Merge with the submitted form data | ||||
|         $code = $input->get('code', $options->fixed_code, 'raw'); | ||||
|  | ||||
|         // Make sure the code is not empty | ||||
|         if (empty($code)) { | ||||
|             throw new \RuntimeException(Text::_('PLG_MULTIFACTORAUTH_FIXED_ERR_EMPTYCODE')); | ||||
|         } | ||||
|  | ||||
|         // Return the configuration to be serialized | ||||
|         $event->addResult(['fixed_code' => $code]); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Validates the Multi-factor Authentication code submitted by the user in the Multi-Factor | ||||
|      * Authentication. If the record does not correspond to your plugin return FALSE. | ||||
|      * | ||||
|      * @param   Validate  $event  The event we are handling | ||||
|      * | ||||
|      * @return  void | ||||
|      * @since   4.2.0 | ||||
|      */ | ||||
|     public function onUserMultifactorValidate(Validate $event): void | ||||
|     { | ||||
|         /** | ||||
|          * @var   MfaTable    $record The MFA Method's record you're validating against | ||||
|          * @var   User        $user   The user record | ||||
|          * @var   string|null $code   The submitted code | ||||
|          */ | ||||
|         $record = $event['record']; | ||||
|         $user   = $event['user']; | ||||
|         $code   = $event['code']; | ||||
|  | ||||
|         // Make sure we are actually meant to handle this Method | ||||
|         if ($record->method != $this->mfaMethodName) { | ||||
|             $event->addResult(false); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Load the options from the record (if any) | ||||
|         $options = $this->decodeRecordOptions($record); | ||||
|  | ||||
|         // Double check the MFA Method is for the correct user | ||||
|         if ($user->id != $record->user_id) { | ||||
|             $event->addResult(false); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Check the MFA code for validity | ||||
|         $event->addResult(hash_equals($options->fixed_code, $code ?? '')); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Decodes the options from a record into an options object. | ||||
|      * | ||||
|      * @param   MfaTable  $record  The record to decode options for | ||||
|      * | ||||
|      * @return  object | ||||
|      * @since 4.2.0 | ||||
|      */ | ||||
|     private function decodeRecordOptions(MfaTable $record): object | ||||
|     { | ||||
|         $options = [ | ||||
|             'fixed_code' => '', | ||||
|         ]; | ||||
|  | ||||
|         if (!empty($record->options)) { | ||||
|             $recordOptions = $record->options; | ||||
|  | ||||
|             $options = array_merge($options, $recordOptions); | ||||
|         } | ||||
|  | ||||
|         return (object) $options; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user