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
101 changes: 80 additions & 21 deletions src/Glpi/Features/AssignableItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

namespace Glpi\Features;

use CommonITILObject;
use Glpi\DBAL\QueryExpression;
use Glpi\DBAL\QuerySubQuery;
use Group_Item;
Expand Down Expand Up @@ -105,8 +106,26 @@ public function canUpdateItem(): bool
* @return array[]|QueryExpression[]
* @see AssignableItemInterface::getAssignableVisiblityCriteria()
*/
public static function getAssignableVisiblityCriteria(?string $item_table_reference = null): array
{
public static function getAssignableVisiblityCriteria(
?string $item_table_reference = null
): array {
$criteria = Session::getCurrentInterface() === "central"
? self::getAssignableVisiblityCriteriaForCentral($item_table_reference)
: self::getAssignableVisiblityCriteriaForHelpdesk($item_table_reference)
;

// Add another layer to the array to prevent losing duplicates keys if the
// result of the function is merged with another array
return [crc32(serialize($criteria)) => $criteria];
}

/**
* @param string|null $item_table_reference
* @return array[]|QueryExpression[]
*/
private static function getAssignableVisiblityCriteriaForCentral(
?string $item_table_reference = null
): array {
if (!Session::haveRightsOr(static::$rightname, [READ, READ_ASSIGNED, READ_OWNED])) {
return [new QueryExpression('0')];
}
Expand Down Expand Up @@ -138,29 +157,69 @@ public static function getAssignableVisiblityCriteria(?string $item_table_refere
}
}
if (Session::haveRight(static::$rightname, READ_OWNED)) {
$or += self::getOwnAssetsCriteria($item_table, $relation_table);
}

return ['OR' => $or];
}

/**
* @param string|null $item_table_reference
* @return array[]|QueryExpression[]
*/
private static function getAssignableVisiblityCriteriaForHelpdesk(
?string $item_table_reference = null
): array {
// Helpdesk doesn't support READ, READ_ASSIGNED, READ_OWNED rights.
// Instead, we will directly check the helpdesk_hardware and
// helpdesk_item_type properties from the profile
$profile = Session::getCurrentProfile();

$raw_allowed_itemtypes = $profile->fields['helpdesk_item_type'];
$allowed_itemtypes = importArrayFromDB($raw_allowed_itemtypes);
if (!in_array(static::class, $allowed_itemtypes)) {
return [new QueryExpression('0')];
}

$raw_rights = $profile->fields['helpdesk_hardware'];
$all_assets = $raw_rights & (2 ** CommonITILObject::HELPDESK_ALL_HARDWARE);
if ($all_assets) {
return [new QueryExpression('1')];
}

$my_assets = $raw_rights & (2 ** CommonITILObject::HELPDESK_MY_HARDWARE);
if ($my_assets) {
$item_table = $item_table_reference ?? static::getTable();
$relation_table = Group_Item::getTable();
$or = self::getOwnAssetsCriteria($item_table, $relation_table);

return ['OR' => $or];
}

// User can't see any assets
return [new QueryExpression('0')];
}

private static function getOwnAssetsCriteria(
string $item_table,
string $relation_table,
): array {
$or = [$item_table . '.users_id' => $_SESSION['glpiID']];
if (count($_SESSION['glpigroups']) > 0) {
$or[] = [
$item_table . '.users_id' => $_SESSION['glpiID'],
$item_table . '.id' => new QuerySubQuery([
'SELECT' => $relation_table . '.items_id',
'FROM' => $relation_table,
'WHERE' => [
'itemtype' => static::class,
'groups_id' => $_SESSION['glpigroups'],
'type' => Group_Item::GROUP_TYPE_NORMAL,
],
]),
];
if (count($_SESSION['glpigroups']) > 0) {
$or[] = [
$item_table . '.id' => new QuerySubQuery([
'SELECT' => $relation_table . '.items_id',
'FROM' => $relation_table,
'WHERE' => [
'itemtype' => static::class,
'groups_id' => $_SESSION['glpigroups'],
'type' => Group_Item::GROUP_TYPE_NORMAL,
],
]),
];
}
}

// Add another layer to the array to prevent losing duplicates keys if the
// result of the function is merged with another array
$criteria = [crc32(serialize($or)) => ['OR' => $or]];

return $criteria;
return $or;
}

/** @see AssignableItemInterface::getRights() */
Expand Down
90 changes: 86 additions & 4 deletions tests/functional/DropdownTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
namespace tests\units;

use CommonDBTM;
use CommonITILObject;
use Computer;
use DbTestCase;
use Entity;
use Generator;
use Glpi\Asset\Asset_PeripheralAsset;
use Glpi\Features\AssignableItem;
Expand All @@ -45,9 +47,11 @@
use Item_DeviceSimcard;
use Monitor;
use PHPUnit\Framework\Attributes\DataProvider;
use Profile;
use Session;
use State;
use Symfony\Component\DomCrawler\Crawler;
use Ticket;
use User;

/* Test for inc/dropdown.class.php */
Expand Down Expand Up @@ -2537,20 +2541,20 @@ public function testSupplierActorDropdownOnlyActive()
'is_recursive' => 1,
]);
$params = [
'itemtype' => \Ticket::class,
'itemtype' => Ticket::class,
'actortype' => 'assign',
'returned_itemtypes' => [\Supplier::class],
'searchText' => '',
];
$results = \Dropdown::getDropdownActors($params + ['_idor_token' => Session::getNewIDORToken(\Ticket::class, $params)], false);
$results = \Dropdown::getDropdownActors($params + ['_idor_token' => Session::getNewIDORToken(Ticket::class, $params)], false);
$this->assertNotEmpty($results['results'][0]['children']);
$this->assertCount(0, array_filter($results['results'][0]['children'], function ($result) use ($inactive_supplier) {
return $result['id'] === \Supplier::class . '_' . $inactive_supplier->getID();
}));

// If asking for inactive_deleted, it should return the inactive supplier
$params['inactive_deleted'] = 1;
$results = \Dropdown::getDropdownActors($params + ['_idor_token' => Session::getNewIDORToken(\Ticket::class, $params)], false);
$results = \Dropdown::getDropdownActors($params + ['_idor_token' => Session::getNewIDORToken(Ticket::class, $params)], false);
$this->assertNotEmpty($results['results'][0]['children']);
$this->assertCount(1, array_filter($results['results'][0]['children'], function ($result) use ($inactive_supplier) {
return $result['id'] === \Supplier::class . '_' . $inactive_supplier->getID();
Expand Down Expand Up @@ -2653,7 +2657,7 @@ public function testGetDropdownMyDevices()
]);

// Ensure proper permissions and helpdesk types
$_SESSION["glpiactiveprofile"]["helpdesk_hardware"] = pow(2, \Ticket::HELPDESK_MY_HARDWARE);
$_SESSION["glpiactiveprofile"]["helpdesk_hardware"] = pow(2, Ticket::HELPDESK_MY_HARDWARE);
$_SESSION["glpiactiveprofile"]["helpdesk_item_type"] = ['Computer', 'Monitor', 'Printer'];

$post = [
Expand Down Expand Up @@ -2736,4 +2740,82 @@ public function testGetDropdownMyDevices()
// Test that count is accurate
$this->assertGreaterThan(0, $result['count']);
}

public static function assetsDropdownForHelpdeskProvider(): iterable
{
yield 'no rights' => [
'can_view' => 0,
'itemtypes' => [Computer::class],
'expected' => [],
];
yield 'see his own computers' => [
'can_view' => 2 ** CommonITILObject::HELPDESK_MY_HARDWARE,
'itemtypes' => [Computer::class],
'expected' => ['My computer'],
];
yield 'see all computers' => [
'can_view' => 2 ** CommonITILObject::HELPDESK_ALL_HARDWARE,
'itemtypes' => [Computer::class],
'expected' => ['My computer', 'Not my computer'],
];
yield 'see all monitors' => [
'can_view' => 2 ** CommonITILObject::HELPDESK_ALL_HARDWARE,
'itemtypes' => [Monitor::class],
'expected' => [],
];
}

#[DataProvider('assetsDropdownForHelpdeskProvider')]
public function testAssetsDropdownForHelpdesk(
int $can_view,
array $itemtypes,
array $expected,
): void {
// Arrange: assign a computer to a self-service user and set up the
// profile with the given rights.
// Wrap items in an entity for better test isolation
$this->login(); // Need to be logged in to create an entity
$entity = $this->createItem(Entity::class, [
'name' => 'My entity',
'entities_id' => $this->getTestRootEntity(only_id: true),
]);
$this->logOut();
$this->createItem(Computer::class, [
'name' => 'My computer',
'entities_id' => $entity->getID(),
'users_id' => getItemByTypeName(User::class, "post-only", true),
]);
$this->createItem(Computer::class, [
'name' => 'Not my computer',
'entities_id' => $entity->getID(),
]);
$this->updateItem(
Profile::class,
getItemByTypeName(Profile::class, 'Self-Service', onlyid: true),
[
'helpdesk_hardware' => $can_view,
'helpdesk_item_type' => $itemtypes,
],
['helpdesk_item_type'],
);

// Act: get dropdown values for this user
$this->login('post-only');
$this->setEntity("My entity", false);
$params = [
'itemtype' => Computer::class,
];
$params['_idor_token'] = Session::getNewIDORToken(Computer::class, $params);
$results = \Dropdown::getDropdownValue($params, false);

// Assert: only one computer should be count
$this->assertEquals(count($expected), $results["count"]);
if (!empty($expected)) {
$found_items = array_map(
fn($data) => $data['text'],
$results["results"][1]["children"],
);
$this->assertEquals($expected, $found_items);
}
}
}