mirror of
https://github.com/bdnugget/magento2.git
synced 2026-11-07 17:27:36 +00:00
593 lines
14 KiB
PHP
593 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Copyright © Magento, Inc. All rights reserved.
|
|
* See COPYING.txt for license details.
|
|
*/
|
|
declare(strict_types=1);
|
|
|
|
namespace Magento\Captcha\Model;
|
|
|
|
use Magento\Authorization\Model\UserContextInterface;
|
|
use Magento\Captcha\Helper\Data;
|
|
use Magento\Framework\App\ObjectManager;
|
|
use Magento\Framework\Math\Random;
|
|
|
|
/**
|
|
* Implementation of \Laminas\Captcha\Image
|
|
*
|
|
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
|
|
*
|
|
* @api
|
|
* @since 100.0.2
|
|
*/
|
|
class DefaultModel extends \Laminas\Captcha\Image implements \Magento\Captcha\Model\CaptchaInterface
|
|
{
|
|
/**
|
|
* Key in session for captcha code
|
|
*/
|
|
const SESSION_WORD = 'word';
|
|
|
|
/**
|
|
* Min captcha lengths default value
|
|
*/
|
|
const DEFAULT_WORD_LENGTH_FROM = 3;
|
|
|
|
/**
|
|
* Max captcha lengths default value
|
|
*/
|
|
const DEFAULT_WORD_LENGTH_TO = 5;
|
|
|
|
/**
|
|
* @var Data
|
|
* @since 100.2.0
|
|
*/
|
|
protected $captchaData;
|
|
|
|
/**
|
|
* Captcha expire time
|
|
* @var int
|
|
* @since 100.2.0
|
|
*/
|
|
protected $expiration;
|
|
|
|
/**
|
|
* Override default value to prevent a captcha cut off
|
|
* @var int
|
|
* @see \Laminas\Captcha\Image::$fsize
|
|
* @since 100.2.0
|
|
*/
|
|
protected $fsize = 22;
|
|
|
|
/**
|
|
* Captcha form id
|
|
* @var string
|
|
* @since 100.2.0
|
|
*/
|
|
protected $formId;
|
|
|
|
/**
|
|
* @var \Magento\Captcha\Model\ResourceModel\LogFactory
|
|
* @since 100.2.0
|
|
*/
|
|
protected $resLogFactory;
|
|
|
|
/**
|
|
* Overrides parent parameter as session comes in constructor.
|
|
*
|
|
* @var bool
|
|
* @since 100.2.0
|
|
*/
|
|
protected $keepSession = true;
|
|
|
|
/**
|
|
* @var \Magento\Framework\Session\SessionManagerInterface
|
|
* @since 100.2.0
|
|
*/
|
|
protected $session;
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
private $words;
|
|
|
|
/**
|
|
* @var Random
|
|
*/
|
|
private $randomMath;
|
|
|
|
/**
|
|
* @var UserContextInterface
|
|
*/
|
|
private $userContext;
|
|
|
|
/**
|
|
* @param \Magento\Framework\Session\SessionManagerInterface $session
|
|
* @param \Magento\Captcha\Helper\Data $captchaData
|
|
* @param ResourceModel\LogFactory $resLogFactory
|
|
* @param string $formId
|
|
* @param Random $randomMath
|
|
* @param UserContextInterface|null $userContext
|
|
* @throws \Laminas\Captcha\Exception\ExtensionNotLoadedException
|
|
*/
|
|
public function __construct(
|
|
\Magento\Framework\Session\SessionManagerInterface $session,
|
|
\Magento\Captcha\Helper\Data $captchaData,
|
|
\Magento\Captcha\Model\ResourceModel\LogFactory $resLogFactory,
|
|
$formId,
|
|
Random $randomMath = null,
|
|
?UserContextInterface $userContext = null
|
|
) {
|
|
parent::__construct();
|
|
$this->session = $session;
|
|
$this->captchaData = $captchaData;
|
|
$this->resLogFactory = $resLogFactory;
|
|
$this->formId = $formId;
|
|
$this->randomMath = $randomMath ?? ObjectManager::getInstance()->get(Random::class);
|
|
$this->userContext = $userContext ?? ObjectManager::getInstance()->get(UserContextInterface::class);
|
|
}
|
|
|
|
/**
|
|
* Returns key with respect of current form ID
|
|
*
|
|
* @param string $key
|
|
* @return string
|
|
*/
|
|
private function getFormIdKey($key)
|
|
{
|
|
return $this->formId . '_' . $key;
|
|
}
|
|
|
|
/**
|
|
* Get Block Name
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getBlockName()
|
|
{
|
|
return \Magento\Captcha\Block\Captcha\DefaultCaptcha::class;
|
|
}
|
|
|
|
/**
|
|
* Whether captcha is required to be inserted to this form
|
|
*
|
|
* @param null|string $login
|
|
* @return bool
|
|
*/
|
|
public function isRequired($login = null)
|
|
{
|
|
if (($this->isUserAuth()
|
|
&& !$this->isShownToLoggedInUser())
|
|
|| !$this->isEnabled()
|
|
|| !in_array(
|
|
$this->formId,
|
|
$this->getTargetForms()
|
|
)
|
|
|| $this->userContext->getUserType() === UserContextInterface::USER_TYPE_INTEGRATION
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return $this->isShowAlways()
|
|
|| $this->isOverLimitAttempts($login)
|
|
|| $this->session->getData($this->getFormIdKey('show_captcha'));
|
|
}
|
|
|
|
/**
|
|
* Check if CAPTCHA has to be shown to logged in user on this form
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isShownToLoggedInUser()
|
|
{
|
|
$forms = (array)$this->captchaData->getConfig('shown_to_logged_in_user');
|
|
foreach ($forms as $formId => $isShownToLoggedIn) {
|
|
if ($isShownToLoggedIn && $this->formId == $formId) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check is over limit attempts
|
|
*
|
|
* @param string $login
|
|
* @return bool
|
|
*/
|
|
private function isOverLimitAttempts($login)
|
|
{
|
|
return $this->isOverLimitIpAttempt() || $this->isOverLimitLoginAttempts($login);
|
|
}
|
|
|
|
/**
|
|
* Returns number of allowed attempts for same login
|
|
*
|
|
* @return int
|
|
*/
|
|
private function getAllowedAttemptsForSameLogin()
|
|
{
|
|
return (int)$this->captchaData->getConfig('failed_attempts_login');
|
|
}
|
|
|
|
/**
|
|
* Returns number of allowed attempts from same IP
|
|
*
|
|
* @return int
|
|
*/
|
|
private function getAllowedAttemptsFromSameIp()
|
|
{
|
|
return (int)$this->captchaData->getConfig('failed_attempts_ip');
|
|
}
|
|
|
|
/**
|
|
* Check is over limit saved attempts from one ip
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isOverLimitIpAttempt()
|
|
{
|
|
$countAttemptsByIp = $this->getResourceModel()->countAttemptsByRemoteAddress();
|
|
return $countAttemptsByIp >= $this->getAllowedAttemptsFromSameIp();
|
|
}
|
|
|
|
/**
|
|
* Is Over Limit Login Attempts
|
|
*
|
|
* @param string $login
|
|
* @return bool
|
|
*/
|
|
private function isOverLimitLoginAttempts($login)
|
|
{
|
|
if ($login != false) {
|
|
$countAttemptsByLogin = $this->getResourceModel()->countAttemptsByUserLogin($login);
|
|
return $countAttemptsByLogin >= $this->getAllowedAttemptsForSameLogin();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check is user auth
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isUserAuth()
|
|
{
|
|
return $this->session->isLoggedIn() || $this->userContext->getUserId();
|
|
}
|
|
|
|
/**
|
|
* Whether to respect case while checking the answer
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isCaseSensitive()
|
|
{
|
|
return (string)$this->captchaData->getConfig('case_sensitive');
|
|
}
|
|
|
|
/**
|
|
* Get font to use when generating captcha
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getFont()
|
|
{
|
|
$font = (string)$this->captchaData->getConfig('font');
|
|
$fonts = $this->captchaData->getFonts();
|
|
|
|
if (isset($fonts[$font])) {
|
|
$fontPath = $fonts[$font]['path'];
|
|
} else {
|
|
$fontData = array_shift($fonts);
|
|
$fontPath = $fontData['path'];
|
|
}
|
|
|
|
return $fontPath;
|
|
}
|
|
|
|
/**
|
|
* After this time isCorrect() is going to return FALSE even if word was guessed correctly
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getExpiration()
|
|
{
|
|
if (!$this->expiration) {
|
|
/**
|
|
* as "timeout" configuration parameter specifies timeout in minutes - we multiply it on 60 to set
|
|
* expiration in seconds
|
|
*/
|
|
$this->expiration = (int)$this->captchaData->getConfig('timeout') * 60;
|
|
}
|
|
return $this->expiration;
|
|
}
|
|
|
|
/**
|
|
* Get timeout for session token
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getTimeout()
|
|
{
|
|
return $this->getExpiration();
|
|
}
|
|
|
|
/**
|
|
* Get captcha image directory
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getImgDir()
|
|
{
|
|
return $this->captchaData->getImgDir();
|
|
}
|
|
|
|
/**
|
|
* Get captcha image base URL
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getImgUrl()
|
|
{
|
|
return $this->captchaData->getImgUrl();
|
|
}
|
|
|
|
/**
|
|
* Checks whether captcha was guessed correctly by user
|
|
*
|
|
* @param string $word
|
|
* @return bool
|
|
*/
|
|
public function isCorrect($word)
|
|
{
|
|
$storedWords = $this->getWords();
|
|
$this->clearWord();
|
|
|
|
if (!$word || !$storedWords) {
|
|
return false;
|
|
}
|
|
|
|
if (!$this->isCaseSensitive()) {
|
|
$storedWords = strtolower($storedWords);
|
|
$word = strtolower($word);
|
|
}
|
|
return in_array($word, explode(',', $storedWords));
|
|
}
|
|
|
|
/**
|
|
* Return full URL to captcha image
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getImgSrc()
|
|
{
|
|
return $this->getImgUrl() . $this->getId() . $this->getSuffix();
|
|
}
|
|
|
|
/**
|
|
* Log attempt
|
|
*
|
|
* @param string $login
|
|
* @return $this
|
|
*/
|
|
public function logAttempt($login)
|
|
{
|
|
if ($this->isEnabled() && in_array($this->formId, $this->getTargetForms())) {
|
|
$this->getResourceModel()->logAttempt($login);
|
|
if ($this->isOverLimitLoginAttempts($login)) {
|
|
$this->setShowCaptchaInSession(true);
|
|
}
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set show_captcha flag in session
|
|
*
|
|
* @param bool $value
|
|
* @return void
|
|
* @since 100.1.0
|
|
*/
|
|
public function setShowCaptchaInSession($value = true)
|
|
{
|
|
if ($value !== true) {
|
|
$value = false;
|
|
}
|
|
|
|
$this->session->setData($this->getFormIdKey('show_captcha'), $value);
|
|
}
|
|
|
|
/**
|
|
* Generate word used for captcha render
|
|
*
|
|
* @return string
|
|
* @throws \Magento\Framework\Exception\LocalizedException
|
|
* @since 100.2.0
|
|
*/
|
|
protected function generateWord()
|
|
{
|
|
$symbols = (string)$this->captchaData->getConfig('symbols');
|
|
$wordLen = $this->getWordLen();
|
|
return $this->randomMath->getRandomString($wordLen, $symbols);
|
|
}
|
|
|
|
/**
|
|
* Returns length for generating captcha word. This value may be dynamic.
|
|
*
|
|
* @return int
|
|
* @throws \Magento\Framework\Exception\LocalizedException
|
|
* @since 100.2.0
|
|
*/
|
|
public function getWordLen()
|
|
{
|
|
$from = 0;
|
|
$to = 0;
|
|
$length = (string)$this->captchaData->getConfig('length');
|
|
if (!is_numeric($length)) {
|
|
if (preg_match('/(\d+)-(\d+)/', $length, $matches)) {
|
|
$from = (int)$matches[1];
|
|
$to = (int)$matches[2];
|
|
}
|
|
} else {
|
|
$from = (int)$length;
|
|
$to = (int)$length;
|
|
}
|
|
|
|
if ($to < $from || $from < 1 || $to < 1) {
|
|
$from = self::DEFAULT_WORD_LENGTH_FROM;
|
|
$to = self::DEFAULT_WORD_LENGTH_TO;
|
|
}
|
|
|
|
return Random::getRandomNumber($from, $to);
|
|
}
|
|
|
|
/**
|
|
* Whether to show captcha for this form every time
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isShowAlways()
|
|
{
|
|
$captchaMode = (string)$this->captchaData->getConfig('mode');
|
|
|
|
if ($captchaMode === Data::MODE_ALWAYS) {
|
|
return true;
|
|
}
|
|
|
|
if ($captchaMode === Data::MODE_AFTER_FAIL
|
|
&& $this->getAllowedAttemptsForSameLogin() === 0
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
$alwaysFor = $this->captchaData->getConfig('always_for');
|
|
foreach ($alwaysFor as $nodeFormId => $isAlwaysFor) {
|
|
if ($isAlwaysFor && $this->formId == $nodeFormId) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Whether captcha is enabled at this area
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isEnabled()
|
|
{
|
|
return (string)$this->captchaData->getConfig('enable');
|
|
}
|
|
|
|
/**
|
|
* Retrieve list of forms where captcha must be shown
|
|
*
|
|
* For frontend this list is based on current website
|
|
*
|
|
* @return array
|
|
*/
|
|
private function getTargetForms()
|
|
{
|
|
$formsString = (string)$this->captchaData->getConfig('forms');
|
|
return explode(',', $formsString);
|
|
}
|
|
|
|
/**
|
|
* Get captcha word
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getWord()
|
|
{
|
|
$sessionData = $this->session->getData($this->getFormIdKey(self::SESSION_WORD));
|
|
return time() < $sessionData['expires'] ? $sessionData['data'] : null;
|
|
}
|
|
|
|
/**
|
|
* Get captcha words
|
|
*
|
|
* @return string
|
|
*/
|
|
private function getWords()
|
|
{
|
|
$sessionData = $this->session->getData($this->getFormIdKey(self::SESSION_WORD));
|
|
$words = '';
|
|
if (isset($sessionData['expires'], $sessionData['words']) && time() < $sessionData['expires']) {
|
|
$words = $sessionData['words'];
|
|
}
|
|
|
|
return $words;
|
|
}
|
|
|
|
/**
|
|
* Set captcha word
|
|
*
|
|
* @param string $word
|
|
* @return $this
|
|
* @since 100.2.0
|
|
*/
|
|
protected function setWord($word)
|
|
{
|
|
$this->words = $this->words ? $this->words . ',' . $word : $word;
|
|
$this->session->setData(
|
|
$this->getFormIdKey(self::SESSION_WORD),
|
|
['data' => $word, 'words' => $this->words, 'expires' => time() + $this->getTimeout()]
|
|
);
|
|
$this->word = $word;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set captcha word
|
|
*
|
|
* @return $this
|
|
*/
|
|
private function clearWord()
|
|
{
|
|
$this->session->unsetData($this->getFormIdKey(self::SESSION_WORD));
|
|
$this->word = null;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Override function to generate less curly captcha that will not cut off
|
|
*
|
|
* @see \Laminas\Captcha\Image::_randomSize()
|
|
* @return int
|
|
* @throws \Magento\Framework\Exception\LocalizedException
|
|
* @since 100.2.0
|
|
*/
|
|
protected function randomSize()
|
|
{
|
|
return Random::getRandomNumber(280, 300) / 100;
|
|
}
|
|
|
|
/**
|
|
* Overlap of the parent method
|
|
*
|
|
* @return void
|
|
*
|
|
* Now deleting old captcha images make crontab script
|
|
* @see \Magento\Captcha\Cron\DeleteExpiredImages::execute
|
|
*
|
|
* Added SuppressWarnings since this method is declared in parent class and we can not use other method name.
|
|
* @SuppressWarnings(PHPMD.ShortMethodName)
|
|
* @since 100.2.0
|
|
*/
|
|
protected function gc()
|
|
{
|
|
return; // required for static testing to pass
|
|
}
|
|
|
|
/**
|
|
* Get resource model
|
|
*
|
|
* @return \Magento\Captcha\Model\ResourceModel\Log
|
|
*/
|
|
private function getResourceModel()
|
|
{
|
|
return $this->resLogFactory->create();
|
|
}
|
|
}
|