* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Chill\MainBundle\Security\PasswordRecover; use Chill\MainBundle\Redis\ChillRedis; use Psr\Log\LoggerInterface; /** * * * @author Julien Fastré */ class PasswordRecoverLocker { /** * The maximum of invalid token globally (across all ip) */ const MAX_INVALID_TOKEN_GLOBAL = 50; /** * The maximum of invalid token by ip */ const MAX_INVALID_TOKEN_BY_IP = 10; /** * TTL to keep invalid token recorded */ const INVALID_TOKEN_TTL = 3600; const MAX_ASK_TOKEN_INVALID_FORM_GLOBAL = 50; const MAX_ASK_TOKEN_INVALID_FORM_BY_IP = 10; const MAX_ASK_TOKEN_BY_USER = 10; const ASK_TOKEN_INVALID_FORM_TTL = 3600; /** * * @var ChillRedis */ protected $chillRedis; /** * * @var LoggerInterface */ protected $logger; public function __construct(ChillRedis $chillRedis, LoggerInterface $logger) { $this->chillRedis = $chillRedis; $this->logger = $logger; } public function createLock($usage, $discriminator = null) { $max = $this->getMax($usage); $ttl = $this->getTtl($usage); for ($i = 0; $i < $max; $i++) { $key = self::generateLockKey($usage, $i, $discriminator); if (0 === $this->chillRedis->exists($key)) { $this->chillRedis->set($key, 1); $this->chillRedis->setTimeout($key, $ttl); break; } } } public function isLocked($usage, $discriminator = null) { $max = $this->getMax($usage); for ($i = 0; $i < $max; $i++) { $key = self::generateLockKey($usage, $i, $discriminator); if ($this->chillRedis->exists($key) === 0) { return false; } } $this->logger->warning("locking reaching for password recovery", [ 'usage' => $usage, 'discriminator' => $discriminator ]); return true; } public function getMax($usage) { switch ($usage) { case 'invalid_token_global': return self::MAX_INVALID_TOKEN_GLOBAL; case 'invalid_token_by_ip': return self::MAX_INVALID_TOKEN_BY_IP; case 'ask_token_invalid_form_global': return self::MAX_ASK_TOKEN_INVALID_FORM_GLOBAL; case 'ask_token_invalid_form_by_ip': return self::MAX_ASK_TOKEN_INVALID_FORM_BY_IP; case 'ask_token_success_by_user': return self::MAX_ASK_TOKEN_BY_USER; default: throw new \UnexpectedValueException("this usage '$usage' is not yet implemented"); } } public function getTtl($usage) { switch ($usage) { case 'invalid_token_global': case 'invalid_token_by_ip': return self::INVALID_TOKEN_TTL; case 'ask_token_invalid_form_global': case 'ask_token_invalid_form_by_ip': return self::ASK_TOKEN_INVALID_FORM_TTL; case 'ask_token_success_by_user': return self::ASK_TOKEN_INVALID_FORM_TTL * 24; default: throw new \UnexpectedValueException("this usage '$usage' is not yet implemented"); } } /** * * @param string $usage 'invalid_token_global' or ... * @param int $number */ public static function generateLockKey($usage, int $number, $discriminator = null) { switch($usage) { case "invalid_token_global": return sprintf('invalid_token_global_%d', $number); case "invalid_token_by_ip": return sprintf('invalid_token_ip_%s_%d', $discriminator, $number); case "ask_token_invalid_form_global": return sprintf('ask_token_invalid_form_global_%d', $number); case "ask_token_invalid_form_by_ip": return sprintf('ask_token_invalid_form_by_ip_%s_%d', $discriminator, $number); case 'ask_token_success_by_user': return sprintf('ask_token_success_by_user_%s_%d', $discriminator->getId(), $number); default: throw new \LogicException("this usage is not implemented"); } } }