v2.0.5
29
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d31173fa5e |
Banner: Einzahl, wo eines gemeint ist
"1 Clients online" liest man jeden Tag einmal zu oft. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>v2.0.5 |
||
|
|
05885bfff1 |
Anmeldebanner wieder in Betrieb -- ohne die Anwendung zu starten
Das Banner der Vorgaengerfassung hing an /srv/tesm/motd_banner.py und importierte dafuer die ganze Flask-Anwendung samt deren venv. Nach dem Umstieg zeigte es damit die Zahlen einer Datenbank, die niemand mehr fuellt -- eingefroren, aber unverdaechtig. Aufgefallen ist es erst, als der alte Ueberwachungsdienst abgeschaltet wurde. deploy/tesm-motd loest das anders: **nur Standardbibliothek**, kein venv, kein Import der Anwendung, die Datenbank wird nur lesend geoeffnet (mode=ro). Ein Lauf dauert Millisekunden statt eines Flask-Starts. install.sh legt das Skript nach /usr/local/lib/tesm/ und schreibt /etc/update-motd.d/50-<name>, abgesichert mit "timeout 3" und "|| true" -- ein Banner darf eine Anmeldung nie blockieren. Ein Banner der Vorgaengerfassung wird entfernt, aber nur, wenn es genau auf motd_banner.py zeigt. Damit es nicht zwei Wahrheiten ueber "online" gibt, steht die Zaehlung der Statusuebersicht jetzt als Konstante DASHBOARD_COUNT_SQL in monitor.py; das Banner traegt sie wortgleich, und ein Hygienetest vergleicht beide. Dasselbe fuer die Zeile unter der Wortmarke. Ein weiterer Test verbietet dem Banner jeden Import ausserhalb der Standardbibliothek -- genau dieser Import war die Ursache. Der Lizenzserver bekommt sein eigenes Banner (Kunden und ausgestellte Lizenzen). Eine Tagline hat er bewusst nicht: die Funktionsliste von TESM waere dort falsch, und eine erfundene waere schlimmer als keine. Version 2.0.5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
168d10bf87 |
Leerlaufabmeldung: die zweite Schreibstelle und ein Tab, der das alte Skript faehrt
Die Abmeldung hat weiterhin nicht ausgeloest. Zwei Gruende, beide fuer sich ausreichend: * **save_session** schreibt ``idle_expires_at`` bei jeder Anfrage, die die Sitzung anfasst -- ein neu erzeugtes CSRF-Token genuegt. Die Pruefung in ``open_session`` lief damit ins Leere. Sitzungsdaten werden weiter gespeichert, nur die Fristen bleiben stehen. Dieselbe Stelle benutzte ausserdem ``idle_minutes`` statt der einstellbaren Zeit; bei der Vorgabe von dreissig Minuten fiel das nicht auf. * **Der Kopf allein reicht nicht.** Er setzt voraus, dass der Browser das aktuelle Skript faehrt -- ein Tab, der seit gestern Abend offen ist, faehrt das alte und schickt ihn nicht. Genau dieser Tab ist der Fall, um den es geht. Der Server weiss es ohne Zutun des Browsers: die Endpunkte, die eine Kachel oder ein Protokoll nachlaedt, sind als Aktualisierungsausschnitt gekennzeichnet (``@fragment``) und gelten damit immer als automatisch. ``request.endpoint`` steht in ``open_session`` noch nicht -- Flask oeffnet die Sitzung vor dem Routing --, deshalb sieht die Erkennung dort selbst nach. Dass es auf dem Lizenzserver zuverlaessig funktionierte, war der Hinweis: dort pollt keine Kachel. Dazu ein Kopf ``X-Session-Expired`` fuer Seiten, die auch oeffentlich antworten. Die Statusuebersicht liefert nach dem Ablauf eine 200 mit den oeffentlichen Kacheln -- am Status ist der Verlust der Sitzung also nicht zu erkennen, und der offene Tab zeigte weiter eine Oberflaeche, als sei alles in Ordnung. Jetzt laedt er neu und zeigt die Anmeldemaske. Version 2.0.4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>v2.0.4 |
||
|
|
2dea198bcc |
Upgrade von 1.x, Leerlaufabmeldung, Mitschrift im Audit
upgrade.sh -- ein Weg vom Stand SONNET5 auf diese Fassung, mit Daten. Der Ablauf braucht keine Schalter: alte Installation sichern, abschalten, neu installieren, exportieren, uebersetzen, einspielen. Die Sicherung steht bewusst vor dem Export -- ohne --instance installiert install.sh in dasselbe Verzeichnis, und wer erst exportiert und dann sichert, sichert im schlechtesten Fall nichts mehr. Gelesen wird nur aus der Kopie. Dafuer zwei neue Befehle: "legacy-export" liest eine alte Installation unmittelbar (der Vorgaenger kann nur ueber die Weboberflaeche exportieren -- fuer ein Upgrade der falsche Weg, und unmoeglich, wenn die alte Anwendung nicht mehr startet) und schreibt eine Datei im alten Format, die deshalb auch als Rueckweg taugt. "legacy-import" liest eine Datei im neuen Format ein, ueber transfer.apply_import -- dieselbe Funktion wie die Seite "Sicherung", kein zweiter Schreibpfad. Am Lizenzserver hat der Lauf gegen echte Altdaten zwei Fehler gezeigt, die jede Uebernahme unmoeglich machten: * Der alte Signaturschluessel ist URL-sicher kodiert (- und _). Das neue Protokoll prueft Base64 streng -- richtig so, aber beim Lesen einer alten Datei brach die Uebernahme mit einem Traceback ueber "kein gueltiges Base64" ab. Umkodiert wird jetzt an der Grenze zum Altformat; der kanonische Decoder bleibt streng. * Der Anbieterblock wurde aus den Einstellungen der *neuen* Installation gebaut, und ohne Endpunkt brach die Uebernahme ab. Bei einem Upgrade ist die neue Installation frisch -- dort steht nichts. Endpunkt und Anbieterdaten kommen jetzt aus der alten Installation, aber nur dort, wo neu nichts steht. Der alte Signaturschluessel wird uebernommen: sonst muesste jeder Kunde einen Ausstellerwechsel bestaetigen. Leerlaufabmeldung: sie hat nie ausgeloest. Ein offener Tab fragt seine Kacheln alle 15 Sekunden neu ab, und jede Abfrage verlaengerte das Leerlauffenster -- nachweisbar an einer Sitzung, deren last_seen_at eine Minute alt war, obwohl seit zehn Stunden niemand am Rechner sass. Der Client kennzeichnet automatische Anfragen jetzt mit einem Kopf, der Server verlaengert daraufhin nichts, und last_seen_at bleibt, was der Name sagt. Faellt die Sitzung waehrend des Pollens, antwortet der Server mit 401 statt mit einer Umleitung: der Tab laedt neu und zeigt die Anmeldemaske, statt stumm mit alten Zahlen stehenzubleiben. Im Audit ist der Name der Mitschrift jetzt ein Verweis auf die Datei -- es ist derselbe Weg wie zuvor, nur einen Klick weit. Verlinkt wird nur, wenn die Datei existiert und der Betrachter Protokolle lesen darf; der Rest des Eintrags wird maskiert, denn ein Auditeintrag enthaelt Fremdeingaben. Ausserdem: Version 2.0.3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>v2.0.3 |
||
|
|
50da075c15 |
Aktualisierung: zwei Pruefungen, die nie zugegriffen haben
Der Lauf gegen die echte Installation hat zwei Fehler gezeigt, die den
Aktualisierungsweg fuer jede Version unbrauchbar machten:
* Die Suche nach install.sh im entpackten Paket lief mit "-maxdepth 2".
release.sh packt mit dem Praefix "<name>/", das Skript liegt also drei
Ebenen tief -- gefunden wurde nie etwas, und die Meldung schob es auf den
Download ("Im Paket fehlt deploy/install.sh").
* Die CRLF-Pruefung rief "file" auf. Das Paket gehoert nicht zur
Grundinstallation eines Servers; fehlt es, endete die Pipeline in "command
not found", grep bekam nichts zu sehen, der if-Zweig wurde nicht betreten --
und genau der Fehler, den die Pruefung abfangen soll, ging unbemerkt durch.
Der Wagenruecklauf wird jetzt ohne Zusatzpaket gesucht, mit "-U": unter Linux
ohne Wirkung, auf einer Windows-Buildmaschine entscheidend, weil grep dort
den Wagenruecklauf beim Lesen entfernt.
Beide Pruefungen fuehren die echten Zeilen aus dem Skript aus -- die eine
gegen den Verzeichnisaufbau, den release.sh erzeugt, die andere gegen zwei
Dateien mit und ohne Wagenruecklauf.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v2.0.2
|
||
|
|
23980182b0 |
Release-Pakete gehoeren nicht ins Repository
Der gebaute Tarball lag im Arbeitsbaum und wanderte in den Commit; das naechste Paket enthielt daraufhin das vorige -- doppelte Groesse, verschachtelter Inhalt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
85bec34d7d |
Aktualisierungsweg gangbar machen
Drei Fehler, die erst beim Aktualisieren auffallen -- also genau dann, wenn niemand sie sehen will: * update.sh zeigte auf "gitea.example.invalid" und den Besitzer "wis". Auf keinem Host konnte das funktionieren, und die Doku erwaehnte die Variablen nicht, mit denen man es haette umlenken koennen. Die Vorgaben zeigen jetzt dorthin, wo die Releases liegen; das Repository heisst wie die Anwendung. * update.sh gab nichts an install.sh weiter. Eine benannte Instanz war damit nicht aktualisierbar: ohne --instance entsteht neben "tesm-opus" eine zweite Installation namens "tesm", mit eigener Datenbank und eigener nginx-Site. * install.sh setzte bei einer Instanz die Ports auf 8080/8443 zurueck, auch wenn sie seit der Installation auf Port 80 lief. Ein Reverse Proxy davor haette danach ins Leere gezeigt. Bestehende Ports werden jetzt aus Umgebungsdatei und nginx-Site uebernommen -- dieselbe Regel wie beim Secure-Flag: eine Aktualisierung nimmt nichts weg, was nicht ausdruecklich neu gesetzt wird. Zwei Tests fuehren dazu die echten Zeilen aus den Skripten aus, statt sie nachzubauen. Beide fallen ohne die Korrektur durch. Ausserdem: die vier subprocess-Aufrufe im Textmodus nageln die Kodierung auf UTF-8 fest. "text=True" allein nimmt die Locale des Prozesses -- unter systemd haeufig C/ASCII --, und das Lesen bricht ab, sobald ein Werkzeug einen Umlaut ausgibt. Der Fehler entstand im Leser-Thread von subprocess und damit weit weg von seiner Ursache; im Testlauf war er als Warnung sichtbar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ab5fda658e |
Umlaute in der Oberflaeche, Sitzungskopplung, Statusanzeigen
Echte Umlaute in allen Texten der Oberflaeche und in den Markdown-Dateien. Die Einbindung von Dateifreigaben haengt jetzt an der Sitzung: wird sie widerrufen oder laeuft sie ab, verschwinden die Freigaben mit ihr. Neu einstellbar ist die automatische Abmeldung bei Leerlauf (Vorgabe 30 Minuten). Ein Geraet, das nicht antwortet, bietet nur noch "Einschalten (PoE)" an -- Neustart ueber SSH oder RPC braucht ein erreichbares Geraet. Der Zeitgeber der naechsten Pruefung und die Restlaufzeit der Lizenz stehen jetzt oben in der Kopfzeile; der doppelte Knopf darunter ist weg. Millisekunden werden ab einer Sekunde als Sekunden und ab einer Minute als m:ss dargestellt. Ausserdem: Release-Tarball nicht mehr im Repository -- er haengt am Release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4e488625fe |
Herkunft eines gepinnten Ausstellers in Klartext
In der Spalte "Quelle" stand das Kuerzel des Protokolls: "tofu". Das ist Fachjargon (Trust On First Use) und als Wert in einer Tabelle unbrauchbar -- es sieht aus wie ein Tippfehler. Jetzt: "Erstkontakt" (beim ersten Einspielen automatisch uebernommen) und "Bestaetigt" (Ausstellerwechsel von einem Administrator freigegeben). Die Uebersetzung liegt in tesm_core.ui als ISSUER_SOURCES mit zwei Jinja-Filtern, nicht in zwei Vorlagen: der Lizenzserver zeigt dieselbe Angabe unter "Eigene Lizenz", und zwei Texte fuer denselben Sachverhalt laufen auseinander. Unbekannte Werte bleiben unveraendert sichtbar statt hinter einem Platzhalter zu verschwinden. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9799843cd2 |
Kurze Hostnamen in Freigaben aufloesen, Protokolle am Ende oeffnen
Freigaben mit kurzem Hostnamen (\S2025\Share) scheiterten mit "could not resolve address for S2025". Ursache: dieser Server hat keine Suchdomaene (search . in resolv.conf), ein Windows-Client hat eine. Der Name ist also richtig, nur hier nicht auflösbar. Die AD-Domaene ist aus den Einstellungen des Verzeichnisdienstes bekannt und wird jetzt zum Vervollstaendigen benutzt; laesst sich der Name gar nicht aufloesen, sagt die Meldung was zu tun ist statt "Unknown error". Auf POETEST gegen echtes DNS geprueft: \S2025\LDAP_Share -> \S2025.ad.eertmoed.net\LDAP_Share, und dieser Pfad bindet ein. Protokollansichten standen beim Oeffnen oben. Die neueste Zeile steht aber unten, und weil die Aktualisierung nur mitzieht, wenn man ohnehin am Ende ist, zog sie danach nie mit -- es sah aus wie ein kaputter Autoscroll, war aber nur die falsche Anfangsposition. Die Protokollansichten sind jetzt per data-tail markiert und springen beim Laden nach unten. Konfigurationsvorschauen (nginx, Kea) und der Offline-Code sind bewusst nicht markiert: die beginnen oben. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8b39bfaf10 |
Dateifeld sichtbar machen, RPC-Fehler verstaendlich melden
Der Knopf im Dateifeld kommt vom Browser und ignoriert jede Aussenfarbe: im dunklen Thema ein hellgraues Kaestchen mit dunkler Schrift, kaum zu lesen und passend zu nichts. Jetzt explizit wie ein Sekundaerknopf gestaltet -- einmal zentral, denn es betrifft vier Felder (Sicherung, Lizenz, Profilbild, Dateifreigaben). Dazu zwei neue Hinweise fuer den Windows-Neustart. "Could not initialise pipe winreg / NT_STATUS_OBJECT_NAME_NOT_FOUND" liest sich wie ein technisches Detail, hat aber fast immer eine Ursache: das Konto ist auf dem Zielgeraet kein Administrator. An einem echten Fall nachgemessen -- die Anmeldung gelang, aber schon "net rpc info" und der Dienstmanager antworteten mit ACCESS_DENIED. Ein Standardkonto bekommt die winreg-Pipe gar nicht zu sehen. Die Meldung sagt das jetzt und nennt den Pruefbefehl. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8e66d9e7bc |
apt-Installation laeuft ausserhalb des Dienst-Sandkastens
Der Sandkasten wird an dpkg und dessen Maintainer-Skripte weitervererbt; mit ProtectKernelLogs=yes liefert "dmesg" ein EPERM und ein Postinst-Skript bricht daran ab -- die Installation scheitert mit "dpkg returned an error code (1)", obwohl mit dem Paket alles in Ordnung ist. apt laeuft jetzt ueber systemd-run, also von PID 1 gestartet und damit ohne unsere Haertung. Der zuvor eingebaute nsenter-Umweg beim Einbinden von Freigaben ist zurueckgenommen. Diese Verben ruft der Lizenzserver nicht auf -- der Helfer bleibt aber in beiden Repositorien dieselbe Datei, weil eine zweite Fassung zwei Stellen waere, die auseinanderlaufen koennen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4bc8637a74 |
Freigaben einbinden: Mount im Namensraum des Wirts
Eine Freigabe wurde nie eingebunden -- "mount error(1): Operation not permitted". Gemessen: mit denselben Optionen und Zugangsdaten gelingt die Einbindung aus einer normalen Root-Shell sofort. Ursache ist der eigene Mount-Namensraum des Dienstes (ProtectSystem, ProtectHome, PrivateTmp, ReadWritePaths); darin laesst sich kein neues Dateisystem einbinden. Nachgebaut und belegt: derselbe Aufruf scheitert in einem transienten Unit mit dieser Haertung und gelingt, sobald er ueber nsenter im Namensraum des Wirts laeuft. Von dort wird der Mount in den Namensraum des Dienstes weitergegeben, die Anwendung sieht ihn also -- geprueft, inklusive Lesbarkeit fuer den Dienstbenutzer. mount-cifs und umount laufen deshalb ueber nsenter. Ohne nsenter im System bleibt das Verhalten unveraendert. Die Haertung des Dienstes bleibt vollstaendig erhalten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>v2.0.1 |
||
|
|
6ac2ed0a62 |
Nur noch, was zum Installieren und Betreiben des Lizenzservers noetig ist
Entfernt: Tests, Architekturdokumentation und die Anwendung TESM. Beides liegt im Entwicklungsrepository alientim/TESM-DEV. Neu: packages/tesm-licensing-server -- die Serverhaelfte des Lizenzprotokolls (issue_license, resign_license, build_master_response, verify_client_request, Schluesselerzeugung). Sie war bisher Teil von tesm-licensing und lag damit auch in jeder Client-Installation. Die gemeinsame Haelfte bleibt in tesm-licensing und wird von beiden Seiten benutzt. install.sh installiert diese Serverhaelfte ausschliesslich fuer --app tesm-license und bricht ab, wenn sie fehlt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>v2.0.0 |
||
|
|
f7805a2180 |
TESM-Lizenzserver 2.0.0 -- Neubau
Vollstaendiger Neubau der Anwendung. Der vorherige Stand bleibt unveraendert im Zweig SONNET5 erhalten. Aufbau: apps/tesm-license (Anwendung), packages/tesm-core (gemeinsamer Kern), packages/tesm-licensing (Lizenzprotokoll), deploy (Installation, systemd, privilegierter Helfer), docs, tests. Die verwaltete Anwendung liegt in ihrem eigenen Repository; beide Repositorien bringen die gemeinsamen Pakete mit, damit sich jedes allein installieren laesst. Die wichtigsten Unterschiede zum Vorgaenger: * Keine doppelte licensing.py -- ein Paket, das beide Anwendungen installieren, statt zweier Dateien, die byte-identisch bleiben sollen. * Der Webprozess laeuft unprivilegiert; alles, was Root braucht, geht ueber einen einzigen Helfer mit Positivlisten fuer jedes Argument. * CSRF-Schutz ueberhaupt -- der Vorgaenger hatte keinen. * Rechte werden serverseitig geprueft, nicht nur im Template ausgeblendet. * Keine Lizenz ohne master_endpoint: eine Ausstellung ohne Endpunkt wird abgelehnt statt eine Lizenz zu erzeugen, die sich nie aktivieren kann. * Offline-Aktivierung in beide Richtungen; die Lizenz bleibt als "Aktivierung offen" markiert, bis sie zurueckkommt. * Getrennte Signaturkontexte je Nachrichtenart, Nonce gegen Wiedereinspielung, seq gegen das Zurueckrollen auf eine aeltere Lizenz. * Kein Hostname im Maschinen-Fingerabdruck. * Verschachtelte Datenbankverbindungen sind ein Fehler, kein Deadlock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c130c85b17 |
Sub-Lizenzserver melden ihren Kunden-/Ticket-/Lizenzstand an den Master
Ein Sub-Lizenzserver (role='license_server') meldet jetzt seinen kompletten eigenen Kunden-/Ticket-/Lizenzstand an den Master, der ihn als Ueberblick auf einer neuen Kunden-Detailseite anzeigt: unter dem Kunden, der die Lizenzserver-Lizenz haelt, erscheinen dessen eigene Kunden mit ihren Tickets/Lizenzen -- genau wie gewuenscht. Transport: huckepack auf dem bereits bestehenden 6-Stunden-Heartbeat (licensing.py: neues optionales 'report'-Feld in build_client_request(), signiert wie jedes andere Feld -- ein Angreifer ohne den privaten Lizenzschluessel kann keinen gefaelschten Bericht unterschieben). Zusaetzlich sofortiger Best-Effort-Report per Hintergrund-Thread nach jeder lokalen Ticket-/Kunden-/Lizenz-Aenderung (_report_to_master_async()), damit der Master nicht bis zu 6h auf Aktualitaet warten muss. Funktioniert automatisch auch ueber den Offline-Code-Weg (/licenses/manual-code), da beide Transportwege denselben _process_heartbeat()-Code nutzen. - Drei neue, rein informative Tabellen (reported_sub_customers/ _tickets/_licenses), verankert an source_license_id (die Lizenz, die den Sub-Lizenzserver identifiziert). Ein Bericht ist immer ein VOLLER Schnappschuss -- bei jedem Empfang werden alle bisherigen Zeilen dieser source_license_id ersetzt (kein Diffing noetig, ein beim Sub-Lizenzserver geloeschtes Ticket verschwindet dadurch automatisch korrekt auch hier). - _build_own_issuance_report(): baut den Schnappschuss auf der Sub-Lizenzserver-Seite. - _ingest_sub_report(): uebernimmt ihn auf der Master-Seite, NUR wenn row['role']=='license_server' fuer die absendende Lizenz (Verteid- igung in der Tiefe -- row['role'] steht in der Master-DB fest, nicht im Client-Request, ein gewoehnlicher Kunde kann sich also nicht als Lizenzserver ausgeben). - Neue Kunden-Detailseite (/customers/<id>, customer_detail.html): Stammdaten, eigene Tickets bei uns, und -- falls dieser Kunde eine aktive role='license_server'-Lizenz haelt -- die gemeldeten eigenen Kunden mit Tickets/Lizenzen (Ampelfarbe/Resttage ueber dieselbe _license_dict_for_display()-Funktion wie ueberall sonst). Kunden- liste verlinkt jetzt auf die Detailseite, zeigt 'Lizenzserver'-Badge. Getestet: licensing.py isoliert (report signiert+verifiziert, Manipulation bricht die Signatur, unveraendertes Verhalten ohne report-Argument), _ingest_sub_report() isoliert (Erstbericht korrekt uebernommen, Zweitbericht ersetzt korrekt inkl. verschwindender Tickets, Isolation zwischen mehreren source_license_id sauber). ZUSAETZLICH ein vollstaendiger Zwei-Prozess-Integrationstest ueber echtes HTTP (zwei unabhaengige, native laufende Instanzen: 'Master' + 'Sub-Lizenzserver' auf unterschiedlichen Ports): Ticket mit role=license_server angelegt, Lizenz ausgestellt, auf der zweiten Instanz hochgeladen und ONLINE aktiviert, dort ein eigener Endkunde samt Ticket/Lizenz angelegt, automatischer Report ausgeloest -- Master-Kundendetailseite zeigt den gemeldeten Endkunden mit korrektem Ticket-Typ/Laufzeit/Status und aktuellem Heartbeat-Zeitstempel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>v1.2.7 |
||
|
|
ed2ed00a4e |
Sub-Lizenzserver: Lizenzen fuer Lizenzserver ausstellbar (role='license_server')
Der Master kann jetzt ueber den normalen Kunde->Ticket->Lizenz-Flow
Lizenzen ausstellen, die eine ANDERE Lizenzserver-Instanz voll
funktionsfaehig machen (Kunden/Tickets/Lizenzen fuer eigene Kunden
ausstellen, Benutzer/Gruppen/Einstellungen verwalten) -- mit genau
einer strukturellen Einschraenkung: der so lizenzierte Sub-Lizenzserver
kann selbst KEINE weiteren Lizenzserver-Lizenzen ausstellen. Die
eigene Lizenz DIESES Masters bleibt weiterhin ausschliesslich per
create_master_license.py erzeugbar, nie ueber die Ticket-UI.
- licensing.py: neues, signiertes role-Feld (master/license_server/
customer), siehe separates TESM-Release fuer die synchronisierte
Kopie (harte Invariante: beide Dateien bleiben identisch).
- create_master_license.py: setzt role='master' beim Selbstausstellen.
- Neue STRUKTURELLE Sperre can_issue_license_server_licenses(): True
NUR wenn die eigene, aktive Lizenz dieser Instanz role=='master' ist
-- haengt an der Instanz-Rolle, nicht an einer vergebbaren Permission,
daher von lokalen Admins eines Sub-Lizenzservers nicht aufhebbar.
Kombiniert mit current_user.is_admin an jeder Aufrufstelle (nur echte
Admins duerfen es je nutzen, wie gewuenscht -- kein delegierbares
Recht).
- license_tickets.role-Spalte (Migration, Default 'customer').
license_tickets.role bestimmt, welche Rolle eine daraus ausgestellte
Lizenz traegt -- nach Anlage unveraendert wie customer_id.
- ticket_new(): neue, nur fuer can_issue_license_server_licenses()+Admin
sichtbare Option 'Dies ist eine Lizenzserver-Lizenz'. Jeder andere
Versuch (manipuliertes Formular, Sub-Lizenzserver, Nicht-Admin) wird
serverseitig still auf 'customer' zurueckgestuft. Module werden fuer
role!='customer' erzwungen auf [] (TESM-Client-Konzept, fuer einen
Lizenzserver bedeutungslos).
- Self-Service (unauthentifiziert, nur ueber die Ticket-ID) ist fuer
role!='customer'-Tickets komplett gesperrt (404) -- Lizenzserver-
Lizenzen sind ausschliesslich admin-verwaltet, kein Kundenweg. Trial-
Ausstellung ('30 Tage, alle Module') ebenfalls nur fuer role=customer
moeglich (ValueError sonst, sauber abgefangen statt 500er).
- license_send_email(): ueberspringt Self-Service-/Kundenportal-Hinweise
fuer role='license_server'-Lizenzen (waeren ohnehin blockiert).
- UI: 'Lizenzserver'-Badge auf Tickets-/Lizenzen-Liste und Ticket-
Detailseite.
Getestet: licensing.py isoliert (role signiert+verifiziert, Manipulation
bricht die Signatur, Altformat ohne role faellt auf 'customer' zurueck),
can_issue_license_server_licenses()-Logik (6 Kombinationen), Schema-
Migration (Alt-DB ohne role-Spalte, idempotent), Ende-zu-Ende Ticket->
Lizenz-Ausstellung gegen echte create_db.py-DB. ZUSAETZLICH gegen einen
echten, lokal laufenden Flask-Server (native Windows-Python, WSL-
Netzwerksandbox in dieser Umgebung unzuverlaessig) voll durchgespielt:
Checkbox erscheint nur fuer role=master, Ticket+Lizenz-Ausstellung mit
role=license_server funktioniert Ende-zu-Ende (signierte Datei, Module
leer, Self-Service liefert 404), UND der Sicherheitstest: nach Wechsel
der eigenen Lizenz auf role=license_server verschwindet die Checkbox
UND ein direkter manipulierter POST mit role=license_server wird
serverseitig zuverlaessig auf 'customer' zurueckgestuft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.2.6
|
||
|
|
286a3464f1 |
Eigenes Recht fuer Tickets (tickets.view/.create/.edit)
Tickets liefen im Rechtebaum komplett ueber licenses.view/.create/.edit mit, obwohl Tickets ein eigener, gleichberechtigter Navigationspunkt neben Kunden und Lizenzen ist -- analog zu TESM, wo Clients/Switche/ Zugangsdaten trotz enger fachlicher Verwandtschaft jeweils eigene Rechte haben. Ergaenzt die im vorigen Release (v1.2.4) behobene Lizenz-Rechte-Luecke um diese zweite, gleich gelagerte Luecke. - Neues Rechtepaar tickets.view/tickets.create/tickets.edit (PERMISSIONS/PERMISSION_LABELS), Property can_manage_tickets. - Nav-Sichtbarkeit sowie tickets_list()/ticket_new()/ticket_edit()/ ticket_delete() auf das neue Recht umgestellt. - ticket_detail(): GET-Zugriff jetzt ueber can_manage_tickets. POST jetzt PRO SUB-AKTION geprueft statt eines einzigen Gates: 'toggle_block' (Sperren/Entsperren) -> tickets.edit (neu, Ticket- Domain); 'issue_license' (erstellt eine LIZENZ) bleibt bewusst unveraendert an licenses.edit gebunden, damit sich 'Tickets verwalten' und 'Lizenzen ausstellen' unabhaengig voneinander vergeben lassen, ohne dass irgendjemand automatisch die eine Faehigkeit durch die andere verliert. - tickets.html/ticket_detail.html: can_edit-Variable entsprechend in can_edit_ticket/can_issue_license aufgeteilt. - Migration (_migrated_tickets_permission_v1, einmalig geguarded): jede Gruppe mit licenses.view/.create/.edit bekommt automatisch das gespiegelte tickets.view/.create/.edit dazu -- niemand verliert beim Update stillschweigend eine Faehigkeit, die vorher implizit ueber licenses.* mitlief. - Getestet gegen isolierte Test-DB: Backfill korrekt (Gruppe mit voller Lizenz-Berechtigung bekommt volle Ticket-Berechtigung, Gruppe mit nur .view bekommt nur tickets.view), idempotent, keine Duplikate. PERMISSIONS/PERMISSION_LABELS/NAV_ITEMS erneut vollstaendig konsistent (keine Nav-Keys mehr ohne Rechte-Entsprechung), app.py syntaktisch fehlerfrei kompiliert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>v1.2.5 |
||
|
|
619673ac76 |
Eigenes Recht fuer die Lizenzseite (settings_license.view/.edit)
Die Lizenzseite (Einstellungen -> Lizenz) hatte im Rechtebaum keinen eigenen Eintrag -- sie lief komplett ueber settings_system.view/.edit mit, genau wie im TESM-Client (siehe dortiger Fix). Damit konnte 'Lizenz verwalten' nicht unabhaengig von 'Systemeinstellungen verwalten' vergeben/eingeschraenkt werden. - Neues Rechtepaar settings_license.view/settings_license.edit (PERMISSIONS/PERMISSION_LABELS), Property can_view_settings_license. - Nav-Sichtbarkeit und alle /settings/license*-Routen auf das neue Recht umgestellt (view-Route: can_view_settings_license; alle mutierenden Routen -upload/-activate(/confirm)/-deactivate(/confirm): settings_license.edit). Das 'auch ohne aktive Lizenz sichtbar bleiben'-Verhalten bleibt unveraendert (andere Pruefung, license_active()). - settings_license.html: can_edit auf settings_license.edit umgestellt. - Migration (_migrated_settings_license_permission_v1, einmalig geguarded): jede Gruppe mit settings_system.view/.edit bekommt automatisch settings_license.view/.edit dazu -- niemand verliert beim Update stillschweigend Zugriff, den er vorher implizit hatte. - Getestet gegen isolierte Test-DB (create_db.py-Frischinstallation): Migration korrekt (inkl. Gruppe mit nur .view, die korrekt KEIN .edit bekommt), idempotent bei zweitem Lauf, keine Duplikate. PERMISSIONS/ PERMISSION_LABELS/NAV_ITEMS strukturell konsistent geprueft, app.py syntaktisch fehlerfrei kompiliert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>v1.2.4 |
||
|
|
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>v1.2.3 |
||
|
|
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>
v1.2.2
|
||
|
|
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>v1.2.1 |
||
|
|
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>v1.2.0 |
||
|
|
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>v1.1.0 |
||
|
|
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>v1.0.1 |
||
|
|
5fa60ec3ef |
update.sh: 'latest' zuverlaessig ueber die Gitea-API aufloesen
Identischer Bug/Fix wie im TESM-Hauptrepo (siehe dortiger Commit fuer Details): /releases/download/latest/tesm-license-latest.tar.gz liefert HTTP 200, aber Giteas automatisch generiertes Quellcode-Archiv des aktuellen Default-Branch-Stands statt des echten Release-Assets. 'latest' wird jetzt zuerst per Gitea-API auf den echten Tag aufgeloest. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a9fc56ff47 |
Eigenstaendiges Repo: install.sh/update.sh auf tesm-license umgestellt, README + .gitignore
Ausgelagert aus dem TESM-Hauptrepo (srv/tesm-license/, install-license.sh, update-license.sh, zugehoerige systemd-/nginx-Vorlagen) per git-filter-repo, komplette bisherige Historie erhalten. install-license.sh und update-license.sh wurden zu install.sh/update.sh (jetzt die einzigen Skripte in diesem Repo, keine Namenskollision mit TESM mehr noetig). update.sh zeigt jetzt auf dieses eigene Repo/Release-Paket (tesm-license-vX.Y.Z.tar.gz) statt auf das gemeinsame TESM-Paket. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>v1.0.0 |
||
|
|
3f5d85c7ee |
Lizenzserver-Deployment: install-license.sh/update-license.sh + systemd/nginx-Vorlagen
Fork von install.sh/update.sh/tesm.service/nginx-Site (TESM selbst), beschraenkt auf /srv/tesm-license -- deployt ausschliesslich den in Phase 2 gebauten Master-Lizenzserver, laesst eine evtl. auf demselben Host vorhandene TESM-Installation komplett unberuehrt (eigener Pfad-/Env-Var- Namespace, siehe app.py). Gleiches Backup+Health-Check+Rueckroll- Sicherheitsnetz wie bei TESMs eigenem Installer. Teil von Phase 3 (Infrastruktur), siehe Plan toasty-twirling-hickey.md. 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>
|