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.
This commit is contained in:
@@ -320,6 +320,19 @@ return static function (AppContext $ctx): void {
|
|||||||
$generatedRole = $selectedRole;
|
$generatedRole = $selectedRole;
|
||||||
$generatedDb = $dbName;
|
$generatedDb = $dbName;
|
||||||
$ok[] = 'Baza danych została utworzona.';
|
$ok[] = 'Baza danych została utworzona.';
|
||||||
|
|
||||||
|
// Reset the create-database form so the next database can be
|
||||||
|
// created without manually clearing the previous input.
|
||||||
|
$_POST['db_suffix'] = '';
|
||||||
|
$_POST['db_name'] = '';
|
||||||
|
$_POST['role_name'] = '';
|
||||||
|
$_POST['password'] = '';
|
||||||
|
$_POST['existing_role'] = '';
|
||||||
|
$_POST['role_mode'] = 'new';
|
||||||
|
$_POST['creation_mode'] = 'simple';
|
||||||
|
$_POST['remote_host'] = '';
|
||||||
|
$_POST['remote_comment'] = '';
|
||||||
|
$advancedPosted = false;
|
||||||
} catch (Throwable $inner) {
|
} catch (Throwable $inner) {
|
||||||
if ($createdDatabase) {
|
if ($createdDatabase) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ final class PhpMyAdminRuntimeConfig
|
|||||||
'host' => 'dynamic:' . basename($settings->mysqlCredentialsFile()),
|
'host' => 'dynamic:' . basename($settings->mysqlCredentialsFile()),
|
||||||
'credentials_file' => $settings->mysqlCredentialsFile(),
|
'credentials_file' => $settings->mysqlCredentialsFile(),
|
||||||
'client_bin_dir' => $settings->mysqlClientBinDir(),
|
'client_bin_dir' => $settings->mysqlClientBinDir(),
|
||||||
|
'directadmin_panel_port' => $settings->directAdminPanelPort(),
|
||||||
'updated_at' => time(),
|
'updated_at' => time(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ final class PhpMyAdminSso
|
|||||||
|
|
||||||
$this->db->createTemporaryRole($role, $password, $expiresAt);
|
$this->db->createTemporaryRole($role, $password, $expiresAt);
|
||||||
try {
|
try {
|
||||||
foreach ($databaseNames as $dbName) {
|
foreach (self::determineGrantTargets($targetDatabase, $databaseNames) as $dbName) {
|
||||||
$this->db->grantTemporaryAdminerAccess($dbName, $role);
|
$this->db->grantTemporaryAdminerAccess($dbName, $role);
|
||||||
}
|
}
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
@@ -86,6 +86,15 @@ final class PhpMyAdminSso
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $ownedDatabases
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private static function determineGrantTargets(string $targetDatabase, array $ownedDatabases): array
|
||||||
|
{
|
||||||
|
return in_array($targetDatabase, $ownedDatabases, true) ? [$targetDatabase] : [];
|
||||||
|
}
|
||||||
|
|
||||||
private function buildPhpMyAdminLoginUrl(string $database, string $ticket): string
|
private function buildPhpMyAdminLoginUrl(string $database, string $ticket): string
|
||||||
{
|
{
|
||||||
$query = http_build_query([
|
$query = http_build_query([
|
||||||
|
|||||||
@@ -309,6 +309,12 @@ final class Settings
|
|||||||
return $ttl;
|
return $ttl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function directAdminPanelPort(): string
|
||||||
|
{
|
||||||
|
$port = trim($this->getString('DIRECTADMIN_PANEL_PORT', '2222'));
|
||||||
|
return ctype_digit($port) && $port !== '' ? $port : '2222';
|
||||||
|
}
|
||||||
|
|
||||||
public function phpMyAdminIntegrationWarning(): string
|
public function phpMyAdminIntegrationWarning(): string
|
||||||
{
|
{
|
||||||
if (!$this->getBool('ENABLE_PHPMYADMIN', $this->getBool('ENABLE_ADMINER', true))) {
|
if (!$this->getBool('ENABLE_PHPMYADMIN', $this->getBool('ENABLE_ADMINER', true))) {
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ PHPMYADMIN_TICKET_TTL=120
|
|||||||
PHPMYADMIN_SESSION_TTL=900
|
PHPMYADMIN_SESSION_TTL=900
|
||||||
# PHPMYADMIN_ROLE_TTL - ważność tymczasowego użytkownika MySQL dla sesji phpMyAdmin (sekundy).
|
# PHPMYADMIN_ROLE_TTL - ważność tymczasowego użytkownika MySQL dla sesji phpMyAdmin (sekundy).
|
||||||
PHPMYADMIN_ROLE_TTL=1200
|
PHPMYADMIN_ROLE_TTL=1200
|
||||||
|
# DIRECTADMIN_PANEL_PORT - port panelu DirectAdmin, na który phpMyAdmin wraca po wylogowaniu.
|
||||||
|
DIRECTADMIN_PANEL_PORT=2222
|
||||||
# ROUNDCUBE_* - opcjonalna integracja Roundcube z alt-mariadb; migrację uruchamia się ręcznie skryptem roundcube_migrate_to_alt_mysql.sh.
|
# ROUNDCUBE_* - opcjonalna integracja Roundcube z alt-mariadb; migrację uruchamia się ręcznie skryptem roundcube_migrate_to_alt_mysql.sh.
|
||||||
ROUNDCUBE_ENABLED=true
|
ROUNDCUBE_ENABLED=true
|
||||||
ROUNDCUBE_DB_NAME=roundcube
|
ROUNDCUBE_DB_NAME=roundcube
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ name=alt-mysql
|
|||||||
id=alt-mysql
|
id=alt-mysql
|
||||||
type=user
|
type=user
|
||||||
author=HITME.PL
|
author=HITME.PL
|
||||||
version=1.2.13
|
version=1.2.14
|
||||||
active=no
|
active=no
|
||||||
installed=no
|
installed=no
|
||||||
user_run_as=root
|
user_run_as=root
|
||||||
|
|||||||
@@ -308,6 +308,58 @@ function da_mysql_phpmyadmin_signon_url(array $runtimeConfig): string
|
|||||||
. '/da_login.php';
|
. '/da_login.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function da_mysql_phpmyadmin_panel_logout_url(array $runtimeConfig): string
|
||||||
|
{
|
||||||
|
$scheme = 'http';
|
||||||
|
$https = strtolower((string)($_SERVER['HTTPS'] ?? ''));
|
||||||
|
if ($https !== '' && $https !== 'off' && $https !== '0') {
|
||||||
|
$scheme = 'https';
|
||||||
|
} elseif (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https') {
|
||||||
|
$scheme = 'https';
|
||||||
|
} elseif (strtolower((string)($_SERVER['REQUEST_SCHEME'] ?? '')) === 'https') {
|
||||||
|
$scheme = 'https';
|
||||||
|
}
|
||||||
|
|
||||||
|
$host = trim((string)($_SERVER['HTTP_HOST'] ?? ''));
|
||||||
|
if ($host === '') {
|
||||||
|
$host = trim((string)($_SERVER['SERVER_NAME'] ?? 'localhost'));
|
||||||
|
}
|
||||||
|
if ($host === '') {
|
||||||
|
$host = 'localhost';
|
||||||
|
}
|
||||||
|
$host = preg_replace('/:\d+$/', '', $host) ?? $host;
|
||||||
|
|
||||||
|
$port = trim((string)($runtimeConfig['directadmin_panel_port'] ?? '2222'));
|
||||||
|
if ($port === '' || !ctype_digit($port)) {
|
||||||
|
$port = '2222';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $scheme . '://' . $host . ':' . $port . '/CMD_PLUGINS/alt-mysql/index.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
function da_mysql_phpmyadmin_only_db(): string
|
||||||
|
{
|
||||||
|
$sessionName = 'DA_MYSQL_PHPMYADMIN';
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sessionId = (string)($_COOKIE[$sessionName] ?? '');
|
||||||
|
if ($sessionId === '' || preg_match('/\A[A-Za-z0-9,-]+\z/', $sessionId) !== 1) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
session_name($sessionName);
|
||||||
|
session_id($sessionId);
|
||||||
|
session_start();
|
||||||
|
$auth = $_SESSION['DA_MYSQL_PHPMYADMIN_AUTH'] ?? [];
|
||||||
|
$db = is_array($auth) ? (string)($auth['db'] ?? '') : '';
|
||||||
|
session_write_close();
|
||||||
|
|
||||||
|
return $db;
|
||||||
|
}
|
||||||
|
|
||||||
$runtimeConfig = da_mysql_phpmyadmin_runtime_config();
|
$runtimeConfig = da_mysql_phpmyadmin_runtime_config();
|
||||||
$credentialsFile = (string)($runtimeConfig['credentials_file'] ?? DA_MYSQL_PMA_DEFAULT_CONF);
|
$credentialsFile = (string)($runtimeConfig['credentials_file'] ?? DA_MYSQL_PMA_DEFAULT_CONF);
|
||||||
if ($credentialsFile === '') {
|
if ($credentialsFile === '') {
|
||||||
@@ -329,10 +381,10 @@ $cfg['Servers'][$i]['auth_type'] = 'signon';
|
|||||||
$cfg['Servers'][$i]['SignonScript'] = __DIR__ . '/da_signon.php';
|
$cfg['Servers'][$i]['SignonScript'] = __DIR__ . '/da_signon.php';
|
||||||
$cfg['Servers'][$i]['SignonSession'] = 'DA_MYSQL_PHPMYADMIN';
|
$cfg['Servers'][$i]['SignonSession'] = 'DA_MYSQL_PHPMYADMIN';
|
||||||
$cfg['Servers'][$i]['SignonURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
|
$cfg['Servers'][$i]['SignonURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
|
||||||
$cfg['Servers'][$i]['LogoutURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
|
$cfg['Servers'][$i]['LogoutURL'] = da_mysql_phpmyadmin_panel_logout_url($runtimeConfig);
|
||||||
$cfg['Servers'][$i]['AllowNoPassword'] = false;
|
$cfg['Servers'][$i]['AllowNoPassword'] = false;
|
||||||
$cfg['Servers'][$i]['verbose'] = 'DirectAdmin alt-mysql';
|
$cfg['Servers'][$i]['verbose'] = 'DirectAdmin alt-mysql';
|
||||||
$cfg['Servers'][$i]['only_db'] = '';
|
$cfg['Servers'][$i]['only_db'] = da_mysql_phpmyadmin_only_db();
|
||||||
$cfg['ServerDefault'] = 1;
|
$cfg['ServerDefault'] = 1;
|
||||||
$cfg['AllowArbitraryServer'] = false;
|
$cfg['AllowArbitraryServer'] = false;
|
||||||
PHP
|
PHP
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
ARCHIVE="${1:-$(cd "$PLUGIN_DIR/.." && pwd)/archives/1.2.13/alt-mysql.tar.gz}"
|
ARCHIVE="${1:-$(cd "$PLUGIN_DIR/.." && pwd)/archives/1.2.14/alt-mysql.tar.gz}"
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
echo "FAIL: $*" >&2
|
echo "FAIL: $*" >&2
|
||||||
@@ -37,7 +37,7 @@ fi
|
|||||||
CONF="$(tar -xOzf "$ARCHIVE" ./plugin.conf 2>/dev/null || tar -xOzf "$ARCHIVE" plugin.conf)"
|
CONF="$(tar -xOzf "$ARCHIVE" ./plugin.conf 2>/dev/null || tar -xOzf "$ARCHIVE" plugin.conf)"
|
||||||
printf '%s\n' "$CONF" | grep -Fxq "name=alt-mysql" || fail "archive plugin name is not alt-mysql"
|
printf '%s\n' "$CONF" | grep -Fxq "name=alt-mysql" || fail "archive plugin name is not alt-mysql"
|
||||||
printf '%s\n' "$CONF" | grep -Fxq "id=alt-mysql" || fail "archive plugin id is not alt-mysql"
|
printf '%s\n' "$CONF" | grep -Fxq "id=alt-mysql" || fail "archive plugin id is not alt-mysql"
|
||||||
printf '%s\n' "$CONF" | grep -Fxq "version=1.2.13" || fail "archive plugin version is not 1.2.13"
|
printf '%s\n' "$CONF" | grep -Fxq "version=1.2.14" || fail "archive plugin version is not 1.2.14"
|
||||||
|
|
||||||
SETTINGS="$(tar -xOzf "$ARCHIVE" ./plugin-settings.conf 2>/dev/null || tar -xOzf "$ARCHIVE" plugin-settings.conf)"
|
SETTINGS="$(tar -xOzf "$ARCHIVE" ./plugin-settings.conf 2>/dev/null || tar -xOzf "$ARCHIVE" plugin-settings.conf)"
|
||||||
printf '%s\n' "$SETTINGS" | grep -Fxq "PHPMYADMIN_INSTALL_DIR=/var/www/html/alt-mysql-phpmyadmin" \
|
printf '%s\n' "$SETTINGS" | grep -Fxq "PHPMYADMIN_INSTALL_DIR=/var/www/html/alt-mysql-phpmyadmin" \
|
||||||
|
|||||||
@@ -116,11 +116,30 @@ php -r '
|
|||||||
fwrite(STDERR, "SignonURL mismatch\n");
|
fwrite(STDERR, "SignonURL mismatch\n");
|
||||||
exit(5);
|
exit(5);
|
||||||
}
|
}
|
||||||
if (($server["LogoutURL"] ?? "") !== "https://panel.example.test/alt-mysql-phpmyadmin/da_login.php") {
|
if (($server["LogoutURL"] ?? "") !== "https://panel.example.test:2222/CMD_PLUGINS/alt-mysql/index.html") {
|
||||||
fwrite(STDERR, "LogoutURL mismatch\n");
|
fwrite(STDERR, "LogoutURL mismatch, got: " . ($server["LogoutURL"] ?? "(empty)") . "\n");
|
||||||
exit(6);
|
exit(6);
|
||||||
}
|
}
|
||||||
' "$SERVER_JSON" || fail "phpMyAdmin config does not target alt-mariadb TCP endpoint"
|
' "$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" \
|
grep -Fq 'return [$username, $password];' "$SIGNON_SCRIPT" \
|
||||||
|| fail "signon script should return username/password only"
|
|| 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" \
|
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" \
|
||||||
|
|||||||
@@ -38,6 +38,17 @@ function load_settings(string $contents): Settings
|
|||||||
return $settings;
|
return $settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- phpMyAdmin logout must know the DirectAdmin panel port to build a valid return URL ---
|
||||||
|
|
||||||
|
$defaultPanelPortSettings = load_settings('');
|
||||||
|
assert_same('2222', $defaultPanelPortSettings->directAdminPanelPort(), 'DIRECTADMIN_PANEL_PORT should default to 2222');
|
||||||
|
|
||||||
|
$customPanelPortSettings = load_settings("DIRECTADMIN_PANEL_PORT=2280\n");
|
||||||
|
assert_same('2280', $customPanelPortSettings->directAdminPanelPort(), 'DIRECTADMIN_PANEL_PORT should be configurable');
|
||||||
|
|
||||||
|
$invalidPanelPortSettings = load_settings("DIRECTADMIN_PANEL_PORT=not-a-port\n");
|
||||||
|
assert_same('2222', $invalidPanelPortSettings->directAdminPanelPort(), 'a non-numeric DIRECTADMIN_PANEL_PORT should fall back to 2222');
|
||||||
|
|
||||||
// --- Temporary SSO role must be host-scoped, not wildcard '%' ---
|
// --- Temporary SSO role must be host-scoped, not wildcard '%' ---
|
||||||
|
|
||||||
$mysqlServiceSource = (string)file_get_contents($pluginDir . '/exec/lib/MySQLService.php');
|
$mysqlServiceSource = (string)file_get_contents($pluginDir . '/exec/lib/MySQLService.php');
|
||||||
@@ -50,6 +61,24 @@ $mysqlServiceClass = new ReflectionClass(MySQLService::class);
|
|||||||
assert_true($mysqlServiceClass->hasConstant('TMP_ROLE_HOST'), 'MySQLService must define a TMP_ROLE_HOST constant');
|
assert_true($mysqlServiceClass->hasConstant('TMP_ROLE_HOST'), 'MySQLService must define a TMP_ROLE_HOST constant');
|
||||||
assert_same('127.0.0.1', $mysqlServiceClass->getConstant('TMP_ROLE_HOST'), 'TMP_ROLE_HOST must scope the temporary SSO role to the phpMyAdmin app host');
|
assert_same('127.0.0.1', $mysqlServiceClass->getConstant('TMP_ROLE_HOST'), 'TMP_ROLE_HOST must scope the temporary SSO role to the phpMyAdmin app host');
|
||||||
|
|
||||||
|
// --- phpMyAdmin SSO must only grant access to the database being logged into, not every database the account owns ---
|
||||||
|
|
||||||
|
$ssoClassForGrants = new ReflectionClass(PhpMyAdminSso::class);
|
||||||
|
assert_true($ssoClassForGrants->hasMethod('determineGrantTargets'), 'PhpMyAdminSso must expose a determineGrantTargets() decision method');
|
||||||
|
$determineGrantTargets = $ssoClassForGrants->getMethod('determineGrantTargets');
|
||||||
|
$determineGrantTargets->setAccessible(true);
|
||||||
|
|
||||||
|
assert_same(
|
||||||
|
['demo_target'],
|
||||||
|
$determineGrantTargets->invoke(null, 'demo_target', ['demo_target', 'demo_other']),
|
||||||
|
'issuing a ticket for one database must only grant that database, not every database the user owns'
|
||||||
|
);
|
||||||
|
assert_same(
|
||||||
|
[],
|
||||||
|
$determineGrantTargets->invoke(null, 'not_owned', ['demo_target', 'demo_other']),
|
||||||
|
'a target database that is not in the owned list must never be granted'
|
||||||
|
);
|
||||||
|
|
||||||
// --- phpMyAdmin login ticket files must not be world-readable ---
|
// --- phpMyAdmin login ticket files must not be world-readable ---
|
||||||
|
|
||||||
$ssoDir = sys_get_temp_dir() . '/altmysql-pma-sso-' . bin2hex(random_bytes(4));
|
$ssoDir = sys_get_temp_dir() . '/altmysql-pma-sso-' . bin2hex(random_bytes(4));
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ case "$(basename "$PLUGIN_DIR")" in
|
|||||||
esac
|
esac
|
||||||
grep -Fxq "name=alt-mysql" "$PLUGIN_DIR/plugin.conf" || fail "plugin.conf name is not alt-mysql"
|
grep -Fxq "name=alt-mysql" "$PLUGIN_DIR/plugin.conf" || fail "plugin.conf name is not alt-mysql"
|
||||||
grep -Fxq "id=alt-mysql" "$PLUGIN_DIR/plugin.conf" || fail "plugin.conf id is not alt-mysql"
|
grep -Fxq "id=alt-mysql" "$PLUGIN_DIR/plugin.conf" || fail "plugin.conf id is not alt-mysql"
|
||||||
grep -Fxq "version=1.2.13" "$PLUGIN_DIR/plugin.conf" || fail "plugin.conf version is not 1.2.13"
|
grep -Fxq "version=1.2.14" "$PLUGIN_DIR/plugin.conf" || fail "plugin.conf version is not 1.2.14"
|
||||||
grep -Fq "return '/CMD_PLUGINS/alt-mysql';" "$PLUGIN_DIR/exec/lib/AppContext.php" \
|
grep -Fq "return '/CMD_PLUGINS/alt-mysql';" "$PLUGIN_DIR/exec/lib/AppContext.php" \
|
||||||
|| fail "AppContext base URL is not alt-mysql"
|
|| fail "AppContext base URL is not alt-mysql"
|
||||||
grep -Fq "\$pluginId = 'alt-mysql';" "$PLUGIN_DIR/exec/lib/DirectAdminUser.php" \
|
grep -Fq "\$pluginId = 'alt-mysql';" "$PLUGIN_DIR/exec/lib/DirectAdminUser.php" \
|
||||||
|
|||||||
Reference in New Issue
Block a user