From 7a7204a476bdc761a115e0d597815bc18e2e507c Mon Sep 17 00:00:00 2001
From: Hari Venu V <hari.venu@qed42.com>
Date: Thu, 9 Jan 2025 16:11:22 +0530
Subject: [PATCH 1/4] Issue #3425451: Multiple IPs returning, split and use 1st
 one

---
 .../ReverseProxyHeaderClientIpRestore.php     | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php b/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php
index f521745..6560a6d 100644
--- a/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php
+++ b/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php
@@ -55,7 +55,8 @@ class ReverseProxyHeaderClientIpRestore implements EventSubscriberInterface {
    * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
-    // Priority is set to 350 (to run before RouterListener::onKernelRequest).
+    // Priority is set to 350, so it runs
+    // before RouterListener::onKernelRequest.
     // Which allows AuthenticationSubscriber methods to use the request IP.
     // @see \Symfony\Component\HttpKernel\EventListener\RouterListener::getSubscribedEvents
     // @see \Drupal\Core\EventSubscriber\AuthenticationSubscriber::getSubscribedEvents
@@ -78,7 +79,21 @@ class ReverseProxyHeaderClientIpRestore implements EventSubscriberInterface {
     // 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)) {
+
+    // Split and extract the first IP address.
+    $connecting_ips = array_map('trim', explode(',', $connecting_ip));
+    if (count($connecting_ips) > 1) {
+      foreach ($connecting_ips as $ip) {
+        if ($this->isInvalidIpAddress($connecting_ip, $reverse_proxy_header_name)) {
+          continue;
+        }
+        $connecting_ip = $ip;
+        break;
+      }
+    }
+
+    if (empty($connecting_ip)) {
+      $this->logger->notice('No valid IP address found in the @header_name header.', ['@header_name' => $reverse_proxy_header_name]);
       return;
     }
 
-- 
GitLab


From 557aa95ab53a32654253697727c1848632b6f7d1 Mon Sep 17 00:00:00 2001
From: Bohdan Artemchuk <5767-bohart@users.noreply.drupalcode.org>
Date: Thu, 9 Jan 2025 22:04:01 +0200
Subject: [PATCH 2/4] Issue #3425451 by bohart: Added tests coverage.

---
 tests/src/Kernel/ClientIpRestoreTest.php      | 76 ++++++++++++-------
 .../src/Kernel/ReverseProxyHeaderTestBase.php | 26 ++-----
 2 files changed, 54 insertions(+), 48 deletions(-)

diff --git a/tests/src/Kernel/ClientIpRestoreTest.php b/tests/src/Kernel/ClientIpRestoreTest.php
index 9d40e2d..5fb9e4a 100644
--- a/tests/src/Kernel/ClientIpRestoreTest.php
+++ b/tests/src/Kernel/ClientIpRestoreTest.php
@@ -14,17 +14,17 @@ 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);
     $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 47036f1..3e61126 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');
-- 
GitLab


From aa2ae20060b59a6c95c36de96f35f79c5c96bb2d Mon Sep 17 00:00:00 2001
From: Bohdan Artemchuk <5767-bohart@users.noreply.drupalcode.org>
Date: Thu, 9 Jan 2025 22:21:23 +0200
Subject: [PATCH 3/4] Issue #3425451 by bohart: Added tests coverage.

---
 .../ReverseProxyHeaderClientIpRestore.php     | 40 +++++++++----------
 tests/src/Kernel/ClientIpRestoreTest.php      |  2 +-
 2 files changed, 19 insertions(+), 23 deletions(-)

diff --git a/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php b/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php
index 9d8e0cd..8d38c3e 100644
--- a/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php
+++ b/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php
@@ -80,32 +80,28 @@ class ReverseProxyHeaderClientIpRestore implements EventSubscriberInterface {
     }
 
     // Checks the header value.
-    $current_request = $event->getRequest();
-    $connecting_ip = $current_request->server->get($reverse_proxy_header_name);
+    $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;
+    }
 
     // Extracts the first valid IP address from the header.
-    $connecting_ips = array_map('trim', explode(',', $connecting_ip));
-    if (count($connecting_ips) > 1) {
-      foreach ($connecting_ips as $ip) {
-        if ($this->isInvalidIpAddress($connecting_ip, $reverse_proxy_header_name)) {
-          continue;
-        }
-        $connecting_ip = $ip;
-        break;
+    $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 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();
+        return;
       }
     }
 
-    if (empty($connecting_ip)) {
-      $this->logger->notice('No valid IP address found in the @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();
+    $this->logger->notice('No valid IP address found in the @header_name header.', ['@header_name' => $reverse_proxy_header_name]);
   }
 
   /**
@@ -129,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 5fb9e4a..891bf83 100644
--- a/tests/src/Kernel/ClientIpRestoreTest.php
+++ b/tests/src/Kernel/ClientIpRestoreTest.php
@@ -26,7 +26,7 @@ class ClientIpRestoreTest extends ReverseProxyHeaderTestBase {
     string $header_value,
     string $valid_ip,
   ): void {
-    $request = $this->reverseProxyHeaderRequest($header_name, $valid_ip);
+    $request = $this->reverseProxyHeaderRequest($header_name, $header_value);
     $this->assertEquals($request->getClientIp(), $valid_ip);
   }
 
-- 
GitLab


From 566b4b8d0c263757608ec43e48550df7cec62e27 Mon Sep 17 00:00:00 2001
From: Bohdan Artemchuk <5767-bohart@users.noreply.drupalcode.org>
Date: Thu, 9 Jan 2025 22:26:07 +0200
Subject: [PATCH 4/4] Issue #3425451 by bohart: Added tests coverage.

---
 src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php | 2 +-
 tests/src/Kernel/ReverseProxyHeaderTestBase.php           | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php b/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php
index 8d38c3e..736ffa6 100644
--- a/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php
+++ b/src/EventSubscriber/ReverseProxyHeaderClientIpRestore.php
@@ -92,7 +92,7 @@ class ReverseProxyHeaderClientIpRestore implements EventSubscriberInterface {
       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 as well.
+        // 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);
diff --git a/tests/src/Kernel/ReverseProxyHeaderTestBase.php b/tests/src/Kernel/ReverseProxyHeaderTestBase.php
index 3e61126..609a29b 100644
--- a/tests/src/Kernel/ReverseProxyHeaderTestBase.php
+++ b/tests/src/Kernel/ReverseProxyHeaderTestBase.php
@@ -84,7 +84,7 @@ abstract class ReverseProxyHeaderTestBase extends KernelTestBase {
    */
   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';
+    $message = 'No valid IP address found in the @header_name header.';
     $arguments = ['@header_name' => $header_name];
     $variables = (new LogMessageParser())->parseMessagePlaceholders($message, $arguments);
 
-- 
GitLab