feat: remove mx helper mode
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once PLUGIN_ROOT . '/mx-helper/cleanup-expired-login-urls.php';
|
||||
|
||||
test('cleanup deletes only expired login urls matching local mail-login state', function (): void {
|
||||
$state = [
|
||||
'timestamp' => 1782810000,
|
||||
'expires' => 1782810030,
|
||||
'correlation' => 'autologin-h4-mxtest-1782810000',
|
||||
'id' => 'HASHURLMATCH',
|
||||
'redirect_path' => '/',
|
||||
'client_ip' => '198.51.100.10',
|
||||
'ip_bound' => 1,
|
||||
'status' => 'created',
|
||||
];
|
||||
|
||||
$matching = [
|
||||
'id' => 'HASHURLMATCH',
|
||||
'created' => '2026-06-30T10:00:00Z',
|
||||
'expires' => '2026-06-30T10:00:30Z',
|
||||
'redirectURL' => '/',
|
||||
'allowNetworks' => ['198.51.100.10/32'],
|
||||
];
|
||||
$wrongRedirect = $matching;
|
||||
$wrongRedirect['id'] = 'HASHURLWRONG';
|
||||
$wrongRedirect['redirectURL'] = '/admin';
|
||||
$notExpired = $matching;
|
||||
$notExpired['id'] = 'HASHURLFRESH';
|
||||
$notExpired['expires'] = '2099-06-30T10:00:30Z';
|
||||
|
||||
assert_true(mailLoginCleanupShouldDelete($matching, [$state], strtotime('2026-06-30T10:01:00Z')));
|
||||
assert_false(mailLoginCleanupShouldDelete($wrongRedirect, [$state], strtotime('2026-06-30T10:01:00Z')));
|
||||
assert_false(mailLoginCleanupShouldDelete($notExpired, [$state], strtotime('2026-06-30T10:01:00Z')));
|
||||
|
||||
$stateWithoutId = $state;
|
||||
unset($stateWithoutId['id']);
|
||||
assert_false(mailLoginCleanupShouldDelete($matching, [$stateWithoutId], strtotime('2026-06-30T10:01:00Z')));
|
||||
});
|
||||
|
||||
test('cleanup rejects non-https directadmin api base url', function (): void {
|
||||
try {
|
||||
mailLoginCleanupValidateBaseUrl('http://127.0.0.1:2222');
|
||||
throw new RuntimeException('Expected http base URL to fail');
|
||||
} catch (InvalidArgumentException $e) {
|
||||
assert_contains('https', strtolower($e->getMessage()));
|
||||
}
|
||||
});
|
||||
@@ -92,8 +92,8 @@ test('keygen script pins mx host key from plugin settings', function (): void {
|
||||
assert_contains('Pinned MX host key:', $output);
|
||||
});
|
||||
|
||||
test('keygen script prints helper forced command line when mx login mode is helper', function (): void {
|
||||
$keyDir = TEST_TMP . '/keys-helper';
|
||||
test('keygen script ignores helper mode variables and prints direct ssh line only', function (): void {
|
||||
$keyDir = TEST_TMP . '/keys-direct-only';
|
||||
$script = PLUGIN_ROOT . '/scripts/keygen.sh';
|
||||
$cmd = sprintf(
|
||||
'MAIL_LOGIN_KEY_DIR=%s MX_LOGIN_MODE=2 SERVERNAME=%s SOURCE_IP=%s %s',
|
||||
@@ -107,6 +107,7 @@ test('keygen script prints helper forced command line when mx login mode is help
|
||||
|
||||
assert_same(0, $code, implode("\n", $out));
|
||||
$output = implode("\n", $out);
|
||||
assert_contains('command="/usr/local/hitme_plugins/mail-login-helper/bin/create-login-url"', $output);
|
||||
assert_not_contains('command=', $output);
|
||||
assert_not_contains('mail-login-helper', $output);
|
||||
assert_contains('KLUCZ PLUGINU MX-Autologin DLA SERWERA h4', $output);
|
||||
});
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
test('mx helper conditionally passes user ip binding to da login-url', function (): void {
|
||||
$bin = TEST_TMP . '/fake-da';
|
||||
$log = TEST_TMP . '/fake-da.log';
|
||||
$audit = TEST_TMP . '/audit.log';
|
||||
$state = TEST_TMP . '/state';
|
||||
|
||||
test_write($bin, "#!/bin/sh\nprintf '%s\\n' \"$@\" >> " . escapeshellarg($log) . "\necho 'URL: https://mx1.domena.pl:2222/api/login/url?key=SECRET'\n");
|
||||
chmod($bin, 0755);
|
||||
|
||||
$script = PLUGIN_ROOT . '/mx-helper/mail-login-authorized-command.sh';
|
||||
$cmd = sprintf(
|
||||
'MAIL_LOGIN_DA_BIN=%s MAIL_LOGIN_AUDIT_LOG=%s MAIL_LOGIN_STATE_DIR=%s MAIL_LOGIN_ALLOWED_SOURCE_HOSTS=%s %s %s',
|
||||
escapeshellarg($bin),
|
||||
escapeshellarg($audit),
|
||||
escapeshellarg($state),
|
||||
escapeshellarg('h4.domena.pl'),
|
||||
escapeshellarg($script),
|
||||
'mail-login-create-url mxtest h4.domena.pl h4 30 1 198.51.100.10 / 1782810000'
|
||||
);
|
||||
exec($cmd, $out, $code);
|
||||
assert_same(0, $code, implode("\n", $out));
|
||||
assert_contains('--ip=198.51.100.10', file_get_contents($log) ?: '');
|
||||
assert_contains('autologin-h4-mxtest-', file_get_contents($audit) ?: '');
|
||||
|
||||
file_put_contents($log, '');
|
||||
$cmdNoIp = sprintf(
|
||||
'MAIL_LOGIN_DA_BIN=%s MAIL_LOGIN_AUDIT_LOG=%s MAIL_LOGIN_STATE_DIR=%s MAIL_LOGIN_ALLOWED_SOURCE_HOSTS=%s %s %s',
|
||||
escapeshellarg($bin),
|
||||
escapeshellarg($audit),
|
||||
escapeshellarg($state),
|
||||
escapeshellarg('h4.domena.pl'),
|
||||
escapeshellarg($script),
|
||||
'mail-login-create-url mxtest h4.domena.pl h4 30 0 198.51.100.10 / 1782810001'
|
||||
);
|
||||
exec($cmdNoIp, $outNoIp, $codeNoIp);
|
||||
assert_same(0, $codeNoIp, implode("\n", $outNoIp));
|
||||
assert_not_contains('--ip=', file_get_contents($log) ?: '');
|
||||
assert_contains('ip_bound_disabled', file_get_contents($audit) ?: '');
|
||||
});
|
||||
|
||||
test('mx helper accepts forced command arguments from ssh original command', function (): void {
|
||||
$bin = TEST_TMP . '/fake-da-original';
|
||||
$log = TEST_TMP . '/fake-da-original.log';
|
||||
$audit = TEST_TMP . '/audit-original.log';
|
||||
$state = TEST_TMP . '/state-original';
|
||||
|
||||
test_write($bin, "#!/bin/sh\nprintf '%s\\n' \"$@\" >> " . escapeshellarg($log) . "\necho 'URL: https://mx1.domena.pl:2222/api/login/url?key=SECRET'\n");
|
||||
chmod($bin, 0755);
|
||||
|
||||
$script = PLUGIN_ROOT . '/mx-helper/mail-login-authorized-command.sh';
|
||||
$cmd = sprintf(
|
||||
'MAIL_LOGIN_DA_BIN=%s MAIL_LOGIN_AUDIT_LOG=%s MAIL_LOGIN_STATE_DIR=%s MAIL_LOGIN_ALLOWED_SOURCE_HOSTS=%s SSH_ORIGINAL_COMMAND=%s %s',
|
||||
escapeshellarg($bin),
|
||||
escapeshellarg($audit),
|
||||
escapeshellarg($state),
|
||||
escapeshellarg('h4.domena.pl'),
|
||||
escapeshellarg('mail-login-create-url mxtest h4.domena.pl evil 30 1 198.51.100.10 / 1782810002'),
|
||||
escapeshellarg($script)
|
||||
);
|
||||
exec($cmd, $out, $code);
|
||||
|
||||
assert_same(0, $code, implode("\n", $out));
|
||||
assert_contains('--user=mxtest', file_get_contents($log) ?: '');
|
||||
assert_contains('autologin-h4-mxtest-', file_get_contents($audit) ?: '');
|
||||
assert_not_contains('autologin-evil-mxtest-', file_get_contents($audit) ?: '');
|
||||
});
|
||||
|
||||
test('mx helper rejects disallowed source hosts and invalid client ips', function (): void {
|
||||
$bin = TEST_TMP . '/fake-da-reject';
|
||||
$audit = TEST_TMP . '/audit-reject.log';
|
||||
$state = TEST_TMP . '/state-reject';
|
||||
test_write($bin, "#!/bin/sh\necho 'URL: https://mx1.domena.pl:2222/api/login/url?key=SECRET'\n");
|
||||
chmod($bin, 0755);
|
||||
$script = PLUGIN_ROOT . '/mx-helper/mail-login-authorized-command.sh';
|
||||
|
||||
$badHost = sprintf(
|
||||
'MAIL_LOGIN_DA_BIN=%s MAIL_LOGIN_AUDIT_LOG=%s MAIL_LOGIN_STATE_DIR=%s MAIL_LOGIN_ALLOWED_SOURCE_HOSTS=%s %s %s 2>/dev/null',
|
||||
escapeshellarg($bin),
|
||||
escapeshellarg($audit),
|
||||
escapeshellarg($state),
|
||||
escapeshellarg('h4.domena.pl'),
|
||||
escapeshellarg($script),
|
||||
'mail-login-create-url mxtest evil.domena.pl evil 30 1 198.51.100.10 / 1782810003'
|
||||
);
|
||||
exec($badHost, $outHost, $codeHost);
|
||||
assert_same(1, $codeHost);
|
||||
|
||||
$badIp = sprintf(
|
||||
'MAIL_LOGIN_DA_BIN=%s MAIL_LOGIN_AUDIT_LOG=%s MAIL_LOGIN_STATE_DIR=%s MAIL_LOGIN_ALLOWED_SOURCE_HOSTS=%s %s %s 2>/dev/null',
|
||||
escapeshellarg($bin),
|
||||
escapeshellarg($audit),
|
||||
escapeshellarg($state),
|
||||
escapeshellarg('h4.domena.pl'),
|
||||
escapeshellarg($script),
|
||||
'mail-login-create-url mxtest h4.domena.pl h4 30 1 999.999.999.999 / 1782810004'
|
||||
);
|
||||
exec($badIp, $outIp, $codeIp);
|
||||
assert_same(1, $codeIp);
|
||||
assert_not_contains("\nBROKEN", file_get_contents($audit) ?: '');
|
||||
});
|
||||
@@ -8,7 +8,7 @@ test('plugin metadata and packaging docs match project rules', function (): void
|
||||
assert_contains('name=MX-Autologin', $conf);
|
||||
assert_contains('id=mxautologin', $conf);
|
||||
assert_contains('type=user', $conf);
|
||||
assert_contains('version=1.0.14', $conf);
|
||||
assert_contains('version=1.1.1', $conf);
|
||||
assert_contains('user_run_as=root', $conf);
|
||||
assert_contains('/Users/marek/GPT/ChatGPT/da_plugins/mail-login/src', $packing);
|
||||
assert_contains('/Users/marek/GPT/ChatGPT/da_plugins/mail-login/archives/X.Y.Z/mxautologin.tar.gz', $packing);
|
||||
@@ -39,6 +39,31 @@ test('package source excludes local-only files and dangerous placeholders', func
|
||||
assert_same([], $bad);
|
||||
});
|
||||
|
||||
test('package source contains no mx helper implementation or mode switch', function (): void {
|
||||
assert_false(is_dir(PLUGIN_ROOT . '/mx-helper'), 'mx-helper directory must not be shipped');
|
||||
|
||||
$forbidden = [];
|
||||
$needles = ['MX_LOGIN_MODE', 'mail-login-helper', 'mail-login-create-url', '/mx-helper/'];
|
||||
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(PLUGIN_ROOT, FilesystemIterator::SKIP_DOTS));
|
||||
foreach ($it as $file) {
|
||||
if (!$file->isFile()) {
|
||||
continue;
|
||||
}
|
||||
$relative = substr($file->getPathname(), strlen(PLUGIN_ROOT) + 1);
|
||||
if (str_starts_with($relative, '.git/') || str_starts_with($relative, 'tests/')) {
|
||||
continue;
|
||||
}
|
||||
$content = file_get_contents($file->getPathname()) ?: '';
|
||||
foreach ($needles as $needle) {
|
||||
if (str_contains($relative, $needle) || str_contains($content, $needle)) {
|
||||
$forbidden[] = $relative . ':' . $needle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_same([], $forbidden);
|
||||
});
|
||||
|
||||
test('directadmin entry points use mx autologin directory and display label', function (): void {
|
||||
$txt = file_get_contents(PLUGIN_ROOT . '/hooks/user_txt.html') ?: '';
|
||||
$img = file_get_contents(PLUGIN_ROOT . '/hooks/user_img.html') ?: '';
|
||||
|
||||
@@ -22,35 +22,13 @@ test('remote login url client builds direct ssh command by default', function ()
|
||||
assert_contains('UserKnownHostsFile=/secure/known_hosts', implode(' ', $command));
|
||||
assert_contains('root@mx1.domena.pl', implode(' ', $command));
|
||||
assert_not_contains('mail-login-create-url', implode(' ', $command));
|
||||
assert_not_contains('MX_LOGIN_MODE', implode(' ', $command));
|
||||
assert_contains('/usr/bin/da login-url', implode(' ', $command));
|
||||
assert_contains('--user=mxtest', implode(' ', $command));
|
||||
assert_contains('--expiry=30s', implode(' ', $command));
|
||||
assert_contains('--ip=198.51.100.10', implode(' ', $command));
|
||||
});
|
||||
|
||||
test('remote login url client builds helper ssh command when mode is helper', function (): void {
|
||||
$settings = Settings::fromArray([
|
||||
'MAIL_LOGIN_TARGET_BASE_URL' => 'https://mx1.domena.pl:2222',
|
||||
'MAIL_LOGIN_SSH_HOST' => 'mx1.domena.pl',
|
||||
'MAIL_LOGIN_SSH_PORT' => '222',
|
||||
'MAIL_LOGIN_SSH_USER' => 'root',
|
||||
'MAIL_LOGIN_SSH_IDENTITY' => '/secure/key',
|
||||
'MAIL_LOGIN_SSH_KNOWN_HOSTS' => '/secure/known_hosts',
|
||||
'LOGIN_KEY_TTL' => '30',
|
||||
'USER_IP_BOUND' => '1',
|
||||
'MX_LOGIN_MODE' => '2',
|
||||
]);
|
||||
$request = new LoginUrlRequest('mxtest', 'h4.domena.pl', 'h4', 30, true, '198.51.100.10', '/', 1782810000);
|
||||
$command = RemoteLoginUrlClient::buildCommand($settings, $request);
|
||||
|
||||
assert_same('ssh', $command[0]);
|
||||
$pos = array_search('mail-login-create-url', $command, true);
|
||||
assert_true(is_int($pos), 'Command marker missing');
|
||||
assert_same('mxtest', $command[$pos + 1]);
|
||||
assert_same('1', $command[$pos + 5]);
|
||||
assert_same('1782810000', $command[$pos + 8]);
|
||||
});
|
||||
|
||||
test('remote login url client omits expiry when ttl is zero', function (): void {
|
||||
$settings = Settings::fromArray([
|
||||
'MAIL_LOGIN_TARGET_BASE_URL' => 'https://mx1.domena.pl:2222',
|
||||
@@ -65,7 +43,7 @@ test('remote login url client omits expiry when ttl is zero', function (): void
|
||||
assert_not_contains('--expiry=', implode(' ', $command));
|
||||
});
|
||||
|
||||
test('remote login url client validates helper output before returning it', function (): void {
|
||||
test('remote login url client validates direct ssh output before returning it', function (): void {
|
||||
$settings = Settings::fromArray(['MAIL_LOGIN_TARGET_BASE_URL' => 'https://mx1.domena.pl:2222']);
|
||||
$request = new LoginUrlRequest('mxtest', 'h4.domena.pl', 'h4', 30, true, '198.51.100.10', '/', 1782810000);
|
||||
|
||||
@@ -81,7 +59,7 @@ test('remote login url client validates helper output before returning it', func
|
||||
|
||||
try {
|
||||
$badClient->create($settings, $request);
|
||||
throw new RuntimeException('Expected invalid helper URL to fail');
|
||||
throw new RuntimeException('Expected invalid direct SSH URL to fail');
|
||||
} catch (RuntimeException $e) {
|
||||
assert_contains('login url', strtolower($e->getMessage()));
|
||||
}
|
||||
@@ -90,7 +68,6 @@ test('remote login url client validates helper output before returning it', func
|
||||
test('remote login url client reports direct ssh failure diagnostics', function (): void {
|
||||
$settings = Settings::fromArray([
|
||||
'MAIL_LOGIN_TARGET_BASE_URL' => 'https://mx1.domena.pl:2222',
|
||||
'MX_LOGIN_MODE' => '1',
|
||||
]);
|
||||
$request = new LoginUrlRequest('mxtest', 'h4.domena.pl', 'h4', 30, true, '198.51.100.10', '/', 1782810000);
|
||||
$client = new RemoteLoginUrlClient(function (array $command, int $timeout): ProcessResult {
|
||||
|
||||
+8
-14
@@ -12,7 +12,6 @@ test('settings expose secure defaults from plugin-settings.conf', function (): v
|
||||
assert_same('MASKED', $settings->auditClientIp('198.51.100.10'));
|
||||
assert_true($settings->defaultEnable());
|
||||
assert_same('login_url', $settings->method());
|
||||
assert_same(1, $settings->mxLoginMode());
|
||||
assert_same('root', $settings->sshUser());
|
||||
assert_same('https://mx1.mojserwer.pl:2222', $settings->targetBaseUrl());
|
||||
});
|
||||
@@ -37,14 +36,12 @@ test('settings accept explicit ttl and user ip binding toggle', function (): voi
|
||||
'LOGIN_KEY_TTL' => '5',
|
||||
'USER_IP_BOUND' => '0',
|
||||
'IP_MASK' => '0',
|
||||
'MX_LOGIN_MODE' => '2',
|
||||
]);
|
||||
|
||||
assert_same(5, $settings->loginKeyTtl());
|
||||
assert_false($settings->userIpBound());
|
||||
assert_false($settings->ipMask());
|
||||
assert_same('198.51.100.10', $settings->auditClientIp('198.51.100.10'));
|
||||
assert_same(2, $settings->mxLoginMode());
|
||||
});
|
||||
|
||||
test('settings accept zero login key ttl as directadmin default expiry', function (): void {
|
||||
@@ -64,17 +61,6 @@ test('settings reject unsupported ip mask values', function (): void {
|
||||
}
|
||||
});
|
||||
|
||||
test('settings reject unsupported mx login mode values', function (): void {
|
||||
foreach (['0', '3', 'helper', ''] as $mode) {
|
||||
try {
|
||||
Settings::fromArray(['MX_LOGIN_MODE' => $mode]);
|
||||
throw new RuntimeException('Expected invalid MX_LOGIN_MODE to fail: ' . $mode);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
assert_contains('MX_LOGIN_MODE', $e->getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('source plugin settings do not expose local source host allowlist', function (): void {
|
||||
$defaults = Settings::defaults();
|
||||
$config = file_get_contents(PLUGIN_ROOT . '/plugin-settings.conf') ?: '';
|
||||
@@ -83,6 +69,14 @@ test('source plugin settings do not expose local source host allowlist', functio
|
||||
assert_not_contains('MAIL_LOGIN_ALLOWED_SOURCE_HOSTS', $config);
|
||||
});
|
||||
|
||||
test('settings do not expose mx helper mode', function (): void {
|
||||
$defaults = Settings::defaults();
|
||||
$config = file_get_contents(PLUGIN_ROOT . '/plugin-settings.conf') ?: '';
|
||||
|
||||
assert_false(array_key_exists('MX_LOGIN_MODE', $defaults));
|
||||
assert_not_contains('MX_LOGIN_MODE', $config);
|
||||
});
|
||||
|
||||
test('settings reject disabled plugin and unsupported methods through access guard', function (): void {
|
||||
try {
|
||||
Settings::fromArray(['MAIL_LOGIN_METHOD' => 'password']);
|
||||
|
||||
Reference in New Issue
Block a user