diff --git a/modules/tide_tfa/src/Controller/TideTfaUserController.php b/modules/tide_tfa/src/Controller/TideTfaUserController.php new file mode 100644 index 000000000..7be18ee78 --- /dev/null +++ b/modules/tide_tfa/src/Controller/TideTfaUserController.php @@ -0,0 +1,143 @@ +requestStack = $container->get('request_stack'); + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function doResetPassLogin($uid, $timestamp, $hash, $request = NULL) { + // Ensure a valid request object. + if (!$request) { + $request = $this->requestStack->getCurrentRequest(); + } + + // Check if the PRLP module is enabled. + if (!\Drupal::moduleHandler()->moduleExists('prlp')) { + // If PRLP is not enabled, call the parent method. + return parent::doResetPassLogin($uid, $timestamp, $hash, $request); + } + + // Create an instance of PrlpController. + $prlp_controller = new PrlpController( + \Drupal::service('date.formatter'), + \Drupal::entityTypeManager()->getStorage('user'), + \Drupal::service('user.data'), + \Drupal::service('logger.factory')->get('prlp'), + \Drupal::service('flood'), + \Drupal::service('event_dispatcher') + ); + + /** @var \Drupal\user\UserInterface $user */ + $user = $this->userStorage->load($uid); + $this->setUser($user); + + // Let Drupal core deal with the one-time login, + // if TFA is not enabled or + // current user can skip TFA while resetting password. + if ($this->isTfaDisabled() || $this->canSkipPassReset()) { + // Use PRLP's resetPassLogin instead of the core function. + return $prlp_controller->prlpResetPassLogin($request, $uid, $timestamp, $hash); + } + + // Whether the TFA Validation Plugin is set and ready for use. + $tfa_ready = $this->isReady(); + + // Check for authentication plugin. + if ($tfa_ready && $this->pluginAllowsLogin()) { + $this->messenger()->addStatus($this->t('You have logged in on a trusted browser.')); + return $prlp_controller->prlpResetPassLogin($request, $uid, $timestamp, $hash); + } + + // Borrow the following codes from the core function: + $current = \Drupal::time()->getRequestTime(); + + // Verify that the user exists and is active. + if ($user === NULL || !$user->isActive()) { + throw new AccessDeniedHttpException(); + } + + // Time out, in seconds, until login URL expires. + $timeout = $this->config('user.settings')->get('password_reset_timeout'); + if ($user->getLastLoginTime() && $current - $timestamp > $timeout) { + $this->messenger()->addError($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.')); + return $this->redirect('user.pass'); + } + elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && hash_equals($hash, user_pass_rehash($user, $timestamp))) { + if ($tfa_ready) { + $this->session->migrate(); + $token = Crypt::randomBytesBase64(55); + $request->getSession()->set('pass_reset_' . $uid, $token); + + $this->logger->notice('User %name used one-time login link at time %timestamp.', [ + '%name' => $user->getDisplayName(), + '%timestamp' => $timestamp, + ]); + + $this->tempStoreUid($user->id()); + + return $this->redirect('tfa.entry', [ + 'uid' => $uid, + 'hash' => $this->getLoginHash($user), + ], [ + 'query' => ['pass-reset-token' => $token], + 'absolute' => TRUE, + ]); + } + else { + if ($this->canLoginWithoutTfa($this->getLogger('tfa'))) { + return $this->redirectToUserForm($user, $request, $timestamp); + } + else { + return $this->redirect(''); + } + } + } + + // Use PRLP's resetPassLogin instead of the core function. + return $prlp_controller->prlpResetPassLogin($request, $uid, $timestamp, $hash); + } + + /** + * Determines if the user can skip tfa on password reset. + * + * This function checks the TFA settings to see if the option to skip TFA + * during password reset is enabled. If enabled, users will not be required + * to complete two-factor authentication when resetting their password. + * + * @return bool + * TRUE if the user can skip TFA on password reset, FALSE otherwise. + */ + public function canSkipPassReset() { + return $this->tfaSettings->get('reset_pass_skip_enabled'); + } + +} diff --git a/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php b/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php new file mode 100644 index 000000000..fbf1d9760 --- /dev/null +++ b/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php @@ -0,0 +1,30 @@ +get('user.reset.login')) { + $route->setDefault('_controller', '\Drupal\tide_tfa\Controller\TideTfaUserController::doResetPassLogin'); + } + } + +} diff --git a/modules/tide_tfa/src/TideTfaOperation.php b/modules/tide_tfa/src/TideTfaOperation.php index 922721979..281c7f2c8 100644 --- a/modules/tide_tfa/src/TideTfaOperation.php +++ b/modules/tide_tfa/src/TideTfaOperation.php @@ -56,11 +56,17 @@ public static function setupTfaSettings() { $roles = Role::loadMultiple(); // Initialize the $tfa_required_roles array. $tfa_required_roles = []; - // Iterate through the roles and map the role IDs. - foreach ($roles as $role) { - if ($role->id() !== 'authenticated') { - // Map the role ID to itself. - $tfa_required_roles[$role->id()] = $role->id(); + // Define the roles to exclude in a variable. + $excluded_roles = ['authenticated', 'previewer', 'secure_file_user']; + + if (!empty($roles)) { + // Iterate through the roles and map the role IDs. + foreach ($roles as $role) { + // Check if the current role is not in the excluded roles. + if (!in_array($role->id(), $excluded_roles)) { + // Map the role ID to itself. + $tfa_required_roles[$role->id()] = $role->id(); + } } } diff --git a/modules/tide_tfa/tide_tfa.module b/modules/tide_tfa/tide_tfa.module index e894ea473..f9771b950 100644 --- a/modules/tide_tfa/tide_tfa.module +++ b/modules/tide_tfa/tide_tfa.module @@ -11,6 +11,12 @@ use Drupal\Core\Form\FormStateInterface; * Implements hook_form_alter(). */ function tide_tfa_form_alter(&$form, FormStateInterface $form_state, $form_id) { + // [SD-375] Bypass tfa during reset pass for all users. + if ($form_id == 'tfa_settings_form') { + if (isset($form['reset_pass_skip_enabled'])) { + $form['reset_pass_skip_enabled']['#description'] = t('Allow TFA to be bypassed during password reset by the authenticated user.'); + } + } if ($form_id == 'tfa_entry_form') { // [SD-294] Change the label of the 'Send' button. if (isset($form['actions']['send'])) { diff --git a/modules/tide_tfa/tide_tfa.services.yml b/modules/tide_tfa/tide_tfa.services.yml new file mode 100644 index 000000000..fa90d9713 --- /dev/null +++ b/modules/tide_tfa/tide_tfa.services.yml @@ -0,0 +1,5 @@ +services: + tide_tfa.route_subscriber: + class: Drupal\tide_tfa\Routing\TideTfaRouteSubscriber + tags: + - { name: event_subscriber }