Skip to content

Instantly share code, notes, and snippets.

@backbone87
Last active August 19, 2019 01:56
Show Gist options
  • Save backbone87/a03b426797385a04666d to your computer and use it in GitHub Desktop.
Save backbone87/a03b426797385a04666d to your computer and use it in GitHub Desktop.
<?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