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');
+ }
+}
+