- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
214 lines
6.5 KiB
PHP
214 lines
6.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\View\Processors;
|
|
|
|
use App\Framework\Template\Processing\StringProcessor;
|
|
use App\Framework\View\RenderContext;
|
|
|
|
/**
|
|
* CSRF-Token-Processor für Cache-sichere CSRF-Token-Behandlung
|
|
*
|
|
* Dieser Processor löst das Problem mit gecachten CSRF-Tokens:
|
|
* 1. Beim Caching: Ersetzt CSRF-Tokens mit Platzhaltern
|
|
* 2. Beim Rendern: Ersetzt Platzhalter mit frischen Tokens
|
|
*/
|
|
final class CsrfTokenProcessor implements StringProcessor
|
|
{
|
|
private const string CSRF_PLACEHOLDER = '__CSRF_TOKEN_PLACEHOLDER__';
|
|
private const string META_PLACEHOLDER = '__CSRF_META_PLACEHOLDER__';
|
|
private const string FIELD_PLACEHOLDER = '__CSRF_FIELD_PLACEHOLDER__';
|
|
|
|
public function __construct(
|
|
private readonly bool $cacheMode = false,
|
|
private readonly bool $debugMode = false
|
|
) {
|
|
}
|
|
|
|
public function process(string $html, RenderContext $context): string
|
|
{
|
|
return $html;
|
|
|
|
if (! $this->shouldProcessCsrf($context)) {
|
|
return $html;
|
|
}
|
|
|
|
if ($this->cacheMode) {
|
|
// Cache-Modus: Ersetze echte Tokens mit Platzhaltern
|
|
return $this->replaceTokensWithPlaceholders($html, $context);
|
|
} else {
|
|
// Render-Modus: Ersetze Platzhalter mit echten Tokens
|
|
return $this->replacePlaceholdersWithTokens($html, $context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prüft ob das Template CSRF-Token-Processing benötigt
|
|
*/
|
|
private function shouldProcessCsrf(RenderContext $context): bool
|
|
{
|
|
// Check 1: Data enthält CSRF-Token
|
|
if (isset($context->data['csrf_token']) || isset($context->data['_token'])) {
|
|
return true;
|
|
}
|
|
|
|
// Check 2: Template-Name deutet auf Formulare hin
|
|
$formKeywords = ['form', 'contact', 'login', 'register', 'checkout', 'admin'];
|
|
foreach ($formKeywords as $keyword) {
|
|
if (str_contains($context->template, $keyword)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Cache-Modus: Ersetzt echte CSRF-Tokens mit Platzhaltern
|
|
*/
|
|
private function replaceTokensWithPlaceholders(string $html, RenderContext $context): string
|
|
{
|
|
$this->debugLog("Converting CSRF tokens to placeholders for caching");
|
|
|
|
// 1. Ersetze Template-Syntax CSRF-Aufrufe
|
|
$html = $this->replaceTemplateCsrfCalls($html);
|
|
|
|
// 2. Ersetze echte Token-Werte falls vorhanden
|
|
if (isset($context->data['csrf_token'])) {
|
|
$html = str_replace($context->data['csrf_token'], self::CSRF_PLACEHOLDER, $html);
|
|
}
|
|
|
|
if (isset($context->data['_token'])) {
|
|
$html = str_replace($context->data['_token'], self::CSRF_PLACEHOLDER, $html);
|
|
}
|
|
|
|
// 3. Ersetze HTML-Pattern
|
|
$html = $this->replaceHtmlCsrfPatterns($html);
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Render-Modus: Ersetzt Platzhalter mit echten CSRF-Tokens
|
|
*/
|
|
private function replacePlaceholdersWithTokens(string $html, RenderContext $context): string
|
|
{
|
|
$this->debugLog("Converting placeholders to fresh CSRF tokens");
|
|
|
|
$csrfToken = $this->getCsrfToken($context);
|
|
|
|
// Ersetze alle Platzhalter-Types
|
|
$replacements = [
|
|
self::CSRF_PLACEHOLDER => $csrfToken,
|
|
self::META_PLACEHOLDER => $csrfToken,
|
|
self::FIELD_PLACEHOLDER => $this->generateCsrfField($csrfToken),
|
|
];
|
|
|
|
return str_replace(array_keys($replacements), array_values($replacements), $html);
|
|
}
|
|
|
|
/**
|
|
* Ersetzt Template-Syntax CSRF-Aufrufe mit Platzhaltern
|
|
*/
|
|
private function replaceTemplateCsrfCalls(string $html): string
|
|
{
|
|
$patterns = [
|
|
// {{ csrf_token() }} oder {{ csrf_token }}
|
|
'/\{\{\s*csrf_token\(\s*\)\s*\}\}/' => self::CSRF_PLACEHOLDER,
|
|
'/\{\{\s*csrf_token\s*\}\}/' => self::CSRF_PLACEHOLDER,
|
|
|
|
// {!! csrf_token() !!}
|
|
'/\{!!\s*csrf_token\(\s*\)\s*!!\}/' => self::CSRF_PLACEHOLDER,
|
|
|
|
// {{ _token }}
|
|
'/\{\{\s*_token\s*\}\}/' => self::CSRF_PLACEHOLDER,
|
|
|
|
// @csrf Blade-Direktive
|
|
'/@csrf/' => self::FIELD_PLACEHOLDER,
|
|
|
|
// csrf_field() Helper
|
|
'/\{\{\s*csrf_field\(\s*\)\s*\}\}/' => self::FIELD_PLACEHOLDER,
|
|
'{!! csrf_field() !!}' => self::FIELD_PLACEHOLDER,
|
|
];
|
|
|
|
return preg_replace(array_keys($patterns), array_values($patterns), $html);
|
|
}
|
|
|
|
/**
|
|
* Ersetzt HTML-CSRF-Patterns mit Platzhaltern
|
|
*/
|
|
private function replaceHtmlCsrfPatterns(string $html): string
|
|
{
|
|
// Hidden Input Fields mit CSRF-Token
|
|
$html = preg_replace(
|
|
'/<input[^>]*name=["\']_token["\'][^>]*value=["\']([^"\']+)["\'][^>]*>/',
|
|
'<input type="hidden" name="_token" value="' . self::CSRF_PLACEHOLDER . '">',
|
|
$html
|
|
);
|
|
|
|
// Meta Tags mit CSRF-Token
|
|
$html = preg_replace(
|
|
'/<meta[^>]*name=["\']csrf-token["\'][^>]*content=["\']([^"\']+)["\'][^>]*>/',
|
|
'<meta name="csrf-token" content="' . self::META_PLACEHOLDER . '">',
|
|
$html
|
|
);
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Holt den aktuellen CSRF-Token
|
|
*/
|
|
private function getCsrfToken(RenderContext $context): string
|
|
{
|
|
// Priorität: Context-Daten > Generierter Token
|
|
return $context->data['csrf_token'] ??
|
|
$context->data['_token'] ??
|
|
$this->generateFreshCsrfToken();
|
|
}
|
|
|
|
/**
|
|
* Generiert einen neuen CSRF-Token
|
|
*/
|
|
private function generateFreshCsrfToken(): string
|
|
{
|
|
// Integration mit deinem CSRF-System
|
|
// Das könnte auch ein Service-Call sein
|
|
return bin2hex(random_bytes(32));
|
|
}
|
|
|
|
/**
|
|
* Generiert ein komplettes CSRF-Hidden-Field
|
|
*/
|
|
private function generateCsrfField(string $token): string
|
|
{
|
|
return '<input type="hidden" name="_token" value="' . $token . '">';
|
|
}
|
|
|
|
/**
|
|
* Debug-Logging
|
|
*/
|
|
private function debugLog(string $message): void
|
|
{
|
|
// Debug logging removed for production
|
|
// Use proper logger in production environment
|
|
}
|
|
|
|
/**
|
|
* Factory-Methode für Cache-Modus
|
|
*/
|
|
public static function forCaching(bool $debugMode = false): self
|
|
{
|
|
return new self(cacheMode: true, debugMode: $debugMode);
|
|
}
|
|
|
|
/**
|
|
* Factory-Methode für Render-Modus
|
|
*/
|
|
public static function forRendering(bool $debugMode = false): self
|
|
{
|
|
return new self(cacheMode: false, debugMode: $debugMode);
|
|
}
|
|
}
|