You are here

class TfaTotp in TFA Basic plugins 7

Class TfaTotp

Hierarchy

Expanded class hierarchy of TfaTotp

1 string reference to 'TfaTotp'
tfa_basic_tfa_api in ./tfa_basic.module
Implements hook_tfa_api().

File

includes/tfa_totp.inc, line 10
classes for tfa_totp

View source
class TfaTotp extends TfaBasePlugin implements TfaValidationPluginInterface {

  /**
   * @var PHPGangsta_GoogleAuthenticator
   */
  protected $ga;

  /**
   * @var int
   */
  protected $timeSkew;

  /**
   * @var bool
   */
  protected $alreadyAccepted;

  /**
   * @copydoc TfaBasePlugin::__construct()
   */
  public function __construct(array $context) {
    parent::__construct($context);
    $this->ga = new PHPGangsta_GoogleAuthenticator();

    // Allow codes within tolerance range of 3 * 30 second units.
    $this->timeSkew = variable_get('tfa_basic_time_skew', 3);

    // Recommended: set variable tfa_totp_secret_key in settings.php.
    $this->encryptionKey = variable_get('tfa_basic_secret_key', drupal_get_private_key());
    $this->alreadyAccepted = FALSE;
  }

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

  /**
   * @copydoc TfaValidationPluginInterface::getForm()
   */
  public function getForm(array $form, array &$form_state) {
    $form['code'] = array(
      '#type' => 'textfield',
      '#title' => t('Application verification code'),
      '#description' => t('Verification code is application generated and !length digits long.', array(
        '!length' => $this->codeLength,
      )),
      '#required' => TRUE,
      '#attributes' => array(
        'autocomplete' => 'off',
      ),
    );
    if (module_exists('elements')) {
      $form['code']['#type'] = 'numberfield';
    }
    $form['actions']['#type'] = 'actions';
    $form['actions']['login'] = array(
      '#type' => 'submit',
      '#value' => t('Verify'),
    );
    return $form;
  }

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

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

  /**
   * @copydoc TfaBasePlugin::validate()
   */
  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();
      $this->isValid = $seed && $this->ga
        ->verifyCode($seed, $code, $this->timeSkew);
    }
    return $this->isValid;
  }

  /**
   * @param string $code
   */
  protected function storeAcceptedCode($code) {
    $code = preg_replace('/\\s+/', '', $code);
    $hash = hash('sha1', drupal_get_hash_salt() . $code);
    db_insert('tfa_accepted_code')
      ->fields(array(
      'uid' => $this->context['uid'],
      'code_hash' => $hash,
      'time_accepted' => REQUEST_TIME,
    ))
      ->execute();
  }

  /**
   * Whether code has recently been accepted.
   *
   * @param string $code
   * @return bool
   */
  protected function alreadyAcceptedCode($code) {
    $hash = hash('sha1', drupal_get_hash_salt() . $code);
    $result = db_query("SELECT code_hash FROM {tfa_accepted_code} WHERE uid = :uid AND code_hash = :code", array(
      ':uid' => $this->context['uid'],
      ':code' => $hash,
    ))
      ->fetchAssoc();
    if (!empty($result)) {
      $this->alreadyAccepted = TRUE;
      return TRUE;
    }
    return FALSE;
  }

  /**
   * 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 = db_query("SELECT seed FROM {tfa_totp_seed} WHERE uid = :uid", array(
      ':uid' => $this->context['uid'],
    ))
      ->fetchAssoc();
    if (!empty($result)) {
      $encrypted = base64_decode($result['seed']);
      $seed = $this
        ->decrypt($encrypted);
      if (!empty($seed)) {
        return $seed;
      }
    }
    return FALSE;
  }

  /**
   * Delete users seeds.
   *
   * @return int
   */
  public function deleteSeed() {
    $query = db_delete('tfa_totp_seed')
      ->condition('uid', $this->context['uid']);
    return $query
      ->execute();
  }

}

Members

Namesort descending Modifiers Type Description Overrides
TfaBasePlugin::$code protected property TFA code.
TfaBasePlugin::$codeLength protected property Code Length.
TfaBasePlugin::$context protected property Context of current TFA process.
TfaBasePlugin::$encryptionKey protected property Encryption key.
TfaBasePlugin::$errorMessages protected property Error messages.
TfaBasePlugin::$isValid protected property Code is valid.
TfaBasePlugin::CRYPT_VERSION constant
TfaBasePlugin::decrypt protected function Decrypt a encrypted string.
TfaBasePlugin::decryptLegacyDataWithMcrypt protected function Decrypt using the deprecated Mcrypt extension.
TfaBasePlugin::decryptLegacyDataWithOpenSSL protected function Use OpenSSL to decrypt data that was originally encrypted with Mcrypt.
TfaBasePlugin::encrypt protected function Encrypt a plaintext string.
TfaBasePlugin::encryptWithMcrypt protected function Encrypt using the deprecated Mcrypt extension.
TfaBasePlugin::generate protected function Generate a random string of characters of length $this->codeLength.
TfaBasePlugin::getErrorMessages public function Get error messages suitable for form_set_error().
TfaBasePlugin::submitForm public function Submit form. 1
TfaBasePlugin::timingSafeEquals private function A timing safe equals comparison.
TfaTotp::$alreadyAccepted protected property
TfaTotp::$ga protected property
TfaTotp::$timeSkew protected property
TfaTotp::alreadyAcceptedCode protected function Whether code has recently been accepted.
TfaTotp::deleteSeed public function Delete users seeds.
TfaTotp::getForm public function @copydoc TfaValidationPluginInterface::getForm() Overrides TfaValidationPluginInterface::getForm
TfaTotp::getSeed protected function Get seed for this account.
TfaTotp::ready public function @copydoc TfaBasePlugin::ready() Overrides TfaBasePlugin::ready
TfaTotp::storeAcceptedCode protected function
TfaTotp::validate protected function @copydoc TfaBasePlugin::validate() Overrides TfaBasePlugin::validate 1
TfaTotp::validateForm public function @copydoc TfaValidationPluginInterface::validateForm() Overrides TfaValidationPluginInterface::validateForm
TfaTotp::__construct public function @copydoc TfaBasePlugin::__construct() Overrides TfaBasePlugin::__construct 1