Docker Compose mit drei Services (nginx auf Port 8000), Migrationsmechanik, Update- und Backup-Skript. Anwendung: Router mit CSRF-Prüfung, Anmeldung und Ersteinrichtung, zentraler Einstellungsdialog mit Reitern, ein- und ausschaltbares Debug-Log mit Maskierung von Zugangsdaten. Layout nach Vorbild des FinanzManagers: feste Navigation links, Werkzeugleiste oben, Tab-Leiste; Farbschema hell/dunkel/System. Tests: 51 PHP-Prüfungen und 7 Playwright-Tests, alle grün.
102 lines
3.5 KiB
PHP
102 lines
3.5 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use Finanzmanager\Auth;
|
||
use Finanzmanager\Models\Benutzer;
|
||
|
||
class AuthTest extends TestCase
|
||
{
|
||
protected string $testName = 'Test 01: Anmeldung und CSRF';
|
||
|
||
public function run(): void
|
||
{
|
||
$this->benutzerAnlegenUndPruefen();
|
||
$this->falschesPasswort();
|
||
$this->unbekannterBenutzer();
|
||
$this->inaktiverBenutzer();
|
||
$this->csrfToken();
|
||
$this->cookieAttribute();
|
||
}
|
||
|
||
private function benutzerAnlegenUndPruefen(): void
|
||
{
|
||
$name = 'test_' . bin2hex(random_bytes(4));
|
||
$id = Benutzer::anlegen($name, 'geheim1234567', 'Testnutzer');
|
||
|
||
$this->assertGreaterThan(0, $id, 'Benutzer bekommt eine ID');
|
||
|
||
$benutzer = Benutzer::pruefeAnmeldung($name, 'geheim1234567');
|
||
$this->assertNotEmpty($benutzer, 'Anmeldung mit richtigem Passwort gelingt');
|
||
$this->assertEquals('Testnutzer', $benutzer['anzeigename'] ?? '', 'Anzeigename wird übernommen');
|
||
|
||
// Der Hash darf nie das Klartextpasswort enthalten.
|
||
$this->assert(
|
||
!str_contains((string)($benutzer['passwort_hash'] ?? ''), 'geheim'),
|
||
'Passwort wird gehasht abgelegt'
|
||
);
|
||
}
|
||
|
||
private function falschesPasswort(): void
|
||
{
|
||
$name = 'test_' . bin2hex(random_bytes(4));
|
||
Benutzer::anlegen($name, 'richtigespasswort');
|
||
|
||
$this->assertNull(
|
||
Benutzer::pruefeAnmeldung($name, 'falschespasswort'),
|
||
'Falsches Passwort wird abgewiesen'
|
||
);
|
||
}
|
||
|
||
private function unbekannterBenutzer(): void
|
||
{
|
||
$this->assertNull(
|
||
Benutzer::pruefeAnmeldung('gibtesnicht_' . bin2hex(random_bytes(4)), 'egal'),
|
||
'Unbekannter Benutzername wird abgewiesen'
|
||
);
|
||
}
|
||
|
||
private function inaktiverBenutzer(): void
|
||
{
|
||
$name = 'test_' . bin2hex(random_bytes(4));
|
||
$id = Benutzer::anlegen($name, 'geheim1234567');
|
||
Finanzmanager\Database::execute('UPDATE benutzer SET aktiv = 0 WHERE id = ?', [$id]);
|
||
|
||
$this->assertNull(
|
||
Benutzer::pruefeAnmeldung($name, 'geheim1234567'),
|
||
'Deaktivierter Benutzer kann sich nicht anmelden'
|
||
);
|
||
}
|
||
|
||
private function csrfToken(): void
|
||
{
|
||
$_SESSION['csrf_token'] = 'abc123';
|
||
|
||
$this->assert(Auth::istGueltigerCsrfToken('abc123'), 'Richtiges CSRF-Token wird akzeptiert');
|
||
$this->assert(!Auth::istGueltigerCsrfToken('falsch'), 'Falsches CSRF-Token wird abgelehnt');
|
||
$this->assert(!Auth::istGueltigerCsrfToken(''), 'Leeres CSRF-Token wird abgelehnt');
|
||
|
||
$_SESSION['csrf_token'] = '';
|
||
$this->assert(
|
||
!Auth::istGueltigerCsrfToken(''),
|
||
'Ohne gespeichertes Token schlägt jede Prüfung fehl'
|
||
);
|
||
}
|
||
|
||
private function cookieAttribute(): void
|
||
{
|
||
$params = Auth::cookieParams();
|
||
|
||
$this->assert($params['httponly'] === true, 'Session-Cookie ist HttpOnly');
|
||
$this->assertEquals('Lax', $params['samesite'], 'Session-Cookie nutzt SameSite=Lax');
|
||
|
||
// Ohne HTTPS kein Secure-Flag – sonst wäre der lokale Betrieb auf
|
||
// http://localhost:8000 nicht möglich.
|
||
unset($_SERVER['HTTPS'], $_SERVER['HTTP_X_FORWARDED_PROTO']);
|
||
$this->assert(Auth::cookieParams()['secure'] === false, 'Ohne HTTPS kein Secure-Flag');
|
||
|
||
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
|
||
$this->assert(Auth::cookieParams()['secure'] === true, 'Hinter HTTPS-Proxy wird Secure gesetzt');
|
||
unset($_SERVER['HTTP_X_FORWARDED_PROTO']);
|
||
}
|
||
}
|