[!!!][FEATURE] Add IP locking for IPv6 47/52947/29
authorAlexander Stehlik <alexander.stehlik@gmail.com>
Mon, 30 Oct 2017 16:57:16 +0000 (17:57 +0100)
committerOliver Hader <oliver.hader@typo3.org>
Thu, 18 Jul 2019 09:54:04 +0000 (11:54 +0200)
A new IpLocker class replaces the lock functionality in
AbstractUserAuthentication.

The new class is capable of locking to IPv4 and IPv6 addresses.

Resolves: #21638
Releases: master
Change-Id: I0075a9e49690e31c938abf7242c1f088c73bb37d
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/52947
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
typo3/sysext/core/Classes/Authentication/IpLocker.php [new file with mode: 0644]
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml
typo3/sysext/core/Documentation/Changelog/master/Breaking-21638-LockIPPropertyRemoved.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-21638-IntroducedIpLockingForIpv6.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Functional/Authentication/BackendUserAuthenticationTest.php
typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
typo3/sysext/core/Tests/Unit/Authentication/IpLockerTest.php [new file with mode: 0644]
typo3/sysext/frontend/Tests/Unit/Authentication/FrontendUserAuthenticationTest.php

index 3e9462c..47f4aa6 100644 (file)
@@ -32,7 +32,6 @@ use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
 use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
 use TYPO3\CMS\Core\Session\SessionManager;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\MathUtility;
 
 /**
  * Authentication of users in TYPO3
@@ -202,13 +201,6 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
     public $hash_length = 32;
 
     /**
-     * If set to 4, the session will be locked to the user's IP address (all four numbers).
-     * Reducing this to 1-3 means that only the given number of parts of the IP address is used.
-     * @var int
-     */
-    public $lockIP = 4;
-
-    /**
      * @var string
      */
     public $warningEmail = '';
@@ -303,6 +295,11 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
     public $uc;
 
     /**
+     * @var IpLocker
+     */
+    protected $ipLocker;
+
+    /**
      * @var SessionBackendInterface
      */
     protected $sessionBackend;
@@ -331,6 +328,12 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
         if (empty($this->loginType)) {
             throw new Exception('No loginType defined, must be set explicitly by subclass', 1476045345);
         }
+
+        $this->ipLocker = GeneralUtility::makeInstance(
+            IpLocker::class,
+            $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockIP'],
+            $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockIPv6']
+        );
     }
 
     /**
@@ -893,10 +896,7 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
      */
     public function getNewSessionRecord($tempuser)
     {
-        $sessionIpLock = '[DISABLED]';
-        if ($this->lockIP && empty($tempuser['disableIPlock'])) {
-            $sessionIpLock = $this->ipLockClause_remoteIPNumber($this->lockIP);
-        }
+        $sessionIpLock = $this->ipLocker->getSessionIpLock((string)GeneralUtility::getIndpEnv('REMOTE_ADDR'), empty($tempuser['disableIPlock']));
 
         return [
             'ses_id' => $this->id,
@@ -1041,10 +1041,7 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
             }
             // If the session does not match the current IP lock, it should be treated as invalid
             // and a new session should be created.
-            if ($sessionRecord['ses_iplock'] !== $this->ipLockClause_remoteIPNumber($this->lockIP) && $sessionRecord['ses_iplock'] !== '[DISABLED]') {
-                return false;
-            }
-            return true;
+            return $this->ipLocker->validateRemoteAddressAgainstSessionIpLock((string)GeneralUtility::getIndpEnv('REMOTE_ADDR'), $sessionRecord['ses_iplock']);
         } catch (SessionNotFoundException $e) {
             return false;
         }
@@ -1101,27 +1098,6 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
         return $restrictionContainer;
     }
 
-    /**
-     * Returns the IP address to lock to.
-     * The IP address may be partial based on $parts.
-     *
-     * @param int $parts 1-4: Indicates how many parts of the IP address to return. 4 means all, 1 means only first number.
-     * @return string (Partial) IP address for REMOTE_ADDR
-     */
-    protected function ipLockClause_remoteIPNumber($parts)
-    {
-        $IP = GeneralUtility::getIndpEnv('REMOTE_ADDR');
-        if ($parts >= 4) {
-            return $IP;
-        }
-        $parts = MathUtility::forceIntegerInRange($parts, 1, 3);
-        $IPparts = explode('.', $IP);
-        for ($a = 4; $a > $parts; $a--) {
-            unset($IPparts[$a - 1]);
-        }
-        return implode('.', $IPparts);
-    }
-
     /*************************
      *
      * Session and Configuration Handling
diff --git a/typo3/sysext/core/Classes/Authentication/IpLocker.php b/typo3/sysext/core/Classes/Authentication/IpLocker.php
new file mode 100644 (file)
index 0000000..60e48c2
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\Authentication;
+
+/*
+ * 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\MathUtility;
+
+/**
+ * Handles the locking of sessions to IP addresses.
+ */
+class IpLocker
+{
+    const DISABLED_LOCK_VALUE = '[DISABLED]';
+
+    /**
+     * If set to 4, the session will be locked to the user's IP address (all four numbers).
+     * Reducing this to 1-3 means that only the given number of parts of the IP address is used.
+     *
+     * @var int
+     */
+    protected $lockIPv4PartCount = 4;
+
+    /**
+     * Same setting as lockIP but for IPv6 addresses.
+     *
+     * @var int
+     */
+    protected $lockIPv6PartCount = 8;
+
+    public function __construct(int $lockIPv4PartCount, int $lockIPv6PartCount)
+    {
+        $this->lockIPv4PartCount = $lockIPv4PartCount;
+        $this->lockIPv6PartCount = $lockIPv6PartCount;
+    }
+
+    public function getSessionIpLock(string $ipAddress, bool $enableLocking = true): string
+    {
+        if (!$enableLocking) {
+            return static::DISABLED_LOCK_VALUE;
+        }
+
+        if ($this->isIpv6Address($ipAddress)) {
+            return $this->getIpLockPartForIpv6Address($ipAddress);
+        }
+        return $this->getIpLockPartForIpv4Address($ipAddress);
+    }
+
+    public function validateRemoteAddressAgainstSessionIpLock(string $ipAddress, string $sessionIpLock): bool
+    {
+        if ($sessionIpLock === static::DISABLED_LOCK_VALUE) {
+            return true;
+        }
+
+        $ipToCompare = $this->isIpv6Address($sessionIpLock)
+            ? $this->getIpLockPartForIpv6Address($ipAddress)
+            : $this->getIpLockPartForIpv4Address($ipAddress);
+        return $ipToCompare === $sessionIpLock;
+    }
+
+    protected function getIpLockPart(string $ipAddress, int $numberOfParts, int $maxParts, string $delimiter): string
+    {
+        if ($numberOfParts >= $maxParts) {
+            return $ipAddress;
+        }
+
+        $numberOfParts = MathUtility::forceIntegerInRange($numberOfParts, 1, $maxParts);
+        $ipParts = explode($delimiter, $ipAddress);
+        for ($a = $maxParts; $a > $numberOfParts; $a--) {
+            $ipPartValue = $delimiter === '.' ? '0' : str_pad('', strlen($ipParts[$a - 1]), '0');
+            $ipParts[$a - 1] = $ipPartValue;
+        }
+
+        return implode($delimiter, $ipParts);
+    }
+
+    protected function getIpLockPartForIpv4Address(string $ipAddress): string
+    {
+        if ($this->lockIPv4PartCount === 0) {
+            return static::DISABLED_LOCK_VALUE;
+        }
+
+        return $this->getIpLockPart($ipAddress, $this->lockIPv4PartCount, 4, '.');
+    }
+
+    protected function getIpLockPartForIpv6Address(string $ipAddress): string
+    {
+        if ($this->lockIPv6PartCount === 0) {
+            return static::DISABLED_LOCK_VALUE;
+        }
+
+        // inet_pton also takes care of IPv4-mapped addresses (see https://en.wikipedia.org/wiki/IPv6_address#Representation)
+        $expandedAddress = rtrim(chunk_split(unpack('H*hex', inet_pton($ipAddress))['hex'], 4, ':'), ':');
+        return $this->getIpLockPart($expandedAddress, $this->lockIPv6PartCount, 8, ':');
+    }
+
+    protected function isIpv6Address(string $ipAddress): bool
+    {
+        return strpos($ipAddress, ':') !== false;
+    }
+}
index 68dd1b7..11cc118 100644 (file)
@@ -1067,6 +1067,7 @@ return [
         'warning_email_addr' => '',
         'warning_mode' => 0,
         'lockIP' => 4,
+        'lockIPv6' => 2,
         'sessionTimeout' => 28800,  // a backend user logged in for 8 hours
         'IPmaskList' => '',
         'lockBeUserToDBmounts' => true,
@@ -1247,6 +1248,7 @@ return [
         'addRootLineFields' => '',
         'checkFeUserPid' => true,
         'lockIP' => 2,
+        'lockIPv6' => 2,
         'loginSecurityLevel' => 'normal',
         'lifetime' => 0,
         'sessionTimeout' => 6000,
index 189bf46..e38f38f 100644 (file)
@@ -273,8 +273,21 @@ BE:
               '1': 'Use the first part of the editors'' IPv4 address (e.g. "192.") as part of the session locking of Backend Users'
               '2': 'Use the first two parts of the editors'' IPv4 address (e.g. "192.168") as part of the session locking of Backend Users'
               '3': 'Use the first three parts of the editors'' IPv4 address (e.g. "192.168.13") as part of the session locking of Backend Users'
-              '4': 'Default: Use the full editors'' IPv4 address (e.g. "192.168.13.84") as part of the session locking of Backend Users (highest security)'
+              '4': 'Default: Use the editors'' full IPv4 address (e.g. "192.168.13.84") as part of the session locking of Backend Users (highest security)'
             description: 'Session IP locking for backend users. See <a href="#FE-lockIP">[FE][lockIP]</a> for details.'
+        lockIPv6:
+            type: int
+            allowedValues:
+              '0': 'Do not lock Backend User sessions to their IP address at all'
+              '1': 'Use the first block (16 bits) of the editors'' IPv6 address (e.g. "2001:") as part of the session locking of Backend Users'
+              '2': 'Use the first two blocks (32 bits) of the editors'' IPv6 address (e.g. "2001:0db8") as part of the session locking of Backend Users'
+              '3': 'Use the first three blocks (48 bits) of the editors'' IPv6 address (e.g. "2001:0db8:85a3") as part of the session locking of Backend Users'
+              '4': 'Use the first four blocks (64 bits) of the editors'' IPv6 address (e.g. "2001:0db8:85a3:08d3") as part of the session locking of Backend Users'
+              '5': 'Use the first five blocks (80 bits) of the editors'' IPv6 address (e.g. "2001:0db8:85a3:08d3:1319") as part of the session locking of Backend Users'
+              '6': 'Use the first six blocks (96 bits) of the editors'' IPv6 address (e.g. "2001:0db8:85a3:08d3:1319:8a2e") as part of the session locking of Backend Users'
+              '7': 'Use the first seven blocks (112 bits) of the editors'' IPv6 address (e.g. "2001:0db8:85a3:08d3:1319:8a2e:0370") as part of the session locking of Backend Users'
+              '8': 'Default: Use the editors'' full IPv6 address (e.g. "2001:0db8:85a3:08d3:1319:8a2e:0370:7344") as part of the session locking of Backend Users (highest security)'
+            description: 'Session IPv6 locking for backend users. See <a href="#FE-lockIPv6">[FE][lockIPv6]</a> for details.'
         sessionTimeout:
             type: int
             description: 'Session time out for backend users in seconds. The value must be at least 180 to avoid side effects. Default is 28.800 seconds = 8 hours.'
@@ -396,8 +409,21 @@ FE:
               '1': 'Use the first part of the visitors'' IPv4 address (e.g. "192.") as part of the session locking of Frontend Users'
               '2': 'Default - Use the first two parts of the visitors'' IPv4 address (e.g. "192.168") as part of the session locking of Frontend Users'
               '3': 'Use the first three parts of the visitors'' IPv4 address (e.g. "192.168.13") as part of the session locking of Frontend Users'
-              '4': 'Use the full visitors'' IPv4 address (e.g. "192.168.13.84") as part of the session locking of Frontend Users (highest security)'
-            description: 'If activated, Frontend Users are locked to (a part of) their public IP (<code>$_SERVER[''REMOTE_ADDR'']</code>) for their session. Enhances security but may throw off users that may change IP during their session (in which case you can lower it to 2 or 3). The integer indicates how many parts of the IP address to include in the check for session (next to the user agent)..'
+              '4': 'Use the visitors'' full IPv4 address (e.g. "192.168.13.84") as part of the session locking of Frontend Users (highest security)'
+            description: 'If activated, Frontend Users are locked to (a part of) their public IP (<code>$_SERVER[''REMOTE_ADDR'']</code>) for their session, if REMOTE_ADDR is an IPv4-address. Enhances security but may throw off users that may change IP during their session (in which case you can lower it). The integer indicates how many parts of the IP address to include in the check for session (next to the user agent).'
+        lockIPv6:
+            type: int
+            allowedValues:
+              '0': 'Do not lock Backend User sessions to their IP address at all'
+              '1': 'Use the first block (16 bits) of the editors'' IPv6 address (e.g. "2001:") as part of the session locking of Backend Users'
+              '2': 'Use the first two blocks (32 bits) of the editors'' IPv6 address (e.g. "2001:0db8") as part of the session locking of Backend Users'
+              '3': 'Use the first three blocks (48 bits) of the editors'' IPv6 address (e.g. "2001:0db8:85a3") as part of the session locking of Backend Users'
+              '4': 'Use the first four blocks (64 bits) of the editors'' IPv6 address (e.g. "2001:0db8:85a3:08d3") as part of the session locking of Backend Users'
+              '5': 'Use the first five blocks (80 bits) of the editors'' IPv6 address (e.g. "2001:0db8:85a3:08d3:1319") as part of the session locking of Backend Users'
+              '6': 'Use the first six blocks (96 bits) of the editors'' IPv6 address (e.g. "2001:0db8:85a3:08d3:1319:8a2e") as part of the session locking of Backend Users'
+              '7': 'Use the first seven blocks (112 bits) of the editors'' IPv6 address (e.g. "2001:0db8:85a3:08d3:1319:8a2e:0370") as part of the session locking of Backend Users'
+              '8': 'Default: Use the visitors'' full IPv6 address (e.g. "2001:0db8:85a3:08d3:1319:8a2e:0370:7344") as part of the session locking of Backend Users (highest security)'
+            description: 'If activated, Frontend Users are locked to (a part of) their public IP (<code>$_SERVER[''REMOTE_ADDR'']</code>) for their session, if REMOTE_ADDR is an IPv6-address. Enhances security but may throw off users that may change IP during their session (in which case you can lower it). The integer indicates how many parts of the IP address to include in the check for session (next to the user agent).'
         loginSecurityLevel:
             type: text
             description: 'See description for <a href="#BE-loginSecurityLevel">[BE][loginSecurityLevel]</a>. Default state for frontend is "normal". The client/server communication should be secured with HTTPS.'
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-21638-LockIPPropertyRemoved.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-21638-LockIPPropertyRemoved.rst
new file mode 100644 (file)
index 0000000..50563c1
--- /dev/null
@@ -0,0 +1,36 @@
+.. include:: ../../Includes.txt
+
+======================================================================
+Breaking: #21638 - AbstractUserAuthentication::lockIP property removed
+======================================================================
+
+See :issue:`21638`
+
+Description
+===========
+
+The IP-locking-functionality is extended from IPv4 only to now also support IPv6. A separate IpLocker-functionality was added.
+
+The public property :php:`lockIP` in :php:`AbstractUserAuthentication` is now removed. It usually shouldn't have been accessed directly and supported IPv4 only.
+
+
+Impact
+======
+
+Extensions relying on :php:`lockIP` won't be able to perform their task anymore.
+This might for example be the case when "lockIP" was set dynamically, depending on the REMOTE_ADDR.
+
+
+Affected Installations
+======================
+
+Every 3rd party extension depending on the formerly public :php:`lockIP` property is affected.
+
+
+Migration
+=========
+
+Set :php:`lockIP` and :php:`lockIPv6` in :php:`TYPO3_CONF_VARS` - for FE or BE depending on the usecase.
+Use the new :php:`\TYPO3\CMS\Core\Authentication\IpLocker` API.
+
+.. index:: Backend, Frontend, LocalConfiguration, NotScanned
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-21638-IntroducedIpLockingForIpv6.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-21638-IntroducedIpLockingForIpv6.rst
new file mode 100644 (file)
index 0000000..e1a53b4
--- /dev/null
@@ -0,0 +1,30 @@
+.. include:: ../../Includes.txt
+
+================================================
+Feature: #21638 - Introduced IP locking for IPv6
+================================================
+
+See :issue:`21638`
+
+Description
+===========
+
+The IP-locking functionality has been extended to support IPv6 now as well.
+This security feature enables binding of a user session (Backend or Frontend) to an IP address or a part of it.
+
+The available configuration options with their default values for IP-locking are:
+
+.. code-block:: typoscript
+
+   $GLOBALS['TYPO3_CONF_VARS']['FE']['lockIP'] = 2;
+   $GLOBALS['TYPO3_CONF_VARS']['FE']['lockIPv6'] = 2;
+
+   $GLOBALS['TYPO3_CONF_VARS']['BE']['lockIP'] = 4;
+   $GLOBALS['TYPO3_CONF_VARS']['BE']['lockIPv6'] = 2;
+
+The configuration can be changed via the Admin Tools -> Settings menu.
+The exact meaning of the numbers used for the configuration are documented there.
+
+Code-wise a separate IpLocker class :php:`\TYPO3\CMS\Core\Authentication\IpLocker` has been added, which takes care of the IP-locking for both IP versions.
+
+.. index:: Backend, Frontend, LocalConfiguration
index 7f65aba..865098e 100644 (file)
@@ -52,6 +52,7 @@ class BackendUserAuthenticationTest extends FunctionalTestCase
         $GLOBALS['TYPO3_CONF_VARS']['BE']['cookieName'] = 'be_typo_user';
         $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'] = '';
         $GLOBALS['TYPO3_CONF_VARS']['BE']['lockIP'] = 4;
+        $GLOBALS['TYPO3_CONF_VARS']['BE']['lockIPv6'] = 8;
         $GLOBALS['TYPO3_CONF_VARS']['BE']['sessionTimeout'] = 28800;
 
         $this->subject = new BackendUserAuthentication();
index e167194..876d99b 100644 (file)
@@ -59,6 +59,12 @@ class BackendUserAuthenticationTest extends UnitTestCase
         'recursivedeleteFolder' => false
     ];
 
+    protected function setUp(): void
+    {
+        $GLOBALS['TYPO3_CONF_VARS']['BE']['lockIP'] = 4;
+        $GLOBALS['TYPO3_CONF_VARS']['BE']['lockIPv6'] = 8;
+    }
+
     /**
      * Tear down
      */
diff --git a/typo3/sysext/core/Tests/Unit/Authentication/IpLockerTest.php b/typo3/sysext/core/Tests/Unit/Authentication/IpLockerTest.php
new file mode 100644 (file)
index 0000000..1466ea6
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Authentication;
+
+/*
+ * 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\Authentication\IpLocker;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Testcase for class \TYPO3\CMS\Core\Authentication\IpLocker
+ */
+class IpLockerTest extends UnitTestCase
+{
+    /**
+      * @return array
+      */
+    public function getSessionIpLockDataProvider(): array
+    {
+        return [
+            'basic IPv4-locks, part-count 0' => [
+                '192.168.0.1',
+                0,
+                8,
+                '[DISABLED]',
+            ],
+            'basic IPv4-locks, part-count 1' => [
+                '192.168.0.1',
+                1,
+                8,
+                '192.0.0.0',
+            ],
+            'basic IPv4-locks, part-count 2' => [
+                '192.168.0.1',
+                2,
+                8,
+                '192.168.0.0',
+            ],
+            'basic IPv4-locks, part-count 3' => [
+                '192.168.0.1',
+                3,
+                8,
+                '192.168.0.0',
+            ],
+            'basic IPv4-locks, part-count 4' => [
+                '192.168.0.1',
+                4,
+                8,
+                '192.168.0.1',
+            ],
+            'basic IPv6-locks, part-count 0' => [
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:7344',
+                4,
+                0,
+                '[DISABLED]',
+            ],
+            'basic IPv6-locks, part-count 1' => [
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:7344',
+                4,
+                1,
+                '2001:0000:0000:0000:0000:0000:0000:0000',
+            ],
+            'basic IPv6-locks, part-count 2' => [
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:7344',
+                4,
+                2,
+                '2001:0db8:0000:0000:0000:0000:0000:0000',
+            ],
+            'basic IPv6-locks, part-count 3' => [
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:7344',
+                4,
+                3,
+                '2001:0db8:85a3:0000:0000:0000:0000:0000',
+            ],
+            'basic IPv6-locks, part-count 4' => [
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:7344',
+                4,
+                4,
+                '2001:0db8:85a3:08d3:0000:0000:0000:0000',
+            ],
+            'basic IPv6-locks, part-count 5' => [
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:7344',
+                4,
+                5,
+                '2001:0db8:85a3:08d3:1319:0000:0000:0000',
+            ],
+            'basic IPv6-locks, part-count 6' => [
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:7344',
+                4,
+                6,
+                '2001:0db8:85a3:08d3:1319:8a2e:0000:0000',
+            ],
+            'basic IPv6-locks, part-count 7' => [
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:7344',
+                4,
+                7,
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:0000',
+            ],
+            'basic IPv6-locks, part-count 8' => [
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:7344',
+                4,
+                8,
+                '2001:0db8:85a3:08d3:1319:8a2e:0370:7344',
+            ],
+            'compressed IPv6-lock, IP ::1, part-count 8' => [
+                '::1',
+                4,
+                8,
+                '0000:0000:0000:0000:0000:0000:0000:0001',
+            ],
+            'compressed IPv6-lock, IP 2001:db8:0:200::7, part-count 8' => [
+                '2001:db8:0:200::7',
+                4,
+                8,
+                '2001:0db8:0000:0200:0000:0000:0000:0007',
+            ],
+            'compressed IPv6-lock, IPv4-mapped IP ::ffff:127.0.0.1, part-count 8' => [
+                '::ffff:127.0.0.1',
+                4,
+                8,
+                '0000:0000:0000:0000:0000:ffff:7f00:0001',
+            ],
+        ];
+    }
+
+    /**
+     * @param string $ipAddress
+     * @param $lockIPv4PartCount
+     * @param $lockIPv6PartCount
+     * @param string $expectedLock
+     * @test
+     * @dataProvider getSessionIpLockDataProvider
+     */
+    public function getSessionIpLock($ipAddress, $lockIPv4PartCount, $lockIPv6PartCount, $expectedLock): void
+    {
+        $ipLocker = GeneralUtility::makeInstance(IpLocker::class, $lockIPv4PartCount, $lockIPv6PartCount);
+        $lock = $ipLocker->getSessionIpLock($ipAddress);
+
+        $this->assertEquals($expectedLock, $lock);
+    }
+}
index 8ce5b25..f0b94a2 100644 (file)
@@ -184,6 +184,7 @@ class FrontendUserAuthenticationTest extends UnitTestCase
     {
         $uniqueSessionId = $this->getUniqueId('test');
         $_COOKIE['fe_typo_user'] = $uniqueSessionId;
+        $GLOBALS['TYPO3_CONF_VARS']['FE']['lockIP'] = 0;
         $currentTime = $GLOBALS['EXEC_TIME'];
 
         // This setup fakes the "getAuthInfoArray() db call
@@ -231,7 +232,6 @@ class FrontendUserAuthenticationTest extends UnitTestCase
         $subject->setLogger(new NullLogger());
         $subject->gc_probability = -1;
         $subject->start();
-        $subject->lockIP = 0;
         $this->assertEmpty($subject->getSessionData($uniqueSessionId));
         $this->assertEmpty($subject->user);
         $subject->setSessionData('foo', 'bar');