130 lines
4.2 KiB
PHP
130 lines
4.2 KiB
PHP
<?php
|
|
namespace Misuzu;
|
|
|
|
use Index\CsrfToken;
|
|
use Index\Config\Config;
|
|
use Index\Http\Content\FormContent;
|
|
use Index\Http\Routing\{HandlerContext,RouteHandler,RouteHandlerCommon};
|
|
use Index\Http\Routing\Processors\Preprocessor;
|
|
use Index\Templating\Extension\TplExtensionCommon;
|
|
use Twig\TwigFunction;
|
|
use Twig\Extension\LastModifiedExtensionInterface;
|
|
|
|
class CsrfContext implements RouteHandler, LastModifiedExtensionInterface {
|
|
use RouteHandlerCommon, TplExtensionCommon;
|
|
|
|
private ?CsrfToken $instance = null;
|
|
|
|
public function __construct(
|
|
private Config $config,
|
|
) {}
|
|
|
|
public function createInstance(string $identity): CsrfToken {
|
|
return new CsrfToken(
|
|
$this->config->getString('secret', 'soup'),
|
|
$identity,
|
|
);
|
|
}
|
|
|
|
public function isAvailable(): bool {
|
|
return $this->instance !== null;
|
|
}
|
|
|
|
public function setIdentity(string $identity): void {
|
|
$this->instance = $this->createInstance($identity);
|
|
}
|
|
|
|
public function createToken(): string {
|
|
return $this->instance?->createToken() ?? '';
|
|
}
|
|
|
|
public function verifyToken(string $token, int $tolerance = -1): bool {
|
|
return $this->instance?->verifyToken($token, $tolerance) ?? false;
|
|
}
|
|
|
|
public function verifyLegacy(int $tolerance = -1): bool {
|
|
if(!$this->isAvailable())
|
|
return false;
|
|
|
|
$token = isset($_POST['_csrf']) && is_string($_POST['_csrf']) ? $_POST['_csrf'] : '';
|
|
if(empty($token))
|
|
$token = isset($_GET['csrf']) && is_string($_GET['csrf']) ? $_GET['csrf'] : '';
|
|
|
|
return $this->verifyToken($token, $tolerance);
|
|
}
|
|
|
|
#[\Override]
|
|
public function getFunctions() {
|
|
return [
|
|
new TwigFunction('csrf_available', $this->isAvailable(...)),
|
|
new TwigFunction('csrf_token', $this->createToken(...)),
|
|
];
|
|
}
|
|
|
|
/** @return void|int|array{error: array{name: string, text: string}} */
|
|
#[Preprocessor('csrf:header')]
|
|
public function preVerifyHeader(
|
|
HandlerContext $context,
|
|
string $type = '',
|
|
string $argName = 'csrf',
|
|
) {
|
|
if($type === 'arg' && isset($context->args[$argName]) && $context->args[$argName] === true)
|
|
return;
|
|
|
|
$result = $this->isAvailable()
|
|
&& $context->request->hasHeader('X-CSRF-Token')
|
|
&& $this->verifyToken($context->request->getHeaderLine('X-CSRF-Token'));
|
|
|
|
if($result) {
|
|
$context->response->setHeader('X-CSRF-Token', $this->createToken());
|
|
if($type === 'arg')
|
|
$context->setArgument($argName, true);
|
|
} else {
|
|
if($type === 'arg')
|
|
$context->setArgument($argName, false);
|
|
elseif($type === 'json') {
|
|
$context->response->statusCode = 403;
|
|
return [
|
|
'error' => [
|
|
'name' => 'csrf',
|
|
'text' => 'Request could not be verified. Please try again.',
|
|
],
|
|
];
|
|
} else return 403;
|
|
}
|
|
}
|
|
|
|
/** @return void|int|array{error: array{name: string, text: string}} */
|
|
#[Preprocessor('csrf:form')]
|
|
public function preVerifyForm(
|
|
HandlerContext $context,
|
|
?FormContent $content,
|
|
string $formParam = '_csrf',
|
|
string $type = '',
|
|
string $argName = 'csrf',
|
|
) {
|
|
if($type === 'arg' && isset($context->args[$argName]) && $context->args[$argName] === true)
|
|
return;
|
|
|
|
$result = $this->isAvailable()
|
|
&& $content?->hasParam($formParam) === true
|
|
&& $this->verifyToken((string)$content->getParam($formParam));
|
|
|
|
if($result) {
|
|
if($type === 'arg')
|
|
$context->setArgument($argName, true);
|
|
} else {
|
|
if($type === 'arg') {
|
|
$context->setArgument($argName, false);
|
|
} elseif($type === 'json') {
|
|
$context->response->statusCode = 403;
|
|
return [
|
|
'error' => [
|
|
'name' => 'csrf',
|
|
'text' => 'Request could not be verified. Please try again.',
|
|
],
|
|
];
|
|
} else return 403;
|
|
}
|
|
}
|
|
}
|