Complete Section 1 security/auth hardening
This commit is contained in:
@@ -14,6 +14,12 @@ from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.core.auth import (
|
||||
AuthContext,
|
||||
ensure_website_scope_matches,
|
||||
get_auth_context,
|
||||
require_website_auth,
|
||||
)
|
||||
from app.models import Item, Session, Tryout
|
||||
from app.services.cat_selection import (
|
||||
CATSelectionError,
|
||||
@@ -106,7 +112,8 @@ class CATTestResponse(BaseModel):
|
||||
)
|
||||
async def get_next_item_endpoint(
|
||||
session_id: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> NextItemResponse:
|
||||
"""
|
||||
Get the next item for a session.
|
||||
@@ -116,8 +123,13 @@ async def get_next_item_endpoint(
|
||||
Calls appropriate selection function based on selection_mode.
|
||||
Returns item or completion status.
|
||||
"""
|
||||
website_id = require_website_auth(auth, allowed_roles={"student", "admin", "system_admin"})
|
||||
|
||||
# Get session
|
||||
session_query = select(Session).where(Session.session_id == session_id)
|
||||
session_query = select(Session).where(
|
||||
Session.session_id == session_id,
|
||||
Session.website_id == website_id,
|
||||
)
|
||||
session_result = await db.execute(session_query)
|
||||
session = session_result.scalar_one_or_none()
|
||||
|
||||
@@ -126,6 +138,11 @@ async def get_next_item_endpoint(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Session {session_id} not found"
|
||||
)
|
||||
if auth.role == "student" and session.wp_user_id != auth.wp_user_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Session does not belong to this authenticated user",
|
||||
)
|
||||
|
||||
if session.is_completed:
|
||||
return NextItemResponse(
|
||||
@@ -214,7 +231,8 @@ async def get_next_item_endpoint(
|
||||
async def submit_answer_endpoint(
|
||||
session_id: str,
|
||||
request: SubmitAnswerRequest,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> SubmitAnswerResponse:
|
||||
"""
|
||||
Submit an answer for an item.
|
||||
@@ -224,8 +242,13 @@ async def submit_answer_endpoint(
|
||||
Updates theta estimate.
|
||||
Records response time.
|
||||
"""
|
||||
website_id = require_website_auth(auth, allowed_roles={"student", "admin", "system_admin"})
|
||||
|
||||
# Get session
|
||||
session_query = select(Session).where(Session.session_id == session_id)
|
||||
session_query = select(Session).where(
|
||||
Session.session_id == session_id,
|
||||
Session.website_id == website_id,
|
||||
)
|
||||
session_result = await db.execute(session_query)
|
||||
session = session_result.scalar_one_or_none()
|
||||
|
||||
@@ -234,6 +257,11 @@ async def submit_answer_endpoint(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Session {session_id} not found"
|
||||
)
|
||||
if auth.role == "student" and session.wp_user_id != auth.wp_user_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Session does not belong to this authenticated user",
|
||||
)
|
||||
|
||||
if session.is_completed:
|
||||
raise HTTPException(
|
||||
@@ -242,7 +270,11 @@ async def submit_answer_endpoint(
|
||||
)
|
||||
|
||||
# Get item
|
||||
item_query = select(Item).where(Item.id == request.item_id)
|
||||
item_query = select(Item).where(
|
||||
Item.id == request.item_id,
|
||||
Item.website_id == session.website_id,
|
||||
Item.tryout_id == session.tryout_id,
|
||||
)
|
||||
item_result = await db.execute(item_query)
|
||||
item = item_result.scalar_one_or_none()
|
||||
|
||||
@@ -296,7 +328,8 @@ async def submit_answer_endpoint(
|
||||
)
|
||||
async def test_cat_endpoint(
|
||||
request: CATTestRequest,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> CATTestResponse:
|
||||
"""
|
||||
Test CAT selection algorithm.
|
||||
@@ -304,10 +337,13 @@ async def test_cat_endpoint(
|
||||
Simulates CAT selection for a tryout and returns
|
||||
the sequence of selected items with theta progression.
|
||||
"""
|
||||
website_id = require_website_auth(auth, allowed_roles={"admin", "system_admin"})
|
||||
ensure_website_scope_matches(website_id, request.website_id)
|
||||
|
||||
# Verify tryout exists
|
||||
tryout_query = select(Tryout).where(
|
||||
Tryout.tryout_id == request.tryout_id,
|
||||
Tryout.website_id == request.website_id
|
||||
Tryout.website_id == website_id
|
||||
)
|
||||
tryout_result = await db.execute(tryout_query)
|
||||
tryout = tryout_result.scalar_one_or_none()
|
||||
@@ -315,14 +351,14 @@ async def test_cat_endpoint(
|
||||
if not tryout:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Tryout {request.tryout_id} not found for website {request.website_id}"
|
||||
detail=f"Tryout {request.tryout_id} not found for website {website_id}"
|
||||
)
|
||||
|
||||
# Run simulation
|
||||
result = await simulate_cat_selection(
|
||||
db,
|
||||
tryout_id=request.tryout_id,
|
||||
website_id=request.website_id,
|
||||
website_id=website_id,
|
||||
initial_theta=request.initial_theta,
|
||||
selection_mode=request.selection_mode,
|
||||
max_items=request.max_items,
|
||||
@@ -346,13 +382,19 @@ async def test_cat_endpoint(
|
||||
)
|
||||
async def get_session_status_endpoint(
|
||||
session_id: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> dict:
|
||||
"""
|
||||
Get session status for admin monitoring.
|
||||
"""
|
||||
website_id = require_website_auth(auth, allowed_roles={"admin", "system_admin"})
|
||||
|
||||
# Get session
|
||||
session_query = select(Session).where(Session.session_id == session_id)
|
||||
session_query = select(Session).where(
|
||||
Session.session_id == session_id,
|
||||
Session.website_id == website_id,
|
||||
)
|
||||
session_result = await db.execute(session_query)
|
||||
session = session_result.scalar_one_or_none()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user