You are here

class GALoginHotpValidation in Google Authenticator login 8

HOTP validation class for performing HOTP validation.

Plugin annotation


@TfaValidation(
  id = "ga_login_hotp",
  label = @Translation("GA Login Hmac-based OTP(HOTP)"),
  description = @Translation("GA Login Hotp Validation Plugin"),
  setupPluginId = "ga_login_hotp_setup",
)

Hierarchy

Expanded class hierarchy of GALoginHotpValidation

1 file declares its use of GALoginHotpValidation
GALoginHotpSetup.php in src/Plugin/TfaSetup/GALoginHotpSetup.php

File

src/Plugin/TfaValidation/GALoginHotpValidation.php, line 31

Namespace

Drupal\ga_login\Plugin\TfaValidation
View source
class GALoginHotpValidation extends TfaBasePlugin implements TfaValidationInterface, ContainerFactoryPluginInterface {
  use StringTranslationTrait;

  /**
   * Object containing the external validation library.
   *
   * @var object
   */
  public $auth;

  /**
   * The counter window in which the validation should be done.
   *
   * @var int
   */
  protected $counterWindow;

  /**
   * Whether or not the prefix should use the site name.
   *
   * @var bool
   */
  protected $siteNamePrefix;

  /**
   * Name prefix.
   *
   * @var string
   */
  protected $namePrefix;

  /**
   * Configurable name of the issuer.
   *
   * @var string
   */
  protected $issuer;

  /**
   * Whether the code has already been used or not.
   *
   * @var bool
   */
  protected $alreadyAccepted;

  /**
   * The Datetime service.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected $time;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, UserDataInterface $user_data, EncryptionProfileManagerInterface $encryption_profile_manager, EncryptServiceInterface $encrypt_service, ConfigFactoryInterface $config_factory, TimeInterface $time) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $user_data, $encryption_profile_manager, $encrypt_service);
    $this->auth = new \StdClass();
    $this->auth->otp = new Otp();
    $this->auth->ga = new GoogleAuthenticator();
    $plugin_settings = $config_factory
      ->get('tfa.settings')
      ->get('validation_plugin_settings');
    $settings = isset($plugin_settings['ga_login_hotp']) ? $plugin_settings['ga_login_hotp'] : [];
    $settings = array_replace([
      'counter_window' => 10,
      'site_name_prefix' => TRUE,
      'name_prefix' => 'TFA',
      'issuer' => 'Drupal',
    ], $settings);
    $this->counterWindow = $settings['counter_window'];
    $this->siteNamePrefix = $settings['site_name_prefix'];
    $this->namePrefix = $settings['name_prefix'];
    $this->issuer = $settings['issuer'];
    $this->alreadyAccepted = FALSE;
    $this->time = $time;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('user.data'), $container
      ->get('encrypt.encryption_profile.manager'), $container
      ->get('encryption'), $container
      ->get('config.factory'), $container
      ->get('datetime.time'));
  }

  /**
   * {@inheritdoc}
   */
  public function ready() {
    return $this
      ->getSeed() !== FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getForm(array $form, FormStateInterface $form_state) {
    $message = $this
      ->t('Verification code is application generated and @length digits long.', [
      '@length' => $this->codeLength,
    ]);
    if ($this
      ->getUserData('tfa', 'tfa_recovery_code', $this->uid, $this->userData)) {
      $message .= '<br/>' . $this
        ->t("Can't access your account? Use one of your recovery codes.");
    }
    $form['code'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('Application verification code'),
      '#description' => $message,
      '#required' => TRUE,
      '#attributes' => [
        'autocomplete' => 'off',
      ],
    ];
    $form['actions']['#type'] = 'actions';
    $form['actions']['login'] = [
      '#type' => 'submit',
      '#button_type' => 'primary',
      '#value' => $this
        ->t('Verify'),
    ];
    return $form;
  }

  /**
   * The configuration form for this validation plugin.
   *
   * @param \Drupal\Core\Config\Config $config
   *   Config object for tfa settings.
   * @param array $state
   *   Form state array determines if this form should be shown.
   *
   * @return array
   *   Form array specific for this validation plugin.
   */
  public function buildConfigurationForm(Config $config, array $state = []) {
    $settings_form['counter_window'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('Counter Window'),
      '#default_value' => $this->counterWindow ?: 5,
      '#description' => $this
        ->t('How far ahead from current counter should we check the code.'),
      '#size' => 2,
      '#states' => $state,
      '#required' => TRUE,
    ];
    $settings_form['site_name_prefix'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Use site name as OTP QR code name prefix.'),
      '#default_value' => $this->siteNamePrefix,
      '#description' => $this
        ->t('If checked, the site name will be used instead of a static string. This can be useful for multi-site installations.'),
      '#states' => $state,
    ];

    // Hide custom name prefix when site name prefix is selected.
    $state['visible'] += [
      ':input[name="validation_plugin_settings[ga_login_hotp][site_name_prefix]"]' => [
        'checked' => FALSE,
      ],
    ];
    $settings_form['name_prefix'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('OTP QR Code Prefix'),
      '#default_value' => $this->namePrefix ?: 'tfa',
      '#description' => $this
        ->t('Prefix for OTP QR code names. Suffix is account username.'),
      '#size' => 15,
      '#states' => $state,
    ];
    $settings_form['issuer'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('Issuer'),
      '#default_value' => $this->issuer,
      '#description' => $this
        ->t('The provider or service this account is associated with.'),
      '#size' => 15,
      '#required' => TRUE,
    ];
    return $settings_form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array $form, FormStateInterface $form_state) {
    $values = $form_state
      ->getValues();
    if (!$this
      ->validate($values['code'])) {
      $this->errorMessages['code'] = $this
        ->t('Invalid application code. Please try again.');
      if ($this->alreadyAccepted) {
        $form_state
          ->clearErrors();
        $this->errorMessages['code'] = $this
          ->t('Invalid code, it was recently used for a login. Please try a new code.');
      }
      return FALSE;
    }
    else {

      // Store accepted code to prevent replay attacks.
      $this
        ->storeAcceptedCode($values['code']);
      return TRUE;
    }
  }

  /**
   * Simple validate for web services.
   *
   * @param int $code
   *   OTP Code.
   *
   * @return bool
   *   True if validation was successful otherwise false.
   */
  public function validateRequest($code) {
    if ($this
      ->validate($code)) {
      $this
        ->storeAcceptedCode($code);
      return TRUE;
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  protected function validate($code) {

    // Strip whitespace.
    $code = preg_replace('/\\s+/', '', $code);
    if ($this
      ->alreadyAcceptedCode($code)) {
      $this->isValid = FALSE;
    }
    else {

      // Get OTP seed.
      $seed = $this
        ->getSeed();
      $counter = $this
        ->getHotpCounter();
      $this->isValid = $seed && ($counter = $this->auth->otp
        ->checkHotpResync(Encoding::base32DecodeUpper($seed), $counter, $code, $this->counterWindow));
      $this
        ->setUserData('tfa', [
        'tfa_hotp_counter' => ++$counter,
      ], $this->uid, $this->userData);
    }
    return $this->isValid;
  }

  /**
   * Returns whether code has already been used or not.
   *
   * @return bool
   *   True is code already used otherwise false.
   */
  public function isAlreadyAccepted() {
    return $this->alreadyAccepted;
  }

  /**
   * Get seed for this account.
   *
   * @return string
   *   Decrypted account OTP seed or FALSE if none exists.
   */
  protected function getSeed() {

    // Lookup seed for account and decrypt.
    $result = $this
      ->getUserData('tfa', 'tfa_hotp_seed', $this->uid, $this->userData);
    if (!empty($result)) {
      $encrypted = base64_decode($result['seed']);
      $seed = $this
        ->decrypt($encrypted);
      if (!empty($seed)) {
        return $seed;
      }
    }
    return FALSE;
  }

  /**
   * Save seed for account.
   *
   * @param string $seed
   *   Un-encrypted seed.
   */
  public function storeSeed($seed) {

    // Encrypt seed for storage.
    $encrypted = $this
      ->encrypt($seed);
    $record = [
      'tfa_hotp_seed' => [
        'seed' => base64_encode($encrypted),
        'created' => $this->time
          ->getRequestTime(),
      ],
    ];
    $this
      ->setUserData('tfa', $record, $this->uid, $this->userData);
  }

  /**
   * Delete the seed of the current validated user.
   */
  protected function deleteSeed() {
    $this
      ->deleteUserData('tfa', 'tfa_hotp_seed', $this->uid, $this->userData);
  }

  /**
   * Get the HOTP counter.
   *
   * @return int
   *   The current value of the HOTP counter, or 1 if no value was found.
   */
  public function getHotpCounter() {
    return $this
      ->getUserData('tfa', 'tfa_hotp_counter', $this->uid, $this->userData) ?: 1;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
GALoginHotpValidation::$alreadyAccepted protected property Whether the code has already been used or not. Overrides TfaBasePlugin::$alreadyAccepted
GALoginHotpValidation::$auth public property Object containing the external validation library.
GALoginHotpValidation::$counterWindow protected property The counter window in which the validation should be done.
GALoginHotpValidation::$issuer protected property Configurable name of the issuer.
GALoginHotpValidation::$namePrefix protected property Name prefix.
GALoginHotpValidation::$siteNamePrefix protected property Whether or not the prefix should use the site name.
GALoginHotpValidation::$time protected property The Datetime service.
GALoginHotpValidation::buildConfigurationForm public function The configuration form for this validation plugin.
GALoginHotpValidation::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create
GALoginHotpValidation::deleteSeed protected function Delete the seed of the current validated user.
GALoginHotpValidation::getForm public function Get TFA process form from plugin. Overrides TfaValidationInterface::getForm
GALoginHotpValidation::getHotpCounter public function Get the HOTP counter.
GALoginHotpValidation::getSeed protected function Get seed for this account.
GALoginHotpValidation::isAlreadyAccepted public function Returns whether code has already been used or not.
GALoginHotpValidation::ready public function Determine if the plugin can run for the current TFA context. Overrides TfaBasePlugin::ready
GALoginHotpValidation::storeSeed public function Save seed for account.
GALoginHotpValidation::validate protected function Validate code. Overrides TfaBasePlugin::validate 1
GALoginHotpValidation::validateForm public function Validate form. Overrides TfaValidationInterface::validateForm
GALoginHotpValidation::validateRequest public function Simple validate for web services.
GALoginHotpValidation::__construct public function Constructs a new Tfa plugin object. Overrides TfaBasePlugin::__construct 1
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
TfaBasePlugin::$code protected property The user submitted code to be validated.
TfaBasePlugin::$codeLength protected property The allowed code length.
TfaBasePlugin::$encryptionProfile protected property Encryption profile.
TfaBasePlugin::$encryptService protected property Encryption service.
TfaBasePlugin::$errorMessages protected property The error for the current validation.
TfaBasePlugin::$isValid protected property Whether the validation succeeded or not.
TfaBasePlugin::$uid protected property The user id.
TfaBasePlugin::$userData protected property Provides the user data service object.
TfaBasePlugin::alreadyAcceptedCode protected function Whether code has already been used.
TfaBasePlugin::decrypt protected function Decrypt a encrypted string.
TfaBasePlugin::encrypt protected function Encrypt a plaintext string.
TfaBasePlugin::getErrorMessages public function Get error messages suitable for form_set_error().
TfaBasePlugin::getLabel public function Get the plugin label.
TfaBasePlugin::storeAcceptedCode protected function Store validated code to prevent replay attack.
TfaBasePlugin::submitForm public function Submit form. 1
TfaDataTrait::deleteUserData protected function Deletes data stored for the current validated user account.
TfaDataTrait::getUserData protected function Returns data stored for the current validated user account.
TfaDataTrait::setUserData protected function Store user specific information.
TfaDataTrait::tfaGetTfaData protected function Get TFA data for an account.
TfaDataTrait::tfaSaveTfaData public function Save TFA data for an account.