src/EventSubscriber/Enforce2FASubscriber.php line 122

Open in your IDE?
  1. <?php
  2. namespace MedBrief\MSR\EventSubscriber;
  3. use Override;
  4. use Doctrine\ORM\EntityManagerInterface;
  5. use MedBrief\MSR\Entity\User;
  6. use MedBrief\MSR\Entity\UserPolicy;
  7. use MedBrief\MSR\Security\MedBriefLoginAuthenticator;
  8. use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvent;
  9. use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvents;
  10. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  11. use Symfony\Component\HttpFoundation\RedirectResponse;
  12. use Symfony\Component\HttpKernel\Event\RequestEvent;
  13. use Symfony\Component\HttpKernel\KernelEvents;
  14. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  15. use Symfony\Component\Routing\RouterInterface;
  16. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  17. class Enforce2FASubscriber implements EventSubscriberInterface
  18. {
  19. public function __construct(private readonly RouterInterface $router, private readonly TokenStorageInterface $tokenStorage, private readonly EntityManagerInterface $entityManager)
  20. {
  21. }
  22. #[Override]
  23. public static function getSubscribedEvents(): array
  24. {
  25. return [
  26. KernelEvents::REQUEST => [
  27. ['enforce2FA', 0],
  28. ],
  29. TwoFactorAuthenticationEvents::COMPLETE => [
  30. ['onTwoFactorAuthenticationComplete', 0],
  31. ],
  32. ];
  33. }
  34. public function enforce2FA(RequestEvent $event): void
  35. {
  36. $request = $event->getRequest();
  37. // Check if we need to redirect for policy acceptance
  38. if ($request->getSession()->has('policy_redirect_needed')
  39. && $request->getSession()->get('policy_redirect_needed') === true) {
  40. $redirectUrl = $request->getSession()->get('policy_redirect_url');
  41. $request->getSession()->remove('policy_redirect_needed');
  42. $request->getSession()->remove('policy_redirect_url');
  43. $event->setResponse(new RedirectResponse($redirectUrl));
  44. return; // Exit early
  45. }
  46. $token = $this->tokenStorage->getToken();
  47. $user = null;
  48. if ($token) {
  49. /** @var User $user */
  50. $user = $token->getUser();
  51. }
  52. $enforce2FADetail = $request->getSession()->get(MedBriefLoginAuthenticator::ENFORCE_2FA_DETAIL_SESSION_KEY);
  53. // Check if 2FA is required from the session variable.
  54. // If the user is set, and the user has enabled 2FA...
  55. if (is_array($enforce2FADetail) && $enforce2FADetail['2faRequired'] === true && $user) {
  56. if ($user->hasEnabledMfa()) {
  57. // Redirect to the target path or the dashboard, and remove the session indicator for 2FA required.
  58. $targetPath = $enforce2FADetail['targetPath'];
  59. if ($targetPath !== null) {
  60. $event->setResponse(new RedirectResponse($targetPath));
  61. } else {
  62. $event->setResponse(new RedirectResponse($this->router->generate('infology_medbrief_dashboard')));
  63. }
  64. $request->getSession()->remove(MedBriefLoginAuthenticator::ENFORCE_2FA_DETAIL_SESSION_KEY);
  65. } else {
  66. // Allowed routes that do not require 2FA
  67. $allowedRoutes = [
  68. 'mfa_enable',
  69. 'mfa_enable_force',
  70. 'mfa_generate_codes',
  71. 'mfa_generate_initial_codes',
  72. 'auth_app_enable',
  73. 'auth_app_disable',
  74. 'email_mfa_enable',
  75. 'email_mfa_disable',
  76. 'user_policy_accept_decline',
  77. 'user_policy_accept_policies',
  78. 'user_policy_decline_policies',
  79. 'logout',
  80. ];
  81. // Redirect to the 2FA setup page if the current route is not allowed
  82. if (!in_array($request->attributes->get('_route'), $allowedRoutes, true)) {
  83. // Create the redirect URL
  84. $redirectUrl = $this->router->generate('mfa_enable_force');
  85. // Only proceed with policy checks for users WITHOUT 2FA
  86. $relevantPolicies = $this->entityManager->getRepository(UserPolicy::class)
  87. ->findUnacceptedActivePoliciesForUser($user->getId())
  88. ;
  89. // If the user has any pending policies, store them in the session along with the targetPath
  90. if ($relevantPolicies != null && $relevantPolicies != []) {
  91. $request->getSession()->set('pending-policies', $relevantPolicies);
  92. $request->getSession()->set('pending-policies-detail', [
  93. 'targetPath' => $redirectUrl,
  94. ]);
  95. }
  96. // Set the response with the combined URL
  97. $event->setResponse(new RedirectResponse($redirectUrl));
  98. }
  99. }
  100. }
  101. }
  102. public function onTwoFactorAuthenticationComplete(TwoFactorAuthenticationEvent $event): void
  103. {
  104. $request = $event->getRequest();
  105. $token = $this->tokenStorage->getToken();
  106. $user = null;
  107. if ($token) {
  108. /** @var User $user */
  109. $user = $token->getUser();
  110. }
  111. $targetPath = $request->getSession()->get('_security.main.target_path');
  112. $relevantPolicies = $this->entityManager->getRepository(UserPolicy::class)
  113. ->findUnacceptedActivePoliciesForUser($user->getId())
  114. ;
  115. // If the user has any pending policies, store them in the session along with the targetPath
  116. if ($relevantPolicies != null && $relevantPolicies != []) {
  117. $request->getSession()->set('pending-policies', $relevantPolicies);
  118. $request->getSession()->set('pending-policies-detail', [
  119. 'targetPath' => $targetPath,
  120. ]);
  121. // Store the redirect URL in the session
  122. $request->getSession()->set('policy_redirect_needed', true);
  123. $request->getSession()->set(
  124. 'policy_redirect_url',
  125. $targetPath
  126. );
  127. }
  128. }
  129. }