"""DDA session users and RBAC (Phase 7).""" from __future__ import annotations import os from typing import Iterable, Optional from fastapi import Depends, HTTPException, Request from sqlalchemy.orm import Session from ..auth import get_or_create_guest_user, get_user_by_id, get_user_from_token from ..database import get_db from ..models import User DDA_SESSION_COOKIE = "dda_session_id" COOKIE_NAME = "satellite_token" ROLE_RANK = { "viewer": 0, "uploader": 1, "analyst": 2, "admin": 3, } TRAINING_EXPORT_KEY = os.environ.get("DDA_TRAINING_EXPORT_KEY", "").strip() def _session_email(session_id: str) -> str: safe = "".join(c for c in session_id if c.isalnum() or c in "-_")[:64] return f"session-{safe}@dda.local" def get_user_role(db: Session, user: User) -> str: role = getattr(user, "role", None) if role: return str(role) try: from sqlalchemy import text as sa_text row = db.execute( sa_text("SELECT role FROM users WHERE id = :uid"), {"uid": user.id}, ).fetchone() if row and row[0]: return str(row[0]) except Exception: pass return "analyst" def get_dda_user(request: Request, db: Session) -> User: """Resolve user: JWT login > per-browser session > shared guest.""" auth_header = request.headers.get("authorization", "") if auth_header.lower().startswith("bearer "): token = auth_header.split(" ", 1)[1].strip() user = get_user_from_token(token, db) if user: return user token = request.cookies.get(COOKIE_NAME) if token: user = get_user_from_token(token, db) if user and not user.email.startswith("__guest__"): return user session_id = request.cookies.get(DDA_SESSION_COOKIE) if session_id: email = _session_email(session_id) from ..auth import get_user_by_email, get_password_hash user = get_user_by_email(db, email) if user: return user user = User( email=email, hashed_password=get_password_hash("session-not-used"), full_name=f"Session {session_id[:8]}", ) db.add(user) db.commit() db.refresh(user) try: from sqlalchemy import text as sa_text db.execute( sa_text("UPDATE users SET role = 'analyst' WHERE id = :uid"), {"uid": user.id}, ) db.commit() except Exception: db.rollback() return user return get_or_create_guest_user(db) def current_dda_user(request: Request, db: Session = Depends(get_db)) -> User: return get_dda_user(request, db) def require_min_role(user: User, db: Session, minimum: str) -> None: role = get_user_role(db, user) need = ROLE_RANK.get(minimum, 99) have = ROLE_RANK.get(role, 0) if have < need: raise HTTPException( status_code=403, detail=f"Role '{role}' cannot perform this action (requires {minimum} or higher).", ) def require_admin_or_key(request: Request, user: User, db: Session) -> None: key = request.headers.get("x-dda-training-key", "").strip() if TRAINING_EXPORT_KEY and key == TRAINING_EXPORT_KEY: return require_min_role(user, db, "admin") def seed_dda_admin(db: Session) -> None: """Optional admin account from env (dev/UAT).""" email = os.environ.get("DDA_ADMIN_EMAIL", "").strip() password = os.environ.get("DDA_ADMIN_PASSWORD", "").strip() if not email or not password: return from ..auth import get_password_hash, get_user_by_email user = get_user_by_email(db, email) if not user: user = User( email=email, hashed_password=get_password_hash(password), full_name=os.environ.get("DDA_ADMIN_NAME", "DDA Admin"), ) db.add(user) db.commit() db.refresh(user) try: from sqlalchemy import text as sa_text db.execute( sa_text("UPDATE users SET role = 'admin' WHERE id = :uid"), {"uid": user.id}, ) db.commit() except Exception: db.rollback()