Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,6 @@

final class TrustedHeaderClientIpEventSubscriber implements EventSubscriberInterface
Copy link
Contributor Author

@vidarl vidarl Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final class TrustedHeaderClientIpEventSubscriber implements EventSubscriberInterface
final class IbexaCloudTrustedProxiesEventSubscriber implements EventSubscriberInterface

Or this is considered a BC ? Class is final though, so maybe not a problem?

{
private const PLATFORM_SH_TRUSTED_HEADER_CLIENT_IP = 'X-Client-IP';

private ?string $trustedHeaderName;

public function __construct(
?string $trustedHeaderName
) {
$this->trustedHeaderName = $trustedHeaderName;
}

public static function getSubscribedEvents(): array
{
return [
Expand All @@ -36,28 +26,9 @@ public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();

$trustedProxies = Request::getTrustedProxies();
$trustedHeaderSet = Request::getTrustedHeaderSet();

$trustedHeaderName = $this->trustedHeaderName;
if (null === $trustedHeaderName && $this->isPlatformShProxy($request)) {
$trustedHeaderName = self::PLATFORM_SH_TRUSTED_HEADER_CLIENT_IP;
if ($this->isPlatformShProxy($request) && $request->headers->get('Client-Cdn') === 'fastly') {
Request::setTrustedProxies(['REMOTE_ADDR'], Request::getTrustedHeaderSet());
}

if (null === $trustedHeaderName) {
return;
}

$trustedClientIp = $request->headers->get($trustedHeaderName);

if (null !== $trustedClientIp) {
if ($trustedHeaderSet !== -1) {
$trustedHeaderSet |= Request::HEADER_X_FORWARDED_FOR;
}
$request->headers->set('X_FORWARDED_FOR', $trustedClientIp);
}

Request::setTrustedProxies($trustedProxies, $trustedHeaderSet);
}

private function isPlatformShProxy(Request $request): bool
Expand Down
2 changes: 0 additions & 2 deletions src/bundle/Core/Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,6 @@ services:
$cache: '@ibexa.cache_pool'

Ibexa\Bundle\Core\EventSubscriber\TrustedHeaderClientIpEventSubscriber:
arguments:
$trustedHeaderName: '%ibexa.trusted_header_client_ip_name%'
tags:
- {name: kernel.event_subscriber}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,10 @@

final class TrustedHeaderClientIpEventSubscriberTest extends TestCase
{
private const PLATFORM_SH_TRUSTED_HEADER_CLIENT_IP = 'X-Client-IP';

private ?string $originalRemoteAddr;

private const PROXY_IP = '127.100.100.1';

private const REAL_CLIENT_IP = '98.76.123.234';

private const CUSTOM_CLIENT_IP = '234.123.78.98';

/**
* @param array<mixed> $data
*/
Expand All @@ -53,114 +47,47 @@ protected function tearDown(): void
public function getTrustedHeaderEventSubscriberTestData(): array
{
return [
'default behaviour' => [
self::REAL_CLIENT_IP,
self::REAL_CLIENT_IP,
],
'use custom header name with valid value' => [
self::REAL_CLIENT_IP,
self::PROXY_IP,
'X-Custom-Header',
['X-Custom-Header' => self::REAL_CLIENT_IP],
'request from random client received on non-Upsun platform' => [
false,
[],
[],
],
'use custom header name without valid value' => [
self::PROXY_IP,
self::PROXY_IP,
'X-Custom-Header',
'request from random client, forging Client-Cdn received on non-Upsun platform' => [
false,
['Client-Cdn' => 'fastly'],
[],
],
'use custom header value without custom header name' => [
self::PROXY_IP,
self::PROXY_IP,
null,
['X-Custom-Header' => self::REAL_CLIENT_IP],
],
'default platform.sh behaviour' => [
self::REAL_CLIENT_IP,
self::PROXY_IP,
null,
['X-Client-IP' => self::REAL_CLIENT_IP],
['PLATFORM_RELATIONSHIPS' => true],
],
'use custom header name without valid value on platform.sh' => [
self::PROXY_IP,
self::PROXY_IP,
'X-Custom-Header',
[self::PLATFORM_SH_TRUSTED_HEADER_CLIENT_IP => self::REAL_CLIENT_IP],
'request from random client received on Upsun platform' => [
false,
[],
['PLATFORM_RELATIONSHIPS' => true],
],
'use custom header with valid value on platform.sh' => [
self::CUSTOM_CLIENT_IP,
self::PROXY_IP,
'X-Custom-Header',
[
self::PLATFORM_SH_TRUSTED_HEADER_CLIENT_IP => self::REAL_CLIENT_IP,
'X-Custom-Header' => self::CUSTOM_CLIENT_IP,
],
['PLATFORM_RELATIONSHIPS' => true],
],
'use valid value without custom header name on platform.sh' => [
self::REAL_CLIENT_IP,
self::PROXY_IP,
null,
[
self::PLATFORM_SH_TRUSTED_HEADER_CLIENT_IP => self::REAL_CLIENT_IP,
'X-Custom-Header' => self::CUSTOM_CLIENT_IP,
],
'request via Fastly received on Upsun platform' => [
true,
['Client-Cdn' => 'fastly'],
['PLATFORM_RELATIONSHIPS' => true],
],
];
}

public function testTrustedHeaderEventSubscriberWithoutTrustedProxy(): void
{
$_SERVER['REMOTE_ADDR'] = self::PROXY_IP;

$eventDispatcher = new EventDispatcher();
$eventDispatcher->addSubscriber(
new TrustedHeaderClientIpEventSubscriber('X-Custom-Header')
);

$request = Request::create('/', 'GET', [], [], [], array_merge(
$_SERVER,
['PLATFORM_RELATIONSHIPS' => true],
));
$request->headers->add([
'X-Custom-Header' => self::REAL_CLIENT_IP,
]);

$event = $eventDispatcher->dispatch(new RequestEvent(
self::createMock(KernelInterface::class),
$request,
HttpKernelInterface::MAIN_REQUEST
), KernelEvents::REQUEST);

/** @var \Symfony\Component\HttpFoundation\Request $request */
$request = $event->getRequest();

self::assertEquals(self::PROXY_IP, $request->getClientIp());
}

/**
* @dataProvider getTrustedHeaderEventSubscriberTestData
*/
public function testTrustedHeaderEventSubscriberWithTrustedProxy(
string $expectedIp,
string $remoteAddrIp,
?string $trustedHeaderName = null,
bool $isFromTrustedProxy,
array $headers = [],
array $server = []
): void {
$_SERVER['REMOTE_ADDR'] = $remoteAddrIp;
Request::setTrustedProxies(['REMOTE_ADDR'], Request::getTrustedHeaderSet());
$_SERVER['REMOTE_ADDR'] = self::REAL_CLIENT_IP;

$eventDispatcher = new EventDispatcher();
$eventDispatcher->addSubscriber(
new TrustedHeaderClientIpEventSubscriber($trustedHeaderName)
new TrustedHeaderClientIpEventSubscriber()
);

$request = Request::create('/', 'GET', [], [], [], array_merge(
$server,
['REMOTE_ADDR' => $remoteAddrIp],
['REMOTE_ADDR' => self::REAL_CLIENT_IP],
));
$request->headers->add($headers);

Expand All @@ -173,6 +100,6 @@ public function testTrustedHeaderEventSubscriberWithTrustedProxy(
/** @var \Symfony\Component\HttpFoundation\Request $request */
$request = $event->getRequest();

self::assertEquals($expectedIp, $request->getClientIp());
self::assertEquals($isFromTrustedProxy, $request->isFromTrustedProxy());
}
}
Loading