Skip to content
41 changes: 41 additions & 0 deletions src/SoftwareLicense_User.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,47 @@ class SoftwareLicense_User extends CommonDBRelation

public static $checkItem_2_Rights = self::HAVE_SAME_RIGHT_ON_ITEM;

public function prepareInputForAdd($input)
{
if (
!isset($input['softwarelicenses_id'])
|| !is_numeric($input['softwarelicenses_id'])
|| !isset($input['users_id'])
) {
trigger_error('softwarelicenses_id and users_id are mandatory', E_USER_WARNING);
return false;
}

$softwarelicenses_id = (int) $input['softwarelicenses_id'];

$license = new SoftwareLicense();
if (!$license->getFromDB($softwarelicenses_id)) {
trigger_error(sprintf('Unable to load software license %d', $softwarelicenses_id), E_USER_WARNING);
return false;
}

// Check quota if not unlimited (-1) and over-quota not allowed
if (
$license->getField('number') != -1
&& !$license->getField('allow_overquota')
) {
// Count current assignments (users + items)
$count = self::countForLicense($softwarelicenses_id);
$count += Item_SoftwareLicense::countForLicense($softwarelicenses_id);

if ($count >= $license->getField('number')) {
Session::addMessageAfterRedirect(
__s('Maximum number of items reached for this license.'),
false,
ERROR
);
return false;
}
}

return parent::prepareInputForAdd($input);
}

public static function getTypeName($nb = 0)
{
return SoftwareLicense::getTypeName($nb);
Expand Down
51 changes: 50 additions & 1 deletion tests/functional/SoftwareLicenseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,12 @@ public function testConsistentInstallationCounting()
'entities_id' => getItemByTypeName('Entity', '_test_root_entity', true),
])->getID();

// Create a license
// Create a license with overquota allowed (this test explicitly exceeds quota)
$license_id = $this->createItem(\SoftwareLicense::class, [
'name' => 'Test license for counting consistency',
'softwares_id' => $software_id,
'number' => 5,
'allow_overquota' => 1,
'entities_id' => getItemByTypeName('Entity', '_test_root_entity', true),
])->getID();

Expand Down Expand Up @@ -578,4 +579,52 @@ private function actionExists($actions, $action_key)
{
return array_key_exists($action_key, $actions);
}

public function testOverQuotaBypassWhenAssigningUserDirectly()
{
$this->login();

$software_id = $this->createItem(\Software::class, [
'name' => 'Test software for over-quota bypass',
'entities_id' => getItemByTypeName('Entity', '_test_root_entity', true),
])->getID();

$license_id = $this->createItem(\SoftwareLicense::class, [
'name' => 'Test license strict quota',
'softwares_id' => $software_id,
'number' => 1,
'allow_overquota' => 0,
'entities_id' => getItemByTypeName('Entity', '_test_root_entity', true),
])->getID();

$user1 = getItemByTypeName('User', TU_USER);
$user2 = getItemByTypeName('User', 'glpi');

// First assignment should succeed (quota: 1/1)
$license_user1_id = $this->createItem(\SoftwareLicense_User::class, [
'softwarelicenses_id' => $license_id,
'users_id' => $user1->getID(),
])->getID();
$this->assertGreaterThan(0, $license_user1_id);

$count_after_first = \SoftwareLicense_User::countForLicense($license_id);
$this->assertEquals(1, $count_after_first);

// Second assignment should FAIL (quota exceeded, allow_overquota=0)
$license_user = new \SoftwareLicense_User();
$result = $license_user->add([
'softwarelicenses_id' => $license_id,
'users_id' => $user2->getID(),
]);

// Expected: false (assignment rejected due to quota)
$this->assertFalse($result);

// Consume the error message added by prepareInputForAdd
$this->hasSessionMessages(ERROR, ['Maximum number of items reached for this license.']);

// Verify count remains at 1 (no over-quota bypass)
$count_after_second = \SoftwareLicense_User::countForLicense($license_id);
$this->assertEquals(1, $count_after_second);
}
}