1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\Core\RateLimiter;
19
20use Psr\Http\Message\ServerRequestInterface;
21use Symfony\Component\RateLimiter\LimiterInterface;
22use Symfony\Component\RateLimiter\RateLimiterFactory as SymfonyRateLimiterFactory;
23use Symfony\Component\RateLimiter\Storage\InMemoryStorage;
24use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
25use TYPO3\CMS\Core\Http\NormalizedParams;
26use TYPO3\CMS\Core\RateLimiter\Storage\CachingFrameworkStorage;
27use TYPO3\CMS\Core\Utility\GeneralUtility;
28
29/**
30 * @internal This is not part of the official TYPO3 Core API due to a limitation of the experimental Symfony Rate Limiter API.
31 */
32class RateLimiterFactory
33{
34    public function createLoginRateLimiter(AbstractUserAuthentication $userAuthentication, ServerRequestInterface $request): LimiterInterface
35    {
36        $loginType = $userAuthentication->loginType;
37        $normalizedParams = $request->getAttribute('normalizedParams') ?? NormalizedParams::createFromRequest($request);
38        $remoteIp = $normalizedParams->getRemoteAddress();
39        $limiterId = sha1('typo3-login-' . $loginType);
40        $limit = (int)($GLOBALS['TYPO3_CONF_VARS'][$loginType]['loginRateLimit'] ?? 5);
41        $interval = $GLOBALS['TYPO3_CONF_VARS'][$loginType]['loginRateLimitInterval'] ?? '15 minutes';
42
43        // If not enabled, return a null limiter
44        $enabled = !$this->isIpExcluded($loginType, $remoteIp) && $limit > 0;
45
46        $config = [
47            'id' => $limiterId,
48            'policy' => ($enabled ? 'sliding_window' : 'no_limit'),
49            'limit' => $limit,
50            'interval' => $interval,
51        ];
52        $storage = ($enabled ? GeneralUtility::makeInstance(CachingFrameworkStorage::class) : new InMemoryStorage());
53        $limiterFactory = new SymfonyRateLimiterFactory(
54            $config,
55            $storage
56        );
57        return $limiterFactory->create($remoteIp);
58    }
59
60    protected function isIpExcluded(string $loginType, string $remoteAddress): bool
61    {
62        $ipMask = trim($GLOBALS['TYPO3_CONF_VARS'][$loginType]['loginRateLimitIpExcludeList'] ?? '');
63        return GeneralUtility::cmpIP($remoteAddress, $ipMask);
64    }
65}
66