-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Added new Forwarded header support for Request::getClientIps #11379
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ | |
*/ | ||
class Request | ||
{ | ||
const HEADER_FORWARDED = 'forwarded'; | ||
const HEADER_CLIENT_IP = 'client_ip'; | ||
const HEADER_CLIENT_HOST = 'client_host'; | ||
const HEADER_CLIENT_PROTO = 'client_proto'; | ||
|
@@ -46,6 +47,9 @@ class Request | |
const METHOD_TRACE = 'TRACE'; | ||
const METHOD_CONNECT = 'CONNECT'; | ||
|
||
/** | ||
* @var string[] | ||
*/ | ||
protected static $trustedProxies = array(); | ||
|
||
/** | ||
|
@@ -62,10 +66,13 @@ class Request | |
* Names for headers that can be trusted when | ||
* using trusted proxies. | ||
* | ||
* The default names are non-standard, but widely used | ||
* The FORWARDED header is the standard as of rfc7239. | ||
* | ||
* The other headers are non-standard, but widely used | ||
* by popular reverse proxies (like Apache mod_proxy or Amazon EC2). | ||
*/ | ||
protected static $trustedHeaders = array( | ||
self::HEADER_FORWARDED => 'FORWARDED', | ||
self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', | ||
self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', | ||
self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', | ||
|
@@ -823,24 +830,26 @@ public function setSession(SessionInterface $session) | |
*/ | ||
public function getClientIps() | ||
{ | ||
$clientIps = array(); | ||
$ip = $this->server->get('REMOTE_ADDR'); | ||
|
||
if (!self::$trustedProxies) { | ||
return array($ip); | ||
} | ||
|
||
if (!self::$trustedHeaders[self::HEADER_CLIENT_IP] || !$this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) { | ||
return array($ip); | ||
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) { | ||
$forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]); | ||
preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches); | ||
$clientIps = $matches[3]; | ||
} elseif (self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) { | ||
$clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP]))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alexandresalome I now check the header is part of the |
||
} | ||
|
||
$clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP]))); | ||
$clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from | ||
|
||
$ip = $clientIps[0]; // Fallback to this when the client IP falls into the range of trusted proxies | ||
|
||
// Eliminate all IPs from the forwarded IP chain which are trusted proxies | ||
foreach ($clientIps as $key => $clientIp) { | ||
// Remove port on IPv4 address (unfortunately, it does happen) | ||
// Remove port (unfortunately, it does happen) | ||
if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) { | ||
$clientIps[$key] = $clientIp = $match[1]; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -875,6 +875,31 @@ public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $tru | |
Request::setTrustedProxies(array()); | ||
} | ||
|
||
/** | ||
* @dataProvider testGetClientIpsForwardedProvider | ||
*/ | ||
public function testGetClientIpsForwarded($expected, $remoteAddr, $httpForwarded, $trustedProxies) | ||
{ | ||
$request = $this->getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies); | ||
|
||
$this->assertEquals($expected, $request->getClientIps()); | ||
|
||
Request::setTrustedProxies(array()); | ||
} | ||
|
||
public function testGetClientIpsForwardedProvider() | ||
{ | ||
// $expected $remoteAddr $httpForwarded $trustedProxies | ||
return array( | ||
array(array('127.0.0.1'), '127.0.0.1', 'for="_gazonk"', null), | ||
array(array('_gazonk'), '127.0.0.1', 'for="_gazonk"', array('127.0.0.1')), | ||
array(array('88.88.88.88'), '127.0.0.1', 'for="88.88.88.88:80"', array('127.0.0.1')), | ||
array(array('192.0.2.60'), '::1', 'for=192.0.2.60;proto=http;by=203.0.113.43', array('::1')), | ||
array(array('2620:0:1cfe:face:b00c::3', '192.0.2.43'), '::1', 'for=192.0.2.43, for=2620:0:1cfe:face:b00c::3', array('::1')), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be worth adding a test case for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also worth adding a test for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've checked and it does not handle the port as is. I'll make the necessary changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Seldaek I can't add a test for ips like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tony-co There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah sorry my initial comment was misleading I forgot that this method is called getClientIp while I was lost reading the spec :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ping @stof Ok I added the test for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess you should strip port numbers before passing them to checkIp, since this is just about getting the IP out it makes no sense to keep port info anyway. |
||
array(array('2001:db8:cafe::17'), '::1', 'for="[2001:db8:cafe::17]:4711', array('::1')), | ||
); | ||
} | ||
|
||
public function testGetClientIpsProvider() | ||
{ | ||
// $expected $remoteAddr $httpForwardedFor $trustedProxies | ||
|
@@ -1467,6 +1492,25 @@ private function getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedF | |
return $request; | ||
} | ||
|
||
private function getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies) | ||
{ | ||
$request = new Request(); | ||
|
||
$server = array('REMOTE_ADDR' => $remoteAddr); | ||
|
||
if (null !== $httpForwarded) { | ||
$server['HTTP_FORWARDED'] = $httpForwarded; | ||
} | ||
|
||
if ($trustedProxies) { | ||
Request::setTrustedProxies($trustedProxies); | ||
} | ||
|
||
$request->initialize(array(), array(), array(), array(), array(), $server); | ||
|
||
return $request; | ||
} | ||
|
||
public function testTrustedProxies() | ||
{ | ||
$request = Request::create('http://example.com/'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alexandresalome Concerning your previous comment, I am still checking the trusted headers here. Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your first test is to verify if headers contains Forwarded, but you should only trust it if the IP is a Proxy IP.