-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
IPs can be configured to bypass signed queries #9696
The configuration `allowedIps` supports a list of IP or subnet in v4 or v6. If the request is coming from one of those ranges, and it is not signed at all, then it will be accepted. However, if the request is signed, the `allowedIps` has no effect at all. This is because we want to be able to test signed queries even from an allowed IP.
- Loading branch information
Showing
6 changed files
with
207 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Ecodev\Felix\Validator; | ||
|
||
abstract class IPRange | ||
{ | ||
final public static function matches(string $ip, string $cidr): bool | ||
{ | ||
if (str_contains($ip, ':')) { | ||
return self::matchesIPv6($ip, $cidr); | ||
} | ||
|
||
return self::matchesIPv4($ip, $cidr); | ||
} | ||
|
||
private static function matchesIPv4(string $ip, string $cidr): bool | ||
{ | ||
$mask = 32; | ||
$subnet = $cidr; | ||
|
||
if (str_contains($cidr, '/')) { | ||
[$subnet, $mask] = explode('/', $cidr, 2); | ||
$mask = (int) $mask; | ||
} | ||
|
||
if ($mask < 0 || $mask > 32) { | ||
return false; | ||
} | ||
|
||
$ip = ip2long($ip); | ||
$subnet = ip2long($subnet); | ||
if (false === $ip || false === $subnet) { | ||
// Invalid data | ||
return false; | ||
} | ||
|
||
return 0 === substr_compare( | ||
sprintf('%032b', $ip), | ||
sprintf('%032b', $subnet), | ||
0, | ||
$mask | ||
); | ||
} | ||
|
||
private static function matchesIPv6(string $ip, string $cidr): bool | ||
{ | ||
$mask = 128; | ||
$subnet = $cidr; | ||
|
||
if (str_contains($cidr, '/')) { | ||
[$subnet, $mask] = explode('/', $cidr, 2); | ||
$mask = (int) $mask; | ||
} | ||
|
||
if ($mask < 0 || $mask > 128) { | ||
return false; | ||
} | ||
|
||
$ip = inet_pton($ip); | ||
$subnet = inet_pton($subnet); | ||
|
||
if (false === $ip || false === $subnet) { | ||
// Invalid data | ||
return false; | ||
} | ||
|
||
// mask 0: if it's a valid IP, it's valid | ||
if ($mask === 0) { | ||
return (bool) unpack('n*', $ip); | ||
} | ||
|
||
/** @see http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet, MW answer */ | ||
$binMask = str_repeat('f', (int) ($mask / 4)); | ||
switch ($mask % 4) { | ||
case 0: | ||
break; | ||
case 1: | ||
$binMask .= '8'; | ||
|
||
break; | ||
case 2: | ||
$binMask .= 'c'; | ||
|
||
break; | ||
case 3: | ||
$binMask .= 'e'; | ||
|
||
break; | ||
} | ||
|
||
$binMask = str_pad($binMask, 32, '0'); | ||
$binMask = pack('H*', $binMask); | ||
|
||
return ($ip & $binMask) === $subnet; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace EcodevTests\Felix\Validator; | ||
|
||
use Ecodev\Felix\Validator\IPRange; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class IPRangeTest extends TestCase | ||
{ | ||
/** | ||
* @dataProvider IPv4Data | ||
* @dataProvider IPv6Data | ||
*/ | ||
public function testMatches(bool $result, string $remoteAddr, string $cidr): void | ||
{ | ||
self::assertSame($result, IPRange::matches($remoteAddr, $cidr)); | ||
} | ||
|
||
public function IPv4Data(): iterable | ||
{ | ||
return [ | ||
'valid - exact (no mask; /32 equiv)' => [true, '192.168.1.1', '192.168.1.1'], | ||
'valid - entirety of class-c (/1)' => [true, '192.168.1.1', '192.168.1.1/1'], | ||
'valid - class-c private subnet (/24)' => [true, '192.168.1.1', '192.168.1.0/24'], | ||
'valid - any subnet (/0)' => [true, '1.2.3.4', '0.0.0.0/0'], | ||
'valid - subnet expands to all' => [true, '1.2.3.4', '192.168.1.0/0'], | ||
'invalid - class-a invalid subnet' => [false, '192.168.1.1', '1.2.3.4/1'], | ||
'invalid - CIDR mask out-of-range' => [false, '192.168.1.1', '192.168.1.1/33'], | ||
'invalid - invalid cidr notation' => [false, '1.2.3.4', '256.256.256/0'], | ||
'invalid - invalid IP address' => [false, 'an_invalid_ip', '192.168.1.0/24'], | ||
'invalid - empty IP address' => [false, '', '1.2.3.4/1'], | ||
'invalid - proxy wildcard' => [false, '192.168.20.13', '*'], | ||
'invalid - proxy missing netmask' => [false, '192.168.20.13', '0.0.0.0'], | ||
'invalid - request IP with invalid proxy wildcard' => [false, '0.0.0.0', '*'], | ||
]; | ||
} | ||
|
||
public function IPv6Data(): iterable | ||
{ | ||
return [ | ||
'valid - ipv4 subnet' => [true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'], | ||
'valid - exact' => [true, '0:0:0:0:0:0:0:1', '::1'], | ||
'valid - all subnets' => [true, '0:0:603:0:396e:4789:8e99:0001', '::/0'], | ||
'valid - subnet expands to all' => [true, '0:0:603:0:396e:4789:8e99:0001', '2a01:198:603:0::/0'], | ||
'invalid - not in subnet' => [false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'], | ||
'invalid - does not match exact' => [false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'], | ||
'invalid - compressed notation, does not match exact' => [false, '0:0:603:0:396e:4789:8e99:0001', '::1'], | ||
'invalid - garbage IP' => [false, '}__test|O:21:"JDatabaseDriverMysqli":3:{s:2', '::1'], | ||
'invalid - invalid cidr' => [false, '2a01:198:603:0:396e:4789:8e99:890f', 'unknown'], | ||
'invalid - empty IP address' => [false, '', '::1'], | ||
]; | ||
} | ||
} |