Files
alt-mysql/tests/phpmyadmin_install_test.sh
Marek Miklewicz 766380c9eb Read only_db restriction from a dedicated session key
da_login.php now stores the ticket's restrict_db into
DA_MYSQL_PHPMYADMIN_RESTRICT_DB, and config.inc.php's only_db builder
reads that key instead of the landing-page db field, so an
all-databases login (empty restrict_db) leaves phpMyAdmin unrestricted.
2026-07-04 21:31:02 +02:00

239 lines
9.8 KiB
Bash

#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT="$PLUGIN_DIR/scripts/setup/phpmyadmin_install.sh"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
grep -Fq 'PHPMYADMIN_VERSION="${PHPMYADMIN_VERSION:-5.2.3}"' "$SCRIPT" \
|| fail "phpMyAdmin default version is not 5.2.3"
grep -Fq 'PHPMYADMIN_TARBALL_URL="${PHPMYADMIN_TARBALL_URL:-https://files.phpmyadmin.net/phpMyAdmin/5.2.3/phpMyAdmin-5.2.3-all-languages.tar.gz}"' "$SCRIPT" \
|| fail "phpMyAdmin tarball URL is not the explicit 5.2.3 URL"
mkdir -p "$TMP_DIR/phpMyAdmin-source"
cat > "$TMP_DIR/phpMyAdmin-source/index.php" <<'PHP'
<?php
echo 'private phpMyAdmin';
PHP
CUSTOMBUILD_DIR="$TMP_DIR/custombuild"
mkdir -p "$CUSTOMBUILD_DIR"
PHPMYADMIN_PRIVATE_DIR="$TMP_DIR/alt-mysql-phpmyadmin" \
PHPMYADMIN_SOURCE_DIR="$TMP_DIR/phpMyAdmin-source" \
PHPMYADMIN_INSTALL_ASSUME_ROOT=1 \
CUSTOMBUILD_DIR="$CUSTOMBUILD_DIR" \
bash "$SCRIPT" >/dev/null
PRIVATE_DIR="$TMP_DIR/alt-mysql-phpmyadmin"
CONFIG_INC="$PRIVATE_DIR/config.inc.php"
SIGNON_SCRIPT="$PRIVATE_DIR/da_signon.php"
SIGNON_URL_PAGE="$PRIVATE_DIR/da_login.php"
TICKET_DIR="$PRIVATE_DIR/runtime/tickets"
SESSION_DIR="$TMP_DIR/sessions"
ALIAS_CONF="$CUSTOMBUILD_DIR/custom/ap2/conf/extra/httpd-alias.conf"
[ -r "$PRIVATE_DIR/index.php" ] || fail "private phpMyAdmin copy was not installed"
[ -r "$CONFIG_INC" ] || fail "private phpMyAdmin config was not created"
[ -r "$SIGNON_SCRIPT" ] || fail "signon script was not created"
[ -r "$SIGNON_URL_PAGE" ] || fail "phpMyAdmin SignonURL page was not created"
[ -d "$TICKET_DIR" ] || fail "phpMyAdmin ticket directory was not created"
[ -r "$ALIAS_CONF" ] || fail "Apache alias config was not created"
grep -Fq "# BEGIN ALT_MYSQL_PHPMYADMIN" "$ALIAS_CONF" || fail "Apache alias block marker missing"
grep -Fq "Alias /alt-mysql-phpmyadmin ${PRIVATE_DIR}" "$ALIAS_CONF" || fail "Apache alias does not point at private phpMyAdmin dir"
grep -Fq "# END ALT_MYSQL_PHPMYADMIN" "$ALIAS_CONF" || fail "Apache alias end marker missing"
PHPMYADMIN_PRIVATE_DIR="$TMP_DIR/alt-mysql-phpmyadmin" \
PHPMYADMIN_SOURCE_DIR="$TMP_DIR/phpMyAdmin-source" \
PHPMYADMIN_INSTALL_ASSUME_ROOT=1 \
CUSTOMBUILD_DIR="$CUSTOMBUILD_DIR" \
bash "$SCRIPT" >/dev/null
BEGIN_COUNT="$(grep -Fc "# BEGIN ALT_MYSQL_PHPMYADMIN" "$ALIAS_CONF" || true)"
[ "$BEGIN_COUNT" -eq 1 ] || fail "Apache alias block was duplicated on re-run (found $BEGIN_COUNT times)"
# Under SuExec (DirectAdmin's default vhost convention), PHP for the shared
# /var/www/html vhost executes as the SuexecUserGroup account (e.g. "webapps"),
# not as Apache's own httpd.conf "Group" (e.g. "apache"). Ticket files must be
# group-owned by whichever account actually executes PHP there, or phpMyAdmin's
# da_login.php cannot read them.
FAKE_HTTPD_CONF="$TMP_DIR/httpd.conf"
cat > "$FAKE_HTTPD_CONF" <<'EOF'
User apache
Group apache
EOF
FAKE_VHOSTS_CONF="$TMP_DIR/httpd-vhosts.conf"
cat > "$FAKE_VHOSTS_CONF" <<'EOF'
<VirtualHost 127.0.0.1:80>
DocumentRoot /var/www/html
SuexecUserGroup webapps webapps
</VirtualHost>
EOF
SUEXEC_CUSTOMBUILD_DIR="$TMP_DIR/custombuild-suexec"
mkdir -p "$SUEXEC_CUSTOMBUILD_DIR"
SUEXEC_STDERR="$TMP_DIR/suexec-install.stderr"
PHPMYADMIN_PRIVATE_DIR="$TMP_DIR/alt-mysql-phpmyadmin-suexec" \
PHPMYADMIN_SOURCE_DIR="$TMP_DIR/phpMyAdmin-source" \
PHPMYADMIN_INSTALL_ASSUME_ROOT=1 \
CUSTOMBUILD_DIR="$SUEXEC_CUSTOMBUILD_DIR" \
APACHE_HTTPD_CONF="$FAKE_HTTPD_CONF" \
APACHE_VHOSTS_CONF="$FAKE_VHOSTS_CONF" \
bash "$SCRIPT" >/dev/null 2>"$SUEXEC_STDERR"
grep -Fq "diradmin:webapps" "$SUEXEC_STDERR" \
|| fail "phpMyAdmin installer must chown the runtime/tickets dirs to the SuexecUserGroup account (webapps), not Apache's own Group (apache). stderr was: $(cat "$SUEXEC_STDERR")"
SERVER_JSON="$(php -r '
$_SERVER["HTTPS"] = "on";
$_SERVER["HTTP_HOST"] = "panel.example.test";
$cfg=[];
include $argv[1];
echo json_encode($cfg["Servers"][1] ?? []);
' "$CONFIG_INC")"
php -r '
$server = json_decode($argv[1], true);
if (($server["host"] ?? "") !== "127.0.0.1") {
fwrite(STDERR, "host mismatch\n");
exit(2);
}
if ((string)($server["port"] ?? "") !== "33033") {
fwrite(STDERR, "port mismatch\n");
exit(3);
}
if (($server["socket"] ?? "not-empty") !== "") {
fwrite(STDERR, "socket mismatch\n");
exit(4);
}
if (($server["SignonURL"] ?? "") !== "https://panel.example.test/alt-mysql-phpmyadmin/da_login.php") {
fwrite(STDERR, "SignonURL mismatch\n");
exit(5);
}
if (($server["LogoutURL"] ?? "") !== "https://panel.example.test:2222/CMD_PLUGINS/alt-mysql/index.html") {
fwrite(STDERR, "LogoutURL mismatch, got: " . ($server["LogoutURL"] ?? "(empty)") . "\n");
exit(6);
}
' "$SERVER_JSON" || fail "phpMyAdmin config does not target alt-mariadb TCP endpoint"
# config.inc.php must restrict phpMyAdmin's visible database to the one the
# SSO ticket was issued for, not show every database the account can reach.
mkdir -p "$SESSION_DIR"
ONLY_DB="$(php -d session.save_path="$SESSION_DIR" -r '
session_name("DA_MYSQL_PHPMYADMIN");
session_id("onlydbtest");
session_start();
$_SESSION["DA_MYSQL_PHPMYADMIN_AUTH"] = ["user" => "x", "password" => "y", "db" => "demo_target_db"];
$_SESSION["DA_MYSQL_PHPMYADMIN_RESTRICT_DB"] = "demo_target_db";
session_write_close();
$_COOKIE["DA_MYSQL_PHPMYADMIN"] = "onlydbtest";
$cfg = [];
include $argv[1];
echo $cfg["Servers"][1]["only_db"] ?? "";
' "$CONFIG_INC")"
[ "$ONLY_DB" = "demo_target_db" ] \
|| fail "config.inc.php only_db should be scoped to the SSO ticket's database, got: '$ONLY_DB'"
# When no specific database was requested (the top-nav PHPMYADMIN button),
# only_db must be empty so phpMyAdmin shows every granted database.
ALL_DB_ONLY_DB="$(php -d session.save_path="$SESSION_DIR" -r '
session_name("DA_MYSQL_PHPMYADMIN");
session_id("alldbtest");
session_start();
$_SESSION["DA_MYSQL_PHPMYADMIN_AUTH"] = ["user" => "x", "password" => "y", "db" => "demo_landing_db"];
$_SESSION["DA_MYSQL_PHPMYADMIN_RESTRICT_DB"] = "";
session_write_close();
$_COOKIE["DA_MYSQL_PHPMYADMIN"] = "alldbtest";
$cfg = [];
include $argv[1];
echo $cfg["Servers"][1]["only_db"] ?? "(unset)";
' "$CONFIG_INC")"
[ "$ALL_DB_ONLY_DB" = "" ] \
|| fail "config.inc.php only_db must be empty for an all-databases login, got: '$ALL_DB_ONLY_DB'"
grep -Fq 'return [$username, $password];' "$SIGNON_SCRIPT" \
|| fail "signon script should return username/password only"
php -d display_errors=1 -r 'set_error_handler(static function($errno, $errstr) { fwrite(STDERR, $errstr . "\n"); exit(9); }); $cfg=[]; include $argv[1]; include $argv[2];' "$CONFIG_INC" "$SIGNON_SCRIPT" \
|| fail "phpMyAdmin config and signon script cannot be loaded in one PHP process"
mkdir -p "$SESSION_DIR"
cat > "$TICKET_DIR/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.json" <<'JSON'
{"version":1,"expires_at":4102444800,"auth":{"user":"da_tmp_phpmyadmin_test","password":"secret","db":"user_db"},"route":"/database/structure","db":"user_db"}
JSON
php -d display_errors=1 -d session.save_path="$SESSION_DIR" -r '
$_GET["ticket"] = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
$_COOKIE["DA_MYSQL_PHPMYADMIN"] = "tickettest";
$_SERVER["HTTPS"] = "on";
$_SERVER["HTTP_HOST"] = "panel.example.test";
include $argv[1];
' "$SIGNON_URL_PAGE" >/tmp/altmysql-da-login-test.out 2>/tmp/altmysql-da-login-test.err \
|| fail "da_login.php did not accept a valid ticket"
[ ! -e "$TICKET_DIR/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.json" ] \
|| fail "da_login.php did not consume the SSO ticket"
php -d session.save_path="$SESSION_DIR" -r '
session_name("DA_MYSQL_PHPMYADMIN");
session_id("tickettest");
session_start();
$auth = $_SESSION["DA_MYSQL_PHPMYADMIN_AUTH"] ?? [];
if (($auth["user"] ?? "") !== "da_tmp_phpmyadmin_test") {
fwrite(STDERR, "session user mismatch\n");
exit(2);
}
if (($auth["password"] ?? "") !== "secret") {
fwrite(STDERR, "session password mismatch\n");
exit(3);
}
if (($auth["db"] ?? "") !== "user_db") {
fwrite(STDERR, "session db mismatch\n");
exit(4);
}
' || fail "da_login.php did not create the phpMyAdmin signon session"
php -d session.save_path="$SESSION_DIR" -r '
session_name("DA_MYSQL_PHPMYADMIN");
session_id("tickettest");
session_start();
$_SESSION["DA_MYSQL_PHPMYADMIN_AUTH"] = [
"user" => "da_tmp_phpmyadmin_test",
"password" => "secret",
"db" => "user_db",
];
session_write_close();
session_name("phpmyadmin");
session_id("pmaactive");
session_start();
$_SESSION["unrelated"] = "active phpMyAdmin session";
$_COOKIE["DA_MYSQL_PHPMYADMIN"] = "tickettest";
include $argv[1];
[$username, $password] = get_login_credentials("ignored");
if ($username !== "da_tmp_phpmyadmin_test") {
fwrite(STDERR, "signon user mismatch with active phpMyAdmin session\n");
exit(2);
}
if ($password !== "secret") {
fwrite(STDERR, "signon password mismatch with active phpMyAdmin session\n");
exit(3);
}
' "$SIGNON_SCRIPT" || fail "signon script cannot read SSO session while phpMyAdmin session is active"
PHPMYADMIN_PRIVATE_DIR="$PRIVATE_DIR" CUSTOMBUILD_DIR="$CUSTOMBUILD_DIR" \
bash "$PLUGIN_DIR/scripts/setup/phpmyadmin_health_check.sh" >/dev/null \
|| fail "phpMyAdmin health check should pass for SignonURL-capable config with Apache alias present"
EMPTY_CUSTOMBUILD_DIR="$TMP_DIR/custombuild-empty"
mkdir -p "$EMPTY_CUSTOMBUILD_DIR"
HEALTH_OUTPUT="$(PHPMYADMIN_PRIVATE_DIR="$PRIVATE_DIR" CUSTOMBUILD_DIR="$EMPTY_CUSTOMBUILD_DIR" \
bash "$PLUGIN_DIR/scripts/setup/phpmyadmin_health_check.sh" 2>&1)" \
&& fail "phpMyAdmin health check should fail when the Apache alias is missing"
echo "$HEALTH_OUTPUT" | grep -Fqi "alias" \
|| fail "phpMyAdmin health check failure message should mention the missing Apache alias (got: $HEALTH_OUTPUT)"
echo "phpmyadmin_install_test: OK"