Phase 3 harden CSRF invalid POST and work access handling

This commit is contained in:
A R R R Associates
2026-06-26 11:41:00 +05:30
parent 4ce5556e9d
commit fc31a5f4e9
7 changed files with 503 additions and 94 deletions
+31 -6
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
from fastapi import APIRouter, Form, Request from fastapi import APIRouter, Form, Request
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from app.core.http_responses import not_found_response
from sqlalchemy import select from sqlalchemy import select
from app.core.db.common import CommonSessionLocal from app.core.db.common import CommonSessionLocal
@@ -17,6 +18,12 @@ from app.modules.core.rbac.services import build_permissions_payload, build_role
router = APIRouter(prefix="/system-settings/rbac", tags=["rbac-ui"]) router = APIRouter(prefix="/system-settings/rbac", tags=["rbac-ui"])
def _csrf_rejected(request: Request):
from app.core.http_responses import forbidden_response
return forbidden_response(request, "CSRF validation failed")
def _redirect_login(): def _redirect_login():
return RedirectResponse(url="/login", status_code=303) return RedirectResponse(url="/login", status_code=303)
@@ -47,6 +54,15 @@ def rbac_dashboard(request: Request):
return RedirectResponse(url="/system-settings/rbac/roles", status_code=303) return RedirectResponse(url="/system-settings/rbac/roles", status_code=303)
# Phase 3 security hardening: the roles list endpoint must not accept direct unsafe POSTs.
# Existing role creation continues to use /roles/new.
@router.post("/roles")
def roles_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.get("/roles") @router.get("/roles")
def roles_list(request: Request, q: str = "", page: int = 1, per_page: int = 10): def roles_list(request: Request, q: str = "", page: int = 1, per_page: int = 10):
db = CommonSessionLocal() db = CommonSessionLocal()
@@ -76,7 +92,10 @@ def role_create_submit(
is_active: str | None = Form(None), is_active: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -124,7 +143,7 @@ def role_permissions_edit(request: Request, role_id: int):
role = db.execute(select(Role).where(Role.id == role_id)).scalar_one_or_none() role = db.execute(select(Role).where(Role.id == role_id)).scalar_one_or_none()
if not role: if not role:
return RedirectResponse(url="/system-settings/rbac/roles", status_code=303) return not_found_response(request, "Role not found")
scope = build_scope(db, current_user) scope = build_scope(db, current_user)
try: try:
@@ -160,7 +179,10 @@ def role_permissions_submit(
permission_ids: list[int] = Form([]), permission_ids: list[int] = Form([]),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -172,7 +194,7 @@ def role_permissions_submit(
role = db.execute(select(Role).where(Role.id == role_id)).scalar_one_or_none() role = db.execute(select(Role).where(Role.id == role_id)).scalar_one_or_none()
if not role: if not role:
return RedirectResponse(url="/system-settings/rbac/roles", status_code=303) return not_found_response(request, "Role not found")
old_permission_ids = db.execute( old_permission_ids = db.execute(
select(RolePermission.permission_id).where(RolePermission.role_id == role.id) select(RolePermission.permission_id).where(RolePermission.role_id == role.id)
@@ -233,7 +255,10 @@ def permission_create_submit(
is_active: str | None = Form(None), is_active: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -265,4 +290,4 @@ def permission_create_submit(
return RedirectResponse(url="/system-settings/rbac/permissions", status_code=303) return RedirectResponse(url="/system-settings/rbac/permissions", status_code=303)
finally: finally:
db.close() db.close()
+213 -48
View File
@@ -135,6 +135,12 @@ from app.modules.employees.service import (
) )
router = APIRouter(prefix="/employees", tags=["employees-ui"]) router = APIRouter(prefix="/employees", tags=["employees-ui"])
def _csrf_rejected(request: Request):
from app.core.http_responses import forbidden_response
return forbidden_response(request, "CSRF validation failed")
portal_router = APIRouter(prefix="/employee", tags=["employee-portal-ui"]) portal_router = APIRouter(prefix="/employee", tags=["employee-portal-ui"])
@@ -416,7 +422,10 @@ def employee_new(request: Request):
@router.post("/new") @router.post("/new")
async def employee_create_submit(request: Request): async def employee_create_submit(request: Request):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -447,6 +456,21 @@ async def employee_create_submit(request: Request):
db.close() db.close()
# Phase 3 security hardening: list pages must not accept direct unsafe POSTs.
# Existing attendance and leave actions continue to use their dedicated action routes.
@router.post("/attendance")
def employee_attendance_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/leaves")
@router.post("/leave")
def employee_leave_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.get("/attendance") @router.get("/attendance")
def employees_attendance_list( def employees_attendance_list(
request: Request, request: Request,
@@ -506,7 +530,10 @@ def employees_attendance_manual_submit(
remarks: str | None = Form(None), remarks: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -541,7 +568,10 @@ def employees_attendance_review_submit(
review_notes: str | None = Form(None), review_notes: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -583,7 +613,10 @@ def employee_leave_types(request: Request, include_inactive: str | None = None):
@router.post("/leave-types/defaults") @router.post("/leave-types/defaults")
def employee_leave_type_defaults(request: Request, csrf_token: str = Form(...)): def employee_leave_type_defaults(request: Request, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -604,7 +637,10 @@ def employee_leave_type_defaults(request: Request, csrf_token: str = Form(...)):
@router.post("/leave-types/new") @router.post("/leave-types/new")
def employee_leave_type_create(request: Request, code: str = Form(...), name: str = Form(...), description: str | None = Form(None), annual_quota_days: int = Form(0), carry_forward_allowed: str | None = Form(None), allow_negative_balance: str | None = Form(None), requires_approval: str | None = Form(None), is_paid: str | None = Form(None), is_active: str | None = Form(None), csrf_token: str = Form(...)): def employee_leave_type_create(request: Request, code: str = Form(...), name: str = Form(...), description: str | None = Form(None), annual_quota_days: int = Form(0), carry_forward_allowed: str | None = Form(None), allow_negative_balance: str | None = Form(None), requires_approval: str | None = Form(None), is_paid: str | None = Form(None), is_active: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -625,7 +661,10 @@ def employee_leave_type_create(request: Request, code: str = Form(...), name: st
@router.post("/leave-types/{leave_type_id}/edit") @router.post("/leave-types/{leave_type_id}/edit")
def employee_leave_type_edit(request: Request, leave_type_id: int, name: str = Form(...), description: str | None = Form(None), annual_quota_days: int = Form(0), carry_forward_allowed: str | None = Form(None), allow_negative_balance: str | None = Form(None), requires_approval: str | None = Form(None), is_paid: str | None = Form(None), is_active: str | None = Form(None), csrf_token: str = Form(...)): def employee_leave_type_edit(request: Request, leave_type_id: int, name: str = Form(...), description: str | None = Form(None), annual_quota_days: int = Form(0), carry_forward_allowed: str | None = Form(None), allow_negative_balance: str | None = Form(None), requires_approval: str | None = Form(None), is_paid: str | None = Form(None), is_active: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -669,7 +708,10 @@ def employee_leave_balances(request: Request, employee_id: int | None = None):
@router.post("/leave-balances/adjust") @router.post("/leave-balances/adjust")
def employee_leave_balance_adjust(request: Request, employee_id: int = Form(...), leave_type_id: int = Form(...), adjusted_days: int = Form(0), notes: str | None = Form(None), csrf_token: str = Form(...)): def employee_leave_balance_adjust(request: Request, employee_id: int = Form(...), leave_type_id: int = Form(...), adjusted_days: int = Form(0), notes: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -712,7 +754,10 @@ def employee_leave_requests(request: Request, employee_id: int | None = None, st
@router.post("/leave/{request_id}/review") @router.post("/leave/{request_id}/review")
def employee_leave_review_submit(request: Request, request_id: int, status: str = Form(...), review_notes: str | None = Form(None), csrf_token: str = Form(...)): def employee_leave_review_submit(request: Request, request_id: int, status: str = Form(...), review_notes: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -798,7 +843,10 @@ def employee_registration_request_approve(
review_notes: str | None = Form(None), review_notes: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -825,7 +873,10 @@ def employee_registration_request_reject(
review_notes: str | None = Form(None), review_notes: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -869,7 +920,10 @@ def employee_onboarding_checklist(request: Request):
@router.post("/onboarding-checklist/defaults") @router.post("/onboarding-checklist/defaults")
def employee_onboarding_checklist_defaults(request: Request, csrf_token: str = Form(...)): def employee_onboarding_checklist_defaults(request: Request, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -891,7 +945,10 @@ def employee_onboarding_checklist_defaults(request: Request, csrf_token: str = F
@router.post("/onboarding-checklist") @router.post("/onboarding-checklist")
async def employee_onboarding_checklist_create(request: Request): async def employee_onboarding_checklist_create(request: Request):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -913,7 +970,10 @@ async def employee_onboarding_checklist_create(request: Request):
@router.post("/onboarding-checklist/{item_id}/edit") @router.post("/onboarding-checklist/{item_id}/edit")
async def employee_onboarding_checklist_edit(request: Request, item_id: int): async def employee_onboarding_checklist_edit(request: Request, item_id: int):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -954,7 +1014,10 @@ def employee_onboarding_tasks(request: Request, employee_id: int | None = None):
@router.post("/{employee_id}/onboarding/generate") @router.post("/{employee_id}/onboarding/generate")
def employee_generate_onboarding(request: Request, employee_id: int, csrf_token: str = Form(...)): def employee_generate_onboarding(request: Request, employee_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -976,7 +1039,10 @@ def employee_generate_onboarding(request: Request, employee_id: int, csrf_token:
@router.post("/onboarding/{task_id}/status") @router.post("/onboarding/{task_id}/status")
def employee_onboarding_task_status(request: Request, task_id: int, status: str = Form(...), review_notes: str | None = Form(None), csrf_token: str = Form(...)): def employee_onboarding_task_status(request: Request, task_id: int, status: str = Form(...), review_notes: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1019,7 +1085,10 @@ def employee_offboarding_requests(request: Request, status: str | None = None):
@router.post("/{employee_id}/offboarding/initiate") @router.post("/{employee_id}/offboarding/initiate")
async def employee_offboarding_initiate(request: Request, employee_id: int): async def employee_offboarding_initiate(request: Request, employee_id: int):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1041,7 +1110,10 @@ async def employee_offboarding_initiate(request: Request, employee_id: int):
@router.post("/offboarding/{request_id}/review") @router.post("/offboarding/{request_id}/review")
def employee_offboarding_review(request: Request, request_id: int, status: str = Form(...), approved_relieving_date: str | None = Form(None), review_notes: str | None = Form(None), csrf_token: str = Form(...)): def employee_offboarding_review(request: Request, request_id: int, status: str = Form(...), approved_relieving_date: str | None = Form(None), review_notes: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1063,7 +1135,10 @@ def employee_offboarding_review(request: Request, request_id: int, status: str =
@router.post("/offboarding/tasks/{task_id}/status") @router.post("/offboarding/tasks/{task_id}/status")
def employee_offboarding_task_status(request: Request, task_id: int, status: str = Form(...), review_notes: str | None = Form(None), csrf_token: str = Form(...)): def employee_offboarding_task_status(request: Request, task_id: int, status: str = Form(...), review_notes: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1085,7 +1160,10 @@ def employee_offboarding_task_status(request: Request, task_id: int, status: str
@router.post("/offboarding/{request_id}/complete") @router.post("/offboarding/{request_id}/complete")
def employee_offboarding_complete(request: Request, request_id: int, csrf_token: str = Form(...)): def employee_offboarding_complete(request: Request, request_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1127,7 +1205,10 @@ def employee_document_types(request: Request, include_inactive: str | None = Non
@router.post("/document-types/defaults") @router.post("/document-types/defaults")
def employee_document_types_defaults(request: Request, csrf_token: str = Form(...)): def employee_document_types_defaults(request: Request, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1149,7 +1230,10 @@ def employee_document_types_defaults(request: Request, csrf_token: str = Form(..
@router.post("/document-types") @router.post("/document-types")
async def employee_document_type_create(request: Request): async def employee_document_type_create(request: Request):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1174,7 +1258,10 @@ async def employee_document_type_create(request: Request):
@router.post("/document-types/{type_id}/edit") @router.post("/document-types/{type_id}/edit")
async def employee_document_type_update(request: Request, type_id: int): async def employee_document_type_update(request: Request, type_id: int):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1216,7 +1303,10 @@ def employee_documents(request: Request, employee_id: int | None = None, status:
@router.post("/{employee_id}/documents/upload") @router.post("/{employee_id}/documents/upload")
async def employee_document_upload(request: Request, employee_id: int, document_file: UploadFile = File(...)): async def employee_document_upload(request: Request, employee_id: int, document_file: UploadFile = File(...)):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1258,7 +1348,10 @@ async def employee_document_upload(request: Request, employee_id: int, document_
@router.post("/documents/{document_id}/review") @router.post("/documents/{document_id}/review")
def employee_document_review(request: Request, document_id: int, status: str = Form(...), verification_notes: str | None = Form(None), csrf_token: str = Form(...)): def employee_document_review(request: Request, document_id: int, status: str = Form(...), verification_notes: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1280,7 +1373,10 @@ def employee_document_review(request: Request, document_id: int, status: str = F
@router.post("/documents/{document_id}/archive") @router.post("/documents/{document_id}/archive")
def employee_document_archive(request: Request, document_id: int, notes: str | None = Form(None), csrf_token: str = Form(...)): def employee_document_archive(request: Request, document_id: int, notes: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1324,7 +1420,10 @@ def employee_self_documents(request: Request):
@portal_router.post("/documents/upload") @portal_router.post("/documents/upload")
async def employee_self_document_upload(request: Request, document_file: UploadFile = File(...)): async def employee_self_document_upload(request: Request, document_file: UploadFile = File(...)):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1398,7 +1497,10 @@ def employee_salary_structures(request: Request, employee_id: int | None = None)
@router.post("/payroll/structures") @router.post("/payroll/structures")
async def employee_salary_structure_submit(request: Request): async def employee_salary_structure_submit(request: Request):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1425,7 +1527,10 @@ async def employee_salary_structure_submit(request: Request):
@router.post("/payroll/structures/{structure_id}/update") @router.post("/payroll/structures/{structure_id}/update")
async def employee_salary_structure_update(request: Request, structure_id: int): async def employee_salary_structure_update(request: Request, structure_id: int):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1468,7 +1573,10 @@ def employee_payroll_runs(request: Request):
@router.post("/payroll/runs") @router.post("/payroll/runs")
def employee_payroll_run_create(request: Request, pay_year: int = Form(...), pay_month: int = Form(...), notes: str | None = Form(None), csrf_token: str = Form(...)): def employee_payroll_run_create(request: Request, pay_year: int = Form(...), pay_month: int = Form(...), notes: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1489,7 +1597,10 @@ def employee_payroll_run_create(request: Request, pay_year: int = Form(...), pay
@router.post("/payroll/runs/{run_id}/generate") @router.post("/payroll/runs/{run_id}/generate")
def employee_payroll_run_generate(request: Request, run_id: int, csrf_token: str = Form(...)): def employee_payroll_run_generate(request: Request, run_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1511,7 +1622,10 @@ def employee_payroll_run_generate(request: Request, run_id: int, csrf_token: str
@router.post("/payroll/runs/{run_id}/approve") @router.post("/payroll/runs/{run_id}/approve")
def employee_payroll_run_approve(request: Request, run_id: int, csrf_token: str = Form(...)): def employee_payroll_run_approve(request: Request, run_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1533,7 +1647,10 @@ def employee_payroll_run_approve(request: Request, run_id: int, csrf_token: str
@router.post("/payroll/runs/{run_id}/paid") @router.post("/payroll/runs/{run_id}/paid")
def employee_payroll_run_paid(request: Request, run_id: int, csrf_token: str = Form(...)): def employee_payroll_run_paid(request: Request, run_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -1680,7 +1797,10 @@ def employee_work_task_assign(
require_permission(db, current_user, "employees.work.manage") require_permission(db, current_user, "employees.work.manage")
except Exception: except Exception:
return _redirect_denied() return _redirect_denied()
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
tenant_id = request.session.get("active_tenant_id") or current_user.tenant_id tenant_id = request.session.get("active_tenant_id") or current_user.tenant_id
branch_id = request.session.get("active_branch_id") branch_id = request.session.get("active_branch_id")
scope = build_employee_scope(db, current_user, tenant_id=tenant_id, branch_id=branch_id) scope = build_employee_scope(db, current_user, tenant_id=tenant_id, branch_id=branch_id)
@@ -1720,7 +1840,10 @@ def employee_my_work_task_status(
require_permission(db, current_user, "employees.work.view_self") require_permission(db, current_user, "employees.work.view_self")
except Exception: except Exception:
return _redirect_denied() return _redirect_denied()
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
tenant_id = request.session.get("active_tenant_id") or current_user.tenant_id tenant_id = request.session.get("active_tenant_id") or current_user.tenant_id
branch_id = request.session.get("active_branch_id") branch_id = request.session.get("active_branch_id")
scope = build_employee_scope(db, current_user, tenant_id=tenant_id, branch_id=branch_id) scope = build_employee_scope(db, current_user, tenant_id=tenant_id, branch_id=branch_id)
@@ -1860,7 +1983,10 @@ def employee_work_task_add_communication(
require_permission(db, current_user, "employees.work.manage") require_permission(db, current_user, "employees.work.manage")
except Exception: except Exception:
return _redirect_denied() return _redirect_denied()
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
tenant_id = request.session.get("active_tenant_id") or current_user.tenant_id tenant_id = request.session.get("active_tenant_id") or current_user.tenant_id
branch_id = request.session.get("active_branch_id") branch_id = request.session.get("active_branch_id")
scope = build_employee_scope(db, current_user, tenant_id=tenant_id, branch_id=branch_id) scope = build_employee_scope(db, current_user, tenant_id=tenant_id, branch_id=branch_id)
@@ -1935,7 +2061,10 @@ def employee_my_work_task_add_communication(
require_permission(db, current_user, "employees.work.view_self") require_permission(db, current_user, "employees.work.view_self")
except Exception: except Exception:
return _redirect_denied() return _redirect_denied()
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
tenant_id = request.session.get("active_tenant_id") or current_user.tenant_id tenant_id = request.session.get("active_tenant_id") or current_user.tenant_id
branch_id = request.session.get("active_branch_id") branch_id = request.session.get("active_branch_id")
scope = build_employee_scope(db, current_user, tenant_id=tenant_id, branch_id=branch_id) scope = build_employee_scope(db, current_user, tenant_id=tenant_id, branch_id=branch_id)
@@ -2026,7 +2155,10 @@ def employee_import_template(request: Request, import_type: str):
@router.post("/imports/preview") @router.post("/imports/preview")
async def employee_import_preview(request: Request, import_type: str = Form(...), import_file: UploadFile = File(...)): async def employee_import_preview(request: Request, import_type: str = Form(...), import_file: UploadFile = File(...)):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2069,7 +2201,10 @@ async def employee_import_preview(request: Request, import_type: str = Form(...)
@router.post("/imports/commit") @router.post("/imports/commit")
def employee_import_commit(request: Request, csrf_token: str = Form(...)): def employee_import_commit(request: Request, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2162,7 +2297,10 @@ def employee_edit(request: Request, employee_id: int):
@router.post("/{employee_id}/edit") @router.post("/{employee_id}/edit")
async def employee_edit_submit(request: Request, employee_id: int): async def employee_edit_submit(request: Request, employee_id: int):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2199,7 +2337,10 @@ async def employee_edit_submit(request: Request, employee_id: int):
@router.post("/{employee_id}/link-user") @router.post("/{employee_id}/link-user")
def employee_link_user_submit(request: Request, employee_id: int, user_id: str | None = Form(None), csrf_token: str = Form(...)): def employee_link_user_submit(request: Request, employee_id: int, user_id: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2231,7 +2372,10 @@ def employee_status_submit(
date_of_leaving: str | None = Form(None), date_of_leaving: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2324,7 +2468,10 @@ def employee_punch_in_submit(
accuracy_meters: str | None = Form(None), accuracy_meters: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2357,7 +2504,10 @@ def employee_punch_out_submit(
accuracy_meters: str | None = Form(None), accuracy_meters: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2405,7 +2555,10 @@ def employee_self_leave(request: Request):
@portal_router.post("/leave/apply") @portal_router.post("/leave/apply")
def employee_self_leave_apply(request: Request, leave_type_id: int = Form(...), from_date: str = Form(...), to_date: str = Form(...), reason: str | None = Form(None), csrf_token: str = Form(...)): def employee_self_leave_apply(request: Request, leave_type_id: int = Form(...), from_date: str = Form(...), to_date: str = Form(...), reason: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2423,7 +2576,10 @@ def employee_self_leave_apply(request: Request, leave_type_id: int = Form(...),
@portal_router.post("/leave/{request_id}/cancel") @portal_router.post("/leave/{request_id}/cancel")
def employee_self_leave_cancel(request: Request, request_id: int, csrf_token: str = Form(...)): def employee_self_leave_cancel(request: Request, request_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2461,7 +2617,10 @@ def employee_self_offboarding(request: Request):
@portal_router.post("/offboarding/request") @portal_router.post("/offboarding/request")
async def employee_self_offboarding_request(request: Request): async def employee_self_offboarding_request(request: Request):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2513,7 +2672,10 @@ def employee_self_profile(request: Request):
@portal_router.post("/profile") @portal_router.post("/profile")
async def employee_self_profile_submit(request: Request): async def employee_self_profile_submit(request: Request):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
@@ -2605,7 +2767,10 @@ def employee_registration_form(request: Request):
@portal_router.post("/register") @portal_router.post("/register")
async def employee_registration_submit(request: Request): async def employee_registration_submit(request: Request):
form = await request.form() form = await request.form()
validate_csrf(request, form.get("csrf_token")) try:
validate_csrf(request, form.get("csrf_token"))
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
current_user = get_current_user(request, db=db) current_user = get_current_user(request, db=db)
+30 -6
View File
@@ -34,6 +34,12 @@ router = APIRouter(prefix="/marketplace", tags=["marketplace-ui"])
public_router = APIRouter(tags=["marketplace-public-ui"]) public_router = APIRouter(tags=["marketplace-public-ui"])
def _csrf_rejected(request: Request):
from app.core.http_responses import forbidden_response
return forbidden_response(request, "CSRF validation failed")
def _public_marketplace_ctx(request: Request, db, **ctx): def _public_marketplace_ctx(request: Request, db, **ctx):
@@ -135,7 +141,10 @@ def marketplace_public_request_service_submit(request: Request, csrf_token: str
return RedirectResponse(url="/marketplace/public-lead", status_code=303) return RedirectResponse(url="/marketplace/public-lead", status_code=303)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
lead = create_marketplace_lead( lead = create_marketplace_lead(
db, db,
lead_name=lead_name, lead_name=lead_name,
@@ -254,7 +263,10 @@ def lead_create(request: Request, csrf_token: str = Form(...), lead_name: str =
user, response = _require_user(request, db, "marketplace_leads.create") user, response = _require_user(request, db, "marketplace_leads.create")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
lead = create_marketplace_lead(db, lead_name=lead_name, business_name=business_name, email=email, mobile=mobile, city=city, state=state, service_category=service_category, service_requested=service_requested, message=message, source=source, priority=priority, estimated_value=estimated_value, created_by_user_id=user.id) lead = create_marketplace_lead(db, lead_name=lead_name, business_name=business_name, email=email, mobile=mobile, city=city, state=state, service_category=service_category, service_requested=service_requested, message=message, source=source, priority=priority, estimated_value=estimated_value, created_by_user_id=user.id)
return RedirectResponse(url=f"/marketplace/leads/{lead.id}", status_code=303) return RedirectResponse(url=f"/marketplace/leads/{lead.id}", status_code=303)
finally: finally:
@@ -274,7 +286,10 @@ def public_lead_form(request: Request):
def public_lead_submit(request: Request, csrf_token: str = Form(...), lead_name: str = Form(...), business_name: str = Form(""), email: str = Form(""), mobile: str = Form(""), city: str = Form(""), state: str = Form(""), service_category: str = Form(""), service_requested: str = Form(...), message: str = Form("")): def public_lead_submit(request: Request, csrf_token: str = Form(...), lead_name: str = Form(...), business_name: str = Form(""), email: str = Form(""), mobile: str = Form(""), city: str = Form(""), state: str = Form(""), service_category: str = Form(""), service_requested: str = Form(...), message: str = Form("")):
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
lead = create_marketplace_lead(db, lead_name=lead_name, business_name=business_name, email=email, mobile=mobile, city=city, state=state, service_category=service_category, service_requested=service_requested, message=message, source="public_website", priority="NORMAL") lead = create_marketplace_lead(db, lead_name=lead_name, business_name=business_name, email=email, mobile=mobile, city=city, state=state, service_category=service_category, service_requested=service_requested, message=message, source="public_website", priority="NORMAL")
return _render(request, "modules/marketplace/templates/marketplace/public_thank_you.html", db, None, title="Thank You", lead=lead) return _render(request, "modules/marketplace/templates/marketplace/public_thank_you.html", db, None, title="Thank You", lead=lead)
finally: finally:
@@ -303,7 +318,10 @@ def lead_assign(request: Request, lead_id: int, csrf_token: str = Form(...), ten
user, response = _require_user(request, db, "marketplace_leads.assign") user, response = _require_user(request, db, "marketplace_leads.assign")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
lead = get_marketplace_lead(db, lead_id) lead = get_marketplace_lead(db, lead_id)
if lead: if lead:
assign_marketplace_lead(db, lead=lead, tenant_id=tenant_id, branch_id=branch_id or None, partner_user_id=partner_user_id or None, notes=notes, assigned_by_user_id=user.id) assign_marketplace_lead(db, lead=lead, tenant_id=tenant_id, branch_id=branch_id or None, partner_user_id=partner_user_id or None, notes=notes, assigned_by_user_id=user.id)
@@ -319,7 +337,10 @@ def lead_status_update(request: Request, lead_id: int, csrf_token: str = Form(..
user, response = _require_user(request, db, "marketplace_leads.update") user, response = _require_user(request, db, "marketplace_leads.update")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
lead = get_marketplace_lead(db, lead_id) lead = get_marketplace_lead(db, lead_id)
if lead: if lead:
update_lead_status(db, lead=lead, status=status, user_id=user.id) update_lead_status(db, lead=lead, status=status, user_id=user.id)
@@ -335,7 +356,10 @@ def lead_convert_client(request: Request, lead_id: int, csrf_token: str = Form(.
user, response = _require_user(request, db, "marketplace_leads.convert") user, response = _require_user(request, db, "marketplace_leads.convert")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
lead = get_marketplace_lead(db, lead_id) lead = get_marketplace_lead(db, lead_id)
if lead: if lead:
client = convert_lead_to_client(db, lead=lead, tenant_id=tenant_id, branch_id=branch_id, partner_user_id=partner_user_id or None, client_code=client_code, user_id=user.id) client = convert_lead_to_client(db, lead=lead, tenant_id=tenant_id, branch_id=branch_id, partner_user_id=partner_user_id or None, client_code=client_code, user_id=user.id)
+34 -7
View File
@@ -46,6 +46,12 @@ from app.modules.core.tenancy.year_control import redirect_if_financial_year_loc
router = APIRouter(prefix="/notice-cases", tags=["notice-cases-ui"]) router = APIRouter(prefix="/notice-cases", tags=["notice-cases-ui"])
def _csrf_rejected(request: Request):
from app.core.http_responses import forbidden_response
return forbidden_response(request, "CSRF validation failed")
def _base_ctx(request: Request, user, db, **ctx): def _base_ctx(request: Request, user, db, **ctx):
base = { base = {
"request": request, "request": request,
@@ -249,7 +255,10 @@ def case_create_submit(
remarks: str = Form(""), remarks: str = Form(""),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user, response = _require_user(request, db, "notice_cases.create") user, response = _require_user(request, db, "notice_cases.create")
@@ -316,7 +325,10 @@ def case_edit_page(request: Request, case_id: int):
@router.post("/{case_id}/edit") @router.post("/{case_id}/edit")
def case_edit_submit(request: Request, case_id: int, department: str = Form("GST"), case_type: str = Form("Notice"), title: str = Form(""), reference_no: str = Form(""), din_ack_no: str = Form(""), notice_date: str = Form(""), due_date: str = Form(""), financial_year: str = Form(""), assessment_year: str = Form(""), period_label: str = Form(""), status: str = Form("open"), priority: str = Form("normal"), assigned_partner_user_id: str = Form(""), assigned_manager_user_id: str = Form(""), assigned_staff_user_id: str = Form(""), issue_summary: str = Form(""), remarks: str = Form(""), csrf_token: str = Form(...)): def case_edit_submit(request: Request, case_id: int, department: str = Form("GST"), case_type: str = Form("Notice"), title: str = Form(""), reference_no: str = Form(""), din_ack_no: str = Form(""), notice_date: str = Form(""), due_date: str = Form(""), financial_year: str = Form(""), assessment_year: str = Form(""), period_label: str = Form(""), status: str = Form("open"), priority: str = Form("normal"), assigned_partner_user_id: str = Form(""), assigned_manager_user_id: str = Form(""), assigned_staff_user_id: str = Form(""), issue_summary: str = Form(""), remarks: str = Form(""), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user, response = _require_user(request, db, "notice_cases.edit") user, response = _require_user(request, db, "notice_cases.edit")
@@ -348,7 +360,10 @@ def case_edit_submit(request: Request, case_id: int, department: str = Form("GST
@router.post("/{case_id}/events") @router.post("/{case_id}/events")
def case_add_event(request: Request, case_id: int, event_type: str = Form("Internal Note"), event_date: str = Form(""), description: str = Form(""), next_due_date: str = Form(""), csrf_token: str = Form(...)): def case_add_event(request: Request, case_id: int, event_type: str = Form("Internal Note"), event_date: str = Form(""), description: str = Form(""), next_due_date: str = Form(""), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user, response = _require_user(request, db, "notice_cases.events.manage") user, response = _require_user(request, db, "notice_cases.events.manage")
@@ -368,7 +383,10 @@ def case_add_event(request: Request, case_id: int, event_type: str = Form("Inter
@router.post("/{case_id}/hearings") @router.post("/{case_id}/hearings")
def case_add_hearing(request: Request, case_id: int, hearing_date: str = Form(""), hearing_time: str = Form(""), venue_or_mode: str = Form(""), officer_name: str = Form(""), agenda: str = Form(""), outcome: str = Form(""), status: str = Form("scheduled"), csrf_token: str = Form(...)): def case_add_hearing(request: Request, case_id: int, hearing_date: str = Form(""), hearing_time: str = Form(""), venue_or_mode: str = Form(""), officer_name: str = Form(""), agenda: str = Form(""), outcome: str = Form(""), status: str = Form("scheduled"), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user, response = _require_user(request, db, "notice_cases.hearings.manage") user, response = _require_user(request, db, "notice_cases.hearings.manage")
@@ -388,7 +406,10 @@ def case_add_hearing(request: Request, case_id: int, hearing_date: str = Form(""
@router.post("/{case_id}/orders") @router.post("/{case_id}/orders")
def case_add_order(request: Request, case_id: int, order_type: str = Form("Other"), order_no: str = Form(""), order_date: str = Form(""), demand_amount: str = Form("0"), tax_amount: str = Form("0"), interest_amount: str = Form("0"), penalty_amount: str = Form("0"), summary: str = Form(""), appeal_due_date: str = Form(""), appeal_filed: str | None = Form(None), csrf_token: str = Form(...)): def case_add_order(request: Request, case_id: int, order_type: str = Form("Other"), order_no: str = Form(""), order_date: str = Form(""), demand_amount: str = Form("0"), tax_amount: str = Form("0"), interest_amount: str = Form("0"), penalty_amount: str = Form("0"), summary: str = Form(""), appeal_due_date: str = Form(""), appeal_filed: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user, response = _require_user(request, db, "notice_cases.orders.manage") user, response = _require_user(request, db, "notice_cases.orders.manage")
@@ -408,7 +429,10 @@ def case_add_order(request: Request, case_id: int, order_type: str = Form("Other
@router.post("/{case_id}/documents/upload") @router.post("/{case_id}/documents/upload")
def case_document_upload(request: Request, case_id: int, title: str = Form(""), document_type: str = Form("OTHER"), description: str = Form(""), event_id: str = Form(""), file: UploadFile = File(...), csrf_token: str = Form(...)): def case_document_upload(request: Request, case_id: int, title: str = Form(""), document_type: str = Form("OTHER"), description: str = Form(""), event_id: str = Form(""), file: UploadFile = File(...), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user, response = _require_user(request, db, "notice_cases.documents.upload") user, response = _require_user(request, db, "notice_cases.documents.upload")
@@ -451,7 +475,10 @@ def case_document_download(request: Request, document_id: int):
@router.post("/documents/{document_id}/delete") @router.post("/documents/{document_id}/delete")
def case_document_delete(request: Request, document_id: int, csrf_token: str = Form(...)): def case_document_delete(request: Request, document_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user, response = _require_user(request, db, "notice_cases.documents.delete") user, response = _require_user(request, db, "notice_cases.documents.delete")
+101 -14
View File
@@ -48,6 +48,12 @@ from app.modules.platform_billing.services import (
router = APIRouter(prefix="/platform-billing", tags=["platform-billing-ui"]) router = APIRouter(prefix="/platform-billing", tags=["platform-billing-ui"])
def _csrf_rejected(request: Request):
from app.core.http_responses import forbidden_response
return forbidden_response(request, "CSRF validation failed")
def _redirect_denied(): def _redirect_denied():
from app.core.http_responses import ui_access_denied from app.core.http_responses import ui_access_denied
return ui_access_denied() return ui_access_denied()
@@ -121,6 +127,45 @@ def dashboard(request: Request):
db.close() db.close()
# Phase 3 security hardening: list pages must not accept direct unsafe POSTs.
# Existing create/update features continue to use their dedicated /new or action routes.
@router.post("/plans")
def plans_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/accounts")
def accounts_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/audit-firm-subscriptions")
def audit_firm_subscriptions_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/client-dashboard-subscriptions")
def client_dashboard_subscriptions_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/consultant-subscriptions")
def consultant_subscriptions_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/subscriptions")
def subscriptions_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/invoices")
def invoices_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.get("/plans") @router.get("/plans")
def plans_list(request: Request, q: str = ""): def plans_list(request: Request, q: str = ""):
db = CommonSessionLocal() db = CommonSessionLocal()
@@ -152,7 +197,10 @@ def plan_create(request: Request, csrf_token: str = Form(...), code: str = Form(
user, response = _require_user(request, db, "platform_plans.manage") user, response = _require_user(request, db, "platform_plans.manage")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
create_platform_plan(db, code=code, name=name, target_account_type=target_account_type, billing_cycle=billing_cycle, base_amount=base_amount, gst_rate=gst_rate, description=description, feature_text=feature_text) create_platform_plan(db, code=code, name=name, target_account_type=target_account_type, billing_cycle=billing_cycle, base_amount=base_amount, gst_rate=gst_rate, description=description, feature_text=feature_text)
return RedirectResponse(url="/platform-billing/plans", status_code=303) return RedirectResponse(url="/platform-billing/plans", status_code=303)
finally: finally:
@@ -190,7 +238,10 @@ def account_create(request: Request, csrf_token: str = Form(...), account_type:
user, response = _require_user(request, db, "platform_billing.create") user, response = _require_user(request, db, "platform_billing.create")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
create_platform_account(db, account_type=account_type, account_code=account_code, display_name=display_name, tenant_id=int(tenant_id) if tenant_id else None, client_id=int(client_id) if client_id else None, consultant_id=int(consultant_id) if consultant_id else None, email=email, mobile=mobile, gstin=gstin, pan=pan, billing_address=billing_address, state=state, notes=notes, user_id=user.id) create_platform_account(db, account_type=account_type, account_code=account_code, display_name=display_name, tenant_id=int(tenant_id) if tenant_id else None, client_id=int(client_id) if client_id else None, consultant_id=int(consultant_id) if consultant_id else None, email=email, mobile=mobile, gstin=gstin, pan=pan, billing_address=billing_address, state=state, notes=notes, user_id=user.id)
return RedirectResponse(url="/platform-billing/accounts", status_code=303) return RedirectResponse(url="/platform-billing/accounts", status_code=303)
finally: finally:
@@ -228,7 +279,10 @@ def audit_firm_accounts_sync(request: Request, csrf_token: str = Form(...)):
user, response = _require_user(request, db, "platform_subscriptions.manage") user, response = _require_user(request, db, "platform_subscriptions.manage")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = sync_audit_firm_billing_accounts(db, user_id=user.id) result = sync_audit_firm_billing_accounts(db, user_id=user.id)
return RedirectResponse(url=f"/platform-billing/audit-firm-subscriptions?synced=created-{result['created']}-updated-{result['updated']}", status_code=303) return RedirectResponse(url=f"/platform-billing/audit-firm-subscriptions?synced=created-{result['created']}-updated-{result['updated']}", status_code=303)
finally: finally:
@@ -264,7 +318,10 @@ def audit_firm_generate_submit(request: Request, csrf_token: str = Form(...), su
user, response = _require_user(request, db, "platform_billing.generate") user, response = _require_user(request, db, "platform_billing.generate")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = generate_audit_firm_subscription_invoices( result = generate_audit_firm_subscription_invoices(
db, db,
subscription_ids=subscription_ids, subscription_ids=subscription_ids,
@@ -329,7 +386,10 @@ def client_dashboard_accounts_sync(request: Request, csrf_token: str = Form(...)
user, response = _require_user(request, db, "platform_subscriptions.manage") user, response = _require_user(request, db, "platform_subscriptions.manage")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = sync_client_dashboard_billing_accounts(db, user_id=user.id) result = sync_client_dashboard_billing_accounts(db, user_id=user.id)
return RedirectResponse(url=f"/platform-billing/client-dashboard-subscriptions?synced=created-{result['created']}-updated-{result['updated']}", status_code=303) return RedirectResponse(url=f"/platform-billing/client-dashboard-subscriptions?synced=created-{result['created']}-updated-{result['updated']}", status_code=303)
finally: finally:
@@ -343,7 +403,10 @@ def client_dashboard_subscription_create(request: Request, csrf_token: str = For
user, response = _require_user(request, db, "platform_subscriptions.manage") user, response = _require_user(request, db, "platform_subscriptions.manage")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
create_platform_subscription( create_platform_subscription(
db, db,
account_id=account_id, account_id=account_id,
@@ -392,7 +455,10 @@ def client_dashboard_generate_submit(request: Request, csrf_token: str = Form(..
user, response = _require_user(request, db, "platform_billing.generate") user, response = _require_user(request, db, "platform_billing.generate")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = generate_client_dashboard_subscription_invoices( result = generate_client_dashboard_subscription_invoices(
db, db,
subscription_ids=subscription_ids, subscription_ids=subscription_ids,
@@ -456,7 +522,10 @@ def consultant_accounts_sync(request: Request, csrf_token: str = Form(...)):
user, response = _require_user(request, db, "platform_subscriptions.manage") user, response = _require_user(request, db, "platform_subscriptions.manage")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = sync_consultant_billing_accounts(db, user_id=user.id) result = sync_consultant_billing_accounts(db, user_id=user.id)
return RedirectResponse(url=f"/platform-billing/consultant-subscriptions?synced=created-{result['created']}-updated-{result['updated']}", status_code=303) return RedirectResponse(url=f"/platform-billing/consultant-subscriptions?synced=created-{result['created']}-updated-{result['updated']}", status_code=303)
finally: finally:
@@ -470,7 +539,10 @@ def consultant_subscription_create(request: Request, csrf_token: str = Form(...)
user, response = _require_user(request, db, "platform_subscriptions.manage") user, response = _require_user(request, db, "platform_subscriptions.manage")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
create_platform_subscription( create_platform_subscription(
db, db,
account_id=account_id, account_id=account_id,
@@ -519,7 +591,10 @@ def consultant_generate_submit(request: Request, csrf_token: str = Form(...), su
user, response = _require_user(request, db, "platform_billing.generate") user, response = _require_user(request, db, "platform_billing.generate")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = generate_consultant_subscription_invoices( result = generate_consultant_subscription_invoices(
db, db,
subscription_ids=subscription_ids, subscription_ids=subscription_ids,
@@ -580,7 +655,10 @@ def subscription_create(request: Request, csrf_token: str = Form(...), account_i
user, response = _require_user(request, db, "platform_subscriptions.manage") user, response = _require_user(request, db, "platform_subscriptions.manage")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
create_platform_subscription(db, account_id=account_id, plan_id=plan_id, subscription_code=subscription_code, start_date=parse_date(start_date), end_date=parse_date(end_date), billing_cycle=billing_cycle, amount=amount, gst_rate=gst_rate, auto_generate_invoice=auto_generate_invoice == "on", notes=notes, user_id=user.id) create_platform_subscription(db, account_id=account_id, plan_id=plan_id, subscription_code=subscription_code, start_date=parse_date(start_date), end_date=parse_date(end_date), billing_cycle=billing_cycle, amount=amount, gst_rate=gst_rate, auto_generate_invoice=auto_generate_invoice == "on", notes=notes, user_id=user.id)
return RedirectResponse(url="/platform-billing/subscriptions", status_code=303) return RedirectResponse(url="/platform-billing/subscriptions", status_code=303)
finally: finally:
@@ -618,7 +696,10 @@ def invoice_create(request: Request, csrf_token: str = Form(...), account_id: in
user, response = _require_user(request, db, "platform_billing.create") user, response = _require_user(request, db, "platform_billing.create")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
invoice = create_platform_invoice( invoice = create_platform_invoice(
db, db,
account_id=account_id, account_id=account_id,
@@ -660,7 +741,10 @@ def invoice_post(request: Request, invoice_id: int, csrf_token: str = Form(...))
user, response = _require_user(request, db, "platform_billing.post") user, response = _require_user(request, db, "platform_billing.post")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
invoice = get_platform_invoice(db, invoice_id) invoice = get_platform_invoice(db, invoice_id)
if invoice and invoice.status == "DRAFT": if invoice and invoice.status == "DRAFT":
post_platform_invoice(db, invoice, user.id) post_platform_invoice(db, invoice, user.id)
@@ -676,7 +760,10 @@ def payment_create(request: Request, invoice_id: int, csrf_token: str = Form(...
user, response = _require_user(request, db, "platform_billing.payment.create") user, response = _require_user(request, db, "platform_billing.payment.create")
if response: if response:
return response return response
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
invoice = get_platform_invoice(db, invoice_id) invoice = get_platform_invoice(db, invoice_id)
if invoice: if invoice:
record_platform_payment(db, invoice=invoice, amount=amount, mode=mode, reference_no=reference_no, notes=notes, user_id=user.id) record_platform_payment(db, invoice=invoice, amount=amount, mode=mode, reference_no=reference_no, notes=notes, user_id=user.id)
+69 -11
View File
@@ -25,6 +25,12 @@ from app.modules.system_settings.year_backup_service import build_year_backup_ex
router = APIRouter(prefix="/system-settings", tags=["system-settings-ui"]) router = APIRouter(prefix="/system-settings", tags=["system-settings-ui"])
def _csrf_rejected(request: Request):
from app.core.http_responses import forbidden_response
return forbidden_response(request, "CSRF validation failed")
def _base_ctx(request: Request, user, db, **ctx): def _base_ctx(request: Request, user, db, **ctx):
base = { base = {
"request": request, "request": request,
@@ -172,6 +178,25 @@ def dashboard(request: Request):
# ----------------------------- # -----------------------------
# Tenant - System Admin only # Tenant - System Admin only
# ----------------------------- # -----------------------------
# Phase 3 security hardening: list/index pages must not accept direct unsafe POSTs.
# Existing create/update features continue to use their dedicated /new or action routes.
@router.post("/tenants")
def tenants_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/branches")
def branches_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/financial-years")
def financial_years_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.get("/tenants") @router.get("/tenants")
def tenants_list(request: Request, q: str = "", page: int = 1, per_page: int = 10): def tenants_list(request: Request, q: str = "", page: int = 1, per_page: int = 10):
db = CommonSessionLocal() db = CommonSessionLocal()
@@ -222,7 +247,10 @@ def tenant_create_submit(
default_storage_mode: str = Form("local_only"), default_storage_mode: str = Form("local_only"),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -296,7 +324,10 @@ def tenant_edit_submit(
is_active: str | None = Form(None), is_active: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -432,7 +463,10 @@ def branch_create_submit(
attendance_allowed_ip_csv: str = Form(""), attendance_allowed_ip_csv: str = Form(""),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -627,7 +661,10 @@ def branch_edit_submit(
lockout_attempts: str = Form("5"), lockout_attempts: str = Form("5"),
lockout_minutes: str = Form("15"), lockout_minutes: str = Form("15"),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -881,7 +918,10 @@ async def branding_save(
favicon_file: UploadFile | None = File(None), favicon_file: UploadFile | None = File(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -1043,7 +1083,10 @@ def financial_year_create_submit(
is_current: str | None = Form(None), is_current: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -1140,7 +1183,10 @@ def financial_year_edit_submit(
end_date: str = Form(...), end_date: str = Form(...),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -1182,7 +1228,10 @@ def financial_year_edit_submit(
@router.post("/financial-years/{fy_id}/make-current") @router.post("/financial-years/{fy_id}/make-current")
def financial_year_make_current(request: Request, fy_id: int, csrf_token: str = Form(...)): def financial_year_make_current(request: Request, fy_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -1209,7 +1258,10 @@ def financial_year_make_current(request: Request, fy_id: int, csrf_token: str =
@router.post("/financial-years/{fy_id}/lock") @router.post("/financial-years/{fy_id}/lock")
def financial_year_lock(request: Request, fy_id: int, csrf_token: str = Form(...)): def financial_year_lock(request: Request, fy_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -1232,7 +1284,10 @@ def financial_year_lock(request: Request, fy_id: int, csrf_token: str = Form(...
@router.post("/financial-years/{fy_id}/unlock") @router.post("/financial-years/{fy_id}/unlock")
def financial_year_unlock(request: Request, fy_id: int, csrf_token: str = Form(...)): def financial_year_unlock(request: Request, fy_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -1289,7 +1344,10 @@ def financial_year_backup_page(request: Request, fy_id: int):
@router.post("/financial-years/{fy_id}/backup/export") @router.post("/financial-years/{fy_id}/backup/export")
def financial_year_backup_export_submit(request: Request, fy_id: int, csrf_token: str = Form(...)): def financial_year_backup_export_submit(request: Request, fy_id: int, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
+25 -2
View File
@@ -14,6 +14,12 @@ from app.modules.work_detail.service import get_task_for_action, load_unified_en
router = APIRouter(prefix="/work", tags=["unified-work-detail-ui"]) router = APIRouter(prefix="/work", tags=["unified-work-detail-ui"])
def _csrf_rejected(request: Request):
from app.core.http_responses import forbidden_response
return forbidden_response(request, "CSRF validation failed")
def _base_ctx(request: Request, db, user, **ctx): def _base_ctx(request: Request, db, user, **ctx):
base = { base = {
"request": request, "request": request,
@@ -56,6 +62,15 @@ def unified_work_board(request: Request):
db.close() db.close()
# Phase 3 security hardening: /work/engagements is a listing alias, not a create endpoint.
# Existing engagement creation remains in the services module.
@router.post("/engagements")
def unified_engagements_post_rejected(request: Request):
return _csrf_rejected(request)
@router.get("/engagements") @router.get("/engagements")
def unified_engagements_index(request: Request): def unified_engagements_index(request: Request):
"""Compatibility alias for /work/engagements. """Compatibility alias for /work/engagements.
@@ -79,6 +94,8 @@ def unified_engagement_detail(request: Request, engagement_id: int):
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
if not user: if not user:
return RedirectResponse(url="/login", status_code=303) return RedirectResponse(url="/login", status_code=303)
if "Client" in set(get_user_roles(db, user.id)):
return forbidden_response(request, "Access denied")
detail = load_unified_engagement_detail(db, request=request, user=user, engagement_id=engagement_id) detail = load_unified_engagement_detail(db, request=request, user=user, engagement_id=engagement_id)
if not detail: if not detail:
return not_found_response(request, "Work engagement not found or access denied") return not_found_response(request, "Work engagement not found or access denied")
@@ -92,7 +109,10 @@ def unified_engagement_detail(request: Request, engagement_id: int):
@router.post("/tasks/{task_id}/status") @router.post("/tasks/{task_id}/status")
async def unified_task_status_update(request: Request, task_id: int, csrf_token: str = Form(...), status: str = Form(...), priority: str = Form("")): async def unified_task_status_update(request: Request, task_id: int, csrf_token: str = Form(...), status: str = Form(...), priority: str = Form("")):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)
@@ -122,7 +142,10 @@ async def unified_task_comment_add(
visibility: str = Form("internal"), visibility: str = Form("internal"),
message: str = Form(""), message: str = Form(""),
): ):
validate_csrf(request, csrf_token) try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
user = get_current_user(request, db=db) user = get_current_user(request, db=db)