Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .phpcs-ruleset-pure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@
<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
<exclude name="PSR1.Classes.ClassDeclaration.MultipleClasses" />
</rule>
<rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
<exclude name="PSR1.Methods.CamelCapsMethodName.NotCamelCaps" />
</rule>
</ruleset>
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ For details on the changes in each release, see [the Releases page](https://gith

## Version-specific update instructions:

### 1.8 -> 1.9
- schema migration:
- the new LDAP schema should be added in conjunction with the old
- all entries should have the `piGroup` objectClass replaced with `unityGroup`
- the `setup-pi-group-owners.php` worker should be run
- all existing PI group requests in SQL must be converted to the format "owner_uid:group_gid"
- ownership of any course groups should be transferred to a Unity admin
- dummy course group owners should be deleted

### 1.7 -> 1.8
- the [webhook] section of the config file can be removed
- all mail templates are no longer PHP files, now they are `.html.twig`
Expand Down
3 changes: 1 addition & 2 deletions deployment/domain_overrides/course-creator/config.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# we have hacked in course groups using normal PI accounts but using a different IDnumber range
# we have hacked in course groups using normal PI groups but using a different IDnumber range
[ldap]
offset_UIDGID = 20000
offset_PIGID = 20000

[site]
Expand Down
2 changes: 1 addition & 1 deletion resources/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
}

$_SESSION["user_exists"] = $USER->exists() && !$USER->getFlag(UserFlag::DISABLED);
$_SESSION["is_pi"] = $USER->isPI();
$_SESSION["owned_managed_pi_group_gids"] = $USER->getOwnedPIGroupGIDs();

$days_idle = $SQL->convertLastLoginToDaysIdle($SQL->getUserLastLogin($USER->uid));
$SQL->addLog("user_login", $OPERATOR->uid);
Expand Down
93 changes: 50 additions & 43 deletions resources/lib/UnityGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ enum UnityGroupUserRemovedReason: string
*/
class UnityGroup extends PosixGroup
{
public const string PI_PREFIX = "pi_";
public const string NAMESAKE_PI_PREFIX = "pi_";
public string $gid;
private UnityLDAP $LDAP;
private UnitySQL $SQL;
Expand All @@ -37,21 +37,25 @@ public function __toString(): string
return $this->gid;
}

public function requestGroup(?bool $send_mail_to_admins = null, bool $send_mail = true): void
{
public function requestGroup(
string $owner_uid,
?bool $send_mail_to_admins = null,
bool $send_mail = true,
): void {
$send_mail_to_admins ??= CONFIG["mail"]["send_pimesg_to_admins"];
if ($this->exists() && !$this->getIsDisabled()) {
return;
}
$owner = new UnityUser($owner_uid, $this->LDAP, $this->SQL, $this->MAILER);
$context = [
"user" => $this->getOwner()->uid,
"org" => $this->getOwner()->getOrg(),
"name" => $this->getOwner()->getFullName(),
"email" => $this->getOwner()->getMail(),
"user" => $owner_uid,
"org" => $owner->getOrg(),
"name" => $owner->getFullName(),
"email" => $owner->getMail(),
];
$this->SQL->addRequest($this->getOwner()->uid, UnitySQL::REQUEST_BECOME_PI);
$this->SQL->addRequest("$owner_uid:$this->gid", UnitySQL::REQUEST_CREATE_PI_GROUP);
if ($send_mail) {
$this->MAILER->sendMail($this->getOwner()->getMail(), "group_request");
$this->MAILER->sendMail($owner->getMail(), "group_request");
if ($send_mail_to_admins) {
$this->MAILER->sendMail("admin", "group_request_admin", $context);
}
Expand Down Expand Up @@ -117,52 +121,59 @@ private function reenable(bool $send_mail = true): void
/**
* 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
public function approveGroup(string $owner_uid, bool $send_mail = true): void
{
$uid = $this->getOwner()->uid;
$request = $this->SQL->getRequest($uid, UnitySQL::REQUEST_BECOME_PI);
assert($this->getOwner()->exists());
$request = "$owner_uid:$this->gid";
assert($this->SQL->requestExists($request, UnitySQL::REQUEST_CREATE_PI_GROUP));
$owner = new UnityUser($owner_uid, $this->LDAP, $this->SQL, $this->MAILER);
assert($owner->exists());
if (!$this->entry->exists()) {
$this->init();
$this->init($owner_uid);
} 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);
$this->SQL->removeRequest($request, UnitySQL::REQUEST_CREATE_PI_GROUP);
$this->SQL->addLog("approved_group", $request);
if ($send_mail) {
$this->MAILER->sendMail($this->getOwner()->getMail(), "group_created");
$this->MAILER->sendMail($owner->getMail(), "group_created");
}
// having your own group makes you qualified
$this->getOwner()->updateIsQualified($send_mail);
$owner->updateIsQualified($send_mail);
}

/**
* This method is executed when an admin denys the PI group request
*/
public function denyGroup(bool $send_mail = true): void
public function denyGroup(string $owner_uid, bool $send_mail = true): void
{
$request = $this->SQL->getRequest($this->getOwner()->uid, UnitySQL::REQUEST_BECOME_PI);
$this->SQL->removeRequest($this->getOwner()->uid, UnitySQL::REQUEST_BECOME_PI);
$request = "$owner_uid:$this->gid";
assert($this->SQL->requestExists($request, UnitySQL::REQUEST_CREATE_PI_GROUP));
$owner = new UnityUser($owner_uid, $this->LDAP, $this->SQL, $this->MAILER);
assert($owner->exists());
$this->SQL->removeRequest($request, UnitySQL::REQUEST_CREATE_PI_GROUP);
if ($this->exists()) {
return;
}
$this->SQL->addLog("denied_group", $this->getOwner()->uid);
$this->SQL->addLog("denied_group", $owner_uid);
if ($send_mail) {
$this->MAILER->sendMail($this->getOwner()->getMail(), "group_denied");
$this->MAILER->sendMail($owner->getMail(), "group_denied");
}
}

public function cancelGroupRequest(bool $send_mail = true): void
public function cancelGroupRequest(string $owner_uid, bool $send_mail = true): void
{
if (!$this->SQL->requestExists($this->getOwner()->uid, UnitySQL::REQUEST_BECOME_PI)) {
$request = "$owner_uid:$this->gid";
if (!$this->SQL->requestExists($request, UnitySQL::REQUEST_CREATE_PI_GROUP)) {
return;
}
$this->SQL->removeRequest($this->getOwner()->uid, UnitySQL::REQUEST_BECOME_PI);
$owner = new UnityUser($owner_uid, $this->LDAP, $this->SQL, $this->MAILER);
assert($owner->exists());
$this->SQL->removeRequest($request, UnitySQL::REQUEST_CREATE_PI_GROUP);
if ($send_mail) {
$this->MAILER->sendMail("pi_approve", "group_request_cancelled", [
"uid" => $this->getOwner()->uid,
"uid" => $owner_uid,
]);
}
}
Expand Down Expand Up @@ -346,15 +357,15 @@ public function requestExists(UnityUser $user): bool
return false;
}

private function init(): void
private function init(string $owner_uid): void
{
$owner = $this->getOwner();
assert(!$this->entry->exists());
$nextGID = $this->LDAP->getNextPIGIDNumber();
$this->entry->create([
"objectclass" => ["piGroup", "posixGroup", "top"],
"objectclass" => ["unityGroup", "posixGroup", "top"],
"gidnumber" => strval($nextGID),
"memberuid" => [$owner->uid],
"memberuid" => [$owner_uid],
"owneruid" => $owner_uid,
]);
// TODO if we ever make this project based,
// we need to update the cache here with the memberuid
Expand All @@ -367,25 +378,21 @@ private function addRequest(string $uid): void

public function getOwner(): UnityUser
{
return new UnityUser(
self::GID2OwnerUID($this->gid),
$this->LDAP,
$this->SQL,
$this->MAILER,
);
$uid = $this->entry->getAttribute("ownerUid")[0];
return new UnityUser($uid, $this->LDAP, $this->SQL, $this->MAILER);
}

public static function ownerUID2GID(string $uid): string
public static function ownerUID2NamesakeGID(string $uid): string
{
return self::PI_PREFIX . $uid;
return self::NAMESAKE_PI_PREFIX . $uid;
}

public static function GID2OwnerUID(string $gid): string
public static function NamesakeGID2OwnerUID(string $gid): string
{
if (substr($gid, 0, strlen(self::PI_PREFIX)) != self::PI_PREFIX) {
if (substr($gid, 0, strlen(self::NAMESAKE_PI_PREFIX)) != self::NAMESAKE_PI_PREFIX) {
throw new Exception("PI group GID doesn't have the correct prefix.");
}
return substr($gid, strlen(self::PI_PREFIX));
return substr($gid, strlen(self::NAMESAKE_PI_PREFIX));
}

/**
Expand Down Expand Up @@ -480,7 +487,7 @@ public function addPlusAddressToMail(string $mail): string
$owner = $this->getOwner();
$suffix = "_" . $owner->getOrg();
assert(str_ends_with($owner->uid, $suffix));
$short_name = substr($owner->uid, 0, -1 * strlen($suffix));
$short_name = substr($owner->uid, 0, -(1 * strlen($suffix)));
$parts = explode("@", $mail, 2);
return sprintf("%s+%s@%s", $parts[0], $short_name, $parts[1]);
}
Expand Down
23 changes: 21 additions & 2 deletions resources/lib/UnityLDAP.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,31 @@ public function getPIGroupGIDsWithManagerUID(
);
}

/** @return string[] */
public function getPIGroupGIDsWithOwnerUID(
string $uid,
string $base_filter = self::NON_DISABLED,
): array {
return array_map(
fn($x) => $x["cn"][0],
$this->pi_groupOU->getChildrenArrayStrict(
["cn"],
recursive: false,
filter: sprintf(
"(&(owneruid=%s)%s)",
ldap_escape($uid, flags: LDAP_ESCAPE_FILTER),
$base_filter,
),
),
);
}

/** @return string[] */
public function getPIGroupOwnerUIDs(string $filter = self::NON_DISABLED): array
{
return array_map(
fn($x) => UnityGroup::GID2OwnerUID((string) $x["cn"][0]),
$this->getPIGroupsAttributes(["cn"], filter: $filter),
fn($x) => (string) $x["owneruid"][0],
$this->getPIGroupsAttributes(["ownerUid"], filter: $filter),
);
}

Expand Down
3 changes: 2 additions & 1 deletion resources/lib/UnitySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ class UnitySQL
private const string TABLE_AUDIT_LOG = "audit_log";
private const string TABLE_USER_LAST_LOGINS = "user_last_logins";
// FIXME this string should be changed to something more intuitive, requires production change
public const string REQUEST_BECOME_PI = "admin";
public const string REQUEST_CREATE_PI_GROUP = "admin";
private const int TABLE_AUDIT_LOG_RECIPIENT_MAX_MB_STR_LEN = 768;
// FIXME the `uid` column should use a more generic name since it can now conatin "uid:gid"

private PDO $conn;

Expand Down
23 changes: 16 additions & 7 deletions resources/lib/UnityUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -362,18 +362,27 @@ public function getHomeDir(): string
return $this->entry->getAttribute("homedirectory")[0];
}

/**
* Checks if current user is a PI
*/
/** @return string[] */
public function getOwnedPIGroupGIDs(): array
{
return $this->LDAP->getPIGroupGIDsWithOwnerUID($this->uid);
}

/** @return string[] */
public function getManagedPIGroupGIDs(): array
{
return $this->LDAP->getPIGroupGIDsWithManagerUID($this->uid);
}

public function isPI(): bool
{
return $this->getPIGroup()->exists() && !$this->getPIGroup()->getIsDisabled();
return $this->getNamesakePIGroup()->exists();
}

public function getPIGroup(): UnityGroup
public function getNamesakePIGroup(): UnityGroup
{
return new UnityGroup(
UnityGroup::ownerUID2GID($this->uid),
UnityGroup::ownerUID2NamesakeGID($this->uid),
$this->LDAP,
$this->SQL,
$this->MAILER,
Expand Down Expand Up @@ -418,7 +427,7 @@ public function disable(
bool $send_mail_pi_group_owner = true,
bool $send_mail_admin = true,
): void {
$pi_group = $this->getPIGroup();
$pi_group = $this->getNamesakePIGroup();
if ($pi_group->exists() && !$pi_group->getIsDisabled()) {
$pi_group->disable($send_mail);
}
Expand Down
10 changes: 7 additions & 3 deletions resources/templates/header.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,13 @@
{{ getRelativeHyperlink('Account Settings', 'panel/account.php')|raw }}
{{ getRelativeHyperlink('My PIs', 'panel/groups.php')|raw }}

{% if is_pi %}
{{ getRelativeHyperlink('My Users', 'panel/pi.php')|raw }}
{% endif %}
{% for gid in owned_managed_pi_group_gids %}
{% if uid in gid %}
{{ getRelativeHyperlink('My Users', 'panel/pi.php')|raw }}
{% else %}
{{ getRelativeHyperlink('PI Group ' ~ (gid|e), 'panel/pi.php?gid=' ~ (gid|e))|raw }}
{% endif %}
{% endfor %}

{% for label in CONFIG['menuitems_secure']['labels'] %}
<a target="_blank" href="{{ CONFIG['menuitems_secure']['links'][loop.index0] }}">{{ label }}</a>
Expand Down
3 changes: 2 additions & 1 deletion resources/templates/header.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"messages" => UnityHTTPD::getMessages(),
"viewUser" => $_SESSION["viewUser"] ?? null,
"user_exists" => $_SESSION["user_exists"] ?? false,
"is_pi" => $_SESSION["is_pi"] ?? false,
"uid" => $_SESSION["SSO"]["user"] ?? null,
"owned_managed_pi_group_gids" => $_SESSION["owned_managed_pi_group_gids"] ?? [],
"is_admin" => $_SESSION["is_admin"] ?? false
]);
Loading
Loading