Phase 2 fix remaining RBAC safe responses and work route aliases

This commit is contained in:
A R R R Associates
2026-06-25 22:30:49 +05:30
parent b97af0ea1e
commit 4ce5556e9d
4 changed files with 64 additions and 17 deletions
+12 -1
View File
@@ -904,8 +904,19 @@ def partner_branch_storage_dashboard(request: Request):
It intentionally reuses the same storage node data model and keeps the full
Storage Nodes screen intact. Partner/Branch Manager users are automatically
scoped to their own branch.
scoped to their own branch. Staff users receive a clear 403 instead of a
generic dashboard page.
"""
db = CommonSessionLocal()
try:
user, response = _require_user(request, db, "documents.view")
if response:
return response
scope = build_document_scope(request, db, user)
if not _can_manage_branch_storage(scope):
return _redirect_denied()
finally:
db.close()
return storage_nodes(request)
+15 -14
View File
@@ -4,6 +4,7 @@ from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Form, Request
from fastapi.responses import RedirectResponse
from app.core.http_responses import forbidden_response
from sqlalchemy import desc, func, or_, select
from app.core.db.common import CommonSessionLocal
@@ -72,7 +73,7 @@ def email_audit_dashboard_page(request: Request):
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
now = datetime.now(timezone.utc)
since_24h = now - timedelta(hours=24)
@@ -197,7 +198,7 @@ def email_settings_page(request: Request, flash: str | None = None):
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
if not tenant_id:
return RedirectResponse(url="/system-settings", status_code=303)
@@ -258,7 +259,7 @@ def save_email_settings(
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
setting = get_or_create_email_setting(db, int(tenant_id), branch_id, actor_user_id=int(user.id))
setting.smtp_host = smtp_host.strip() or None
@@ -305,7 +306,7 @@ def test_email_settings(request: Request, csrf_token: str = Form(...), test_emai
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
recipient = (test_email or getattr(user, "email", "")).strip()
send_template_email(
@@ -333,7 +334,7 @@ def email_logs_page(request: Request):
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
q = select(EmailLog).where(EmailLog.tenant_id == tenant_id)
if branch_id:
@@ -356,7 +357,7 @@ def email_queue_page(request: Request, flash: str | None = None):
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
q = select(EmailLog).where(EmailLog.tenant_id == tenant_id, EmailLog.status.in_(["PENDING", "FAILED"]))
if branch_id:
@@ -379,7 +380,7 @@ def process_email_queue_page(request: Request, csrf_token: str = Form(...), limi
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
result = process_pending_email_queue(db, tenant_id=tenant_id, branch_id=branch_id, limit=limit)
db.commit()
@@ -397,7 +398,7 @@ def email_inbox_page(request: Request, flash: str | None = None):
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
q = select(EmailIncomingMessage).where(EmailIncomingMessage.tenant_id == tenant_id)
if branch_id:
@@ -427,7 +428,7 @@ def fetch_email_inbox_page(
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
setting = get_or_create_email_setting(db, int(tenant_id), branch_id, actor_user_id=int(user.id))
result = fetch_incoming_emails(
@@ -458,7 +459,7 @@ def email_inbox_detail_page(request: Request, message_id: int):
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
filters = [EmailIncomingMessage.id == message_id, EmailIncomingMessage.tenant_id == tenant_id]
if branch_id:
@@ -503,7 +504,7 @@ def email_inbox_map_all(request: Request, csrf_token: str = Form(...), limit: in
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
result = auto_map_unmapped_emails(db, tenant_id=tenant_id, branch_id=branch_id, limit=limit)
db.commit()
@@ -529,7 +530,7 @@ def email_inbox_manual_map_page(
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
filters = [EmailIncomingMessage.id == message_id, EmailIncomingMessage.tenant_id == tenant_id]
if branch_id:
@@ -557,7 +558,7 @@ def email_templates_page(request: Request):
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
seed_default_email_templates(db, tenant_id=tenant_id, branch_id=branch_id)
db.commit()
@@ -590,7 +591,7 @@ def update_email_template(
if not user:
return _redirect_login()
if not _user_can_manage_email(db, user):
return RedirectResponse(url="/employee/dashboard", status_code=303)
return forbidden_response(request, "Access denied: email settings require administrator permission")
tenant_id, branch_id = _scope_from_request(request, user)
row = db.execute(
select(EmailTemplate).where(
+2 -1
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
from fastapi import APIRouter, Form, Request
from fastapi.responses import RedirectResponse
from app.core.http_responses import not_found_response
from app.core.db.common import CommonSessionLocal
from app.core.security.csrf import get_or_create_csrf_token, validate_csrf
@@ -289,7 +290,7 @@ def lead_detail(request: Request, lead_id: int):
return response
lead = get_marketplace_lead(db, lead_id)
if not lead:
return RedirectResponse(url="/marketplace/leads", status_code=303)
return not_found_response(request, "Marketplace lead not found")
return _render(request, "modules/marketplace/templates/marketplace/lead_detail.html", db, user, title=f"Lead {lead.lead_no}", lead=lead, audit_firms=list_reference_audit_firms(db), branches=list_reference_branches(db, lead.assigned_tenant_id), partners=list_reference_partners(db, lead.assigned_tenant_id), can_assign=_has_perm(db, user, "marketplace_leads.assign"), can_update=_has_perm(db, user, "marketplace_leads.update"), can_convert=_has_perm(db, user, "marketplace_leads.convert"))
finally:
db.close()
+35 -1
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
from fastapi import APIRouter, Form, Request
from fastapi.responses import RedirectResponse
from app.core.http_responses import forbidden_response, not_found_response
from app.core.db.common import CommonSessionLocal
from app.core.security.csrf import get_or_create_csrf_token, validate_csrf
@@ -38,6 +39,39 @@ def _fallback_for_user(db, user) -> str:
return "/employee/work"
@router.get("")
def unified_work_board(request: Request):
"""Compatibility alias for the common /work route.
Existing functionality remains in the Services Work Tracker module.
This route only prevents stale bookmarks/tests from receiving a 404.
"""
db = CommonSessionLocal()
try:
user = get_current_user(request, db=db)
if not user:
return RedirectResponse(url="/login", status_code=303)
return RedirectResponse(url="/services/work-tracker", status_code=303)
finally:
db.close()
@router.get("/engagements")
def unified_engagements_index(request: Request):
"""Compatibility alias for /work/engagements.
Engagement listing is maintained under /services/engagements.
"""
db = CommonSessionLocal()
try:
user = get_current_user(request, db=db)
if not user:
return RedirectResponse(url="/login", status_code=303)
return RedirectResponse(url="/services/engagements", status_code=303)
finally:
db.close()
@router.get("/engagements/{engagement_id}")
def unified_engagement_detail(request: Request, engagement_id: int):
db = CommonSessionLocal()
@@ -47,7 +81,7 @@ def unified_engagement_detail(request: Request, engagement_id: int):
return RedirectResponse(url="/login", status_code=303)
detail = load_unified_engagement_detail(db, request=request, user=user, engagement_id=engagement_id)
if not detail:
return RedirectResponse(url=_fallback_for_user(db, user), status_code=303)
return not_found_response(request, "Work engagement not found or access denied")
return templates.TemplateResponse(
"modules/work_detail/templates/work_detail/engagement_detail.html",
_base_ctx(request, db, user, title="Work Details", **detail),