Skip to content

Check access to the escalated item before escalating - #46

Merged
Rom1-B merged 4 commits into
mainfrom
fix/escalation-access-control
Nov 6, 2026
Merged

Rom1-B merged 4 commits into
mainfrom
fix/escalation-access-control

Conversation

@Lainow

@Lainow Lainow commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

The escalation form (front/escalation.form.php) and its modal (ajax/escalation_form.php) only checked canAssign(), a global profile right: a user allowed to assign items in one entity could escalate any ticket, change or problem of another entity by its id. The comment was then added to its timeline, its assigned groups replaced and, depending on the configuration, its technicians removed and its status changed.

Escalation also relied on the generic CommonDBTM rights with $rightname = 'ticket'. Its table has no entities_id, so through the legacy API any user with the ticket right could create escalations on items of any entity (even where escalation is disabled), list every escalation (private ones included), and edit or delete them. As the table has is_private and users_id, CommonDBTM::can() also granted every right, creation included, to the author of a private escalation.

Changes

  • Escalation::canEscalate(): escalation enabled for the item entity, item visible to the user (can($id, READ), which checks its entity) and assignable. Used by the form, the modal, the "Escalate" button and canCreateItem().
  • The form goes through Escalation::check(-1, CREATE), the same check as the API.
  • Escalations cannot be read, edited nor deleted on their own (canView()/canUpdate()/canDelete()/canPurge() are false, as for Group_Link): they are only shown in the timeline of their item, which reads them directly. canViewItem() still tells whether the item, and the private followups for a private one, can be seen.
  • maybePrivate() is false: is_private means internal, not personal.
  • prepareInputForAdd() refuses items of entities where escalation is not enabled. The rights of the user are deliberately not checked there: business rules (EscalationRule) escalate items whatever the user who triggers them, e.g. the mail collector.

Tests

New tests in EscalationTest, for tickets, changes and problems:

  • an item of an entity the user has no access to cannot be escalated, even with the "private escalation" input;
  • an item of an entity where escalation is not enabled cannot be escalated;
  • an escalation cannot be read, edited nor deleted on its own, even by its author.

The escalation form and modal only checked the global assign right, so a
user could escalate a ticket, change or problem of an entity they have no
access to. Escalation also relied on the generic CommonDBTM rights: its
table has no entity, so the API let any ticket holder create, list, edit or
delete escalations of any entity. As it has `is_private` and `users_id`
fields, the core also granted every right to the author of a private one.

- Escalation::canEscalate(): escalation enabled, item visible (which
  checks its entity) and assignable; used by the form, the modal, the
  button and canCreateItem()
- front/escalation.form.php goes through Escalation::check(CREATE)
- reading, editing and deleting escalations on their own is refused: they
  are only shown in the timeline of their item
- maybePrivate() is false: `is_private` means internal, not personal
- prepareInputForAdd() refuses items of entities where escalation is not
  enabled; the rights of the user are not checked there, business rules
  escalate whatever the user who triggers them
@Lainow Lainow self-assigned this Nov 5, 2026
@Lainow
Lainow requested review from a team, Mary-Clb, jdurand-teclib and stonebuzz and removed request for a team November 5, 2026 15:21
stonebuzz
stonebuzz previously approved these changes Nov 5, 2026
Comment thread src/Escalation.php Outdated
return $item instanceof CommonITILObject && self::canEscalate($item);
}

public function canViewItem(): bool

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.

canViewItem() is never reached in production: canView() returns false so can($id, READ) short-circuits, and the timeline uses getEscalationsOf(), which duplicates the private rule (SEEPRIVATE) on its own.
Only the tests call it, so the two privacy rules can drift; either drop it or make it the single source of the rule.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, i've deleted canViewItem()

It was never reached: canView() is false, so can($id, READ) stops there,
and the timeline applies the private rule through getEscalationsOf().
@stonebuzz
stonebuzz requested review from Rom1-B and removed request for Mary-Clb and jdurand-teclib November 6, 2026 07:12
@stonebuzz stonebuzz added the bug Something isn't working label Nov 6, 2026
@Rom1-B
Rom1-B merged commit d560b36 into main Nov 6, 2026
7 checks passed
@Rom1-B
Rom1-B deleted the fix/escalation-access-control branch November 6, 2026 07:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants