<?php
namespace MedBrief\MSR\EventSubscriber;
use Override;
use Doctrine\ORM\EntityManagerInterface;
use MedBrief\MSR\Entity\User;
use MedBrief\MSR\Entity\UserPolicy;
use MedBrief\MSR\Security\MedBriefLoginAuthenticator;
use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvent;
use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class Enforce2FASubscriber implements EventSubscriberInterface
{
public function __construct(private readonly RouterInterface $router, private readonly TokenStorageInterface $tokenStorage, private readonly EntityManagerInterface $entityManager)
{
}
#[Override]
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [
['enforce2FA', 0],
],
TwoFactorAuthenticationEvents::COMPLETE => [
['onTwoFactorAuthenticationComplete', 0],
],
];
}
public function enforce2FA(RequestEvent $event): void
{
$request = $event->getRequest();
// Check if we need to redirect for policy acceptance
if ($request->getSession()->has('policy_redirect_needed')
&& $request->getSession()->get('policy_redirect_needed') === true) {
$redirectUrl = $request->getSession()->get('policy_redirect_url');
$request->getSession()->remove('policy_redirect_needed');
$request->getSession()->remove('policy_redirect_url');
$event->setResponse(new RedirectResponse($redirectUrl));
return; // Exit early
}
$token = $this->tokenStorage->getToken();
$user = null;
if ($token) {
/** @var User $user */
$user = $token->getUser();
}
$enforce2FADetail = $request->getSession()->get(MedBriefLoginAuthenticator::ENFORCE_2FA_DETAIL_SESSION_KEY);
// Check if 2FA is required from the session variable.
// If the user is set, and the user has enabled 2FA...
if (is_array($enforce2FADetail) && $enforce2FADetail['2faRequired'] === true && $user) {
if ($user->hasEnabledMfa()) {
// Redirect to the target path or the dashboard, and remove the session indicator for 2FA required.
$targetPath = $enforce2FADetail['targetPath'];
if ($targetPath !== null) {
$event->setResponse(new RedirectResponse($targetPath));
} else {
$event->setResponse(new RedirectResponse($this->router->generate('infology_medbrief_dashboard')));
}
$request->getSession()->remove(MedBriefLoginAuthenticator::ENFORCE_2FA_DETAIL_SESSION_KEY);
} else {
// Allowed routes that do not require 2FA
$allowedRoutes = [
'mfa_enable',
'mfa_enable_force',
'mfa_generate_codes',
'mfa_generate_initial_codes',
'auth_app_enable',
'auth_app_disable',
'email_mfa_enable',
'email_mfa_disable',
'user_policy_accept_decline',
'user_policy_accept_policies',
'user_policy_decline_policies',
'logout',
];
// Redirect to the 2FA setup page if the current route is not allowed
if (!in_array($request->attributes->get('_route'), $allowedRoutes, true)) {
// Create the redirect URL
$redirectUrl = $this->router->generate('mfa_enable_force');
// Only proceed with policy checks for users WITHOUT 2FA
$relevantPolicies = $this->entityManager->getRepository(UserPolicy::class)
->findUnacceptedActivePoliciesForUser($user->getId())
;
// If the user has any pending policies, store them in the session along with the targetPath
if ($relevantPolicies != null && $relevantPolicies != []) {
$request->getSession()->set('pending-policies', $relevantPolicies);
$request->getSession()->set('pending-policies-detail', [
'targetPath' => $redirectUrl,
]);
}
// Set the response with the combined URL
$event->setResponse(new RedirectResponse($redirectUrl));
}
}
}
}
public function onTwoFactorAuthenticationComplete(TwoFactorAuthenticationEvent $event): void
{
$request = $event->getRequest();
$token = $this->tokenStorage->getToken();
$user = null;
if ($token) {
/** @var User $user */
$user = $token->getUser();
}
$targetPath = $request->getSession()->get('_security.main.target_path');
$relevantPolicies = $this->entityManager->getRepository(UserPolicy::class)
->findUnacceptedActivePoliciesForUser($user->getId())
;
// If the user has any pending policies, store them in the session along with the targetPath
if ($relevantPolicies != null && $relevantPolicies != []) {
$request->getSession()->set('pending-policies', $relevantPolicies);
$request->getSession()->set('pending-policies-detail', [
'targetPath' => $targetPath,
]);
// Store the redirect URL in the session
$request->getSession()->set('policy_redirect_needed', true);
$request->getSession()->set(
'policy_redirect_url',
$targetPath
);
}
}
}