87 lines
5.4 KiB
PHP
87 lines
5.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Prüft die Fehler-Mail bei schweren PHP-Fehlern: Empfänger, Inhalt (ohne Parameter und Serverpfade), Drosselung, Ausschalter
|
|
* und einmal echt: Eine Seite mit einem schweren Fehler löst in einem eigenen Prozess die Meldung aus.
|
|
*/
|
|
|
|
$db = sys_get_temp_dir() . '/deskhub-ea-' . bin2hex(random_bytes(4)) . '.sqlite';
|
|
putenv('DESKHUB_TESTING=1');
|
|
putenv('DESKHUB_SQLITE_PATH=' . $db);
|
|
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
|
|
|
$failures = [];
|
|
$check = function (bool $ok, string $what) use (&$failures): void {
|
|
if (!$ok) $failures[] = $what;
|
|
};
|
|
$pdo = get_db();
|
|
$mails = [];
|
|
$send = function (string $to, string $subject, string $html) use (&$mails): bool {
|
|
$mails[] = compact('to', 'subject', 'html');
|
|
return true;
|
|
};
|
|
$root = str_replace('\\', '/', dirname(__DIR__));
|
|
$err = ['type' => E_ERROR, 'message' => "Uncaught Error: Call to undefined function foo() in {$root}/staff/x.php:12", 'file' => $root . '/staff/x.php', 'line' => 12];
|
|
|
|
// Empfänger
|
|
$pdo->prepare("INSERT INTO staff (name, email, password_hash, role, is_active, created_at) VALUES ('Chef','chef@firma.example','x','admin',1,?)")->execute([db_now()]);
|
|
set_setting('admin_email', '');
|
|
$check(in_array('chef@firma.example', error_alert_recipients($pdo), true), 'ohne eigene Adresse gehen die Mails an die Administratoren');
|
|
set_setting('admin_email', 'it@firma.example');
|
|
$check(error_alert_recipients($pdo) === ['it@firma.example'], 'die Admin-Adresse der Seite hat Vorrang');
|
|
set_setting('error_alert_email', 'a@firma.example, b@firma.example; kaputt');
|
|
$check(error_alert_recipients($pdo) === ['a@firma.example', 'b@firma.example'], 'eigene Empfänger (mehrere, ungültige werden übersprungen)');
|
|
|
|
// Inhalt
|
|
$_SERVER['REQUEST_URI'] = '/staff/tickets.php?token=GEHEIM&x=1';
|
|
[$subject, $html] = error_alert_compose($err);
|
|
$check(str_contains($html, 'staff/x.php') && str_contains($html, 'Zeile 12') && str_contains($html, '/staff/tickets.php'), 'Mail nennt Datei, Zeile und Seite');
|
|
$check(!str_contains($html, 'GEHEIM') && !str_contains($html, $root), 'Mail enthält weder Adress-Parameter noch den Serverpfad');
|
|
[, $evil] = error_alert_compose(['message' => '<script>alert(1)</script>', 'file' => 'x', 'line' => 1]);
|
|
$check(!str_contains($evil, '<script>'), 'Fehlertext wird maskiert');
|
|
|
|
// Senden und Drosselung
|
|
$n = error_alert_dispatch($pdo, $err, $send);
|
|
$check($n === 2 && count($mails) === 2, 'erste Meldung geht an beide Empfänger');
|
|
$check(error_alert_dispatch($pdo, $err, $send) === 0 && count($mails) === 2, 'dieselbe Meldung wird innerhalb einer Stunde nicht noch einmal gesendet');
|
|
$check(error_alert_dispatch($pdo, ['line' => 99, 'message' => 'Anderer Fehler'] + $err, $send) === 2, 'eine andere Meldung geht durch');
|
|
$same = error_alert_dispatch($pdo, ['message' => "Uncaught Error: Call to undefined function foo() in {$root}/staff/x.php:13", 'line' => 13] + $err, $send);
|
|
$check($same === 0, 'dieselbe Meldung mit anderer Zeilennummer im Text zählt als gleich');
|
|
for ($i = 0; $i < 20; $i++) error_alert_dispatch($pdo, ['message' => "Fehler Nummer-{$i}x", 'file' => "/f{$i}x.php"] + $err, $send);
|
|
$check(count($mails) <= 2 * 10 + 0 && count(array_unique(array_map(fn($m) => $m['subject'] . $m['html'], $mails))) <= 10, 'höchstens 10 verschiedene Fehler-Mails pro Stunde');
|
|
|
|
// Ausschalter
|
|
set_setting('error_alert_log', '[]');
|
|
set_setting('error_alert', '0');
|
|
$before = count($mails);
|
|
$check(error_alert_dispatch($pdo, $err, $send) === 0 && count($mails) === $before, 'ausgeschaltet wird nichts gesendet');
|
|
set_setting('error_alert', '1');
|
|
|
|
// Nicht schwere Fehler und Hinweise lösen nichts aus
|
|
$check(!in_array(E_WARNING, ERROR_ALERT_FATAL, true) && !in_array(E_NOTICE, ERROR_ALERT_FATAL, true) && !in_array(E_DEPRECATED, ERROR_ALERT_FATAL, true), 'Warnungen und Hinweise zählen nicht');
|
|
|
|
// Echt: eigener Prozess mit schwerem Fehler
|
|
set_setting('error_alert_log', '[]');
|
|
$script = sys_get_temp_dir() . '/ea-page-' . bin2hex(random_bytes(3)) . '.php';
|
|
file_put_contents($script, "<?php\nputenv('DESKHUB_TESTING=1');\nputenv('DESKHUB_SQLITE_PATH=' . " . var_export($db, true) . ");\nrequire " . var_export(dirname(__DIR__) . '/includes/bootstrap.php', true) . ";\nnoch_nie_definiert_im_system();\n");
|
|
exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($script) . ' 2>&1', $out, $code);
|
|
@unlink($script);
|
|
$log = json_decode((string) $pdo->query("SELECT setting_value FROM settings WHERE setting_key = 'error_alert_log'")->fetchColumn(), true);
|
|
$check(is_array($log) && count($log) === 1, 'echter schwerer Fehler in einer Seite löst die Meldung aus (Prozess-Ende ' . $code . ')');
|
|
// Seite ohne Fehler: kein Eintrag
|
|
set_setting('error_alert_log', '[]');
|
|
$script = sys_get_temp_dir() . '/ea-ok-' . bin2hex(random_bytes(3)) . '.php';
|
|
file_put_contents($script, "<?php\nputenv('DESKHUB_TESTING=1');\nputenv('DESKHUB_SQLITE_PATH=' . " . var_export($db, true) . ");\nrequire " . var_export(dirname(__DIR__) . '/includes/bootstrap.php', true) . ";\ntrigger_error('nur eine Warnung', E_USER_WARNING);\n");
|
|
exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($script) . ' 2>&1', $out2, $code2);
|
|
@unlink($script);
|
|
$log = json_decode((string) $pdo->query("SELECT setting_value FROM settings WHERE setting_key = 'error_alert_log'")->fetchColumn(), true);
|
|
$check(($log === [] || $log === null), 'eine Warnung löst keine Meldung aus');
|
|
|
|
@unlink($db);
|
|
if ($failures) {
|
|
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
echo 'Error alert checks passed.' . "\n";
|