137 lines
8.3 KiB
PHP
137 lines
8.3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Prüft den Anmelde-Wächter: erste Anmeldung still gemerkt, bekanntes Gerät ohne Warnung, neues Gerät und neuer Ort mit Warnung
|
|
* (Link „Das war ich nicht“), App-Anmeldungen, Fehlversuche und Sperre im Protokoll (einmal je Sperrzeit), „Konto sichern“
|
|
* (Passwort ungültig, Tokens und Geräte weg, Sitzungen ungültig, Link nur einmal), Ablauf des Links, Ausschalter und Aufbewahrung.
|
|
*/
|
|
|
|
$db = sys_get_temp_dir() . '/deskhub-lg-' . 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();
|
|
$pdo->prepare("INSERT INTO staff (name, email, password_hash, role, is_active, created_at) VALUES (?,?,?,?,1,?)")
|
|
->execute(['Test Person', 'person@firma.example', password_hash('geheim-123', PASSWORD_DEFAULT), 'agent', db_now()]);
|
|
$sid = (int) $pdo->lastInsertId();
|
|
$pdo->prepare("INSERT INTO staff (name, email, password_hash, role, is_active, created_at) VALUES (?,?,?,?,1,?)")
|
|
->execute(['Chef', 'chef@firma.example', password_hash('x', PASSWORD_DEFAULT), 'admin', db_now()]);
|
|
$staff = fn() => $pdo->query('SELECT * FROM staff WHERE id = ' . $sid)->fetch();
|
|
$events = fn(string $ev = '') => (int) $pdo->query('SELECT COUNT(*) FROM login_events' . ($ev !== '' ? " WHERE event = '$ev'" : ''))->fetchColumn();
|
|
$chrome = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36';
|
|
$phone = 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36';
|
|
$as = function (string $ip, string $ua, ?string $cookie): void {
|
|
$_SERVER['REMOTE_ADDR'] = $ip;
|
|
$_SERVER['HTTP_USER_AGENT'] = $ua;
|
|
unset($_COOKIE[LOGIN_GUARD_COOKIE]);
|
|
if ($cookie !== null) $_COOKIE[LOGIN_GUARD_COOKIE] = $cookie;
|
|
};
|
|
$dev1 = str_repeat('a', 32);
|
|
$dev2 = str_repeat('b', 32);
|
|
|
|
// Beschreibung und Ort
|
|
$check(login_guard_describe_device($chrome) === 'Chrome auf Windows', 'Gerätename: Chrome auf Windows');
|
|
$check(login_guard_describe_device($phone) === 'Chrome auf Android', 'Gerätename: Chrome auf Android');
|
|
$check(login_guard_place('203.0.113.9')[0] === 'N:203.0' && login_guard_place('10.0.0.5')[0] === 'local', 'Ort: Netz der IP bzw. lokales Netz');
|
|
$check(str_starts_with(login_guard_place('2a02:810d:1::5')[0], 'N6:2a02:810d'), 'Ort: IPv6-Netz');
|
|
|
|
// 1) Erste Anmeldung: still gemerkt, keine Warnung
|
|
$as('203.0.113.9', $chrome, $dev1);
|
|
$GLOBALS['login_guard_last_link'] = null;
|
|
$check(login_guard_success($staff(), 'web') === 'first', 'Erste Anmeldung wird still gemerkt');
|
|
$check($GLOBALS['login_guard_last_link'] === null && $events('success') === 1, 'Erste Anmeldung: keine Warnung, ein Protokolleintrag');
|
|
|
|
// 2) Bekanntes Gerät, gleicher Ort
|
|
$check(login_guard_success($staff(), 'web') === 'known' && $GLOBALS['login_guard_last_link'] === null, 'Bekanntes Gerät: keine Warnung');
|
|
|
|
// 3) Neues Gerät: Warnung mit Link
|
|
$as('203.0.113.9', $phone, $dev2);
|
|
$check(login_guard_success($staff(), 'web') === 'new_device', 'Neues Gerät wird erkannt');
|
|
$link = (string) $GLOBALS['login_guard_last_link'];
|
|
$check(str_contains($link, '/staff/login_alert.php?t='), 'Warn-Link wurde erzeugt');
|
|
$token1 = substr($link, strpos($link, 't=') + 2);
|
|
$check($events('new_device') === 1, 'Protokoll: Neues Gerät');
|
|
$check(login_guard_success($staff(), 'web') === 'known', 'Das neue Gerät ist danach bekannt');
|
|
|
|
// 4) Bekanntes Gerät, neuer Ort
|
|
$GLOBALS['login_guard_last_link'] = null;
|
|
$as('198.51.100.7', $chrome, $dev1);
|
|
$check(login_guard_success($staff(), 'web') === 'new_place' && $GLOBALS['login_guard_last_link'] !== null, 'Neuer Ort vom bekannten Gerät wird gemeldet');
|
|
$token2 = substr((string) $GLOBALS['login_guard_last_link'], -48);
|
|
$GLOBALS['login_guard_last_link'] = null;
|
|
$as('192.0.2.77', $chrome, $dev1);
|
|
set_setting('login_guard_warn_place', '0');
|
|
$check(login_guard_success($staff(), 'web') === 'known' && $GLOBALS['login_guard_last_link'] === null, 'Ort-Warnung lässt sich ausschalten');
|
|
set_setting('login_guard_warn_place', '1');
|
|
|
|
// 5) App: Gerät = Gerätename
|
|
$as('203.0.113.9', 'okhttp/4.12', null);
|
|
$check(login_guard_success($staff(), 'app', 'Pixel 8') === 'new_device', 'App mit neuem Gerätenamen wird gemeldet');
|
|
$check(login_guard_success($staff(), 'app', 'Pixel 8') === 'known', 'Gleiches App-Gerät ist bekannt');
|
|
$check((int) $pdo->query("SELECT COUNT(*) FROM login_devices WHERE channel = 'app'")->fetchColumn() === 1, 'App-Gerät gemerkt');
|
|
|
|
// 6) Fehlversuche und Sperre
|
|
login_guard_failed(null, 'unbekannt@x.example', 'web', 'Unbekannte E-Mail-Adresse');
|
|
login_guard_failed($staff(), 'person@firma.example', 'web', 'Falsches Passwort');
|
|
$check($events('failed') === 2, 'Fehlversuche stehen im Protokoll');
|
|
login_guard_locked('person@firma.example', 'web');
|
|
login_guard_locked('person@firma.example', 'web');
|
|
$check($events('locked') === 1, 'Sperre wird je Sperrzeit nur einmal protokolliert');
|
|
$pdo->exec("UPDATE login_events SET created_at = '2000-01-01 00:00:00' WHERE event = 'locked'");
|
|
login_guard_locked('person@firma.example', 'web');
|
|
$check($events('locked') === 2, 'Nach der Sperrzeit wird eine neue Sperre wieder protokolliert');
|
|
set_setting('login_guard_max_attempts', '999');
|
|
$check(login_guard_max_attempts() === 20, 'Anzahl Fehlversuche ist auf 20 begrenzt');
|
|
set_setting('login_guard_max_attempts', '5');
|
|
|
|
// 7) Link prüfen, einmal verwenden
|
|
$check(login_guard_find_alert($pdo, 'zzz') === null && login_guard_find_alert($pdo, str_repeat('0', 48)) === null, 'Ungültiger Link wird abgelehnt');
|
|
$ev = login_guard_find_alert($pdo, $token1);
|
|
$check($ev !== null && (int) $ev['staff_id'] === $sid, 'Gültiger Link findet den Eintrag');
|
|
$pdo->prepare('UPDATE login_events SET created_at = ? WHERE id = ?')->execute(['2000-01-01 00:00:00', (int) $ev['id']]);
|
|
$check(login_guard_find_alert($pdo, $token1) === null, 'Link ist nach 7 Tagen abgelaufen');
|
|
|
|
// 8) Konto sichern
|
|
$pdo->prepare('INSERT INTO mobile_api_tokens (staff_id, token_hash, device_name, expires_at, created_at, last_used_at) VALUES (?,?,?,?,?,?)')
|
|
->execute([$sid, hash('sha256', 't'), 'Pixel 8', date('Y-m-d H:i:s', time() + 3600), db_now(), db_now()]);
|
|
$ev2 = login_guard_find_alert($pdo, $token2);
|
|
$check($ev2 !== null, 'Zweiter Link ist gültig');
|
|
$_SESSION = ['staff_login_at' => time() - 100];
|
|
$check(login_guard_session_revoked($sid) === false, 'Vor dem Sichern ist die Sitzung gültig');
|
|
$check(login_guard_revoke($pdo, $ev2) === true, 'Konto sichern gelingt');
|
|
$check(!password_verify('geheim-123', (string) $staff()['password_hash']), 'Passwort ist ungültig');
|
|
$check((int) $pdo->query('SELECT COUNT(*) FROM mobile_api_tokens WHERE staff_id = ' . $sid)->fetchColumn() === 0, 'App-Anmeldungen sind beendet');
|
|
$check((int) $pdo->query('SELECT COUNT(*) FROM login_devices WHERE staff_id = ' . $sid)->fetchColumn() === 0, 'Gemerkte Geräte sind vergessen');
|
|
$check($events('revoked') === 1, 'Protokoll: Konto gesichert');
|
|
$check(login_guard_session_revoked($sid) === true, 'Ältere Sitzungen sind ungültig');
|
|
$_SESSION = ['staff_login_at' => time() + 5];
|
|
$check(login_guard_session_revoked($sid) === false, 'Eine spätere Anmeldung bleibt gültig');
|
|
$check(login_guard_find_alert($pdo, $token2) === null, 'Der Link ist nur einmal verwendbar');
|
|
|
|
// 9) Ausschalter
|
|
set_setting('login_guard', '0');
|
|
$before = $events();
|
|
$as('203.0.113.9', $chrome, str_repeat('c', 32));
|
|
$check(login_guard_success($staff(), 'web') === 'off' && $events() === $before, 'Ausgeschaltet wird nichts gemerkt oder protokolliert');
|
|
login_guard_failed(null, 'x@y.example', 'web', 'x');
|
|
$check($events() === $before, 'Ausgeschaltet gibt es auch keine Fehlversuch-Einträge');
|
|
set_setting('login_guard', '1');
|
|
|
|
// 10) Aufbewahrung
|
|
$pdo->exec("UPDATE login_events SET created_at = '2000-01-01 00:00:00' WHERE id IN (SELECT id FROM login_events ORDER BY id LIMIT 3)");
|
|
$n0 = $events();
|
|
$old = (int) $pdo->query("SELECT COUNT(*) FROM login_events WHERE created_at < '2001-01-01'")->fetchColumn();
|
|
login_guard_purge($pdo);
|
|
$check($old >= 3 && $events() === $n0 - $old, 'Alte Einträge werden nach der Aufbewahrungsfrist gelöscht');
|
|
|
|
@unlink($db);
|
|
if ($failures) {
|
|
fwrite(STDERR, "Anmelde-Wächter-Prüfung fehlgeschlagen:\n- " . implode("\n- ", $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
echo "Anmelde-Wächter-Prüfungen bestanden.\n";
|