diff --git a/Entity/AuthenticationEntry.php b/Entity/AuthenticationEntry.php new file mode 100644 index 0000000..dc8d2ce --- /dev/null +++ b/Entity/AuthenticationEntry.php @@ -0,0 +1,104 @@ +. + */ +namespace vierbergenlars\AuthserverStatsBundle\Entity; + +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Entity + * @ORM\Table(name="vierbergenlars_stats_auth") + */ +class AuthenticationEntry +{ + + /** + * @ORM\Column(type="integer") + * @ORM\Id + * @ORM\GeneratedValue(strategy="AUTO") + * + * @var integer + */ + private $id; + + /** + * @ORM\Column(name="ip", type="string") + * + * @var string + */ + private $ip; + + /** + * @ORM\Column(name="ts", type="datetime") + * + * @var \DateTime + */ + private $timeStamp; + + /** + * @ORM\Column(name="success", type="boolean") + * + * @var boolean + */ + private $success; + + public function __construct($ip, $success) + { + $this->ip = $ip; + $this->success = $success; + $this->timeStamp = new \DateTime(); + } + + /** + * + * @return number + */ + public function getId() + { + return $this->id; + } + + /** + * + * @return string + */ + public function getIp() + { + return $this->ip; + } + + /** + * + * @return \DateTime + */ + public function getTimeStamp() + { + return $this->timeStamp; + } + + /** + * + * @return boolean + */ + public function isSuccess() + { + return $this->success; + } +} \ No newline at end of file diff --git a/EventListener/AuthenticationStatsListener.php b/EventListener/AuthenticationStatsListener.php new file mode 100644 index 0000000..d9ce090 --- /dev/null +++ b/EventListener/AuthenticationStatsListener.php @@ -0,0 +1,119 @@ +. + */ +namespace vierbergenlars\AuthserverStatsBundle\EventListener; + +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; + +class AuthenticationStatsListener implements EventSubscriberInterface +{ + + /** + * + * @var RegistryInterface + */ + private $registry; + + /** + * + * @var RequestStack + */ + private $requestStack; + + public static function getSubscribedEvents() + { + return [ + StatsEvent::class => [ + [ + 'getAuthStats', + -1 + ] + ], + AuthenticationEvents::AUTHENTICATION_SUCCESS => [ + 'onAuthSuccess' + ], + AuthenticationEvents::AUTHENTICATION_FAILURE => [ + 'onAuthFailure' + ] + ]; + } + + public function __construct(RegistryInterface $registry, RequestStack $requestStack) + { + $this->registry = $registry; + $this->requestStack = $requestStack; + } + + public function onAuthSuccess(AuthenticationEvent $event) + { + if ($event->getAuthenticationToken() instanceof AnonymousToken) + return; + $authSuccess = new AuthenticationEntry($this->requestStack->getMasterRequest()->getClientIp(), true); + $em = $this->registry->getManagerForClass(AuthenticationEntry::class); + $em->persist($authSuccess); + $em->flush($authSuccess); + } + + 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); + } +} diff --git a/EventListener/LoginStatsListener.php b/EventListener/LoginStatsListener.php index 29f34da..38271d8 100644 --- a/EventListener/LoginStatsListener.php +++ b/EventListener/LoginStatsListener.php @@ -25,6 +25,7 @@ use Symfony\Bridge\Doctrine\RegistryInterface; use Symfony\Component\Security\Http\SecurityEvents; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use vierbergenlars\AuthserverStatsBundle\Entity\LoginEntry; +use Symfony\Component\Security\Core\AuthenticationEvents; class LoginStatsListener implements EventSubscriberInterface { diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 1112ce2..d83a1a4 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -31,5 +31,10 @@ + + + + + diff --git a/Resources/migrations/VersionAuthserverStats20171102211518.php b/Resources/migrations/VersionAuthserverStats20171102211518.php new file mode 100644 index 0000000..dbd772e --- /dev/null +++ b/Resources/migrations/VersionAuthserverStats20171102211518.php @@ -0,0 +1,47 @@ +. + */ +namespace Application\Migrations; + +use Doctrine\DBAL\Migrations\AbstractMigration; +use Doctrine\DBAL\Schema\Schema; + +class VersionAuthserverStats20171102211518 extends AbstractMigration +{ + + public function up(Schema $schema) + { + $authEntry = $schema->createTable('vierbergenlars_stats_auth'); + + $authEntry->addColumn('id', 'integer')->setAutoincrement(true); + $authEntry->addColumn('ip', 'string'); + $authEntry->addColumn('ts', 'datetime'); + $authEntry->addColumn('success', 'boolean'); + + $authEntry->setPrimaryKey([ + 'id' + ]); + } + + public function down(Schema $schema) + { + $schema->dropTable('vierbergenlars_stats_auth'); + } +} +