Files
simple-php-router/src/Pecee/Http/Middleware/BaseCsrfVerifier.php

132 lines
3.4 KiB
PHP

<?php
namespace Pecee\Http\Middleware;
use Pecee\Http\Middleware\Exceptions\TokenMismatchException;
use Pecee\Http\Request;
use Pecee\Http\Security\CookieTokenProvider;
use Pecee\Http\Security\ITokenProvider;
class BaseCsrfVerifier implements IMiddleware
{
public const POST_KEY = 'csrf_token';
public const HEADER_KEY = 'X-CSRF-TOKEN';
/**
* Urls to ignore. You can use * to exclude all sub-urls on a given path.
* For example: /admin/*
* @var array|null
*/
protected array $except = [];
/**
* Urls to include. Can be used to include urls from a certain path.
* @var array|null
*/
protected array $include = [];
/**
* @var ITokenProvider
*/
protected ITokenProvider $tokenProvider;
/**
* BaseCsrfVerifier constructor.
*/
public function __construct()
{
$this->tokenProvider = new CookieTokenProvider();
}
protected function isIncluded(Request $request): bool
{
if (count($this->include) > 0) {
foreach ($this->include as $includeUrl) {
$includeUrl = rtrim($includeUrl, '/');
if ($includeUrl[strlen($includeUrl) - 1] === '*') {
$includeUrl = rtrim($includeUrl, '*');
return $request->getUrl()->contains($includeUrl);
}
return ($includeUrl === rtrim($request->getUrl()->getRelativeUrl(false), '/'));
}
}
return false;
}
/**
* Check if the url matches the urls in the except property
* @param Request $request
* @return bool
*/
protected function skip(Request $request): bool
{
if (count($this->except) === 0) {
return false;
}
foreach ($this->except as $url) {
$url = rtrim($url, '/');
if ($url[strlen($url) - 1] === '*') {
$url = rtrim($url, '*');
$skip = $request->getUrl()->contains($url);
} else {
$skip = ($url === rtrim($request->getUrl()->getRelativeUrl(false), '/'));
}
if ($skip === true) {
$skip = !$this->isIncluded($request);
if ($skip === false) {
continue;
}
return true;
}
}
return false;
}
/**
* Handle request
*
* @param Request $request
* @throws TokenMismatchException
*/
public function handle(Request $request): void
{
if ($this->skip($request) === false && ($request->isPostBack() === true || $request->isPostBack() === true && $this->isIncluded($request) === true)) {
$token = $request->getInputHandler()->value(
static::POST_KEY,
$request->getHeader(static::HEADER_KEY),
);
if ($this->tokenProvider->validate((string)$token) === false) {
throw new TokenMismatchException('Invalid CSRF-token.');
}
}
// Refresh existing token
$this->tokenProvider->refresh();
}
public function getTokenProvider(): ITokenProvider
{
return $this->tokenProvider;
}
/**
* Set token provider
* @param ITokenProvider $provider
*/
public function setTokenProvider(ITokenProvider $provider): void
{
$this->tokenProvider = $provider;
}
}