Created
February 24, 2015 08:40
-
-
Save jaytaph/c75ea98dbefea9e83790 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
services: | |
security.authentication.switchuser_listener: | |
class: Rainbow\SecurityBundle\Security\Firewall\SwitchUserListener | |
public: false | |
abstract: true | |
arguments: [ "@security.context", | |
"", | |
"@security.user_checker", | |
"", | |
"@security.access.decision_manager", | |
"@?logger", | |
"_switch_user", | |
"ROLE_ALLOWED_TO_SWITCH", | |
"@?event_dispatcher", | |
"ROLE_SWITCHABLE" | |
] | |
tags: | |
- { name: monolog.logger, channel: security } |
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 Rainbow\SecurityBundle\Security\Firewall; | |
use Symfony\Component\Security\Core\Exception\AccessDeniedException; | |
use Symfony\Component\Security\Core\SecurityContextInterface; | |
use Symfony\Component\Security\Core\User\UserProviderInterface; | |
use Symfony\Component\Security\Core\User\UserCheckerInterface; | |
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; | |
use Psr\Log\LoggerInterface; | |
use Symfony\Component\HttpKernel\Event\GetResponseEvent; | |
use Symfony\Component\Security\Core\Exception\AuthenticationException; | |
use Symfony\Component\HttpFoundation\RedirectResponse; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\Security\Core\Role\SwitchUserRole; | |
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; | |
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; | |
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |
use Symfony\Component\Security\Http\Event\SwitchUserEvent; | |
use Symfony\Component\Security\Http\Firewall\ListenerInterface; | |
use Symfony\Component\Security\Http\SecurityEvents; | |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
class SwitchUserListener implements ListenerInterface | |
{ | |
private $securityContext; | |
private $provider; | |
private $userChecker; | |
private $providerKey; | |
private $accessDecisionManager; | |
private $usernameParameter; | |
private $role; | |
private $logger; | |
private $dispatcher; | |
private $switchableRole; | |
public function __construct(SecurityContextInterface $securityContext, | |
UserProviderInterface $provider, | |
UserCheckerInterface $userChecker, | |
$providerKey, | |
AccessDecisionManagerInterface $accessDecisionManager, | |
LoggerInterface $logger = null, | |
$usernameParameter = '_switch_user', | |
$role = 'ROLE_ALLOWED_TO_SWITCH', | |
EventDispatcherInterface $dispatcher = null, | |
$switchableRole = 'ROLE_SWITCHABLE') | |
{ | |
if (empty($providerKey)) { | |
throw new \InvalidArgumentException('$providerKey must not be empty.'); | |
} | |
$this->securityContext = $securityContext; | |
$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->switchableRole = $switchableRole; | |
} | |
public function handle(GetResponseEvent $event) | |
{ | |
$request = $event->getRequest(); | |
if (!$request->get($this->usernameParameter)) { | |
return; | |
} | |
if ('_exit' === $request->get($this->usernameParameter)) { | |
$this->securityContext->setToken($this->attemptExitUser($request)); | |
} else { | |
try { | |
$this->securityContext->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); | |
} | |
private function attemptSwitchUser(Request $request) | |
{ | |
$token = $this->securityContext->getToken(); | |
$originalToken = $this->getOriginalToken($token); | |
if (false !== $originalToken) { | |
if ($token->getUsername() === $request->get($this->usernameParameter)) { | |
return $token; | |
} else { | |
throw new \LogicException( | |
sprintf('You are already switched to "%s" user.', $token->getUsername()) | |
); | |
} | |
} | |
if (false === $this->accessDecisionManager->decide($token, array($this->role))) { | |
throw new AccessDeniedException(); | |
} | |
$username = $request->get($this->usernameParameter); | |
if (null !== $this->logger) { | |
$this->logger->info(sprintf('Attempt to switch to user "%s"', $username)); | |
} | |
$user = $this->provider->loadUserByUsername($username); | |
$this->userChecker->checkPostAuth($user); | |
$roles = $user->getRoles(); | |
$roles[] = new SwitchUserRole('ROLE_PREVIOUS_ADMIN', $this->securityContext->getToken()); | |
$token = new UsernamePasswordToken( | |
$user, | |
$user->getPassword(), | |
$this->providerKey, | |
$roles | |
); | |
// START: THIS IS WHAT WE NEED TO ADD | |
// Do not allow access when the user we switch to has not the switchable role | |
if (false === $this->accessDecisionManager->decide($token, array($this->switchableRole))) { | |
throw new AccessDeniedException(); | |
} | |
// END: THIS IS WHAT WE NEED TO ADD | |
if (null !== $this->dispatcher) { | |
$switchEvent = new SwitchUserEvent($request, $token->getUser()); | |
$this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent); | |
} | |
return $token; | |
} | |
private function attemptExitUser(Request $request) | |
{ | |
if (false === $original = $this->getOriginalToken($this->securityContext->getToken())) { | |
throw new AuthenticationCredentialsNotFoundException( | |
'Could not find original Token object.' | |
); | |
} | |
if (null !== $this->dispatcher) { | |
$switchEvent = new SwitchUserEvent($request, $original->getUser()); | |
$this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent); | |
} | |
return $original; | |
} | |
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