Grant oversight_admin write access to campaigns and proofs
Oversight admins can now create campaigns, upload proofs, and flag/resolve issues when they have an agency assigned. They retain all existing cross-agency read access for analytics, auditing, and user management. Oversight admins without an agency see a read-only campaigns view. Changes: - Add oversight_admin to canWrite permission in UserContext - Guard readOnly for oversight_admin without agency in App.tsx - Remove oversight_admin block from require_write_access dependency - Remove WebSocket oversight_admin upload block in main.py - Require agency for oversight_admin campaign creation in routes.py Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0348693ebd
commit
0432635153
5 changed files with 10 additions and 24 deletions
|
|
@ -149,8 +149,8 @@ async def create_campaign(
|
|||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_write_access),
|
||||
):
|
||||
"""Create a new campaign. Blocked for oversight_admin."""
|
||||
if current_user.agency_id is None and current_user.role not in ("super_admin", "oversight_admin"):
|
||||
"""Create a new campaign."""
|
||||
if current_user.agency_id is None and current_user.role != "super_admin":
|
||||
raise HTTPException(status_code=403, detail="You must be assigned to an agency before creating campaigns.")
|
||||
repo = CampaignRepository(db)
|
||||
|
||||
|
|
@ -222,7 +222,7 @@ async def update_campaign(
|
|||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_write_access),
|
||||
):
|
||||
"""Update a campaign. Blocked for oversight_admin."""
|
||||
"""Update a campaign. """
|
||||
repo = CampaignRepository(db)
|
||||
|
||||
# Verify campaign exists and user has access
|
||||
|
|
@ -265,7 +265,7 @@ async def delete_campaign(
|
|||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_write_access),
|
||||
):
|
||||
"""Delete a campaign and all associated files. Blocked for oversight_admin."""
|
||||
"""Delete a campaign and all associated files. """
|
||||
repo = CampaignRepository(db)
|
||||
|
||||
campaign = await repo.get_by_id(campaign_id)
|
||||
|
|
@ -375,7 +375,7 @@ async def delete_proof(
|
|||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_write_access),
|
||||
):
|
||||
"""Delete a proof and its associated files. Blocked for oversight_admin."""
|
||||
"""Delete a proof and its associated files. """
|
||||
repo = ProofRepository(db)
|
||||
|
||||
proof = await repo.get_by_id(proof_id)
|
||||
|
|
@ -402,7 +402,7 @@ async def flag_proof_version(
|
|||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_write_access),
|
||||
):
|
||||
"""Flag an issue on a proof version. Blocked for oversight_admin."""
|
||||
"""Flag an issue on a proof version. """
|
||||
proof_repo = ProofRepository(db)
|
||||
audit_repo = AuditRepository(db)
|
||||
|
||||
|
|
@ -441,7 +441,7 @@ async def resolve_proof_version(
|
|||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_write_access),
|
||||
):
|
||||
"""Resolve an issue on a proof version. Blocked for oversight_admin."""
|
||||
"""Resolve an issue on a proof version. """
|
||||
proof_repo = ProofRepository(db)
|
||||
audit_repo = AuditRepository(db)
|
||||
|
||||
|
|
|
|||
|
|
@ -138,13 +138,5 @@ def require_role(*allowed_roles: str):
|
|||
async def require_write_access(
|
||||
current_user: User = Depends(get_current_db_user),
|
||||
) -> User:
|
||||
"""
|
||||
Dependency that blocks oversight_admin from write/mutation operations.
|
||||
All other roles are allowed through.
|
||||
"""
|
||||
if current_user.role == "oversight_admin":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Oversight Admin has read-only access",
|
||||
)
|
||||
"""Dependency for write/mutation operations."""
|
||||
return current_user
|
||||
|
|
|
|||
|
|
@ -201,12 +201,6 @@ async def websocket_analyze(websocket: WebSocket):
|
|||
ws_user_repo = UserRepository(ws_session)
|
||||
azure_oid = user_claims.get("oid") or user_claims.get("sub")
|
||||
ws_user = await ws_user_repo.get_by_azure_oid(azure_oid) if azure_oid else None
|
||||
if ws_user and ws_user.role == "oversight_admin":
|
||||
await manager.send_message(client_id, {
|
||||
"type": "error",
|
||||
"message": "Oversight Admin has read-only access and cannot analyze proofs."
|
||||
})
|
||||
continue
|
||||
current_user_id = ws_user.id if ws_user else None
|
||||
except Exception as role_err:
|
||||
logger.warning(f"[MAIN] Role check failed for client {client_id}: {role_err}")
|
||||
|
|
|
|||
|
|
@ -855,7 +855,7 @@ const AppContent: React.FC<{ msalInstance: any }> = ({ msalInstance }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const readOnly = !canWrite;
|
||||
const readOnly = !canWrite || (isOversightAdmin && !user?.agencyId);
|
||||
|
||||
const renderContent = () => {
|
||||
switch (currentView) {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||
isLoading,
|
||||
isSuperAdmin: role === 'super_admin',
|
||||
isOversightAdmin: role === 'oversight_admin',
|
||||
canWrite: role === 'super_admin' || role === 'agency_admin' || role === 'basic_user',
|
||||
canWrite: role === 'super_admin' || role === 'oversight_admin' || role === 'agency_admin' || role === 'basic_user',
|
||||
canSeeAnalytics: role === 'super_admin' || role === 'oversight_admin' || role === 'agency_admin',
|
||||
canSeeAuditing: role === 'super_admin' || role === 'oversight_admin',
|
||||
canSeeKnowledgeBase: role === 'super_admin',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue