From 9e62330bc45bae92d9046edc75e0d78e25a73fe3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 26 Jun 2019 16:20:24 +0200 Subject: [PATCH] [HttpFoundation] Add a way to anonymize IPs --- .../Component/HttpFoundation/CHANGELOG.md | 3 +- .../Component/HttpFoundation/IpUtils.php | 32 +++++++++++++++++++ .../HttpFoundation/Tests/IpUtilsTest.php | 26 +++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 10eaa0ac5a..e33b33ccbe 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -11,7 +11,8 @@ CHANGELOG make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database to speed up garbage collection of expired sessions. * added `SessionHandlerFactory` to create session handlers with a DSN - + * added `IpUtils::anonymize()` to help with GDPR compliance. + 4.3.0 ----- diff --git a/src/Symfony/Component/HttpFoundation/IpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php index 67d13e57aa..72c53a4710 100644 --- a/src/Symfony/Component/HttpFoundation/IpUtils.php +++ b/src/Symfony/Component/HttpFoundation/IpUtils.php @@ -153,4 +153,36 @@ class IpUtils return self::$checkedIps[$cacheKey] = true; } + + /** + * Anonymizes an IP/IPv6. + * + * Removes the last byte for v4 and the last 8 bytes for v6 IPs + */ + public static function anonymize(string $ip): string + { + $wrappedIPv6 = false; + if ('[' === substr($ip, 0, 1) && ']' === substr($ip, -1, 1)) { + $wrappedIPv6 = true; + $ip = substr($ip, 1, -1); + } + + $packedAddress = inet_pton($ip); + if (4 === \strlen($packedAddress)) { + $mask = '255.255.255.0'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff:ffff'))) { + $mask = '::ffff:ffff:ff00'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff'))) { + $mask = '::ffff:ff00'; + } else { + $mask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; + } + $ip = inet_ntop($packedAddress & inet_pton($mask)); + + if ($wrappedIPv6) { + $ip = '['.$ip.']'; + } + + return $ip; + } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php index d3b262e048..13b5743794 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php @@ -101,4 +101,30 @@ class IpUtilsTest extends TestCase 'invalid request IP with invalid proxy wildcard' => ['0.0.0.0', '*'], ]; } + + /** + * @dataProvider anonymizedIpData + */ + public function testAnonymize($ip, $expected) + { + $this->assertSame($expected, IpUtils::anonymize($ip)); + } + + public function anonymizedIpData() + { + return [ + ['192.168.1.1', '192.168.1.0'], + ['1.2.3.4', '1.2.3.0'], + ['2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603::'], + ['2a01:198:603:10:396e:4789:8e99:890f', '2a01:198:603:10::'], + ['::1', '::'], + ['0:0:0:0:0:0:0:1', '::'], + ['1:0:0:0:0:0:0:1', '1::'], + ['0:0:603:50:396e:4789:8e99:0001', '0:0:603:50::'], + ['[0:0:603:50:396e:4789:8e99:0001]', '[0:0:603:50::]'], + ['[2a01:198::3]', '[2a01:198::]'], + ['::ffff:123.234.235.236', '::ffff:123.234.235.0'], // IPv4-mapped IPv6 addresses + ['::123.234.235.236', '::123.234.235.0'], // deprecated IPv4-compatible IPv6 address + ]; + } }