Repeating Tasks Ignored Company Scope - edmozley/freeitsm GitHub Wiki
Security: repeating tasks ignored company scope
Fixed in 3.10.0 · Found while mapping #181 · Present in every release from 1.0.0 to 3.9.0 · Module: Tasks · Only installs with more than one company
The short version
On an install with more than one company, three parts of repeating tasks did not check which company a task belonged to:
- Setting up, changing or stopping a repeat on a task.
- Previewing a repeat - which also showed the task's title.
- The calendar's projected occurrences - the faint future dates of repeats on a fixed schedule, with their titles.
So an analyst with Tasks access in one company could change or stop another company's repeats, or read their task titles, by sending a task number they were not allowed to see. Every other task screen and endpoint already checked; these three did not.
A single-company install was never affected. Nothing was exposed to the self-service portal or to anybody without Tasks access.
What was wrong
Every task endpoint answers "not found" for a task in a company the analyst cannot reach, through analystCanAccessTask(), and the task list filters by company through activeTenantReadFilter(). Repeats were added later, and their two endpoints loaded the task by its number alone:
$t = $conn->prepare("SELECT id, due_date, recurrence_id, parent_task_id FROM tasks WHERE id = ?");
and TaskRecurrence::project(), which draws the calendar's future occurrences, selected every active series on the install with no company filter.
The fix
api/tasks/save_recurrence.phpandapi/tasks/recurrence_preview.phpcheckanalystCanAccessTask()straight after loading the task, and answer exactly as for a task that does not exist - so another company's task number is not even confirmed.TaskRecurrence::project()takes the analyst and applies the same company filter as the task list, so the calendar shows the repeats of the company you are working in (or every company you can reach under All companies), exactly like the board.
📁 Files changed
| File | Change | |
|---|---|---|
| 🟦 | api/tasks/save_recurrence.php |
Company check after loading the task |
| 🟦 | api/tasks/recurrence_preview.php |
Company check after loading the task |
| 🟦 | api/tasks/projected.php |
Passes the analyst to project() |
| 🟦 | includes/services/task_recurrence.php |
project() filters by company when given an analyst |
| 🟩 | tests/task-recurrence-company-scope.php |
The test below |
How it was verified
php tests/task-recurrence-company-scope.php creates a throwaway analyst who can reach one company and two repeating tasks - one in that company, one in another - and calls the real endpoints over HTTP:
- the calendar projects their company's repeat and not the other's;
- previewing the other company's task is refused, without its title;
- stopping the other company's repeat is refused, and it keeps running;
- their own company's task still works;
- positive control: an administrator who can reach every company still previews the other task, and their calendar shows their working company's repeat.
The control that matters: the same test against the code before the fix fails 6 of 10 - the calendar showed the other company's repeat, the preview returned it, and the repeat was actually stopped. With the fix, 10 of 10 pass.
What this means for you
Upgrade. If you run several companies and want to check nothing was changed: a repeat stopped by somebody who could not see the task left no record (repeat history arrives with #181), so look over the repeating tasks you rely on and confirm each is still set to repeat.