feat: 3-tier role system across all platform apps
Portal: - Replace is_admin boolean with role column (admin/advanced/standard) - Settings UI: 3-tier portal role select + per-app role dropdowns - /portal-return endpoint: re-establishes session from JWT for sub-app back-links - /api/internal/nv-users: internal endpoint for NetworkView user sync - portal/migrate_roles.py: one-time DB migration script - Role badges (admin/advanced/standard) in topbar and settings table DigiServer: - Add editor and viewer roles (portal advanced->editor, standard->viewer) - PlaylistPermission model: grant viewer users edit access to specific playlists - app/utils/access.py: shared editor_required, admin_required, can_edit_playlist helpers - Content routes: editor_required on create/delete, per-playlist permission check on mutations - Admin: playlist_permissions route + template to manage viewer playlist grants - Base template: hide Admin nav for viewers, Portal button (⬡) returns to portal - content_list_new: hide create/delete for viewers; Manage vs View button per permission - manage_playlist_content: view-only mode when user lacks edit permission NetworkView: - backend/src/middleware/rbac.js: requireRole + requireWriteAccess helpers - site_permissions table: one site per advanced user - All mutating routes guarded (admin=all, advanced=assigned site, viewer=read-only) - Portal SSO auto-upsert: user row synced from X-Auth-Role on every request - GET /api/users: merges portal users list with local NV data (all 4 portal users visible) - GET/PUT /api/users/:id/site-permission: assign site to advanced user - Settings Users tab: role badges, site dropdown for advanced, (portal only) indicator - Sidebar: ⬡ Portal button between Settings and Logout - Frontend build: VITE_API_BASE=/networkview/api now set in start-dev.sh IT Assets / Server Monitor: - portal_sso.py updated: map advanced->editor/viewer, standard->readonly - AdminUser model: add editor role + is_editor property
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Shared role / access helpers for DigiServer blueprints.
|
||||
|
||||
Import these instead of duplicating decorators in every blueprint.
|
||||
"""
|
||||
from functools import wraps
|
||||
from flask import abort, flash, redirect, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
|
||||
# ── Role-gate decorators ──────────────────────────────────────────────────────
|
||||
|
||||
def editor_required(f):
|
||||
"""Allow admin and editor roles; redirect viewers with a flash message."""
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
if not current_user.is_authenticated:
|
||||
return redirect(url_for('auth.login'))
|
||||
if current_user.role not in ('admin', 'editor'):
|
||||
flash('You need editor or admin privileges to perform this action.', 'danger')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
|
||||
|
||||
def admin_required(f):
|
||||
"""Allow admin role only."""
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
if not current_user.is_authenticated:
|
||||
return redirect(url_for('auth.login'))
|
||||
if current_user.role != 'admin':
|
||||
flash('Administrator access required.', 'danger')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
|
||||
|
||||
# ── Playlist permission check ─────────────────────────────────────────────────
|
||||
|
||||
def can_edit_playlist(user, playlist_id: int) -> bool:
|
||||
"""
|
||||
Return True if *user* is allowed to edit the given playlist.
|
||||
|
||||
- admin / editor → always True
|
||||
- viewer → True only when a PlaylistPermission row exists
|
||||
"""
|
||||
if user.role in ('admin', 'editor'):
|
||||
return True
|
||||
from app.models.playlist_permission import PlaylistPermission
|
||||
return PlaylistPermission.query.filter_by(
|
||||
user_id=user.id, playlist_id=playlist_id
|
||||
).first() is not None
|
||||
|
||||
|
||||
def get_editable_playlist_ids(user) -> set:
|
||||
"""Return the set of playlist IDs the user may edit (used in list views)."""
|
||||
if user.role in ('admin', 'editor'):
|
||||
# Import here to avoid circular imports at module load time
|
||||
from app.models.playlist import Playlist
|
||||
return {p.id for p in Playlist.query.with_entities(Playlist.id).all()}
|
||||
from app.models.playlist_permission import PlaylistPermission
|
||||
rows = PlaylistPermission.query.filter_by(user_id=user.id).all()
|
||||
return {r.playlist_id for r in rows}
|
||||
Reference in New Issue
Block a user