diff --git a/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php b/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php index 0ae60453efe6a7efc3dccb9b8ab184eb538004a0..736ffa660f13920facf40a90bfaab44c209c0a8d 100644 --- a/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php +++ b/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php @@ -73,25 +73,35 @@ class ReverseProxyHeaderClientIpRestore implements EventSubscriberInterface { */ public function onRequest(RequestEvent $event): void { - // Check the available settings. + // Checks the available settings. $reverse_proxy_header_name = $this->settings->get('reverse_proxy_header'); if (!$reverse_proxy_header_name) { return; } - // Check the header value. - $current_request = $event->getRequest(); - $connecting_ip = $current_request->server->get($reverse_proxy_header_name); - if ($this->isInvalidIpAddress($connecting_ip, $reverse_proxy_header_name)) { + // Checks the header value. + $connecting_ips = $event->getRequest()->server->get($reverse_proxy_header_name); + if (empty($connecting_ips)) { + $this->logger->notice('Empty value retrieved from @header_name header.', ['@header_name' => $reverse_proxy_header_name]); return; } - // As the changed remote address will make it impossible to determine - // a trusted proxy, we need to make sure we set the right protocol as well. - // @see \Symfony\Component\HttpFoundation\Request::isSecure() - $event->getRequest()->server->set('HTTPS', $event->getRequest()->isSecure() ? 'on' : 'off'); - $event->getRequest()->server->set('REMOTE_ADDR', $connecting_ip); - $event->getRequest()->overrideGlobals(); + // Extracts the first valid IP address from the header. + $connecting_ips = array_map('trim', explode(',', $connecting_ips)); + foreach ($connecting_ips as $connecting_ip) { + if (!$this->isInvalidIpAddress($connecting_ip, $reverse_proxy_header_name)) { + + // As the changed remote address will make it impossible to determine + // a trusted proxy, we need to make sure we set the right protocol. + // @see \Symfony\Component\HttpFoundation\Request::isSecure() + $event->getRequest()->server->set('HTTPS', $event->getRequest()->isSecure() ? 'on' : 'off'); + $event->getRequest()->server->set('REMOTE_ADDR', $connecting_ip); + $event->getRequest()->overrideGlobals(); + return; + } + } + + $this->logger->notice('No valid IP address found in the @header_name header.', ['@header_name' => $reverse_proxy_header_name]); } /** @@ -115,7 +125,7 @@ class ReverseProxyHeaderClientIpRestore implements EventSubscriberInterface { ]; if (empty($ip_address)) { - $this->logger->notice('Empty IP address value retrieved from @header_name header is invalid.', $variables); + $this->logger->notice('Empty IP address value retrieved from @header_name header.', $variables); return TRUE; } diff --git a/tests/src/Kernel/ClientIpRestoreTest.php b/tests/src/Kernel/ClientIpRestoreTest.php index 9d40e2db09b1051a6b59604c697a24fa7df730f2..891bf83873e7508a56467bb62d41535d9a82ad85 100644 --- a/tests/src/Kernel/ClientIpRestoreTest.php +++ b/tests/src/Kernel/ClientIpRestoreTest.php @@ -14,19 +14,19 @@ class ClientIpRestoreTest extends ReverseProxyHeaderTestBase { * * @param string $header_name * The header name. + * @param string $header_value + * The header value. * @param string $valid_ip - * The valid client IP address. - * @param string $invalid_ip - * The invalid client IP address. + * The valid expected client IP address. * - * @dataProvider providerClientIpsHeaders + * @dataProvider providerClientValidIps */ public function testValidIps( string $header_name, + string $header_value, string $valid_ip, - string $invalid_ip, ): void { - $request = $this->reverseProxyHeaderRequest($header_name, $valid_ip); + $request = $this->reverseProxyHeaderRequest($header_name, $header_value); $this->assertEquals($request->getClientIp(), $valid_ip); } @@ -35,50 +35,70 @@ class ClientIpRestoreTest extends ReverseProxyHeaderTestBase { * * @param string $header_name * The header name. - * @param string $valid_ip - * The valid client IP address. - * @param string $invalid_ip - * The invalid client IP address. + * @param string $header_value + * The header value. * - * @dataProvider providerClientIpsHeaders + * @dataProvider providerClientInvalidIps */ public function testInvalidIps( string $header_name, - string $valid_ip, - string $invalid_ip, + string $header_value, ): void { - $this->reverseProxyHeaderRequest($header_name, $invalid_ip); - $this->assertInvalidIpNoticeExist($invalid_ip, $header_name); + $this->reverseProxyHeaderRequest($header_name, $header_value); + $this->assertInvalidIpNoticeExist($header_name); } /** - * Provider for testing ClientIpRestoreTest. + * Provider for testing ClientIpRestoreTest::testValidIps. * * @return array * Test Data to simulate incoming IP address. */ - public static function providerClientIpsHeaders(): array { + public static function providerClientValidIps(): array { return [ [ // It should work well with default headers as well. - 'HEADER_X_FORWARDED_FOR', - '127.0.0.1', - '', + 'header_name' => 'HEADER_X_FORWARDED_FOR', + 'header_value' => '127.0.0.1', + 'valid_ip' => '127.0.0.1', ], [ - 'CUSTOM_HEADER_2', - '10.0.10.10', - '', + 'header_name' => 'HEADER_X_CUSTOM_HEADER_NAME', + 'header_value' => '8.8.8.8', + 'valid_ip' => '8.8.8.8', + ], + [ + 'header_name' => 'HEADER_WITH_ONE_INVALID_IP', + 'header_value' => '127.0.0.1,not-an-ip,127.0.0.2', + 'valid_ip' => '127.0.0.1', + ], + [ + 'header_name' => 'HEADER_WITH_INVALID_IP_FIRST', + 'header_value' => 'not-an-ip,10.0.10.10', + 'valid_ip' => '10.0.10.10', + ], + ]; + } + + /** + * Provider for testing ClientIpRestoreTest::testInvalidIps. + * + * @return array + * Test Data to simulate incoming IP address. + */ + public static function providerClientInvalidIps(): array { + return [ + [ + 'header_name' => 'HEADER_X_FORWARDED_FOR', + 'header_value' => '444.555.666.777', ], [ - 'HEADER_CUSTOM_HEADER_3', - '1.1.1.1', - '444.555.666.777', + 'header_name' => 'HEADER_X_CUSTOM_HEADER_2', + 'header_value' => 'this_is_a_text_not_ip', ], [ - 'HEADER_X_CUSTOM_HEADER_4', - '8.8.8.8', - 'this_is_a_text_not_ip', + 'header_name' => 'HEADER_WITH_ALL_INVALID_IPS', + 'header_value' => 'some-value,444.555.666.777', ], ]; } diff --git a/tests/src/Kernel/ReverseProxyHeaderTestBase.php b/tests/src/Kernel/ReverseProxyHeaderTestBase.php index 47036f1b51c3d97c8b4f6c88399132be63487a5d..609a29be12c9822269d9daa448e8a18f061beeeb 100644 --- a/tests/src/Kernel/ReverseProxyHeaderTestBase.php +++ b/tests/src/Kernel/ReverseProxyHeaderTestBase.php @@ -77,30 +77,16 @@ abstract class ReverseProxyHeaderTestBase extends KernelTestBase { } /** - * Check if exist notice for current IP address. + * Checks if the notice exist for current invalid IP address. * - * @param string $ip_address - * IP address. * @param string $header_name * The header name. */ - protected function assertInvalidIpNoticeExist( - string $ip_address, - string $header_name, - ): void { - // Prepare the message and its arguments for testing. - if (empty($ip_address)) { - $message = 'Empty IP address value retrieved from @header_name header is invalid.'; - } - else { - $message = 'IP address `@ip_address` retrieved from @header_name header is invalid.'; - } - $arguments = [ - '@ip_address' => $ip_address, - '@header_name' => $header_name, - ]; - $parser = new LogMessageParser(); - $variables = $parser->parseMessagePlaceholders($message, $arguments); + protected function assertInvalidIpNoticeExist(string $header_name): void { + // Prepares the message and its arguments for testing. + $message = 'No valid IP address found in the @header_name header.'; + $arguments = ['@header_name' => $header_name]; + $variables = (new LogMessageParser())->parseMessagePlaceholders($message, $arguments); // Select the expected notice from the database. $query = Database::getConnection()->select('watchdog');