<?php declare(strict_types=1);
namespace Valkarch\FormBotBlocker\Subscriber;
use Exception;
use Shopware\Core\PlatformRequest;
use Shopware\Core\Framework\Context;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Shopware\Storefront\Framework\Routing\RequestTransformer;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Valkarch\FormBotBlocker\Service\FormService;
use Valkarch\FormBotBlocker\Service\BlockService;
use Valkarch\FormBotBlocker\Service\PluginService;
use Valkarch\FormBotBlocker\Service\BotLogService;
use Valkarch\FormBotBlocker\Service\CaptchaService;
use Valkarch\FormBotBlocker\Service\HoneypotService;
use Valkarch\FormBotBlocker\Service\ReactionTimeService;
use Valkarch\FormBotBlocker\Service\ExternalPluginsService;
use Valkarch\FormBotBlocker\Content\FormSubmit\FormSubmitDefinition;
use Valkarch\FormBotBlocker\Content\BotLogProtector\BotLogProtectorEntity;
class RequestSubscriber implements EventSubscriberInterface
{
/**
* @var FormService
*/
private FormService $formService;
/**
* @var BlockService
*/
private BlockService $blockService;
/**
* @var PluginService
*/
private PluginService $pluginService;
/**
* @var BotLogService
*/
private BotLogService $botLogService;
/**
* @var CaptchaService
*/
private CaptchaService $captchaService;
/**
* @var HoneypotService
*/
private HoneypotService $honeypotService;
/**
* @var ReactionTimeService
*/
private ReactionTimeService $reactionTimeService;
/**
* @var ExternalPluginsService
*/
private ExternalPluginsService $externalPluginsService;
/**
* RequestSubscriber constructor.
*
* @param FormService $formService
* @param BlockService $blockService
* @param PluginService $pluginService
* @param BotLogService $botLogService
* @param CaptchaService $captchaService
* @param HoneypotService $honeypotService
* @param ReactionTimeService $reactionTimeService
* @param ExternalPluginsService $externalPluginsService
*/
public function __construct(
FormService $formService,
BlockService $blockService,
PluginService $pluginService,
BotLogService $botLogService,
CaptchaService $captchaService,
HoneypotService $honeypotService,
ReactionTimeService $reactionTimeService,
ExternalPluginsService $externalPluginsService
)
{
$this->formService = $formService;
$this->blockService = $blockService;
$this->pluginService = $pluginService;
$this->botLogService = $botLogService;
$this->captchaService = $captchaService;
$this->honeypotService = $honeypotService;
$this->reactionTimeService = $reactionTimeService;
$this->externalPluginsService = $externalPluginsService;
}
/**
* The event names to listen to
*
* @return array<string, string>
*/
public static function getSubscribedEvents(): array
{
return [
RequestEvent::class => 'onRequest',
KernelEvents::CONTROLLER => ['onKernelController', -19]
];
}
/**
* Deactivate captcha validation if needed
* Use before KERNEL_CONTROLLER_EVENT_SCOPE_VALIDATE = -20;
*
* @param KernelEvent $event
*/
public function onKernelController(KernelEvent $event): void
{
if ($event->getRequest()->hasSession() &&
($session = $event->getRequest()->getSession())->isStarted() &&
$session->has(PluginService::SESSION_FORM_BOT_BLOCKER_CAPTCHA_STATUS)
) {
$session->remove(PluginService::SESSION_FORM_BOT_BLOCKER_CAPTCHA_STATUS);
// Deactivate captcha validation
$event->getRequest()->attributes->set(PlatformRequest::ATTRIBUTE_CAPTCHA, false);
}
}
/**
* Evaluation of the protective measures
*
* @param RequestEvent $event
* @throws Exception
*/
public function onRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$route = $request->attributes->get('_route');
// Unsupported requests
if (!$route ||
$request->getMethod() != 'POST' ||
$request->request->count() === 0
) {
return;
}
$routeFormLogName = $this->formService->getRouteFormLogName($route);
// Unsupported forms
if (!$routeFormLogName) {
return;
}
$session = $request->getSession();
$ip = $this->formService->getRealIpAddr();
$context = Context::createDefaultContext();
// Is ip blocked
if ($this->blockService->isIpBlocked($ip, $context)) {
$response = $this->formService->getResponse($session, $request, 200, 'invalid_ip', 'formBotBlocker.errors.ipBlocked');
$event->setResponse($response);
return;
}
$salesChannelId = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
// Not activating forms
if (!$this->formService->isFormToProtectActive($route, $salesChannelId)) {
return;
}
// Not supported external plugin versioned form
if ($this->externalPluginsService->isVersionedForm($request->request)) {
if (!$this->externalPluginsService->isSupportedVersionedForm($request->request, $context)) {
return;
}
}
$requestParams = $request->request->all();
$requestUrl = $request->attributes->get(RequestTransformer::ORIGINAL_REQUEST_URI);
$shopUrl = $request->attributes->get(RequestTransformer::SALES_CHANNEL_ABSOLUTE_BASE_URL);
// Is simple honeypot active
if ($this->pluginService->isSimpleHoneypotProtectorActive($salesChannelId)) {
// Simple honeypot protector
if (!$this->honeypotService->isValidSimpleHoneypot($request->request)) {
$this->botLogService->saveLog($ip,
$shopUrl,
$routeFormLogName,
BotLogProtectorEntity::SIMPLE_HONEYPOT_PROTECTOR,
$requestUrl,
$requestParams,
$salesChannelId,
$context
);
$response = $this->formService->getResponse($session, $request, 200, 'invalid_input', 'formBotBlocker.errors.honeypot');
$event->setResponse($response);
return;
}
}
// Is dynamic honeypot active
if ($this->pluginService->isDynamicHoneypotProtectorActive($salesChannelId)) {
// Dynamic honeypot protector
if (!$this->honeypotService->isValidDynamicHoneypot($request->request, $context)) {
$this->botLogService->saveLog($ip,
$shopUrl,
$routeFormLogName,
BotLogProtectorEntity::DYNAMIC_HONEYPOT_PROTECTOR,
$requestUrl,
$requestParams,
$salesChannelId,
$context
);
$response = $this->formService->getResponse($session, $request, 200, 'invalid_input', 'formBotBlocker.errors.honeypot');
$event->setResponse($response);
return;
}
}
// Is reaction time active
if ($this->pluginService->isReactionTimeProtectorActive($salesChannelId)) {
// Reaction time protector
if (!$this->reactionTimeService->isValidReactionTime($session, $salesChannelId)) {
$this->botLogService->saveLog($ip,
$shopUrl,
$routeFormLogName,
BotLogProtectorEntity::REACTION_TIME_PROTECTOR,
$requestUrl,
$requestParams,
$salesChannelId,
$context
);
$response = $this->formService->getResponse($session, $request, 200, 'invalid_reaction_time', 'formBotBlocker.errors.reactionTime');
$event->setResponse($response);
return;
}
}
$actionProtectorRouteFormLogName = $this->formService->getActionProtectorRouteFormLogName($route);
if ($actionProtectorRouteFormLogName) {
// Is form action protector active
if ($this->pluginService->isFormActionProtectorActive($salesChannelId)) {
$this->botLogService->saveLog($ip,
$shopUrl,
$actionProtectorRouteFormLogName,
BotLogProtectorEntity::FORM_ACTION_PROTECTOR,
$requestUrl,
$requestParams,
$salesChannelId,
$context
);
$response = $this->formService->getResponse($session, $request, 200, 'invalid_form', 'formBotBlocker.errors.formAction');
$event->setResponse($response);
return;
}
}
// Is captcha defer active
if ($this->pluginService->isCaptchaDeferActive($salesChannelId)) {
// Set deactivate captcha validation flag in session
if (!$this->captchaService->isCaptchaActive($ip, $salesChannelId, $context)) {
$session->set(PluginService::SESSION_FORM_BOT_BLOCKER_CAPTCHA_STATUS, false);
}
// Save form submits
$writtenEvent = $this->captchaService->saveFormSubmit($ip, $salesChannelId, $context);
// Automatically delete old form submits of all ip's
if ($formSubmitEvent = $writtenEvent->getEventByEntityName(FormSubmitDefinition::ENTITY_NAME)) {
$submits = $formSubmitEvent->getWriteResults()[0]->getPayload()['submits'];
// Delete when someone has made 3 summits
if ($submits === 3) {
$this->captchaService->deleteOldFormSubmitsOfAllUsers($salesChannelId, $context);
}
}
}
}
}