src/Controller/SecurityController.php line 31

Open in your IDE?
  1. <?php
  2. namespace MedBrief\MSR\Controller;
  3. use LogicException;
  4. use MedBrief\MSR\Service\Security\IPFailedLogin;
  5. use MedBrief\MSR\Service\Security\SecurityTimeDelayHelperService;
  6. use MedBrief\MSR\Service\Security\UserFailedLogin;
  7. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  8. use Symfony\Component\HttpFoundation\Request;
  9. use Symfony\Component\HttpFoundation\Response;
  10. use Symfony\Component\Routing\Annotation\Route;
  11. use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
  12. class SecurityController extends AbstractController
  13. {
  14. private readonly string $clientIpAddress;
  15. public function __construct(private readonly SecurityTimeDelayHelperService $securityTimeDelayHelper)
  16. {
  17. $this->clientIpAddress = Request::createFromGlobals()->getClientIp() ?? 'Unknown';
  18. }
  19. /**
  20. * @Route("/login", name="login")
  21. *
  22. * @param AuthenticationUtils $authenticationUtils
  23. * @param UserFailedLogin $userFailedLogin
  24. * @param IPFailedLogin $IPFailedLogin
  25. */
  26. public function login(AuthenticationUtils $authenticationUtils, UserFailedLogin $userFailedLogin, IPFailedLogin $IPFailedLogin): Response
  27. {
  28. // Start time to measure response duration
  29. $startTime = microtime(true);
  30. // Get the login error if there is one
  31. $error = $authenticationUtils->getLastAuthenticationError();
  32. // Last username entered by the user
  33. $lastUsername = $authenticationUtils->getLastUsername();
  34. // If the user is already logged in, redirect them to the dashboard
  35. if ($this->getUser()) {
  36. // Clear failed logins for authenticated user
  37. $userFailedLogin->loginFailureClear($this->getUser()->getUsername()); //TODO: replace ->getUsername() with ->getUserIdentifier() during next Symfony upgrade.
  38. // Clear failed logins for ip address by username
  39. $IPFailedLogin->loginFailureClear($this->getUser()->getUsername());
  40. return $this->redirectToRoute('infology_medbrief_dashboard');
  41. }
  42. // This is for email enumeration attacks
  43. // Use the SecurityHelper to determine if the user exists
  44. $userExists = $this->securityTimeDelayHelper->determineIfUserExists();
  45. if (!$userExists) {
  46. // Add an extra delay for wrong email to match password timing
  47. usleep(500 * 1000);
  48. }
  49. // Set currentCount and IPcurrentCount to 0
  50. $currentCount = 0;
  51. $IPcurrentCount = 0;
  52. // Determine if there is a login failure and Track the login failure to an IP address as well
  53. if ($error !== null) {
  54. $IPcurrentCount = $IPFailedLogin->loginFailure($lastUsername)['currentCount'];
  55. $currentCount = $userFailedLogin->loginFailure($lastUsername)['currentCount'];
  56. }
  57. // Get the maximum attempts allowed for a failed login attempt
  58. $maxAttempts = $userFailedLogin->getMaxAttempts();
  59. // Get the maximum attempts allowed for a failed login attempt on IP Address
  60. $IPmaxAttempts = $IPFailedLogin->getMaxAttempts();
  61. // Check if the IP Address is blacklisted or exceeded the max login attempts
  62. if ($IPFailedLogin->loginAllow() === false) {
  63. // Get the login lockout time left
  64. $IPloginLockoutTimeLeft = $IPFailedLogin->getLoginLockoutTimer($lastUsername);
  65. // If there is a lockout time left, load the login lockout screen and pass relevant variables
  66. if ($IPloginLockoutTimeLeft !== null) {
  67. // Load the login lockout screen and pass relevant variables
  68. return $this->render('security/IPBlacklisted.html.twig', ['IPloginLockoutTimeLeft' => $IPloginLockoutTimeLeft, 'clientIPAddress' => $this->clientIpAddress]);
  69. }
  70. }
  71. // Check if the user or clientIP is blacklisted
  72. if ($userFailedLogin->loginAllow($lastUsername) === false) {
  73. // Get the login lockout time left
  74. $loginLockoutTimeLeft = $userFailedLogin->getLoginLockoutTimer($lastUsername);
  75. // If there is a lockout time left, load the login lockout screen and pass relevant variables
  76. if ($loginLockoutTimeLeft !== null) {
  77. // Load the login lockout screen and pass relevant variables
  78. return $this->render('security/loginLockout.html.twig', ['loginLockoutTimeLeft' => $loginLockoutTimeLeft]);
  79. }
  80. }
  81. // Use the SecurityHelper to add a random delay
  82. $this->securityTimeDelayHelper->addRandomDelay($startTime);
  83. // Load the login screen and pass relevant variables
  84. return $this->render('security/login.html.twig', [
  85. 'last_username' => $lastUsername,
  86. 'error' => $error,
  87. 'failedLoginCount' => $currentCount,
  88. 'IPfailedLoginCount' => $IPcurrentCount,
  89. 'maxAttempts' => $maxAttempts,
  90. 'IPmaxAttempts' => $IPmaxAttempts,
  91. ]);
  92. }
  93. /**
  94. * @Route("/logout", name="logout")
  95. */
  96. public function logout(): void
  97. {
  98. throw new LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
  99. }
  100. }