ba64342702
phpMyAdmin login was broken because phpmyadmin_install.sh never wired the private phpMyAdmin copy into the web server: no Apache Alias was registered via DirectAdmin CustomBuild, so the SSO redirect target was unreachable. Add configure_apache_alias()/apply_apache_alias() (Alias + Directory blocks, ./build rewrite_confs, httpd reload), detect the real Apache group instead of hardcoding diradmin:diradmin, stop silently swallowing chown/chmod failures, and verify an optional SHA256 for the downloaded phpMyAdmin tarball. Extend phpmyadmin_health_check.sh to detect a missing/incorrect Apache alias and to probe HTTP reachability. Security hardening: scope temporary phpMyAdmin SSO MySQL roles to 127.0.0.1 instead of '%', tighten SSO ticket file permissions to 0640 (they contain a plaintext MySQL password), and log MySQL connection failures for diagnosis instead of swallowing them silently. Bump version to 1.2.12 and rebuild the release archive.
97 lines
3.4 KiB
PHP
97 lines
3.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
$pluginDir = dirname(__DIR__);
|
|
require_once $pluginDir . '/exec/lib/Settings.php';
|
|
require_once $pluginDir . '/exec/lib/MySQLService.php';
|
|
require_once $pluginDir . '/exec/lib/PhpMyAdminSso.php';
|
|
|
|
function fail(string $message): void
|
|
{
|
|
fwrite(STDERR, "FAIL: {$message}\n");
|
|
exit(1);
|
|
}
|
|
|
|
function assert_true(bool $condition, string $message): void
|
|
{
|
|
if (!$condition) {
|
|
fail($message);
|
|
}
|
|
}
|
|
|
|
function assert_same($expected, $actual, string $message): void
|
|
{
|
|
if ($expected !== $actual) {
|
|
fail($message . ' expected=' . var_export($expected, true) . ' actual=' . var_export($actual, true));
|
|
}
|
|
}
|
|
|
|
function load_settings(string $contents): Settings
|
|
{
|
|
$path = tempnam(sys_get_temp_dir(), 'altmysql-settings-');
|
|
if ($path === false) {
|
|
fail('cannot create temp settings file');
|
|
}
|
|
file_put_contents($path, $contents);
|
|
$settings = Settings::load($path);
|
|
unlink($path);
|
|
return $settings;
|
|
}
|
|
|
|
// --- Temporary SSO role must be host-scoped, not wildcard '%' ---
|
|
|
|
$mysqlServiceSource = (string)file_get_contents($pluginDir . '/exec/lib/MySQLService.php');
|
|
assert_true(
|
|
!str_contains($mysqlServiceSource, "@\\'%\\'"),
|
|
'MySQLService must not create/grant/drop temporary phpMyAdmin SSO roles on the wildcard host (%)'
|
|
);
|
|
|
|
$mysqlServiceClass = new ReflectionClass(MySQLService::class);
|
|
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');
|
|
|
|
// --- phpMyAdmin login ticket files must not be world-readable ---
|
|
|
|
$ssoDir = sys_get_temp_dir() . '/altmysql-pma-sso-' . bin2hex(random_bytes(4));
|
|
mkdir($ssoDir, 0755, true);
|
|
|
|
$settings = load_settings("PHPMYADMIN_SSO_DIR={$ssoDir}\nPHPMYADMIN_TICKET_TTL=60\n");
|
|
|
|
$ssoClass = new ReflectionClass(PhpMyAdminSso::class);
|
|
/** @var PhpMyAdminSso $sso */
|
|
$sso = $ssoClass->newInstanceWithoutConstructor();
|
|
$settingsProp = $ssoClass->getProperty('settings');
|
|
$settingsProp->setAccessible(true);
|
|
$settingsProp->setValue($sso, $settings);
|
|
|
|
$writeTicket = $ssoClass->getMethod('writeLoginTicket');
|
|
$writeTicket->setAccessible(true);
|
|
$auth = [
|
|
'user' => 'da_tmp_phpmyadmin_demo_test',
|
|
'password' => 'secret-password',
|
|
'host' => '127.0.0.1',
|
|
'port' => 33033,
|
|
'db' => 'demo_db',
|
|
];
|
|
$token = $writeTicket->invoke($sso, $auth, 'demo_db');
|
|
|
|
assert_true(preg_match('/\A[a-f0-9]{64}\z/', $token) === 1, 'ticket token must be a 64 char lowercase hex string');
|
|
|
|
$ticketPath = $ssoDir . '/tickets/' . $token . '.json';
|
|
assert_true(is_file($ticketPath), 'ticket file must be written to <sso_dir>/tickets/<token>.json');
|
|
|
|
$mode = fileperms($ticketPath) & 0777;
|
|
assert_same(0640, $mode, 'ticket files contain a plaintext MySQL password and must not be world-readable (expected 0640)');
|
|
|
|
$decoded = json_decode((string)file_get_contents($ticketPath), true);
|
|
assert_true(is_array($decoded), 'ticket file must contain valid JSON');
|
|
assert_same('demo_db', $decoded['db'] ?? null, 'ticket must record the target database');
|
|
assert_same('da_tmp_phpmyadmin_demo_test', $decoded['auth']['user'] ?? null, 'ticket must record the temporary role name');
|
|
|
|
// cleanup
|
|
array_map('unlink', glob($ssoDir . '/tickets/*') ?: []);
|
|
@rmdir($ssoDir . '/tickets');
|
|
@rmdir($ssoDir);
|
|
|
|
echo "phpmyadmin_sso_test: OK\n";
|