security(graduation): enforce manager-only assessment approval server-side - #267
security(graduation): enforce manager-only assessment approval server-side#267gonzalesedwin1123 wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces robust security restrictions to prevent non-manager users from approving, rejecting, resetting, or directly modifying workflow-related fields on graduation assessments at the ORM level. It also adds comprehensive unit tests to verify these boundaries. The review feedback suggests further strengthening security by preventing non-managers from modifying any fields on assessments that are not in the draft state, and adding a corresponding test case to verify this behavior.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## 19.0-staging-sec-batch1 #267 +/- ##
==========================================================
Coverage ? 68.57%
==========================================================
Files ? 103
Lines ? 8523
Branches ? 0
==========================================================
Hits ? 5845
Misses ? 2678
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Address Gemini review (security-high) on PR #267: the ORM guards blocked state changes and the manager-only outcome fields, but a non-manager assessor could still edit content (recommendation, partner_id, responses, ...) on an already submitted/approved/rejected assessment — e.g. submit recommendation='extend', flip to 'graduate' while pending, and the manager's approval then sets a graduation_date for a recommendation they did not review. Freeze the assessor-editable content fields (partner_id, pathway_id, assessment_date, recommendation, recommendation_notes, response_ids) for non-managers once the assessment leaves draft; editing requires a manager reset to draft. Only these business fields are frozen, so chatter/activity/technical writes on submitted records are unaffected. Manager/superuser unrestricted. Test: a user cannot change recommendation/notes on a submitted assessment. spp_graduation 58/58.
17276e3 to
c2a6e69
Compare
spp.graduation.assessment gated its approve/reject/reset buttons to the
graduation manager group in the VIEW only. The Python workflow methods checked
just the state transition, and group_spp_graduation_user has write/create on
their own assessments (assessor_id == user). So a normal user could, via RPC,
self-approve their own assessment (action_submit then action_approve, setting a
graduation_date) or bypass the methods entirely with a raw
write({'state':'approved', ...}) / create({'state':'approved', ...}).
Enforce the boundary server-side:
- action_approve/action_reject/action_reset_draft raise AccessError unless the
caller is a graduation manager (superuser/sudo exempt for system flows).
- create()/write() forbid non-managers from setting the manager-only outcome
fields (approved_by_id, approved_date, graduation_date) and from moving state
beyond the one user-allowed transition draft -> submitted (create must be
draft). Closes the raw-RPC bypass.
action_submit stays user-allowed.
Tests: user cannot approve/reject/reset own assessment, cannot write approved
state or approval fields, cannot create a non-draft assessment; manager can
still approve (graduation_date set); user can still submit. spp_graduation 54/54.
Post-review defense-in-depth tests: a user can still edit recommendation/notes on their own draft (guard does not over-block); a user cannot un-submit (submitted -> draft is manager-only); a user cannot create an assessment for a different assessor (record-rule protection). spp_graduation 57/57.
Address Gemini review (security-high) on PR #267: the ORM guards blocked state changes and the manager-only outcome fields, but a non-manager assessor could still edit content (recommendation, partner_id, responses, ...) on an already submitted/approved/rejected assessment — e.g. submit recommendation='extend', flip to 'graduate' while pending, and the manager's approval then sets a graduation_date for a recommendation they did not review. Freeze the assessor-editable content fields (partner_id, pathway_id, assessment_date, recommendation, recommendation_notes, response_ids) for non-managers once the assessment leaves draft; editing requires a manager reset to draft. Only these business fields are frozen, so chatter/activity/technical writes on submitted records are unaffected. Manager/superuser unrestricted. Test: a user cannot change recommendation/notes on a submitted assessment. spp_graduation 58/58.
Record the manager-only assessment approval hardening in the version and HISTORY changelog (regenerated README).
c2a6e69 to
50c5dff
Compare
|
Pulled from security batch 1 (retargeted back to The core of the fix is sound and stays: the three action guards ( Must fix before this can merge
Strongly recommended in the same change
Note for whoever picks this up: all pre-existing tests run as superuser ( |
Summary
spp.graduation.assessmentgated its approve/reject/reset buttons togroup_spp_graduation_managerin the view only. The Python workflow methods checked only the state transition, andgroup_spp_graduation_userhas write/create on their own assessments (record ruleassessor_id == user.id). So a normal graduation user could, via RPC, self-approve their own assessment (action_submitthenaction_approve, which sets agraduation_date) — or bypass the methods entirely with a rawwrite({'state':'approved', ...})orcreate({'state':'approved', ...})— crossing the intended manager approval boundary and corrupting beneficiary graduation status.Fix (server-side, two layers)
action_approve/action_reject/action_reset_draftraiseAccessErrorunless the caller is a graduation manager (_is_graduation_manager()= manager group, with superuser/sudo exempt so system/programmatic flows aren't blocked).action_submitstays user-allowed.create()/write()guards: a non-manager may not set the manager-only outcome fields (approved_by_id,approved_date,graduation_date) and may only movestatedraft → submitted(create must bedraft; no-op same-state writes allowed). This closes the raw-RPC bypass that the action guards alone would miss.Tests
action_approve/reject/reset_drafton own assessment; rawwriteofstate='approved'/graduation_date/approved_by_id;createof a non-draft assessment; un-submit (submitted→draft); create for a different assessor.state='approved',approved_by_id,graduation_date); user submit; user editingrecommendation/notes on their own draft.spp_graduation57/57; lint/semgrep clean.Reviewed
Adversarial staff review: APPROVE, no blockers. Confirmed no unguarded write path, no sudo/automation escape, and that the downstream
spp_case_graduationonly treats a beneficiary as graduated whenstate=='approved'andgraduation_dateis set (both manager-only) — sorecommendation/scores are not levers. The three review test-gap suggestions were added.