Sicherheit: - Bulk-Erstellung serverseitig auf eingeloggte Nutzer beschränkt; expire_minutes wird validiert (anonym: nur Default/Template-Werte, eingeloggt: max. 1 Jahr) - IP-basiertes Rate-Limit über neue Tabelle request_throttle (Voucher-Erstellung + Passwort-Reset-Anfragen), Session-Fallback für Alt-Installationen; Migration 0002 - session_regenerate_id() nach Login, Secure-Cookie-Flag bei HTTPS - Admin-/Aktiv-Status wird pro Request live aus der DB geprüft (Rechteentzug & Deaktivierung wirken sofort); Schutz vor Selbst-Degradierung im Benutzer-Edit - Alle state-ändernden Admin-Aktionen von GET auf POST umgestellt (kein CSRF-Token mehr in URLs) - login_simple.php (Legacy, Debug-Leak) entfernt; cron_test.php nur noch für Admins; .htaccess auf Apache-2.4-Syntax inkl. cron_test.php - M365 Client Secret wird nicht mehr ins Formular zurückgegeben - Updater: Zip-Slip-/Pfad-Traversal-Schutz, Backup vor dem Anwenden mit automatischem Rollback bei Fehlern, AuditLogger-Bug behoben - cron_sync: Token-Vergleich mit hash_equals; login_attempts-Pruning - CSV-Export gegen Excel-Formula-Injection abgesichert Bugfixes: - M365-Login: Fallback auf userPrincipalName, wenn Graph kein 'mail' liefert (Nutzer ohne Exchange-Postfach konnten sich nie anmelden) - PRG-Pattern überall: F5 erzeugt keine Duplikat-Voucher und wiederholt keine Admin-Aktionen (Session-Flash-Messages) - QR-Code nicht mehr invertiert (schwarz auf weiß, scanbar) - Bulk-Erstellung nutzt den UniFi 'n'-Parameter: 1 API-Call statt n× Login + Voucherlisten-Abruf; exaktes Code-Matching per create_time statt "global neuester Voucher" - Mailer: doppelte Zeilenumbrüche behoben, AUTH nur mit Credentials, SMTP-Dot-Stuffing, CLI-sicherer EHLO-Host - forgot_password: System-URL-Auto-Detect (Reset-Link war sonst relativ/kaputt) + Rate-Limit - Audit-Log-Labels an tatsächliche Action-Keys angepasst; Voucher-Erstellung (einzeln & bulk) wird jetzt auditiert - Site-Edit testet die Verbindung auch ohne Passwortänderung UX/UI: - Alert-/Badge-Styles zentral in global.css mit Dark-Mode-Variablen (vorher 7× dupliziert mit hart codierten Hellfarben) - Sticky-Formulare + Tab-Erhalt nach Validierungsfehlern (Bulk), Settings kehren nach dem Speichern zum aktiven Tab zurück - Gültigkeit menschenlesbar (z.B. "8 Stunden" statt "480 Minuten") - Voucher-Name-Default "Gast/Guest" im öffentlichen Modus - Favicon auch auf Login-/öffentlichen Seiten - Verbindungstest-Button pro Site-Karte (Health-Check) - i18n-Pass: Confirm-Dialoge, Toasts, Fehl-/Erfolgsmeldungen in de/en - Sprachumschalter ohne fetch+reload (kein Re-Submit-Dialog) - A11y: Esc schließt Modals, aria-live für Toasts, aria-labels auf Icon-Buttons; APP_KEY-Warnbanner im Dashboard - Dashboard-Sync: set_time_limit passend zur Site-Anzahl; Voucher-Sync mit Map statt SELECT pro Voucher Tooling: - GitHub-Actions-Workflow: PHP-Lint aller Dateien + de/en-Key-Parität https://claude.ai/code/session_01KKVpVPJjrTKGoRgpJcySD4
4.3 KiB
Updater (OpenVoucherTool)
Auto-Update-System nach dem OpenNIT-Modell: zieht Quellcode + DB-Migrationen aus einem zentralen Repository über einen HTTP-Update-Proxy nach.
[diese App] ←HTTPS→ [Update-Proxy] ←pull→ [Git-Repo]
- Versions-Identität: 40-stelliger Git-Commit-SHA in
updater/storage/.version - Channels:
stable→https://update.loheide.eu/openvouchertool,development→https://update.loheide.eu/openvouchertool-development - User-Agent:
OpenVoucherTool-Updater/1.0 - DB-Treiber: MySQL/MariaDB (Migrations-Tracking-Tabelle
_updater_migrations)
Aufruf
Admin-Oberfläche: /admin/update.php (nur für angemeldete Admins).
| Endpoint | Methode | Zweck |
|---|---|---|
admin/update.php |
GET | Admin-UI |
admin/update.php?action=check |
GET | Update-Prüfung (JSON) |
admin/update.php?action=progress |
GET | Fortschritt (JSON) |
admin/update.php?action=migrations |
GET | Migrations-Status (JSON) |
admin/update.php action=install |
POST | Update installieren (+csrf_token) |
admin/update.php action=set_channel |
POST | Channel wählen (+csrf_token) |
Optional: In der Admin-Navigation (
admin/index.php) einen Link zuupdate.phpergänzen, damit die Seite auffindbar ist. Das ist bewusst nicht automatisch geschehen (Isolations-Prinzip – siehe unten).
Isolation
Der Updater liegt vollständig in updater/ (eigener Namespace Updater\) plus
zwei dünne, klar markierte Anknüpfungen:
- Front-Controller-Hook in
index.php(Maintenance-Check, markiert mit// Updater maintenance hook). - Entry-Shim
admin/update.php(lädt nur Basis + Bootstrap, delegiert an den Controller).
Eigene Settings (updater/storage/updater-settings.json), eigener Autoloader
(updater/bootstrap.php), eigener AuditLogger (schreibt in die vorhandene
audit_log-Tabelle), eigenes Migrations-System (updater/migrations/ +
Tabelle _updater_migrations). Es werden keine Projekt-Klassen erweitert –
\Database und \Auth werden nur per Injection genutzt.
Geschützte Pfade (werden bei Updates nie überschrieben)
config.php, .htaccess, .env*, .git/, .gitignore, public/uploads/,
vendor/, composer.lock, updater/storage/.
Rückbau (restlos entfernen)
- In
index.phpden Block „Updater maintenance hook" (die Zeilen 2–8, beginnend mit$maintenanceFile = …bis zum schließenden}) entfernen. rm -r updater/rm admin/update.php- (optional) In der Datenbank:
DROP TABLE _updater_migrations; - (optional, falls vorhanden) Laufzeitdateien sind bereits in
updater/und damit mit Schritt 2 weg. Nichts liegt außerhalb.
Danach ist keine Spur des Updaters mehr im Bestandscode – der Beweis für die Isolation.
Smoke-Test
# 1) Syntax aller Updater-Dateien
php -l updater/UpdateManager.php
php -l updater/MigrationRunner.php
php -l updater/UpdateController.php
php -l updater/UpdaterFactory.php
php -l updater/AuditLogger.php
# 2) Admin-Seite aufrufen (eingeloggt als Admin):
# https://<host>/admin/update.php
# -> "Auf Updates prüfen" klicken. Erwartung: Proxy-Antwort oder klare
# Fehlermeldung (wenn Proxy/Channel nicht erreichbar).
# 3) Maintenance-Mode manuell testen:
touch updater/storage/.maintenance # index.php zeigt jetzt 503-Wartungsseite
rm updater/storage/.maintenance # wieder normal
Hinweis: Ein vollständiger Installations-Durchlauf (
action=install) setzt einen erreichbaren Update-Proxy unter den oben genannten URLs voraus.
Sicherheits-Hinweise & Limitierungen
- Keine Paket-Signatur: Die Integrität der Updates hängt derzeit allein an
TLS zur Update-Proxy-URL. Der Updater validiert Zip-Einträge und Dateipfade
gegen Pfad-Traversal und legt vor dem Anwenden ein Backup an
(
updater/storage/.backup-last), das bei Fehlern automatisch zurückgespielt wird. Eine kryptografische Signaturprüfung der Pakete (z.B. signierte SHA-256-Manifeste) erfordert serverseitige Unterstützung des Update-Proxys und steht noch aus. - Rollback: Schlägt das Anwenden des Updates oder eine Migration fehl, werden die überschriebenen Dateien aus dem Backup wiederhergestellt. Datenbank-Migrationen werden dabei nicht automatisch rückgängig gemacht (jede Migration läuft aber in einer eigenen Transaktion).