87 lines
6.6 KiB
PHP
87 lines
6.6 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Prüft „Von allen Geräten abmelden“ (includes/signout.php): App-Anmeldungen, Push, Ticket-Sperren, Browser-Sitzungen (über den Widerrufs-Zeitpunkt),
|
|
* dass andere Mitarbeiter unberührt bleiben, und das Protokoll.
|
|
*/
|
|
|
|
putenv('DESKHUB_TESTING=1');
|
|
putenv('DESKHUB_SQLITE_PATH=:memory:');
|
|
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
|
|
|
$failures = [];
|
|
$total = 0;
|
|
$check = function (bool $ok, string $what) use (&$failures, &$total): void {
|
|
$total++;
|
|
if (!$ok) $failures[] = $what;
|
|
};
|
|
$pdo = get_db();
|
|
$now = date('Y-m-d H:i:s');
|
|
$dept = (int) $pdo->query('SELECT id FROM departments ORDER BY id LIMIT 1')->fetchColumn();
|
|
$prio = (int) $pdo->query('SELECT id FROM priorities ORDER BY id LIMIT 1')->fetchColumn();
|
|
$pdo->exec('DELETE FROM staff');
|
|
$ins = $pdo->prepare('INSERT INTO staff (name, email, password_hash, role, department_id, is_active, created_at) VALUES (?,?,?,?,?,1,?)');
|
|
$ins->execute(['Chef', 'chef@test.de', 'x', 'admin', $dept, $now]);
|
|
$chef = (int) $pdo->lastInsertId();
|
|
$ins->execute(['Eva', 'eva@test.de', 'x', 'agent', $dept, $now]);
|
|
$eva = (int) $pdo->lastInsertId();
|
|
$ins->execute(['Max', 'max@test.de', 'x', 'agent', $dept, $now]);
|
|
$max = (int) $pdo->lastInsertId();
|
|
|
|
$tok = function (int $sid, string $name, string $hash, ?string $revoked = null) use ($pdo, $now): void {
|
|
$pdo->prepare('INSERT INTO mobile_api_tokens (staff_id, token_hash, device_name, expires_at, last_used_at, revoked_at, created_at) VALUES (?,?,?,?,?,?,?)')
|
|
->execute([$sid, $hash, $name, date('Y-m-d H:i:s', time() + 86400), $now, $revoked, $now]);
|
|
};
|
|
$tok($eva, 'Android Pixel', str_repeat('a', 64)); $tok($eva, 'Windows-Programm', str_repeat('b', 64)); $tok($eva, 'Chrome', str_repeat('c', 64));
|
|
$tok($max, 'Max Handy', str_repeat('d', 64));
|
|
$pdo->prepare('INSERT INTO push_subscriptions (staff_id, endpoint, p256dh, auth) VALUES (?,?,?,?)')->execute([$eva, 'https://push.example/1', 'k', 'a']);
|
|
$pdo->prepare('INSERT INTO push_subscriptions (staff_id, endpoint, p256dh, auth) VALUES (?,?,?,?)')->execute([$max, 'https://push.example/2', 'k', 'a']);
|
|
$pdo->prepare('INSERT INTO push_devices (staff_id, token, token_hash, platform, device_name, created_at) VALUES (?,?,?,?,?,?)')->execute([$eva, 'fcm-eva', str_repeat('e', 64), 'android', 'Pixel', $now]);
|
|
$pdo->prepare('INSERT INTO push_devices (staff_id, token, token_hash, platform, device_name, created_at) VALUES (?,?,?,?,?,?)')->execute([$max, 'fcm-max', str_repeat('f', 64), 'android', 'Max', $now]);
|
|
$pdo->prepare('INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, locked_by, locked_at, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)')
|
|
->execute(['S-1', 'Gesperrt', 'K', 'k@example.org', $dept, $prio, 'open', 'web', $eva, $now, $now, $now]);
|
|
$pdo->prepare('INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, locked_by, locked_at, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)')
|
|
->execute(['S-2', 'Von Max gesperrt', 'K', 'k@example.org', $dept, $prio, 'open', 'web', $max, $now, $now, $now]);
|
|
|
|
$check(count(staff_app_logins($pdo, $eva)) === 3 && count(staff_app_logins($pdo, $max)) === 1, 'Vorher: Evas drei App-Anmeldungen sind sichtbar');
|
|
$tok($eva, 'Abgelaufen', str_repeat('9', 64), $now);
|
|
$check(count(staff_app_logins($pdo, $eva)) === 3, 'bereits widerrufene Anmeldungen werden nicht angezeigt');
|
|
|
|
// Eine Browser-Sitzung, die vor dem Widerruf angemeldet wurde, ist danach ungültig
|
|
$_SESSION['staff_login_at'] = time() - 600;
|
|
$check(!login_guard_session_revoked($eva), 'Vorher: Sitzung gültig');
|
|
$r = staff_sign_out_everywhere($pdo, $eva, $chef);
|
|
$check($r['apps'] === 4 && $r['browsers'] === 1 && $r['devices'] === 1 && $r['locks'] === 1, 'Zahlen: ' . json_encode($r));
|
|
$check(login_guard_session_revoked($eva), 'Danach: ältere Browser-Sitzung ist ungültig');
|
|
$_SESSION['staff_login_at'] = time() + 5;
|
|
$check(!login_guard_session_revoked($eva), 'eine neue Anmeldung danach ist gültig');
|
|
$check((int) $pdo->query("SELECT COUNT(*) FROM mobile_api_tokens WHERE staff_id = {$eva}")->fetchColumn() === 0, 'App-Anmeldungen von Eva gelöscht');
|
|
$check((int) $pdo->query("SELECT COUNT(*) FROM push_subscriptions WHERE staff_id = {$eva}")->fetchColumn() === 0 && (int) $pdo->query("SELECT COUNT(*) FROM push_devices WHERE staff_id = {$eva}")->fetchColumn() === 0, 'Push von Eva gelöscht (Browser und App)');
|
|
$check($pdo->query("SELECT locked_by FROM tickets WHERE ticket_number = 'S-1'")->fetchColumn() === null, 'Ticket-Sperre von Eva freigegeben');
|
|
// Andere bleiben unberührt
|
|
$check((int) $pdo->query("SELECT COUNT(*) FROM mobile_api_tokens WHERE staff_id = {$max}")->fetchColumn() === 1, 'Max behält seine App-Anmeldung');
|
|
$check((int) $pdo->query("SELECT COUNT(*) FROM push_subscriptions WHERE staff_id = {$max}")->fetchColumn() === 1 && (int) $pdo->query("SELECT COUNT(*) FROM push_devices WHERE staff_id = {$max}")->fetchColumn() === 1, 'Max behält sein Push');
|
|
$check((int) $pdo->query("SELECT locked_by FROM tickets WHERE ticket_number = 'S-2'")->fetchColumn() === $max, 'Max behält seine Ticket-Sperre');
|
|
$_SESSION['staff_login_at'] = time() - 600;
|
|
$check(!login_guard_session_revoked($max), 'Maxens Browser-Sitzung bleibt gültig');
|
|
// Protokoll
|
|
$ev = $pdo->query("SELECT * FROM login_events WHERE event = 'signout' AND staff_id = {$eva}")->fetch(PDO::FETCH_ASSOC);
|
|
$check($ev && str_contains((string) $ev['detail'], 'Administrator'), 'Protokolleintrag „Überall abgemeldet“ mit Hinweis auf den Administrator');
|
|
$check(isset(login_guard_event_labels()['signout']), 'Ereignisart ist beschriftet');
|
|
$check((int) $pdo->query("SELECT COUNT(*) FROM admin_audit_log WHERE action LIKE 'Mitarbeiter #{$eva} von allen Geräten abgemeldet%'")->fetchColumn() === 1, 'Audit-Log-Eintrag');
|
|
// Selbst abmelden
|
|
$r2 = staff_sign_out_everywhere($pdo, $max, $max);
|
|
$ev2 = $pdo->query("SELECT detail FROM login_events WHERE event = 'signout' AND staff_id = {$max}")->fetchColumn();
|
|
$check($r2['apps'] === 1 && str_contains((string) $ev2, 'eigene Veranlassung'), 'eigene Abmeldung wird als solche protokolliert');
|
|
// Unbekannter Mitarbeiter, zweimal hintereinander
|
|
$check(staff_sign_out_everywhere($pdo, 999999, $chef)['at'] === 0, 'unbekannter Mitarbeiter: nichts passiert, kein Fehler');
|
|
$r3 = staff_sign_out_everywhere($pdo, $eva, $chef);
|
|
$check($r3['apps'] === 0 && $r3['browsers'] === 0, 'zweites Mal: nichts mehr zu beenden, kein Fehler');
|
|
|
|
if ($failures) {
|
|
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
echo "Sign out everywhere checks passed ({$total} Prüfungen).\n";
|