Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions LDAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ Terminology:
- **native user**: a user created by this account portal
- **non-native user**: inverse of native
- users created for administrative purposes should not be mixed with native users in the LDAP OUs given in `config.ini` or else this account portal may get confused
- **disabled group**: a PI group that was disabled by its owner or had its owner disabled
- memberuid attribute should be empty
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ rm "$prod" && ln -s "$old" "$prod"
- the `home` page can be copied over to `deployment/templates_overrides/home.php`
- the `support` page should be moved over to wherever you host your documentation
- the `notices` SQL table should be droppped
- a new LDAP schema needs to be added:
```shell
scp tools/docker-dev/identity/unity-cluster-schema.ldif root@your-ldap-server:/root/unity-cluster-schema.ldif
ssh root@your-ldap-server ldapadd -Y EXTERNAL -H ldapi:/// -f /root/unity-cluster-schema.ldif
```

### 1.5 -> 1.6

Expand Down
138 changes: 95 additions & 43 deletions resources/lib/UnityGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function __toString(): string
public function requestGroup(?bool $send_mail_to_admins = null, bool $send_mail = true): void
{
$send_mail_to_admins ??= CONFIG["mail"]["send_pimesg_to_admins"];
if ($this->exists()) {
if ($this->exists() && !$this->getIsDisabled()) {
return;
}
if ($this->SQL->accDeletionRequestExists($this->getOwner()->uid)) {
Expand All @@ -63,18 +63,70 @@ public function requestGroup(?bool $send_mail_to_admins = null, bool $send_mail
}
}

public function disable(bool $send_mail = true): void
{
if ($this->getIsDisabled()) {
throw new Exception("cannot disable an already disabled group");
}
$this->SQL->addLog("disable_pi_group", $this->gid);
$memberuids = $this->getMemberUIDs();
if ($send_mail) {
$member_attributes = $this->LDAP->getUsersAttributes($memberuids, ["mail"]);
$member_mails = array_map(fn($x) => (string) $x["mail"][0], $member_attributes);
if (count($member_mails) > 0) {
$this->MAILER->sendMail($member_mails, "group_disabled", [
"group_name" => $this->gid,
]);
}
}
$this->setIsDisabled(true);
if (count($memberuids) > 0) {
$this->entry->setAttribute("memberuid", []);
}
// TODO optimize
// UnityUser::__construct() makes one LDAP query for each user
// updateIsQualified() makes one LDAP query for each member
// if user is no longer in any PI group, disqualify them
foreach ($memberuids as $uid) {
$user = new UnityUser($uid, $this->LDAP, $this->SQL, $this->MAILER, $this->WEBHOOK);
$user->updateIsQualified($send_mail);
}
}

private function reenable(bool $send_mail = true): void
{
if (!$this->getIsDisabled()) {
throw new Exception("cannot re-enable a group that is not disabled");
}
$this->SQL->addLog("reenabled_pi_group", $this->gid);
if ($send_mail) {
$this->MAILER->sendMail($this->getOwner()->getMail(), "group_reenabled", [
"group_name" => $this->gid,
]);
}
$this->setIsDisabled(false);
$owner_uid = $this->getOwner()->uid;
if (!$this->memberUIDExists($owner_uid)) {
$this->addMemberUID($owner_uid);
}
$this->getOwner()->updateIsQualified($send_mail);
}

/**
* This method will create the group (this is what is executed when an admin approved the group)
*/
public function approveGroup(bool $send_mail = true): void
{
$uid = $this->getOwner()->uid;
$request = $this->SQL->getRequest($uid, UnitySQL::REQUEST_BECOME_PI);
if ($this->exists()) {
return;
}
\ensure($this->getOwner()->exists());
$this->init();
if (!$this->entry->exists()) {
$this->init();
} elseif ($this->getIsDisabled()) {
$this->reenable();
} else {
throw new Exception("cannot approve group that already exists and is not disabled");
}
$this->SQL->removeRequest($this->getOwner()->uid, UnitySQL::REQUEST_BECOME_PI);
$this->SQL->addLog("approved_group", $this->getOwner()->uid);
if ($send_mail) {
Expand Down Expand Up @@ -126,42 +178,6 @@ public function cancelGroupJoinRequest(UnityUser $user, bool $send_mail = true):
}
}

// /**
// * This method will delete the group, either by admin action or PI action
// */
// public function removeGroup($send_mail = true)
// {
// // remove any pending requests
// // this will silently fail if the request doesn't exist (which is what we want)
// $this->SQL->removeRequests($this->gid);

// // we don't need to do anything extra if the group is already deleted
// if (!$this->exists()) {
// return;
// }

// // first, we must record the users in the group currently
// $users = $this->getGroupMembers();

// // now we delete the ldap entry
// $this->entry->ensureExists();
// $this->entry->delete();

// // Logs the change
// $this->SQL->addLog("removed_group", $this->gid);

// // send email to every user of the now deleted PI group
// if ($send_mail) {
// foreach ($users as $user) {
// $this->MAILER->sendMail(
// $user->getMail(),
// "group_disband",
// array("group_name" => $this->gid)
// );
// }
// }
// }

/**
* This method is executed when a user is approved to join the group
* (either by admin or the group owner)
Expand Down Expand Up @@ -220,7 +236,7 @@ public function removeUser(UnityUser $new_user, bool $send_mail = true): void
return;
}
if ($new_user->uid == $this->getOwner()->uid) {
throw new Exception("Cannot delete group owner from group. Disband group instead");
throw new Exception("Cannot delete group owner from group. Disable group instead");
}
$this->removeMemberUID($new_user->uid);
$this->SQL->addLog(
Expand Down Expand Up @@ -326,7 +342,7 @@ private function init(): void
\ensure(!$this->entry->exists());
$nextGID = $this->LDAP->getNextPIGIDNumber();
$this->entry->create([
"objectclass" => UnityLDAP::POSIX_GROUP_CLASS,
"objectclass" => ["unityClusterPIGroup", "posixGroup", "top"],
"gidnumber" => strval($nextGID),
"memberuid" => [$owner->uid],
]);
Expand Down Expand Up @@ -376,4 +392,40 @@ public function getGroupMembersAttributes(array $attributes, array $default_valu
$default_values,
);
}

public function getIsDisabled(): bool
{
$value = $this->entry->getAttribute("isDisabled");
switch (count($value)) {
case 0:
return false;
case 1:
switch ($value[0]) {
case "TRUE":
return true;
case "FALSE":
return false;
default:
throw new \RuntimeException(
sprintf(
"unexpected value for isDisabled: '%s'. expected 'TRUE' or 'FALSE'",
$value[0],
),
);
}
default:
throw new \RuntimeException(
sprintf(
"expected value of length 0 or 1, found value %s of length %s",
_json_encode($value),
count($value),
),
);
}
}

public function setIsDisabled(bool $new_value): void
{
$this->entry->setAttribute("isDisabled", $new_value ? "TRUE" : "FALSE");
}
}
30 changes: 19 additions & 11 deletions resources/lib/UnityLDAP.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class UnityLDAP extends LDAPConn
"ldapPublicKey",
];

public const array POSIX_GROUP_CLASS = ["posixGroup", "top"];
// isDisabled unset or set to "FALSE"
private static string $NON_DISABLED_FILTER = "(|(!(isDisabled=*))(isDisabled=FALSE))";

private string $custom_mappings_path =
__DIR__ . "/../../" . CONFIG["ldap"]["custom_user_mappings_dir"];
Expand Down Expand Up @@ -198,7 +199,7 @@ public function getAllNativeUsersAttributes(
}

/** @return UnityGroup[] */
public function getAllPIGroups(
public function getAllNonDisabledPIGroups(
UnitySQL $UnitySQL,
UnityMailer $UnityMailer,
UnityWebhook $UnityWebhook,
Expand All @@ -207,6 +208,7 @@ public function getAllPIGroups(
$pi_groups_attributes = $this->pi_groupOU->getChildrenArrayStrict(
attributes: ["cn"],
recursive: false,
filter: self::$NON_DISABLED_FILTER,
);
foreach ($pi_groups_attributes as $attributes) {
array_push(
Expand All @@ -222,35 +224,41 @@ public function getAllPIGroups(
* @param attributes $default_values
* @return attributes[]
*/
public function getAllPIGroupsAttributes(array $attributes, array $default_values = []): array
{
public function getAllNonDisabledPIGroupsAttributes(
array $attributes,
array $default_values = [],
): array {
return $this->pi_groupOU->getChildrenArrayStrict(
$attributes,
false, // non-recursive
"objectClass=posixGroup",
self::$NON_DISABLED_FILTER,
$default_values,
);
}

/** @return string[] */
public function getPIGroupGIDsWithMemberUID(string $uid): array
public function getNonDisabledPIGroupGIDsWithMemberUID(string $uid): array
{
return array_map(
fn($x) => $x["cn"][0],
$this->pi_groupOU->getChildrenArrayStrict(
["cn"],
false,
"(memberuid=" . ldap_escape($uid, flags: LDAP_ESCAPE_FILTER) . ")",
sprintf(
"(&(memberuid=%s)%s)",
ldap_escape($uid, flags: LDAP_ESCAPE_FILTER),
self::$NON_DISABLED_FILTER,
),
),
);
}

/** @return string[] */
public function getAllPIGroupOwnerUIDs(): array
public function getAllNonDisabledPIGroupOwnerUIDs(): array
{
return array_map(
fn($x) => UnityGroup::GID2OwnerUID($x["cn"][0]),
$this->pi_groupOU->getChildrenArrayStrict(["cn"]),
fn($x) => UnityGroup::GID2OwnerUID((string) $x["cn"][0]),
$this->getAllNonDisabledPIGroupsAttributes(["cn"]),
);
}

Expand Down Expand Up @@ -280,7 +288,7 @@ public function getUID2PIGIDs(): array
{
$uid2pigids = [];
// for each PI group, append that GID to the member list for each of its member UIDs
$pi_groups_attributes = $this->getAllPIGroupsAttributes(
$pi_groups_attributes = $this->getAllNonDisabledPIGroupsAttributes(
["cn", "memberuid"],
default_values: ["memberuid" => []],
);
Expand Down
2 changes: 1 addition & 1 deletion resources/lib/UnityOrg.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function init(): void
\ensure(!$this->entry->exists());
$nextGID = $this->LDAP->getNextOrgGIDNumber();
$this->entry->create([
"objectclass" => UnityLDAP::POSIX_GROUP_CLASS,
"objectclass" => ["posixGroup", "top"],
"gidnumber" => strval($nextGID),
]);
}
Expand Down
6 changes: 3 additions & 3 deletions resources/lib/UnityUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function init(
$id = $this->LDAP->getNextUIDGIDNumber($this->uid);
\ensure(!$ldapGroupEntry->exists());
$ldapGroupEntry->create([
"objectclass" => UnityLDAP::POSIX_GROUP_CLASS,
"objectclass" => ["posixGroup", "top"],
"gidnumber" => strval($id),
]);
\ensure(!$this->entry->exists());
Expand Down Expand Up @@ -346,7 +346,7 @@ public function getHomeDir(): string
*/
public function isPI(): bool
{
return $this->getPIGroup()->exists();
return $this->getPIGroup()->exists() && !$this->getPIGroup()->getIsDisabled();
}

public function getPIGroup(): UnityGroup
Expand All @@ -371,7 +371,7 @@ public function getOrgGroup(): UnityOrg
*/
public function getPIGroupGIDs(): array
{
return $this->LDAP->getPIGroupGIDsWithMemberUID($this->uid);
return $this->LDAP->getNonDisabledPIGroupGIDsWithMemberUID($this->uid);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?php

$this->Subject = "PI Group Disbanded"; ?>
$this->Subject = "PI Group Disabled"; ?>

<p>Hello,</p>

<p>Your PI group, <?php echo $data["group_name"]; ?>, has been disbanded on the UnityHPC Platform.
<p>Your PI group, <?php echo $data["group_name"]; ?>, has been disabled on the UnityHPC Platform.
Any jobs associated with this PI account have been killed.</p>

<p>If you believe this to be a mistake, please reply to this email</p>
11 changes: 11 additions & 0 deletions resources/mail/group_reenabled.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

$this->Subject = "PI Group Re-Enabled"; ?>

<p>Hello,</p>

<p>
Your PI group, <?php echo $data["group_name"]; ?>, has been re-enabled on the UnityHPC Platform.
</p>

<p>If you believe this to be a mistake, please reply to this email.</p>
23 changes: 23 additions & 0 deletions test/functional/PIBecomeApproveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,27 @@ public function testApprovePI()
$this->assertFalse($USER->getFlag(UserFlag::QUALIFIED));
}
}

public function testReenableGroup()
{
global $USER, $SSO, $LDAP, $SQL, $MAILER, $WEBHOOK;
$this->switchUser("ReenabledOwnerOfDisabledPIGroup");
$this->assertFalse($USER->isPI());
$user = $USER;
$pi_group = $USER->getPIGroup();
$approve_uid = $USER->uid;
try {
$this->requestGroupCreation();
$this->assertRequestedPIGroup(true);
$this->switchUser("Admin");
$this->approveGroup($approve_uid);
$this->assertTrue($user->isPI());
} finally {
if ($pi_group->memberUIDExists($approve_uid)) {
$pi_group->removeMemberUID($approve_uid);
$pi_group->setIsDisabled(true);
assert(!$user->isPI());
}
}
}
}
Loading