Skip to content

"view as user" test, rework uses of die() #212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fa5d021
replace die with exception
simonLeary42 Apr 25, 2025
4bf3d45
use if/then instead of die
simonLeary42 Apr 25, 2025
6951cf5
set unique session cache location for each phpunit run
simonLeary42 Apr 25, 2025
e466ba2
viewAsUser test
simonLeary42 Apr 25, 2025
01e7581
UnitySite not static
simonLeary42 Apr 25, 2025
a6ea0ad
remove pointless file
simonLeary42 Apr 25, 2025
8eb8a0e
ajax php don't initialize entire stack
simonLeary42 Apr 28, 2025
f14bbef
every switchUser has a fresh session by default
simonLeary42 Apr 28, 2025
c2ceb00
get post always discard output
simonLeary42 Apr 28, 2025
ace27c4
Revert "UnitySite not static"
simonLeary42 Apr 29, 2025
65a7e5d
add UnitySite::die
simonLeary42 Apr 29, 2025
8ff1cc7
Revert "replace die with exception"
simonLeary42 Apr 29, 2025
42091f8
add functions errorLog, badRequest
simonLeary42 Apr 29, 2025
7b4b4a1
update ViewAsUserTest
simonLeary42 Apr 29, 2025
e32f557
get test working
simonLeary42 Apr 29, 2025
160b563
add exceptions
simonLeary42 Apr 29, 2025
e2b5a0d
delete comments
simonLeary42 Apr 29, 2025
62e6972
unused import
simonLeary42 Apr 29, 2025
22b8b41
rename post to http_post, get to http_get
simonLeary42 Apr 29, 2025
e4fd195
fix die
simonLeary42 Apr 29, 2025
532080f
add pre commit check for usage of die
simonLeary42 Apr 29, 2025
fe9ea0f
no color in die checker
simonLeary42 Apr 29, 2025
2ed84d9
dont print to stderr
simonLeary42 Apr 29, 2025
5c5f0ef
ignore phpunit result cache
simonLeary42 Apr 29, 2025
18c3c8e
don't allow exit() either
simonLeary42 Apr 29, 2025
c0a3163
refactor
simonLeary42 Apr 29, 2025
f535351
show both die and exit
simonLeary42 Apr 29, 2025
95bb809
get -> http_get
simonLeary42 Apr 29, 2025
d2f4c87
revert conditional
simonLeary42 Apr 29, 2025
d1f07cb
revert conditional 2
simonLeary42 Apr 29, 2025
e3938b7
add message
simonLeary42 Apr 29, 2025
fb8d2f4
remove unused exception class
simonLeary42 Apr 29, 2025
dc29a00
exclude UnitySite from die check
simonLeary42 Apr 29, 2025
ce9409d
use unauthorized function, don't print anything to use
simonLeary42 Apr 29, 2025
aef306a
allow empty die(), rename unauthorized to forbidden
simonLeary42 Apr 29, 2025
e4db086
don't explode trace
simonLeary42 Apr 29, 2025
88fdc46
remove broken magic response code reason
simonLeary42 Apr 29, 2025
06fcb51
httpResponseCode private
simonLeary42 Apr 29, 2025
9d39d30
more tests
simonLeary42 Apr 30, 2025
7b6a731
allow undefined server protocol
simonLeary42 Apr 30, 2025
71a8246
more tests
simonLeary42 Apr 30, 2025
f01f7a7
clear view before redirect nonexistent user
simonLeary42 Apr 30, 2025
80c2e08
remove comment
simonLeary42 Apr 30, 2025
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ composer.lock
deployment/*
!deployment/**/README.md
!deployment/deploy.sh

.phpunit.result.cache
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,9 @@ repos:
language: system
files: \.php$
args: [-l]
- id: assert-no-die-exit
name: Assert no die()/exit()
entry: ./test/assert-no-die-exit.bash
language: system
files: \.php$
exclude: resources/lib/UnitySite\.php$
55 changes: 54 additions & 1 deletion resources/lib/UnitySite.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,70 @@
namespace UnityWebPortal\lib;

use phpseclib3\Crypt\PublicKeyLoader;
use UnityWebPortal\lib\exceptions\PhpUnitNoDieException;

class UnitySite
{
public static function die($x = null)
{
if (@$GLOBALS["PHPUNIT_NO_DIE_PLEASE"] == true) {
if (is_null($x)) {
throw new PhpUnitNoDieException();
} else {
throw new PhpUnitNoDieException($x);
}
} else {
if (is_null($x)) {
die();
} else {
die($x);
}
}
}

public static function redirect($destination)
{
if ($_SERVER["PHP_SELF"] != $destination) {
header("Location: $destination");
die("Redirect failed, click <a href='$destination'>here</a> to continue.");
self::die("Redirect failed, click <a href='$destination'>here</a> to continue.");
}
}

private static function headerResponseCode(int $code, string $reason)
{
$protocol = @$_SERVER["SERVER_PROTOCOL"] ?? "HTTP/1.1";
$msg = $protocol . " " . strval($code) . " " . $reason;
header($msg, true, $code);
}

public static function errorLog(string $title, string $message)
{
error_log(
"$title: " . json_encode(
[
"message" => $message,
"REMOTE_USER" => @$_SERVER["REMOTE_USER"],
"REMOTE_ADDR" => @$_SERVER["REMOTE_ADDR"],
"trace" => (new \Exception())->getTraceAsString()
]
)
);
}

public static function badRequest($message)
{
self::headerResponseCode(400, "bad request");
self::errorLog("bad request", $message);
self::die();
}

public static function forbidden($message)
{
self::headerResponseCode(403, "forbidden");
self::errorLog("forbidden", $message);
self::die();
}

public static function removeTrailingWhitespace($arr)
{
$out = array();
Expand Down
6 changes: 6 additions & 0 deletions resources/lib/exceptions/PhpUnitNoDieException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php
namespace UnityWebPortal\lib\exceptions;

class PhpUnitNoDieException extends \Exception
{
}
43 changes: 24 additions & 19 deletions resources/templates/header.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

use UnityWebPortal\lib\UnitySite;

if ((@$_SESSION["is_admin"] ?? false) == true
&& $_SERVER["REQUEST_METHOD"] == "POST"
&& (@$_POST["form_name"] ?? null) == "clearView"
) {
unset($_SESSION["viewUser"]);
UnitySite::redirect($CONFIG["site"]["prefix"] . "/admin/user-mgmt.php");
}

if (isset($SSO)) {
if (!$_SESSION["user_exists"]) {
UnitySite::redirect($CONFIG["site"]["prefix"] . "/panel/new_account.php");
Expand Down Expand Up @@ -116,23 +124,20 @@
<main>

<?php
if (isset($_SESSION["is_admin"]) && $_SESSION["is_admin"]) {
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["form_name"]) && $_POST["form_name"] == "clearView") {
unset($_SESSION["viewUser"]);
UnitySite::redirect($CONFIG["site"]["prefix"] . "/admin/user-mgmt.php");
}

if (isset($_SESSION["viewUser"])) {
echo "<div id='viewAsBar'>";
echo "<span>You are accessing the web portal as the user <strong>" .
$_SESSION["viewUser"] . "</strong></span>";
echo
"<form method='POST' action=''>
<input type='hidden' name='form_name' value='clearView'>
<input type='hidden' name='uid' value='" . $_SESSION["viewUser"] . "'>
<input type='submit' value='Return to My User'>
</form>";
echo "</div>";
}
if (isset($_SESSION["is_admin"])
&& $_SESSION["is_admin"]
&& isset($_SESSION["viewUser"])
) {
$viewUser = $_SESSION["viewUser"];
echo "
<div id='viewAsBar'>
<span>You are accessing the web portal as the user <strong>$viewUser</strong></span>
<form method='POST' action=''>
<input type='hidden' name='form_name' value='clearView'>
<input type='hidden' name='uid' value='$viewUser'>
<input type='submit' value='Return to My User'>
</form>
</div>
";
}
?>
?>
28 changes: 28 additions & 0 deletions test/assert-no-die-exit.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "at least one argument required"
exit 1
fi

rc=0

# --color=never because magit git output log doesn't support it
die_occurrences="$(
grep -H --color=never --line-number -P '\bdie\s*[\(;]' "$@" | grep -v -P 'UnitySite::die'
)" || true
if [ -n "$die_occurrences" ]; then
echo "die is not allowed! use UnitySite::die() instead."
echo "$die_occurrences"
rc=1
fi

# --color=never because magit git output log doesn't support it
exit_occurrences="$(grep -H --color=never --line-number -P '\bexit\s*[\(;]' "$@")" || true
if [ -n "$exit_occurrences" ]; then
echo "exit is not allowed! use UnitySite::die() instead."
echo "$exit_occurrences"
rc=1
fi

exit "$rc"
6 changes: 3 additions & 3 deletions test/functional/AccountDeletionRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ public function testRequestAccountDeletionUserHasNoGroups()
$this->assertEmpty($USER->getGroups());
$this->assertNumberAccountDeletionRequests(0);
try {
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
["form_type" => "account_deletion_request"]
);
$this->assertNumberAccountDeletionRequests(1);
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
["form_type" => "account_deletion_request"]
);
Expand All @@ -62,7 +62,7 @@ public function testRequestAccountDeletionUserHasGroup()
$this->assertNotEmpty($USER->getGroups());
$this->assertNumberAccountDeletionRequests(0);
try {
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
["form_type" => "account_deletion_request"]
);
Expand Down
2 changes: 1 addition & 1 deletion test/functional/LoginShellSetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function testSetLoginShell(string $shell): void
if (!$this->isShellValid($shell)) {
$this->expectException("Exception");
}
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
["form_type" => "loginshell", "shellSelect" => $shell]
);
Expand Down
6 changes: 3 additions & 3 deletions test/functional/PiBecomeRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ public function testRequestBecomePi()
$this->assertFalse($USER->isPI());
$this->assertNumberPiBecomeRequests(0);
try {
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
["form_type" => "pi_request"]
);
$this->assertNumberPiBecomeRequests(1);
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
["form_type" => "pi_request"]
);
Expand All @@ -61,7 +61,7 @@ public function testRequestBecomePiUserRequestedAccountDeletion()
$this->assertNumberPiBecomeRequests(0);
$this->assertTrue($SQL->accDeletionRequestExists($USER->getUID()));
try {
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
["form_type" => "pi_request"]
);
Expand Down
8 changes: 4 additions & 4 deletions test/functional/SSHKeyAddTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class SSHKeyAddTest extends TestCase
{
private function addSshKeysPaste(array $keys): void {
foreach ($keys as $key) {
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
[
"form_type" => "addKey",
Expand All @@ -27,7 +27,7 @@ private function addSshKeysImport(array $keys): void {
fwrite($tmp, $key);
$_FILES["keyfile"] = ["tmp_name" => $tmp_path];
try {
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
["form_type" => "addKey", "add_type" => "import"]
);
Expand All @@ -40,7 +40,7 @@ private function addSshKeysImport(array $keys): void {

private function addSshKeysGenerate(array $keys): void {
foreach ($keys as $key) {
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
[
"form_type" => "addKey",
Expand All @@ -57,7 +57,7 @@ private function addSshKeysGithub(array $keys): void {
$GITHUB = $this->createMock(UnityGithub::class);
$GITHUB->method("getSshPublicKeys")->willReturn($keys);
try {
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
[
"form_type" => "addKey",
Expand Down
2 changes: 1 addition & 1 deletion test/functional/SSHKeyDeleteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static function setUpBeforeClass(): void{
}

private function deleteKey(string $index): void {
post(
http_post(
__DIR__ . "/../../webroot/panel/account.php",
["form_type" => "delKey", "delIndex" => $index]
);
Expand Down
83 changes: 83 additions & 0 deletions test/functional/ViewAsUserTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

use UnityWebPortal\lib\UnitySite;
use UnityWebPortal\lib\exceptions\PhpUnitNoDieException;
use PHPUnit\Framework\TestCase;

class ViewAsUserTest extends TestCase
{
public function _testViewAsUser(array $beforeUser, array $afterUser)
{
global $USER;
switchUser(...$afterUser);
$afterUid = $USER->getUID();
switchUser(...$beforeUser);
// $this->assertTrue($USER->isAdmin());
$beforeUid = $USER->getUID();
// $this->assertNotEquals($afterUid, $beforeUid);
try {
http_post(
__DIR__ . "/../../webroot/admin/user-mgmt.php",
[
"form_name" => "viewAsUser",
"uid" => $afterUid,
],
);
} catch (PhpUnitNoDieException) {}
$this->assertArrayHasKey("viewUser", $_SESSION);
// redirect means that php process dies and user's browser will initiate a new one
// this makes `require_once autoload.php` run again and init.php changes $USER
session_write_close();
http_get(__DIR__ . "/../../resources/init.php");
// now we should be new user
$this->assertEquals($afterUid, $USER->getUID());
// $this->assertTrue($_SESSION["user_exists"]);
try {
http_post(
__DIR__ . "/../../resources/templates/header.php",
["form_name" => "clearView"],
);
} catch (PhpUnitNoDieException) {}
$this->assertArrayNotHasKey("viewUser", $_SESSION);
// redirect means that php process dies and user's browser will initiate a new one
// this makes `require_once autoload.php` run again and init.php changes $USER
session_write_close();
http_get(__DIR__ . "/../../resources/init.php");
// now we should be back to original user
$this->assertEquals($beforeUid, $USER->getUID());
}

public function testViewAsUser()
{
$this->_testViewAsUser(getAdminUser(), getNormalUser());
}

public function testViewAsNonExistentUser()
{
$this->_testViewAsUser(getAdminUser(), getNonExistentUser());
}

public function testViewAsSelf()
{
$this->_testViewAsUser(getAdminUser(), getAdminUser());
}

public function testNonAdminViewAsAdmin()
{
global $USER;
switchUser(...getAdminUser());
$adminUid = $USER->getUID();
$this->assertTrue($USER->isAdmin());
switchUser(...getNormalUser());
try {
http_post(
__DIR__ . "/../../webroot/admin/user-mgmt.php",
[
"form_name" => "viewAsUser",
"uid" => $adminUid,
],
);
} catch (PhpUnitNoDieException) {}
$this->assertArrayNotHasKey("viewUser", $_SESSION);
}
}
Loading
Loading