diff --git a/deployment/config.base.ini b/deployment/config.base.ini index 4fad60ad2..cf60d2f5a 100644 --- a/deployment/config.base.ini +++ b/deployment/config.base.ini @@ -1,6 +1,6 @@ ; DO NOT EDIT THIS FILE. Copy this file to `config.ini` and edit that file. [upstream] -version = "1.7.4" ; Current upstream version of the account portal +version = "1.7.6" ; Current upstream version of the account portal repo = "https://github.com/UnityHPC/account-portal" ; Upstream URL for the account portal [site] diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index 95dad81e6..b4e7b7e21 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -33,17 +33,24 @@ public static function die(?string $x = null): never } } + public static function redirectOverrideMethodGet(?string $dest = null): never + { + self::redirect(dest: $dest, preserve_request_method: false); + } + /* send HTTP header, set HTTP response code, print a message just in case the browser fails to redirect if PHP is not being run from the CLI, and then die */ - public static function redirect(?string $dest = null): never - { + public static function redirect( + ?string $dest = null, + bool $preserve_request_method = true, + ): never { $dest ??= getRelativeURL($_SERVER["REQUEST_URI"]); // TODO check $_SERVER["REDIRECT_STATUS"]? header("Location: $dest"); - http_response_code(302); + http_response_code($preserve_request_method ? 307 : 303); if (CONFIG["site"]["enable_redirect_message"]) { echo "If you're reading this message, then your browser has failed to redirect you " . "to the proper destination. click here to continue."; @@ -81,10 +88,11 @@ public static function gracefulDie( self::errorLog($log_title, $log_message, data: $data, error: $error, errorid: $errorid); if ( ($_SERVER["REQUEST_METHOD"] ?? "") == "POST" && - !str_starts_with($_SERVER["REQUEST_URI"], "/lan/api/") + !str_starts_with($_SERVER["REQUEST_URI"], "/lan/api/") && + !str_starts_with($_SERVER["REQUEST_URI"], "/panel/ajax/") ) { self::messageError($title, implode("\n", $body_paragraphs)); - self::redirect(); + self::redirectOverrideMethodGet(); } else { if (!headers_sent()) { http_response_code($http_response_code); @@ -239,10 +247,21 @@ public static function errorHandler( return false; } + public static function assertRequestMethod(string $expected): void + { + if (($found = $_SERVER["REQUEST_METHOD"] ?? "") != $expected) { + UnityHTTPD::badRequest( + "expected request method '$expected', got '$found'", + "invalid request method", + ); + } + } + public static function getPostData(string $key): string { + self::assertRequestMethod("POST"); if (!array_key_exists($key, $_POST)) { - self::badRequest("\$_POST has no array key '$key'"); + self::badRequest("\$_POST has no array key '$key'", data: ['$_POST' => $_POST]); } return $_POST[$key]; } @@ -254,7 +273,7 @@ public static function getQueryParameter(string $key, bool $die_if_not_found = t { if (!array_key_exists($key, $_GET)) { if ($die_if_not_found) { - self::badRequest("\$_GET has no array key '$key'"); + self::badRequest("\$_GET has no array key '$key'", data: ['$_GET' => $_GET]); } else { return null; } @@ -417,7 +436,7 @@ public static function validatePostCSRFToken(): void "Invalid Session Token", "This can happen if you leave your browser open for too long. Error ID: $errorid", ); - self::redirect(); + self::redirectOverrideMethodGet(); } } diff --git a/resources/templates/header.html.twig b/resources/templates/header.html.twig index f4013b5e5..df35c2cdf 100644 --- a/resources/templates/header.html.twig +++ b/resources/templates/header.html.twig @@ -101,9 +101,7 @@
- {% if messages|length >= 3 %} - - {% endif %} + {% set level_enum = enum('UnityWebPortal\\lib\\UnityHTTPDMessageLevel') %} {% for message in messages|reverse %} {% set title = message[0] %} diff --git a/resources/templates/header.php b/resources/templates/header.php index 3f2f4be8b..36d33858f 100644 --- a/resources/templates/header.php +++ b/resources/templates/header.php @@ -11,7 +11,7 @@ && ($_POST["form_type"] ?? null) == "clearView" ) { unset($_SESSION["viewUser"]); - UnityHTTPD::redirect(getRelativeURL("admin/user-mgmt.php")); + UnityHTTPD::redirectOverrideMethodGet(getRelativeURL("admin/user-mgmt.php")); } // Webroot files need to handle their own POSTs before loading the header // so that they can do UnityHTTPD::badRequest before anything else has been printed. @@ -19,7 +19,7 @@ // header also needs to handle POST data. So this header does the PRG redirect // for all pages. unset($_POST); // unset ensures that header must not come before POST handling - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } if (isset($SSO)) { diff --git a/webroot/admin/pi-mgmt.php b/webroot/admin/pi-mgmt.php index 18b1fb479..a86c1fd82 100644 --- a/webroot/admin/pi-mgmt.php +++ b/webroot/admin/pi-mgmt.php @@ -49,7 +49,7 @@ $group = new UnityGroup(UnityHTTPD::getPostData("pi"), $LDAP, $SQL, $MAILER); if ($group->getIsDisabled()) { UnityHTTPD::messageError("Cannot Disable PI Group", "Group is already disabled"); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } $group->disable(); UnityHTTPD::messageSuccess("Group Disabled", $group->gid); diff --git a/webroot/admin/user-mgmt.php b/webroot/admin/user-mgmt.php index 098ccb8be..181155774 100644 --- a/webroot/admin/user-mgmt.php +++ b/webroot/admin/user-mgmt.php @@ -14,7 +14,7 @@ switch ($_POST["form_type"]) { case "viewAsUser": $_SESSION["viewUser"] = $_POST["uid"]; - UnityHTTPD::redirect(getRelativeURL("panel/account.php")); + UnityHTTPD::redirectOverrideMethodGet(getRelativeURL("panel/account.php")); break; /** @phpstan-ignore deadCode.unreachable */ } } diff --git a/webroot/js/messages.js b/webroot/js/messages.js index 36eeead33..1f885aecd 100644 --- a/webroot/js/messages.js +++ b/webroot/js/messages.js @@ -1,15 +1,18 @@ -function hideClearAllMessagesButtonIfAllMessagesAlreadyCleared() { +function updateClearMessagesButtonVisibility(minMessageCount = 3) { var visibleMessages = $('#messages .message:visible').length; - if (visibleMessages === 0) { + if (visibleMessages >= minMessageCount) { + $('#clear_all_messages_button').show(); + } else { $('#clear_all_messages_button').hide(); } } $(document).ready(function () { + updateClearMessagesButtonVisibility(); + $('#messages').on('click', '.message button', function () { var button = $(this); var message = button.parent(); - message.hide(); $.ajax({ url: '/panel/ajax/delete_message.php', method: 'POST', @@ -18,14 +21,27 @@ $(document).ready(function () { 'title': button.data('title'), 'body': button.data('body') }, + success: function () { + message.hide(); + updateClearMessagesButtonVisibility(); + }, error: function (result) { $("#messages").append(result.responseText); } }); - hideClearAllMessagesButtonIfAllMessagesAlreadyCleared(); }); $('#clear_all_messages_button').on('click', function () { - $('#messages .message:visible button').click(); + $.ajax({ + url: '/panel/ajax/clear_messages.php', + method: 'POST', + success: function () { + $('#messages .message').hide(); + $('#clear_all_messages_button').hide(); + }, + error: function (result) { + $("#messages").append(result.responseText); + } + }); }); }); diff --git a/webroot/lan/api/bump-last-login.php b/webroot/lan/api/bump-last-login.php index de377c1c7..49d469248 100644 --- a/webroot/lan/api/bump-last-login.php +++ b/webroot/lan/api/bump-last-login.php @@ -4,9 +4,7 @@ use UnityWebPortal\lib\UnityHTTPD; -if ($_SERVER["REQUEST_METHOD"] !== "POST") { - UnityHTTPD::badRequest("invalid request method {$_SERVER['REQUEST_METHOD']}"); -} +UnityHTTPD::assertRequestMethod("POST"); UnityHTTPD::validateAPIKey(); $uid = UnityHTTPD::getQueryParameter("uid"); $SQL->updateUserLastLogin($uid); diff --git a/webroot/panel/account.php b/webroot/panel/account.php index 468c6a11d..a78a0a8b2 100644 --- a/webroot/panel/account.php +++ b/webroot/panel/account.php @@ -26,7 +26,7 @@ } catch (EncodingUnknownException | EncodingConversionException $e) { UnityHTTPD::errorLog("uploaded key has bad encoding", "", error: $e); UnityHTTPD::messageError("SSH Key Not Added: Invalid Encoding", ""); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } break; case "generate": @@ -40,7 +40,7 @@ "No Keys Added", "No keys found associated with GitHub account." ); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } break; default: @@ -77,7 +77,7 @@ $stub_fingprint = substr($sha256_fingerprint, 0, 6); UnityHTTPD::messageSuccess("SSH Key Added", "Fingerprint: $stub_fingprint"); } - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); break; /** @phpstan-ignore deadCode.unreachable */ case "delKey": $key = _base64_decode(UnityHTTPD::getPostData("delKey")); @@ -86,10 +86,10 @@ $USER->removeSSHKey($key); } catch (ArrayKeyException) { UnityHTTPD::messageError("Cannot Remove SSH Key", "Key not found"); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } UnityHTTPD::messageSuccess("SSH Key Removed", "$key_short"); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); break; /** @phpstan-ignore deadCode.unreachable */ case "loginshell": $shell = UnityHTTPD::getPostData("shellSelect"); @@ -98,32 +98,32 @@ } $USER->setLoginShell($shell); UnityHTTPD::messageSuccess("Login Shell Changed", ""); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); break; /** @phpstan-ignore deadCode.unreachable */ case "pi_request": if ($USER->isPI()) { UnityHTTPD::messageError("Cannot Submit PI Request", "Already a PI"); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } if ($SQL->requestExists($USER->uid, UnitySQL::REQUEST_BECOME_PI)) { UnityHTTPD::messageError("Cannot Submit PI Request", "This request already exists"); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } if ($_POST["tos"] != "agree") { UnityHTTPD::badRequest("user did not agree to terms of service"); } $USER->getPIGroup()->requestGroup(); UnityHTTPD::messageSuccess("PI Group Requested", ""); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); break; /** @phpstan-ignore deadCode.unreachable */ case "cancel_pi_request": if (!$SQL->requestExists($USER->uid, UnitySQL::REQUEST_BECOME_PI)) { UnityHTTPD::messageError("Cannot Cancel PI Request", "No PI request found"); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } $USER->getPIGroup()->cancelGroupRequest(); UnityHTTPD::messageSuccess("PI Request Cancelled", ""); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); break; /** @phpstan-ignore deadCode.unreachable */ case "disable": if ($hasGroups) { @@ -131,14 +131,14 @@ "Cannot Disable", "You are a PI or you are a member of at least one PI group" ); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } if ($USER->getFlag(UserFlag::DISABLED)) { UnityHTTPD::badRequest("user is already disabled", ""); } $USER->disable(UnityUserDisabledReason::DisabledSelf); UnityHTTPD::messageSuccess("Account Disabled", ""); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); break; /** @phpstan-ignore deadCode.unreachable */ } } diff --git a/webroot/panel/ajax/clear_messages.php b/webroot/panel/ajax/clear_messages.php new file mode 100644 index 000000000..5bd52af6f --- /dev/null +++ b/webroot/panel/ajax/clear_messages.php @@ -0,0 +1,9 @@ +reEnable(); UnityHTTPD::messageSuccess("Account Re-Enabled", ""); - UnityHTTPD::redirect(getRelativeURL("panel/account.php")); + UnityHTTPD::redirectOverrideMethodGet(getRelativeURL("panel/account.php")); } } require getTemplatePath("header.php"); diff --git a/webroot/panel/groups.php b/webroot/panel/groups.php index cd7f8533d..0af20e478 100644 --- a/webroot/panel/groups.php +++ b/webroot/panel/groups.php @@ -12,7 +12,7 @@ $pi_group = new UnityGroup($gid, $LDAP, $SQL, $MAILER); if (!$pi_group->exists()) { UnityHTTPD::messageError("This PI Doesn't Exist", $gid); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } return $pi_group; }; @@ -32,28 +32,28 @@ "Invalid Group Membership Request", "You've already requested this" ); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } if ($pi_account->memberUIDExists($USER->uid)) { UnityHTTPD::messageError( "Invalid Group Membership Request", "You're already in this PI group" ); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } } $pi_account->newUserRequest($USER); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); break; /** @phpstan-ignore deadCode.unreachable */ case "removePIForm": $pi_account = $getPIGroupFromPost(); $pi_account->removeUser($USER, UnityGroupUserRemovedReason::RemovedSelf); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); break; /** @phpstan-ignore deadCode.unreachable */ case "cancelPIForm": $pi_account = $getPIGroupFromPost(); $pi_account->cancelGroupJoinRequest($USER); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); break; /** @phpstan-ignore deadCode.unreachable */ } } diff --git a/webroot/panel/new_account.php b/webroot/panel/new_account.php index ec2f2b8e4..4ec9b2741 100644 --- a/webroot/panel/new_account.php +++ b/webroot/panel/new_account.php @@ -11,7 +11,7 @@ if (UnityHTTPD::getPostData("form_type") === "register") { UnityHTTPD::validatePostCSRFToken(); $USER->init($SSO["firstname"], $SSO["lastname"], $SSO["mail"], $SSO["org"]); - UnityHTTPD::redirect(getRelativeURL("panel/account.php")); + UnityHTTPD::redirectOverrideMethodGet(getRelativeURL("panel/account.php")); } } require getTemplatePath("header.php"); diff --git a/webroot/panel/pi.php b/webroot/panel/pi.php index 7043b71da..e6ca7f351 100644 --- a/webroot/panel/pi.php +++ b/webroot/panel/pi.php @@ -41,11 +41,11 @@ if ($_POST["action"] == "Approve") { $group->approveUser($form_user); UnityHTTPD::messageSuccess("User Approved", ""); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } elseif ($_POST["action"] == "Deny") { $group->denyUser($form_user); UnityHTTPD::messageSuccess("User Denied", ""); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } else { UnityHTTPD::badRequest(sprintf("unrecognized action: '%s'", $_POST["action"]), ""); } @@ -57,9 +57,9 @@ UnityHTTPD::messageSuccess("User Removed", ""); // group manager removed themself if ($USER->uid === $form_user->uid) { - UnityHTTPD::redirect("/panel/groups.php"); + UnityHTTPD::redirectOverrideMethodGet("/panel/groups.php"); } else { - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } break; /** @phpstan-ignore deadCode.unreachable */ case "disable": @@ -68,15 +68,15 @@ } if (count($group->getMemberUIDs()) > 1) { UnityHTTPD::messageError("Cannot Disable PI Group", "Group still has members"); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } if ($group->getIsDisabled()) { UnityHTTPD::messageError("Cannot Disable PI Group", "Group is already disabled"); - UnityHTTPD::redirect(); + UnityHTTPD::redirectOverrideMethodGet(); } $group->disable(); UnityHTTPD::messageSuccess("Group Disabled", ""); - UnityHTTPD::redirect(getRelativeURL("panel/account.php")); + UnityHTTPD::redirectOverrideMethodGet(getRelativeURL("panel/account.php")); break; /** @phpstan-ignore deadCode.unreachable */ } }