Skip to content

Introduce Guardian Portal and enhance portal API permissions - #451

Merged
tuwafula merged 5 commits into
version-16from
version-16-hotfix
Jun 25, 2026
Merged

Introduce Guardian Portal and enhance portal API permissions#451
tuwafula merged 5 commits into
version-16from
version-16-hotfix

Conversation

@tuwafula

Copy link
Copy Markdown
Collaborator

No description provided.

tuwafula added 4 commits June 24, 2026 10:01
- Add ownership checks for student-scoped endpoints (programs, invoices,
attendance), guard student group and assessment result access by doctype
permission, and remove whitelist decorators from internal helpers
(get_student_guardians, get_result).
Extend the SPA portal to support Guardian users in addition to Students.
A guardian lands on a student-selector page showing their wards and, after
picking one, reuses the existing Schedule/Grades/Fees/Attendance pages
scoped to the selected student. Also rebrands the portal route from
"student-portal" to the role-neutral "edu-portal".

Backend (education/education/api.py):
- Add get_portal_context() as the single role-aware bootstrap call,
  returning {role, student} for students and {role, guardian, students}
  for guardians
- Add get_guardian_info() and get_guardian_students() that resolve the
  guardian strictly from the session user (no client-supplied id)
- Add get_student_context(student) and refactor get_student_info() to
  delegate to it; both reuse get_current_enrollment/get_student_groups
- Add get_user_role() returning Guardian/Student/None
- Add get_guardian_for_user()/is_guardian_of() helpers and extend
  check_permission() so a guardian linked via Student Guardian is
  authorized for their wards (student-owner and Administrator still pass);
  get_student_programs/get_student_invoices/get_student_attendance remain
  permission-checked

Roles & provisioning:
- Add create_guardian_role patch (desk-access-less "Guardian" role) and
  register it in patches.txt
- invite_guardian() now assigns the Guardian role to the created user

Routing & serving:
- Rename website route rule and page to /edu-portal (hooks.py), rename
  www/student_portal.py -> www/edu_portal.py, and fix the build copy
  target to www/edu-portal.html (package.json); update .gitignore

Frontend:
- Add stores/portal.js (role + ward list + persisted activeStudentId)
- Refactor stores/student.js to load the active student's context
  reactively (self vs. selected ward)
- Add pages/Students.vue (ward cards) and the /edu-portal router guard
  that loads portal context, redirects guardians without a selection to
  the cards page, sends non-portal users to the desk, and fixes the
  invalid next(false) call
- Update Navbar/Sidebar/SidebarLink/ProfileModal for the role-aware UI
Substantive changes:

- guardian.py: run the "Guardian" write permission check (has_permission
  ..., throw=True) before loading the Guardian document in invite_guardian,
  so authorization is enforced prior to any document fetch
- api.py: in check_permission, replace the _(f"...{resource_type}") f-string
  with _("...{0}").format(resource_type); f-strings inside _() cannot be
  extracted for translation, this preserves the same message while keeping
  it translatable
Add a read-only "Guardian" permission entry to the doctypes the guardian
portal needs so guardian users can view their students' data. Each grant is
read/print/email/export/report/share only - no write, create, delete,
submit, or cancel - keeping guardians strictly read-only.

Doctypes updated:
- Assessment Result
- Course Schedule
- Guardian
- Program
- Program Enrollment
- Student
- Student Attendance
- Student Leave Application
@tuwafula tuwafula changed the title feat: introduce Guardian Portal and enhance portal API permissions Introduce Guardian Portal and enhance portal API permissions Jun 25, 2026
@tuwafula
tuwafula merged commit 93bc707 into version-16 Jun 25, 2026
1 of 3 checks passed
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The Guardian portal paths need fixes before merging. Broad DocPerms can expose unrelated student records, and role setup can leave guardians unable to enter the portal.

  • Standard Frappe endpoints can bypass the custom linked-guardian checks.
  • Existing guardian users do not receive the new portal role.
  • Fresh installs do not create the Guardian role during install.
  • The profile modal can render stale empty data after async store loading.

education/education/doctype/*.json Guardian permissions, education/education/doctype/guardian/guardian.py, education/install.py, frontend/src/components/ProfileModal.vue

Security Review

Guardian DocPerms are broader than the portal checks. Standard Frappe read/export/report paths can expose unrelated student data unless these permissions are scoped or removed.

Important Files Changed

Filename Overview
education/education/api.py Adds role-aware portal endpoints and per-student checks for custom portal reads.
education/education/doctype/student/student.json Adds broad Guardian read/export/report/share access to Student records.
education/education/doctype/guardian/guardian.py Adds Guardian role assignment for newly created users but skips existing users.
education/install.py Defines Guardian role setup but does not run it on fresh install.
frontend/src/components/ProfileModal.vue Adapts the profile modal for guardian and student profiles, but reads async store state non-reactively.
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
education/education/doctype/student/student.json:387-395
**Guardian Access Is Global**

This DocPerm gives every Guardian role direct read/export/report/share access to `Student` records through standard Frappe endpoints, without the linked-ward check used by the portal APIs. A guardian can enumerate unrelated students; the same permission pattern on attendance, results, leave, and schedules exposes those records too.

### Issue 2 of 4
education/education/doctype/guardian/guardian.py:48-52
**Existing Users Miss Role**

When the guardian email already has a `User`, this branch returns before adding the `Guardian` role. That invited guardian then reaches `get_user_role()` with no portal role and gets redirected away from the portal.

```suggestion
		if guardian_as_user:
			user = frappe.get_doc("User", guardian_as_user)
			user.add_roles("Guardian")
			user.save(ignore_permissions=True)
			frappe.msgprint(
				_("User {0} already exists").format(getlink("User", guardian_as_user))
			)
			return guardian_as_user
```

### Issue 3 of 4
education/install.py:7-13
**Fresh Installs Miss Role**

`create_guardian_role()` is defined but not called during install. On a fresh site, `invite_guardian()` can try to add a role that was never created, so guardian invitation or portal access fails until a patch is run.

```suggestion
def after_install():
	setup_fixtures()
	create_student_role()
	create_guardian_role()
	create_parent_assessment_group()
	create_invoice_permissions()
	create_custom_fields(get_custom_fields())
	create_permissions(get_permissions())
```

### Issue 4 of 4
frontend/src/components/ProfileModal.vue:76-81
**Profile State Freezes Empty**

This reads portal and student refs once during setup, before the async portal context and active student data may finish loading. If the modal mounts early, `profileInfo` stays blank or uses the wrong branch after the store updates, so the profile dialog shows missing guardian/student data.

Reviews (1): Last reviewed commit: "Merge branch 'version-16' into version-1..." | Re-trigger Greptile

@frappe frappe deleted a comment from greptile-apps Bot Jun 25, 2026
@frappe frappe deleted a comment from greptile-apps Bot Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant