T0-E1.2 — Authority Callsite Audit (gcgscola production Python)¶
| Item | Value |
|---|---|
| Mode | Server /home/scola/odoo |
| Workspace root | /home/scola/odoo |
| Docs read | workspace-governance.md, T0-E1.1-result.md, T0-E1.2-preflight.md |
| Scope | custom_addons_scola/gcgscola — controllers / services / models (skip tests, scripts, migrations unless production-capable) |
| Thoroughness | medium |
| BE HEAD scanned | d947ed9 (main) |
| FE docs repo | 7ca45c6c (develop) — docs only; Python not modified |
| Prior control | E1.1 unified AuthorityContext + compute_permissions active-role-only (no preferred/group widen for capabilities) |
Method¶
Inventory patterns (production paths only):
has_group('scola_core...resolve_role_id(user.scola_roleused for authorization (not display/serialize)_user_has_scola_product_authoritycompute_permissions(without explicitactive_role=/session=- Preferred-role / group fallbacks used for authz
Classification legend:
| Class | Meaning |
|---|---|
FRAMEWORK_ELIGIBILITY_ONLY |
Decides whether a gate applies to a Scola actor; does not grant product caps by inactive role |
DEFAULT_ROLE_SELECTION_ONLY |
Login / preferred / display default; not request capability axis |
DISPLAY_ONLY |
Payload / UI labels |
AUTHORIZATION |
Grants or denies API/model action |
TENANT_SCOPE |
Widens/narrows company set |
RECORD_SCOPE |
Widens/narrows which records are visible/mutable |
LEGACY_DEAD |
Unreachable or superseded path |
Recommended E1.2 action: KEEP | ROUTE_THROUGH_AUTHORITY | REMOVE | NARROW.
Risk after E1.1: HIGH = still grants admin/school-wide power via group or preferred role while active role may be narrower; MED = wrong role axis / union of assigned roles / group OR preferred without session; LOW = session-aware or eligibility/display.
Inventory summary (production)¶
| Pattern | Approx. production sites | Notes |
|---|---|---|
has_group('scola_core…') |
~70 hits / 33 files | Dense in CBT, portal notification admin, student_activity models |
resolve_role_id( |
3 call clusters | Defn in auth_capabilities; callers: auth.py, core_reference_api audit log |
_user_has_scola_product_authority |
1 def + 2 uses | ir_http dataset / product web gate |
compute_permissions(user=…) w/o explicit session |
~25 controllers + 1 model | After E1.1 helper auto-reads request.session → active role; residual risk mainly non-HTTP callers |
user.scola_role authz |
~12 controllers/models | Dashboard access + finance role unions + news audience |
| Preferred/group fallback helpers | resolve_role_id, SubscriptionWorkflowMixin._resolve_role, several _role_codes |
Still OR preferred + groups |
Already aligned (brief — not HIGH/MED residual)¶
authority_context.build_authority_context/get_request_authority— session active role only.compute_permissionsbody — no preferred/resolve_role_idfallback (E1.1).company_scope.resolve_user_company_ids/ foundation expand — active role only.teacher_scope.requires_teacher_student_scope— active role; faculty groups only narrow, never widen school-wide.- Controllers using
CoreApiController._scola_role()/get_request_authorityfor gates (LMS, fees finance config, leadership hub, identity_admin primary checks, etc.).
HIGH / MED residual sites¶
A. Direct group_scola_admin (and peers) for AUTHORIZATION — can widen beyond active role¶
| file:line | Snippet | Class | Residual after E1.1 | E1.2 action |
|---|---|---|---|---|
scola_portal/controllers/calendar_admin_api.py:28 |
_can_manage_calendar: active role OR has_group('…group_scola_admin') |
AUTHORIZATION | HIGH — admin group grants calendar admin while acting as teacher | ROUTE_THROUGH_AUTHORITY (drop group OR; keep active-role set) |
scola_report_card/controllers/report_card_api.py:56-62 |
_is_admin_user: scola_is_admin OR group_scola_admin |
AUTHORIZATION | HIGH — persistent flag/group bypasses active role | ROUTE_THROUGH_AUTHORITY |
scola_report_card/controllers/learning_outcome_admin_api.py:62-65 |
scola_role in allowed OR group_scola_admin OR report_card manager OR base.group_system |
AUTHORIZATION | HIGH — preferred role + admin group widen | ROUTE_THROUGH_AUTHORITY |
scola_student_activity/controllers/violation_admin_api.py:65-67 |
_has_violation_master_manage_access: group_scola_admin OR activity manager |
AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_student_activity/models/violation_log.py:268 |
manage check via group_scola_admin |
AUTHORIZATION / RECORD_SCOPE | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_student_activity/models/achievement.py:237 |
same pattern | AUTHORIZATION / RECORD_SCOPE | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_student_activity/models/student_permit.py:163,173,241 |
approve / group access: admin group short-circuit | AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_core/models/academic_term.py:51 |
action_unlock requires group_scola_admin |
AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_lms/models/ir_attachment.py:98-99 |
_check_lms_material_manage_access admin group (duplicated OR) |
AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_lms/models/op_teaching_assignment.py:516 |
calendar domain: if not admin group → empty; else school-wide | RECORD_SCOPE | HIGH — admin group ⇒ unscoped assignment feed | ROUTE_THROUGH_AUTHORITY / NARROW |
scola_cbt/controllers/exam.py:472,482 |
Access Denied unless faculty-like OR CBT groups OR group_scola_admin |
AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_cbt/controllers/participant.py:94,104 |
same admin-group OR | AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_cbt/controllers/report.py:46,118,143 |
report access: system OR admin OR CBT manager (+ principal / wakasek) | AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_cbt/controllers/grading.py:119 |
grading gate includes group_scola_admin |
AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_cbt/models/exam.py:700 |
model gate: system OR admin OR CBT manager | AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_portal/controllers/portal_api.py:586,630,671,695,736,755 |
notification / WA / Telegram config: portal admin OR system OR group_scola_admin |
AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY (prefer capability/notification_config_api authority path) |
scola_hr_attendance/controllers/hr_attendance_checkin_api.py:521 |
_can_proxy_hr_attendance: HR manager OR group_scola_admin OR system |
AUTHORIZATION | HIGH — proxy punch for others | ROUTE_THROUGH_AUTHORITY |
scola_core/controllers/admin_import_api.py:30-33 |
scola_is_admin OR user-management authority group |
AUTHORIZATION | HIGH | ROUTE_THROUGH_AUTHORITY |
scola_core/models/student_import_job.py:84 |
same scola_is_admin pattern |
AUTHORIZATION | MED→HIGH | ROUTE_THROUGH_AUTHORITY |
B. Preferred scola_role / assigned-role union for AUTHORIZATION¶
| file:line | Snippet | Class | Residual after E1.1 | E1.2 action |
|---|---|---|---|---|
scola_bos_rkas_executive_bridge/controller_support.py:13-16 |
user.scola_role in principal set OR group_scola_principal |
AUTHORIZATION | MED — preferred/group, not session | ROUTE_THROUGH_AUTHORITY |
scola_account/controllers/bendahara_dashboard_api.py:20-24 |
treasurer group OR scola_role == treasurer OR group name string |
AUTHORIZATION | MED | ROUTE_THROUGH_AUTHORITY |
scola_account/controllers/principal_finance_api.py:20-24 |
principal / wakasek finance / account manager groups only | AUTHORIZATION | MED — inactive group while acting teacher | ROUTE_THROUGH_AUTHORITY |
scola_library/controllers/librarian_dashboard_api.py:29 |
OpenEduCat/library groups OR scola_role in librarian |
AUTHORIZATION | MED | ROUTE_THROUGH_AUTHORITY |
scola_library/controllers/library_canonical_ops_api.py:43 |
library groups OR group_scola_principal OR system |
AUTHORIZATION | MED | ROUTE_THROUGH_AUTHORITY |
scola_counseling/controllers/counselor_dashboard_api.py:32 |
counseling groups OR scola_role in counselor… |
AUTHORIZATION | MED | ROUTE_THROUGH_AUTHORITY |
scola_inventory/controllers/laboran_dashboard_api.py:23 |
scola_role == lab_technician OR group name |
AUTHORIZATION | MED | ROUTE_THROUGH_AUTHORITY |
scola_fees/controllers/manual_payment_api.py:40-51 |
_role_codes: preferred + all scola_roles M2M → finance/kasir |
AUTHORIZATION / RECORD_SCOPE | HIGH — union of assigned roles ignores active role | NARROW to active role (+ capability) |
scola_fees/controllers/finance_spp_report_api.py:56-104 |
_role_codes / _can_access / _is_homeroom_scope: preferred + M2M + homeroom group; dual-role widen when preferred teacher |
AUTHORIZATION / RECORD_SCOPE | HIGH | ROUTE_THROUGH_AUTHORITY + NARROW |
scola_fees/controllers/payment_gateway_api.py / payment_setup_api.py |
same _role_codes style (M2M union) |
AUTHORIZATION | MED→HIGH | NARROW |
scola_platform_support/subscription_workflow.py:55-73 |
_resolve_role: preferred scola_role then group map (platform_operator / head_admin / principal) |
AUTHORIZATION | MED — platform/tenant subscription gates | ROUTE_THROUGH_AUTHORITY |
scola_cbt/controllers/exam.py:22-31 (+ question/participant/question_set) |
faculty-like via teacher/homeroom groups + preferred/scola_roles codes |
AUTHORIZATION | MED | ROUTE_THROUGH_AUTHORITY |
scola_hr_attendance/controllers/hr_attendance_reports_api.py:680,726 |
lock period: HR manager OR principal OR foundation_hr_admin groups | AUTHORIZATION | MED | ROUTE_THROUGH_AUTHORITY |
C. RECORD_SCOPE / TENANT_SCOPE via group or preferred role¶
| file:line | Snippet | Class | Residual after E1.1 | E1.2 action |
|---|---|---|---|---|
scola_lms/models/op_teaching_assignment.py:461-516 |
domain by student/parent/faculty groups; admin group = no teacher narrow | RECORD_SCOPE | HIGH (admin branch); MED (student/parent via group while other active role) | NARROW / ROUTE_THROUGH_AUTHORITY |
scola_counseling/controllers/counseling_portal_api.py:92,142 |
student group → self-domain | RECORD_SCOPE | MED | NARROW to active student / identity |
scola_cbt/controllers/student_schedule.py:34 |
student group → own student ids | RECORD_SCOPE | MED | NARROW |
scola_core/services/academic_calendar_scope.py:137,195 |
scola_role(user) OR student group for viewer students/levels |
RECORD_SCOPE | MED — scola_role() is preferred helper |
ROUTE_THROUGH_AUTHORITY / use _active_role_code |
scola_core/models/ir_attachment.py:13 |
student group → attachment create for own submission | RECORD_SCOPE | LOW–MED (create only; identity-bound) | KEEP or NARROW |
scola_news/models/scola_news.py:714,729 |
_user_is_parent/_student: preferred role OR groups OR identity search |
RECORD_SCOPE | MED | NARROW to identity + active role |
scola_student_activity/models/student_permit.py:192 |
_is_student_permit_user via student group |
RECORD_SCOPE | MED | NARROW |
D. Framework / default-role / resolve_role_id (not product cap axis, but residual callers)¶
| file:line | Snippet | Class | Residual after E1.1 | E1.2 action |
|---|---|---|---|---|
scola_core/services/auth_capabilities.py:829-855 |
resolve_role_id: preferred then group_scola_admin→school_admin, system→admin, GROUP_ROLE_MAP |
DEFAULT_ROLE_SELECTION_ONLY | LOW if callers stay display/login; MED if reused for authz | KEEP for login default; ban from authz (NARROW docs/guards) |
scola_core/controllers/auth.py:122,187 |
_get_role_id / _build_auth_payload: active_role or resolve_role_id(user) |
DEFAULT_ROLE_SELECTION_ONLY (+ payload role) | MED — empty session role falls back to preferred/group (can surface school_admin in payload) |
NARROW: fail-closed empty active role; never group-widen payload caps (caps already session-scoped via compute_permissions) |
scola_core/controllers/core_reference_api.py:91 |
audit log field uses resolve_role_id(user) labeled active_role |
DISPLAY_ONLY (mislabel) | LOW — logging only | NARROW label → preferred/resolved |
scola_core/models/ir_http.py:148-151 |
session_info: role = active or user.scola_role; preferred_role; is_admin = scola_is_admin |
DISPLAY_ONLY / FRAMEWORK | LOW–MED — FE may trust role/is_admin |
NARROW display contract |
scola_core/models/ir_http.py:268-297 |
_user_has_scola_product_authority: scola_role OR scola_roles OR product groups |
FRAMEWORK_ELIGIBILITY_ONLY | LOW for T0F-001 widen; eligibility for dataset deny | KEEP (do not use for product caps) |
scola_core/utils/teacher_scope.py:64-68 |
faculty groups → require teacher scope (narrow) | RECORD_SCOPE (narrowing) | LOW — intentional fail-closed narrow | KEEP |
scola_attendance/controllers/attendance_api_mixin.py:1587 |
pickup officer group check on driver user | AUTHORIZATION (device eligibility) | LOW–MED | KEEP / review if dual-role |
E. compute_permissions(user=…) without explicit session¶
Post-E1.1, compute_permissions resolves request.session when active_role omitted. Call sites are LOW residual on HTTP request path; list for hygiene:
admission_config_api, privacy_api, security_visitor_api, committee_dashboard_api, foundation_auditor_dashboard_api, faculty_admin_api, violation_admin_api, kesiswaan_*, counseling_domain_api, fee_enrollment_api, accounting_*, budget_admin_api, procurement_admin_api, payslip_admin_api, payroll_config_api, hr_admin_api, employee_admin_api, hr_attendance_admin_api, inventory_admin_api, student_face_admin_api, debug_access_api, lms_weekly_approval_api, weekly_teacher_report (model).
| Class | Residual | E1.2 action |
|---|---|---|
| AUTHORIZATION (capability set) | LOW on /api with session; MED if invoked from cron/shell/model without session → empty caps (fail-closed) or stale scola_effective_permissions on privacy |
Prefer get_request_authority().capabilities / pass session= explicitly (ROUTE_THROUGH_AUTHORITY) |
Short summary — sites that can still widen school_admin beyond active role¶
Setelah E1.1, capability matrix (compute_permissions / registry capability gate) sudah session-active-role-only. Residual widen ke kekuasaan setara school_admin / admin masih terjadi di jalur yang bypass authority dan membaca group / flag / preferred role:
has_group('scola_core.group_scola_admin')short-circuit — calendar admin, report card admin, learning outcome admin, violation/achievement/permit models, academic term unlock, LMS attachment + assignment calendar scope, CBT exam/participant/report/grading/model, portal notification config (6 call sites), HR attendance proxy punch.scola_is_adminflag — report_card_is_admin_user, admin import / student import job (dan finance SPP role codes treatingscola_is_adminas admin).- Assigned-role union — fees
_role_codes(manual payment / SPP report / payment setup): preferred + fullscola_rolesM2M grants finance/admin-like access without acting as that role. resolve_role_id/ auth payload fallback — tidak lagi mengisi capabilities, tetapi masih bisa menampilkan / memilih defaultschool_admindari group jika session active role kosong (auth.py).
Tidak lagi (setelah E1.1) jalur utama: inactive platform_operator group → widen school_admin lewat compute_permissions (T0F-001 fixed on capability path). Residual adalah callsite lokal yang belum diarahkan ke AuthorityContext.
Suggested E1.2 priority (implementation later; this doc is audit-only)¶
- Strip
group_scola_admin/scola_is_adminOR-branches on mutation & school-wide reads (calendar, portal notify, CBT, student_activity, academic_term, LMS). - Replace fees/dashboard
_role_codes/ preferred-role checks withget_request_authority().active_role_in(...)/has_capability(...). - Keep
resolve_role_id+_user_has_scola_product_authorityas eligibility/default only; add lint/tests forbidding new authz use.
Explicit non-claims¶
- Does not claim T0 CLOSED / independent audit pass.
- Does not modify Python.
- Medium thoroughness: clustered CBT/portal duplicates; optional packages with identical patterns noted by representative sites. )