<commit_before><?php declare(strict_types=1);

namespace Packagist\WebBundle\Security;

use Packagist\Redis\FailedLoginCounter;
use Predis\Client;
use Symfony\Component\HttpFoundation\Request;

class RecaptchaHelper
{
    private const LOGIN_BASE_KEY_IP = 'bf:login:ip:';
    private const LOGIN_BASE_KEY_USER = 'bf:login:user:';

    /** @var Client */
    private $redis;
    /** @var bool */
    private $recaptchaEnabled;

    public function __construct(Client $redis, bool $recaptchaEnabled)
    {
        $this->redis = $redis;
        $this->recaptchaEnabled = $recaptchaEnabled;
    }

    public function requiresRecaptcha(string $ip, string $username): bool
    {
        if (!$this->recaptchaEnabled) {
            return false;
        }

        $keys = [self::LOGIN_BASE_KEY_IP . $ip];
        if ($username) {
            $keys[] = self::LOGIN_BASE_KEY_USER . strtolower($username);
        }

        $result = $this->redis->mget($keys);
        foreach ($result as $count) {
            if ($count >= 3) {
                return true;
            }
        }

        return false;
    }

    public function increaseCounter(Request $request): void
    {
        if ($this->recaptchaEnabled) {
            $this->redis->getProfile()->defineCommand('incrFailedLoginCounter', FailedLoginCounter::class);

            $ipKey = self::LOGIN_BASE_KEY_IP . $request->getClientIp();
            $userKey = self::LOGIN_BASE_KEY_USER . strtolower($request->get('_username'));
            $this->redis->incrFailedLoginCounter($ipKey, $userKey);
        }
    }

    public function clearCounter(Request $request): void
    {
        if ($this->recaptchaEnabled) {
            $userKey = self::LOGIN_BASE_KEY_USER . strtolower($request->get('_username'));
            $this->redis->del([$userKey]);
        }
    }
}<commit_msg>Recaptcha: assert _username is always a string to increase/reset counters<commit_after><?php declare(strict_types=1);

namespace Packagist\WebBundle\Security;

use Packagist\Redis\FailedLoginCounter;
use Predis\Client;
use Symfony\Component\HttpFoundation\Request;

class RecaptchaHelper
{
    private const LOGIN_BASE_KEY_IP = 'bf:login:ip:';
    private const LOGIN_BASE_KEY_USER = 'bf:login:user:';

    /** @var Client */
    private $redis;
    /** @var bool */
    private $recaptchaEnabled;

    public function __construct(Client $redis, bool $recaptchaEnabled)
    {
        $this->redis = $redis;
        $this->recaptchaEnabled = $recaptchaEnabled;
    }

    public function requiresRecaptcha(string $ip, string $username): bool
    {
        if (!$this->recaptchaEnabled) {
            return false;
        }

        $keys = [self::LOGIN_BASE_KEY_IP . $ip];
        if ($username) {
            $keys[] = self::LOGIN_BASE_KEY_USER . strtolower($username);
        }

        $result = $this->redis->mget($keys);
        foreach ($result as $count) {
            if ($count >= 3) {
                return true;
            }
        }

        return false;
    }

    public function increaseCounter(Request $request): void
    {
        if ($this->recaptchaEnabled) {
            $this->redis->getProfile()->defineCommand('incrFailedLoginCounter', FailedLoginCounter::class);

            $ipKey = self::LOGIN_BASE_KEY_IP . $request->getClientIp();
            $userKey = self::LOGIN_BASE_KEY_USER . strtolower((string) $request->get('_username'));
            $this->redis->incrFailedLoginCounter($ipKey, $userKey);
        }
    }

    public function clearCounter(Request $request): void
    {
        if ($this->recaptchaEnabled) {
            $userKey = self::LOGIN_BASE_KEY_USER . strtolower((string) $request->get('_username'));
            $this->redis->del([$userKey]);
        }
    }
}
{'fraction_non_alphanumeric': 0.13294180459156432, 'fraction_numerical': 0.0010678056593699946, 'mean_word_length': 2.606352261790183, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4550339', 'n_tokens_mistral': 1153, 'n_tokens_neox': 1130, 'n_words': 207}