feat: Implement user permission management with presets and dynamic access control
This commit is contained in:
+198
-1
@@ -145,6 +145,79 @@ _NOTIFICATION_CACHE_LOCK = threading.Lock()
|
|||||||
|
|
||||||
SCHOOL_PERIODS = cfg.SCHOOL_PERIODS
|
SCHOOL_PERIODS = cfg.SCHOOL_PERIODS
|
||||||
|
|
||||||
|
PERMISSION_ACTION_OPTIONS = [
|
||||||
|
('can_borrow', 'Ausleihe erlauben'),
|
||||||
|
('can_insert', 'Einfügen/Hochladen erlauben'),
|
||||||
|
('can_edit', 'Bearbeiten erlauben'),
|
||||||
|
('can_delete', 'Löschen erlauben'),
|
||||||
|
('can_manage_users', 'Benutzerverwaltung erlauben'),
|
||||||
|
('can_manage_settings', 'Systemverwaltung erlauben'),
|
||||||
|
('can_view_logs', 'Logs/Audit einsehen erlauben'),
|
||||||
|
]
|
||||||
|
|
||||||
|
PERMISSION_PAGE_OPTIONS = [
|
||||||
|
('home', 'Artikel (Inventar)'),
|
||||||
|
('tutorial_page', 'Tutorial'),
|
||||||
|
('my_borrowed_items', 'Meine Ausleihen'),
|
||||||
|
('notifications_view', 'Benachrichtigungen'),
|
||||||
|
('impressum', 'Impressum'),
|
||||||
|
('license', 'Lizenz'),
|
||||||
|
('library_view', 'Bibliothek (Medien)'),
|
||||||
|
('terminplan', 'Terminplan'),
|
||||||
|
('home_admin', 'Admin Startseite'),
|
||||||
|
('upload_admin', 'Admin Upload Inventar'),
|
||||||
|
('library_admin', 'Admin Upload Bibliothek'),
|
||||||
|
('admin_borrowings', 'Admin Ausleihen'),
|
||||||
|
('library_loans_admin', 'Admin Bibliotheks-Ausleihen'),
|
||||||
|
('admin_damaged_items', 'Admin Defekte Items'),
|
||||||
|
('admin_audit_dashboard', 'Admin Audit Dashboard'),
|
||||||
|
('logs', 'System-Logs'),
|
||||||
|
('user_del', 'Benutzerverwaltung'),
|
||||||
|
('register', 'Benutzer anlegen'),
|
||||||
|
('manage_filters', 'Filter verwalten'),
|
||||||
|
('manage_locations', 'Orte verwalten'),
|
||||||
|
]
|
||||||
|
|
||||||
|
PERMISSION_EXEMPT_ENDPOINTS = {
|
||||||
|
'static',
|
||||||
|
'login',
|
||||||
|
'logout',
|
||||||
|
'impressum',
|
||||||
|
'license',
|
||||||
|
'uploaded_file',
|
||||||
|
'thumbnail_file',
|
||||||
|
'preview_file',
|
||||||
|
}
|
||||||
|
|
||||||
|
PERMISSION_ACTION_ENDPOINTS = {
|
||||||
|
'upload_item': 'can_insert',
|
||||||
|
'upload_inventory_excel': 'can_insert',
|
||||||
|
'upload_library_excel': 'can_insert',
|
||||||
|
'upload_student_cards_excel': 'can_insert',
|
||||||
|
'edit_item': 'can_edit',
|
||||||
|
'api_library_item_update': 'can_edit',
|
||||||
|
'admin_update_user_name': 'can_edit',
|
||||||
|
'delete_item': 'can_delete',
|
||||||
|
'delete_user': 'can_delete',
|
||||||
|
'ausleihen': 'can_borrow',
|
||||||
|
'zurueckgeben': 'can_borrow',
|
||||||
|
'api_library_scan_action': 'can_borrow',
|
||||||
|
'user_del': 'can_manage_users',
|
||||||
|
'register': 'can_manage_users',
|
||||||
|
'admin_reset_user_password': 'can_manage_users',
|
||||||
|
'admin_update_user_permissions': 'can_manage_users',
|
||||||
|
'home_admin': 'can_manage_settings',
|
||||||
|
'upload_admin': 'can_manage_settings',
|
||||||
|
'library_admin': 'can_manage_settings',
|
||||||
|
'admin_borrowings': 'can_manage_settings',
|
||||||
|
'library_loans_admin': 'can_manage_settings',
|
||||||
|
'admin_damaged_items': 'can_manage_settings',
|
||||||
|
'manage_filters': 'can_manage_settings',
|
||||||
|
'manage_locations': 'can_manage_settings',
|
||||||
|
'admin_audit_dashboard': 'can_view_logs',
|
||||||
|
'logs': 'can_view_logs',
|
||||||
|
}
|
||||||
|
|
||||||
# Apply the configuration for general use throughout the app
|
# Apply the configuration for general use throughout the app
|
||||||
APP_VERSION = __version__
|
APP_VERSION = __version__
|
||||||
RELEASE_STATE_FILE = os.path.join(os.path.dirname(BASE_DIR), '.release-version')
|
RELEASE_STATE_FILE = os.path.join(os.path.dirname(BASE_DIR), '.release-version')
|
||||||
@@ -199,6 +272,47 @@ def _enforce_csrf_protection():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def _enforce_user_permissions():
|
||||||
|
endpoint = request.endpoint
|
||||||
|
if not endpoint:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if endpoint == 'static' or endpoint.startswith('static'):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if endpoint in PERMISSION_EXEMPT_ENDPOINTS:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if 'username' not in session:
|
||||||
|
return None
|
||||||
|
|
||||||
|
permissions = _get_current_user_permissions()
|
||||||
|
if not permissions:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not _page_access_allowed(permissions, endpoint):
|
||||||
|
message = 'Diese Seite ist für Ihren Benutzer aktuell gesperrt.'
|
||||||
|
if request.path.startswith('/api/') or request.is_json:
|
||||||
|
return jsonify({'ok': False, 'message': message}), 403
|
||||||
|
|
||||||
|
flash(message, 'error')
|
||||||
|
fallback_endpoint = _permission_denied_fallback_endpoint(permissions)
|
||||||
|
return redirect(url_for(fallback_endpoint))
|
||||||
|
|
||||||
|
action_key = PERMISSION_ACTION_ENDPOINTS.get(endpoint)
|
||||||
|
if action_key and not _action_access_allowed(permissions, action_key):
|
||||||
|
message = 'Für diese Aktion fehlen Ihnen die erforderlichen Berechtigungen.'
|
||||||
|
if request.path.startswith('/api/') or request.is_json:
|
||||||
|
return jsonify({'ok': False, 'message': message}), 403
|
||||||
|
|
||||||
|
flash(message, 'error')
|
||||||
|
fallback_endpoint = _permission_denied_fallback_endpoint(permissions)
|
||||||
|
return redirect(url_for(fallback_endpoint))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_asset_version():
|
def _get_asset_version():
|
||||||
"""Return a cache-busting asset version tied to deployment state."""
|
"""Return a cache-busting asset version tied to deployment state."""
|
||||||
env_version = os.getenv('INVENTAR_ASSET_VERSION', '').strip()
|
env_version = os.getenv('INVENTAR_ASSET_VERSION', '').strip()
|
||||||
@@ -237,6 +351,37 @@ def _get_current_module(path):
|
|||||||
return 'inventory'
|
return 'inventory'
|
||||||
|
|
||||||
|
|
||||||
|
def _get_current_user_permissions():
|
||||||
|
username = session.get('username')
|
||||||
|
if not username:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return us.get_effective_permissions(username)
|
||||||
|
except Exception:
|
||||||
|
return us.build_default_permission_payload('standard_user')
|
||||||
|
|
||||||
|
|
||||||
|
def _page_access_allowed(permissions, endpoint):
|
||||||
|
if not permissions or not endpoint:
|
||||||
|
return True
|
||||||
|
page_permissions = permissions.get('pages', {})
|
||||||
|
return bool(page_permissions.get(endpoint, True))
|
||||||
|
|
||||||
|
|
||||||
|
def _action_access_allowed(permissions, action_key):
|
||||||
|
if not permissions or not action_key:
|
||||||
|
return True
|
||||||
|
action_permissions = permissions.get('actions', {})
|
||||||
|
return bool(action_permissions.get(action_key, True))
|
||||||
|
|
||||||
|
|
||||||
|
def _permission_denied_fallback_endpoint(permissions):
|
||||||
|
for candidate in ('home', 'my_borrowed_items', 'tutorial_page', 'notifications_view', 'impressum'):
|
||||||
|
if _page_access_allowed(permissions, candidate):
|
||||||
|
return candidate
|
||||||
|
return 'logout'
|
||||||
|
|
||||||
|
|
||||||
def _append_audit_event(db, event_type, payload):
|
def _append_audit_event(db, event_type, payload):
|
||||||
"""Write an audit entry; never break business flow on audit failures."""
|
"""Write an audit entry; never break business flow on audit failures."""
|
||||||
try:
|
try:
|
||||||
@@ -845,12 +990,18 @@ def inject_version():
|
|||||||
asset_version = _get_asset_version()
|
asset_version = _get_asset_version()
|
||||||
csrf_token = _get_csrf_token()
|
csrf_token = _get_csrf_token()
|
||||||
unread_notification_count = 0
|
unread_notification_count = 0
|
||||||
|
current_permissions = us.build_default_permission_payload('standard_user')
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
try:
|
try:
|
||||||
is_admin = us.check_admin(session['username'])
|
is_admin = us.check_admin(session['username'])
|
||||||
except Exception:
|
except Exception:
|
||||||
is_admin = False
|
is_admin = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_permissions = us.get_effective_permissions(session['username'])
|
||||||
|
except Exception:
|
||||||
|
current_permissions = us.build_default_permission_payload('standard_user')
|
||||||
|
|
||||||
client = None
|
client = None
|
||||||
try:
|
try:
|
||||||
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
||||||
@@ -874,6 +1025,10 @@ def inject_version():
|
|||||||
'student_cards_module_enabled': cfg.STUDENT_CARDS_MODULE_ENABLED,
|
'student_cards_module_enabled': cfg.STUDENT_CARDS_MODULE_ENABLED,
|
||||||
'is_admin': is_admin,
|
'is_admin': is_admin,
|
||||||
'unread_notification_count': unread_notification_count,
|
'unread_notification_count': unread_notification_count,
|
||||||
|
'current_permissions': current_permissions,
|
||||||
|
'permission_action_options': PERMISSION_ACTION_OPTIONS,
|
||||||
|
'permission_page_options': PERMISSION_PAGE_OPTIONS,
|
||||||
|
'permission_presets': us.get_permission_preset_definitions(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create necessary directories at startup
|
# Create necessary directories at startup
|
||||||
@@ -6577,6 +6732,10 @@ def user_del():
|
|||||||
break
|
break
|
||||||
|
|
||||||
if username and username != session['username']:
|
if username and username != session['username']:
|
||||||
|
try:
|
||||||
|
permissions_payload = us.get_effective_permissions(username)
|
||||||
|
except Exception:
|
||||||
|
permissions_payload = us.build_default_permission_payload('standard_user')
|
||||||
try:
|
try:
|
||||||
name = us.get_name(username)
|
name = us.get_name(username)
|
||||||
last_name = us.get_last_name(username)
|
last_name = us.get_last_name(username)
|
||||||
@@ -6597,7 +6756,10 @@ def user_del():
|
|||||||
'admin': user.get('Admin', False),
|
'admin': user.get('Admin', False),
|
||||||
'fullname': fullname,
|
'fullname': fullname,
|
||||||
'name': name,
|
'name': name,
|
||||||
'last_name': last_name
|
'last_name': last_name,
|
||||||
|
'permission_preset': permissions_payload.get('preset', 'standard_user'),
|
||||||
|
'action_permissions': permissions_payload.get('actions', {}),
|
||||||
|
'page_permissions': permissions_payload.get('pages', {}),
|
||||||
})
|
})
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
@@ -7614,6 +7776,41 @@ def admin_update_user_name():
|
|||||||
return redirect(url_for('user_del'))
|
return redirect(url_for('user_del'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin_update_user_permissions', methods=['POST'])
|
||||||
|
def admin_update_user_permissions():
|
||||||
|
"""Admin route to update permission preset and per-endpoint overrides for a user."""
|
||||||
|
if 'username' not in session or not us.check_admin(session['username']):
|
||||||
|
flash('Nicht autorisierter Zugriff', 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
username = request.form.get('username', '').strip()
|
||||||
|
preset_key = request.form.get('permission_preset', 'standard_user').strip()
|
||||||
|
|
||||||
|
if not username:
|
||||||
|
flash('Kein Benutzer ausgewählt', 'error')
|
||||||
|
return redirect(url_for('user_del'))
|
||||||
|
|
||||||
|
target_user = us.get_user(username)
|
||||||
|
if not target_user:
|
||||||
|
flash(f'Benutzer {username} nicht gefunden', 'error')
|
||||||
|
return redirect(url_for('user_del'))
|
||||||
|
|
||||||
|
action_permissions = {}
|
||||||
|
for action_key, _ in PERMISSION_ACTION_OPTIONS:
|
||||||
|
action_permissions[action_key] = request.form.get(f'action_{action_key}') == 'on'
|
||||||
|
|
||||||
|
page_permissions = {}
|
||||||
|
for endpoint_name, _ in PERMISSION_PAGE_OPTIONS:
|
||||||
|
page_permissions[endpoint_name] = request.form.get(f'page_{endpoint_name}') == 'on'
|
||||||
|
|
||||||
|
if us.update_user_permissions(username, preset_key, action_permissions, page_permissions):
|
||||||
|
flash(f'Berechtigungen für {username} wurden aktualisiert.', 'success')
|
||||||
|
else:
|
||||||
|
flash('Fehler beim Aktualisieren der Berechtigungen.', 'error')
|
||||||
|
|
||||||
|
return redirect(url_for('user_del'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/logs')
|
@app.route('/logs')
|
||||||
def logs():
|
def logs():
|
||||||
"""
|
"""
|
||||||
|
|||||||
+56
-4
@@ -769,18 +769,24 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="inventoryNavContent">
|
<div class="collapse navbar-collapse" id="inventoryNavContent">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="inventoryNavList">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="inventoryNavList">
|
||||||
|
{% if current_permissions.pages.get('home', True) %}
|
||||||
<li class="nav-item" data-nav-fixed="true">
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
<a class="nav-link {% if current_path == url_for('home') %}nav-active{% endif %}" href="{{ url_for('home') }}">Artikel</a>
|
<a class="nav-link {% if current_path == url_for('home') %}nav-active{% endif %}" href="{{ url_for('home') }}">Artikel</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if 'username' in session %}
|
{% if 'username' in session %}
|
||||||
|
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||||
<li class="nav-item" data-nav-fixed="true">
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}">Meine Ausleihen</a>
|
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}">Meine Ausleihen</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||||
<li class="nav-item" data-nav-fixed="true">
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}">Tutorial</a>
|
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}">Tutorial</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
{% endif %}
|
||||||
|
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.pages.get('upload_admin', True) and current_permissions.actions.get('can_manage_settings', True) %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link nav-priority-link {% if current_path == url_for('upload_admin') %}nav-active{% endif %}" href="{{ url_for('upload_admin') }}">➕ Hochladen</a>
|
<a class="nav-link nav-priority-link {% if current_path == url_for('upload_admin') %}nav-active{% endif %}" href="{{ url_for('upload_admin') }}">➕ Hochladen</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -791,24 +797,46 @@
|
|||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invMoreDropdown">
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invMoreDropdown">
|
||||||
{% if 'username' in session %}
|
{% if 'username' in session %}
|
||||||
|
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('my_borrowed_items') }}">Meine Ausleihen</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('my_borrowed_items') }}">Meine Ausleihen</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('tutorial_page') }}">Tutorial</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('tutorial_page') }}">Tutorial</a></li>
|
||||||
|
{% endif %}
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.actions.get('can_manage_settings', True) %}
|
||||||
<li><h6 class="dropdown-header">Verwaltung</h6></li>
|
<li><h6 class="dropdown-header">Verwaltung</h6></li>
|
||||||
|
{% if current_permissions.pages.get('manage_filters', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('manage_filters') }}">Filter verwalten</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('manage_filters') }}">Filter verwalten</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('manage_locations', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('manage_locations') }}">Orte verwalten</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('manage_locations') }}">Orte verwalten</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('admin_borrowings', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('admin_borrowings') }}">Ausleihen</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('admin_borrowings') }}">Ausleihen</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('admin_damaged_items', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('admin_damaged_items') }}">Defekte Items</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('admin_damaged_items') }}">Defekte Items</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.actions.get('can_view_logs', True) and current_permissions.pages.get('admin_audit_dashboard', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('admin_audit_dashboard') }}">Audit Dashboard</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('admin_audit_dashboard') }}">Audit Dashboard</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.actions.get('can_view_logs', True) and current_permissions.pages.get('logs', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('logs') }}">Logs</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('logs') }}">Logs</a></li>
|
||||||
|
{% endif %}
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
{% if current_permissions.actions.get('can_manage_users', True) %}
|
||||||
<li><h6 class="dropdown-header">System</h6></li>
|
<li><h6 class="dropdown-header">System</h6></li>
|
||||||
|
{% if current_permissions.pages.get('user_del', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('user_del') }}">Benutzer verwalten</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('user_del') }}">Benutzer verwalten</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('register', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('register') }}">Neuer Benutzer</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('register') }}">Neuer Benutzer</a></li>
|
||||||
|
{% endif %}
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('impressum') }}">Impressum</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('impressum') }}">Impressum</a></li>
|
||||||
<li><a class="dropdown-item" href="{{ url_for('license') }}">Lizenz</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('license') }}">Lizenz</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -837,7 +865,9 @@
|
|||||||
<span class="user-notification-dot {% if unread_notification_count and unread_notification_count > 0 %}visible{% endif %}" aria-hidden="true"></span>
|
<span class="user-notification-dot {% if unread_notification_count and unread_notification_count > 0 %}visible{% endif %}" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invUserMenuDropdown">
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invUserMenuDropdown">
|
||||||
|
{% if current_permissions.pages.get('notifications_view', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('notifications_view') }}">Benachrichtigungen</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('notifications_view') }}">Benachrichtigungen</a></li>
|
||||||
|
{% endif %}
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="{{ url_for('change_password') }}">Passwort ändern</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('change_password') }}">Passwort ändern</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
@@ -860,18 +890,24 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="libraryNavContent">
|
<div class="collapse navbar-collapse" id="libraryNavContent">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="libraryNavList">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="libraryNavList">
|
||||||
|
{% if current_permissions.pages.get('library_view', True) %}
|
||||||
<li class="nav-item" data-nav-fixed="true">
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
<a class="nav-link {% if current_path == url_for('library_view') %}nav-active{% endif %}" href="{{ url_for('library_view') }}">Medien</a>
|
<a class="nav-link {% if current_path == url_for('library_view') %}nav-active{% endif %}" href="{{ url_for('library_view') }}">Medien</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if 'username' in session %}
|
{% if 'username' in session %}
|
||||||
|
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||||
<li class="nav-item" data-nav-fixed="true">
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}">Meine Medien</a>
|
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}">Meine Medien</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||||
<li class="nav-item" data-nav-fixed="true">
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}">Tutorial</a>
|
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}">Tutorial</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
{% endif %}
|
||||||
|
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.actions.get('can_manage_settings', True) and current_permissions.pages.get('library_admin', True) %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link nav-priority-link {% if current_path == url_for('library_admin') %}nav-active{% endif %}" href="{{ url_for('library_admin') }}">📖 Hochladen</a>
|
<a class="nav-link nav-priority-link {% if current_path == url_for('library_admin') %}nav-active{% endif %}" href="{{ url_for('library_admin') }}">📖 Hochladen</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -882,23 +918,37 @@
|
|||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="libMoreDropdown">
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="libMoreDropdown">
|
||||||
{% if 'username' in session %}
|
{% if 'username' in session %}
|
||||||
|
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('my_borrowed_items') }}">Meine Medien</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('my_borrowed_items') }}">Meine Medien</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('tutorial_page') }}">Tutorial</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('tutorial_page') }}">Tutorial</a></li>
|
||||||
|
{% endif %}
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.actions.get('can_manage_settings', True) %}
|
||||||
<li><h6 class="dropdown-header">Bibliotheks-Verwaltung</h6></li>
|
<li><h6 class="dropdown-header">Bibliotheks-Verwaltung</h6></li>
|
||||||
|
{% if current_permissions.pages.get('library_loans_admin', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('library_loans_admin') }}">Ausleihen</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('library_loans_admin') }}">Ausleihen</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('admin_damaged_items', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('admin_damaged_items') }}">Defekte Items</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('admin_damaged_items') }}">Defekte Items</a></li>
|
||||||
|
{% endif %}
|
||||||
{% if student_cards_module_enabled %}
|
{% if student_cards_module_enabled %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('student_cards_admin') }}">Bibliotheksausweis</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('student_cards_admin') }}">Bibliotheksausweis</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
{% if current_permissions.actions.get('can_manage_users', True) %}
|
||||||
<li><h6 class="dropdown-header">System</h6></li>
|
<li><h6 class="dropdown-header">System</h6></li>
|
||||||
|
{% if current_permissions.pages.get('user_del', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('user_del') }}">Benutzer verwalten</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('user_del') }}">Benutzer verwalten</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_permissions.pages.get('register', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('register') }}">Neuer Benutzer</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('register') }}">Neuer Benutzer</a></li>
|
||||||
|
{% endif %}
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('impressum') }}">Impressum</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('impressum') }}">Impressum</a></li>
|
||||||
<li><a class="dropdown-item" href="{{ url_for('license') }}">Lizenz</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('license') }}">Lizenz</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -927,7 +977,9 @@
|
|||||||
<span class="user-notification-dot {% if unread_notification_count and unread_notification_count > 0 %}visible{% endif %}" aria-hidden="true"></span>
|
<span class="user-notification-dot {% if unread_notification_count and unread_notification_count > 0 %}visible{% endif %}" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="libUserMenuDropdown">
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="libUserMenuDropdown">
|
||||||
|
{% if current_permissions.pages.get('notifications_view', True) %}
|
||||||
<li><a class="dropdown-item" href="{{ url_for('notifications_view') }}">Benachrichtigungen</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('notifications_view') }}">Benachrichtigungen</a></li>
|
||||||
|
{% endif %}
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="{{ url_for('change_password') }}">Passwort ändern</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('change_password') }}">Passwort ändern</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
<th>Vorname</th>
|
<th>Vorname</th>
|
||||||
<th>Nachname</th>
|
<th>Nachname</th>
|
||||||
<th>Administrator</th>
|
<th>Administrator</th>
|
||||||
|
<th>Rechte-Preset</th>
|
||||||
<th>Aktionen</th>
|
<th>Aktionen</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
<td>{{ user.name if user.name else user.username }}</td>
|
<td>{{ user.name if user.name else user.username }}</td>
|
||||||
<td>{{ user.last_name if user.last_name else '' }}</td>
|
<td>{{ user.last_name if user.last_name else '' }}</td>
|
||||||
<td>{{ "Ja" if user.admin else "Nein" }}</td>
|
<td>{{ "Ja" if user.admin else "Nein" }}</td>
|
||||||
|
<td>{{ permission_presets.get(user.permission_preset, {}).get('label', user.permission_preset) }}</td>
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<form method="POST" action="{{ url_for('delete_user') }}" class="d-inline">
|
<form method="POST" action="{{ url_for('delete_user') }}" class="d-inline">
|
||||||
<input type="hidden" name="username" value="{{ user.username }}">
|
<input type="hidden" name="username" value="{{ user.username }}">
|
||||||
@@ -90,6 +92,14 @@
|
|||||||
onclick="openResetPasswordModal('{{ user.username }}')">
|
onclick="openResetPasswordModal('{{ user.username }}')">
|
||||||
Passwort zurücksetzen
|
Passwort zurücksetzen
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="btn btn-info btn-sm"
|
||||||
|
data-username="{{ user.username }}"
|
||||||
|
data-preset="{{ user.permission_preset }}"
|
||||||
|
data-action-permissions='{{ user.action_permissions | tojson }}'
|
||||||
|
data-page-permissions='{{ user.page_permissions | tojson }}'
|
||||||
|
onclick="openPermissionsModal(this)">
|
||||||
|
Berechtigungen
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -99,6 +109,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Permission Modal -->
|
||||||
|
<div class="modal fade" id="permissionsModal" tabindex="-1" aria-labelledby="permissionsModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="permissionsModalLabel">Berechtigungen bearbeiten</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="{{ url_for('admin_update_user_permissions') }}">
|
||||||
|
<div class="modal-body permissions-modal-body">
|
||||||
|
<input type="hidden" id="perm-username" name="username">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="perm-username-display" class="form-label">Benutzer</label>
|
||||||
|
<input type="text" class="form-control" id="perm-username-display" disabled>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="permission-preset" class="form-label">Preset</label>
|
||||||
|
<select class="form-select" id="permission-preset" name="permission_preset">
|
||||||
|
{% for preset_key, preset_value in permission_presets.items() %}
|
||||||
|
<option value="{{ preset_key }}">{{ preset_value.label }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="permissions-grid">
|
||||||
|
<div class="permissions-group">
|
||||||
|
<h6>Aktionsrechte</h6>
|
||||||
|
{% for action_key, action_label in permission_action_options %}
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input permission-action-checkbox" type="checkbox" id="action-{{ action_key }}" name="action_{{ action_key }}">
|
||||||
|
<label class="form-check-label" for="action-{{ action_key }}">{{ action_label }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="permissions-group">
|
||||||
|
<h6>Seitenrechte</h6>
|
||||||
|
{% for endpoint_name, endpoint_label in permission_page_options %}
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input permission-page-checkbox" type="checkbox" id="page-{{ endpoint_name }}" name="page_{{ endpoint_name }}">
|
||||||
|
<label class="form-check-label" for="page-{{ endpoint_name }}">{{ endpoint_label }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Berechtigungen speichern</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Edit User Modal -->
|
<!-- Edit User Modal -->
|
||||||
<div class="modal fade" id="editUserModal" tabindex="-1" aria-labelledby="editUserModalLabel" aria-hidden="true">
|
<div class="modal fade" id="editUserModal" tabindex="-1" aria-labelledby="editUserModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@@ -165,6 +231,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
var permissionPresets = {{ permission_presets | tojson }};
|
||||||
|
|
||||||
|
function applyPresetToPermissionForm(presetKey) {
|
||||||
|
var preset = permissionPresets[presetKey] || {};
|
||||||
|
var actionDefaults = preset.actions || {};
|
||||||
|
var pageDefaults = preset.pages || {};
|
||||||
|
|
||||||
|
document.querySelectorAll('.permission-action-checkbox').forEach(function (checkbox) {
|
||||||
|
var key = checkbox.name.replace('action_', '');
|
||||||
|
checkbox.checked = !!actionDefaults[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.permission-page-checkbox').forEach(function (checkbox) {
|
||||||
|
var key = checkbox.name.replace('page_', '');
|
||||||
|
checkbox.checked = !!pageDefaults[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function applyFilters() {
|
function applyFilters() {
|
||||||
var search = document.getElementById('filter-search').value.toLowerCase();
|
var search = document.getElementById('filter-search').value.toLowerCase();
|
||||||
var adminFilter = document.getElementById('filter-admin').value.toLowerCase();
|
var adminFilter = document.getElementById('filter-admin').value.toLowerCase();
|
||||||
@@ -229,6 +313,13 @@
|
|||||||
document.getElementById('filter-admin').addEventListener('change', applyFilters);
|
document.getElementById('filter-admin').addEventListener('change', applyFilters);
|
||||||
document.getElementById('filter-column').addEventListener('change', applyFilters);
|
document.getElementById('filter-column').addEventListener('change', applyFilters);
|
||||||
document.getElementById('filter-direction').addEventListener('change', applyFilters);
|
document.getElementById('filter-direction').addEventListener('change', applyFilters);
|
||||||
|
|
||||||
|
var presetSelect = document.getElementById('permission-preset');
|
||||||
|
if (presetSelect) {
|
||||||
|
presetSelect.addEventListener('change', function () {
|
||||||
|
applyPresetToPermissionForm(this.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function openEditUserModal(button) {
|
function openEditUserModal(button) {
|
||||||
@@ -253,6 +344,42 @@
|
|||||||
var modal = new bootstrap.Modal(document.getElementById('resetPasswordModal'));
|
var modal = new bootstrap.Modal(document.getElementById('resetPasswordModal'));
|
||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openPermissionsModal(button) {
|
||||||
|
var username = button.getAttribute('data-username') || '';
|
||||||
|
var preset = button.getAttribute('data-preset') || 'standard_user';
|
||||||
|
var actionPermissions = {};
|
||||||
|
var pagePermissions = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
actionPermissions = JSON.parse(button.getAttribute('data-action-permissions') || '{}');
|
||||||
|
} catch (err) {
|
||||||
|
actionPermissions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pagePermissions = JSON.parse(button.getAttribute('data-page-permissions') || '{}');
|
||||||
|
} catch (err) {
|
||||||
|
pagePermissions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('perm-username').value = username;
|
||||||
|
document.getElementById('perm-username-display').value = username;
|
||||||
|
document.getElementById('permission-preset').value = preset;
|
||||||
|
|
||||||
|
document.querySelectorAll('.permission-action-checkbox').forEach(function (checkbox) {
|
||||||
|
var key = checkbox.name.replace('action_', '');
|
||||||
|
checkbox.checked = !!actionPermissions[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.permission-page-checkbox').forEach(function (checkbox) {
|
||||||
|
var key = checkbox.name.replace('page_', '');
|
||||||
|
checkbox.checked = !!pagePermissions[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
var modal = new bootstrap.Modal(document.getElementById('permissionsModal'));
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -286,5 +413,28 @@
|
|||||||
.password-requirements p {
|
.password-requirements p {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.permissions-modal-body {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-group {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-group h6 {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
+221
-1
@@ -11,6 +11,7 @@ Provides methods for creating, validating, and retrieving user information.
|
|||||||
For commercial licensing inquiries: https://github.com/AIIrondev
|
For commercial licensing inquiries: https://github.com/AIIrondev
|
||||||
'''
|
'''
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import copy
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
import settings as cfg
|
import settings as cfg
|
||||||
from settings import MongoClient
|
from settings import MongoClient
|
||||||
@@ -23,6 +24,220 @@ def normalize_student_card_id(card_id):
|
|||||||
return str(card_id).strip().upper()
|
return str(card_id).strip().upper()
|
||||||
|
|
||||||
|
|
||||||
|
ACTION_PERMISSION_KEYS = (
|
||||||
|
'can_borrow',
|
||||||
|
'can_insert',
|
||||||
|
'can_edit',
|
||||||
|
'can_delete',
|
||||||
|
'can_manage_users',
|
||||||
|
'can_manage_settings',
|
||||||
|
'can_view_logs',
|
||||||
|
)
|
||||||
|
|
||||||
|
DEFAULT_ACTION_PERMISSIONS = {
|
||||||
|
'can_borrow': True,
|
||||||
|
'can_insert': False,
|
||||||
|
'can_edit': False,
|
||||||
|
'can_delete': False,
|
||||||
|
'can_manage_users': False,
|
||||||
|
'can_manage_settings': False,
|
||||||
|
'can_view_logs': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_PAGE_PERMISSIONS = {
|
||||||
|
'home': True,
|
||||||
|
'tutorial_page': True,
|
||||||
|
'my_borrowed_items': True,
|
||||||
|
'notifications_view': True,
|
||||||
|
'impressum': True,
|
||||||
|
'license': True,
|
||||||
|
'library_view': True,
|
||||||
|
'terminplan': True,
|
||||||
|
'home_admin': False,
|
||||||
|
'upload_admin': False,
|
||||||
|
'library_admin': False,
|
||||||
|
'admin_borrowings': False,
|
||||||
|
'library_loans_admin': False,
|
||||||
|
'admin_damaged_items': False,
|
||||||
|
'admin_audit_dashboard': False,
|
||||||
|
'logs': False,
|
||||||
|
'user_del': False,
|
||||||
|
'register': False,
|
||||||
|
'manage_filters': False,
|
||||||
|
'manage_locations': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
PERMISSION_PRESETS = {
|
||||||
|
'standard_user': {
|
||||||
|
'label': 'Standard (Ausleihe)',
|
||||||
|
'actions': {
|
||||||
|
'can_borrow': True,
|
||||||
|
},
|
||||||
|
'pages': {
|
||||||
|
'home': True,
|
||||||
|
'tutorial_page': True,
|
||||||
|
'my_borrowed_items': True,
|
||||||
|
'notifications_view': True,
|
||||||
|
'impressum': True,
|
||||||
|
'license': True,
|
||||||
|
'library_view': True,
|
||||||
|
'terminplan': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'editor': {
|
||||||
|
'label': 'Editor (Einfügen/Bearbeiten)',
|
||||||
|
'actions': {
|
||||||
|
'can_borrow': True,
|
||||||
|
'can_insert': True,
|
||||||
|
'can_edit': True,
|
||||||
|
},
|
||||||
|
'pages': {
|
||||||
|
'home': True,
|
||||||
|
'tutorial_page': True,
|
||||||
|
'my_borrowed_items': True,
|
||||||
|
'notifications_view': True,
|
||||||
|
'impressum': True,
|
||||||
|
'license': True,
|
||||||
|
'library_view': True,
|
||||||
|
'terminplan': True,
|
||||||
|
'upload_admin': True,
|
||||||
|
'library_admin': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'manager': {
|
||||||
|
'label': 'Manager (inkl. Löschen)',
|
||||||
|
'actions': {
|
||||||
|
'can_borrow': True,
|
||||||
|
'can_insert': True,
|
||||||
|
'can_edit': True,
|
||||||
|
'can_delete': True,
|
||||||
|
'can_manage_settings': True,
|
||||||
|
'can_view_logs': True,
|
||||||
|
},
|
||||||
|
'pages': {
|
||||||
|
'home': True,
|
||||||
|
'tutorial_page': True,
|
||||||
|
'my_borrowed_items': True,
|
||||||
|
'notifications_view': True,
|
||||||
|
'impressum': True,
|
||||||
|
'license': True,
|
||||||
|
'library_view': True,
|
||||||
|
'terminplan': True,
|
||||||
|
'home_admin': True,
|
||||||
|
'upload_admin': True,
|
||||||
|
'library_admin': True,
|
||||||
|
'admin_borrowings': True,
|
||||||
|
'library_loans_admin': True,
|
||||||
|
'admin_damaged_items': True,
|
||||||
|
'admin_audit_dashboard': True,
|
||||||
|
'logs': True,
|
||||||
|
'manage_filters': True,
|
||||||
|
'manage_locations': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'full_access': {
|
||||||
|
'label': 'Vollzugriff',
|
||||||
|
'actions': {
|
||||||
|
'can_borrow': True,
|
||||||
|
'can_insert': True,
|
||||||
|
'can_edit': True,
|
||||||
|
'can_delete': True,
|
||||||
|
'can_manage_users': True,
|
||||||
|
'can_manage_settings': True,
|
||||||
|
'can_view_logs': True,
|
||||||
|
},
|
||||||
|
'pages': {
|
||||||
|
'home': True,
|
||||||
|
'tutorial_page': True,
|
||||||
|
'my_borrowed_items': True,
|
||||||
|
'notifications_view': True,
|
||||||
|
'impressum': True,
|
||||||
|
'license': True,
|
||||||
|
'library_view': True,
|
||||||
|
'terminplan': True,
|
||||||
|
'home_admin': True,
|
||||||
|
'upload_admin': True,
|
||||||
|
'library_admin': True,
|
||||||
|
'admin_borrowings': True,
|
||||||
|
'library_loans_admin': True,
|
||||||
|
'admin_damaged_items': True,
|
||||||
|
'admin_audit_dashboard': True,
|
||||||
|
'logs': True,
|
||||||
|
'user_del': True,
|
||||||
|
'register': True,
|
||||||
|
'manage_filters': True,
|
||||||
|
'manage_locations': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_bool_map(source, defaults):
|
||||||
|
result = dict(defaults)
|
||||||
|
if isinstance(source, dict):
|
||||||
|
for key, value in source.items():
|
||||||
|
result[str(key)] = bool(value)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_permission_preset_definitions():
|
||||||
|
return copy.deepcopy(PERMISSION_PRESETS)
|
||||||
|
|
||||||
|
|
||||||
|
def build_default_permission_payload(preset_key='standard_user'):
|
||||||
|
selected_key = preset_key if preset_key in PERMISSION_PRESETS else 'standard_user'
|
||||||
|
preset = PERMISSION_PRESETS.get(selected_key, {})
|
||||||
|
action_defaults = _normalize_bool_map(preset.get('actions', {}), DEFAULT_ACTION_PERMISSIONS)
|
||||||
|
page_defaults = _normalize_bool_map(preset.get('pages', {}), DEFAULT_PAGE_PERMISSIONS)
|
||||||
|
return {
|
||||||
|
'preset': selected_key,
|
||||||
|
'actions': action_defaults,
|
||||||
|
'pages': page_defaults,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_effective_permissions(username):
|
||||||
|
user = get_user(username)
|
||||||
|
if not user:
|
||||||
|
return build_default_permission_payload('standard_user')
|
||||||
|
|
||||||
|
preset_key = user.get('PermissionPreset') or 'standard_user'
|
||||||
|
payload = build_default_permission_payload(preset_key)
|
||||||
|
payload['actions'] = _normalize_bool_map(user.get('ActionPermissions', {}), payload['actions'])
|
||||||
|
payload['pages'] = _normalize_bool_map(user.get('PagePermissions', {}), payload['pages'])
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def update_user_permissions(username, preset_key, action_permissions=None, page_permissions=None):
|
||||||
|
selected_key = preset_key if preset_key in PERMISSION_PRESETS else 'standard_user'
|
||||||
|
payload = build_default_permission_payload(selected_key)
|
||||||
|
|
||||||
|
if isinstance(action_permissions, dict):
|
||||||
|
for key, value in action_permissions.items():
|
||||||
|
payload['actions'][str(key)] = bool(value)
|
||||||
|
|
||||||
|
if isinstance(page_permissions, dict):
|
||||||
|
for key, value in page_permissions.items():
|
||||||
|
payload['pages'][str(key)] = bool(value)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'PermissionPreset': payload['preset'],
|
||||||
|
'ActionPermissions': payload['actions'],
|
||||||
|
'PagePermissions': payload['pages'],
|
||||||
|
}
|
||||||
|
|
||||||
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||||
|
db = client[cfg.MONGODB_DB]
|
||||||
|
users = db['users']
|
||||||
|
result = users.update_one({'Username': username}, {'$set': update_data})
|
||||||
|
|
||||||
|
if result.matched_count == 0:
|
||||||
|
result = users.update_one({'username': username}, {'$set': update_data})
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
return result.matched_count > 0
|
||||||
|
|
||||||
|
|
||||||
# === FAVORITES MANAGEMENT ===
|
# === FAVORITES MANAGEMENT ===
|
||||||
def get_favorites(username):
|
def get_favorites(username):
|
||||||
"""Return a list of favorite item ObjectId strings for the user."""
|
"""Return a list of favorite item ObjectId strings for the user."""
|
||||||
@@ -144,6 +359,8 @@ def add_user(username, password, name, last_name, is_student=False, student_card
|
|||||||
users = db['users']
|
users = db['users']
|
||||||
if not check_password_strength(password):
|
if not check_password_strength(password):
|
||||||
return False
|
return False
|
||||||
|
permission_defaults = build_default_permission_payload('standard_user')
|
||||||
|
|
||||||
user_doc = {
|
user_doc = {
|
||||||
'Username': username,
|
'Username': username,
|
||||||
'Password': hashing(password),
|
'Password': hashing(password),
|
||||||
@@ -151,7 +368,10 @@ def add_user(username, password, name, last_name, is_student=False, student_card
|
|||||||
'active_ausleihung': None,
|
'active_ausleihung': None,
|
||||||
'name': name,
|
'name': name,
|
||||||
'last_name': last_name,
|
'last_name': last_name,
|
||||||
'IsStudent': bool(is_student)
|
'IsStudent': bool(is_student),
|
||||||
|
'PermissionPreset': permission_defaults['preset'],
|
||||||
|
'ActionPermissions': permission_defaults['actions'],
|
||||||
|
'PagePermissions': permission_defaults['pages'],
|
||||||
}
|
}
|
||||||
|
|
||||||
normalized_card = normalize_student_card_id(student_card_id)
|
normalized_card = normalize_student_card_id(student_card_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user