124 lines
4.6 KiB
PHP
124 lines
4.6 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once PLUGIN_ROOT . '/exec/lib/Settings.php';
|
|
require_once PLUGIN_ROOT . '/exec/lib/DirectAdminContext.php';
|
|
require_once PLUGIN_ROOT . '/exec/lib/ClientIp.php';
|
|
require_once PLUGIN_ROOT . '/exec/lib/KeyName.php';
|
|
require_once PLUGIN_ROOT . '/exec/lib/SourceAuthorizer.php';
|
|
|
|
test('directadmin context derives server name from first host label', function (): void {
|
|
$settings = Settings::fromArray(['MAIL_LOGIN_SOURCE_SERVER_NAME' => '']);
|
|
$ctx = DirectAdminContext::fromServer([
|
|
'USERNAME' => 'mxtest',
|
|
'HTTP_HOST' => 'https://h4.domena.pl:2222',
|
|
'SERVER_NAME' => 'h4.domena.pl',
|
|
'REMOTE_ADDR' => '198.51.100.10',
|
|
'REQUEST_METHOD' => 'GET',
|
|
], $settings);
|
|
|
|
assert_same('mxtest', $ctx->username());
|
|
assert_same('h4.domena.pl', $ctx->sourceHost());
|
|
assert_same('h4', $ctx->serverName());
|
|
});
|
|
|
|
test('directadmin context prefers trusted configured source server name over host header', function (): void {
|
|
$settings = Settings::fromArray(['MAIL_LOGIN_SOURCE_SERVER_NAME' => 'h4']);
|
|
$ctx = DirectAdminContext::fromServer([
|
|
'USERNAME' => 'mxtest',
|
|
'HTTP_HOST' => 'evil.example.net:2222',
|
|
'SERVER_NAME' => 'h4.domena.pl',
|
|
'REMOTE_ADDR' => '198.51.100.10',
|
|
'REQUEST_METHOD' => 'GET',
|
|
], $settings);
|
|
|
|
assert_same('h4.domena.pl', $ctx->sourceHost());
|
|
assert_same('h4', $ctx->serverName());
|
|
});
|
|
|
|
test('directadmin context fails closed without directadmin username', function (): void {
|
|
try {
|
|
DirectAdminContext::fromServer([
|
|
'USER' => 'root',
|
|
'HTTP_HOST' => 'h4.domena.pl:2222',
|
|
'REMOTE_ADDR' => '198.51.100.10',
|
|
'REQUEST_METHOD' => 'GET',
|
|
], Settings::fromArray([]));
|
|
throw new RuntimeException('Expected missing USERNAME to fail');
|
|
} catch (InvalidArgumentException $e) {
|
|
assert_contains('username', strtolower($e->getMessage()));
|
|
}
|
|
});
|
|
|
|
test('directadmin context rejects invalid usernames', function (): void {
|
|
try {
|
|
DirectAdminContext::fromServer([
|
|
'USERNAME' => '../../root',
|
|
'HTTP_HOST' => 'h4.domena.pl:2222',
|
|
'REMOTE_ADDR' => '198.51.100.10',
|
|
'REQUEST_METHOD' => 'GET',
|
|
], Settings::fromArray([]));
|
|
throw new RuntimeException('Expected invalid username to fail');
|
|
} catch (InvalidArgumentException $e) {
|
|
assert_contains('username', strtolower($e->getMessage()));
|
|
}
|
|
});
|
|
|
|
test('client ip resolves direct remote address by default', function (): void {
|
|
$settings = Settings::fromArray(['MAIL_LOGIN_CLIENT_IP_MODE' => 'direct']);
|
|
$ip = ClientIp::resolve([
|
|
'REMOTE_ADDR' => '198.51.100.10',
|
|
'HTTP_X_FORWARDED_FOR' => '203.0.113.20',
|
|
], $settings);
|
|
|
|
assert_same('198.51.100.10', $ip);
|
|
});
|
|
|
|
test('client ip honors forwarded header only from trusted proxy', function (): void {
|
|
$settings = Settings::fromArray([
|
|
'MAIL_LOGIN_CLIENT_IP_MODE' => 'trusted_proxy',
|
|
'MAIL_LOGIN_TRUSTED_PROXIES' => '10.0.0.1',
|
|
]);
|
|
$ip = ClientIp::resolve([
|
|
'REMOTE_ADDR' => '10.0.0.1',
|
|
'HTTP_X_FORWARDED_FOR' => '203.0.113.20, 10.0.0.1',
|
|
], $settings);
|
|
|
|
assert_same('203.0.113.20', $ip);
|
|
});
|
|
|
|
test('client ip supports trusted proxy cidr ranges', function (): void {
|
|
$settings = Settings::fromArray([
|
|
'MAIL_LOGIN_CLIENT_IP_MODE' => 'trusted_proxy',
|
|
'MAIL_LOGIN_TRUSTED_PROXIES' => '10.0.0.0/24',
|
|
]);
|
|
$ip = ClientIp::resolve([
|
|
'REMOTE_ADDR' => '10.0.0.25',
|
|
'HTTP_X_FORWARDED_FOR' => '203.0.113.20',
|
|
], $settings);
|
|
|
|
assert_same('203.0.113.20', $ip);
|
|
});
|
|
|
|
test('source authorizer rejects hosts outside allowlist', function (): void {
|
|
$settings = Settings::fromArray(['MAIL_LOGIN_ALLOWED_SOURCE_HOSTS' => 'h4.domena.pl']);
|
|
|
|
SourceAuthorizer::assertAllowed('h4.domena.pl', $settings);
|
|
|
|
try {
|
|
SourceAuthorizer::assertAllowed('evil.example.net', $settings);
|
|
throw new RuntimeException('Expected disallowed source host to fail');
|
|
} catch (RuntimeException $e) {
|
|
assert_contains('source host', strtolower($e->getMessage()));
|
|
}
|
|
});
|
|
|
|
test('key name uses requested format and truncates long values deterministically', function (): void {
|
|
assert_same('autologin-h4-mxtest-1782810000', KeyName::build('h4', 'mxtest', 1782810000));
|
|
|
|
$long = KeyName::build(str_repeat('server-', 15), str_repeat('user-', 15), 1782810000);
|
|
assert_true(strlen($long) <= 96, 'Key name must be at most 96 chars');
|
|
assert_true(str_starts_with($long, 'autologin-'));
|
|
assert_true((bool)preg_match('/-[a-f0-9]{10}-1782810000$/', $long), 'Long key should contain hash suffix');
|
|
});
|