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:
@@ -1,5 +1,6 @@
|
||||
from flask import Blueprint, request, make_response, current_app
|
||||
from flask import Blueprint, request, make_response, current_app, jsonify
|
||||
import jwt
|
||||
import secrets as _secrets
|
||||
|
||||
bp = Blueprint('api', __name__, url_prefix='/api')
|
||||
|
||||
@@ -70,3 +71,38 @@ def _app_from_uri(uri):
|
||||
if uri.startswith('/srvmonitor/'):
|
||||
return 'srvmonitor'
|
||||
return None
|
||||
|
||||
|
||||
@bp.route('/internal/nv-users')
|
||||
def nv_users_internal():
|
||||
"""
|
||||
Internal endpoint for the NetworkView backend to fetch all portal users
|
||||
that have NetworkView access, together with their assigned NV role.
|
||||
Protected by X-Internal-Token header (shared INTERNAL_SYNC_SECRET).
|
||||
"""
|
||||
secret = current_app.config.get('INTERNAL_SYNC_SECRET', '')
|
||||
provided = request.headers.get('X-Internal-Token', '')
|
||||
if not secret or not _secrets.compare_digest(provided, provided and secret):
|
||||
# constant-time compare — reject if secret mismatch or empty
|
||||
if not secret or not provided or not _secrets.compare_digest(provided, secret):
|
||||
return jsonify({'error': 'forbidden'}), 403
|
||||
|
||||
from app.models.user import PortalUser
|
||||
from app.models.app_access import AppAccess
|
||||
|
||||
users = PortalUser.query.filter_by(is_active=True).order_by(PortalUser.username).all()
|
||||
result = []
|
||||
for u in users:
|
||||
nv_access = AppAccess.query.filter_by(
|
||||
user_id=u.id, app_name='networkview', is_active=True
|
||||
).first()
|
||||
if nv_access is None:
|
||||
continue # skip users who have no NV access at all
|
||||
result.append({
|
||||
'portal_id': str(u.id),
|
||||
'username': u.username,
|
||||
'email': u.email,
|
||||
'portal_role': u.role,
|
||||
'nv_role': nv_access.app_role or u.role, # per-app override or portal role
|
||||
})
|
||||
return jsonify(result)
|
||||
|
||||
Reference in New Issue
Block a user