From 1ba05b923ce516fec44f0e5b181a2b0dfa5811d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sessing=C3=B8?= Date: Wed, 21 Oct 2015 18:12:53 +0200 Subject: [PATCH] [FEATURE] Csrf token - Added functionality to CsrfToken class. - Added header support to Request class. - Added option to set BaseCsrfVerifier class in RouterBase and SimpleRouter. --- src/Pecee/CsrfToken.php | 26 +++++++++++---- .../Http/Middleware/BaseCsrfVerifier.php | 33 +++++++++++++++++++ src/Pecee/Http/Middleware/Middleware.php | 4 +-- src/Pecee/Http/Middleware/VerifyCsrfToken.php | 9 ----- src/Pecee/Http/Request.php | 19 +++++++++++ src/Pecee/SimpleRouter/RouterBase.php | 33 +++++++++++++++++-- src/Pecee/SimpleRouter/SimpleRouter.php | 15 +++++++++ 7 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 src/Pecee/Http/Middleware/BaseCsrfVerifier.php delete mode 100644 src/Pecee/Http/Middleware/VerifyCsrfToken.php diff --git a/src/Pecee/CsrfToken.php b/src/Pecee/CsrfToken.php index 80311c7..ebf4aa6 100644 --- a/src/Pecee/CsrfToken.php +++ b/src/Pecee/CsrfToken.php @@ -3,22 +3,21 @@ namespace Pecee; class CsrfToken { - const CSRF_KEY = 'csrf'; + const CSRF_KEY = 'XSRF-TOKEN'; protected $token; public function __construct() { - $this->lastToken = isset($_SESSION[self::CSRF_KEY]) ? $_SESSION[self::CSRF_KEY] : null; - $this->currentToken = $this->generate(); - - $_COOKIE[self::CSRF_KEY] = $this->currentToken; + if($this->getToken() === null) { + $this->setToken($this->generateToken()); + } } /** * Generate random identifier for CSRF token * @return string */ - public static function generate() { + public static function generateToken() { if (function_exists('mcrypt_create_iv')) { return bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); } @@ -32,10 +31,23 @@ class CsrfToken { * @return bool */ public function validate($token) { - return hash_equals($token, $this->getCurrentToken()); + if($token !== null && $this->getToken() !== null) { + return hash_equals($token, $this->getToken()); + } + return false; } /** + * Set csrf token cookie + * + * @param $token + */ + public function setToken($token) { + setcookie(self::CSRF_KEY, $token, time() + 60 * 120, '/'); + } + + /** + * Get csrf token * @return string|null */ public function getToken(){ diff --git a/src/Pecee/Http/Middleware/BaseCsrfVerifier.php b/src/Pecee/Http/Middleware/BaseCsrfVerifier.php new file mode 100644 index 0000000..4a401cc --- /dev/null +++ b/src/Pecee/Http/Middleware/BaseCsrfVerifier.php @@ -0,0 +1,33 @@ +getMethod() != 'get') { + + $token = (isset($_POST[self::POST_KEY])) ? $_POST[self::POST_KEY] : null; + + // If the token is not posted, check headers for valid x-csrf-token + if($token === null) { + $token = $request->getHeader(self::HEADER_KEY); + } + + $tokenValidator = new CsrfToken(); + if( !$tokenValidator->validate( $token ) ) { + throw new RouterException('Invalid csrf-token.'); + } + + } + + } +} \ No newline at end of file diff --git a/src/Pecee/Http/Middleware/Middleware.php b/src/Pecee/Http/Middleware/Middleware.php index 3f04715..4b70ec0 100644 --- a/src/Pecee/Http/Middleware/Middleware.php +++ b/src/Pecee/Http/Middleware/Middleware.php @@ -7,7 +7,5 @@ use Pecee\SimpleRouter\RouterEntry; abstract class Middleware { - public function handle(Request $request) { - return true; - } + abstract function handle(Request $request); } \ No newline at end of file diff --git a/src/Pecee/Http/Middleware/VerifyCsrfToken.php b/src/Pecee/Http/Middleware/VerifyCsrfToken.php deleted file mode 100644 index fdaa63f..0000000 --- a/src/Pecee/Http/Middleware/VerifyCsrfToken.php +++ /dev/null @@ -1,9 +0,0 @@ -host = $_SERVER['HTTP_HOST']; $this->uri = rtrim($_SERVER['REQUEST_URI'], '/') . '/'; $this->method = (isset($_POST['_method'])) ? strtolower($_POST['_method']) : strtolower($_SERVER['REQUEST_METHOD']); + $this->headers = getallheaders(); } /** @@ -50,4 +52,21 @@ class Request { return (isset($_SERVER['PHP_AUTH_PW'])) ? $_SERVER['PHP_AUTH_PW']: null; } + /** + * Get headers + * @return array + */ + public function getHeaders() { + return $this->headers; + } + + /** + * Get header value by name + * @param string $name + * @return string|null + */ + public function getHeader($name) { + return (isset($this->headers[$name])) ? $this->headers[$name] : null; + } + } \ No newline at end of file diff --git a/src/Pecee/SimpleRouter/RouterBase.php b/src/Pecee/SimpleRouter/RouterBase.php index b5cc3d0..3b6559c 100644 --- a/src/Pecee/SimpleRouter/RouterBase.php +++ b/src/Pecee/SimpleRouter/RouterBase.php @@ -2,6 +2,7 @@ namespace Pecee\SimpleRouter; use Pecee\ArrayUtil; +use Pecee\Http\Middleware\BaseCsrfVerifier; use Pecee\Http\Request; use Pecee\Url; @@ -17,6 +18,7 @@ class RouterBase { protected $backstack; protected $loadedRoute; protected $defaultNamespace; + protected $baseCsrfVerifier; // TODO: make interface for controller routers, so they can be easily detected // TODO: clean up - cut some of the methods down to smaller pieces @@ -26,6 +28,7 @@ class RouterBase { $this->backstack = array(); $this->controllerUrlMap = array(); $this->request = new Request(); + $this->baseCsrfVerifier = new BaseCsrfVerifier(); } public function addRoute(RouterEntry $route) { @@ -85,8 +88,16 @@ class RouterBase { } public function routeRequest() { - // Loop through each route-request + // Verify csrf token for request + if($this->baseCsrfVerifier !== null) { + /* @var $csrfVerifier BaseCsrfVerifier */ + $csrfVerifier = $this->baseCsrfVerifier; + $csrfVerifier = new $csrfVerifier(); + $csrfVerifier->handle($this->request); + } + + // Loop through each route-request $this->processRoutes($this->routes); // Make sure the urls is in the right order when comparing @@ -100,7 +111,6 @@ class RouterBase { foreach($this->controllerUrlMap as $route) { $routeMatch = $route->matchRoute($this->request); - if($routeMatch && !($routeMatch instanceof RouterGroup)) { if(count($route->getRequestMethods()) && !in_array($this->request->getMethod(), $route->getRequestMethods())) { @@ -179,6 +189,25 @@ class RouterBase { return $this->request; } + /** + * Get base csrf verifier class + * @return BaseCsrfVerifier + */ + public function getBaseCsrfVerifier() { + return $this->baseCsrfVerifier; + } + + /** + * Set base csrf verifier class + * + * @param BaseCsrfVerifier $baseCsrfVerifier + * @return self + */ + public function setBaseCsrfVerifier(BaseCsrfVerifier $baseCsrfVerifier) { + $this->baseCsrfVerifier = $baseCsrfVerifier; + return $this; + } + protected function processUrl($route, $method = null, $parameters = null, $getParams = null) { $url = $route->getUrl(); diff --git a/src/Pecee/SimpleRouter/SimpleRouter.php b/src/Pecee/SimpleRouter/SimpleRouter.php index 99b050a..c4e6501 100644 --- a/src/Pecee/SimpleRouter/SimpleRouter.php +++ b/src/Pecee/SimpleRouter/SimpleRouter.php @@ -9,14 +9,29 @@ namespace Pecee\SimpleRouter; +use Pecee\Http\Middleware\BaseCsrfVerifier; + class SimpleRouter { + /** + * Start/route request + * @param null $defaultNamespace + * @throws RouterException + */ public static function start($defaultNamespace = null) { $router = RouterBase::GetInstance(); $router->setDefaultNamespace($defaultNamespace); $router->routeRequest(); } + /** + * Set base csrf verifier + * @param BaseCsrfVerifier $baseCsrfVerifier + */ + public static function csrfVerifier(BaseCsrfVerifier $baseCsrfVerifier) { + RouterBase::getInstance()->setBaseCsrfVerifier($baseCsrfVerifier); + } + public static function get($url, $callback, array $settings = null) { $route = new RouterRoute($url, $callback); $route->addSettings($settings);