View source
<?php
namespace Drupal\tfa\Plugin\TfaValidation;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\encrypt\EncryptionProfileManagerInterface;
use Drupal\encrypt\EncryptServiceInterface;
use Drupal\tfa\Plugin\TfaBasePlugin;
use Drupal\tfa\Plugin\TfaValidationInterface;
use Drupal\tfa\TfaRandomTrait;
use Drupal\user\UserDataInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class TfaRecoveryCode extends TfaBasePlugin implements TfaValidationInterface, ContainerFactoryPluginInterface {
use TfaRandomTrait;
use StringTranslationTrait;
protected $codeLimit = 10;
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;
}
}
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'));
}
public function ready() {
$codes = $this
->getCodes();
return !empty($codes);
}
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;
}
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;
}
public function validateForm(array $form, FormStateInterface $form_state) {
$values = $form_state
->getValues();
return $this
->validate($values['code']);
}
public function validateRequest($code) {
if ($this
->validate($code)) {
$this
->storeAcceptedCode($code);
return TRUE;
}
return FALSE;
}
public function generateCodes() {
$codes = [];
for ($i = 0; $i < $this->codeLimit; $i++) {
$codes[] = $this
->randomCharacters(9, '1234567890');
}
return $codes;
}
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;
}
public function storeCodes(array $codes) {
$this
->deleteCodes();
array_walk($codes, function (&$v, $k) {
$v = $this
->encrypt($v);
});
$data = [
'tfa_recovery_code' => $codes,
];
$this
->setUserData('tfa', $data, $this->uid, $this->userData);
}
protected function deleteCodes() {
$this
->deleteUserData('tfa', 'tfa_recovery_code', $this->uid, $this->userData);
}
protected function validate($code) {
$this->isValid = FALSE;
$codes = $this
->getCodes();
if (empty($codes)) {
$this->errorMessages['recovery_code'] = $this
->t('You have no unused codes available.');
return FALSE;
}
$code = str_replace(' ', '', $code);
foreach ($codes as $id => $stored) {
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;
}
}