src/Controller/User/SecurityController.php line 68

Open in your IDE?
  1. <?php
  2. namespace App\Controller\User;
  3. use App\Entity\User\User;
  4. use App\Exception\Core\SSO\NoTokenException;
  5. use App\Security\Firewall\DefaultFirewall;
  6. use App\Services\User\LoginManager;
  7. use App\Services\Core\AuthService;
  8. use App\Services\Core\CreateDemoClassroom;
  9. use App\Services\Core\EventLogger;
  10. use App\Services\Core\SelfStudyVoterService;
  11. use App\Services\Core\TextbookVoterService;
  12. use DateTime;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use Exception;
  15. use GuzzleHttp\Exception\RequestException;
  16. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  17. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  18. use Symfony\Component\HttpFoundation\RedirectResponse;
  19. use Symfony\Component\HttpFoundation\Request;
  20. use Symfony\Component\HttpFoundation\RequestStack;
  21. use Symfony\Component\HttpFoundation\Response;
  22. use Symfony\Component\Routing\Annotation\Route;
  23. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  24. use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
  25. use Symfony\Component\Security\Core\Security;
  26. use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
  27. use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
  28. use Symfony\Component\Security\Http\Util\TargetPathTrait;
  29. class SecurityController extends AbstractController
  30. {
  31. use TargetPathTrait;
  32. private Request $request;
  33. public function __construct(
  34. private readonly EntityManagerInterface $entityManager,
  35. private readonly EventLogger $eventLogger,
  36. private readonly TextbookVoterService $textbookVoterService,
  37. private readonly SelfStudyVoterService $selfStudyVoterService,
  38. private readonly LoginManager $loginManager,
  39. private readonly CreateDemoClassroom $createDemoClassroom,
  40. private readonly AuthService $authService,
  41. private readonly CsrfTokenManagerInterface $tokenManager,
  42. private readonly RequestStack $requestStack,
  43. private readonly AuthenticationUtils $authenticationUtils,
  44. private readonly ParameterBagInterface $parameterBag
  45. )
  46. {
  47. }
  48. // route : /cms or /testlogin redirects to /login?alternative_login=1
  49. #[Route('/testlogin', name: 'alternative_login')]
  50. #[Route('/cms', name: 'alternative_login_cms')]
  51. public function alternativeLogin(Request $request): RedirectResponse
  52. {
  53. $request->getSession()->set('login_method', 'alternative');
  54. return $this->redirectToRoute("login", [
  55. "alternative_login" => true,
  56. ]);
  57. }
  58. // route /login
  59. #[Route('/login', name: 'login')]
  60. public function loginAction(): Response
  61. {
  62. $this->request = $this->requestStack->getCurrentRequest();
  63. $session = $this->request->getSession();
  64. $authErrorKey = Security::AUTHENTICATION_ERROR;
  65. // get the error if any (works with forward and redirect -- see below)
  66. if ($this->request->attributes->has($authErrorKey)) {
  67. $error = $this->request->attributes->get($authErrorKey);
  68. } elseif (null !== $session && $session->has($authErrorKey)) {
  69. $error = $session->get($authErrorKey);
  70. $session->remove($authErrorKey);
  71. } else {
  72. $error = null;
  73. }
  74. if (!$error instanceof AuthenticationException) {
  75. $error = null; // The value does not come from the security component.
  76. }
  77. $csrfToken = $this->tokenManager->getToken('authenticate')->getValue();
  78. $environment = $this->parameterBag->get('abacus_environment');
  79. $data = [
  80. 'error' => $error,
  81. 'csrf_token' => $csrfToken,
  82. 'environment' => $environment
  83. ];
  84. if ($this->shouldShowAlternativeLogin($error)) {
  85. return $this->render('User/Security/alternative_login.html.twig', $data);
  86. }
  87. return $this->render('User/Security/skip_to_unilogin.html.twig', $data);
  88. }
  89. #[Route('/user/unilogin', name: 'uni_login_return_route')]
  90. public function uniLoginReturn()
  91. {
  92. $ssoProvider = $this->authService->getSsoProvider();
  93. try {
  94. $ssoProvider->initialize();
  95. if (!$ssoProvider->isValidToken()) {
  96. throw $this->createAccessDeniedException('The UNI-login token is invalid.');
  97. }
  98. /* Validate license primaryschool */
  99. if ($this->getParameter('abacus.loginconnector.require_license') == 1) {
  100. if (!$ssoProvider->checkIfUserHasAccess()) {
  101. return $this->render('User/Security/nolicence_kvik.html.twig', []);
  102. }
  103. }
  104. } catch (RequestException | NoTokenException $e) {
  105. return $this->render('User/Security/timeout_error.html.twig');
  106. }
  107. // Find UNI user
  108. /** @var User $user */
  109. $user = $ssoProvider->findUserIfExists();
  110. if ($user instanceof User) {
  111. /* Existing user */
  112. /* Update institution and role */
  113. /** @var User $user */
  114. $user = $ssoProvider->updateUser($user);
  115. $user->setLastLoginWithUnilogin(new DateTime()); // save timestamp
  116. $this->eventLogger->log('login_' . $_SERVER['HTTP_USER_AGENT'], $user);
  117. }
  118. else {
  119. /* New user */
  120. /** @var User $user */
  121. $user = $ssoProvider->generateUser();
  122. $user->setLastLoginWithUnilogin(new DateTime()); // save timestamp
  123. $this->eventLogger->log('createuser', $user, $user->getInstitution()->getName());
  124. }
  125. $this->persistUser($user);
  126. /* Validate license for highschool */
  127. if ($this->getParameter('abacus_environment') === 'highschool') {
  128. if ($user->hasRole(User::role_teacher)) {
  129. $isTemporarilyGrantedAccess = $this->getParameter('abacus.systime.require_license') === 0;
  130. if (!$isTemporarilyGrantedAccess && !$ssoProvider->canHighschoolTeacherAccess($user)) {
  131. return $this->render('User/Security/nolicense_abacus.html.twig', [
  132. 'username' => $user->getUsername()
  133. ]);
  134. }
  135. }
  136. }
  137. return $this->loginUniUser($user);
  138. }
  139. /* Login existing user */
  140. private function loginUniUser(User $user): RedirectResponse
  141. {
  142. $env = $this->getParameter('abacus_environment');
  143. try {
  144. if ($user->hasRole(User::role_student) && $env=="primaryschool") {
  145. if (!is_numeric($user->getClassLevel())) {
  146. $user->setClassLevel(9);
  147. }
  148. }
  149. else if ($user->hasRole(User::role_student) && $env === "highschool") {
  150. $this->textbookVoterService->updateAccess($user);
  151. $this->selfStudyVoterService->updateAccess($user);
  152. }
  153. } catch (Exception $e) {
  154. }
  155. $response = $this->redirectToRoute('login_redirect_route');
  156. $this->loginManager->loginUser(DefaultFirewall::NAME, $user, $response);
  157. $this->createDemoClassroom->create($user);
  158. return $response;
  159. }
  160. public function uniLoginAction(): Response
  161. {
  162. return $this->render('User/Security/uni_login.html.twig');
  163. }
  164. private function persistUser($user)
  165. {
  166. $this->entityManager->persist($user);
  167. $this->entityManager->flush();
  168. }
  169. private function shouldShowAlternativeLogin(?AuthenticationException $error): bool
  170. {
  171. $scope = $this->parameterBag->get('abacus.scope');
  172. if ($scope === 'gale') {
  173. // Never use UniLogin for Gale
  174. return true;
  175. }
  176. if ($this->request->query->has("alternative_login")) {
  177. return true;
  178. }
  179. // Failed alternative login attempt - keep using alternative login
  180. if ($error !== null && $this->request->getSession()->get('login_method') === 'alternative') {
  181. return true;
  182. }
  183. if ($error instanceof InvalidCsrfTokenException) {
  184. // Csrf token should only be used for alternative login
  185. return true;
  186. }
  187. // Trying to access /easyadmin and not logged in - use alternative login
  188. if ($target = $this->getTargetPath($this->request->getSession(), DefaultFirewall::NAME)) {
  189. $targetParts = parse_url($target);
  190. if (str_starts_with($targetParts['path'] ?? '', '/easyadmin')) {
  191. return true;
  192. }
  193. }
  194. return false;
  195. }
  196. }