<?php
declare(strict_types=1);
/*
* This file is part of Contao.
*
* (c) Leo Feyer
*
* @license LGPL-3.0-or-later
*/
namespace Contao\CoreBundle\Security\Authentication\Token;
use Contao\BackendUser;
use Contao\FrontendUser;
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Http\FirewallMapInterface;
class TokenChecker
{
private const FRONTEND_FIREWALL = 'contao_frontend';
private const BACKEND_FIREWALL = 'contao_backend';
/**
* @var RequestStack
*/
private $requestStack;
/**
* @var FirewallMapInterface
*/
private $firewallMap;
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* @var SessionInterface
*/
private $session;
/**
* @var AuthenticationTrustResolverInterface
*/
private $trustResolver;
/**
* @var VoterInterface
*/
private $roleVoter;
/**
* @internal Do not inherit from this class; decorate the "contao.security.token_checker" service instead
*/
public function __construct(RequestStack $requestStack, FirewallMapInterface $firewallMap, TokenStorageInterface $tokenStorage, SessionInterface $session, AuthenticationTrustResolverInterface $trustResolver, VoterInterface $roleVoter)
{
$this->requestStack = $requestStack;
$this->firewallMap = $firewallMap;
$this->tokenStorage = $tokenStorage;
$this->session = $session;
$this->trustResolver = $trustResolver;
$this->roleVoter = $roleVoter;
}
/**
* Checks if a front end user is authenticated.
*/
public function hasFrontendUser(): bool
{
$token = $this->getToken(self::FRONTEND_FIREWALL);
return null !== $token && VoterInterface::ACCESS_GRANTED === $this->roleVoter->vote($token, null, ['ROLE_MEMBER']);
}
/**
* Checks if a back end user is authenticated.
*/
public function hasBackendUser(): bool
{
$token = $this->getToken(self::BACKEND_FIREWALL);
return null !== $token && VoterInterface::ACCESS_GRANTED === $this->roleVoter->vote($token, null, ['ROLE_USER']);
}
/**
* Gets the front end username from the session.
*/
public function getFrontendUsername(): ?string
{
$token = $this->getToken(self::FRONTEND_FIREWALL);
if (null === $token || !$token->getUser() instanceof FrontendUser) {
return null;
}
return $token->getUser()->getUsername();
}
/**
* Gets the back end username from the session.
*/
public function getBackendUsername(): ?string
{
$token = $this->getToken(self::BACKEND_FIREWALL);
if (null === $token || !$token->getUser() instanceof BackendUser) {
return null;
}
return $token->getUser()->getUsername();
}
/**
* Tells whether the front end preview can show unpublished fragments.
*/
public function isPreviewMode(): bool
{
$request = $this->requestStack->getMasterRequest();
if (null === $request || !$request->attributes->get('_preview', false)) {
return false;
}
$token = $this->getToken(self::FRONTEND_FIREWALL);
return $token instanceof FrontendPreviewToken && $token->showUnpublished();
}
private function getToken(string $context): ?TokenInterface
{
$token = $this->getTokenFromStorage($context);
if (null === $token) {
$token = $this->getTokenFromSession('_security_'.$context);
}
if (!$token instanceof TokenInterface || !$token->isAuthenticated()) {
return null;
}
if ($this->trustResolver->isAnonymous($token)) {
return null;
}
return $token;
}
private function getTokenFromStorage(string $context): ?TokenInterface
{
$request = $this->requestStack->getMasterRequest();
if (!$this->firewallMap instanceof FirewallMap || null === $request) {
return null;
}
$config = $this->firewallMap->getFirewallConfig($request);
if (!$config instanceof FirewallConfig || $config->getContext() !== $context) {
return null;
}
return $this->tokenStorage->getToken();
}
private function getTokenFromSession(string $sessionKey): ?TokenInterface
{
if (!$this->session->isStarted()) {
$request = $this->requestStack->getMasterRequest();
if (!$request || !$request->hasPreviousSession()) {
return null;
}
}
// This will start the session if Request::hasPreviousSession() was true
if (!$this->session->has($sessionKey)) {
return null;
}
$token = unserialize($this->session->get($sessionKey), ['allowed_classes' => true]);
if (!$token instanceof TokenInterface) {
return null;
}
return $token;
}
}