/
var
/
www
/
html
/
restaurants
/
vendor
/
symfony
/
security-http
/
Firewall
/
Upload File
HOME
<?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Security\Http\Firewall; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Role\SwitchUserRole; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Event\SwitchUserEvent; use Symfony\Component\Security\Http\SecurityEvents; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * SwitchUserListener allows a user to impersonate another one temporarily * (like the Unix su command). * * @author Fabien Potencier <fabien@symfony.com> * * @final since Symfony 4.3 */ class SwitchUserListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; public const EXIT_VALUE = '_exit'; private $tokenStorage; private $provider; private $userChecker; private $providerKey; private $accessDecisionManager; private $usernameParameter; private $role; private $logger; private $dispatcher; private $stateless; public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, string $providerKey, AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger = null, string $usernameParameter = '_switch_user', string $role = 'ROLE_ALLOWED_TO_SWITCH', EventDispatcherInterface $dispatcher = null, bool $stateless = false) { if (empty($providerKey)) { throw new \InvalidArgumentException('$providerKey must not be empty.'); } $this->tokenStorage = $tokenStorage; $this->provider = $provider; $this->userChecker = $userChecker; $this->providerKey = $providerKey; $this->accessDecisionManager = $accessDecisionManager; $this->usernameParameter = $usernameParameter; $this->role = $role; $this->logger = $logger; if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) { $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); } else { $this->dispatcher = $dispatcher; } $this->stateless = $stateless; } /** * {@inheritdoc} */ public function supports(Request $request): ?bool { // usernames can be falsy $username = $request->get($this->usernameParameter); if (null === $username || '' === $username) { $username = $request->headers->get($this->usernameParameter); } // if it's still "empty", nothing to do. if (null === $username || '' === $username) { return false; } $request->attributes->set('_switch_user_username', $username); return true; } /** * Handles the switch to another user. * * @throws \LogicException if switching to a user failed */ public function authenticate(RequestEvent $event) { $request = $event->getRequest(); $username = $request->attributes->get('_switch_user_username'); $request->attributes->remove('_switch_user_username'); if (null === $this->tokenStorage->getToken()) { throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); } if (self::EXIT_VALUE === $username) { $this->tokenStorage->setToken($this->attemptExitUser($request)); } else { try { $this->tokenStorage->setToken($this->attemptSwitchUser($request, $username)); } catch (AuthenticationException $e) { // Generate 403 in any conditions to prevent user enumeration vulnerabilities throw new AccessDeniedException('Switch User failed: '.$e->getMessage(), $e); } } if (!$this->stateless) { $request->query->remove($this->usernameParameter); $request->server->set('QUERY_STRING', http_build_query($request->query->all(), '', '&')); $response = new RedirectResponse($request->getUri(), 302); $event->setResponse($response); } } /** * Attempts to switch to another user and returns the new token if successfully switched. * * @throws \LogicException * @throws AccessDeniedException */ private function attemptSwitchUser(Request $request, string $username): ?TokenInterface { $token = $this->tokenStorage->getToken(); $originalToken = $this->getOriginalToken($token); if (null !== $originalToken) { if ($token->getUsername() === $username) { return $token; } // User already switched, exit before seamlessly switching to another user $token = $this->attemptExitUser($request); } $currentUsername = $token->getUsername(); $nonExistentUsername = '_'.md5(random_bytes(8).$username); // To protect against user enumeration via timing measurements // we always load both successfully and unsuccessfully try { $user = $this->provider->loadUserByUsername($username); try { $this->provider->loadUserByUsername($nonExistentUsername); } catch (\Exception $e) { } } catch (AuthenticationException $e) { $this->provider->loadUserByUsername($currentUsername); throw $e; } if (false === $this->accessDecisionManager->decide($token, [$this->role], $user)) { $exception = new AccessDeniedException(); $exception->setAttributes($this->role); throw $exception; } if (null !== $this->logger) { $this->logger->info('Attempting to switch to user.', ['username' => $username]); } $this->userChecker->checkPostAuth($user); $roles = $user->getRoles(); $roles[] = new SwitchUserRole('ROLE_PREVIOUS_ADMIN', $token, false); $token = new SwitchUserToken($user, $user->getPassword(), $this->providerKey, $roles, $token); if (null !== $this->dispatcher) { $switchEvent = new SwitchUserEvent($request, $token->getUser(), $token); $this->dispatcher->dispatch($switchEvent, SecurityEvents::SWITCH_USER); // use the token from the event in case any listeners have replaced it. $token = $switchEvent->getToken(); } return $token; } /** * Attempts to exit from an already switched user and returns the original token. * * @throws AuthenticationCredentialsNotFoundException */ private function attemptExitUser(Request $request): TokenInterface { if (null === ($currentToken = $this->tokenStorage->getToken()) || null === $original = $this->getOriginalToken($currentToken)) { throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); } if (null !== $this->dispatcher && $original->getUser() instanceof UserInterface) { $user = $this->provider->refreshUser($original->getUser()); $original->setUser($user); $switchEvent = new SwitchUserEvent($request, $user, $original); $this->dispatcher->dispatch($switchEvent, SecurityEvents::SWITCH_USER); $original = $switchEvent->getToken(); } return $original; } private function getOriginalToken(TokenInterface $token): ?TokenInterface { if ($token instanceof SwitchUserToken) { return $token->getOriginalToken(); } foreach ($token->getRoles(false) as $role) { if ($role instanceof SwitchUserRole) { return $role->getSource(); } } return null; } }