Skip to content

Commit 75e8a64

Browse files
committed
Fix: check the right matching the model behavior (#664)
Co-authored-by: Stanislas Kita <7335054+stonebuzz@users.noreply.github.com> (cherry picked from commit 1b64466)
1 parent 2f007bb commit 75e8a64

6 files changed

Lines changed: 289 additions & 43 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1414
- Fix various minor bugs in the import/export workflow (backport of PR #656 from GLPI 11-compatible line)
1515
- Move network port lookup query to the GLPI DBAL iterator (backport of PR #659 from GLPI 11-compatible line)
1616
- Fix model selector validation and access control (backport of PR #660 from GLPI 11-compatible line)
17+
- Fix validation, permissions and entity handling during imports (backport of PR #664 from GLPI 11-compatible line)
18+
- Improve partial import error reporting (backport of PR #664 from GLPI 11-compatible line)
19+
- Correct user password import and creation handling (policy, history, expiration and confirmation) (backport of PR #664 from GLPI 11-compatible line)
1720

1821
## [2.14.4] - 2025-11-25
1922

‎inc/commoninjectionlib.class.php‎

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -547,24 +547,22 @@ private function manageFieldValues()
547547

548548

549549
/**
550-
* Get the ID associated with a value from the CSV file
551-
*
552-
* @param PluginDatainjectionInjectionInterface|null $injectionClass
553-
* @param string $itemtype itemtype of the values to inject
554-
* @param array $searchOption option associated with the field to check
555-
* @param string $field the field to check
556-
* @param string $value the value coming from the CSV file
557-
* @param boolean $add is insertion (true) or update (false) (true by default)
558-
*
559-
* @return void nothing
560-
**/
550+
* Get the ID associated with a value from the CSV file
551+
*
552+
* @param PluginDatainjectionInjectionInterface|null $injectionClass
553+
* @param string $itemtype itemtype of the values to inject
554+
* @param array $searchOption option associated with the field to check
555+
* @param string $field the field to check
556+
* @param string $value the value coming from the CSV file
557+
*
558+
* @return void nothing
559+
**/
561560
private function getFieldValue(
562561
$injectionClass,
563562
$itemtype,
564563
$searchOption,
565564
$field,
566-
$value,
567-
$add = true
565+
$value
568566
) {
569567
if (isset($searchOption['storevaluein'])) {
570568
$linkfield = $searchOption['storevaluein'];
@@ -583,13 +581,12 @@ private function getFieldValue(
583581
break;
584582

585583
case 'password':
586-
//To add a user password, it's mandatory is give a password and it's confirmation
587-
//Here we cannot detect if it's an add or update. We'll handle updates later in the process
588-
if ($add && $itemtype == 'User') {
584+
//Core needs both the password and its confirmation to validate and hash it, on add as well as on update
585+
if ($itemtype == 'User') {
589586
$this->setValueForItemtype($itemtype, $linkfield, $value);
590-
//Add field password2 is not already present
587+
//Add field password2 if not already present
591588
//(can be present if password was an addtional information)
592-
if (!isset($this->values[$itemtype][$field])) {
589+
if (!isset($this->values[$itemtype][$linkfield . "2"])) {
593590
$this->setValueForItemtype($itemtype, $linkfield . "2", $value);
594591
}
595592
}
@@ -927,7 +924,8 @@ private function unsetValue($itemtype, $field)
927924
**/
928925
private function setValueForItemtype($itemtype, $field, $value, $fromdb = false)
929926
{
930-
if ($itemtype === User::class && $field === "pdffont" && $fromdb) {
927+
//The stored password is a hash: taking it back from the DB would overwrite the imported one
928+
if ($itemtype === User::class && in_array($field, ['pdffont', 'password'], true) && $fromdb) {
931929
return;
932930
}
933931
$injectionClass = self::getInjectionClassInstance($itemtype);
@@ -1577,14 +1575,17 @@ public function processAddOrUpdate()
15771575
$newID = $this->effectiveAddOrUpdate($this->injectionClass, $item, $values, $add);
15781576

15791577
if (!$newID) {
1580-
$this->results['status'] = self::WARNING;
1578+
$this->addCheckWarning(self::WARNING, get_class($item));
15811579
} else {
15821580
//Store id of the injected item
15831581
$this->setValueForItemtype($this->primary_type, 'id', $newID);
15841582

1585-
//If type needs it : process more data after type import
1586-
$this->processAfterInsertOrUpdate($this->injectionClass, $add);
1587-
//$this->results['status'] = self::SUCCESS;
1583+
//If type needs it : process more data after type import
1584+
if ($this->processAfterInsertOrUpdate($this->injectionClass, $add) === false) {
1585+
$this->addCheckWarning(self::WARNING, get_class($item));
1586+
}
1587+
1588+
//$this->results['status'] = self::SUCCESS;
15881589
$this->results[get_class($item)] = $newID;
15891590

15901591
//Process other types
@@ -1619,7 +1620,11 @@ public function processAddOrUpdate()
16191620
$values = $this->getValuesForItemtype($itemtype);
16201621
if ($this->lastCheckBeforeProcess($injectionClass, $values)) {
16211622
$tmpID = $this->effectiveAddOrUpdate($injectionClass, $item, $values, $add);
1622-
$this->processAfterInsertOrUpdate($injectionClass, $add);
1623+
if (!$tmpID) {
1624+
$this->addCheckWarning(self::WARNING, $itemtype);
1625+
} elseif ($this->processAfterInsertOrUpdate($injectionClass, $add) === false) {
1626+
$this->addCheckWarning(self::WARNING, $itemtype);
1627+
}
16231628
}
16241629
}
16251630
}
@@ -1630,6 +1635,20 @@ public function processAddOrUpdate()
16301635
}
16311636

16321637

1638+
/**
1639+
* Flag the current line as partially injected and log the reason
1640+
*
1641+
* @param integer $code log label describing the reason
1642+
* @param string $itemtype itemtype that could not be written
1643+
**/
1644+
private function addCheckWarning(int $code, string $itemtype): void
1645+
{
1646+
$this->results['status'] = self::WARNING;
1647+
$this->results[self::ACTION_CHECK]['status'] = self::WARNING;
1648+
$this->results[self::ACTION_CHECK][] = [$code, $itemtype];
1649+
}
1650+
1651+
16331652
/**
16341653
* Perform data injection into GLPI DB
16351654
*
@@ -1643,7 +1662,25 @@ public function processAddOrUpdate()
16431662
private function effectiveAddOrUpdate($injectionClass, $item, $values, $add = true)
16441663
{
16451664

1646-
//Insert data using the standard add() method
1665+
//The plugin acts as the front controller here: rights must be checked before writing.
1666+
//Skipped without a session, as the lib is also a programmatic entry point for scripts.
1667+
if (Session::getLoginUserID() !== false) {
1668+
$input = is_array($values) ? $values : [];
1669+
if ($add) {
1670+
//Passing the input to can() makes the check cover the target entity
1671+
if (!$item->can(-1, CREATE, $input)) {
1672+
$this->addCheckWarning(self::ERROR_CANNOT_IMPORT, get_class($item));
1673+
return 0;
1674+
}
1675+
1676+
//On the update path the target id is known, so the per-item check also covers the entity scope
1677+
} elseif (!isset($values['id']) || !$item->can($values['id'], UPDATE)) {
1678+
$this->addCheckWarning(self::ERROR_CANNOT_UPDATE, get_class($item));
1679+
return 0;
1680+
}
1681+
}
1682+
1683+
//Insert data using the standard add() method
16471684
$toinject = [];
16481685
$options = $injectionClass->getOptions();
16491686

@@ -1816,7 +1853,6 @@ private function manageRelations()
18161853
$option,
18171854
$option['linkfield'],
18181855
$value,
1819-
true
18201856
);
18211857
}
18221858
}
@@ -2317,15 +2353,17 @@ public static function addTemplateSearchOptions($injectionClass, &$tab)
23172353
* @param PluginDatainjectionInjectionInterface $injectionClass the injection class to use
23182354
* @param $add true if an item is created, false if it's an update
23192355
*
2320-
* @return void nothing
2356+
* @return bool false if the injection class rejected a post-processing step
23212357
**/
23222358
private function processAfterInsertOrUpdate($injectionClass, $add = true)
23232359
{
23242360

23252361
//If itemtype implements special process after type injection
23262362
if (method_exists($injectionClass, 'processAfterInsertOrUpdate')) {
2327-
//Invoke it
2328-
$injectionClass->processAfterInsertOrUpdate($this->values, $add, $this->rights);
2363+
//Invoke it
2364+
return $injectionClass->processAfterInsertOrUpdate($this->values, $add, $this->rights) !== false;
23292365
}
2366+
2367+
return true;
23302368
}
23312369
}

‎inc/model.class.php‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1357,13 +1357,25 @@ public static function checkRightOnModel(int $models_id): bool
13571357
}
13581358
}
13591359

1360+
$check_add = (bool) ($model->fields['behavior_add'] ?? 0);
1361+
$check_update = (bool) ($model->fields['behavior_update'] ?? 0);
1362+
1363+
//A model doing nothing still requires the creation right to be listed
1364+
if (!$check_add && !$check_update) {
1365+
$check_add = true;
1366+
}
1367+
13601368
foreach (array_unique($itemtypes) as $itemtype) {
13611369
if ($itemtype == PluginDatainjectionInjectionType::NO_VALUE || !is_a($itemtype, CommonDBTM::class, true)) {
13621370
continue;
13631371
}
13641372

13651373
$item = new $itemtype();
1366-
if (!$item->canCreate()) {
1374+
if ($check_add && !$item->canCreate()) {
1375+
return false;
1376+
}
1377+
1378+
if ($check_update && !$item->canUpdate()) {
13671379
return false;
13681380
}
13691381
}

‎inc/userinjection.class.php‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,11 @@ public function reformat(&$values)
189189
* @param array $values
190190
* @param boolean $add (true by default)
191191
* @param array|null $rights array
192+
*
193+
* @return bool false if a post-processing step was rejected
192194
*/
193195
public function processAfterInsertOrUpdate($values, $add = true, $rights = [])
194196
{
195-
/** @var DBmysql $DB */
196-
global $DB;
197-
198197
//Manage user emails (both for add and update)
199198
if (
200199
isset($values['User']['useremails_id'])
@@ -234,13 +233,7 @@ public function processAfterInsertOrUpdate($values, $add = true, $rights = [])
234233
}
235234
}
236235

237-
if (isset($values['User']['password']) && ($values['User']['password'] != '')) {
238-
$DB->update(
239-
'glpi_users',
240-
['password' => Auth::getPasswordHash(Sanitizer::unsanitize($values['User']['password']))],
241-
['id' => $values['User']['id']],
242-
);
243-
}
236+
return true;
244237
}
245238

246239

0 commit comments

Comments
 (0)