Sprint 4 PR 4.5c — close /calendar/org/export auth gap - #228
Merged
Merged
Conversation
tomqwu
force-pushed
the
sprint-4-pr-4-5c-calendar-org-export-auth
branch
from
June 25, 2026 13:47
194f159 to
f645388
Compare
tomqwu
marked this pull request as ready for review
June 25, 2026 14:17
Summary: `GET /api/v1/calendar/org/export` previously accepted `person_id` as a query parameter and used it as the auth proxy — "must refer to an admin in this org." Person ids are deterministic (often email-derived slugs), so anyone who could enumerate or guess an admin's id could download the entire org's events. Auth is now JWT-only via Depends(get_current_admin_user), with the same-org check delegated to verify_org_member. This closes the follow-up explicitly named in PR Changed files: - api/routers/calendar.py — replace spoofable person_id query param with Depends(get_current_admin_user) + verify_org_member(current_admin, org_id). The `person_id` query param is dropped from the signature; any extra query params remaining on legacy callers are ignored by FastAPI. - tests/api/test_calendar_auth.py — new TestOrgExportAuth class with 5 cases: unauth → 401/403, volunteer-in-same-org → 403, admin-cross-org → 403, admin-same-org-no-events → 404, and an explicit regression test that a volunteer cannot escalate by passing an admin's id as the legacy ?person_id= spoof param. - tests/conftest.py — extend the unit-tier verify_org_member monkey-patch list to include api.routers.calendar (it now imports the symbol directly so the patch must rebind there too). - tests/unit/test_calendar.py — drop the now-dead ?person_id= query param from the URLs; remove `test_org_export_as_volunteer_denied` (mocked auth always returns an admin so the case is not representable in this tier — the equivalent assertion lives in TestOrgExportAuth). - tests/contract/openapi.snapshot.json — refreshed via make update-openapi-snapshot: person_id parameter removed, HTTPBearer security added to the operation, description bumped. Validation: - poetry run pytest tests/api/test_calendar_auth.py tests/unit/test_calendar.py → 32 passed - make test-unit-fast → 338 passed, 21 skipped - poetry run pytest tests/api tests/contract → 316 passed - poetry run pytest tests/cli tests/integration → 59 passed - poetry run black api tests — clean - poetry run ruff check api tests — clean Follow-ups: - E2E lane will only go green once #227 (fix-e2e-stale-dates) lands and this branch is rebased onto the updated main. The stale-date flake is unrelated to this change and pre-exists in the suite.
tomqwu
force-pushed
the
sprint-4-pr-4-5c-calendar-org-export-auth
branch
from
June 25, 2026 14:21
f645388 to
9ae7d46
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
PR #226 (4.5b) closed three of the four calendar auth gaps but explicitly left this one open as a follow-up:
Person ids are deterministic (often email-derived slugs), so anyone who can enumerate or guess an admin's id can download the entire organization's events — a PII / org-data leak. This PR closes that gap.
What changed
api/routers/calendar.py—export_organization_eventsnow takescurrent_admin: Person = Depends(get_current_admin_user)and delegates the same-org check toverify_org_member(current_admin, org_id). Theperson_idquery parameter (which was the spoof vector) has been removed from the signature. FastAPI silently ignores extra query params, so legacy clients still sending?person_id=...won't 422.tests/api/test_calendar_auth.py— newTestOrgExportAuthclass with 5 cases:401/403403403404(auth passes; the empty-events 404 is the next gate)?person_id=...→403(the legacy spoof vector no longer works)tests/conftest.py— extend the unit-tierverify_org_membermonkey-patch list to includeapi.routers.calendar, since that router now imports the symbol directly.tests/unit/test_calendar.py— drop the now-dead?person_id=query param from the URLs; removetest_org_export_as_volunteer_denied(mocked auth always returns admin so the case isn't representable in this tier — the equivalent assertion lives inTestOrgExportAuth).tests/contract/openapi.snapshot.json— refreshed viamake update-openapi-snapshot:person_idparameter removed,HTTPBearersecurity added, description updated.Validation
poetry run pytest tests/api/test_calendar_auth.py tests/unit/test_calendar.py→ 32 passedmake test-unit-fast→ 338 passed, 21 skippedpoetry run pytest tests/api tests/contract→ 316 passedpoetry run pytest tests/cli tests/integration→ 59 passedpoetry run black api testsclean;poetry run ruff check api testscleanFollow-ups
Generated by Claude Code.
Generated by Claude Code