Lizenzserver 2.8.15: auch eine unbefristete Lizenz laesst sich zurueckholen

This commit is contained in:
alientim committed 2026-09-16 22:40:32 +02:00
1 parent 34a4923304
commit 0024a116ce
4 files changed
+85 -20

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tesm-license"
version = "2.8.14"
version = "2.8.15"
description = "TESM-Lizenzserver -- Kunden, Tickets, Lizenzen, Aktivierung"
requires-python = ">=3.11"
dependencies = [
@@ -78,9 +78,11 @@ def detail(license_id: str):
"license/license_detail.html",
license=row,
state=issuance.display_state(row),
# Bis wohin traegt das Ticket? Die Seite zeigt eine Abweichung an,
# statt sie erst in einer Fehlermeldung auftauchen zu lassen.
# Bis wohin traegt das Ticket, und weicht die Lizenz davon ab? Die
# Seite zeigt es an, statt es erst in einer Fehlermeldung auftauchen zu
# lassen.
ticket_grenze=issuance.ticket_grenze(conn, license_id),
abweichung=issuance.ticket_abweichung(conn, license_id),
document=document,
document_json=json.dumps(document, indent=2, ensure_ascii=False, sort_keys=True),
deliveries=Database.all(
@@ -214,8 +216,13 @@ def align_ticket(license_id: str):
log_event(
"license.resigned",
target=license_id[:12],
detail=f"Revision {neu['seq']}: Laufzeit auf die Ticketlaufzeit gesetzt",
severity="warning",
detail=(
f"Revision {neu['seq']}: Laufzeit auf die Ticketlaufzeit gesetzt "
f"({'unbefristet' if neu.get('lifetime') else neu.get('expires_at')})"
),
# Was etwas wegnimmt, steht als Warnung im Protokoll -- was etwas gibt,
# als Notiz. Beides ist derselbe Knopf, aber nicht dasselbe Ereignis.
severity="notice" if neu.get("lifetime") else "warning",
)
reporting.report_soon(core())
flash(
@@ -893,6 +893,54 @@ def lizenz_dem_ticket_nachziehen(
)
def ticket_abweichung(conn: sqlite3.Connection, license_id: str) -> dict[str, Any] | None:
"""Weicht die Laufzeit dieser Lizenz von ihrem Ticket ab? Sonst ``None``.
Vier Faelle, und sie sind nicht symmetrisch:
=================== =================== ====================================
Ticket Lizenz Abgleich
=================== =================== ====================================
unbefristet unbefristet nichts zu tun
unbefristet befristet wird unbefristet -- ein Gewinn
befristet unbefristet wird befristet -- eine **Ruecknahme**
befristet befristet auf den Tag des Tickets
=================== =================== ====================================
``verkuerzt`` sagt, ob der Abgleich etwas wegnimmt. Die Oberflaeche faerbt
danach ihre Rueckfrage, und das Aenderungsprotokoll seine Stufe: eine
unbefristete Zusage zu befristen ist die schaerfste Form davon.
Testlizenzen bleiben aussen vor -- sie leben nach eigenen Regeln.
"""
row = get_license(conn, license_id)
if row is None or not row["ticket_id"] or str(row["type"]) == "trial":
return None
ticket = get_ticket(conn, str(row["ticket_id"]))
if ticket is None:
return None
ticket_unbefristet = bool(ticket["lifetime"])
lizenz_unbefristet = bool(row["lifetime"])
if ticket_unbefristet and lizenz_unbefristet:
return None
if ticket_unbefristet:
return {"grenze": None, "unbefristet": True, "verkuerzt": False}
grenze = ticket_grenze(conn, license_id)
if grenze is None:
return None
if not lizenz_unbefristet and str(row["expires_at"]) == grenze:
return None
return {
"grenze": grenze,
"unbefristet": False,
# Unbefristet zu befristen nimmt immer etwas weg; sonst entscheidet
# das Datum.
"verkuerzt": lizenz_unbefristet or grenze < str(row["expires_at"]),
}
def lizenz_auf_ticket_setzen(
conn: sqlite3.Connection, license_id: str, *, master_private_key: str
) -> dict[str, Any] | None:
@@ -913,21 +961,27 @@ def lizenz_auf_ticket_setzen(
row = get_license(conn, license_id)
if row is None:
raise IssuanceError("Diese Lizenz existiert nicht.")
if bool(row["lifetime"]):
raise IssuanceError("Diese Lizenz ist unbefristet.")
grenze = ticket_grenze(conn, license_id)
if grenze is None:
raise IssuanceError(
"Zu dieser Lizenz gibt es keine begrenzte Ticketlaufzeit, an der sie "
"auszurichten wäre."
)
if str(row["expires_at"]) == grenze:
abweichung = ticket_abweichung(conn, license_id)
if abweichung is None:
return None
if abweichung["unbefristet"]:
# Das Ticket gewaehrt unbefristet, die Lizenz laeuft ab. Der Abgleich
# gibt etwas -- hier ist nichts zurueckzunehmen.
changes: dict[str, Any] = {"lifetime": True}
else:
# Auch aus einer unbefristeten Lizenz wird hier wieder eine befristete.
# Das ist die schaerfste Ruecknahme, die dieses System kennt, und
# genau deshalb hat sie einen eigenen Knopf mit eigener Rueckfrage --
# statt unmoeglich zu sein. Unmoeglich hiesse: eine unbefristete
# Zusage, die versehentlich entstanden ist, bleibt fuer immer stehen.
changes = {"lifetime": False, "expires_at": abweichung["grenze"]}
return resign_license(
conn,
license_id,
master_private_key=master_private_key,
changes={"expires_at": grenze},
changes=changes,
ueber_ticket=True,
)
@@ -141,14 +141,18 @@
{# Anspruch und Urkunde wieder zur Deckung bringen. In beide Richtungen --
und wenn es verkürzt, ist das eine Rücknahme von Laufzeit: deshalb mit
Rückfrage und eigener Zeile im Änderungsprotokoll. #}
{% if ticket_grenze and ticket_grenze != license.expires_at %}
{% if abweichung %}
<section class="card">
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('shield', size=17) }} Laufzeit weicht vom Ticket ab</div>
<div class="card__sub">
Diese Lizenz läuft bis <strong>{{ license.expires_at | dt }}</strong>, das Ticket
gewährt bis <strong>{{ ticket_grenze | dt }}</strong>.
Diese Lizenz läuft
{% if license.lifetime %}<strong>unbefristet</strong>
{% else %}bis <strong>{{ license.expires_at | dt }}</strong>{% endif %},
das Ticket gewährt
{% if abweichung.unbefristet %}<strong>unbefristet</strong>
{% else %}bis <strong>{{ abweichung.grenze | dt }}</strong>{% endif %}.
</div>
</div>
</div>
@@ -157,10 +161,10 @@
<input type="hidden" name="{{ csrf_field_name }}" value="{{ csrf_token() }}">
<div class="card__footer">
<button class="btn" type="submit"
data-confirm="Die Laufzeit dieser Lizenz auf {{ ticket_grenze | dt }} setzen? {% if ticket_grenze < license.expires_at %}Das nimmt bereits zugesagte Laufzeit zurück -- der Client übernimmt es beim nächsten Heartbeat.{% endif %}"
data-confirm="Die Laufzeit dieser Lizenz auf {% if abweichung.unbefristet %}unbefristet{% else %}{{ abweichung.grenze | dt }}{% endif %} setzen?{% if abweichung.verkuerzt %} Das nimmt bereits zugesagte Laufzeit zurück{% if license.lifetime %} -- aus einer unbefristeten Lizenz wird eine befristete{% endif %}. Der Client übernimmt es beim nächsten Heartbeat.{% endif %}"
data-confirm-title="Auf die Ticketlaufzeit setzen"
data-confirm-ok="Setzen"
data-confirm-style="{{ 'danger' if ticket_grenze < license.expires_at else 'primary' }}">
data-confirm-style="{{ 'danger' if abweichung.verkuerzt else 'primary' }}">
Auf die Ticketlaufzeit setzen
</button>
</div>