Add log entries on user login or logout.

This commit is contained in:
Jan Böhmer 2020-01-26 13:59:30 +01:00
parent d6c6b973bf
commit c8375bfa8b
9 changed files with 323 additions and 5 deletions

View file

@ -154,7 +154,7 @@ abstract class AbstractLogEntry extends DBElement
* Get the user that caused the event associated with this log entry.
* @return User
*/
public function getUser(): User
public function getUser(): ?User
{
return $this->user;
}

View file

@ -22,6 +22,7 @@
namespace App\Entity\LogSystem;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\IpUtils;
/**
* This log entry is created when a user logs in.
@ -32,6 +33,12 @@ class UserLoginLogEntry extends AbstractLogEntry
{
protected $typeString = "user_login";
public function __construct(string $ip_address, bool $anonymize = true)
{
$this->level = self::LEVEL_INFO;
$this->setIPAddress($ip_address, $anonymize);
}
/**
* Return the (anonymized) IP address used to login the user.
* @return string
@ -44,10 +51,15 @@ class UserLoginLogEntry extends AbstractLogEntry
/**
* Sets the IP address used to login the user
* @param string $ip The IP address used to login the user.
* @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant
* @return $this
*/
public function setIPAddress(string $ip): self
public function setIPAddress(string $ip, bool $anonymize = true): self
{
if ($anonymize) {
$ip = IpUtils::anonymize($ip);
}
$this->extra['i'] = $ip;
return $this;
}

View file

@ -23,6 +23,7 @@ namespace App\Entity\LogSystem;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\IpUtils;
/**
* @ORM\Entity()
@ -32,6 +33,12 @@ class UserLogoutLogEntry extends AbstractLogEntry
{
protected $typeString = "user_logout";
public function __construct(string $ip_address, bool $anonymize = true)
{
$this->level = self::LEVEL_INFO;
$this->setIPAddress($ip_address, $anonymize);
}
/**
* Return the (anonymized) IP address used to login the user.
* @return string
@ -44,11 +51,18 @@ class UserLogoutLogEntry extends AbstractLogEntry
/**
* Sets the IP address used to login the user
* @param string $ip The IP address used to login the user.
* @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant
* @return $this
*/
public function setIPAddress(string $ip): self
public function setIPAddress(string $ip, bool $anonymize = true): self
{
if ($anonymize) {
$ip = IpUtils::anonymize($ip);
}
$this->extra['i'] = $ip;
return $this;
}
}

View file

@ -24,6 +24,9 @@ declare(strict_types=1);
namespace App\EventSubscriber;
use App\Entity\LogSystem\UserLoginLogEntry;
use App\Entity\UserSystem\User;
use App\Services\LogSystem\EventLogger;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
@ -37,15 +40,27 @@ final class LoginSuccessListener implements EventSubscriberInterface
{
protected $translator;
protected $flashBag;
protected $eventLogger;
protected $gpdr_compliance;
public function __construct(TranslatorInterface $translator, FlashBagInterface $flashBag)
public function __construct(TranslatorInterface $translator, FlashBagInterface $flashBag, EventLogger $eventLogger, bool $gpdr_compliance)
{
$this->translator = $translator;
$this->flashBag = $flashBag;
$this->eventLogger = $eventLogger;
$this->gpdr_compliance = $gpdr_compliance;
}
public function onLogin(InteractiveLoginEvent $event): void
{
$ip = $event->getRequest()->getClientIp();
$log = new UserLoginLogEntry($ip, $this->gpdr_compliance);
$user = $event->getAuthenticationToken()->getUser();
if ($user instanceof User) {
$log->setTargetElement($user);
}
$this->eventLogger->logAndFlush($log);
$this->flashBag->add('notice', $this->translator->trans('flash.login_successful'));
}

View file

@ -0,0 +1,57 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\EventSubscriber;
use App\Entity\LogSystem\UserLogoutLogEntry;
use App\Entity\UserSystem\User;
use App\Services\LogSystem\EventLogger;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
class LogoutListener implements LogoutHandlerInterface
{
protected $logger;
protected $gpdr_compliance;
public function __construct(EventLogger $logger, bool $gpdr_compliance)
{
$this->logger = $logger;
$this->gpdr_compliance = $gpdr_compliance;
}
/**
* @inheritDoc
*/
public function logout(Request $request, Response $response, TokenInterface $token)
{
$log = new UserLogoutLogEntry($request->getClientIp(), $this->gpdr_compliance);
$user = $token->getUser();
if ($user instanceof User) {
$log->setTargetElement($user);
}
$this->logger->logAndFlush($log);
}
}

View file

@ -0,0 +1,137 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Services\LogSystem;
use App\Entity\LogSystem\AbstractLogEntry;
use App\Entity\UserSystem\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
class EventLogger
{
protected $minimum_log_level;
protected $blacklist;
protected $whitelist;
protected $em;
protected $security;
public function __construct(int $minimum_log_level, array $blacklist, array $whitelist, EntityManagerInterface $em, Security $security)
{
$this->minimum_log_level = $minimum_log_level;
$this->blacklist = $blacklist;
$this->whitelist = $whitelist;
$this->em = $em;
$this->security = $security;
}
/**
* Adds the given log entry to the Log, if the entry fullfills the global configured criterias.
* The change will not be flushed yet.
* @param AbstractLogEntry $logEntry
* @return bool Returns true, if the event was added to log.
*/
public function log(AbstractLogEntry $logEntry): bool
{
$user = $this->security->getUser();
//If the user is not specified explicitly, set it to the current user
if (($user === null || $user instanceof User) && $logEntry->getUser() === null) {
if ($user === null) {
$repo = $this->em->getRepository(User::class);
$user = $repo->getAnonymousUser();
}
$logEntry->setUser($user);
}
if ($this->shouldBeAdded($logEntry)) {
$this->em->persist($logEntry);
return true;
}
return false;
}
/**
* Adds the given log entry to the Log, if the entry fullfills the global configured criterias and flush afterwards.
* @param AbstractLogEntry $logEntry
* @return bool Returns true, if the event was added to log.
*/
public function logAndFlush(AbstractLogEntry $logEntry): bool
{
$tmp = $this->log($logEntry);
$this->em->flush();
return $tmp;
}
public function shouldBeAdded(
AbstractLogEntry $logEntry,
?int $minimum_log_level = null,
?array $blacklist = null,
?array $whitelist = null
): bool {
//Apply the global settings, if nothing was specified
$minimum_log_level = $minimum_log_level ?? $this->minimum_log_level;
$blacklist = $blacklist ?? $this->blacklist;
$whitelist = $whitelist ?? $this->whitelist;
//Dont add the entry if it does not reach the minimum level
if ($logEntry->getLevel() > $minimum_log_level) {
return false;
}
//Check if the event type is black listed
if (!empty($blacklist) && $this->isObjectClassInArray($logEntry, $blacklist)) {
return false;
}
//Check for whitelisting
if (!empty($whitelist) && !$this->isObjectClassInArray($logEntry, $whitelist)) {
return false;
}
// By default all things should be added
return true;
}
/**
* Check if the object type is given in the classes array. This also works for inherited types
* @param object $object The object which should be checked
* @param string[] $classes The list of class names that should be used for checking.
* @return bool
*/
protected function isObjectClassInArray(object $object, array $classes): bool
{
//Check if the class is directly in the classes array
if (in_array(get_class($object), $classes)) {
return true;
}
//Iterate over all classes and check for inheritance
foreach ($classes as $class) {
if (is_a($object, $class)) {
return true;
}
}
return false;
}
}