misuzu/src/CsrfContext.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;
}
}
}