Repository: UnityHPC/unity-web-portal
Pull request #223: Add ability to cancel request, require confirmation of account policy, fix dubious redirect
Author: login: bryank-cs
Created: 2025-05-20T01:44:04Z | Merged: 2025-05-23T21:59:43Z

Modify the new_account page to allow a user without an account to cancel a pending request for either a PI account or joining a group.


Diff:
diff --git a/defaults/config.ini.default b/defaults/config.ini.default
index 9b139f4df..5ab81435e 100644
--- a/defaults/config.ini.default
+++ b/defaults/config.ini.default
@@ -12,6 +12,7 @@ url = "https://127.0.0.1:8000/"  ; URL of the website
 description = "The Unity Web Portal is a lightweight HPC cluster front-end"  ; Description of the website
 logo = "logo.png"  ; path to logo file, in the webroot/assets/branding folder
 terms_of_service_url = "https://github.com" ; this can be external or a portal page created with "content management"
+account_policy_url = "https://github.com" ; this can be external or a portal page created with "content management"
 
 [ldap]
 uri = "ldap://identity"  ; URI of remote LDAP server
diff --git a/resources/lib/UnityGroup.php b/resources/lib/UnityGroup.php
index 80cd7f184..0a91a9eea 100644
--- a/resources/lib/UnityGroup.php
+++ b/resources/lib/UnityGroup.php
@@ -198,6 +198,41 @@ public function denyGroup($operator = null, $send_mail = true)
         }
     }
 
+    public function cancelGroupRequest($send_mail = true)
+    {
+        if (!$this->SQL->requestExists($this->getOwner()->getUID())) {
+            return;
+        }
+
+        $this->SQL->removeRequest($this->getOwner()->getUID());
+
+        if ($send_mail) {
+            // send email to requestor
+            $this->MAILER->sendMail(
+                "admin",
+                "group_request_cancelled"
+            );
+        }
+    }
+
+    public function cancelGroupJoinRequest($user, $send_mail = true)
+    {
+        if (!$this->requestExists($user)) {
+            return;
+        }
+
+        $this->SQL->removeRequest($user->getUID(), $this->pi_uid);
+
+        if ($send_mail) {
+            // send email to requestor
+            $this->MAILER->sendMail(
+                $this->getOwner()->getMail(),
+                "group_join_request_cancelled",
+                ["group" => $this->pi_uid]
+            );
+        }
+    }
+
     // /**
     //  * This method will delete the group, either by admin action or PI action
     //  */
@@ -251,7 +286,7 @@ public function approveUser($new_user, $send_mail = true)
         $this->addUserToGroup($new_user);
 
         // remove request, this will fail silently if the request doesn't exist
-        $this->removeRequest($new_user->getUID());
+        $this->SQL->removeRequest($new_user->getUID(), $this->pi_uid);
 
         // send email to the requestor
         if ($send_mail) {
@@ -283,7 +318,7 @@ public function denyUser($new_user, $send_mail = true)
         }
 
         // remove request, this will fail silently if the request doesn't exist
-        $this->removeRequest($new_user->getUID());
+        $this->SQL->removeRequest($new_user->getUID(), $this->pi_uid);
 
         if ($send_mail) {
             // send email to the user
@@ -522,11 +557,6 @@ private function addRequest($uid)
         $this->SQL->addRequest($uid, $this->pi_uid);
     }
 
-    private function removeRequest($uid)
-    {
-        $this->SQL->removeRequest($uid, $this->pi_uid);
-    }
-
     //
     // Public helper functions
     //
diff --git a/resources/lib/UnityLDAP.php b/resources/lib/UnityLDAP.php
index b7ef7cab8..1e33be9c6 100644
--- a/resources/lib/UnityLDAP.php
+++ b/resources/lib/UnityLDAP.php
@@ -38,6 +38,7 @@ class UnityLDAP extends ldapConn
     private $pi_groupOU;
     private $org_groupOU;
     private $adminGroup;
+    private $userGroup;
 
     private $custom_mappings_path;
 
diff --git a/resources/lib/UnitySQL.php b/resources/lib/UnitySQL.php
index 9aaef1c9d..d9be2ca91 100644
--- a/resources/lib/UnitySQL.php
+++ b/resources/lib/UnitySQL.php
@@ -20,7 +20,7 @@ class UnitySQL
 
 
     // FIXME this string should be changed to something more intuitive, requires production sql change
-    private const REQUEST_BECOME_PI = "admin";
+    public const REQUEST_BECOME_PI = "admin";
 
     private $conn;
 
diff --git a/resources/lib/UnitySite.php b/resources/lib/UnitySite.php
index c8d89bcbc..f19e1438d 100644
--- a/resources/lib/UnitySite.php
+++ b/resources/lib/UnitySite.php
@@ -26,10 +26,8 @@ public static function die($x = null)
 
     public static function redirect($destination)
     {
-        if ($_SERVER["PHP_SELF"] != $destination) {
-            header("Location: $destination");
-            self::die("Redirect failed, click <a href='$destination'>here</a> to continue.");
-        }
+        header("Location: $destination");
+        self::die("Redirect failed, click <a href='$destination'>here</a> to continue.");
     }
 
     private static function headerResponseCode(int $code, string $reason)
diff --git a/resources/mail/group_join_request_cancelled.php b/resources/mail/group_join_request_cancelled.php
new file mode 100644
index 000000000..7fb02df12
--- /dev/null
+++ b/resources/mail/group_join_request_cancelled.php
@@ -0,0 +1,11 @@
+<?php
+
+// This template is sent to the user cancelling the request
+$this->Subject = "Unity Account Request Cancelled";
+?>
+
+<p>Hello,</p>
+
+<p>Your request to join group '<?php echo $data["group"]; ?>' on the Unity Cluster has been cancelled per your request.
+</p>
+
diff --git a/resources/mail/group_request_cancelled.php b/resources/mail/group_request_cancelled.php
new file mode 100644
index 000000000..e143d1d02
--- /dev/null
+++ b/resources/mail/group_request_cancelled.php
@@ -0,0 +1,10 @@
+<?php
+
+// This template is sent to the user cancelling the request
+$this->Subject = "Unity PI Account Request Cancelled";
+?>
+
+<p>Hello,</p>
+
+<p>Your request for a PI account on the Unity Cluster has been cancelled per your request.</p>
+
diff --git a/resources/templates/header.php b/resources/templates/header.php
index 8e48ffccf..590f8943f 100644
--- a/resources/templates/header.php
+++ b/resources/templates/header.php
@@ -11,7 +11,7 @@
 }
 
 if (isset($SSO)) {
-    if (!$_SESSION["user_exists"]) {
+    if (!$_SESSION["user_exists"] && !str_ends_with($_SERVER['PHP_SELF'], "/panel/new_account.php")) {
         UnitySite::redirect($CONFIG["site"]["prefix"] . "/panel/new_account.php");
     }
 }
diff --git a/test/functional/CancelRequestTest.php b/test/functional/CancelRequestTest.php
new file mode 100644
index 000000000..36028d6ee
--- /dev/null
+++ b/test/functional/CancelRequestTest.php
@@ -0,0 +1,71 @@
+<?php
+
+use PHPUnit\Framework\TestCase;
+use UnityWebPortal\lib\exceptions\PhpUnitNoDieException;
+
+class CancelRequestTest extends TestCase
+{
+    private function assertNumberGroupRequests(int $x)
+    {
+        global $USER, $SQL;
+        $this->assertEquals($x, count($SQL->getRequestsByUser($USER->getUID())));
+    }
+
+    public function testCancelPIRequest()
+    {
+        global $USER, $SQL;
+        switchUser(...getNonExistentUser());
+        // First create a request
+        try {
+            http_post(
+                __DIR__ . "/../../webroot/panel/new_account.php",
+                ["new_user_sel" => "pi", "eula" => "agree", "confirm_pi" => "agree"]
+            );
+        } catch (PhpUnitNoDieException $e) {
+            // Ignore the exception from http_post
+        }
+
+        $this->assertNumberGroupRequests(1);
+
+        // Now try to cancel it
+        try {
+            http_post(
+                __DIR__ . "/../../webroot/panel/new_account.php",
+                ["cancel" => "true"] # value of cancel is arbitrary
+            );
+        } catch (PhpUnitNoDieException $e) {
+            // Ignore the exception from http_post
+        }
+
+        $this->assertNumberGroupRequests(0);
+    }
+
+    public function testCancelGroupJoinRequest()
+    {
+        global $USER, $SQL;
+        switchUser(...getNonExistentUser());
+
+        try {
+            http_post(
+                __DIR__ . "/../../webroot/panel/new_account.php",
+                ["new_user_sel" => "not_pi", "eula" => "agree", "pi" => getExistingPI()]
+            );
+        } catch (PhpUnitNoDieException $e) {
+            // Ignore the exception from http_post
+        }
+
+        $this->assertNumberGroupRequests(1);
+
+        // Now try to cancel it
+        try {
+            http_post(
+                __DIR__ . "/../../webroot/panel/new_account.php",
+                ["cancel" => "true"] # value of cancel is arbitrary
+            );
+        } catch (PhpUnitNoDieException $e) {
+            // Ignore the exception from http_post
+        }
+
+        $this->assertNumberGroupRequests(0);
+    }
+}
diff --git a/test/phpunit-bootstrap.php b/test/phpunit-bootstrap.php
index ae5f6a1b9..1b95c51ca 100644
--- a/test/phpunit-bootstrap.php
+++ b/test/phpunit-bootstrap.php
@@ -76,7 +76,9 @@ function switchUser(
 function http_post(string $phpfile, array $post_data): void
 {
     global $CONFIG, $REDIS, $LDAP, $SQL, $MAILER, $WEBHOOK, $GITHUB, $SITE, $SSO, $OPERATOR, $USER, $SEND_PIMESG_TO_ADMINS, $LOC_HEADER, $LOC_FOOTER;
+    $_PREVIOUS_SERVER = $_SERVER;
     $_SERVER["REQUEST_METHOD"] = "POST";
+    $_SERVER["PHP_SELF"] = preg_replace("/.*webroot\//", "/", $phpfile);
     $_POST = $post_data;
     ob_start();
     try {
@@ -84,20 +86,24 @@ function http_post(string $phpfile, array $post_data): void
     } finally {
         ob_get_clean(); // discard output
         unset($_POST);
-        unset($_SERVER["REQUEST_METHOD"]);
+        $_SERVER = $_PREVIOUS_SERVER;
     }
 }
 
-function http_get(string $phpfile): void
+function http_get(string $phpfile, array $get_data = array()): void
 {
     global $CONFIG, $REDIS, $LDAP, $SQL, $MAILER, $WEBHOOK, $GITHUB, $SITE, $SSO, $OPERATOR, $USER, $SEND_PIMESG_TO_ADMINS, $LOC_HEADER, $LOC_FOOTER;
+    $_PREVIOUS_SERVER = $_SERVER;
     $_SERVER["REQUEST_METHOD"] = "GET";
+    $_SERVER["PHP_SELF"] = preg_replace("/.*webroot\//", "/", $phpfile);
+    $_GET = $get_data;
     ob_start();
     try {
         include $phpfile;
     } finally {
         ob_get_clean(); // discard output
-        unset($_SERVER["REQUEST_METHOD"]);
+        unset($_GET);
+        $_PREVIOUS_SERVER = $_SERVER;
     }
 }
 
@@ -153,10 +159,15 @@ function getUserIsPIHasAtLeastOneMember()
 
 function getNonExistentUser()
 {
-    return ["user1@nonexistent.test", "foo", "bar", "user1@nonexistent.test"];
+    return ["user2000@org2.test", "foo", "bar", "user2000@org2.test"];
 }
 
 function getAdminUser()
 {
     return ["user1@org1.test", "foo", "bar", "user1@org1.test"];
 }
+
+function getExistingPI()
+{
+    return "pi_user1005_org3_test";
+}
diff --git a/test/unit/UnityGithubTest.php b/test/unit/UnityGithubTest.php
index a860db3dc..51944d6cb 100644
--- a/test/unit/UnityGithubTest.php
+++ b/test/unit/UnityGithubTest.php
@@ -17,7 +17,7 @@ public static function providerTestGetGithubKeys()
             # user with no keys
             ["sheldor1510", []],
             # user with 1 key
-            ["simonLeary42", ["ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDGRl6JWPj+Gq2Lz9GjYdUl4/unLoFOyfgeiII1CxutpabPByRJbeuonR0zTpn51tZeYuAUOJBOeKt+Lj4i4UDGl6igpdXXSwkBXl7jxfRPwJ6WuTkDx7Z8ynwnqlDV2q089q4OX/b/uuHgsIhIBwrouKsRQaZIqTbwNMfiqQ2zl14V0KMrTPzOiwR6Q+hqSaR5Z29WKE7ff/OWzSC3/0T6avCmcCbQaRPJdVM+QC17B0vl8FzPwRjorMngwZ0cImdQ/0Ww1d12YAL7UWp1c2egfnthKP3MuQZnNF8ixsAk1eIIwTRdiI87BOoorW8NXhxXmhyheRCsFwyP4LJBqyUVoZJ0UYyk0AO4G9EStnfpiz8YXGK+M1G4tUrWgzs1cdjlHtgCWUmITtgabnYCC4141m7n4GZTk2H/lSrJcvAs3JEiwLTj1lzeGgzeSsz/XKsnOJyzjEVr2Jp3iT+J9PbQpfS0SxTCIGgxMqllovv79pfsF/zc+vaxqSShyHW7oyn7hLMHM60LO/IIX1RWGL3rD9ecXx2pXXQ1RhIkVteIi13XkFt+KW00cstFlAd3EHCoY/XorShd2jeID7tpnYlmNfotYUs6IKefvpNC0PWkh5UXFEv3SUfw4Wd8O0DiHfhkrhxn1W/GajqSIlZ5DKgPzFg8EHexv8lSa7WJg0H3YQ=="]]
+            ["simonLeary42", ["ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL2oMxcq04PWw1iB2ZQZezFPGmX2HEKhHD6kLIoLz1RUKTNN7Glw2iF5uMFnKxYgTvdfrNjrvvLnOXvhPBvjeec="]]
         ];
     }
 
diff --git a/webroot/panel/new_account.php b/webroot/panel/new_account.php
index c4698df85..5abb45352 100644
--- a/webroot/panel/new_account.php
+++ b/webroot/panel/new_account.php
@@ -4,103 +4,134 @@
 
 use UnityWebPortal\lib\UnitySite;
 use UnityWebPortal\lib\UnityGroup;
-
-require_once $LOC_HEADER;
+use UnityWebPortal\lib\UnitySQL;
 
 if ($USER->exists()) {
-    UnitySite::redirect($CONFIG["site"]["prefix"] . "/panel/index.php");  // Redirect if account already exists
+    UnitySite::redirect($CONFIG["site"]["prefix"] . "/panel/index.php");
 }
 
-if ($_SERVER["REQUEST_METHOD"] == "POST") {
-    $errors = array();
+$pending_requests = $SQL->getRequestsByUser($USER->getUID());
 
-    if (!isset($_POST["eula"]) || $_POST["eula"] != "agree") {
-        // checkbox was not checked
-        array_push($errors, "Accepting the EULA is required");
-    }
-
-    if ($_POST["new_user_sel"] == "not_pi") {
-        $form_group = new UnityGroup($_POST["pi"], $LDAP, $SQL, $MAILER, $REDIS, $WEBHOOK);
-        if (!$form_group->exists()) {
-            array_push($errors, "The selected PI does not exist");
+if ($_SERVER["REQUEST_METHOD"] == "POST") {
+    if (isset($_POST["new_user_sel"])) {
+        if (!isset($_POST["eula"]) || $_POST["eula"] != "agree") {
+            UnitySite::badRequest("user did not agree to EULA");
+        }
+        if ($_POST["new_user_sel"] == "not_pi") {
+            $form_group = new UnityGroup($_POST["pi"], $LDAP, $SQL, $MAILER, $REDIS, $WEBHOOK);
+            if (!$form_group->exists()) {
+                UnitySite::badRequest("The selected PI does not exist");
+            }
+            $form_group->newUserRequest($USER);
         }
-    }
-
-    // Request Account Form was Submitted
-    if (count($errors) == 0) {
         if ($_POST["new_user_sel"] == "pi") {
-            // requesting a PI account
+            if (!isset($_POST["confirm_pi"]) || $_POST["confirm_pi"] != "agree") {
+                UnitySite::badRequest("user did not agree to account policy");
+            }
             $USER->getPIGroup()->requestGroup($SEND_PIMESG_TO_ADMINS);
-        } elseif ($_POST["new_user_sel"] == "not_pi") {
-            $form_group->newUserRequest($USER);
         }
     }
+    elseif (isset($_POST["cancel"])) {
+        foreach ($pending_requests as $request) {
+            if ($request["request_for"] == "admin") {
+                $USER->getPIGroup()->cancelGroupRequest();
+            } else {
+                $pi_group = new UnityGroup($request["request_for"], $LDAP, $SQL, $MAILER, $REDIS, $WEBHOOK);
+                $pi_group->cancelGroupJoinRequest($user=$USER);
+            }
+        }
+    } else {
+        UnitySite::badRequest("neither 'new_user_sel' or 'cancel' are set!");
+    }
+    UnitySite::redirect($_SERVER['PHP_SELF']);
 }
-
+require_once $LOC_HEADER;
 ?>
 
 <h1>Request Account</h1>
 <hr>
 
-<form id="newAccountForm" action="" method="POST">
-    <p>Please verify that the information below is correct before continuing</p>
-    <div>
-        <strong>Name&nbsp;&nbsp;</strong><?php echo $SSO["firstname"] . " " . $SSO["lastname"]; ?><br>
-        <strong>Email&nbsp;&nbsp;</strong><?php echo $SSO["mail"]; ?>
-    </div>
-    <p>Your unity cluster username will be <strong><?php echo $SSO["user"]; ?></strong></p>
-
-    <p>In order to activate your account on the Unity cluster,
-        you must join an existing PI group, or request your own PI group.</p>
-
-    <hr>
-
-    <?php
-    $pending_requests = $SQL->getRequestsByUser($USER->getUID());
-    if (count($pending_requests) > 0) {
-        // already has pending requests
-        echo "<p>Your request to activate your account has been submitted.
-		You will receive an email when your account is activated.</p>";
-    } else {
-        echo "<label><input type='radio' name='new_user_sel' value='pi'>Request a PI account (I am a PI)</label>";
-        echo "<br>";
-        echo "<label><input type='radio' name='new_user_sel' value='not_pi' checked>Join an existing PI group</label>";
-
-        echo "<div style='position: relative;' id='piSearchWrapper'>";
-        echo "<input type='text' id='pi_search' name='pi' placeholder='Search PI by NetID' required>";
-        echo "<div class='searchWrapper' style='display: none;'></div>";
-        echo "</div>";
-
-        echo "<hr>";
-
-        echo "<label><input type='checkbox' id='chk_eula' name='eula' value='agree' required>
-		I have read and accept the <a target='_blank' href='" . $CONFIG["site"]["terms_of_service_url"] . "'>
-		Unity Terms of Service</a></label>";
-
-        echo "<br>";
-        echo "<input style='margin-top: 10px;' type='submit' value='Request Account'>";
-    }
-    ?>
-
-    <?php
-    if (isset($errors)) {
-        echo "<div class='message'>";
-        foreach ($errors as $err) {
-            echo "<p class='message-failure'>" . $err . "</p>";
+<?php if (count($pending_requests) > 0) : ?>
+    <p>You have pending account activation requests:</p>
+    <?php foreach ($pending_requests as $request) : ?>
+        <ul><li>
+        <?php
+        $pi_uid = $request["request_for"];
+        if ($pi_uid == UnitySQL::REQUEST_BECOME_PI) {
+            $group_uid = $USER->getPIGroup()->getPIUID();
+            echo "<p>Ownership of PI Account/Group: <code>$group_uid</code> </p>";
+        } else {
+            $owner_uid = UnityGroup::getUIDfromPIUID($pi_uid);
+            echo "<p>Membership in PI Group owned by: <code>$owner_uid</code></p>";
         }
-        echo "</div>";
-    }
-    ?>
-</form>
+        ?>
+        </li></ul>
+        <hr>
+        <p><strong>Requesting Ownership of PI Account/Group</strong></p>
+        <p>You will receive an email when your account has been approved.</p>
+        <p>Email <a href="mailto:<?php echo $CONFIG['mail']['support']; ?>"><?php echo $CONFIG['mail']['support_name']; ?></a> if you have not heard back in one business day. </p>
+        <br>
+        <p><strong>Requesting Membership in a PI Group</strong></p>
+        <p>You will receive an email when your account has been approved by the PI.</p>
+        <p>You may need to remind them.</p>
+        <hr>
+        <form action="" method="POST">
+            <input name="cancel" style='margin-top: 10px;' type='submit' value='Cancel Request'/>
+        </form>
+    <?php endforeach; ?>
+<?php else : ?>
+    <form id="newAccountForm" action="" method="POST">
+        <p>Please verify that the information below is correct before continuing</p>
+        <div>
+            <strong>Name&nbsp;&nbsp;</strong><?php echo $SSO["firstname"] . " " . $SSO["lastname"]; ?><br>
+            <strong>Email&nbsp;&nbsp;</strong><?php echo $SSO["mail"]; ?>
+        </div>
+        <p>Your unity cluster username will be <strong><?php echo $SSO["user"]; ?></strong></p>
+
+        <p>In order to activate your account on the Unity cluster,
+            you must join an existing PI group, or request your own PI group.</p>
+
+        <hr>
+
+        <label><input type='radio' name='new_user_sel' value='pi'>Request a PI account</label>
+        <label><input type='radio' name='new_user_sel' value='not_pi' checked>Join an existing PI group</label>
+
+        <div style='position: relative;' id='piSearchWrapper'>
+            <input type='text' id='pi_search' name='pi' placeholder='Search PI by NetID' required>
+            <div class='searchWrapper' style='display: none;'></div>
+        </div>
+
+        <hr>
+
+        <div style='position: relative;display: none;' id='piConfirmWrapper'>
+        <label><input type='checkbox' id='chk_pi' name='confirm_pi' value='agree'>
+           I have read the PI <a href="<?php echo $CONFIG["site"]["account_policy_url"]; ?>">
+            account policy</a> guidelines. </label>
+        </div>
+        <br>
+
+        <label><input type='checkbox' id='chk_eula' name='eula' value='agree' required>
+            I have read and accept the <a target='_blank' href='<?php echo $CONFIG["site"]["terms_of_service_url"]; ?>'>
+                Unity Terms of Service</a>.</label>
+
+        <br>
+        <input style='margin-top: 10px;' type='submit' value='Request Account'>
+    </form>
+<?php endif; ?>
 
 <script>
     $('input[type=radio][name=new_user_sel]').change(function() {
+        let pi_cnf_text = $('#piConfirmWrapper');
         let pi_sel_text = $('#piSearchWrapper');
         if (this.value == 'not_pi') {
+            pi_cnf_text.hide();
             pi_sel_text.show();
+            $("#chk_pi").prop("required", false);
             $("#pi_search").prop("required", true);
         } else if (this.value == 'pi') {
+            pi_cnf_text.show();
             pi_sel_text.hide();
+            $("#chk_pi").prop("required", true);
             $("#pi_search").prop("required", false);
         }
     });


Reviews:
user: login: simonLeary42
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T18:03:30Z
user: login: simonLeary42
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T18:11:10Z
user: login: simonLeary42
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T18:19:52Z
user: login: bryank-cs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T19:58:09Z
user: login: simonLeary42
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T14:01:22Z
user: login: simonLeary42
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T14:03:25Z
user: login: simonLeary42
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T14:08:11Z
user: login: simonLeary42
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T14:08:33Z
user: login: simonLeary42
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-23T21:44:03Z

Review comments:
diff_hunk: @@ -0,0 +1,10 @@
+<?php
+
+// This template is sent to the user cancelling the request
+$this->Subject = "Unity Account Request Cancelled";
+?>
+
+<p>Hello,</p>
+
+<p>Your request for to join <?php echo $data["group"]; ?> on the Unity Cluster has been cancelld per your request.</p>
path: resources/mail/group_join_request_cancelled.php
user: login: simonLeary42
body: ```suggestion
<p>Your request for to join <?php echo $data["group"]; ?> on the Unity Cluster has been cancelled per your request.</p>
```
created_at: 2025-05-21T18:03:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,10 @@
+<?php
+
+// This template is sent to the user cancelling the request
+$this->Subject = "Unity Account Request Cancelled";
+?>
+
+<p>Hello,</p>
+
+<p>Your request for to join <?php echo $data["group"]; ?> on the Unity Cluster has been cancelled per your request.</p>
path: resources/mail/group_join_request_cancelled.php
user: login: simonLeary42
body: ```suggestion
<p>Your request to join group '<?php echo $data["group"]; ?>' on the Unity Cluster has been cancelled per your request.</p>
```
created_at: 2025-05-21T18:11:09Z
author_association: MEMBER
diff_hunk: @@ -76,28 +76,34 @@ function switchUser(
 function http_post(string $phpfile, array $post_data): void
 {
     global $CONFIG, $REDIS, $LDAP, $SQL, $MAILER, $WEBHOOK, $GITHUB, $SITE, $SSO, $OPERATOR, $USER, $SEND_PIMESG_TO_ADMINS, $LOC_HEADER, $LOC_FOOTER;
+    $_PREVIOUS_SERVER = $_SERVER;
     $_SERVER["REQUEST_METHOD"] = "POST";
+    $_SERVER["PHP_SELF"] = preg_replace("/.*webroot\//", "/", $phpfile);
     $_POST = $post_data;
     ob_start();
     try {
         include $phpfile;
     } finally {
         ob_get_clean(); // discard output
         unset($_POST);
-        unset($_SERVER["REQUEST_METHOD"]);
+        $_SERVER = $_PREVIOUS_SERVER;
     }
 }
 
-function http_get(string $phpfile): void
+function http_get(string $phpfile, array $get_data): void
path: test/phpunit-bootstrap.php
user: login: simonLeary42
body: tests are failing now because they are still calling `http_get` with 1 argument
created_at: 2025-05-21T18:19:52Z
author_association: MEMBER
diff_hunk: @@ -76,28 +76,34 @@ function switchUser(
 function http_post(string $phpfile, array $post_data): void
 {
     global $CONFIG, $REDIS, $LDAP, $SQL, $MAILER, $WEBHOOK, $GITHUB, $SITE, $SSO, $OPERATOR, $USER, $SEND_PIMESG_TO_ADMINS, $LOC_HEADER, $LOC_FOOTER;
+    $_PREVIOUS_SERVER = $_SERVER;
     $_SERVER["REQUEST_METHOD"] = "POST";
+    $_SERVER["PHP_SELF"] = preg_replace("/.*webroot\//", "/", $phpfile);
     $_POST = $post_data;
     ob_start();
     try {
         include $phpfile;
     } finally {
         ob_get_clean(); // discard output
         unset($_POST);
-        unset($_SERVER["REQUEST_METHOD"]);
+        $_SERVER = $_PREVIOUS_SERVER;
     }
 }
 
-function http_get(string $phpfile): void
+function http_get(string $phpfile, array $get_data): void
path: test/phpunit-bootstrap.php
user: login: bryank-cs
body: ```suggestion
function http_get(string $phpfile, array $get_data = array()): void
```
created_at: 2025-05-21T19:58:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,82 +26,126 @@
             array_push($errors, "The selected PI does not exist");
         }
     }
-
     // Request Account Form was Submitted
     if (count($errors) == 0) {
         if ($_POST["new_user_sel"] == "pi") {
+            if (!isset($_POST["chk_pi"]) || $_POST["chk_pi"] != "agree") {
+                // checkbox was not checked
+                array_push($errors, "Please confirm you have read the account policy guidelines.");
+            }
             // requesting a PI account
             $USER->getPIGroup()->requestGroup($SEND_PIMESG_TO_ADMINS);
         } elseif ($_POST["new_user_sel"] == "not_pi") {
             $form_group->newUserRequest($USER);
         }
     }
+    header("Location: {$_SERVER['PHP_SELF']}");
+    UnitySite::die();
+}
+
+if (isset($_GET['cancel']) && count($pending_requests) > 0) {
+    foreach ($pending_requests as $request) {
+        if ($request["request_for"] == "admin") {
+            // cancel PI request
+            $pi_group = new UnityGroup(UnityGroup::getPIUIDfromUID(
+                $USER->getUID()
+            ), $LDAP, $SQL, $MAILER, $REDIS, $WEBHOOK);
+            $pi_group->cancelGroupRequest();
path: webroot/panel/new_account.php
user: login: simonLeary42
body: ```suggestion
            $USER->getPIGroup()->cancelGroupRequest();
```
created_at: 2025-05-23T14:01:21Z
author_association: MEMBER
diff_hunk: @@ -76,28 +76,34 @@ function switchUser(
 function http_post(string $phpfile, array $post_data): void
 {
     global $CONFIG, $REDIS, $LDAP, $SQL, $MAILER, $WEBHOOK, $GITHUB, $SITE, $SSO, $OPERATOR, $USER, $SEND_PIMESG_TO_ADMINS, $LOC_HEADER, $LOC_FOOTER;
+    $_PREVIOUS_SERVER = $_SERVER;
     $_SERVER["REQUEST_METHOD"] = "POST";
+    $_SERVER["PHP_SELF"] = preg_replace("/.*webroot\//", "/", $phpfile);
path: test/phpunit-bootstrap.php
user: login: simonLeary42
body: did this fix a problem, or is it just good practice?
created_at: 2025-05-23T14:03:25Z
author_association: MEMBER
diff_hunk: @@ -25,82 +26,126 @@
             array_push($errors, "The selected PI does not exist");
         }
     }
-
     // Request Account Form was Submitted
     if (count($errors) == 0) {
         if ($_POST["new_user_sel"] == "pi") {
+            if (!isset($_POST["chk_pi"]) || $_POST["chk_pi"] != "agree") {
+                // checkbox was not checked
+                array_push($errors, "Please confirm you have read the account policy guidelines.");
+            }
             // requesting a PI account
             $USER->getPIGroup()->requestGroup($SEND_PIMESG_TO_ADMINS);
         } elseif ($_POST["new_user_sel"] == "not_pi") {
             $form_group->newUserRequest($USER);
         }
     }
+    header("Location: {$_SERVER['PHP_SELF']}");
+    UnitySite::die();
+}
+
+if (isset($_GET['cancel']) && count($pending_requests) > 0) {
+    foreach ($pending_requests as $request) {
+        if ($request["request_for"] == "admin") {
+            // cancel PI request
+            $pi_group = new UnityGroup(UnityGroup::getPIUIDfromUID(
+                $USER->getUID()
+            ), $LDAP, $SQL, $MAILER, $REDIS, $WEBHOOK);
+            $pi_group->cancelGroupRequest();
+        } else {
+            $pi_group = new UnityGroup($request["request_for"], $LDAP, $SQL, $MAILER, $REDIS, $WEBHOOK);
+            $pi_group->cancelGroupJoinRequest($user=$USER);
+        }
+    }
+    header("Location: {$_SERVER['PHP_SELF']}");
+    UnitySite::die();
path: webroot/panel/new_account.php
user: login: simonLeary42
body: ```suggestion
    header("Refresh:0");
```
created_at: 2025-05-23T14:08:11Z
author_association: MEMBER
diff_hunk: @@ -76,28 +76,34 @@ function switchUser(
 function http_post(string $phpfile, array $post_data): void
 {
     global $CONFIG, $REDIS, $LDAP, $SQL, $MAILER, $WEBHOOK, $GITHUB, $SITE, $SSO, $OPERATOR, $USER, $SEND_PIMESG_TO_ADMINS, $LOC_HEADER, $LOC_FOOTER;
+    $_PREVIOUS_SERVER = $_SERVER;
     $_SERVER["REQUEST_METHOD"] = "POST";
+    $_SERVER["PHP_SELF"] = preg_replace("/.*webroot\//", "/", $phpfile);
path: test/phpunit-bootstrap.php
user: login: simonLeary42
body: nevermind, I see that it's relevant for `UnitySite::redirect`
created_at: 2025-05-23T14:08:33Z
author_association: MEMBER

PR comments:
user: login: simonLeary42
created_at: 2025-05-23T15:35:29Z
body: resolves https://github.com/UnityHPC/unity-web-portal/issues/224
author_association: MEMBER
user: login: simonLeary42
created_at: 2025-05-23T16:23:21Z
body: Can the EULA check box and the account policy check box work the same? EULA throws an error after submit, account policy throws error before submit. Ideally the submit button would also be grayed out until both are checked but that can be another PR.
author_association: MEMBER
user: login: simonLeary42
created_at: 2025-05-23T16:24:36Z
body: > Can the EULA check box and the account policy check box work the same? EULA throws an error after submit, account policy throws error before submit. Ideally the submit button would also be grayed out until both are checked but that can be another PR.

..err, I was wrong, they both make an error after submit.
author_association: MEMBER
user: login: simonLeary42
created_at: 2025-05-23T21:36:41Z
body: 
https://github.com/user-attachments/assets/478f2f7f-b89a-4cf2-9513-61fbba106e78


author_association: MEMBER
user: login: simonLeary42
created_at: 2025-05-23T21:43:05Z
body: ![image](https://github.com/user-attachments/assets/c8d769d5-f387-4ad0-ba9e-d1727e820697)

![image](https://github.com/user-attachments/assets/d2c13f0d-8543-4625-8fb0-34559fe15cb0)

author_association: MEMBER