Created
May 6, 2020 13:16
-
-
Save wizhippo/d0af6473ceb8956cc9c819e292a480d1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace AppBundle\Security\Http\Firewall; | |
use eZ\Publish\API\Repository\Repository; | |
use eZ\Publish\Core\MVC\Symfony\Event\InteractiveLoginEvent; | |
use eZ\Publish\Core\MVC\Symfony\MVCEvents; | |
use eZ\Publish\Core\MVC\Symfony\Security\UserWrapped; | |
use eZUser; | |
use Psr\Log\LoggerInterface; | |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
use Symfony\Component\HttpFoundation\RedirectResponse; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpKernel\Event\GetResponseEvent; | |
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | |
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; | |
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\UserProviderInterface; | |
use Symfony\Component\Security\Http\Event\SwitchUserEvent; | |
use Symfony\Component\Security\Http\Firewall\ListenerInterface; | |
use Symfony\Component\Security\Http\SecurityEvents; | |
/** | |
* Class SwitchUserListener. | |
*/ | |
class SwitchUserListener implements ListenerInterface | |
{ | |
private $tokenStorage; | |
private $provider; | |
private $userChecker; | |
private $providerKey; | |
private $accessDecisionManager; | |
private $usernameParameter; | |
private $role; | |
private $logger; | |
private $dispatcher; | |
private $stateless; | |
/** | |
* @var Repository | |
*/ | |
private $repository; | |
private $module; | |
private $function; | |
public function __construct( | |
TokenStorageInterface $tokenStorage, | |
UserProviderInterface $provider, | |
UserCheckerInterface $userChecker, | |
$providerKey, | |
AccessDecisionManagerInterface $accessDecisionManager, | |
LoggerInterface $logger = null, | |
$usernameParameter = '_switch_user', | |
$role = 'ROLE_ALLOWED_TO_SWITCH', | |
EventDispatcherInterface $dispatcher = null, | |
$stateless = false, | |
Repository $repository = null, | |
$module = null, | |
$function = null | |
) { | |
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; | |
$this->dispatcher = $dispatcher; | |
$this->stateless = $stateless; | |
$this->repository = $repository; | |
$this->module = $module; | |
$this->function = $function; | |
} | |
/** | |
* Handles the switch to another user. | |
* | |
* @param GetResponseEvent $event A GetResponseEvent instance | |
* | |
* @throws \LogicException if switching to a user failed | |
*/ | |
public function handle(GetResponseEvent $event) | |
{ | |
$request = $event->getRequest(); | |
if (!$request->get($this->usernameParameter)) { | |
return; | |
} | |
if ('_exit' === $request->get($this->usernameParameter)) { | |
$this->tokenStorage->setToken($this->attemptExitUser($request)); | |
} else { | |
try { | |
$this->tokenStorage->setToken($this->attemptSwitchUser($request)); | |
} catch (AuthenticationException $e) { | |
throw new \LogicException(sprintf('Switch User failed: "%s"', $e->getMessage())); | |
} | |
} | |
$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. | |
* | |
* @param Request $request A Request instance | |
* | |
* @return TokenInterface|null The new TokenInterface if successfully switched, null otherwise | |
* | |
* @throws \LogicException | |
* @throws AccessDeniedException | |
*/ | |
private function attemptSwitchUser(Request $request) | |
{ | |
$token = $this->tokenStorage->getToken(); | |
$originalToken = $this->getOriginalToken($token); | |
if (false !== $originalToken) { | |
if ($token->getUsername() === $request->get($this->usernameParameter)) { | |
return $token; | |
} | |
throw new \LogicException(sprintf('You are already switched to "%s" user.', $token->getUsername())); | |
} | |
if ($this->module !== null && $token->getUser() instanceof UserWrapped) { | |
if (false === $this->repository->getPermissionResolver()->hasAccess($this->module, $this->function, | |
$token->getUser()->getAPIUser()) | |
) { | |
throw new AccessDeniedException(); | |
} | |
} else { | |
if (false === $this->accessDecisionManager->decide($token, [$this->role])) { | |
throw new AccessDeniedException(); | |
} | |
} | |
$username = $request->get($this->usernameParameter); | |
if (null !== $this->logger) { | |
$this->logger->info('Attempting to switch to user.', ['username' => $username]); | |
} | |
$user = $this->provider->loadUserByUsername($username); | |
$this->userChecker->checkPostAuth($user); | |
$roles = $user->getRoles(); | |
$roles[] = new SwitchUserRole('ROLE_PREVIOUS_ADMIN', $this->tokenStorage->getToken()); | |
$token = new UsernamePasswordToken($user, $user->getPassword(), $this->providerKey, $roles); | |
if (!$user instanceof eZUser) { | |
$subLoginEvent = new InteractiveLoginEvent($request, $token); | |
$this->dispatcher->dispatch(MVCEvents::INTERACTIVE_LOGIN, $subLoginEvent); | |
if ($subLoginEvent->hasAPIUser()) { | |
$apiUser = $subLoginEvent->getAPIUser(); | |
} else { | |
return $token; | |
} | |
$token = new UsernamePasswordToken( | |
new UserWrapped($user, $apiUser), | |
$token->getCredentials(), | |
$this->providerKey, | |
$token->getRoles() | |
); | |
} | |
if (null !== $this->dispatcher) { | |
$switchEvent = new SwitchUserEvent($request, $token->getUser()); | |
$this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent); | |
} | |
return $token; | |
} | |
/** | |
* Attempts to exit from an already switched user. | |
* | |
* @param Request $request A Request instance | |
* | |
* @return TokenInterface The original TokenInterface instance | |
* | |
* @throws AuthenticationCredentialsNotFoundException | |
*/ | |
private function attemptExitUser(Request $request) | |
{ | |
if (false === $original = $this->getOriginalToken($this->tokenStorage->getToken())) { | |
throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); | |
} | |
if (null !== $this->dispatcher) { | |
$user = $this->provider->refreshUser($original->getUser()); | |
$switchEvent = new SwitchUserEvent($request, $user); | |
$this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent); | |
} | |
return $original; | |
} | |
/** | |
* Gets the original Token from a switched one. | |
* | |
* @param TokenInterface $token A switched TokenInterface instance | |
* | |
* @return TokenInterface|false The original TokenInterface instance, false if the current TokenInterface is not | |
* switched | |
*/ | |
private function getOriginalToken(TokenInterface $token) | |
{ | |
foreach ($token->getRoles() as $role) { | |
if ($role instanceof SwitchUserRole) { | |
return $role->getSource(); | |
} | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment