You are here

class TfaRecoveryCode in Two-factor Authentication (TFA) 8

Recovery validation class for performing recovery codes validation.

Plugin annotation


@TfaValidation(
  id = "tfa_recovery_code",
  label = @Translation("TFA Recovery Code"),
  description = @Translation("TFA Recovery Code Validation Plugin"),
  setupPluginId = "tfa_recovery_code_setup",
)

Hierarchy

Expanded class hierarchy of TfaRecoveryCode

2 files declare their use of TfaRecoveryCode
TfaRecoveryCodeSetup.php in src/Plugin/TfaSetup/TfaRecoveryCodeSetup.php
TfaRecoveryCodeTest.php in tests/src/Unit/Plugin/TfaValidation/TfaRecoveryCodeTest.php

File

src/Plugin/TfaValidation/TfaRecoveryCode.php, line 28

Namespace

Drupal\tfa\Plugin\TfaValidation
View source
class TfaRecoveryCode extends TfaBasePlugin implements TfaValidationInterface, ContainerFactoryPluginInterface {
  use TfaRandomTrait;
  use StringTranslationTrait;

  /**
   * The number of recovery codes to generate.
   *
   * @var int
   */
  protected $codeLimit = 10;

  /**
   * Constructs a new Tfa plugin object.
   *
   * @param array $configuration
   *   The plugin configuration.
   * @param string $plugin_id
   *   The plugin id.
   * @param mixed $plugin_definition
   *   The plugin definition.
   * @param \Drupal\user\UserDataInterface $user_data
   *   User data object to store user specific information.
   * @param \Drupal\encrypt\EncryptionProfileManagerInterface $encryption_profile_manager
   *   Encryption profile manager.
   * @param \Drupal\encrypt\EncryptServiceInterface $encrypt_service
   *   Encryption service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, UserDataInterface $user_data, EncryptionProfileManagerInterface $encryption_profile_manager, EncryptServiceInterface $encrypt_service, ConfigFactoryInterface $config_factory) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $user_data, $encryption_profile_manager, $encrypt_service);
    $codes_amount = $config_factory
      ->get('tfa.settings')
      ->get('validation_plugin_settings.tfa_recovery_code.recovery_codes_amount');
    if (!empty($codes_amount)) {
      $this->codeLimit = $codes_amount;
    }
  }

  /**
   * {@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'));
  }

  /**
   * {@inheritdoc}
   */
  public function ready() {
    $codes = $this
      ->getCodes();
    return !empty($codes);
  }

  /**
   * {@inheritdoc}
   */
  public function getForm(array $form, FormStateInterface $form_state) {
    $form['code'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('Enter one of your recovery codes'),
      '#required' => TRUE,
      '#description' => $this
        ->t('Recovery codes were generated when you first set up TFA. Format: XXX XXX XXX'),
      '#attributes' => [
        'autocomplete' => 'off',
      ],
    ];
    $form['actions']['#type'] = 'actions';
    $form['actions']['login'] = [
      '#type' => 'submit',
      '#button_type' => 'primary',
      '#value' => $this
        ->t('Verify'),
    ];
    return $form;
  }

  /**
   * Configuration form for the recovery code 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['recovery_codes_amount'] = [
      '#type' => 'number',
      '#title' => $this
        ->t('Recovery Codes Amount'),
      '#default_value' => $this->codeLimit,
      '#description' => $this
        ->t('Number of Recovery Codes To Generate.'),
      '#min' => 1,
      '#size' => 2,
      '#states' => $state,
      '#required' => TRUE,
    ];
    return $settings_form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array $form, FormStateInterface $form_state) {
    $values = $form_state
      ->getValues();
    return $this
      ->validate($values['code']);
  }

  /**
   * 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;
  }

  /**
   * Generate an array of secure recovery codes.
   *
   * @return array
   *   An array of randomly generated codes.
   *
   * @throws \Exception
   */
  public function generateCodes() {
    $codes = [];
    for ($i = 0; $i < $this->codeLimit; $i++) {
      $codes[] = $this
        ->randomCharacters(9, '1234567890');
    }
    return $codes;
  }

  /**
   * Get unused recovery codes.
   *
   * @todo consider returning used codes so validate() can error with
   * appropriate message
   *
   * @return array
   *   Array of codes indexed by ID.
   *
   * @throws \Drupal\encrypt\Exception\EncryptionMethodCanNotDecryptException
   * @throws \Drupal\encrypt\Exception\EncryptException
   */
  public function getCodes() {
    $codes = $this
      ->getUserData('tfa', 'tfa_recovery_code', $this->uid, $this->userData) ?: [];
    array_walk($codes, function (&$v, $k) {
      $v = $this
        ->decrypt($v);
    });
    return $codes;
  }

  /**
   * Save recovery codes for current account.
   *
   * @param array $codes
   *   Recovery codes for current account.
   *
   * @throws \Drupal\encrypt\Exception\EncryptException
   */
  public function storeCodes(array $codes) {
    $this
      ->deleteCodes();

    // Encrypt code for storage.
    array_walk($codes, function (&$v, $k) {
      $v = $this
        ->encrypt($v);
    });
    $data = [
      'tfa_recovery_code' => $codes,
    ];
    $this
      ->setUserData('tfa', $data, $this->uid, $this->userData);
  }

  /**
   * Delete existing codes.
   */
  protected function deleteCodes() {

    // Delete any existing codes.
    $this
      ->deleteUserData('tfa', 'tfa_recovery_code', $this->uid, $this->userData);
  }

  /**
   * {@inheritdoc}
   */
  protected function validate($code) {
    $this->isValid = FALSE;

    // Get codes and compare.
    $codes = $this
      ->getCodes();
    if (empty($codes)) {
      $this->errorMessages['recovery_code'] = $this
        ->t('You have no unused codes available.');
      return FALSE;
    }

    // Remove empty spaces.
    $code = str_replace(' ', '', $code);
    foreach ($codes as $id => $stored) {

      // Remove spaces from stored code.
      if (hash_equals(trim(str_replace(' ', '', $stored)), $code)) {
        $this->isValid = TRUE;
        unset($codes[$id]);
        $this
          ->storeCodes($codes);
        return $this->isValid;
      }
    }
    $this->errorMessages['recovery_code'] = $this
      ->t('Invalid recovery code.');
    return $this->isValid;
  }

}

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
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::$alreadyAccepted protected property Whether the code has been used before.
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.
TfaRandomTrait::$allowedRandomLetters protected property Letters allowed during random string generation.
TfaRandomTrait::$allowedRandomNumbers protected property Numbers allowed during random string generation.
TfaRandomTrait::randomCharacters protected function Generate random characters of the given length and allowable characters.
TfaRandomTrait::randomInteger public function Generate a random integer of the given character length.
TfaRandomTrait::randomString public function Generate a random string of the given character length.
TfaRecoveryCode::$codeLimit protected property The number of recovery codes to generate.
TfaRecoveryCode::buildConfigurationForm public function Configuration form for the recovery code plugin.
TfaRecoveryCode::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create
TfaRecoveryCode::deleteCodes protected function Delete existing codes.
TfaRecoveryCode::generateCodes public function Generate an array of secure recovery codes.
TfaRecoveryCode::getCodes public function Get unused recovery codes.
TfaRecoveryCode::getForm public function Get TFA process form from plugin. Overrides TfaValidationInterface::getForm
TfaRecoveryCode::ready public function Determine if the plugin can run for the current TFA context. Overrides TfaBasePlugin::ready 1
TfaRecoveryCode::storeCodes public function Save recovery codes for current account.
TfaRecoveryCode::validate protected function Validate code. Overrides TfaBasePlugin::validate
TfaRecoveryCode::validateForm public function Validate form. Overrides TfaValidationInterface::validateForm
TfaRecoveryCode::validateRequest public function Simple validate for web services.
TfaRecoveryCode::__construct public function Constructs a new Tfa plugin object. Overrides TfaBasePlugin::__construct