Files
alt-mysql/tests/phpmyadmin_install_test.sh
T
Marek Miklewicz 1560759c51 Scope phpMyAdmin SSO to one database, fix logout, reset create form
After live testing on the real server, three follow-up issues surfaced:

- PhpMyAdminSso granted the temporary SSO MySQL role access to every
  database the account owns, not just the one the "Zaloguj do bazy"
  button was clicked for, so phpMyAdmin showed all databases. Extract
  determineGrantTargets() and scope the GRANT to only the requested
  database; also set phpMyAdmin's only_db from the SSO session so the
  UI itself only shows that database.

- phpMyAdmin's LogoutURL pointed at da_login.php with no ticket, which
  always produced "Missing or invalid phpMyAdmin login ticket." on
  logout. Add a DIRECTADMIN_PANEL_PORT setting (default 2222) and
  point LogoutURL at the plugin's own database list
  (/CMD_PLUGINS/alt-mysql/index.html) instead.

- The create-database form kept echoing back the just-submitted
  values after a successful creation, forcing manual clearing before
  creating the next database. Clear the relevant $_POST keys once
  creation succeeds so the form resets while still preserving sticky
  values on validation failure.

Bump version to 1.2.14 and rebuild the release archive.
2026-07-04 20:38:04 +02:00

220 lines
9.0 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_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'"
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"