Instantly share code, notes, and snippets.
Last active
August 19, 2019 01:56
-
Star
(1)
1
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save backbone87/a03b426797385a04666d 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 Symfony\Component\Security\Csrf\TokenStorage; | |
use Symfony\Component\HttpFoundation\Cookie; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpFoundation\RequestStack; | |
use Symfony\Component\HttpKernel\Event\FilterResponseEvent; | |
use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException; | |
/** | |
* @author Oliver Hoff <oliver@hofff.com> | |
*/ | |
class CookieTokenStorage implements TokenStorageInterface { | |
/** | |
* @var string | |
*/ | |
const TRANSIENT_STORAGE_NAME = '_csrf'; | |
/** | |
* @var RequestStack | |
*/ | |
private $requestStack; | |
/** | |
* {@inheritDoc} | |
* @see \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface::getToken() | |
*/ | |
public function getToken($tokenId) { | |
$token = $this->resolveToken($tokenId); | |
if(!strlen($token)) { | |
throw new TokenNotFoundException; | |
} | |
return $token; | |
} | |
/** | |
* {@inheritDoc} | |
* @see \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface::hasToken() | |
*/ | |
public function hasToken($tokenId) { | |
return strlen($this->resolveToken($tokenId)) > 0; | |
} | |
/** | |
* {@inheritDoc} | |
* @see \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface::setToken() | |
*/ | |
public function setToken($tokenId, $token) { | |
if(!strlen($token)) { | |
throw new \InvalidArgumentException('Empty tokens are not allowed'); | |
} | |
$this->updateToken($tokenId, $token); | |
} | |
/** | |
* {@inheritDoc} | |
* @see \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface::removeToken() | |
*/ | |
public function removeToken($tokenId) { | |
$this->updateToken($tokenId, ''); | |
} | |
/** | |
* @param FilterResponseEvent $event | |
* @return void | |
*/ | |
public function onKernelResponse(FilterResponseEvent $event) { | |
// only for master requests? | |
if(!$event->isMasterRequest()) { | |
return; | |
} | |
$request = $event->getRequest(); | |
$secure = $request->isSecure(); | |
$storage = $this->retrieveTransientTokenStorage($request); | |
$response = $event->getResponse(); | |
foreach($storage as $tokenId => $token) { | |
$name = $this->generateCookieName($tokenId, $secure); | |
// empty tokens are handled by the http foundations cookie class | |
// and are recognized as "delete" cookie | |
// the problem is the that the value of deleted cookies get set to | |
// the string "deleted" and not the empty string | |
$cookie = new Cookie($name, $token, 0, null, null, $secure, true); // http only? | |
$response->headers->setCookie($cookie); | |
} | |
} | |
/** | |
* @throws \RuntimeException | |
* @return \Request | |
*/ | |
private function retrieveRequest() { | |
$request = $this->requestStack->getMasterRequest(); | |
// or should we use the current request? | |
// $request = $this->requestStack->getCurrentRequest(); | |
if(!$request) { | |
throw new \RuntimeException('Not in a request context'); | |
} | |
return $request; | |
} | |
/** | |
* @param string $tokenId | |
* @return string | |
*/ | |
private function resolveToken($tokenId) { | |
$request = $this->retrieveRequest(); | |
$storage = $this->retrieveTransientTokenStorage($request); | |
if($storage->offsetExists($tokenId)) { | |
$token = $storage->offsetGet($tokenId); | |
} else { | |
$name = $this->generateCookieName($tokenId, $request->isSecure()); | |
$token = $request->cookies->get($name, ''); | |
} | |
return $token; | |
} | |
/** | |
* @param string $tokenId | |
* @param string $token | |
* @return void | |
*/ | |
private function updateToken($tokenId, $token) { | |
$token = (string) $token; | |
$request = $this->getRequest(); | |
$storage = $this->retrieveTransientTokenStorage($request); | |
$name = $this->generateCookieName($tokenId, $request->isSecure()); | |
if($token === $request->cookies->get($name, '')) { | |
$storage->offsetUnset($tokenId); | |
} else { | |
$storage->offsetSet($tokenId, $token); | |
} | |
} | |
/** | |
* @param Request $request | |
* @param string $tokenId | |
* @return string | |
*/ | |
private function generateCookieName($tokenId, $secure) { | |
return sprintf( | |
'_csrf/%s/%s', | |
$secure ? 'insecure' : 'secure', | |
$tokenId | |
); | |
} | |
/** | |
* @param Request $request | |
* @return \ArrayObject | |
*/ | |
private function retrieveTransientTokenStorage(Request $request) { | |
$storage = $request->attributes->get(self::TRANSIENT_STORAGE_NAME); | |
if(!$storage instanceof \ArrayObject) { | |
$storage = new \ArrayObject; | |
$request->attributes->set(self::TRANSIENT_STORAGE_NAME, $storage); | |
} | |
return $storage; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment