|
|
|
@@ -212,6 +212,10 @@ PERMISSIONS = {
|
|
|
|
|
"label": "Kunden",
|
|
|
|
|
"rows": {"view": "customers.view", "create": "customers.create", "edit": "customers.edit"},
|
|
|
|
|
},
|
|
|
|
|
"tickets": {
|
|
|
|
|
"label": "Tickets",
|
|
|
|
|
"rows": {"view": "tickets.view", "create": "tickets.create", "edit": "tickets.edit"},
|
|
|
|
|
},
|
|
|
|
|
"licenses": {
|
|
|
|
|
"label": "Lizenzen",
|
|
|
|
|
"rows": {"view": "licenses.view", "create": "licenses.create", "edit": "licenses.edit"},
|
|
|
|
@@ -279,6 +283,8 @@ PERMISSION_LABELS = {
|
|
|
|
|
"licensing_group.view": "Lizenzierung-Bereich anzeigen",
|
|
|
|
|
"customers.view": "Kunden lesen", "customers.create": "Kunden anlegen",
|
|
|
|
|
"customers.edit": "Kunden ändern (inkl. Löschen)",
|
|
|
|
|
"tickets.view": "Tickets lesen", "tickets.create": "Tickets anlegen",
|
|
|
|
|
"tickets.edit": "Tickets ändern (Eckdaten, Sperren/Entsperren, Löschen)",
|
|
|
|
|
"licenses.view": "Lizenzen und Aktivierungsstatus lesen",
|
|
|
|
|
"licenses.create": "Lizenzen ausstellen",
|
|
|
|
|
"licenses.edit": "Lizenzen ändern (Deaktivieren/Widerrufen, erneut versenden)",
|
|
|
|
@@ -355,7 +361,7 @@ def _nav_key_visible(key, user):
|
|
|
|
|
if key == "index":
|
|
|
|
|
return True
|
|
|
|
|
if key == "tickets":
|
|
|
|
|
return user.has_permission("licenses.view")
|
|
|
|
|
return user.has_permission("tickets.view")
|
|
|
|
|
if key == "licenses":
|
|
|
|
|
return user.has_permission("licenses.view")
|
|
|
|
|
if key == "customers":
|
|
|
|
@@ -544,6 +550,12 @@ class User(UserMixin):
|
|
|
|
|
["customers.view", "customers.create", "customers.edit"]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def can_manage_tickets(self):
|
|
|
|
|
return self.has_any_permission(
|
|
|
|
|
["tickets.view", "tickets.create", "tickets.edit"]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def can_manage_licenses(self):
|
|
|
|
|
return self.has_any_permission(
|
|
|
|
@@ -914,6 +926,38 @@ def _ensure_schema():
|
|
|
|
|
"INSERT OR IGNORE INTO settings (key, value) VALUES (?, '1')", (_migration_key_license_perm,)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_migration_key_tickets_perm = "_migrated_tickets_permission_v1"
|
|
|
|
|
if not conn.execute("SELECT 1 FROM settings WHERE key=?", (_migration_key_tickets_perm,)).fetchone():
|
|
|
|
|
# "Tickets" hatte bislang KEIN eigenes Recht -- Ticketverwaltung
|
|
|
|
|
# (anlegen/bearbeiten/sperren/loeschen) lief komplett ueber
|
|
|
|
|
# licenses.view/.create/.edit mit, obwohl Tickets ein eigener,
|
|
|
|
|
# gleichberechtigter Navigationspunkt neben Kunden und Lizenzen
|
|
|
|
|
# ist (genau wie bei TESM Clients/Switche/Zugangsdaten trotz enger
|
|
|
|
|
# fachlicher Verwandtschaft jeweils eigene Rechte haben). Backfill:
|
|
|
|
|
# jede Gruppe mit licenses.view/.create/.edit bekommt automatisch
|
|
|
|
|
# das gespiegelte tickets.view/.create/.edit dazu -- das
|
|
|
|
|
# Ausstellen einer Lizenz AUS einem Ticket heraus bleibt bewusst
|
|
|
|
|
# weiterhin an licenses.edit gebunden (siehe ticket_detail()),
|
|
|
|
|
# nur Sperren/Entsperren/Bearbeiten/Loeschen des Tickets selbst
|
|
|
|
|
# wandert auf das neue, unabhaengige Recht.
|
|
|
|
|
_license_to_ticket = {
|
|
|
|
|
"licenses.view": "tickets.view",
|
|
|
|
|
"licenses.create": "tickets.create",
|
|
|
|
|
"licenses.edit": "tickets.edit",
|
|
|
|
|
}
|
|
|
|
|
for license_perm, ticket_perm in _license_to_ticket.items():
|
|
|
|
|
rows = conn.execute(
|
|
|
|
|
"SELECT DISTINCT group_id FROM group_permissions WHERE permission=?", (license_perm,)
|
|
|
|
|
).fetchall()
|
|
|
|
|
for row in rows:
|
|
|
|
|
conn.execute(
|
|
|
|
|
"INSERT OR IGNORE INTO group_permissions (group_id, permission) VALUES (?, ?)",
|
|
|
|
|
(row["group_id"], ticket_perm),
|
|
|
|
|
)
|
|
|
|
|
conn.execute(
|
|
|
|
|
"INSERT OR IGNORE INTO settings (key, value) VALUES (?, '1')", (_migration_key_tickets_perm,)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_migration_key_old_logs = "_cleaned_up_legacy_rpi_log_files_v1"
|
|
|
|
|
if not conn.execute("SELECT 1 FROM settings WHERE key=?", (_migration_key_old_logs,)).fetchone():
|
|
|
|
|
for stale_log in glob.glob("/var/log/rpi-*.log"):
|
|
|
|
@@ -2565,7 +2609,7 @@ def ticket_new():
|
|
|
|
|
Lizenz mit bestimmten Eckdaten. Erstellt bewusst noch KEINE Lizenz;
|
|
|
|
|
das übernimmt ein separater Schritt auf der Ticket-Detailseite (durch
|
|
|
|
|
Admin ODER Kunde per Self-Service)."""
|
|
|
|
|
if not current_user.has_permission("licenses.create"):
|
|
|
|
|
if not current_user.has_permission("tickets.create"):
|
|
|
|
|
flash("Keine Berechtigung, Tickets anzulegen.", "danger")
|
|
|
|
|
return redirect(url_for("index"))
|
|
|
|
|
conn = get_db_connection()
|
|
|
|
@@ -2636,7 +2680,7 @@ def ticket_new():
|
|
|
|
|
@app.route("/tickets/<ticket_id>", methods=["GET", "POST"])
|
|
|
|
|
@login_required
|
|
|
|
|
def ticket_detail(ticket_id):
|
|
|
|
|
if not current_user.can_manage_licenses:
|
|
|
|
|
if not current_user.can_manage_tickets:
|
|
|
|
|
flash("Keine Berechtigung.", "danger")
|
|
|
|
|
return redirect(url_for("index"))
|
|
|
|
|
conn = get_db_connection()
|
|
|
|
@@ -2647,10 +2691,14 @@ def ticket_detail(ticket_id):
|
|
|
|
|
return redirect(url_for("index"))
|
|
|
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
|
if "issue_license" in request.form:
|
|
|
|
|
# Ausstellen erzeugt eine LIZENZ (nicht nur eine Ticket-Aenderung)
|
|
|
|
|
# -- bewusst weiterhin ueber das Lizenz-Recht gegated, nicht ueber
|
|
|
|
|
# tickets.edit, damit sich "Tickets verwalten" und "Lizenzen
|
|
|
|
|
# ausstellen" unabhaengig voneinander vergeben lassen.
|
|
|
|
|
if not current_user.has_permission("licenses.edit"):
|
|
|
|
|
flash("Keine Berechtigung.", "danger")
|
|
|
|
|
elif "issue_license" in request.form:
|
|
|
|
|
if not license_active():
|
|
|
|
|
flash("Keine Berechtigung, Lizenzen auszustellen.", "danger")
|
|
|
|
|
elif not license_active():
|
|
|
|
|
flash("Der Lizenzserver selbst hat keine gültige Lizenz -- Ausstellen ist deaktiviert.", "danger")
|
|
|
|
|
elif ticket["status"] != "open":
|
|
|
|
|
flash("Ticket ist gesperrt.", "danger")
|
|
|
|
@@ -2663,6 +2711,9 @@ def ticket_detail(ticket_id):
|
|
|
|
|
flash("Lizenz ausgestellt.", "success")
|
|
|
|
|
return redirect(url_for("license_detail", license_id=license_file["license_id"]))
|
|
|
|
|
elif "toggle_block" in request.form:
|
|
|
|
|
if not current_user.has_permission("tickets.edit"):
|
|
|
|
|
flash("Keine Berechtigung, Tickets zu sperren/entsperren.", "danger")
|
|
|
|
|
else:
|
|
|
|
|
new_status = "blocked" if ticket["status"] == "open" else "open"
|
|
|
|
|
conn.execute("UPDATE license_tickets SET status=? WHERE id=?", (new_status, ticket_id))
|
|
|
|
|
conn.commit()
|
|
|
|
@@ -2691,7 +2742,8 @@ def ticket_detail(ticket_id):
|
|
|
|
|
master_licensed=license_active(),
|
|
|
|
|
self_service_url=_customer_facing_url("self_service", ticket_id=ticket_id),
|
|
|
|
|
portal_request_url=_customer_facing_url("portal_request"),
|
|
|
|
|
can_edit=current_user.has_permission("licenses.edit"),
|
|
|
|
|
can_edit_ticket=current_user.has_permission("tickets.edit"),
|
|
|
|
|
can_issue_license=current_user.has_permission("licenses.edit"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -2707,7 +2759,7 @@ def ticket_edit(ticket_id):
|
|
|
|
|
hier bewusst nicht vorgesehen (das Self-Service-Ticket ist an genau
|
|
|
|
|
diesen Kunden gebunden, u.a. weil er ueber dessen Portal-Token
|
|
|
|
|
erreichbar ist)."""
|
|
|
|
|
if not current_user.has_permission("licenses.edit"):
|
|
|
|
|
if not current_user.has_permission("tickets.edit"):
|
|
|
|
|
flash("Keine Berechtigung, Tickets zu bearbeiten.", "danger")
|
|
|
|
|
return redirect(url_for("tickets_list"))
|
|
|
|
|
conn = get_db_connection()
|
|
|
|
@@ -2760,7 +2812,7 @@ def ticket_delete(ticket_id):
|
|
|
|
|
System verschwinden). Etwaige bereits widerrufene/deaktivierte
|
|
|
|
|
Lizenzen darunter sind reine Historie und werden mitgeloescht, sonst
|
|
|
|
|
blieben verwaiste Zeilen mit einer toten ticket_id zurueck."""
|
|
|
|
|
if not current_user.has_permission("licenses.edit"):
|
|
|
|
|
if not current_user.has_permission("tickets.edit"):
|
|
|
|
|
flash("Keine Berechtigung, Tickets zu löschen.", "danger")
|
|
|
|
|
return redirect(url_for("tickets_list"))
|
|
|
|
|
conn = get_db_connection()
|
|
|
|
@@ -3712,7 +3764,7 @@ def tickets_list():
|
|
|
|
|
Dashboards auf eine rein informative Kacheluebersicht (siehe index())
|
|
|
|
|
lebt die eigentliche Verwaltung hier, eigenstaendig ueber die
|
|
|
|
|
Navigation erreichbar."""
|
|
|
|
|
if not current_user.has_permission("licenses.view"):
|
|
|
|
|
if not current_user.has_permission("tickets.view"):
|
|
|
|
|
flash("Keine Berechtigung, Tickets anzusehen.", "danger")
|
|
|
|
|
return redirect(url_for("index"))
|
|
|
|
|
conn = get_db_connection()
|
|
|
|
@@ -3745,8 +3797,8 @@ def tickets_list():
|
|
|
|
|
return render_template(
|
|
|
|
|
"tickets.html", tickets=tickets_view, type_labels=LICENSE_TYPE_LABELS, module_labels=MODULE_LABELS,
|
|
|
|
|
all_modules=licensing.ALL_MODULES, license_types=licensing.LICENSE_TYPES,
|
|
|
|
|
can_create=current_user.has_permission("licenses.create"),
|
|
|
|
|
can_edit=current_user.has_permission("licenses.edit"),
|
|
|
|
|
can_create=current_user.has_permission("tickets.create"),
|
|
|
|
|
can_edit=current_user.has_permission("tickets.edit"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|