113 lines
3.6 KiB
PHP
113 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* 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!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Core\Authentication;
|
|
|
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
|
|
|
/**
|
|
* Handles the locking of sessions to IP addresses.
|
|
*/
|
|
class IpLocker
|
|
{
|
|
public 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.
|
|
*/
|
|
protected int $lockIPv4PartCount = 4;
|
|
|
|
/**
|
|
* Same setting as lockIP but for IPv6 addresses.
|
|
*/
|
|
protected int $lockIPv6PartCount = 8;
|
|
|
|
public function __construct(int $lockIPv4PartCount, int $lockIPv6PartCount)
|
|
{
|
|
$this->lockIPv4PartCount = $lockIPv4PartCount;
|
|
$this->lockIPv6PartCount = $lockIPv6PartCount;
|
|
}
|
|
|
|
public function getSessionIpLock(string $ipAddress): string
|
|
{
|
|
if ($this->lockIPv4PartCount === 0 && $this->lockIPv6PartCount === 0) {
|
|
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($ipAddress)
|
|
? $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)
|
|
$unpacked = unpack('H*hex', (string)inet_pton($ipAddress)) ?: [];
|
|
$expandedAddress = rtrim(chunk_split($unpacked['hex'] ?? '', 4, ':'), ':');
|
|
return $this->getIpLockPart($expandedAddress, $this->lockIPv6PartCount, 8, ':');
|
|
}
|
|
|
|
protected function isIpv6Address(string $ipAddress): bool
|
|
{
|
|
return str_contains($ipAddress, ':');
|
|
}
|
|
}
|