Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Fixed

- Fix the escalation form updating the ticket referenced by the submitted form data instead of the escalated ticket
- Fix the escalation form applying the submitted ticket fields for users who are not allowed to update the ticket

## [2.9.21] - 2026-09-07

### Fixed
Expand Down
5 changes: 5 additions & 0 deletions front/ticket.form.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
Html::displayRightError();
}

// The submitted form details are only applied when the user may update the ticket itself
if (!$ticket->canUpdateItem()) {
unset($_POST['ticket_details']);
}

PluginEscaladeTicket::timelineClimbAction($group_id, $tickets_id, $_POST);

$track = new Ticket();
Expand Down
8 changes: 6 additions & 2 deletions inc/ticket.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -1408,13 +1408,17 @@ public static function timelineClimbAction(int $group_id, int $tickets_id, array
$_form_object['status'] = $_SESSION['glpi_plugins']['escalade']['config']['ticket_last_status'];
}

$update_data = $options['ticket_details'] + [
// The updated ticket must be the authorized one, never the one named by the submitted details
unset($options['ticket_details']['id']);

$update_data = [
'id' => $tickets_id,
'_actors' => PluginEscaladeTicket::getTicketFieldsWithActors($tickets_id, $group_id),
'_plugin_escalade_no_history' => true, // Prevent a duplicated task to be added
'actortype' => CommonITILActor::ASSIGN,
'groups_id' => $group_id,
'_form_object' => $_form_object,
];
] + $options['ticket_details'];

// Preserve existing tags
self::preserveExistingTags($tickets_id, $update_data);
Expand Down
6 changes: 6 additions & 0 deletions tests/EscaladeTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@
use Auth;
use Session;
use DbTestCase;
use Group;
use PluginEscaladeConfig;

abstract class EscaladeTestCase extends DbTestCase
{
public function createGroup(string $group_name = 'TestGroup'): Group
{
return $this->createItem(Group::class, ['name' => $group_name]);
}

protected function login(
string $user_name = TU_USER,
string $user_pass = TU_PASS,
Expand Down
52 changes: 52 additions & 0 deletions tests/Units/EscalationAccessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@

namespace GlpiPlugin\Escalade\Tests\Units;

use CommonITILActor;
use GlpiPlugin\Escalade\Tests\EscaladeTestCase;
use Group_Ticket;
use PluginEscaladeTicket;
use ProfileRight;
use Ticket;

Expand Down Expand Up @@ -66,6 +69,55 @@ public function testAssignOnlyUserCanReachEscalationRoutes(): void
);
}

public function testSubmittedTicketDetailsCannotRetargetAnotherTicket(): void
{
$this->initConfig();
$escalated_ticket = $this->createItem(Ticket::class, ['name' => 'Escalated ticket', 'content' => '']);
$other_ticket = $this->createItem(Ticket::class, ['name' => 'Other ticket', 'content' => '']);
$group = $this->createGroup('RetargetTestGroup');

$_POST['comment'] = 'Escalation comment';
PluginEscaladeTicket::timelineClimbAction(
$group->getID(),
$escalated_ticket->getID(),
[
'ticket_details' => [
'id' => $other_ticket->getID(),
'name' => 'Renamed by the submitted details',
],
],
);

$this->assertEquals('Other ticket', $this->loadTicket($other_ticket->getID())->fields['name']);
$this->assertEquals(
'Renamed by the submitted details',
$this->loadTicket($escalated_ticket->getID())->fields['name'],
);

$group_ticket = new Group_Ticket();
$this->assertTrue($group_ticket->getFromDBByCrit([
'tickets_id' => $escalated_ticket->getID(),
'groups_id' => $group->getID(),
'type' => CommonITILActor::ASSIGN,
]));

unset($_POST['comment']);
}

public function testAssignOnlyUserCannotApplySubmittedTicketDetails(): void
{
$this->initConfig();
$tickets_id = $this->createItem(Ticket::class, ['name' => 'Escalation access test', 'content' => ''])->getID();

$this->setTechnicianTicketRight(Ticket::ASSIGN);
$this->login('tech', 'tech');

$this->assertFalse(
$this->loadTicket($tickets_id)->canUpdateItem(),
'A user with only the ASSIGN right must not have the submitted details applied to the ticket',
);
}

public function testUpdateOnlyUserCannotReachEscalationRoutes(): void
{
$this->initConfig();
Expand Down