|
|
@@ -251,6 +251,10 @@ PERMISSIONS = {
|
|
|
|
"label": "Systemeinstellungen",
|
|
|
|
"label": "Systemeinstellungen",
|
|
|
|
"rows": {"view": "settings_system.view", "edit": "settings_system.edit"},
|
|
|
|
"rows": {"view": "settings_system.view", "edit": "settings_system.edit"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"settings_license": {
|
|
|
|
|
|
|
|
"label": "Lizenz",
|
|
|
|
|
|
|
|
"rows": {"view": "settings_license.view", "edit": "settings_license.edit"},
|
|
|
|
|
|
|
|
},
|
|
|
|
"settings_importexport": {
|
|
|
|
"settings_importexport": {
|
|
|
|
"label": "Im-/Export",
|
|
|
|
"label": "Im-/Export",
|
|
|
|
"rows": {
|
|
|
|
"rows": {
|
|
|
@@ -288,6 +292,8 @@ PERMISSION_LABELS = {
|
|
|
|
"groups.view": "Gruppen lesen", "groups.create": "Gruppen anlegen",
|
|
|
|
"groups.view": "Gruppen lesen", "groups.create": "Gruppen anlegen",
|
|
|
|
"groups.edit": "Gruppen ändern (inkl. Löschen)",
|
|
|
|
"groups.edit": "Gruppen ändern (inkl. Löschen)",
|
|
|
|
"settings_system.view": "Systemeinstellungen lesen", "settings_system.edit": "Systemeinstellungen ändern",
|
|
|
|
"settings_system.view": "Systemeinstellungen lesen", "settings_system.edit": "Systemeinstellungen ändern",
|
|
|
|
|
|
|
|
"settings_license.view": "Lizenzstatus ansehen",
|
|
|
|
|
|
|
|
"settings_license.edit": "Lizenzdatei hochladen sowie Lizenz aktivieren/deaktivieren (online oder per Code)",
|
|
|
|
"settings_importexport.view": "Im-/Export-Seite ansehen",
|
|
|
|
"settings_importexport.view": "Im-/Export-Seite ansehen",
|
|
|
|
"settings_importexport.edit": "Daten importieren",
|
|
|
|
"settings_importexport.edit": "Daten importieren",
|
|
|
|
"settings_importexport.export": "Daten exportieren (Export-Datei enthält Passwörter im Klartext)",
|
|
|
|
"settings_importexport.export": "Daten exportieren (Export-Datei enthält Passwörter im Klartext)",
|
|
|
@@ -361,10 +367,11 @@ def _nav_key_visible(key, user):
|
|
|
|
if key == "settings_system":
|
|
|
|
if key == "settings_system":
|
|
|
|
return user.can_view_settings_system
|
|
|
|
return user.can_view_settings_system
|
|
|
|
if key == "settings_license":
|
|
|
|
if key == "settings_license":
|
|
|
|
# Immer erreichbar für jeden mit Systemeinstellungen-Leserecht --
|
|
|
|
# Eigenes Recht (settings_license.view), unabhängig von
|
|
|
|
# muss auch OHNE Lizenz sichtbar bleiben, sonst könnte niemand
|
|
|
|
# settings_system -- muss auch OHNE eine aktive Lizenz sichtbar
|
|
|
|
# jemals eine erste Lizenz hochladen/aktivieren.
|
|
|
|
# bleiben (das ist eine andere Prüfung, siehe license_active()),
|
|
|
|
return user.can_view_settings_system
|
|
|
|
# sonst könnte niemand jemals eine erste Lizenz hochladen/aktivieren.
|
|
|
|
|
|
|
|
return user.can_view_settings_license
|
|
|
|
if key == "settings_importexport":
|
|
|
|
if key == "settings_importexport":
|
|
|
|
return user.can_view_settings_importexport
|
|
|
|
return user.can_view_settings_importexport
|
|
|
|
if key == "settings_ldap":
|
|
|
|
if key == "settings_ldap":
|
|
|
@@ -559,6 +566,10 @@ class User(UserMixin):
|
|
|
|
def can_view_settings_system(self):
|
|
|
|
def can_view_settings_system(self):
|
|
|
|
return self.has_permission("settings_system.view")
|
|
|
|
return self.has_permission("settings_system.view")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def can_view_settings_license(self):
|
|
|
|
|
|
|
|
return self.has_permission("settings_license.view")
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def can_view_settings_importexport(self):
|
|
|
|
def can_view_settings_importexport(self):
|
|
|
|
return self.has_any_permission([
|
|
|
|
return self.has_any_permission([
|
|
|
@@ -877,6 +888,32 @@ def _ensure_schema():
|
|
|
|
conn.execute("UPDATE licenses SET ticket_id=? WHERE license_id=?", (ticket_id, lic["license_id"]))
|
|
|
|
conn.execute("UPDATE licenses SET ticket_id=? WHERE license_id=?", (ticket_id, lic["license_id"]))
|
|
|
|
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, '1')", (_migration_key_tickets,))
|
|
|
|
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, '1')", (_migration_key_tickets,))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_migration_key_license_perm = "_migrated_settings_license_permission_v1"
|
|
|
|
|
|
|
|
if not conn.execute("SELECT 1 FROM settings WHERE key=?", (_migration_key_license_perm,)).fetchone():
|
|
|
|
|
|
|
|
# "Lizenz" (settings_license) hatte bislang KEIN eigenes Recht --
|
|
|
|
|
|
|
|
# die Seite lief komplett über settings_system.view/.edit mit.
|
|
|
|
|
|
|
|
# Backfill: jede Gruppe, die settings_system.view/.edit bereits
|
|
|
|
|
|
|
|
# hatte, bekommt automatisch das neue, gespiegelte
|
|
|
|
|
|
|
|
# settings_license.view/.edit dazu -- ohne diesen Schritt würden
|
|
|
|
|
|
|
|
# bestehende Gruppen beim Update stillschweigend den Zugriff auf
|
|
|
|
|
|
|
|
# die Lizenzseite verlieren, den sie vorher implizit hatten.
|
|
|
|
|
|
|
|
_system_to_license = {
|
|
|
|
|
|
|
|
"settings_system.view": "settings_license.view",
|
|
|
|
|
|
|
|
"settings_system.edit": "settings_license.edit",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for system_perm, license_perm in _system_to_license.items():
|
|
|
|
|
|
|
|
rows = conn.execute(
|
|
|
|
|
|
|
|
"SELECT DISTINCT group_id FROM group_permissions WHERE permission=?", (system_perm,)
|
|
|
|
|
|
|
|
).fetchall()
|
|
|
|
|
|
|
|
for row in rows:
|
|
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
|
|
|
"INSERT OR IGNORE INTO group_permissions (group_id, permission) VALUES (?, ?)",
|
|
|
|
|
|
|
|
(row["group_id"], license_perm),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
|
|
|
"INSERT OR IGNORE INTO settings (key, value) VALUES (?, '1')", (_migration_key_license_perm,)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
_migration_key_old_logs = "_cleaned_up_legacy_rpi_log_files_v1"
|
|
|
|
_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():
|
|
|
|
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"):
|
|
|
|
for stale_log in glob.glob("/var/log/rpi-*.log"):
|
|
|
@@ -1977,6 +2014,23 @@ def _get_master_endpoint():
|
|
|
|
return get_setting("master_endpoint", "")
|
|
|
|
return get_setting("master_endpoint", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _customer_facing_url(endpoint, **values):
|
|
|
|
|
|
|
|
"""Baut eine externe URL fuer an KUNDEN gerichtete Magic-Links (Self-
|
|
|
|
|
|
|
|
Service, Kundenportal-Anfrage/-Zugang, Lizenz-per-E-Mail) auf Basis
|
|
|
|
|
|
|
|
des admin-gepflegten Lizenzserver-Endpunkts (siehe _get_master_endpoint(),
|
|
|
|
|
|
|
|
Einstellungen -> Lizenz -> Anbieter & Lizenzserver) -- NICHT per
|
|
|
|
|
|
|
|
url_for(..., _external=True), das stattdessen den Host-Header der
|
|
|
|
|
|
|
|
GERADE laufenden Anfrage verwendet und damit z.B. die interne IP
|
|
|
|
|
|
|
|
zeigt, ueber die zufaellig der Admin gerade eingeloggt ist, statt der
|
|
|
|
|
|
|
|
fuer Kunden eigentlich gedachten oeffentlichen Domain. Faellt auf die
|
|
|
|
|
|
|
|
normale request-basierte externe URL zurueck, wenn der Endpunkt noch
|
|
|
|
|
|
|
|
nicht konfiguriert ist (z.B. auf einer frischen Installation)."""
|
|
|
|
|
|
|
|
base = _get_master_endpoint().rstrip("/")
|
|
|
|
|
|
|
|
if not base:
|
|
|
|
|
|
|
|
return url_for(endpoint, _external=True, **values)
|
|
|
|
|
|
|
|
return base + url_for(endpoint, **values)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _customer_row(conn, customer_id):
|
|
|
|
def _customer_row(conn, customer_id):
|
|
|
|
return conn.execute("SELECT * FROM license_customers WHERE id=?", (customer_id,)).fetchone()
|
|
|
|
return conn.execute("SELECT * FROM license_customers WHERE id=?", (customer_id,)).fetchone()
|
|
|
|
|
|
|
|
|
|
|
@@ -2635,8 +2689,8 @@ def ticket_detail(ticket_id):
|
|
|
|
license_types=licensing.LICENSE_TYPES, all_modules=licensing.ALL_MODULES,
|
|
|
|
license_types=licensing.LICENSE_TYPES, all_modules=licensing.ALL_MODULES,
|
|
|
|
type_labels=LICENSE_TYPE_LABELS, module_labels=MODULE_LABELS,
|
|
|
|
type_labels=LICENSE_TYPE_LABELS, module_labels=MODULE_LABELS,
|
|
|
|
master_licensed=license_active(),
|
|
|
|
master_licensed=license_active(),
|
|
|
|
self_service_url=url_for("self_service", ticket_id=ticket_id, _external=True),
|
|
|
|
self_service_url=_customer_facing_url("self_service", ticket_id=ticket_id),
|
|
|
|
portal_request_url=url_for("portal_request", _external=True),
|
|
|
|
portal_request_url=_customer_facing_url("portal_request"),
|
|
|
|
can_edit=current_user.has_permission("licenses.edit"),
|
|
|
|
can_edit=current_user.has_permission("licenses.edit"),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
@@ -2876,12 +2930,12 @@ def license_send_email(license_id):
|
|
|
|
return redirect(url_for("license_detail", license_id=license_id))
|
|
|
|
return redirect(url_for("license_detail", license_id=license_id))
|
|
|
|
self_service_note = ""
|
|
|
|
self_service_note = ""
|
|
|
|
if row["ticket_id"]:
|
|
|
|
if row["ticket_id"]:
|
|
|
|
self_service_url = url_for("self_service", ticket_id=row["ticket_id"], _external=True)
|
|
|
|
self_service_url = _customer_facing_url("self_service", ticket_id=row["ticket_id"])
|
|
|
|
self_service_note = (
|
|
|
|
self_service_note = (
|
|
|
|
f"\nÜber diesen Link können Sie Ihre Lizenz jederzeit selbst herunterladen, "
|
|
|
|
f"\nÜber diesen Link können Sie Ihre Lizenz jederzeit selbst herunterladen, "
|
|
|
|
f"widerrufen oder (z.B. nach einem Systemwechsel) neu erstellen:\n{self_service_url}\n"
|
|
|
|
f"widerrufen oder (z.B. nach einem Systemwechsel) neu erstellen:\n{self_service_url}\n"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
portal_request_url = url_for("portal_request", _external=True)
|
|
|
|
portal_request_url = _customer_facing_url("portal_request")
|
|
|
|
self_service_note += (
|
|
|
|
self_service_note += (
|
|
|
|
f"\nEine Übersicht all Ihrer Tickets/Lizenzen können Sie sich jederzeit unter folgendem Link per "
|
|
|
|
f"\nEine Übersicht all Ihrer Tickets/Lizenzen können Sie sich jederzeit unter folgendem Link per "
|
|
|
|
f"E-Mail zuschicken lassen:\n{portal_request_url}\n"
|
|
|
|
f"E-Mail zuschicken lassen:\n{portal_request_url}\n"
|
|
|
@@ -3086,7 +3140,7 @@ def portal_request():
|
|
|
|
(now + timedelta(minutes=PORTAL_ACCESS_LINK_MINUTES)).strftime("%Y-%m-%d %H:%M:%S")),
|
|
|
|
(now + timedelta(minutes=PORTAL_ACCESS_LINK_MINUTES)).strftime("%Y-%m-%d %H:%M:%S")),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
conn.commit()
|
|
|
|
conn.commit()
|
|
|
|
access_url = url_for("portal_access", token=grant_token, _external=True)
|
|
|
|
access_url = _customer_facing_url("portal_access", token=grant_token)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
_graph_send_mail(
|
|
|
|
_graph_send_mail(
|
|
|
|
customer["contact_email"],
|
|
|
|
customer["contact_email"],
|
|
|
@@ -3604,6 +3658,7 @@ def inject_license_topbar():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/")
|
|
|
|
@app.route("/")
|
|
|
|
|
|
|
|
@login_required
|
|
|
|
def index():
|
|
|
|
def index():
|
|
|
|
"""Dashboard des Master-Lizenzservers -- eine rein informative Kachel-
|
|
|
|
"""Dashboard des Master-Lizenzservers -- eine rein informative Kachel-
|
|
|
|
uebersicht der gerade AKTIVEN Lizenzen (analog zur Geraete-Kachel-
|
|
|
|
uebersicht der gerade AKTIVEN Lizenzen (analog zur Geraete-Kachel-
|
|
|
@@ -3613,8 +3668,15 @@ def index():
|
|
|
|
mit status='active' -- alles andere (Tickets anlegen/bearbeiten/
|
|
|
|
mit status='active' -- alles andere (Tickets anlegen/bearbeiten/
|
|
|
|
loeschen, Lizenzen ausstellen/widerrufen/loeschen) ist Verwaltung und
|
|
|
|
loeschen, Lizenzen ausstellen/widerrufen/loeschen) ist Verwaltung und
|
|
|
|
lebt seit der Kachel-Umstellung auf der eigenen Tickets-Seite
|
|
|
|
lebt seit der Kachel-Umstellung auf der eigenen Tickets-Seite
|
|
|
|
(tickets_list())."""
|
|
|
|
(tickets_list()).
|
|
|
|
if not current_user.is_authenticated or not current_user.has_permission("licenses.view"):
|
|
|
|
|
|
|
|
|
|
|
|
@login_required (anders als bei TESM selbst, wo das Geraete-Dashboard
|
|
|
|
|
|
|
|
bewusst auch ohne Login als reines Status-Board sichtbar bleibt): der
|
|
|
|
|
|
|
|
Lizenzserver ist reines internes Verwaltungswerkzeug -- ein anonymer
|
|
|
|
|
|
|
|
Besuch auf '/' soll immer zuerst auf die Login-Seite treffen (via
|
|
|
|
|
|
|
|
login_manager.login_view), nicht auf einen 'bitte anmelden'-Platzhalter
|
|
|
|
|
|
|
|
innerhalb der Dashboard-Huelle."""
|
|
|
|
|
|
|
|
if not current_user.has_permission("licenses.view"):
|
|
|
|
return render_template("index.html", buckets=None)
|
|
|
|
return render_template("index.html", buckets=None)
|
|
|
|
conn = get_db_connection()
|
|
|
|
conn = get_db_connection()
|
|
|
|
active_rows = conn.execute("""
|
|
|
|
active_rows = conn.execute("""
|
|
|
@@ -4674,7 +4736,7 @@ def settings():
|
|
|
|
@app.route("/settings/license")
|
|
|
|
@app.route("/settings/license")
|
|
|
|
@login_required
|
|
|
|
@login_required
|
|
|
|
def settings_license():
|
|
|
|
def settings_license():
|
|
|
|
if not current_user.can_view_settings_system:
|
|
|
|
if not current_user.can_view_settings_license:
|
|
|
|
flash("Keine Berechtigung, die Lizenz anzusehen.", "danger")
|
|
|
|
flash("Keine Berechtigung, die Lizenz anzusehen.", "danger")
|
|
|
|
return redirect(url_for("index"))
|
|
|
|
return redirect(url_for("index"))
|
|
|
|
pending_raw = get_setting("license_pending_request", "")
|
|
|
|
pending_raw = get_setting("license_pending_request", "")
|
|
|
@@ -4697,7 +4759,7 @@ def settings_license():
|
|
|
|
@app.route("/settings/license/upload", methods=["POST"])
|
|
|
|
@app.route("/settings/license/upload", methods=["POST"])
|
|
|
|
@login_required
|
|
|
|
@login_required
|
|
|
|
def license_upload():
|
|
|
|
def license_upload():
|
|
|
|
if not current_user.has_permission("settings_system.edit"):
|
|
|
|
if not current_user.has_permission("settings_license.edit"):
|
|
|
|
flash("Keine Berechtigung, die Lizenz zu ändern.", "danger")
|
|
|
|
flash("Keine Berechtigung, die Lizenz zu ändern.", "danger")
|
|
|
|
return redirect(url_for("settings_license"))
|
|
|
|
return redirect(url_for("settings_license"))
|
|
|
|
uploaded = request.files.get("license_file")
|
|
|
|
uploaded = request.files.get("license_file")
|
|
|
@@ -4790,7 +4852,7 @@ def _license_handshake_confirm(action, success_setting_fn, success_log_action, s
|
|
|
|
@app.route("/settings/license/activate", methods=["POST"])
|
|
|
|
@app.route("/settings/license/activate", methods=["POST"])
|
|
|
|
@login_required
|
|
|
|
@login_required
|
|
|
|
def license_activate():
|
|
|
|
def license_activate():
|
|
|
|
if not current_user.has_permission("settings_system.edit"):
|
|
|
|
if not current_user.has_permission("settings_license.edit"):
|
|
|
|
flash("Keine Berechtigung, die Lizenz zu aktivieren.", "danger")
|
|
|
|
flash("Keine Berechtigung, die Lizenz zu aktivieren.", "danger")
|
|
|
|
return redirect(url_for("settings_license"))
|
|
|
|
return redirect(url_for("settings_license"))
|
|
|
|
if not _license_state.get("file") or not _license_state.get("valid"):
|
|
|
|
if not _license_state.get("file") or not _license_state.get("valid"):
|
|
|
@@ -4807,7 +4869,7 @@ def license_activate():
|
|
|
|
@app.route("/settings/license/activate/confirm", methods=["POST"])
|
|
|
|
@app.route("/settings/license/activate/confirm", methods=["POST"])
|
|
|
|
@login_required
|
|
|
|
@login_required
|
|
|
|
def license_activate_confirm():
|
|
|
|
def license_activate_confirm():
|
|
|
|
if not current_user.has_permission("settings_system.edit"):
|
|
|
|
if not current_user.has_permission("settings_license.edit"):
|
|
|
|
flash("Keine Berechtigung, die Lizenz zu aktivieren.", "danger")
|
|
|
|
flash("Keine Berechtigung, die Lizenz zu aktivieren.", "danger")
|
|
|
|
return redirect(url_for("settings_license"))
|
|
|
|
return redirect(url_for("settings_license"))
|
|
|
|
|
|
|
|
|
|
|
@@ -4821,7 +4883,7 @@ def license_activate_confirm():
|
|
|
|
@app.route("/settings/license/deactivate", methods=["POST"])
|
|
|
|
@app.route("/settings/license/deactivate", methods=["POST"])
|
|
|
|
@login_required
|
|
|
|
@login_required
|
|
|
|
def license_deactivate():
|
|
|
|
def license_deactivate():
|
|
|
|
if not current_user.has_permission("settings_system.edit"):
|
|
|
|
if not current_user.has_permission("settings_license.edit"):
|
|
|
|
flash("Keine Berechtigung, die Lizenz zu deaktivieren.", "danger")
|
|
|
|
flash("Keine Berechtigung, die Lizenz zu deaktivieren.", "danger")
|
|
|
|
return redirect(url_for("settings_license"))
|
|
|
|
return redirect(url_for("settings_license"))
|
|
|
|
if not _license_state.get("file") or not license_activated():
|
|
|
|
if not _license_state.get("file") or not license_activated():
|
|
|
@@ -4841,7 +4903,7 @@ def license_deactivate():
|
|
|
|
@app.route("/settings/license/deactivate/confirm", methods=["POST"])
|
|
|
|
@app.route("/settings/license/deactivate/confirm", methods=["POST"])
|
|
|
|
@login_required
|
|
|
|
@login_required
|
|
|
|
def license_deactivate_confirm():
|
|
|
|
def license_deactivate_confirm():
|
|
|
|
if not current_user.has_permission("settings_system.edit"):
|
|
|
|
if not current_user.has_permission("settings_license.edit"):
|
|
|
|
flash("Keine Berechtigung, die Lizenz zu deaktivieren.", "danger")
|
|
|
|
flash("Keine Berechtigung, die Lizenz zu deaktivieren.", "danger")
|
|
|
|
return redirect(url_for("settings_license"))
|
|
|
|
return redirect(url_for("settings_license"))
|
|
|
|
|
|
|
|
|
|
|
|