Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ 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 uninstall/replace actions processing items without checking item type or user rights on the item

## [2.10.4] - 2026-08-04

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions ajax/locations.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
header("Content-Type: text/html; charset=UTF-8");
Html::header_nocache();

Session::checkRightsOr('uninstall:profile', [READ, PluginUninstallProfile::RIGHT_REPLACE]);
Session::checkRightsOr(PluginUninstallUninstall::$rightname, [READ, PluginUninstallProfile::RIGHT_REPLACE]);

if (
Session::haveRight(PluginUninstallUninstall::$rightname, READ)
&& $_POST['templates_id']
) {
$location = PluginUninstallPreference::getLocationByUserByEntity(
$_POST["users_id"],
Session::getLoginUserID(),
$_POST["templates_id"],
$_POST["entity"],
);
Expand Down
9 changes: 8 additions & 1 deletion front/action.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

Html::header(__s('Transfer'), $_SERVER['PHP_SELF'], "admin", "transfer");

Session::checkRightsOr('uninstall:profile', [READ, PluginUninstallProfile::RIGHT_REPLACE]);
Session::checkRightsOr(PluginUninstallUninstall::$rightname, [READ, PluginUninstallProfile::RIGHT_REPLACE]);

if (
!isset($_REQUEST["device_type"])
Expand All @@ -40,6 +40,12 @@
Html::back();
}

/** @var array $UNINSTALL_TYPES */
global $UNINSTALL_TYPES;
if (!in_array($_REQUEST["device_type"], $UNINSTALL_TYPES, true)) {
Html::back();
}

if (isset($_REQUEST["locations_id"])) {
$location = $_REQUEST["locations_id"];
} else {
Expand All @@ -51,6 +57,7 @@
}

if (isset($_REQUEST["replace"])) {
Session::checkRight(PluginUninstallUninstall::$rightname, PluginUninstallProfile::RIGHT_REPLACE);
PluginUninstallReplace::replace(
$_REQUEST["device_type"],
$_REQUEST["model_id"],
Expand Down
2 changes: 1 addition & 1 deletion inc/model.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ public function showForm($ID, $options = [])
echo "</td>";
echo "<td rowspan='4'>" . __s('Comments') . "</td>";
echo "<td rowspan='4'>";
echo "<textarea cols='60' rows='4' name='comment'>" . $this->fields["comment"] . "</textarea>";
echo "<textarea cols='60' rows='4' name='comment'>" . htmlentities((string) $this->fields["comment"]) . "</textarea>";
echo "</td></tr>";

echo "<tr class='tab_bg_1'><td>" . __s('New status of the computer', 'uninstall') . "</td>";
Expand Down
26 changes: 22 additions & 4 deletions inc/replace.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,28 @@ public static function replace($type, $model_id, $tab_ids, $location)
}

$olditem = new $type();
$olditem->getFromDB($olditem_id);
if (!$olditem->getFromDB($olditem_id) || !$olditem->can($olditem_id, UPDATE)) {
continue;

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.

New rights checks continue silently when an item fails, leaving the caller with no visibility into partial failures. $count is already incremented before the check (line 110), so the progress output counts skipped items as processed, and front/action.php unconditionally outputs "Replacement successful" after the call returns. A user replacing 10 items where 3 are denied gets the same feedback as a fully successful run.

The mass-action handler in inc/uninstall.class.php correctly uses MassiveAction::ACTION_NORIGHT; the replace() method should at minimum track and display skipped items in its HTML table, or return a count of failures to the caller.

}

$newitem = new $type();
$newitem->getFromDB($newitem_id);
if (!$newitem->getFromDB($newitem_id) || !$newitem->can($newitem_id, UPDATE)) {
continue;
}

if (
$model->fields['replace_method'] == self::METHOD_PURGE
&& !$olditem->can($olditem_id, PURGE)
) {
continue;
}

if (
$model->fields['replace_method'] == self::METHOD_DELETE_AND_COMMENT
&& !$olditem->can($olditem_id, DELETE)
) {
continue;
}

//Hook to perform actions before item is being replaced
$olditem->fields['_newid'] = $newitem_id;
Expand Down Expand Up @@ -839,11 +857,11 @@ public static function showReplacementForm($type, $model_id, $tab_ids, $location
echo "<td>" . $commonitem->getName() . "</td>";

if (Search::getOptionNumber($type, 'otherserial')) {
echo "<td>" . $commonitem->fields['otherserial'] . "</td>";
echo "<td>" . htmlentities((string) $commonitem->fields['otherserial']) . "</td>";
}

if (Search::getOptionNumber($type, 'serial')) {
echo "<td>" . $commonitem->fields['serial'] . "</td>";
echo "<td>" . htmlentities((string) $commonitem->fields['serial']) . "</td>";
}

echo "<td>";
Expand Down
9 changes: 7 additions & 2 deletions inc/uninstall.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,12 @@ public static function processMassiveActionsForOneItemtype(MassiveAction $ma, Co
if ($ma->getAction() === "uninstall") {
$itemtype = $ma->getItemtype(false);
foreach ($ids as $id) {
if ($item->getFromDB($id)) {
if ($item->getFromDB($id) && $item->can($id, UPDATE)) {
//Session::addMessageAfterRedirect(sprintf(__s('Form duplicated: %s', 'formcreator'), $item->getName()));
$_SESSION['glpi_uninstalllist'][$itemtype][$id] = $id;
$ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
} else {
$ma->itemDone($item->getType(), $id, MassiveAction::ACTION_NORIGHT);
}
}

Expand Down Expand Up @@ -373,7 +375,9 @@ public static function uninstall($type, $model_id, $tab_ids, $location)
$count++;
if (class_exists($type) && is_a($type, CommonDBTM::class, true)) {
$item = new $type();
$item->getFromDB($id);
if (!$item->getFromDB($id) || !$item->can($id, UPDATE)) {
continue;
}

self::doOneUninstall($model, $transfer, $item, [
'type' => $type,
Expand Down Expand Up @@ -476,6 +480,7 @@ public static function deleteComputerInOCS($ocs_id, $ocs_server_id)
global $DB;
if (class_exists('PluginOcsinventoryngOcsServer')) {
$DBocs = PluginOcsinventoryngOcsServer::getDBocs($ocs_server_id)->getDB();
$ocs_id = (int) $ocs_id;

//First try to remove all the network ports
$query = "DELETE
Expand Down