Repository navigation
fix: Authorize a task update in the service layer on the stored task and its loaded status - EXO-90711 - #658
Conversation
|
Self-review close-out — two reviewer rounds by an independent reviewer agent, head
Verified conform: the edit permission is checked on the task loaded by the path id, and the path id is the one updated ( This change is classified N1 (computed on |
The merge-base changed after approval.
5f3a58d to
2a5a860
Compare
…us - EXO-TBD updateTaskById checks the edit permission on the stored task, applies the update to the task of the path, and loads a posted status by its id, checking the view permission on its project when the task changes project; an unknown status is a bad request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TaskService#updateTask(taskId, task, identity) holds the checks the REST endpoint made: the edit permission on the stored task, the update applied to that task, the posted status loaded by its id (an unknown one is an IllegalArgumentException) and the view permission on its project when the task changes project. updateTaskById delegates to it and maps its exceptions to 404, 403 and 400. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The creator is an edit permission input, so updateTask(taskId, task, identity) keeps createdBy and createdTime from the stored task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eaefff4 to
fc90207
Compare
|
Rebased on new feature/maintenance |
|



Symptom:
PUT /tasks/{id}authorized the update on a task built from the request body: a status posted on a task without a project was copied onto it before the edit check, so the project participants the body declared decided the permission; the update applied to the id the body carried, and a status was trusted as posted, so a task could be moved to a project the user cannot view.Cause: the checks lived in
TaskRestService#updateTaskByIdand read the body instead of the stored task and the stored status, whileTaskService#updateTask(TaskDto)checks nothing.Fix: the checks move to the Service layer, in
TaskService#updateTask(long taskId, TaskDto task, Identity identity):ObjectNotFoundException→ 404) and the edit permission is checked on it (IllegalAccessException→ 403); the update applies to that task whatever id the body carries;IllegalArgumentException("task.status.notFound")→ 400); a status of another project requires the view permission on that project (→ 403); a status change inside the task's project, and an update without a status, keep their behaviour;createdByandcreatedTimeare kept from the stored task: the creator is an edit-permission input.updateTaskByIdonly delegates and maps the exceptions.updateTask(TaskDto)is unchanged for the internal callers (MCP tool, listeners); REST contract and UI are unchanged.Behaviour change, deliberate: the edit check is now
TaskUtil.hasEditPermission(TaskService, TaskDto, Identity), the rule ofTaskAclPluginEDIT, which grants the task creator; the previous ConversationState variant did not. Until the rest ofTaskRestServicemoves to the same check, a creator who is neither assignee, coworker nor project member can update a task through this endpoint whileGET /tasks/{id}, clone, delete, labels andupdateCompletedstill refuse them.Tests:
TaskServiceTest#testUpdateTaskAuthorizesOnTheStoredTaskAndTheLoadedStatuspins each guard (each one, reverted, fails it) and the update without a status;TestTaskRestService#testUpdateTaskByIdpins the exception → status mapping.services: 225 tests green.Knowledge: to follow — the eng-standards PR recording the task update authorization in
domains/task.md(§6 states the ACL is checked by callers only), opened before this PR leaves draft.This change is classified N1 (computed on
f06a87ec8) — its approver must be an Archi/Dev who knows it is N1, not an approval on AI review alone; author ≠ approver.🤖 Generated with Claude Code