Problem/Motivation
(I file this as "feature request", I'm not sure the current behaviour is intended).
Context: we are implementing a custom email authentication plugin to be used along the main one (TOTP) in order to provide our users with alternative methods to access TFA: the user will be able to setup a specific TFA method, or both if they want.
When setting up TFA, the module requires setting a default authentication plugin.
If the user does not then setup the default plugin (eg, they don't use any TOTP application, they just want email OTP) the main module will try to use it anyway (eg, in "src/Form/EntryForm.php@buildForm" and "src/TfaLoginContextTrait.php@isReady"), miss a proper setup and fail.
// src/TfaLoginContextTrait.php
// The user has a validation plugin set, but not the default one;
// $validation_plugin->ready() fails, and the module acts as the user has not set TFA yet.
public function isReady() {
// If possible, set up an instance of tfaValidationPlugin and the user's
// list of plugins.
$default_validation_plugin = $this->tfaSettings->get('default_validation_plugin');
if (!empty($default_validation_plugin)) {
/** @var \Drupal\tfa\Plugin\TfaValidationInterface $validation_plugin */
try {
$validation_plugin = $this->tfaPluginManager->createInstance($default_validation_plugin, ['uid' => $this->user->id()]);
if (isset($validation_plugin) && $validation_plugin->ready()) {
return TRUE;
}
}
catch (PluginException $e) {
return FALSE;
}
}
return FALSE;
}
public function buildForm(array $form, FormStateInterface $form_state, $uid = NULL, string $hash = '') {
$alternate_plugin = $this->getRequest()->get('plugin');
$validation_plugin_definitions = $this->tfaPluginManager->getValidationDefinitions();
$user_settings = $this->userData->get('tfa', $uid, 'tfa_user_settings');
$user_enabled_validation_plugins = $user_settings['data']['plugins'] ?? [];
// Default validation plugin, then check for enabled alternate plugin.
$validation_plugin = $this->tfaSettings->get('default_validation_plugin');
if ($alternate_plugin && !empty($validation_plugin_definitions[$alternate_plugin]) && !empty($user_enabled_validation_plugins[>
$validation_plugin = $alternate_plugin;
$form['#cache'] = ['max-age' => 0];
}
// [...]
Steps to reproduce
- Setup and enable the main TFA module
- Enable the "TOTP" and "HOTP" authentication plugins
- Set "TOTP" as default authentication plugin
- Login with a non-admin user, and just setup HOTP
- Logout and login again
The user sees the Authentication Code form for the selected method (in this case, HOTP)
The user is logged in, and is promped to "set-up a TFA method"; if implemented, the TFA lock-out counter is engaged ("You have X logins left before your account is locked")
Proposed resolution
Instead of always using the default plugin (and after checking if a specific method is requested via request parameter) "src/Form/EntryForm.php@buildForm" and "src/TfaLoginContextTrait.php@isReady" first check if the user has authentication plugins set, and uses the first one available.