Version 2.5.1: Sitzungsdauer im Lizenzserver, Umlaute, Umleitung alter Adressen

This commit is contained in:
alientim committed 2026-09-11 19:16:27 +02:00
1 parent 1ac5dc1637
commit c52b3626b2
10 files changed
+297 -14

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "tesm-license" name = "tesm-license"
version = "2.5.0" version = "2.5.1"
description = "TESM-Lizenzserver -- Kunden, Tickets, Lizenzen, Aktivierung" description = "TESM-Lizenzserver -- Kunden, Tickets, Lizenzen, Aktivierung"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
@@ -54,6 +54,8 @@ def system():
"trash_retention_days", "trash_retention_days",
"log_rotate_interval", "log_rotate_interval",
"log_rotate_keep", "log_rotate_keep",
"session_idle_minutes",
"session_absolute_hours",
], ],
), ),
specs={spec.key: spec for spec in extension.settings.specs()}, specs={spec.key: spec for spec in extension.settings.specs()},
@@ -98,6 +100,8 @@ def system_save():
"trash_retention_days", "trash_retention_days",
"log_rotate_interval", "log_rotate_interval",
"log_rotate_keep", "log_rotate_keep",
"session_idle_minutes",
"session_absolute_hours",
): ):
spec = extension.settings.spec(key) spec = extension.settings.spec(key)
if spec.kind == "bool": if spec.kind == "bool":
@@ -28,7 +28,7 @@ def _is_master(_user: Any) -> bool:
NAVIGATION = NavTree( NAVIGATION = NavTree(
[ [
NavItem(key="index", label="Uebersicht", icon="grid", endpoint="dashboard.index"), NavItem(key="index", label="Übersicht", icon="grid", endpoint="dashboard.index"),
NavItem( NavItem(
key="customers", key="customers",
label="Kunden", label="Kunden",
@@ -60,9 +60,6 @@
selected=values.log_rotate_interval) }} selected=values.log_rotate_interval) }}
{{ field('log_rotate_keep', 'Aufbewahrte Rotationen', value=values.log_rotate_keep, {{ field('log_rotate_keep', 'Aufbewahrte Rotationen', value=values.log_rotate_keep,
type='number') }} type='number') }}
{{ field('session_idle_minutes', 'Automatische Abmeldung (Minuten)',
value=values.session_idle_minutes, type='number',
hint_text=specs['session_idle_minutes'].hint) }}
{{ field('trash_retention_days', 'Papierkorb-Aufbewahrung (Tage)', {{ field('trash_retention_days', 'Papierkorb-Aufbewahrung (Tage)',
value=values.trash_retention_days, type='number') }} value=values.trash_retention_days, type='number') }}
</div> </div>
@@ -73,6 +70,34 @@
</div> </div>
<div class="stack"> <div class="stack">
{# Eigene Karte: die Abmeldung stand zwischen Protokollrotation und
Papierkorb -- drei Dinge ohne Zusammenhang. Wer die Sitzungsdauer sucht,
sucht sie nicht unter "Protokolle". #}
<section class="card">
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('clock', size=17) }} Sitzungen</div>
<div class="card__sub">Wie lange eine Anmeldung gilt</div>
</div>
</div>
<form method="post" action="{{ url_for('settings.system_save') }}">
<input type="hidden" name="{{ csrf_field_name }}" value="{{ csrf_token() }}">
<div class="card__body">
<div class="form-grid">
{{ field('session_idle_minutes', 'Automatische Abmeldung nach Leerlauf (Minuten)',
value=values.session_idle_minutes, type='number',
attrs='min="5" max="1440" step="1"',
hint_text=specs['session_idle_minutes'].hint) }}
{{ field('session_absolute_hours', 'Längstmögliche Sitzungsdauer (Stunden)',
value=values.session_absolute_hours, type='number',
attrs='min="1" max="168" step="1"',
hint_text=specs['session_absolute_hours'].hint) }}
</div>
</div>
<div class="card__footer"><button class="btn btn--primary" type="submit">Speichern</button></div>
</form>
</section>
<section class="card"> <section class="card">
<div class="card__header"> <div class="card__header">
<div class="flex-1"><div class="card__title">{{ icon('server', size=17) }} Host</div></div> <div class="flex-1"><div class="card__title">{{ icon('server', size=17) }} Host</div></div>
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "tesm-core" name = "tesm-core"
version = "2.5.0" version = "2.5.1"
description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver" description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
@@ -13,10 +13,12 @@ import secrets
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any, Sequence from typing import Any, Sequence
from flask import Blueprint, Flask, g, render_template, request from urllib.parse import urlencode
from flask import Blueprint, Flask, g, redirect, render_template, request
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
from . import audit, logging_setup, ui from . import audit, legacy_urls, logging_setup, ui
from .config import CoreConfig from .config import CoreConfig
from .core_schema import CORE_MIGRATIONS from .core_schema import CORE_MIGRATIONS
from .db import Database from .db import Database
@@ -146,7 +148,7 @@ def create_base_app(
#: Wortmarke, ohne Zeile darunter; die Marke selbst zeigen beide #: Wortmarke, ohne Zeile darunter; die Marke selbst zeigen beide
#: Anwendungen. #: Anwendungen.
auth_tagline: str = "", auth_tagline: str = "",
app_version: str = "2.5.0", app_version: str = "2.5.1",
built_in_trusted_keys: tuple[str, ...] = (), built_in_trusted_keys: tuple[str, ...] = (),
enable_license: bool = True, enable_license: bool = True,
template_folder: str | None = None, template_folder: str | None = None,
@@ -356,6 +358,16 @@ def create_base_app(
# -- Fehlerseiten ------------------------------------------------------ # # -- Fehlerseiten ------------------------------------------------------ #
@app.errorhandler(HTTPException) @app.errorhandler(HTTPException)
def _http_error(error: HTTPException): # type: ignore[no-untyped-def] def _http_error(error: HTTPException): # type: ignore[no-untyped-def]
if error.code == 404:
# Vor der Fehlerseite: war das eine Adresse aus der Zeit vor 2.5.0?
# Erst hier gefragt, damit die Tabelle nie eine bestehende Route
# verdeckt -- auf dem Lizenzserver ist /lizenz/<ticket> echt.
ziel = legacy_urls.neue_adresse(request.path)
if ziel is not None:
parameter = legacy_urls.neue_parameter(request.args.to_dict(flat=True))
if parameter:
ziel = ziel + "?" + urlencode(parameter)
return redirect(ziel)
if error.code == 413: if error.code == 413:
# "Request Entity Too Large" allein hilft niemandem weiter: die # "Request Entity Too Large" allein hilft niemandem weiter: die
# Zahl steht in einer Einstellung, und wer hochlaedt, soll sie # Zahl steht in einer Einstellung, und wer hochlaedt, soll sie
@@ -0,0 +1,242 @@
"""Alte, deutsche Adressen auf die neuen englischen umlenken.
Mit 2.5.0 folgen die Adressen der Navigation und sind englisch:
``/monitoring/host-keys`` statt ``/hostschluessel``. Das ist eine gute
Aenderung und eine unangenehme zugleich -- Lesezeichen, offene Browsertabs und
ein Link in einer Mail zeigen weiter auf die alte Adresse. Ein offener Tab ist
dabei der unangenehmste Fall: die Statusuebersicht fragt alle 20 Sekunden ihre
Kacheln ab und bekam nach dem Update jedes Mal einen 404.
Deshalb diese Tabelle. Sie greift **nur, wenn sonst ein 404 herauskaeme** --
also nie vor einer bestehenden Route. Das ist keine Feinheit: auf dem
Lizenzserver ist ``/lizenz/<ticket>`` eine echte, weiterhin gueltige Adresse
(sie steht in verschickten Mails), waehrend dieselbe Wurzel in TESM zur
Lizenzseite fuehrte. Wer zuerst das Routing fragt, muss sich nicht zwischen
beidem entscheiden.
Die Tabelle ist bewusst vollstaendig und nicht nur auf Wurzeln gekuerzt: eine
Umleitung von ``/dateifreigaben/x/vorschau`` auf eine Uebersichtsseite waere
zwar kein 404 mehr, aber auch nicht das, was jemand angeklickt hat.
"""
from __future__ import annotations
from typing import Mapping
#: Alte Wurzel -> (neue Wurzel, Uebersetzung der Segmente darunter).
WURZELN: dict[str, tuple[str, Mapping[str, str]]] = {
"/dateifreigaben": (
"/fileshares",
{
"herunterladen": "download",
"vorschau": "preview",
"hochladen": "upload",
"formular": "form",
"ordner": "folder",
"umbenennen": "rename",
"loeschen": "delete",
"freigeben": "release",
"zuordnungen": "mappings",
},
),
"/hostschluessel": (
"/monitoring/host-keys",
{"pruefen": "probe", "freigeben": "trust", "verwerfen": "forget", "verwaist": "orphaned"},
),
"/zugangsdaten": (
"/monitoring/credentials",
{"neu": "new", "loeschen": "delete", "anzeigen": "reveal"},
),
"/switche": (
"/monitoring/switches",
{
"neu": "new",
"loeschen": "delete",
"hostkey": "host-key",
"pruefen": "probe",
"freigeben": "trust",
"verwerfen": "forget",
"kommando": "command",
"konsole": "console",
},
),
"/clients": (
"/monitoring/clients",
{
"neu": "new",
"aktiv": "active",
"loeschen": "delete",
"neustart": "restart",
"hostkey": "host-key",
"pruefen": "probe",
"freigeben": "trust",
"verwerfen": "forget",
"konsole": "console",
},
),
"/wartung": (
"/maintenance",
{
"paket-nachinstallieren": "install-package",
"auftrag": "job",
"ausgabe": "output",
"aktualisieren": "update",
"neustarten": "restart",
},
),
"/protokolle": (
"/logs",
{
"kea": "dhcp",
"neustarts": "restarts",
"laufend": "live",
"inhalt": "content",
"verlauf": "history",
"datei": "file",
"archiv": "archive",
"archiv-export": "archive-export",
"archiv-loeschen": "archive-delete",
"aenderungen": "audit",
},
),
"/verwaltung": (
"/administration",
{
"benutzer": "users",
"neu": "new",
"loeschen": "delete",
"sitzungen-beenden": "end-sessions",
"verzeichnis-suche": "directory-search",
"verzeichnis-uebernehmen": "directory-import",
"gruppen": "groups",
"vorlage-anwenden": "apply-preset",
"sitzungen": "sessions",
"beenden": "end",
},
),
"/einstellungen": (
"/administration",
{
"zeitzone": "timezone",
"pruefung-jetzt": "check-now",
"netzwerk": "network",
"bestaetigen": "confirm",
"webserver": "web",
"uebernehmen": "apply",
"zertifikat": "certificate",
"selbstsigniert": "self-signed",
"verzeichnisdienst": "directory",
"gruppensuche": "group-search",
"zuordnung": "mapping",
"loeschen": "delete",
"anbieter": "vendor",
"logo-entfernen": "remove-logo",
},
),
"/sicherung": (
"/administration/backup",
{"vorschau": "preview", "uebernehmen": "apply", "verwerfen": "discard"},
),
"/papierkorb": (
"/administration/trash",
{"wiederherstellen": "restore", "endgueltig-loeschen": "purge", "leeren": "empty"},
),
"/diagnose": (
"/administration/diagnostics",
{"aufraeumen": "cleanup", "schluessel-rotieren": "rotate-keys"},
),
"/eigene-lizenz": (
"/own-license",
{
"einspielen": "install",
"aktivieren": "activate",
"deaktivieren": "deactivate",
"offline-antwort": "offline-response",
"signaturschluessel": "signing-key",
},
),
"/lizenzen": (
"/licenses",
{
"aus-ticket": "from-ticket",
"datei": "file",
"neu-signieren": "resign",
"widerrufen": "revoke",
"loeschen": "delete",
"versenden": "send",
"sub-lizenzserver": "sub-servers",
},
),
"/lizenz": (
"/administration/license",
{
"einspielen": "install",
"aktivieren": "activate",
"deaktivieren": "deactivate",
"offline-antwort": "offline-response",
"serverzertifikat": "server-certificate",
"aussteller": "issuer",
"entfernen": "remove",
},
),
"/kunden": ("/customers", {"neu": "new", "loeschen": "delete"}),
"/konto": (
"/account",
{
"stammdaten": "profile",
"profilbild": "avatar",
"sicherheit": "security",
"passwort": "password",
"zwei-faktor": "two-factor",
"starten": "start",
"bestaetigen": "confirm",
"deaktivieren": "disable",
"codes-erneuern": "recovery-codes",
"sitzungen": "sessions",
"beenden": "end",
"alle-beenden": "end-all",
},
),
"/uebersicht": ("/overview", {"kacheln": "tiles"}),
"/gesundheit": ("/health", {}),
"/bestaetigen": ("/confirm", {}),
}
#: Abfrageparameter, die mit umbenannt wurden.
PARAMETER = {
"freigabe": "share",
"pfad": "path",
"aktion": "action",
"benutzer": "actor",
"stufe": "severity",
"seite": "page",
"methode": "method",
"kunde": "customer",
"freischalten": "unlocked",
}
#: Wurzeln, unter denen eine unbekannte Unterseite **nicht** umgelenkt wird.
#: ``/lizenz/<ticket>`` ist auf dem Lizenzserver eine echte Adresse; ein
#: abgelaufenes oder falsches Ticket gehoert dort auf die Fehlerseite und nicht
#: in die Verwaltung.
STRENG = {"/lizenz"}
def neue_adresse(pfad: str) -> str | None:
"""Uebersetzt eine alte Adresse -- oder ``None``, wenn es keine ist."""
for alt, (neu, segmente) in WURZELN.items():
if pfad != alt and not pfad.startswith(alt + "/"):
continue
rest = pfad[len(alt) :]
teile = rest.split("/")
if alt in STRENG:
erstes = next((teil for teil in teile if teil), "")
if erstes and erstes not in segmente:
return None
return neu + "/".join(segmente.get(teil, teil) for teil in teile)
return None
def neue_parameter(args: Mapping[str, str]) -> dict[str, str]:
return {PARAMETER.get(name, name): wert for name, wert in args.items()}
@@ -77,7 +77,7 @@ SETTINGS_NETWORK = Resource(
BACKUP = Resource( BACKUP = Resource(
key="backup", key="backup",
label="Sicherung und Uebertragung", label="Sicherung und Übertragung",
actions=(A.VIEW, A.EXECUTE, A.EXPORT, A.SECRETS), actions=(A.VIEW, A.EXECUTE, A.EXPORT, A.SECRETS),
hint="Verschluesselter Export und Import", hint="Verschluesselter Export und Import",
action_hints={ action_hints={
@@ -121,7 +121,7 @@ LOGS_HISTORY = Resource(
LOGS_AUDIT = Resource( LOGS_AUDIT = Resource(
key="logs_audit", key="logs_audit",
label="Aenderungsprotokoll", label="Änderungsprotokoll",
actions=(A.VIEW, A.EXPORT), actions=(A.VIEW, A.EXPORT),
hint="Wer hat wann was geaendert", hint="Wer hat wann was geaendert",
) )
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "tesm-licensing-server" name = "tesm-licensing-server"
version = "2.5.0" version = "2.5.1"
description = "Serverhaelfte des TESM-Lizenzprotokolls: ausstellen, erneuern, antworten" description = "Serverhaelfte des TESM-Lizenzprotokolls: ausstellen, erneuern, antworten"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = ["tesm-licensing==2.5.0", "cryptography>=41"] dependencies = ["tesm-licensing==2.5.1", "cryptography>=41"]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["src"] where = ["src"]
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "tesm-licensing" name = "tesm-licensing"
version = "2.5.0" version = "2.5.1"
description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver" description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = ["cryptography>=42"] dependencies = ["cryptography>=42"]