Harden auth and persist report schedules
This commit is contained in:
132
tests/test_security_regressions.py
Normal file
132
tests/test_security_regressions.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi.params import Depends
|
||||
|
||||
from app.api.v1.session import SubmitAnswerResponse
|
||||
from app.core.auth import AuthContext, get_auth_context
|
||||
from app.routers import admin as admin_router
|
||||
from app.routers import reports as reports_router
|
||||
from app.routers import wordpress as wordpress_router
|
||||
from app.schemas.session import SessionCompleteResponse, UserAnswerOutput
|
||||
from app.services.reporting import AggregatePerformanceStats, StudentPerformanceReport
|
||||
|
||||
|
||||
def _depends_on_auth(callable_obj, parameter_name: str = "auth") -> bool:
|
||||
parameter = inspect.signature(callable_obj).parameters[parameter_name]
|
||||
default = parameter.default
|
||||
return isinstance(default, Depends) and default.dependency is get_auth_context
|
||||
|
||||
|
||||
def test_admin_actions_require_signed_auth_context():
|
||||
assert _depends_on_auth(admin_router.admin_trigger_calibration)
|
||||
assert _depends_on_auth(admin_router.admin_toggle_ai_generation)
|
||||
assert _depends_on_auth(admin_router.admin_reset_normalization)
|
||||
|
||||
|
||||
def test_wordpress_user_lookup_routes_require_signed_auth_context():
|
||||
assert _depends_on_auth(wordpress_router.get_website_users)
|
||||
assert _depends_on_auth(wordpress_router.get_user_endpoint)
|
||||
|
||||
|
||||
def test_wordpress_roles_map_to_api_admin_roles():
|
||||
assert wordpress_router._api_role_from_wordpress_roles(["subscriber"]) == "student"
|
||||
assert wordpress_router._api_role_from_wordpress_roles(["administrator"]) == "admin"
|
||||
assert wordpress_router._api_role_from_wordpress_roles(["super_admin"]) == "system_admin"
|
||||
|
||||
|
||||
def test_adaptive_submit_response_does_not_expose_answer_key_or_correctness():
|
||||
payload = SubmitAnswerResponse(theta=0.12, theta_se=0.8).model_dump()
|
||||
|
||||
assert "is_correct" not in payload
|
||||
assert "correct_answer" not in payload
|
||||
assert "explanation" not in payload
|
||||
|
||||
|
||||
def test_session_completion_answer_output_does_not_expose_correctness():
|
||||
answer_payload = UserAnswerOutput(
|
||||
id=1,
|
||||
item_id=10,
|
||||
response="A",
|
||||
time_spent=12,
|
||||
bobot_earned=0.5,
|
||||
scoring_mode_used="ctt",
|
||||
).model_dump()
|
||||
|
||||
assert "is_correct" not in answer_payload
|
||||
|
||||
response_payload = SessionCompleteResponse(
|
||||
id=1,
|
||||
session_id="s-1",
|
||||
wp_user_id="wp-1",
|
||||
website_id=2,
|
||||
tryout_id="tryout-1",
|
||||
start_time=datetime.now(timezone.utc),
|
||||
end_time=datetime.now(timezone.utc),
|
||||
is_completed=True,
|
||||
scoring_mode_used="ctt",
|
||||
total_benar=1,
|
||||
total_bobot_earned=0.5,
|
||||
NM=500,
|
||||
NN=500,
|
||||
rataan_used=500,
|
||||
sb_used=100,
|
||||
user_answers=[
|
||||
UserAnswerOutput(
|
||||
id=1,
|
||||
item_id=10,
|
||||
response="A",
|
||||
time_spent=12,
|
||||
bobot_earned=0.5,
|
||||
scoring_mode_used="ctt",
|
||||
)
|
||||
],
|
||||
).model_dump()
|
||||
|
||||
assert "is_correct" not in response_payload["user_answers"][0]
|
||||
|
||||
|
||||
def test_student_performance_report_is_scoped_to_student_user(monkeypatch):
|
||||
captured = {}
|
||||
|
||||
async def fake_generate_student_performance_report(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return StudentPerformanceReport(
|
||||
generated_at=datetime.now(timezone.utc),
|
||||
tryout_id=kwargs["tryout_id"],
|
||||
website_id=kwargs["website_id"],
|
||||
date_range=kwargs["date_range"],
|
||||
aggregate=AggregatePerformanceStats(
|
||||
tryout_id=kwargs["tryout_id"],
|
||||
participant_count=0,
|
||||
avg_nm=None,
|
||||
std_nm=None,
|
||||
min_nm=None,
|
||||
max_nm=None,
|
||||
median_nm=None,
|
||||
avg_nn=None,
|
||||
std_nn=None,
|
||||
avg_theta=None,
|
||||
pass_rate=0.0,
|
||||
avg_time_spent=0.0,
|
||||
),
|
||||
individual_records=[],
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
reports_router,
|
||||
"generate_student_performance_report",
|
||||
fake_generate_student_performance_report,
|
||||
)
|
||||
|
||||
asyncio.run(
|
||||
reports_router.get_student_performance_report(
|
||||
tryout_id="tryout-1",
|
||||
db=object(),
|
||||
auth=AuthContext(website_id=5, role="student", wp_user_id="wp-1"),
|
||||
)
|
||||
)
|
||||
|
||||
assert captured["website_id"] == 5
|
||||
assert captured["wp_user_id"] == "wp-1"
|
||||
Reference in New Issue
Block a user