--- /dev/null
+<?php
+
+namespace Wallabag\UserBundle\EventListener;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Security\Core\AuthenticationEvents;
+
+class AuthenticationFailureListener implements EventSubscriberInterface
+{
+ private $requestStack;
+ private $logger;
+
+ public function __construct(RequestStack $requestStack, LoggerInterface $logger)
+ {
+ $this->requestStack = $requestStack;
+ $this->logger = $logger;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents()
+ {
+ return [
+ AuthenticationEvents::AUTHENTICATION_FAILURE => 'onAuthenticationFailure',
+ ];
+ }
+
+ /**
+ * On failure, add a custom error in log so server admin can configure fail2ban to block IP from people who try to login too much.
+ */
+ public function onAuthenticationFailure()
+ {
+ $request = $this->requestStack->getMasterRequest();
+
+ $this->logger->error('Authentication failure for user "'.$request->request->get('_username').'", from IP "'.$request->getClientIp().'", with UA: "'.$request->server->get('HTTP_USER_AGENT').'".');
+ }
+}
--- /dev/null
+<?php
+
+namespace Tests\Wallabag\UserBundle\EventListener;
+
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\HttpFoundation\Request;
+use Wallabag\UserBundle\EventListener\AuthenticationFailureListener;
+use Monolog\Logger;
+use Monolog\Handler\TestHandler;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Security\Core\AuthenticationEvents;
+use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
+
+class AuthenticationFailureListenerTest extends \PHPUnit_Framework_TestCase
+{
+ private $requestStack;
+ private $logHandler;
+ private $listener;
+ private $dispatcher;
+
+ protected function setUp()
+ {
+ $request = Request::create('/');
+ $request->request->set('_username', 'admin');
+
+ $this->requestStack = new RequestStack();
+ $this->requestStack->push($request);
+
+ $this->logHandler = new TestHandler();
+ $logger = new Logger('test', [$this->logHandler]);
+
+ $this->listener = new AuthenticationFailureListener(
+ $this->requestStack,
+ $logger
+ );
+
+ $this->dispatcher = new EventDispatcher();
+ $this->dispatcher->addSubscriber($this->listener);
+ }
+
+ public function testOnAuthenticationFailure()
+ {
+ $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $exception = $this->getMockBuilder('Symfony\Component\Security\Core\Exception\AuthenticationException')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $event = new AuthenticationFailureEvent(
+ $token,
+ $exception
+ );
+
+ $this->dispatcher->dispatch(
+ AuthenticationEvents::AUTHENTICATION_FAILURE,
+ $event
+ );
+
+ $records = $this->logHandler->getRecords();
+
+ $this->assertCount(1, $records);
+ $this->assertSame('Authentication failure for user "admin", from IP "127.0.0.1", with UA: "Symfony/3.X".', $records[0]['message']);
+ }
+}