# Permission Matrix

Authorization in this system is **layered** — hiding a sidebar link is never the only
protection. Every request passes through up to four server-side checks:

| Layer | Mechanism | Answers |
|---|---|---|
| 1. Route middleware | `permission:<name>` (`EnsurePermission`), `role:<name>` (`EnsureRole`), `active` (`EnsureActiveAccount`), `login.throttle` | *May this role reach this URL at all?* |
| 2. Policies | `$this->authorize(...)` in controllers / `@can` in Blade (`app/Policies/*`) | *May this user perform this action?* |
| 3. Record-level rules | `App\Support\Authorizer` + contextual gates (`attendance.manage-class`, `marks.enter`) | *May this user touch **this** record?* |
| 4. Data scope | School scope + branch scope (`HasSchoolScope`, `HasBranchScope`, `BranchAccess`) | *Is this record even visible to them?* |

`admin` (School Administrator) and `super_admin` (Platform Super Administrator) hold every
school-level permission explicitly and bypass `permission:` checks; `super_admin` additionally
passes the separate platform gate.

---

## 1. Roles (14)

| Key | Display name | Level | Purpose |
|---|---|---:|---|
| `super_admin` | Platform Super Administrator | 100 | Platform scope: schools, branches, cross-school accounts. Separate `platform.*` permission space. |
| `proprietor` | School Proprietor / Owner | 90 | Read-only oversight of the whole school. |
| `headteacher` | Headteacher / Principal | 85 | School-wide academic + administrative leadership (no role-definition edits, no platform). |
| `admin` | School Administrator | 80 | Runs daily operations, including user and role administration. |
| `hr` | HR / Payroll Officer | 60 | Staff records + user-account provisioning only. |
| `accountant` | Accountant / Bursar | 60 | Fees, invoicing, payments. **Never** examination marks. |
| `admissions` | Admissions Officer | 60 | Admits and updates student records. |
| `form_master` | Form / Class Teacher | 50 | Own class: students, attendance, marks. |
| `teacher` | Teacher | 45 | Assigned subjects/classes only. |
| `librarian` | Librarian | 40 | Student identification only (no contact/medical data). |
| `nurse` | School Nurse / Welfare | 40 | Medical/welfare notes + attendance oversight. |
| `transport` | Transport Officer | 40 | Student contact details + timetables. |
| `parent` | Parent / Guardian | 20 | Linked children only. |
| `student` | Student | 10 | Own records only. |

Levels drive the **escalation guards** (§7): you may only assign roles at or below your own
level, and only edit roles strictly below your own level (`super_admin` excepted).

## 2. Permissions (25)

| Permission | Module | Description |
|---|---|---|
| `academics.view` | academics | View classes, sections and subjects |
| `academics.manage` | academics | Manage classes, sections and subjects |
| `students.view` | students | View student records |
| `students.create` | students | Admit new students |
| `students.update` | students | Edit student records |
| `students.delete` | students | Remove student records |
| `students.medical` | students | View student medical / welfare notes |
| `staff.view` | staff | View staff records |
| `staff.create` | staff | Add staff members |
| `staff.update` | staff | Edit staff records |
| `staff.delete` | staff | Remove staff records |
| `attendance.view` | attendance | View attendance records |
| `attendance.manage` | attendance | Record and edit attendance |
| `fees.view` | fees | View fees, invoices and payments |
| `fees.manage` | fees | Create invoices and record payments |
| `exams.view` | exams | View exams, marks and report cards |
| `exams.manage` | exams | Create exams and enter marks |
| `timetable.view` | timetable | View timetables |
| `timetable.manage` | timetable | Build and edit timetables |
| `reports.view` | reports | View school reports and analytics |
| `users.view` | users | List user accounts |
| `users.manage` | users | Create, edit, activate and deactivate accounts |
| `roles.manage` | roles | Edit roles and their permission assignments |
| `audit.view` | audit | View the audit trail |
| `platform.manage` | platform | Platform-level access (schools, branches, cross-school accounts) |

## 3. Role → Permission assignments

| Role | Permissions |
|---|---|
| `super_admin` | `platform.manage`, `academics.view`, `academics.manage`, `students.view`, `students.create`, `students.update`, `students.delete`, `students.medical`, `staff.view`, `staff.create`, `staff.update`, `staff.delete`, `attendance.view`, `attendance.manage`, `fees.view`, `fees.manage`, `exams.view`, `exams.manage`, `timetable.view`, `timetable.manage`, `reports.view`, `users.view`, `users.manage`, `roles.manage`, `audit.view` |
| `proprietor` | `academics.view`, `students.view`, `staff.view`, `attendance.view`, `fees.view`, `exams.view`, `timetable.view`, `reports.view`, `users.view` |
| `headteacher` | `academics.view`, `academics.manage`, `students.view`, `students.create`, `students.update`, `students.delete`, `students.medical`, `staff.view`, `staff.create`, `staff.update`, `staff.delete`, `attendance.view`, `attendance.manage`, `fees.view`, `fees.manage`, `exams.view`, `exams.manage`, `timetable.view`, `timetable.manage`, `reports.view`, `users.view`, `users.manage`, `audit.view` |
| `admin` | `academics.view`, `academics.manage`, `students.view`, `students.create`, `students.update`, `students.delete`, `students.medical`, `staff.view`, `staff.create`, `staff.update`, `staff.delete`, `attendance.view`, `attendance.manage`, `fees.view`, `fees.manage`, `exams.view`, `exams.manage`, `timetable.view`, `timetable.manage`, `reports.view`, `users.view`, `users.manage`, `roles.manage`, `audit.view` |
| `hr` | `staff.view`, `staff.create`, `staff.update`, `staff.delete`, `users.view`, `reports.view` |
| `accountant` | `fees.view`, `fees.manage`, `students.view`, `staff.view`, `academics.view`, `attendance.view`, `exams.view`, `reports.view` |
| `admissions` | `students.view`, `students.create`, `students.update`, `academics.view`, `attendance.view`, `reports.view` |
| `form_master` | `academics.view`, `students.view`, `students.update`, `attendance.view`, `attendance.manage`, `exams.view`, `exams.manage`, `timetable.view`, `reports.view` |
| `teacher` | `academics.view`, `students.view`, `attendance.view`, `attendance.manage`, `exams.view`, `exams.manage`, `timetable.view`, `reports.view` |
| `librarian` | `students.view`, `academics.view`, `timetable.view` |
| `nurse` | `students.view`, `students.medical`, `attendance.view` |
| `transport` | `students.view`, `timetable.view` |
| `parent` | `students.view`, `attendance.view`, `fees.view`, `exams.view` |
| `student` | `students.view`, `attendance.view`, `exams.view`, `timetable.view` |

`platform.manage` is deliberately grantable **only** to `super_admin`; a school-level
`roles.manage` edit can never include it (§7).

## 4. Route / module access (direct URL access)

Legend: ✓ = allowed · ✗ = denied (redirect with flash error, or 403 from policy) ·
**own/assigned** = allowed only for linked records · *list* = view-only index.

| Role | Dash/Profile | Students | Add student | Guardians | Staff | Classes/Subjects | Attendance | Timetable | Exams | Mark entry (POST) | Fees/Invoices/Payments | Users | Roles | Audit | Platform |
|---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| `super_admin` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `proprietor` | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | *list* | *list* | *list* | ✗ | ✗ |
| `headteacher` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | *list* | ✓ | ✗ |
| `admin` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| `hr` | ✓ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | *list* | *list* | ✗ | ✗ |
| `accountant` | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | *list* | **✗** | ✓ | ✗ | ✗ | ✗ | ✗ |
| `admissions` | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| `form_master` | ✓ | own class | ✗ | own students | ✓ | ✓ | assigned | ✓ | ✓ | assigned | ✗ | ✗ | ✗ | ✗ | ✗ |
| `teacher` | ✓ | assigned | ✗ | own students | ✗ | ✓ | assigned | ✓ | ✓ | assigned | ✗ | ✗ | ✗ | ✗ | ✗ |
| `librarian` | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| `nurse` | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| `transport` | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| `parent` | ✓ | own children | ✗ | own | ✗ | ✗ | own | ✗ | *list* | ✗ | own children | ✗ | ✗ | ✗ | ✗ |
| `student` | ✓ | own | ✗ | ✗ | ✗ | ✗ | own | ✓ | *list* | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |

Notes:

* **Accountant vs examinations** — the accountant may *open* exam lists (`exams.view`) but the
  `exams.manage` route middleware rejects any POST to `/exams` or `/exams/{exam}/marks`.
* **Platform** (`/platform/*`) requires `role:super_admin` **and** `permission:platform.manage`
  — the platform permission space is separate from school permissions.
* Guardian browsing (`/guardians`) is additionally governed by `GuardianPolicy`: `librarian`,
  `hr` and `student` are denied entirely even when they hold `students.view`; `teacher`,
  `parent` see only guardians attached to students they may view.

## 5. Record-level rules (`App\Support\Authorizer`)

| Rule | Applies to | Behaviour |
|---|---|---|
| Linked children | `parent` | Sees only students attached to their `guardian_id` (list, detail, invoices, attendance, report cards all scoped). Another family's student/invoice/report card → **403**. |
| Own record | `student` | Sees only `users.student_id`. Another student's record → **403**. `/guardians` → **403** (`viewAny` denies students). |
| Assigned classes/subjects | `teacher`, `form_master` | `assignedClassIds()` = `class_subject.staff_id` + `class_sections.teacher_id` + `timetable_entries.staff_id`. Student lists, attendance and marks are limited to those classes; a form master may update students of their own sections only. |
| `attendance.manage-class` gate | attendance POST | Teachers may submit attendance **only** for an assigned class (else **403**), and every posted student must have an active enrollment in that class (else validation error) — forged class/student combinations are rejected server-side. |
| `marks.enter` gate | marks POST | Teachers may enter marks only for an assigned class **and** assigned subject (else **403**); posted students must be enrolled in the class and subjects must belong to the class (else validation error). |
| Field-level student access | all | `viewContact` (phone/address/guardians — denied to `librarian`), `viewMedical` (only `students.medical`: nurse, leadership), `viewFees` (`fees.view` and never teachers), `viewAcademics` (`exams.view` + link rule). Enforced with `@can` **and** in the controller — not just hidden markup. |
| User management | `users.manage` actors | `UserPolicy`/`Authorizer::canManageUser`: cannot edit or deactivate yourself, cannot touch `super_admin` accounts unless you are `super_admin`, cannot manage accounts above your role level. |

## 6. School, branch and session scope

* **School** — every tenant table carries `school_id` with a global scope; queries never cross schools.
* **Branch** — `students`, `staff`, `classes`, `invoices`, `attendance_records`, `audit_logs`
  and `users` carry `branch_id` with a global scope:
  * Records with `branch_id IS NULL` are **school-wide** and visible to every branch.
  * `super_admin`, `proprietor`, `headteacher` are never branch-restricted.
  * Everyone else sees **home branch + explicitly granted branches** (`user_branches`
    pivot — “unless explicitly authorized”). A record in another branch → **404** at
    route-model binding; lists are filtered.
* **Sessions** — `SESSION_DRIVER=database`. Existing sessions are deleted server-side when an
  account is deactivated, a password changes, a password reset is redeemed, a role changes,
  or MFA is enabled/disabled (`App\Support\SessionInvalidator`), in addition to the `active`
  middleware that logs out deactivated users on their next request.

## 7. Authentication controls & escalation guards

| Control | Implementation |
|---|---|
| Login throttling | `login.throttle` middleware: 5 attempts/min per IP+email on login & MFA verify, 3/min on password-reset endpoints; counter cleared on success; JSON clients receive **429**. |
| Password reset | `password_reset_tokens` (bcrypt-hashed, single-use) with a **60-minute expiry** enforced on both the form and the submission; success rotates `remember_token` and flushes all sessions. |
| Multi-factor (optional, per user) | TOTP (`pragmarx/google2fa`) stored encrypted in `users.two_factor_secret`. Login pauses at a `mfa.pending` session step; only a valid code authenticates. Enable/disable require a valid current code and invalidate other sessions. |
| Account activation | `users.is_active` checked by `active` middleware on every request; deactivation via UI flushes sessions + writes an audit entry. Self-deactivation is refused. |
| Privilege-change audit | `App\Support\AuditLogger` writes entries for `privilege.*` (role, branches, permissions, activation), `login.*`, `password.*`, `mfa.*`, `marks.updated`, plus model-level `Auditable` events. Viewable at `/audit` with `audit.view`. |
| Escalation guards | Role assignment limited to roles ≤ your level (`Rule::in(assignableRoleNames)`); role-permission edits limited to permissions you hold (`Rule::in(grantablePermissionNames)`) — e.g. an `admin` submitting `platform.manage` receives a validation error, and only `super_admin` may edit `super_admin` accounts or the `super_admin` role. |

## 8. Demo accounts

All passwords are `password` (see `database/seeders/PeopleSeeder::seedDemoAccounts`):

| Email | Role |
|---|---|
| `admin@school.local` | `super_admin` |
| `administrator@school.local` | `admin` |
| `headmaster@school.local` | `headteacher` |
| `proprietor@school.local` | `proprietor` |
| `hr@school.local` | `hr` |
| `bursar@school.local` | `accountant` |
| `admissions@school.local` | `admissions` |
| `formmaster@school.local` | `form_master` |
| `teacher@school.local` | `teacher` |
| `librarian@school.local` | `librarian` |
| `nurse@school.local` | `nurse` |
| `transport@school.local` | `transport` |
| `parent@school.local` | `parent` (linked guardian) |
| `student@school.local` | `student` (linked student) |

## 9. Acceptance tests

`tests/Feature/` covers this matrix end-to-end:

* `RoleMatrixTest` — every role's allowed/denied direct URL access (14 roles).
* `RecordLevelTest` — modified record IDs, forged form submissions, escalation attempts, branch isolation.
* `AuthControlsTest` — deactivation/session invalidation, password reset (happy, invalid, expired), MFA login, login throttling, audit entries.
* `PermissionMatrixDocTest` — this document stays in sync with the seeded roles and permissions.

Run with `php artisan test`.
