. */ namespace vierbergenlars\AuthserverStatsBundle\EventListener; use App\Entity\User; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use vierbergenlars\AuthserverStatsBundle\Event\StatsEvent; use Symfony\Bridge\Doctrine\RegistryInterface; use Symfony\Component\Security\Core\AuthenticationEvents; use Symfony\Component\Security\Core\Event\AuthenticationEvent; use vierbergenlars\AuthserverStatsBundle\Entity\AuthenticationEntry; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Http\FirewallMapInterface; use Symfony\Bundle\SecurityBundle\Security\FirewallMap; use Symfony\Component\Security\Http\SecurityEvents; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; class AuthenticationStatsListener implements EventSubscriberInterface { /** * * @var RegistryInterface */ private $registry; /** * * @var RequestStack */ private $requestStack; /** * * @var FirewallMapInterface */ private $firewallMap; public static function getSubscribedEvents() { return [ StatsEvent::class => [ [ 'getAuthStats', -1 ], [ 'getAuthFailureIps' ] ], AuthenticationEvents::AUTHENTICATION_SUCCESS => 'onAuthSuccess', AuthenticationEvents::AUTHENTICATION_FAILURE => 'onAuthFailure', SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin' ]; } public function __construct(RegistryInterface $registry, RequestStack $requestStack, FirewallMapInterface $firewallMap) { $this->registry = $registry; $this->requestStack = $requestStack; $this->firewallMap = $firewallMap; } private function insertAuthSuccess(User $user) { $request = $this->requestStack->getMasterRequest(); $authSuccess = new AuthenticationEntry($request->getClientIp(), true, $user); $em = $this->registry->getManagerForClass(AuthenticationEntry::class); $em->persist($authSuccess); $em->flush($authSuccess); } public function onInteractiveLogin(InteractiveLoginEvent $event) { if ($this->isStatelessFirewall()) return; $this->insertAuthSuccess($event->getAuthenticationToken() ->getUser()); } private function isStatelessFirewall() { $request = $this->requestStack->getMasterRequest(); if ($this->firewallMap instanceof FirewallMap) { $config = $this->firewallMap->getFirewallConfig($request); /* @var $config \Symfony\Bundle\SecurityBundle\Security\FirewallConfig */ if ($config) { if ($config->isStateless()) return true; } } return false; } public function onAuthSuccess(AuthenticationEvent $event) { if ($event->getAuthenticationToken() instanceof AnonymousToken) return; if (!$this->isStatelessFirewall()) return; $this->insertAuthSuccess($event->getAuthenticationToken() ->getUser()); } public function onAuthFailure(AuthenticationFailureEvent $event) { $authFailure = new AuthenticationEntry($this->requestStack->getMasterRequest()->getClientIp(), false); $em = $this->registry->getManagerForClass(AuthenticationEntry::class); $em->persist($authFailure); $em->flush($authFailure); } public function getAuthStats(StatsEvent $event) { if (!$event->isEnabled('login')) return; $event->setMuninConfig('login', $event->getMuninConfig('login') + [ 'auth_succ.label' => 'Authentication passes', 'auth_succ.type' => 'COUNTER', 'auth_fail.label' => 'Authentication failures', 'auth_fail.type' => 'COUNTER' ]); $queryBuilder = $this->registry->getRepository(AuthenticationEntry::class)->createQueryBuilder('e'); /* @var $queryBuilder \Doctrine\ORM\QueryBuilder */ $queryBuilder->select('count(e)', 'e.success')->groupBy('e.success'); $rawStats = $queryBuilder->getQuery()->getArrayResult(); $stats = [ 'login.auth_succ' => 0, 'login.auth_fail' => 0 ]; foreach ($rawStats as $rawStat) { if ($rawStat['success']) { $stats['login.auth_succ'] += $rawStat['1']; } else { $stats['login.auth_fail'] += $rawStat['1']; } } $event->addStatistics($stats); } public function getAuthFailureIps(StatsEvent $event) { if (!$event->isEnabled('login_fail_ips')) return; $queryBuilder = $this->registry->getRepository(AuthenticationEntry::class)->createQueryBuilder('e'); /* @var $queryBuilder \Doctrine\ORM\QueryBuilder */ $queryBuilder->select('count(e) AS c', 'e.ip') ->groupBy('e.ip') ->where('e.success = false AND e.timeStamp > :time') ->setParameter('time', new \DateTime('-1 day')) ->orderBy('c', 'DESC') ->setMaxResults(20); $rawStats = $queryBuilder->getQuery()->getArrayResult(); $config = [ 'graph_title' => 'Authserver authentication failures', 'graph_vlabel' => 'Failures/day', 'graph_category' => 'authserver' ]; foreach ($rawStats as $rawStat) { $ipHash = md5($rawStat['ip']); $config += [ 'auth_fail_' . $ipHash . '.label' => $rawStat['ip'] ]; $event->addStatistic('login_fail_ips.auth_fail_' . $ipHash, $rawStat['c']); } $event->setMuninConfig('login_fail_ips', $config); } }