Skip to content

feat: changes to the view mentoring report policy - #5181

Open
CLC0609 wants to merge 4 commits into
VATSIM-UK:mainfrom
CLC0609:tech-729-restrict-access-to-historic-mentoring-reports
Open

CLC0609 wants to merge 4 commits into
VATSIM-UK:mainfrom
CLC0609:tech-729-restrict-access-to-historic-mentoring-reports

Conversation

@CLC0609

@CLC0609 CLC0609 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Summary of changes

New rules on who can access or see a mentoring report:

  1. The student who attended the session can always see their own report.
  2. Holders of training.mentoring.view.* or training.mentoring.reports.view-all see everything.
  3. Training Group Instructors see reports within their category ladder, no training place requirement.
  4. Mentors see reports for categories below the students TP, but only when the student holds an active training place.

Screenshots (if necessary)

@CLC0609
CLC0609 requested a lite review from Copilot September 11, 2026 21:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved authorization, filtering, category-ladder, and test setup issues remain.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds centralized mentoring-report access control for students, TGIs, mentors, permissions, and active training places.

Changes:

  • Adds report visibility and category-ladder logic.
  • Updates mentoring policies and history filtering.
  • Adds report permissions and unit/feature coverage.
File summaries
File Summary
tests/Unit/Training/Mentoring/MentoringReportAccessServiceTest.php Tests access-service rules.
tests/Unit/Training/Mentoring/MentoringPolicyTest.php Tests policy authorization.
tests/Feature/TrainingPanel/Mentor/ViewMentoringReportTest.php Tests report-page access.
tests/Feature/TrainingPanel/Mentor/MentoringHistoryTest.php Tests filtered history visibility.
database/seeders/RolesAndPermissionsSeeder.php Registers report permissions.
app/Services/Training/MentoringReportAccessService.php Implements report visibility and scoped queries.
app/Policies/Training/Mentoring/MentoringPolicy.php Delegates mentoring authorization.
app/Filament/Training/Pages/Mentor/MentoringHistory.php Applies visibility filtering to report history.
Review details

Suppressed comments (5)

app/Services/Training/MentoringReportAccessService.php:232

  • The view-all early return bypasses the same validity checks used by canViewReport(). A filed session with an unknown position will therefore appear in the history table for a view-all user, but its visible View Report action then leads to a 403 because canViewReport() rejects that position. Apply the recognized-category/student constraints before returning the view-all query, or centralize a shared valid-session scope.
        if ($this->canViewAll($user)) {
            return Session::query();

tests/Unit/Training/Mentoring/MentoringPolicyTest.php:166

  • Once the role fixture is present, this TGI case still cannot reach the ladder check: ATC category resolution is built from TrainingPosition rows, but the test only creates a session with EGLL_TWR. Without an S2 training position, resolveCategoryForCtsCallsign() returns null and the policy denies access. Seed the matching training position before creating the session.
        $s2Session = Session::factory()->create([
            'position' => 'EGLL_TWR',
            'filed' => now(),
        ]);

tests/Unit/Training/Mentoring/MentoringPolicyTest.php:180

  • This new view-all test creates an EGLL_APP session but no TrainingPosition row. The access service resolves the session category before checking the view-all permission, so it returns false for the unregistered callsign and the test fails. Seed the matching S3 training position (as the mentor tests do) before invoking the policy.
        $session = Session::factory()->create([
            'position' => 'EGLL_APP',
            'filed' => now(),
        ]);

tests/Unit/Training/Mentoring/MentoringReportAccessServiceTest.php:555

  • This helper stores only the session callsign; it does not create a TrainingPosition. Category resolution for ATC callsigns is derived from TrainingPosition rows, so the new TGI and view-all cases using this helper resolve null and fail before exercising their access rules (the default test seeder does not add training positions). Seed the corresponding position in each relevant test while leaving the unknown-position case unseeded.
        return Session::factory()->create([
            'student_id' => $studentMember->id,
            'position' => $this->callsignFor($category),
            'filed' => now(),
        ]);

tests/Unit/Training/Mentoring/MentoringReportAccessServiceTest.php:130

  • The first canViewReport() call builds MentorPermissionService's cached callsign map while no TrainingPosition for EGLL_APP exists. Creating the student's place afterward cannot update that same cached map, so the second assertion remains false. Seed the S3 training position before the first assertion while leaving the place absent, or recreate/reset the service between assertions.
        $this->assertFalse($this->service->canViewReport($mentorAccount, $session));
  • Files reviewed: 8/8 changed files
  • Comments generated: 12
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread app/Filament/Training/Pages/Mentor/MentoringHistory.php
Comment thread app/Policies/Training/Mentoring/MentoringPolicy.php Outdated
Comment thread tests/Feature/TrainingPanel/Mentor/ViewMentoringReportTest.php
Comment thread tests/Unit/Training/Mentoring/MentoringPolicyTest.php
Comment thread tests/Unit/Training/Mentoring/MentoringReportAccessServiceTest.php Outdated
Comment thread app/Policies/Training/Mentoring/MentoringPolicy.php
Comment thread app/Services/Training/MentoringReportAccessService.php
Comment thread app/Services/Training/MentoringReportAccessService.php Outdated
Comment thread app/Services/Training/MentoringReportAccessService.php Outdated
Comment thread app/Services/Training/MentoringReportAccessService.php Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved authorization gaps and query performance/filtering issues remain.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (3)

app/Filament/Training/Pages/Mentor/MentoringHistory.php:122

  • The previous history queries eager-loaded student and mentor, but this replacement returns a Session query without either relation. BaseMentoringHistoryPage dereferences both relations for every table row, so this introduces N+1 CTS queries on each history page; eager-load them on the query returned here (and any other caller that renders these rows).
        $query = $accessService->visibleSessionsQueryFor(auth()->user())
            ->where('taken_date', '<', now());

app/Services/Training/MentoringReportAccessService.php:264

  • visibleSessionsQueryFor() returns a fresh Session builder from the union without eager-loading the relations used by BaseMentoringHistoryPage (student, mentor, and cancelReason). Rendering a page of 100 rows will therefore issue per-row CTS queries for these values, whereas the previous repository queries eager-loaded at least the student and mentor relations. Add eager loading to the returned builder.
        return Session::query()
            ->fromSub($union, 'sessions')
            ->select('sessions.*');

app/Services/Training/MentoringReportAccessService.php:328

  • This loads every active training place and its trainable record, then resolves CTS members for every place holder, on each mentoring-history query. The result is only needed for students in the sessions being listed, and activePlaceGroups() is invoked again for the conducted-session arm, so the cost grows with the entire training-place population rather than the current user's history. Scope this to relevant student accounts (or express it as joins/subqueries) and reuse the computed groups within the request.
        return TrainingPlace::query()
            ->whereNull('deleted_at')
            ->with('trainable')
            ->get()
  • Files reviewed: 9/9 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread app/Services/Training/MentoringReportAccessService.php
Comment thread app/Services/Training/MentoringReportAccessService.php
Comment thread app/Filament/Training/Pages/Mentor/MentoringHistory.php
@CLC0609
CLC0609 marked this pull request as ready for review September 12, 2026 18:39
@CLC0609 CLC0609 changed the title feat: restrict mentoring reports to students TGs feat: changes to the view mentoring report policy Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants