feat: add mx login mode selection

This commit is contained in:
Marek Miklewicz
2026-06-30 12:06:32 +02:00
parent 54b2e620ff
commit b23e959491
7 changed files with 74 additions and 7 deletions
+19 -2
View File
@@ -44,7 +44,7 @@ final class RemoteLoginUrlClient
*/
public static function buildCommand(Settings $settings, LoginUrlRequest $request): array
{
return [
$base = [
'ssh',
'-o',
'BatchMode=yes',
@@ -59,6 +59,23 @@ final class RemoteLoginUrlClient
'-p',
(string)$settings->sshPort(),
$settings->sshUser() . '@' . $settings->sshHost(),
];
if ($settings->mxLoginMode() === 1) {
$command = array_merge($base, [
'/usr/bin/da',
'login-url',
'--user=' . $request->username,
'--redirect-url=' . $request->redirectPath,
'--expiry=' . $request->ttl . 's',
]);
if ($request->userIpBound) {
$command[] = '--ip=' . $request->clientIp;
}
return $command;
}
return array_merge($base, [
'mail-login-create-url',
$request->username,
$request->sourceHost,
@@ -68,7 +85,7 @@ final class RemoteLoginUrlClient
$request->clientIp,
$request->redirectPath,
(string)$request->createdAt,
];
]);
}
public function create(Settings $settings, LoginUrlRequest $request): string
+9
View File
@@ -58,6 +58,7 @@ final class Settings
return [
'DEFAULT_ENABLE' => 'true',
'MAIL_LOGIN_METHOD' => 'login_url',
'MX_LOGIN_MODE' => '1',
'MAIL_LOGIN_TARGET_NAME' => 'mx1',
'MAIL_LOGIN_TARGET_BASE_URL' => 'https://mx1.mojserwer.pl:2222',
'MAIL_LOGIN_REDIRECT_URL' => '/',
@@ -115,6 +116,11 @@ final class Settings
return $this->getString('MAIL_LOGIN_METHOD', 'login_url');
}
public function mxLoginMode(): int
{
return (int)$this->getString('MX_LOGIN_MODE', '1');
}
public function redirectUrl(): string
{
return $this->getString('MAIL_LOGIN_REDIRECT_URL', '/');
@@ -275,6 +281,9 @@ final class Settings
if ($this->method() !== 'login_url') {
throw new InvalidArgumentException('MAIL_LOGIN_METHOD must be login_url');
}
if (!in_array($this->getString('MX_LOGIN_MODE', '1'), ['1', '2'], true)) {
throw new InvalidArgumentException('MX_LOGIN_MODE must be 1 or 2');
}
if (!in_array($this->clientIpMode(), ['direct', 'trusted_proxy'], true)) {
throw new InvalidArgumentException('MAIL_LOGIN_CLIENT_IP_MODE must be direct or trusted_proxy');
}
+6 -1
View File
@@ -4,6 +4,11 @@ DEFAULT_ENABLE=true
# Metoda produkcyjna. Wersja 1 używa jednorazowego DirectAdmin login URL generowanego na MX.
MAIL_LOGIN_METHOD=login_url
# Tryb tworzenia login URL na MX:
# 1 = direct SSH command: serwer źródłowy łączy się po SSH do MX i uruchamia bezpośrednio /usr/bin/da login-url; nie wymaga instalowania helpera na MX.
# 2 = helper: serwer źródłowy łączy się po SSH do ograniczonego helpera na MX; lepsza walidacja/audyt/cleanup, ale wymaga instalacji helpera.
MX_LOGIN_MODE=1
# Nazwa i adres docelowego panelu poczty.
MAIL_LOGIN_TARGET_NAME=mx1
MAIL_LOGIN_TARGET_BASE_URL=https://mx1.mojserwer.pl:2222
@@ -25,7 +30,7 @@ MAIL_LOGIN_SOURCE_SERVER_NAME=
MAIL_LOGIN_CLIENT_IP_MODE=direct
MAIL_LOGIN_TRUSTED_PROXIES=
# Serwerowy kanał SSH do helpera na MX. Klucz jest root-owned i nigdy nie trafia do użytkownika.
# Serwerowy kanał SSH do MX. Klucz jest root-owned i nigdy nie trafia do użytkownika.
MAIL_LOGIN_REMOTE_TRANSPORT=ssh
MAIL_LOGIN_SSH_HOST=mx1.mojserwer.pl
MAIL_LOGIN_SSH_PORT=22
+1 -1
View File
@@ -2,7 +2,7 @@ name=mail-login
id=mail-login
type=user
author=HITME.PL
version=1.0.1
version=1.0.2
active=no
installed=no
user_run_as=root
+1 -2
View File
@@ -8,7 +8,7 @@ test('plugin metadata and packaging docs match project rules', function (): void
assert_contains('name=mail-login', $conf);
assert_contains('id=mail-login', $conf);
assert_contains('type=user', $conf);
assert_contains('version=1.0.1', $conf);
assert_contains('version=1.0.2', $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/mail-login.tar.gz', $packing);
@@ -37,4 +37,3 @@ test('package source excludes local-only files and dangerous placeholders', func
}
assert_same([], $bad);
});
+24 -1
View File
@@ -4,7 +4,7 @@ declare(strict_types=1);
require_once PLUGIN_ROOT . '/exec/lib/Settings.php';
require_once PLUGIN_ROOT . '/exec/lib/RemoteLoginUrlClient.php';
test('remote login url client builds fixed ssh command with positional arguments', function (): void {
test('remote login url client builds direct ssh command by default', function (): void {
$settings = Settings::fromArray([
'MAIL_LOGIN_TARGET_BASE_URL' => 'https://mx1.domena.pl:2222',
'MAIL_LOGIN_SSH_HOST' => 'mx1.domena.pl',
@@ -21,6 +21,29 @@ test('remote login url client builds fixed ssh command with positional arguments
assert_same('ssh', $command[0]);
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_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]);
+14
View File
@@ -10,6 +10,7 @@ test('settings expose secure defaults from plugin-settings.conf', function (): v
assert_true($settings->userIpBound());
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());
});
@@ -29,10 +30,23 @@ test('settings accept explicit ttl and user ip binding toggle', function (): voi
$settings = Settings::fromArray([
'LOGIN_KEY_TTL' => '5',
'USER_IP_BOUND' => '0',
'MX_LOGIN_MODE' => '2',
]);
assert_same(5, $settings->loginKeyTtl());
assert_false($settings->userIpBound());
assert_same(2, $settings->mxLoginMode());
});
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('settings parse allowed source hosts as normalized hostnames', function (): void {