vendor/contao/core-bundle/src/Security/Authentication/Token/TokenChecker.php line 62

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of Contao.
  5. *
  6. * (c) Leo Feyer
  7. *
  8. * @license LGPL-3.0-or-later
  9. */
  10. namespace Contao\CoreBundle\Security\Authentication\Token;
  11. use Contao\BackendUser;
  12. use Contao\FrontendUser;
  13. use Doctrine\DBAL\Connection;
  14. use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
  15. use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
  16. use Symfony\Component\HttpFoundation\Request;
  17. use Symfony\Component\HttpFoundation\RequestStack;
  18. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  19. use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
  20. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  21. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  22. use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
  23. use Symfony\Component\Security\Http\FirewallMapInterface;
  24. class TokenChecker
  25. {
  26. private const FRONTEND_FIREWALL = 'contao_frontend';
  27. private const BACKEND_FIREWALL = 'contao_backend';
  28. private RequestStack $requestStack;
  29. private FirewallMapInterface $firewallMap;
  30. private TokenStorageInterface $tokenStorage;
  31. private SessionInterface $session;
  32. private AuthenticationTrustResolverInterface $trustResolver;
  33. private VoterInterface $roleVoter;
  34. private Connection $connection;
  35. private array $previewLinks;
  36. /**
  37. * @internal
  38. */
  39. public function __construct(RequestStack $requestStack, FirewallMapInterface $firewallMap, TokenStorageInterface $tokenStorage, SessionInterface $session, AuthenticationTrustResolverInterface $trustResolver, VoterInterface $roleVoter, Connection $connection)
  40. {
  41. $this->requestStack = $requestStack;
  42. $this->firewallMap = $firewallMap;
  43. $this->tokenStorage = $tokenStorage;
  44. $this->session = $session;
  45. $this->trustResolver = $trustResolver;
  46. $this->roleVoter = $roleVoter;
  47. $this->connection = $connection;
  48. }
  49. /**
  50. * Checks if a front end user is authenticated.
  51. */
  52. public function hasFrontendUser(): bool
  53. {
  54. $token = $this->getToken(self::FRONTEND_FIREWALL);
  55. return null !== $token && VoterInterface::ACCESS_GRANTED === $this->roleVoter->vote($token, null, ['ROLE_MEMBER']);
  56. }
  57. /**
  58. * Checks if a back end user is authenticated.
  59. */
  60. public function hasBackendUser(): bool
  61. {
  62. $token = $this->getToken(self::BACKEND_FIREWALL);
  63. return null !== $token && VoterInterface::ACCESS_GRANTED === $this->roleVoter->vote($token, null, ['ROLE_USER']);
  64. }
  65. /**
  66. * Gets the front end username from the session.
  67. */
  68. public function getFrontendUsername(): ?string
  69. {
  70. $token = $this->getToken(self::FRONTEND_FIREWALL);
  71. if (null === $token || !$token->getUser() instanceof FrontendUser) {
  72. return null;
  73. }
  74. return $token->getUser()->getUserIdentifier();
  75. }
  76. /**
  77. * Gets the back end username from the session.
  78. */
  79. public function getBackendUsername(): ?string
  80. {
  81. $token = $this->getToken(self::BACKEND_FIREWALL);
  82. if (null === $token || !$token->getUser() instanceof BackendUser) {
  83. return null;
  84. }
  85. return $token->getUser()->getUserIdentifier();
  86. }
  87. /**
  88. * Tells whether the front end preview can be accessed.
  89. */
  90. public function canAccessPreview(): bool
  91. {
  92. if ($this->hasBackendUser()) {
  93. return true;
  94. }
  95. $token = $this->getToken(self::FRONTEND_FIREWALL);
  96. if (!$token instanceof FrontendPreviewToken) {
  97. return false;
  98. }
  99. if (null === $token->getPreviewLinkId()) {
  100. return false;
  101. }
  102. return $this->isValidPreviewLink($token);
  103. }
  104. /**
  105. * Tells whether the front end preview can show unpublished fragments.
  106. */
  107. public function isPreviewMode(): bool
  108. {
  109. $request = $this->requestStack->getMainRequest();
  110. if (null === $request || !$request->attributes->get('_preview', false) || !$this->canAccessPreview()) {
  111. return false;
  112. }
  113. $token = $this->getToken(self::FRONTEND_FIREWALL);
  114. return $token instanceof FrontendPreviewToken && $token->showUnpublished();
  115. }
  116. public function isFrontendFirewall(): bool
  117. {
  118. return self::FRONTEND_FIREWALL === $this->getFirewallContext();
  119. }
  120. public function isBackendFirewall(): bool
  121. {
  122. return self::BACKEND_FIREWALL === $this->getFirewallContext();
  123. }
  124. private function getToken(string $context): ?TokenInterface
  125. {
  126. $token = $this->getTokenFromStorage($context);
  127. if (null === $token) {
  128. $token = $this->getTokenFromSession('_security_'.$context);
  129. }
  130. if (!$token instanceof TokenInterface || !$token->isAuthenticated()) {
  131. return null;
  132. }
  133. if ($this->trustResolver->isAnonymous($token)) {
  134. return null;
  135. }
  136. return $token;
  137. }
  138. private function getTokenFromStorage(string $context): ?TokenInterface
  139. {
  140. if ($this->getFirewallContext() !== $context) {
  141. return null;
  142. }
  143. return $this->tokenStorage->getToken();
  144. }
  145. private function getFirewallContext(): ?string
  146. {
  147. $request = $this->requestStack->getMainRequest();
  148. if (!$this->firewallMap instanceof FirewallMap || null === $request) {
  149. return null;
  150. }
  151. $config = $this->firewallMap->getFirewallConfig($request);
  152. if (!$config instanceof FirewallConfig) {
  153. return null;
  154. }
  155. return $config->getContext();
  156. }
  157. private function getTokenFromSession(string $sessionKey): ?TokenInterface
  158. {
  159. if (!$this->session->isStarted()) {
  160. $request = $this->requestStack->getMainRequest();
  161. if (!$request || !$request->hasPreviousSession()) {
  162. return null;
  163. }
  164. }
  165. // This will start the session if Request::hasPreviousSession() was true
  166. if (!$this->session->has($sessionKey)) {
  167. return null;
  168. }
  169. $token = unserialize($this->session->get($sessionKey), ['allowed_classes' => true]);
  170. if (!$token instanceof TokenInterface) {
  171. return null;
  172. }
  173. return $token;
  174. }
  175. private function isValidPreviewLink(FrontendPreviewToken $token): bool
  176. {
  177. $id = $token->getPreviewLinkId();
  178. if (!isset($this->previewLinks[$id])) {
  179. $this->previewLinks[$id] = $this->connection->fetchAssociative(
  180. "
  181. SELECT
  182. url,
  183. showUnpublished,
  184. restrictToUrl
  185. FROM tl_preview_link
  186. WHERE
  187. id = :id
  188. AND published = '1'
  189. AND expiresAt > UNIX_TIMESTAMP()
  190. ",
  191. ['id' => $id],
  192. );
  193. }
  194. $previewLink = $this->previewLinks[$id];
  195. if (!$previewLink) {
  196. return false;
  197. }
  198. if ((bool) $previewLink['showUnpublished'] !== $token->showUnpublished()) {
  199. return false;
  200. }
  201. if (!$previewLink['restrictToUrl']) {
  202. return true;
  203. }
  204. $request = $this->requestStack->getMainRequest();
  205. return $request && strtok($request->getUri(), '?') === strtok(Request::create($previewLink['url'])->getUri(), '?');
  206. }
  207. }