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
102 lines
4.3 KiB
Markdown
102 lines
4.3 KiB
Markdown
# 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 zu
|
||
> `update.php` ergä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:
|
||
|
||
1. **Front-Controller-Hook** in `index.php` (Maintenance-Check, markiert mit
|
||
`// Updater maintenance hook`).
|
||
2. **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)
|
||
|
||
1. In `index.php` den Block **„Updater maintenance hook"** (die Zeilen 2–8,
|
||
beginnend mit `$maintenanceFile = …` bis zum schließenden `}`) entfernen.
|
||
2. `rm -r updater/`
|
||
3. `rm admin/update.php`
|
||
4. *(optional)* In der Datenbank: `DROP TABLE _updater_migrations;`
|
||
5. *(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
|
||
|
||
```bash
|
||
# 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).
|