Papierkorb: eigener globaler Bereich unter Einstellungen (view+edit)

Vorher: Sichtbarkeit einer Papierkorb-Sektion war an das jeweilige
Ressourcen-Leserecht gekoppelt (devices.view fuer Clients-Sektion usw.),
Wiederherstellen/endgueltig Loeschen an das jeweilige Ressourcen-
Bearbeiten-Recht (switches.edit fuer Switche im Papierkorb usw.).

Jetzt: papierkorb.view/papierkorb.edit als eigenstaendiges Rechtepaar
unter Einstellungen (vorher unter Geraete einsortiert), komplett
losgeloest von den einzelnen Ressourcen-Rechten:
- papierkorb.view zeigt ALLE fuenf Kategorien auf einmal, unabhaengig
  davon, ob der Benutzer die jeweilige aktive Ressource sehen darf.
- papierkorb.edit erlaubt Wiederherstellen/endgueltig Loeschen JEDER
  Kategorie, unabhaengig vom sonstigen Bearbeiten-Recht der jeweiligen
  Ressource (z.B. switches.edit gewaehrt jetzt KEINEN Zugriff mehr auf
  Switche im Papierkorb).

Live auf POETEST mit zwei eigens angelegten Testgruppen verifiziert:
ein Nutzer mit switches.edit (aber ohne papierkorb.edit) kann einen
Switch im Papierkorb nachweislich NICHT wiederherstellen (deleted_at
unveraendert vor/nach dem Versuch); ein Nutzer mit papierkorb.edit
(aber ohne switches.edit) kann es.
This commit is contained in:
2026-08-22 09:45:05 +02:00
parent 6ec9e5fd56
commit d90095885f
2 changed files with 47 additions and 45 deletions
+42 -30
View File
@@ -202,12 +202,6 @@ PERMISSIONS = {
"edit": "maintenance.edit",
},
},
"papierkorb": {
"label": "Papierkorb",
"rows": {
"view": "papierkorb.view",
},
},
},
},
"logs_group": {
@@ -259,6 +253,10 @@ PERMISSIONS = {
"label": "LDAP",
"rows": {"view": "settings_ldap.view", "edit": "settings_ldap.edit"},
},
"papierkorb": {
"label": "Papierkorb",
"rows": {"view": "papierkorb.view", "edit": "papierkorb.edit"},
},
},
},
}
@@ -276,7 +274,8 @@ PERMISSION_LABELS = {
"credentials.edit": "Zugangsdaten ändern (inkl. Löschen)",
"maintenance.view": "Wartungsseite ansehen (Geräte-Status, Job-Verlauf)",
"maintenance.edit": "Wartungsaktionen ausführen (SSH-Update, SSH-Neustart)",
"papierkorb.view": "Papierkorb ansehen (gelöschte Geräte/Switche/Zugangsdaten/Benutzer/Gruppen — jede Kategorie zusätzlich nur mit deren eigenem Leserecht sichtbar)",
"papierkorb.view": "Papierkorb ansehen (alle gelöschten Geräte/Switche/Zugangsdaten/Benutzer/Gruppen, unabhängig von deren eigenen Leserechten)",
"papierkorb.edit": "Papierkorb bearbeiten (beliebige Einträge wiederherstellen oder endgültig löschen, unabhängig vom sonstigen Bearbeiten-Recht der jeweiligen Ressource)",
"logs_live.view": "Live-Log lesen", "logs_activity.view": "Änderungslog lesen",
"logs_kea.view": "Kea-DHCP-Log lesen",
"users.view": "Benutzer lesen", "users.create": "Benutzer anlegen",
@@ -322,7 +321,6 @@ NAV_ITEMS = [
{"key": "switches", "label": "Switche", "icon": "share", "endpoint": "switches"},
{"key": "credentials", "label": "Zugangsdaten", "icon": "key", "endpoint": "credentials"},
{"key": "maintenance", "label": "Wartung", "icon": "wrench", "endpoint": "maintenance"},
{"key": "papierkorb", "label": "Papierkorb", "icon": "trash", "endpoint": "papierkorb"},
]},
{"key": "users", "label": "Benutzer", "icon": "users", "endpoint": "users"},
{"key": "groups", "label": "Gruppen", "icon": "groups", "endpoint": "groups"},
@@ -331,6 +329,7 @@ NAV_ITEMS = [
{"key": "settings_ldap", "label": "LDAP", "icon": "users", "endpoint": "settings_ldap"},
{"key": "settings_dhcp", "label": "DHCP", "icon": "network", "endpoint": "settings_dhcp"},
{"key": "settings_importexport", "label": "Im-/Export", "icon": "transfer", "endpoint": "settings_import_export"},
{"key": "papierkorb", "label": "Papierkorb", "icon": "trash", "endpoint": "papierkorb"},
]},
{"key": "logs_group", "label": "Logs", "icon": "terminal", "children": [
{"key": "logs_live", "label": "Live", "icon": "terminal", "endpoint": "logs"},
@@ -529,15 +528,26 @@ class User(UserMixin):
@property
def can_view_papierkorb(self):
"""Papierkorb-Seite: eigenes, dediziertes Recht (papierkorb.view) --
vorher implizit sichtbar für jeden mit irgendeiner der fünf
Ressourcen-Leserechte, wodurch praktisch jeder Benutzer den
Papierkorb sehen konnte (Standardgruppe hat bereits devices.view).
Innerhalb der Seite ist jede Kachel zusätzlich einzeln je Ressource
gated (siehe papierkorb.html) -- papierkorb.view allein zeigt also
noch keine Inhalte, sondern schaltet nur die Seite selbst frei."""
"""Papierkorb: eigener, globaler Bereich unter Einstellungen, komplett
unabhängig von den Leserechten der einzelnen Ressourcen (Geräte/
Switche/Zugangsdaten/Benutzer/Gruppen) -- vorher implizit sichtbar
für jeden mit irgendeiner der fünf Ressourcen-Leserechte, wodurch
praktisch jeder Benutzer den Papierkorb sehen konnte (Standardgruppe
hat bereits devices.view). papierkorb.view zeigt jetzt ALLE fünf
Kategorien auf einmal, unabhängig davon, ob der Benutzer die
jeweilige aktive Ressource überhaupt sehen darf."""
return self.has_permission("papierkorb.view")
@property
def can_edit_papierkorb(self):
"""Wiederherstellen/endgültig Löschen im Papierkorb: eigenes,
globales Recht (papierkorb.edit) -- vorher an das jeweilige
Ressourcen-Recht gekoppelt (z.B. switches.edit für Switche im
Papierkorb). Wer den Papierkorb bearbeiten darf, darf jetzt JEDEN
Eintrag darin wiederherstellen oder endgültig löschen, unabhängig
vom sonstigen Bearbeiten-Recht der jeweiligen Ressource."""
return self.has_permission("papierkorb.edit")
@property
def can_view_live_log(self):
return self.has_permission("logs_live.view")
@@ -4720,7 +4730,7 @@ def _purge_expired_trash():
@app.route("/devices/restore/<mac>", methods=["POST"])
@login_required
def restore_device(mac):
if not current_user.has_permission("devices.edit"):
if not current_user.has_permission("papierkorb.edit"):
flash("Keine Berechtigung, Geräte wiederherzustellen.", "danger")
return redirect(url_for("papierkorb"))
conn = get_db_connection()
@@ -4736,7 +4746,7 @@ def restore_device(mac):
@app.route("/devices/purge/<mac>", methods=["POST"])
@login_required
def purge_device(mac):
if not current_user.has_permission("devices.edit"):
if not current_user.has_permission("papierkorb.edit"):
flash("Keine Berechtigung, Geräte endgültig zu löschen.", "danger")
return redirect(url_for("papierkorb"))
conn = get_db_connection()
@@ -4752,7 +4762,7 @@ def purge_device(mac):
@app.route("/switches/restore/<hostname>", methods=["POST"])
@login_required
def restore_switch(hostname):
if not current_user.has_permission("switches.edit"):
if not current_user.has_permission("papierkorb.edit"):
flash("Keine Berechtigung, Switche wiederherzustellen.", "danger")
return redirect(url_for("papierkorb"))
conn = get_db_connection()
@@ -4767,7 +4777,7 @@ def restore_switch(hostname):
@app.route("/switches/purge/<hostname>", methods=["POST"])
@login_required
def purge_switch(hostname):
if not current_user.has_permission("switches.edit"):
if not current_user.has_permission("papierkorb.edit"):
flash("Keine Berechtigung, Switche endgültig zu löschen.", "danger")
return redirect(url_for("papierkorb"))
conn = get_db_connection()
@@ -4782,7 +4792,7 @@ def purge_switch(hostname):
@app.route("/credentials/restore/<int:cred_id>", methods=["POST"])
@login_required
def restore_credential(cred_id):
if not current_user.has_permission("credentials.edit"):
if not current_user.has_permission("papierkorb.edit"):
flash("Keine Berechtigung, Zugangsdaten wiederherzustellen.", "danger")
return redirect(url_for("papierkorb"))
conn = get_db_connection()
@@ -4798,7 +4808,7 @@ def restore_credential(cred_id):
@app.route("/credentials/purge/<int:cred_id>", methods=["POST"])
@login_required
def purge_credential(cred_id):
if not current_user.has_permission("credentials.edit"):
if not current_user.has_permission("papierkorb.edit"):
flash("Keine Berechtigung, Zugangsdaten endgültig zu löschen.", "danger")
return redirect(url_for("papierkorb"))
conn = get_db_connection()
@@ -4814,7 +4824,7 @@ def purge_credential(cred_id):
@app.route("/users/restore/<int:user_id>", methods=["POST"])
@login_required
def restore_user(user_id):
if not current_user.has_permission("users.edit"):
if not current_user.has_permission("papierkorb.edit"):
flash("Keine Berechtigung, Benutzer wiederherzustellen.", "danger")
return redirect(url_for("papierkorb"))
conn = get_db_connection()
@@ -4836,7 +4846,7 @@ def restore_user(user_id):
@app.route("/users/purge/<int:user_id>", methods=["POST"])
@login_required
def purge_user(user_id):
if not current_user.has_permission("users.edit"):
if not current_user.has_permission("papierkorb.edit"):
flash("Keine Berechtigung, Benutzer endgültig zu löschen.", "danger")
return redirect(url_for("papierkorb"))
conn = get_db_connection()
@@ -4858,7 +4868,7 @@ def purge_user(user_id):
@app.route("/groups/restore/<int:group_id>", methods=["POST"])
@login_required
def restore_group(group_id):
if not current_user.has_permission("groups.edit"):
if not current_user.has_permission("papierkorb.edit"):
flash("Keine Berechtigung, Gruppen wiederherzustellen.", "danger")
return redirect(url_for("papierkorb"))
conn = get_db_connection()
@@ -4874,7 +4884,7 @@ def restore_group(group_id):
@app.route("/groups/purge/<int:group_id>", methods=["POST"])
@login_required
def purge_group(group_id):
if not current_user.has_permission("groups.edit"):
if not current_user.has_permission("papierkorb.edit"):
flash("Keine Berechtigung, Gruppen endgültig zu löschen.", "danger")
return redirect(url_for("papierkorb"))
conn = get_db_connection()
@@ -4916,12 +4926,14 @@ def papierkorb():
return redirect(url_for("index"))
_purge_expired_trash()
conn = get_db_connection()
# Global: papierkorb.view zeigt alle fünf Kategorien, unabhängig vom
# jeweiligen Ressourcen-Leserecht (siehe can_view_papierkorb).
trash = {
"devices": _trashed_devices(conn) if current_user.has_permission("devices.view") else [],
"switches": _trashed_switches(conn) if current_user.has_permission("switches.view") else [],
"credentials": _trashed_credentials(conn) if current_user.has_permission("credentials.view") else [],
"users": _trashed_users(conn) if current_user.has_permission("users.view") else [],
"groups": _trashed_groups(conn) if current_user.has_permission("groups.view") else [],
"devices": _trashed_devices(conn),
"switches": _trashed_switches(conn),
"credentials": _trashed_credentials(conn),
"users": _trashed_users(conn),
"groups": _trashed_groups(conn),
}
conn.close()
return render_template(
+5 -15
View File
@@ -19,7 +19,6 @@
<div style="display:flex; flex-direction:column; gap:16px;">
{% if current_user.has_permission('devices.view') %}
<div class="card card-pad">
<h3 style="font-size:14px; margin:0 0 12px;">Clients ({{ trash.devices|length }})</h3>
{% if trash.devices %}
@@ -33,7 +32,7 @@
<td class="mono">{{ d['ip'] }}</td>
<td class="text-faint" style="font-size:12px;">{{ d['deleted_at'] }}</td>
<td>
{% if current_user.has_permission('devices.edit') %}
{% if current_user.has_permission('papierkorb.edit') %}
<div class="row-actions">
<form method="post" action="{{ url_for('restore_device', mac=d['mac']) }}">
<button type="submit" class="btn btn-sm btn-secondary">Wiederherstellen</button>
@@ -55,9 +54,7 @@
<p class="text-faint" style="font-size:12px;">Papierkorb ist leer.</p>
{% endif %}
</div>
{% endif %}
{% if current_user.has_permission('switches.view') %}
<div class="card card-pad">
<h3 style="font-size:14px; margin:0 0 12px;">Switche ({{ trash.switches|length }})</h3>
{% if trash.switches %}
@@ -71,7 +68,7 @@
<td class="mono">{{ s['ip'] }}</td>
<td class="text-faint" style="font-size:12px;">{{ s['deleted_at'] }}</td>
<td>
{% if current_user.has_permission('switches.edit') %}
{% if current_user.has_permission('papierkorb.edit') %}
<div class="row-actions">
<form method="post" action="{{ url_for('restore_switch', hostname=s['hostname']) }}">
<button type="submit" class="btn btn-sm btn-secondary">Wiederherstellen</button>
@@ -93,9 +90,7 @@
<p class="text-faint" style="font-size:12px;">Papierkorb ist leer.</p>
{% endif %}
</div>
{% endif %}
{% if current_user.has_permission('credentials.view') %}
<div class="card card-pad">
<h3 style="font-size:14px; margin:0 0 12px;">Zugangsdaten ({{ trash.credentials|length }})</h3>
{% if trash.credentials %}
@@ -109,7 +104,7 @@
<td class="mono">{{ c['username'] }}</td>
<td class="text-faint" style="font-size:12px;">{{ c['deleted_at'] }}</td>
<td>
{% if current_user.has_permission('credentials.edit') %}
{% if current_user.has_permission('papierkorb.edit') %}
<div class="row-actions">
<form method="post" action="{{ url_for('restore_credential', cred_id=c['id']) }}">
<button type="submit" class="btn btn-sm btn-secondary">Wiederherstellen</button>
@@ -131,9 +126,7 @@
<p class="text-faint" style="font-size:12px;">Papierkorb ist leer.</p>
{% endif %}
</div>
{% endif %}
{% if current_user.has_permission('users.view') %}
<div class="card card-pad">
<h3 style="font-size:14px; margin:0 0 12px;">Benutzer ({{ trash.users|length }})</h3>
{% if trash.users %}
@@ -148,7 +141,7 @@
<td class="text-dim">{{ full_name or '—' }}</td>
<td class="text-faint" style="font-size:12px;">{{ u['deleted_at'] }}</td>
<td>
{% if current_user.has_permission('users.edit') %}
{% if current_user.has_permission('papierkorb.edit') %}
<div class="row-actions">
<form method="post" action="{{ url_for('restore_user', user_id=u['id']) }}">
<button type="submit" class="btn btn-sm btn-secondary">Wiederherstellen</button>
@@ -170,9 +163,7 @@
<p class="text-faint" style="font-size:12px;">Papierkorb ist leer.</p>
{% endif %}
</div>
{% endif %}
{% if current_user.has_permission('groups.view') %}
<div class="card card-pad">
<h3 style="font-size:14px; margin:0 0 12px;">Gruppen ({{ trash.groups|length }})</h3>
{% if trash.groups %}
@@ -185,7 +176,7 @@
<td class="cell-name">{{ g['name'] }}</td>
<td class="text-faint" style="font-size:12px;">{{ g['deleted_at'] }}</td>
<td>
{% if current_user.has_permission('groups.edit') %}
{% if current_user.has_permission('papierkorb.edit') %}
<div class="row-actions">
<form method="post" action="{{ url_for('restore_group', group_id=g['id']) }}">
<button type="submit" class="btn btn-sm btn-secondary">Wiederherstellen</button>
@@ -207,7 +198,6 @@
<p class="text-faint" style="font-size:12px;">Papierkorb ist leer.</p>
{% endif %}
</div>
{% endif %}
</div>