[FEATURE] Add API to anonymize IP addresses 13/56713/5
authorGeorg Ringer <georg.ringer@gmail.com>
Tue, 27 Feb 2018 04:28:57 +0000 (05:28 +0100)
committerFrank Naegler <frank.naegler@typo3.org>
Thu, 19 Apr 2018 09:54:43 +0000 (11:54 +0200)
Add an API to anonymize IP addresses.
The core uses this API to anonymize all IP addresses before
storing them. E.g. when logging.

This a necessary tool in order to comply with data and privacy
protections laws.

Resolves: #84053
Releases: master, 8.7, 7.6
Change-Id: Id45ee94696dee4fa2293e1226f2076883f6b9ade
Reviewed-on: https://review.typo3.org/56713
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
typo3/sysext/core/Classes/Utility/IpAnonymizationUtility.php [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/7.6.x/Feature-84053-APIToAnonymizeIPAddresses.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Utility/IpAnonymizationUtilityTest.php [new file with mode: 0644]

diff --git a/typo3/sysext/core/Classes/Utility/IpAnonymizationUtility.php b/typo3/sysext/core/Classes/Utility/IpAnonymizationUtility.php
new file mode 100644 (file)
index 0000000..3172753
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Utility;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Anonymize a given IP
+ *
+ * Inspired by https://github.com/geertw/php-ip-anonymizer
+ */
+class IpAnonymizationUtility
+{
+
+    /**
+     * IPv4 netmask used to anonymize IPv4 address.
+     *
+     * 1) Mask host
+     * 2) Mask host and subnet
+     *
+     * @var array
+     */
+    const MASKV4 = [
+        1 => '255.255.255.0',
+        2 => '255.255.0.0'
+    ];
+
+    /**
+     * IPv6 netmask used to anonymize IPv6 address.
+     *
+     * 1) Mask Interface ID
+     * 2) Mask Interface ID and SLA ID
+     *
+     * @var array
+     */
+    const MASKV6 = [
+        1 => 'ffff:ffff:ffff:ffff:0000:0000:0000:0000',
+        2 => 'ffff:ffff:ffff:0000:0000:0000:0000:0000',
+    ];
+
+    /**
+     * Anonymize given IP
+     *
+     * @param string $address IP address
+     * @param int $mask Allowed values are 0 (masking disabled), 1 (mask host), 2 (mask host and subnet)
+     * @return string
+     * @throws \UnexpectedValueException
+     */
+    public static function anonymizeIp(string $address, int $mask = null): string
+    {
+        if ($mask === null) {
+            $mask = (int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['ipAnonymization'];
+        }
+        if ($mask < 0 || $mask > 2) {
+            throw new \UnexpectedValueException(sprintf('The provided value "%d" is not an allowed value for the IP mask.', $mask), 1519739203);
+        }
+        if ($mask === 0) {
+            return $address;
+        }
+        if (empty($address)) {
+            return '';
+        }
+
+        $packedAddress = inet_pton($address);
+        $length = strlen($packedAddress);
+
+        if ($length === 4) {
+            $bitMask = self::MASKV4[$mask];
+        } elseif ($length === 16) {
+            $bitMask = self::MASKV6[$mask];
+        } else {
+            return '';
+        }
+        return inet_ntop($packedAddress & inet_pton($bitMask));
+    }
+}
diff --git a/typo3/sysext/core/Documentation/Changelog/7.6.x/Feature-84053-APIToAnonymizeIPAddresses.rst b/typo3/sysext/core/Documentation/Changelog/7.6.x/Feature-84053-APIToAnonymizeIPAddresses.rst
new file mode 100644 (file)
index 0000000..d195e1f
--- /dev/null
@@ -0,0 +1,36 @@
+.. include:: ../../Includes.txt
+
+===============================================
+Feature: #84053 - API to anonymize IP addresses
+===============================================
+
+See :issue:`84053`
+
+Description
+===========
+
+A new API has been introduced which can be used to anonymize IP addresses.
+This shall help to comply with data protection and privacy laws and requirement.
+
+:php:`\TYPO3\CMS\Core\Utility\IpAnonymizationUtility::anonymizeIp(string $ipAddress, int $mask = null)`
+
+If :php:`$mask` is set to null (default value), the setting :php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['ipAnonymization']` is taken into account.
+
+The following options for :php:`$mask` are possible:
+
+- `0`: The anonymization is disabled.
+- `1`: For IPv4 addresses the last byte is masked. E.g. :code:`192.168.100.10` is transformed to :code:`192.168.100.0`.
+       For IPv6 addresses the Interface ID. E.g. :code:`2002:6dcd:8c74:6501:fb2:61c:ac98:6bea` is transformed to :code:`2002:6dcd:8c74:6501::`
+- `2`: For IPv4 addresses the last two bytes are masked. E.g. :code:`192.168.100.10` is transformed to :code:`192.168.0.0`.
+       For IPv6 addresses the Interface ID and SLA ID. E.g. :code:`2002:6dcd:8c74:6501:fb2:61c:ac98:6bea` is transformed to :code:`2002:6dcd:8c74::`
+
+The default value for :php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['ipAnonymization']` is :php:`1`!
+
+Impact
+======
+
+The core uses this API whenever IP addresses are stored, this includes:
+
+- Indexed Search uses the new setting for its search statistics.
+
+.. index:: PHP-API, ext:core, ext:indexed_search
diff --git a/typo3/sysext/core/Tests/Unit/Utility/IpAnonymizationUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/IpAnonymizationUtilityTest.php
new file mode 100644 (file)
index 0000000..966616f
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Utility;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\IpAnonymizationUtility;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Testcase for class \TYPO3\CMS\Core\Utility\IpAnonymizationUtility
+ */
+class IpAnonymizationUtilityTest extends UnitTestCase
+{
+
+    /**
+     * Data provider for anonymizeIpReturnsCorrectValue
+     *
+     * @return array
+     */
+    public function anonymizeIpReturnsCorrectValueDataProvider(): array
+    {
+        return [
+            'empty address' => ['', 1, ''],
+            'IPv4 address with mask 0' => ['192.158.130.10', 0, '192.158.130.10'],
+            'IPv4 address with mask 1' => ['192.158.130.10', 1, '192.158.130.0'],
+            'IPv4 address with mask 2' => ['192.158.130.10', 2, '192.158.0.0'],
+            'IPv4 address with fallback' => ['192.158.130.10', null, '192.158.130.0'],
+            'IPv6 address with mask 0' => ['0064:ff9b:0000:0000:0000:0000:18.52.86.120', 0, '0064:ff9b:0000:0000:0000:0000:18.52.86.120'],
+            'IPv6 address with mask 1' => ['2002:6dcd:8c74:6501:fb2:61c:ac98:6bea', 1, '2002:6dcd:8c74:6501::'],
+            'IPv6 address with mask 2' => ['2002:6dcd:8c74:6501:fb2:61c:ac98:6bea', 2, '2002:6dcd:8c74::'],
+            'IPv6 address with fallback' => ['2002:6dcd:8c74:6501:fb2:61c:ac98:6bea', null, '2002:6dcd:8c74:6501::'],
+            'IPv4-Embedded IPv6 Address' => ['::ffff:18.52.86.120', 1, '::'],
+            'anonymized IPv4 address' => ['192.158.0.0', 1, '192.158.0.0'],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider anonymizeIpReturnsCorrectValueDataProvider
+     * @param string $address
+     * @param int|null $mask
+     * @param string $expected
+     */
+    public function anonymizeIpReturnsCorrectValue(string $address, int $mask = null, string $expected)
+    {
+        // set the default if $mask is null
+        $GLOBALS['TYPO3_CONF_VARS']['SYS']['ipAnonymization'] = 1;
+        $this->assertEquals($expected, IpAnonymizationUtility::anonymizeIp($address, $mask));
+    }
+
+    /**
+     * @test
+     */
+    public function wrongMaskForAnonymizeIpThrowsException()
+    {
+        $this->expectException(\UnexpectedValueException::class);
+        $this->expectExceptionCode(1519739203);
+
+        IpAnonymizationUtility::anonymizeIp('', 3);
+    }
+}