feat: add mx login mode selection
This commit is contained in:
@@ -44,7 +44,7 @@ final class RemoteLoginUrlClient
|
|||||||
*/
|
*/
|
||||||
public static function buildCommand(Settings $settings, LoginUrlRequest $request): array
|
public static function buildCommand(Settings $settings, LoginUrlRequest $request): array
|
||||||
{
|
{
|
||||||
return [
|
$base = [
|
||||||
'ssh',
|
'ssh',
|
||||||
'-o',
|
'-o',
|
||||||
'BatchMode=yes',
|
'BatchMode=yes',
|
||||||
@@ -59,6 +59,23 @@ final class RemoteLoginUrlClient
|
|||||||
'-p',
|
'-p',
|
||||||
(string)$settings->sshPort(),
|
(string)$settings->sshPort(),
|
||||||
$settings->sshUser() . '@' . $settings->sshHost(),
|
$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',
|
'mail-login-create-url',
|
||||||
$request->username,
|
$request->username,
|
||||||
$request->sourceHost,
|
$request->sourceHost,
|
||||||
@@ -68,7 +85,7 @@ final class RemoteLoginUrlClient
|
|||||||
$request->clientIp,
|
$request->clientIp,
|
||||||
$request->redirectPath,
|
$request->redirectPath,
|
||||||
(string)$request->createdAt,
|
(string)$request->createdAt,
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(Settings $settings, LoginUrlRequest $request): string
|
public function create(Settings $settings, LoginUrlRequest $request): string
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ final class Settings
|
|||||||
return [
|
return [
|
||||||
'DEFAULT_ENABLE' => 'true',
|
'DEFAULT_ENABLE' => 'true',
|
||||||
'MAIL_LOGIN_METHOD' => 'login_url',
|
'MAIL_LOGIN_METHOD' => 'login_url',
|
||||||
|
'MX_LOGIN_MODE' => '1',
|
||||||
'MAIL_LOGIN_TARGET_NAME' => 'mx1',
|
'MAIL_LOGIN_TARGET_NAME' => 'mx1',
|
||||||
'MAIL_LOGIN_TARGET_BASE_URL' => 'https://mx1.mojserwer.pl:2222',
|
'MAIL_LOGIN_TARGET_BASE_URL' => 'https://mx1.mojserwer.pl:2222',
|
||||||
'MAIL_LOGIN_REDIRECT_URL' => '/',
|
'MAIL_LOGIN_REDIRECT_URL' => '/',
|
||||||
@@ -115,6 +116,11 @@ final class Settings
|
|||||||
return $this->getString('MAIL_LOGIN_METHOD', 'login_url');
|
return $this->getString('MAIL_LOGIN_METHOD', 'login_url');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function mxLoginMode(): int
|
||||||
|
{
|
||||||
|
return (int)$this->getString('MX_LOGIN_MODE', '1');
|
||||||
|
}
|
||||||
|
|
||||||
public function redirectUrl(): string
|
public function redirectUrl(): string
|
||||||
{
|
{
|
||||||
return $this->getString('MAIL_LOGIN_REDIRECT_URL', '/');
|
return $this->getString('MAIL_LOGIN_REDIRECT_URL', '/');
|
||||||
@@ -275,6 +281,9 @@ final class Settings
|
|||||||
if ($this->method() !== 'login_url') {
|
if ($this->method() !== 'login_url') {
|
||||||
throw new InvalidArgumentException('MAIL_LOGIN_METHOD must be 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)) {
|
if (!in_array($this->clientIpMode(), ['direct', 'trusted_proxy'], true)) {
|
||||||
throw new InvalidArgumentException('MAIL_LOGIN_CLIENT_IP_MODE must be direct or trusted_proxy');
|
throw new InvalidArgumentException('MAIL_LOGIN_CLIENT_IP_MODE must be direct or trusted_proxy');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ DEFAULT_ENABLE=true
|
|||||||
# Metoda produkcyjna. Wersja 1 używa jednorazowego DirectAdmin login URL generowanego na MX.
|
# Metoda produkcyjna. Wersja 1 używa jednorazowego DirectAdmin login URL generowanego na MX.
|
||||||
MAIL_LOGIN_METHOD=login_url
|
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.
|
# Nazwa i adres docelowego panelu poczty.
|
||||||
MAIL_LOGIN_TARGET_NAME=mx1
|
MAIL_LOGIN_TARGET_NAME=mx1
|
||||||
MAIL_LOGIN_TARGET_BASE_URL=https://mx1.mojserwer.pl:2222
|
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_CLIENT_IP_MODE=direct
|
||||||
MAIL_LOGIN_TRUSTED_PROXIES=
|
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_REMOTE_TRANSPORT=ssh
|
||||||
MAIL_LOGIN_SSH_HOST=mx1.mojserwer.pl
|
MAIL_LOGIN_SSH_HOST=mx1.mojserwer.pl
|
||||||
MAIL_LOGIN_SSH_PORT=22
|
MAIL_LOGIN_SSH_PORT=22
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ name=mail-login
|
|||||||
id=mail-login
|
id=mail-login
|
||||||
type=user
|
type=user
|
||||||
author=HITME.PL
|
author=HITME.PL
|
||||||
version=1.0.1
|
version=1.0.2
|
||||||
active=no
|
active=no
|
||||||
installed=no
|
installed=no
|
||||||
user_run_as=root
|
user_run_as=root
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ test('plugin metadata and packaging docs match project rules', function (): void
|
|||||||
assert_contains('name=mail-login', $conf);
|
assert_contains('name=mail-login', $conf);
|
||||||
assert_contains('id=mail-login', $conf);
|
assert_contains('id=mail-login', $conf);
|
||||||
assert_contains('type=user', $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('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/src', $packing);
|
||||||
assert_contains('/Users/marek/GPT/ChatGPT/da_plugins/mail-login/archives/X.Y.Z/mail-login.tar.gz', $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);
|
assert_same([], $bad);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
require_once PLUGIN_ROOT . '/exec/lib/Settings.php';
|
require_once PLUGIN_ROOT . '/exec/lib/Settings.php';
|
||||||
require_once PLUGIN_ROOT . '/exec/lib/RemoteLoginUrlClient.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([
|
$settings = Settings::fromArray([
|
||||||
'MAIL_LOGIN_TARGET_BASE_URL' => 'https://mx1.domena.pl:2222',
|
'MAIL_LOGIN_TARGET_BASE_URL' => 'https://mx1.domena.pl:2222',
|
||||||
'MAIL_LOGIN_SSH_HOST' => 'mx1.domena.pl',
|
'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_same('ssh', $command[0]);
|
||||||
assert_contains('UserKnownHostsFile=/secure/known_hosts', implode(' ', $command));
|
assert_contains('UserKnownHostsFile=/secure/known_hosts', implode(' ', $command));
|
||||||
assert_contains('root@mx1.domena.pl', 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);
|
$pos = array_search('mail-login-create-url', $command, true);
|
||||||
assert_true(is_int($pos), 'Command marker missing');
|
assert_true(is_int($pos), 'Command marker missing');
|
||||||
assert_same('mxtest', $command[$pos + 1]);
|
assert_same('mxtest', $command[$pos + 1]);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ test('settings expose secure defaults from plugin-settings.conf', function (): v
|
|||||||
assert_true($settings->userIpBound());
|
assert_true($settings->userIpBound());
|
||||||
assert_true($settings->defaultEnable());
|
assert_true($settings->defaultEnable());
|
||||||
assert_same('login_url', $settings->method());
|
assert_same('login_url', $settings->method());
|
||||||
|
assert_same(1, $settings->mxLoginMode());
|
||||||
assert_same('root', $settings->sshUser());
|
assert_same('root', $settings->sshUser());
|
||||||
assert_same('https://mx1.mojserwer.pl:2222', $settings->targetBaseUrl());
|
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([
|
$settings = Settings::fromArray([
|
||||||
'LOGIN_KEY_TTL' => '5',
|
'LOGIN_KEY_TTL' => '5',
|
||||||
'USER_IP_BOUND' => '0',
|
'USER_IP_BOUND' => '0',
|
||||||
|
'MX_LOGIN_MODE' => '2',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert_same(5, $settings->loginKeyTtl());
|
assert_same(5, $settings->loginKeyTtl());
|
||||||
assert_false($settings->userIpBound());
|
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 {
|
test('settings parse allowed source hosts as normalized hostnames', function (): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user