20abadb0e72d310235e0ddd1df6127c1e183c38d
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
20abadb0e7 |
Dashboard erfordert Login, Magic-Links nutzen konfigurierten Endpunkt statt IP (v1.2.3)
## Dashboard nur noch intern '/' (das Kachel-Dashboard) verlangt jetzt @login_required -- ein anonymer Besuch landet zuerst auf der Login-Seite (ueber Flask-Logins bereits konfigurierten login_manager.login_view), statt innerhalb der Dashboard- Huelle einen 'bitte anmelden'-Platzhalter zu zeigen. Anders als bei TESM selbst (dessen Geraete-Dashboard bewusst auch ohne Login als Status-Board sichtbar bleibt) ist der Lizenzserver ein reines internes Verwaltungs- werkzeug. ## Magic-Links zeigen den konfigurierten Lizenzserver-Endpunkt, nicht die IP Self-Service-Link, Kundenportal-Anfrage/-Zugang und der in 'Lizenz per E-Mail senden' eingebettete Link wurden bisher per url_for(..., _external=True) gebaut -- das nutzt den Host-Header der GERADE laufenden Anfrage, zeigt also z.B. die interne IP, ueber die zufaellig der Admin gerade eingeloggt ist, statt der unter Einstellungen -> Lizenz -> Anbieter & Lizenzserver gepflegten oeffentlichen Domain. Neue Hilfs- funktion _customer_facing_url() baut diese Links jetzt stattdessen aus dem admin-gepflegten Lizenzserver-Endpunkt (_get_master_endpoint(), derselbe Wert, der auch in jede Lizenzdatei eingebettet wird) -- faellt auf die alte, request-basierte externe URL zurueck, wenn der Endpunkt noch nicht konfiguriert ist (frische Installation). Verifiziert gegen eine echte Kopie der Live-Datenbank: mit konfiguriertem Endpunkt zeigt der Self-Service-Link jetzt https://tesml.int.eertmoed.net/... statt der IP, unabhaengig davon, ueber welchen Host der Admin gerade zugreift; ohne konfigurierten Endpunkt bleibt der bisherige Fallback erhalten. ## Ausserdem live behoben (kein Code-Fix, reine Konfiguration) /etc/nginx/sites-available/tesm-license auf dem Master zeigte fuer /static/ faelschlich auf /srv/tesm/static/ (ein laengst nicht mehr existierendes Verzeichnis -- Ueberbleibsel aus der Zeit vor der Repo-Trennung) statt auf /srv/tesm-license/static/, wodurch CSS/JS/ Bilder ueberall 404eten (nackte, ungestylte Seiten). install.sh schreibt die nginx-Konfiguration bewusst nur bei einer FRISCHEN Installation (um manuelle Anpassungen wie TLS-Zertifikate bei Updates nicht zu ueberschreiben) -- das im Repo hinterlegte Template selbst war bereits korrekt, nur die schon laenger bestehende Datei auf dem Master war es nicht. Direkt auf dem Master korrigiert und nginx neu geladen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
2d920f581f |
Eigenstaendige Lizenzen-Seite, entschachtelte Ticket-Ansicht, zeitlich begrenzter Kundenportal-Zugang (v1.2.2)
Reaktion auf Feedback: 'Tickets und Lizenzen je Kunden war sehr sehr
unintuitiv dargestellt... Kunde -> Ticket -> Lizenz aber alles auf extra
Seiten, im Menü einzeln anwählbar ohne diese riesen Verschachtelung.'
## Lizenzen als eigene, gleichberechtigte Ebene (neu: /licenses)
Bisher war eine Lizenz nur ueber Kunde -> gefilterte Tickets -> Ticket-
Detail -> eingebettete Historie-Tabelle erreichbar. Jetzt: Kunden,
Tickets UND Lizenzen sind drei eigene, direkt in der Navigation
erreichbare Seiten (Reihenfolge im Menü spiegelt bewusst die fachliche
Hierarchie). Kunden- und Ticket-Seite verlinken mit Filter (?customer_id=/
?ticket_id=) dorthin, statt Lizenzdaten selbst einzubetten.
Ticket-Detailseite dadurch massiv entschlackt: statt einer vollen
Lizenz-Historie-Tabelle nur noch eine kompakte 'Aktuelle Lizenz'-Zeile
mit Link auf die (ggf. gefilterte) Lizenzen-Seite fuer die volle Historie.
## Kundenportal: kein dauerhafter Link mehr, sondern zeitlich begrenzter Zugang
Vorheriger Ansatz (v1.2.0): ein dauerhafter, unbefristeter Token pro
Kunde, von Admins verteilt. Auf Nachfrage ('wie bekommt der Kunde seinen
Magic-Link, wenn er ihn verloren hat?') kam eine oeffentliche
'Zugang anfordern'-Seite dazu (E-Mail eingeben -> Link zugeschickt) --
und direkt im Anschluss die berechtigte Sicherheitsfrage, ob so ein
oeffentlich auf blossen Zuruf ausloesbarer Link nicht zeitlich begrenzt
sein sollte. Ergebnis: der dauerhafte Link wurde komplett entfernt.
Neuer, einziger Weg ins Kundenportal:
1. /portal/request -- Kunde gibt seine E-Mail ein (oeffentlich, keine
Anmeldung noetig). Immer dieselbe generische Erfolgsmeldung,
unabhaengig davon ob die Adresse bekannt ist (keine Enumeration).
2. Bei bekannter Adresse: ein frischer, einmalig nutzbarer Zugangs-Token
(portal_access_tokens-Tabelle) wird per E-Mail verschickt, gueltig nur
PORTAL_ACCESS_LINK_MINUTES (30) ab Anforderung.
3. /portal/access/<token> prueft Gueltigkeit + Einmalnutzung, markiert
den Token als benutzt und setzt eine Session, die ihrerseits nach
PORTAL_SESSION_MINUTES (60) automatisch ablaeuft.
4. /portal/me zeigt das eigentliche Portal (alle Tickets/Lizenzen,
Kontaktdaten pflegen) -- ausschliesslich session-basiert erreichbar.
license_customers.portal_token bleibt in der DB bestehen (keine riskante
Schema-Ruecknahme), wird fuer die Zugriffsentscheidung aber nicht mehr
herangezogen -- alle Stellen, die vorher den dauerhaften Portal-Link
eingebettet haben (Ticket-Seite, Lizenz-per-E-Mail, Self-Service-Seite),
verweisen jetzt stattdessen auf /portal/request.
Verifiziert: bestehende Funktionstests erneut komplett durchlaufen (neue
Lizenzen-Seite, entschachtelte Ticket-Seite, entfernter dauerhafter
Zugang liefert 404), plus 11 neue Tests fuer den kompletten Magic-Link-
Fluss (Token-Erzeugung, Einmalnutzung, Ablehnen bei Wiederverwendung/
unbekanntem/abgelaufenem Token, Session bleibt nach Erstnutzung gueltig,
/portal/me ohne Session leitet zur Anfrage-Seite um) -- alle bestanden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
c2989a6e2f |
KRITISCH: 'database is locked' beim Aktivieren blockierte Online- UND Offline-Aktivierung (v1.2.1)
_process_activate() hielt beim Ersetzen einer bereits aktiven Lizenz fuer denselben Fingerprint (z.B. Trial durch Enterprise, oder schlicht eine Re-Aktivierung nach einem Systemwechsel) eine offene, noch nicht committete Schreibtransaktion auf 'conn', waehrend es innerhalb der Supersede-Schleife log_action_system() aufrief -- diese Funktion oeffnet INTERN eine EIGENE, zweite Verbindung zur selben Datenbank und versucht sofort zu committen. SQLite erlaubt aber nur einen Schreiber gleichzeitig (kein WAL-Modus): die zweite Verbindung wartet auf die erste, die wiederum auf die zweite wartet, bis das Busy-Timeout (Python-Default 5s) mit 'sqlite3.OperationalError: database is locked' aufgibt. Live reproduziert: sowohl /api/activate (Online) als auch /licenses/manual-code (Offline-Code) schlugen dadurch fehl, sobald ein Kunde eine Lizenz aktivierte, die eine fuer denselben Fingerprint bereits aktive ersetzt -- de facto war jede Aktivierung auf einem bereits bekannten System blockiert. Fix: die betroffenen log_action_system()-Aufrufe in der Supersede- Schleife werden nur noch vorgemerkt (reine Lesezugriffe auf die offene Verbindung sind unproblematisch) und erst NACH conn.commit()/ conn.close() nachgeholt. Zusaetzlich get_db_connection() auf timeout=15 (statt Python-Default 5s) angehoben als generelle Verteidigungslinie gegen kurze, legitime Ueberschneidungen -- ersetzt nicht die eigentliche Regel (niemals eine zweite Verbindung waehrend einer noch offenen Schreibtransaktion committen), ist aber zusaetzliche Sicherheit. Alle anderen log_action()/log_action_system()-Aufrufstellen in dieser Session (customers(), ticket_new/-edit/-delete, license_delete, customer_portal, _issue_license_from_ticket, _revoke_license_row, _process_deactivate, _process_heartbeat) wurden systematisch geprueft -- committen bereits korrekt VOR dem Logging, betroffen war ausschliesslich diese eine Stelle. Verifiziert: direkter Reproduktionstest gegen eine echte Kopie der Live-Datenbank (POETESTs echte aktive Lizenz wird durch eine zweite ersetzt, exakt das gemeldete Szenario) -- vorher deadlock/Timeout, nachher 0.04s, korrekt superseded + Hostname + Audit-Log-Eintraege. Als Notfall-Hotfix bereits direkt auf dem Live-Master eingespielt (Dienst neu gestartet, seither keine weiteren 'database is locked'- Fehler); dieser Commit bringt Repo/Release auf denselben Stand, damit 'latest' nicht wieder auf die kaputte v1.2.0 zurueckfaellt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
cee5311b1d |
Ticketverwaltung, Kundenstammdaten, Kundenportal, Lifetime-Lizenzen, Dashboard-Kacheln (v1.2.0)
Reaktion auf mehrere Bug-Reports und Feature-Wuensche zum bestehenden Ticket-System (v1.1.0): 'Logs gehen nicht', 'Ticketverwaltung fehlt', 'Lizenzen koennen nicht vom Dashboard geloescht werden', plus eine Reihe konkreter neuer Anforderungen. ## Bugfixes - /logs: behob einen Absturz (jinja2.exceptions.UndefinedError: 'global_check_interval' is undefined) UND die eigentliche Ursache dahinter -- logs.html war unveraendert aus dem TESM-Fork uebernommen (PoE-Geraete-Polling-Text/-Farblogik, ein Template-Feld, das app.py nie befuellt hat) und ausserdem schrieb auf diesem Server ueberhaupt nichts in live.log (kein poe.sh-Aequivalent vorhanden). Live-Log zeigt jetzt echte Ticket-/Lizenzaktionen, mit passendem Hinweistext und Faerbung. - Lizenz-Loeschung war bereits vorhanden, aber nur ueber Ticket- -> Lizenz-Detailseite erreichbar -- jetzt zusaetzlich inline in der Lizenzhistorie jedes Tickets. ## Ticketverwaltung (neu: eigene Seite /tickets) - Volle CRUD: Liste mit Suche, Bearbeiten (Typ/Module/Laufzeit/Lifetime, wirkt nur auf die naechste Ausstellung), Loeschen (nur wenn keine offene Lizenz mehr besteht, dann inkl. Historie). - Dashboard (/) zeigt seitdem NICHT mehr die Ticket-Tabelle, sondern eine rein informative Kachel-Uebersicht der aktuell AKTIVEN Lizenzen, exakt im Kachel-Stil von TESMs eigenem Geraete-Dashboard: Ampelfarben (gruen >90 Tage, orange 30-90, rot <30/abgelaufen), gruppiert, Firma + Hostname der Kundeninstanz. Hostname kommt neu vom TESM-Client per Activate/ Heartbeat (srv/tesm/licensing.py, abwaerts-kompatibel: alte Clients ohne das Feld ueberschreiben nie einen bereits bekannten Hostnamen). ## Kundenstammdaten - Neue Felder: Strasse/Hausnummer/PLZ/Ort/Ansprechpartner. E-Mail ist beim Anlegen jetzt Pflicht; beim Bearbeiten bewusst nur validiert, wenn ausgefuellt (sonst waeren Bestandskunden aus der Zeit vor dieser Umstellung fuer JEDE Aenderung blockiert gewesen). ## Kundenportal (neu, per Magic-Link, kein Login) - /portal/<token>: zeigt einem Kunden ALLE seine Tickets/Lizenzen auf einen Blick (ergaenzt den bestehenden Pro-Ticket-Link /self-service/<ticket_id>) und erlaubt ihm, seine eigenen Kontakt-/ Anschriftdaten selbst zu pflegen -- Firmenname und kommerzielle Ticket-Eckdaten bleiben bewusst admin-verwaltet. token = eigene Spalte license_customers.portal_token, rueckwirkend fuer Bestandskunden erzeugt. ## Lifetime-Lizenzen - Neues Ticket-Flag 'Lifetime': ignoriert die Laufzeit fuer jede REGULAERE Ausstellung (Sentinel-Ablaufdatum 2099-12-31, licensing. LIFETIME_EXPIRES_AT) -- eine zusaetzlich erzeugte Trial-Lizenz aus demselben Ticket bleibt immer die normale 30-Tage-Variante. Trial ALS TICKET-GRUNDTYP + Lifetime wird serverseitig verhindert (haette eine dauerhafte, nie ablaufende Lizenz ganz ohne Module ergeben). ## Absicherung nach Review Ein Adversarial-Review-Durchlauf ueber den kompletten Diff deckte vor dem Deploy zusaetzlich auf und wurde behoben: - TOCTOU-Race: zwei nahezu gleichzeitige 'Lizenz erstellen'-Anfragen fuer dasselbe Ticket (z.B. Doppelklick im Self-Service) konnten beide durchkommen. Jetzt durch einen partiellen Unique-Index (idx_one_open_license_per_ticket) auf DB-Ebene ausgeschlossen; die zweite Anfrage bekommt sauber die bereits erstellte Lizenz zurueck statt eine zweite zu erzeugen. - Analoger Unique-Index auf license_customers.portal_token als Backstop fuer den neuen Magic-Link. - tickets.html nutzte noch die alte, feste '<=30 Tage'-Schwelle statt der gemeinsamen Ampelfarbe -- Lizenzen im 30-90-Tage-Bereich blieben dort unmarkiert, obwohl das Dashboard sie schon als orange zeigte. SCHEMA_VERSION 2 -> 3 (neue Spalten/Tabellen, ueber install.sh mit Backup+Health-Check abgesichert). Verifiziert: vollstaendige Migration gegen eine echte Kopie der Live-Datenbank, anschliessend 22 Funktionstests direkt gegen die echten Routen (Validierung, Lifetime-Ausstellung, Sperr-/Kaskadenregeln bei Loeschungen, Kundenportal inkl. ungueltiger Eingaben) sowie ein Playwright- Seitenladetest ohne Konsolenfehler -- beides nach den Review-Fixes erneut komplett durchlaufen, inkl. direktem Nachweis, dass der neue Unique-Index eine doppelte offene Lizenz pro Ticket tatsaechlich auf DB-Ebene ablehnt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
7e39320c87 |
Ticket-System fuer Kunden-Self-Service + Fingerprint-Auto-Supersede (v1.1.0)
Konzept: Kunde kauft eine Lizenz -> Admin legt einen Kunden an (oder nutzt einen bestehenden) und weist ihm ein TICKET fuer eine Lizenz mit festen Eckdaten (Typ/Module/Laufzeit) zu -- das Ticket ist die dauerhafte, dem Kunden gehoerende Berechtigung, unabhaengig von der konkreten signierten Lizenzdatei darunter. Admin ODER Kunde (per Self-Service-Link, kein Login noetig) koennen aus einem offenen Ticket jederzeit eine Lizenz erstellen, widerrufen und erneut erstellen -- ein Kunde kann mehrere Tickets haben (z.B. mehrere Systeme). Neu: - license_tickets-Tabelle (id = unerratbares Token, customer_id, Typ/ Module/Laufzeit, status 'open'/'blocked'), licenses.ticket_id-Spalte. Rueckwirkende Migration erzeugt fuer jede bereits VOR diesem Feature direkt ausgestellte Lizenz automatisch ein passendes Ticket (sonst waeren z.B. POETESTs bereits aktivierte Produktivlizenz oder die Demo-Lizenzen im neuen, rein ticket-basierten Dashboard verschwunden) -- gegen eine echte Kopie der Live-Datenbank getestet. - /tickets/new (Ticket anlegen, admin), /tickets/<id> (Details, Lizenz aus Ticket erstellen -- reguraer laut Ticket-Typ ODER als Trial mit vollem Modulumfang unabhaengig vom Ticket-Typ --, Ticket sperren/ entsperren). - /self-service/<ticket_id>: oeffentlich erreichbar (kein Login), Zugriff ueber Besitz des unerratbaren Ticket-Tokens (192 Bit Zufall) als 'Magic Link' -- Kunde kann eigene Lizenz herunterladen, widerrufen und (mit den Ticket-Eckdaten) neu erstellen. Link wird auf der Ticket-Seite angezeigt und in die 'Lizenz per E-Mail senden'-Nachricht aufgenommen. - /licenses/<id>/delete (admin): raeumt widerrufene oder nie aktivierte Lizenzen auf, OHNE das zugehoerige Ticket anzutasten -- der Kunde (oder Admin) kann sich ueber das Ticket jederzeit eine neue mit denselben Eckdaten erzeugen. - Bugfix: wird fuer einen Fingerprint eine neue Lizenz aktiviert (z.B. Trial durch Enterprise ersetzt), werden jetzt automatisch alle ANDEREN fuer denselben Fingerprint noch 'active' vermerkten Lizenzen auf 'deactivated' gesetzt -- vorher blieben beide faelschlich gleichzeitig aktiv in der Uebersicht stehen. Bewusst nach Fingerprint gefiltert (ein Kunde darf mehrere Systeme mit je eigener Lizenz betreiben). - Dashboard zeigt jetzt Tickets statt roher Lizenzzeilen (inkl. aktueller Lizenz je Ticket), Kunden-Seite zeigt Ticket-Anzahl statt Lizenz-Anzahl mit Filter-Link aufs Dashboard. SCHEMA_VERSION 1 -> 2 (neue Tabelle + Spalte, ueber install.sh- Sicherheitsnetz mit Backup+Health-Check abgesichert). Verifiziert: Migration gegen eine echte Kopie der Live-Datenbank (POETESTs aktive Produktivlizenz + 3 Demo-Lizenzen) durchlaufen lassen -- alle 8 bestehenden Lizenzen haben danach ein Ticket, keine verwaist. Anschliessend alle neuen Seiten (Dashboard, Ticket-Anlage, Ticket-Detail, Self-Service) per Playwright gegen diese migrierte Kopie auf einem isolierten Scratch-Port durchgeklickt, keine Fehler. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
b280cbf121 |
Version 1.0.1: update.sh-Fix fuer 'latest'-Aufloesung
Kein App-Code geaendert -- reiner Versions-/Release-Bump, damit 'latest' auch als heruntergeladenes Paket das bereits korrigierte update.sh enthaelt (siehe vorheriger Commit). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
83eb0be3b1 |
Master-Lizenzserver Phase 2: neue Schwester-Anwendung srv/tesm-license (v1.0.0)
Fork von TESM (srv/tesm), auf Kunden-/Lizenzverwaltung reduziert statt
Geraete-/PoE-Management. Wiederverwendet unveraendert: Login/Session/
Benutzer- und Gruppenverwaltung, LDAP/AD, NGINX- und Systemeinstellungen,
Live-Log/Verlauf/Auditlog, Im-/Export-Grundgeruest, das komplette
PERMISSIONS/NAV_ITEMS/inject_nav()-Rechtesystem sowie das in Phase 1
gebaute Lizenzsystem selbst sowohl fuer die MASTER-eigene Bootstrap-Lizenz
als auch fuer das exakt gleiche licensing.py (byte-identisch zu TESM --
Signieren/Verifizieren muss zwischen beiden Seiten kompatibel bleiben).
Entfernt: Clients/Switche/Zugangsdaten/DHCP/Fileshare/Wartung/Papierkorb/
manueller PoE-Neustart/SSH-Terminal inkl. aller zugehoerigen Tabellen,
Routen, Permissions, Nav-Eintraege und Abhaengigkeiten (paramiko/Flask-Sock/
simple-websocket/PyNaCl/pyasn1 aus requirements.txt). Benutzer-/Gruppen-
Loeschung von Soft- auf Hard-Delete umgestellt (kein Papierkorb mehr).
Eigener Pfad-/Env-Var-Namespace (TESM_LICENSE_* statt TESM_*, /var/log/
tesm-license statt /var/log/tesm usw.), damit Master und TESM testweise
sogar auf demselben Host nebeneinander laufen koennen, ohne sich Log-/
Config-Pfade streitig zu machen.
Neu -- der eigentliche Lizenzserver:
- license_customers/licenses-Tabellen, Master-Signaturschluessel
(master_signing_key.json, einmalig erzeugt, NIE automatisch rotiert --
jede Kundenlizenz traegt den zum Ausstellungszeitpunkt aktuellen
master_pubkey fest eingebettet).
- Dashboard ('/') als Kunden-/Lizenzuebersicht (Typ, Module, Ablauf,
Status, letzter Heartbeat), eigene Kunden-Verwaltungsseite.
- Lizenz-Ausstellung (Typ/Module/Laufzeit -> signierte Datei via
licensing.issue_license), Detailseite, Download, Widerruf.
- /api/activate, /api/deactivate, /api/heartbeat (unauthentifiziert per
Design -- die Signatur der Anfrage IST der Berechtigungsnachweis) sowie
eine manuelle Offline-Code-Seite, die dieselben drei Verarbeitungs-
funktionen (_process_activate/_process_deactivate/_process_heartbeat)
nutzt wie die Online-API -- ein Protokoll, zwei Transportwege.
- E-Mail-Versand ausgestellter Lizenzen per Microsoft Graph
(Client-Credentials-Flow, reine Standardbibliothek/urllib, keine neue
Abhaengigkeit) inkl. Einrichtungsanleitung und Verbindungstest.
- create_master_license.py: lokales Bootstrap-/Erneuerungs-Skript fuer
die eigene Lizenz des Masters (kein externer Super-Master noetig).
Verifiziert auf dem Testsystem (192.168.82.51): App laeuft parallel zu der
dort laufenden echten TESM-Instanz (Port 5001 vs. 80/5000, eigene
Log-/DB-Pfade, TESM unangetastet). Kompletter ECHTER End-to-End-Rundlauf
per Playwright durchgespielt -- kein simulierter Gegenpart: Kunde anlegen
-> Custom-Lizenz (dhcp+fileshare) ausstellen -> herunterladen -> auf der
echten TESM-Instanz hochladen -> ECHTE Online-Aktivierung (Master
verzeichnet Fingerprint, TESM zeigt 'Aktiviert') -> ECHTE Online-
Deaktivierung (TESM zeigt 'lizenzlos, Export bleibt moeglich', Master
zeigt Status 'Deaktiviert'). Lokale Syntax-/Templatepruefung (py_compile,
pyflakes, jinja2-Parse aller Templates) sauber.
Bewusst NICHT nach main gemergt/getaggt/ausgerollt -- folgt zusammen mit
Phase 3 (Infrastruktur-Umzug + POETEST-Enterprise-Lizenz), siehe Plan
toasty-twirling-hickey.md (Phase 2 von 3).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|