src/UserBundle/Controller/SecurityController.php line 86

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