diff --git a/defaults/config.ini.default b/defaults/config.ini.default index af44d9ed5..168e3b3c7 100644 --- a/defaults/config.ini.default +++ b/defaults/config.ini.default @@ -32,9 +32,10 @@ group_ou = "ou=groups,dc=unityhpc,dc=test" ; Group organizational unit pigroup_ou = "ou=pi_groups,dc=unityhpc,dc=test" ; PI Group organizational unit orggroup_ou = "ou=org_groups,dc=unityhpc,dc=test" ; ORG group organizational unit def_user_shell = "/bin/bash" ; Default shell for new users -offset_UIDGID = 1000000 ; start point when allocating new UID/GID pairs for a new user -offset_PIGID = 2000000 ; start point when allocating new GID for a new PI group -offset_ORGGID = 3000000 ; start point when allocating new GID for a new org group +offset_user_uidnumber = 1000000 ; start point when allocating new UIDnumber for a new user +offset_user_gidnumber = 1000000 ; start point when allocating new GIDnumber for a new user +offset_pi_gidnumber = 2000000 ; start point when allocating new GIDnumber for a new PI group +offset_org_gidnumber = 3000000 ; start point when allocating new GIDnumber for a new org group user_flag_groups[admin] = "cn=web_admins,dc=unityhpc,dc=test" ; admin user group dn user_flag_groups[ghost] = "cn=ghost,dc=unityhpc,dc=test" ; ghost user group dn user_flag_groups[idlelocked] = "cn=idlelocked,dc=unityhpc,dc=test" ; idlelocked user group dn diff --git a/deployment/overrides/course-creator/config/config.ini b/deployment/overrides/course-creator/config/config.ini index 06847b570..1bf7f8b44 100644 --- a/deployment/overrides/course-creator/config/config.ini +++ b/deployment/overrides/course-creator/config/config.ini @@ -1,7 +1,8 @@ # we have hacked in course groups using normal PI accounts but using a different IDnumber range [ldap] -offset_UIDGID = 20000 -offset_PIGID = 20000 +offset_user_uidnumber = 20000 +offset_user_gidnumber = 20000 +offset_pi_gidnumber = 20000 [site] enable_exception_handler = false diff --git a/resources/lib/UnityLDAP.php b/resources/lib/UnityLDAP.php index d6134c9ac..3b42c0314 100644 --- a/resources/lib/UnityLDAP.php +++ b/resources/lib/UnityLDAP.php @@ -2,6 +2,8 @@ namespace UnityWebPortal\lib; +use RuntimeException; +use UnityWebPortal\lib\exceptions\ArrayKeyException; use UnityWebPortal\lib\exceptions\EntryNotFoundException; use PHPOpenLDAPer\LDAPConn; use PHPOpenLDAPer\LDAPEntry; @@ -35,9 +37,10 @@ class UnityLDAP extends LDAPConn private string $custom_mappings_path = __DIR__ . "/../../" . CONFIG["ldap"]["custom_user_mappings_dir"]; private string $def_user_shell = CONFIG["ldap"]["def_user_shell"]; - private int $offset_UIDGID = CONFIG["ldap"]["offset_UIDGID"]; - private int $offset_PIGID = CONFIG["ldap"]["offset_PIGID"]; - private int $offset_ORGGID = CONFIG["ldap"]["offset_ORGGID"]; + private int $offset_user_uidnumber = CONFIG["ldap"]["offset_user_uidnumber"]; + private int $offset_user_gidnumber = CONFIG["ldap"]["offset_user_gidnumber"]; + private int $offset_pi_gidnumber = CONFIG["ldap"]["offset_pi_gidnumber"]; + private int $offset_org_gidnumber = CONFIG["ldap"]["offset_org_gidnumber"]; // Instance vars for various ldapEntry objects private LDAPEntry $baseOU; @@ -68,40 +71,66 @@ public function getDefUserShell(): string return $this->def_user_shell; } - public function getNextUIDGIDNumber(string $uid): int + public function getNextUserUIDNumber(string $uid): int { - $IDNumsInUse = array_merge($this->getAllUIDNumbersInUse(), $this->getAllGIDNumbersInUse()); + $IDNumsInUse = $this->getAllUIDNumbersInUse(); $customIDMappings = $this->getCustomIDMappings(); - $customMappedID = $customIDMappings[$uid] ?? null; - if (!is_null($customMappedID) && !in_array($customMappedID, $IDNumsInUse)) { - return $customMappedID; + if (array_key_exists($uid, $customIDMappings)) { + $customMappedID = $customIDMappings[$uid][0]; + if (in_array($customMappedID, $IDNumsInUse)) { + UnityHTTPD::errorLog( + "warning", + sprintf( + "user '%s' has a custom mapped GIDNumber %s but it's already in use!", + $uid, + $customMappedID, + ), + ); + } else { + return $customMappedID; + } } - if (!is_null($customMappedID) && in_array($customMappedID, $IDNumsInUse)) { - UnityHTTPD::errorLog( - "warning", - "user '$uid' has a custom mapped IDNumber $customMappedID but it's already in use!", - ); + $customMappedUIDNumbers = array_map(fn($x) => $x[0], $this->getCustomIDMappings()); + $IDNumbersToSkip = array_merge($this->getAllUIDNumbersInUse(), $customMappedUIDNumbers); + return $this->getNextIDNumber($this->offset_user_uidnumber, $IDNumbersToSkip); + } + + public function getNextUserGIDNumber(string $uid): int + { + $IDNumsInUse = $this->getAllGIDNumbersInUse(); + $customIDMappings = $this->getCustomIDMappings(); + if (array_key_exists($uid, $customIDMappings)) { + $customMappedID = $customIDMappings[$uid][1]; + if (in_array($customMappedID, $IDNumsInUse)) { + UnityHTTPD::errorLog( + "warning", + sprintf( + "user '%s' has a custom mapped GIDNumber %s but it's already in use!", + $uid, + $customMappedID, + ), + ); + } else { + return $customMappedID; + } } - return $this->getNextIDNumber( - $this->offset_UIDGID, - array_merge($IDNumsInUse, array_values($this->getCustomIDMappings())), - ); + $customMappedGIDNumbers = array_map(fn($x) => $x[1], $this->getCustomIDMappings()); + $IDNumbersToSkip = array_merge($this->getAllGIDNumbersInUse(), $customMappedGIDNumbers); + return $this->getNextIDNumber($this->offset_user_gidnumber, $IDNumbersToSkip); } public function getNextPIGIDNumber(): int { - return $this->getNextIDNumber( - $this->offset_PIGID, - array_merge($this->getAllGIDNumbersInUse(), array_values($this->getCustomIDMappings())), - ); + $customMappedGIDNumbers = array_map(fn($x) => $x[1], $this->getCustomIDMappings()); + $IDNumbersToSkip = array_merge($this->getAllGIDNumbersInUse(), $customMappedGIDNumbers); + return $this->getNextIDNumber($this->offset_pi_gidnumber, $IDNumbersToSkip); } public function getNextOrgGIDNumber(): int { - return $this->getNextIDNumber( - $this->offset_ORGGID, - array_merge($this->getAllGIDNumbersInUse(), array_values($this->getCustomIDMappings())), - ); + $customMappedGIDNumbers = array_map(fn($x) => $x[1], $this->getCustomIDMappings()); + $IDNumbersToSkip = array_merge($this->getAllGIDNumbersInUse(), $customMappedGIDNumbers); + return $this->getNextIDNumber($this->offset_org_gidnumber, $IDNumbersToSkip); } private function isIDNumberForbidden($id): bool @@ -142,8 +171,8 @@ private function getCustomIDMappings(): array } } $output_map = []; - foreach ($output as [$uid, $uidNumber_str]) { - $output_map[$uid] = digits2int($uidNumber_str); + foreach ($output as [$uid, $uidNumber_str, $gidNumber_str]) { + $output_map[$uid] = [digits2int($uidNumber_str), digits2int($gidNumber_str)]; } return $output_map; } diff --git a/resources/lib/UnityUser.php b/resources/lib/UnityUser.php index bafe6571f..d49359903 100644 --- a/resources/lib/UnityUser.php +++ b/resources/lib/UnityUser.php @@ -57,11 +57,12 @@ public function init( bool $send_mail = true, ): void { $ldapGroupEntry = $this->getGroupEntry(); - $id = $this->LDAP->getNextUIDGIDNumber($this->uid); + $uidnumber = $this->LDAP->getNextUserUIDNumber($this->uid); + $gidnumber = $this->LDAP->getNextUserGIDNumber($this->uid); \ensure(!$ldapGroupEntry->exists()); $ldapGroupEntry->create([ "objectclass" => UnityLDAP::POSIX_GROUP_CLASS, - "gidnumber" => strval($id), + "gidnumber" => strval($gidnumber), ]); \ensure(!$this->entry->exists()); $this->entry->create([ @@ -74,8 +75,8 @@ public function init( "o" => $org, "homedirectory" => self::HOME_DIR . $this->uid, "loginshell" => $this->LDAP->getDefUserShell(), - "uidnumber" => strval($id), - "gidnumber" => strval($id), + "uidnumber" => strval($uidnumber), + "gidnumber" => strval($gidnumber), ]); $org = $this->getOrgGroup(); if (!$org->exists()) { diff --git a/test/custom_user_mappings/test.csv b/test/custom_user_mappings/test.csv index f923992e4..2f3462f32 100644 --- a/test/custom_user_mappings/test.csv +++ b/test/custom_user_mappings/test.csv @@ -1,6 +1,6 @@ -user2002_org998_test,555 -foobar0,1000000 -foobar1,1000001 -foobar2,1000002 -foobar3,1000003 -foobar4,1000004 +user2002_org998_test,555,556 +foobar0,1000000,1000000 +foobar1,1000001,1000001 +foobar2,1000002,1000002 +foobar3,1000003,1000003 +foobar4,1000004,1000004 diff --git a/test/functional/RegisterUserTest.php b/test/functional/RegisterUserTest.php index 4c5340f5f..12152bb5c 100644 --- a/test/functional/RegisterUserTest.php +++ b/test/functional/RegisterUserTest.php @@ -8,11 +8,11 @@ class RegisterUserTest extends UnityWebPortalTestCase public static function provider() { return [ - // defaults/config.ini.default: ldap.offset_UIDGID=1000000 + // defaults/config.ini.default: ldap.offset_user_(uid|gid)number=1000000 // test/custom_user_mappings/test.csv has reservations for 1000000-1000004 - ["NonExistent", 1000005], - // test/custom_user_mappings/test.csv: {user2001: 555} - ["CustomMapped555", 555], + ["NonExistent", "1000005", "1000005"], + // test/custom_user_mappings/test.csv: {user2001: [555, 556]} + ["CustomMapped555", "555", "556"], ]; } @@ -22,8 +22,11 @@ private function register() } #[DataProvider("provider")] - public function testRegisterUserAndCreateOrg($nickname, $expected_uid_gid) - { + public function testRegisterUserAndCreateOrg( + string $nickname, + string $expected_uidnumber, + string $expected_gidnumber, + ): void { global $USER, $SSO, $LDAP, $SQL, $MAILER, $WEBHOOK; $this->switchUser($nickname); $uid = $USER->uid; @@ -39,8 +42,11 @@ public function testRegisterUserAndCreateOrg($nickname, $expected_uid_gid) $this->assertTrue($user_entry->exists()); $this->assertTrue($user_group_entry->exists()); $this->assertTrue($org_entry->exists()); - $this->assertEquals($expected_uid_gid, $user_entry->getAttribute("uidnumber")[0]); - $this->assertEquals($expected_uid_gid, $user_group_entry->getAttribute("gidnumber")[0]); + $this->assertEquals($expected_uidnumber, $user_entry->getAttribute("uidnumber")[0]); + $this->assertEquals( + $expected_gidnumber, + $user_group_entry->getAttribute("gidnumber")[0], + ); } finally { ensureOrgGroupDoesNotExist($org_gid); ensureUserDoesNotExist($uid);