diff --git a/defaults/config.ini.default b/defaults/config.ini.default index 7b3d241f..70507029 100644 --- a/defaults/config.ini.default +++ b/defaults/config.ini.default @@ -15,7 +15,7 @@ terms_of_service_url = "https://github.com" ; this can be external or a portal p account_policy_url = "https://github.com" ; this can be external or a portal page created with "content management" allow_die = true ; internal use only enable_verbose_error_log = true ; internal use only -enable_shutdown_msg = true ; internal use only +enable_error_to_user = true ; internal use only [ldap] uri = "ldap://identity" ; URI of remote LDAP server diff --git a/deployment/overrides/phpunit/config/config.ini b/deployment/overrides/phpunit/config/config.ini index c6d29ec3..414cb7d7 100644 --- a/deployment/overrides/phpunit/config/config.ini +++ b/deployment/overrides/phpunit/config/config.ini @@ -4,4 +4,4 @@ custom_user_mappings_dir = "test/custom_user_mappings" [site] allow_die = false enable_verbose_error_log = false -enable_shutdown_message = false +enable_error_to_user = false diff --git a/deployment/overrides/worker/config/config.ini b/deployment/overrides/worker/config/config.ini index 5e1a2727..53ef3d6b 100644 --- a/deployment/overrides/worker/config/config.ini +++ b/deployment/overrides/worker/config/config.ini @@ -1,2 +1,2 @@ [site] -enable_shutdown_msg = false +enable_error_to_user = false diff --git a/resources/lib/UnitySite.php b/resources/lib/UnitySite.php index 31207f88..ebc5e839 100644 --- a/resources/lib/UnitySite.php +++ b/resources/lib/UnitySite.php @@ -24,85 +24,118 @@ public static function die($x = null, $show_user = false) } } - public static function redirect($destination) + public static function redirect($dest) { - header("Location: $destination"); - self::die("Redirect failed, click here to continue.", true); + header("Location: $dest"); + self::errorToUser("Redirect failed, click here to continue.", 302); + self::die(); } - private static function headerResponseCode(int $code, string $reason) - { - $protocol = $_SERVER["SERVER_PROTOCOL"] ?? "HTTP/1.1"; - $msg = $protocol . " " . strval($code) . " " . $reason; - header($msg, true, $code); + // $data must be JSON serializable + public static function errorLog( + string $title, + string $message, + string|null $errorid = null, + Throwable|null $error = null, + mixed $data = null, + ) { + if (!CONFIG["site"]["enable_verbose_error_log"]) { + error_log("$title: $message"); + return; + } + $output = [ + "message" => $message, + "REMOTE_USER" => $_SERVER["REMOTE_USER"] ?? null, + "REMOTE_ADDR" => $_SERVER["REMOTE_ADDR"] ?? null, + ]; + if (!is_null($errorid)) { + $output["errorid"] = $errorid; + } + if (!is_null($error)) { + $output["error"] = self::throwableToArray($error); + } else { + // newlines are bad for error log, but getTrace() is too verbose + $output["trace"] = explode("\n", (new \Exception())->getTraceAsString()); + } + if (!is_null($data)) { + $output["data"] = $data; + } + error_log("$title: " . json_encode($output, JSON_UNESCAPED_SLASHES)); } - public static function errorLog(string $title, string $message) + // recursive on $t->getPrevious() + private static function throwableToArray(Throwable $t): array { - if (CONFIG["site"]["enable_verbose_error_log"] == false) { - error_log("$title: $message"); + $output = [ + "type" => gettype($t), + "msg" => $t->getMessage(), + // newlines are bad for error log, but getTrace() is too verbose + "trace" => explode("\n", $t->getTraceAsString()), + ]; + $previous = $t->getPrevious(); + if (!is_null($previous)) { + $output["previous"] = self::throwableToArray($previous); + } + return $output; + } + + private static function errorToUser( + string $msg, + int $http_response_code, + string|null $errorid = null + ) { + if (!CONFIG["site"]["enable_error_to_user"]) { return; } - error_log( - "$title: " . json_encode( - [ - "message" => $message, - "REMOTE_USER" => @$_SERVER["REMOTE_USER"], - "REMOTE_ADDR" => @$_SERVER["REMOTE_ADDR"], - "trace" => (new \Exception())->getTraceAsString() - ] - ) - ); + $notes = "Please notify a Unity admin at " . CONFIG["mail"]["support"] . "."; + if (!is_null($errorid)) { + $notes = $notes . " Error ID: $errorid."; + } + if (!headers_sent()) { + http_response_code($http_response_code); + } + // text may not be shown in the webpage in an obvious way, so make a popup + self::alert("$msg $notes"); + echo "

$msg

$notes

"; } - public static function badRequest($message) + public static function badRequest($message, $error = null, $data = null) { - self::headerResponseCode(400, "bad request"); - self::errorLog("bad request", $message); - error_clear_last(); + $errorid = uniqid(); + self::errorToUser("Invalid requested action or submitted data.", 400, $errorid); + self::errorLog("bad request", $message, $errorid, $error, $data); self::die($message); } - public static function forbidden($message) + public static function forbidden($message, $error = null, $data = null) { - self::headerResponseCode(403, "forbidden"); - self::errorLog("forbidden", $message); - error_clear_last(); + $errorid = uniqid(); + self::errorToUser("Permission denied.", 403, $errorid); + self::errorLog("forbidden", $message, $errorid, $error, $data); + self::die($message); + } + + public static function internalServerError($message, $error = null, $data = null) + { + $errorid = uniqid(); + self::errorToUser("An internal server error has occurred.", 500, $errorid); + self::errorLog("internal server error", $message, $errorid, $error, $data); self::die($message); } // https://www.php.net/manual/en/function.register-shutdown-function.php public static function shutdown() { - if (CONFIG["site"]["enable_shutdown_msg"] == false) { - return; - } $e = error_get_last(); if (is_null($e) || $e["type"] !== E_ERROR) { return; } - if (!headers_sent()) { - self::headerResponseCode(500, "internal server error"); + // newlines are bad for error log + if (!is_null($e) && array_key_exists("message", $e) && str_contains($e["message"], "\n")) { + $e["message"] = explode("\n", $e["message"]); } - $errorid = uniqid(); - $e["unity_error_id"] = $errorid; - self::errorLog("internal server error", json_encode($e)); - echo " -

An internal server error has occurred.

-

- Please notify a Unity admin at " - . CONFIG["mail"]["support"] - . ". Error ID: $errorid. -

- "; - // if content already printed, status code will be ignored and alert text may not be - // shown in the webpage in an obvious way, so make a popup - self::alert( - "An internal server error has occurred. " - . "Please notify a Unity admin at " - . CONFIG["mail"]["support"] - . ". Error ID: $errorid." - ); + // error_get_last is an array, not a Throwable + self::internalServerError("An internal server error has occurred.", data: ["error" => $e]); } public static function arrayGetOrBadRequest(array $array, ...$keys) diff --git a/resources/lib/phpopenldaper b/resources/lib/phpopenldaper index d7db0fb4..f24c8682 160000 --- a/resources/lib/phpopenldaper +++ b/resources/lib/phpopenldaper @@ -1 +1 @@ -Subproject commit d7db0fb4b3be1082597afeedac4030be6446565a +Subproject commit f24c868275d23fe2a7d05800e26deb1098aac509