AltchaService: Angriffsprotokoll (Datei-Log + Zugriffsschluessel) hinzugefuegt

This commit is contained in:
2026-09-22 10:47:30 +02:00
parent 49308d311b
commit 0c60f3d15e
@@ -102,6 +102,120 @@ class AltchaService
return (int) $this->getConfigValue('fp_altcha_honeypot_threshold', '50'); return (int) $this->getConfigValue('fp_altcha_honeypot_threshold', '50');
} }
/**
* Das Angriffsprotokoll ist unabhaengig vom (nur fuer Tests gedachten) Debug-Logging und
* standardmaessig aktiv: es haelt fortlaufend die zuletzt abgelehnten Anfragen fest.
*/
public function isAttackLogEnabled(): bool
{
return $this->getConfigValue('fp_altcha_attack_log', 'on') === 'on';
}
/**
* Haengt einen abgelehnten Versuch an die rotierende Protokolldatei an (siehe log.php fuer
* die Ansicht). Bewusst komplett fehlertolerant: schlaegt das Schreiben fehl (z. B. keine
* Schreibrechte), wird das still ignoriert -- die eigentliche Formularpruefung darf davon
* nie beeintraechtigt werden.
*
* @param array<string, mixed> $entry
*/
public function appendAttackLogEntry(array $entry): void
{
try {
$dir = $this->pluginDir . '/data';
$file = $dir . '/fp_altcha_attacklog.jsonl';
if (!\is_dir($dir)) {
@\mkdir($dir, 0755, true);
}
$line = \json_encode($entry, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
if (!\is_string($line)) {
return;
}
@\file_put_contents($file, $line . "\n", \FILE_APPEND | \LOCK_EX);
$this->trimAttackLog($file);
} catch (\Throwable $e) {
// Protokollieren ist ein Zusatzfeature -- ein Fehler hier darf niemals die eigentliche
// Formularpruefung stoeren.
}
}
/**
* Kuerzt die Protokolldatei auf die konfigurierte Hoechstanzahl an Eintraegen. Laeuft nur
* mit geringer Wahrscheinlichkeit bei jedem Aufruf (statt immer), um bei vielen gleichzeitigen
* Anfragen nicht staendig die komplette Datei einlesen zu muessen.
*/
private function trimAttackLog(string $file): void
{
if (\random_int(1, 20) !== 1) {
return;
}
if (!\is_file($file)) {
return;
}
$lines = @\file($file, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES);
if ($lines === false) {
return;
}
$max = $this->getAttackLogMaxEntries();
if (\count($lines) <= $max) {
return;
}
$trimmed = \array_slice($lines, -$max);
@\file_put_contents($file, \implode("\n", $trimmed) . "\n", \LOCK_EX);
}
private function getAttackLogMaxEntries(): int
{
$value = (int) $this->getConfigValue('fp_altcha_attack_log_max', '300');
if ($value < 20 || $value > 5000) {
return 300;
}
return $value;
}
/**
* Liefert den Zugriffsschluessel fuer die eigenstaendige Protokoll-Ansicht (log.php). Wird
* beim allerersten Bedarf automatisch zufaellig erzeugt und in einer eigenen Datei im
* Plugin-Verzeichnis gespeichert -- exakt nach demselben Muster wie der HMAC-Geheimschluessel
* (siehe getOrCreateAutoSecret()).
*/
public function getLogViewToken(): string
{
$dir = $this->pluginDir . '/data';
$file = $dir . '/fp_altcha_log_token.php';
if (\is_file($file)) {
$value = include $file;
if (\is_string($value) && $value !== '') {
return $value;
}
}
$token = \bin2hex(\random_bytes(24));
if (!\is_dir($dir)) {
@\mkdir($dir, 0755, true);
}
@\file_put_contents(
$file,
"<?php\n\n// Automatisch erzeugter Zugriffsschluessel fuer die Protokoll-Ansicht (log.php) --"
. " nicht manuell bearbeiten.\n"
. 'return ' . \var_export($token, true) . ";\n"
);
return $token;
}
/** /**
* Erzeugt eine neue Prüfung und liefert sie als Array, das 1:1 als JSON in die Seite * Erzeugt eine neue Prüfung und liefert sie als Array, das 1:1 als JSON in die Seite
* eingebettet werden kann (siehe TemplateHandler). * eingebettet werden kann (siehe TemplateHandler).