mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Add log entries on user login or logout.
This commit is contained in:
parent
d6c6b973bf
commit
c8375bfa8b
9 changed files with 323 additions and 5 deletions
|
@ -31,7 +31,6 @@ security:
|
|||
|
||||
# https://symfony.com/doc/current/security/form_login_setup.html
|
||||
form_login:
|
||||
|
||||
login_path: login
|
||||
check_path: login
|
||||
csrf_token_generator: security.csrf.token_manager
|
||||
|
@ -41,6 +40,7 @@ security:
|
|||
logout:
|
||||
path: logout
|
||||
target: homepage
|
||||
handlers: [App\EventSubscriber\LogoutListener]
|
||||
|
||||
remember_me:
|
||||
secret: '%kernel.secret%'
|
||||
|
|
|
@ -20,6 +20,8 @@ parameters:
|
|||
sender_email: 'noreply@partdb.changeme' # The email address from which all emails are sent from
|
||||
sender_name: 'Part-DB Mailer' # The name that will be used for all mails sent by Part-DB
|
||||
allow_email_pw_reset: '%env(validMailDSN:MAILER_DSN)%' # Config if users are able, to reset their password by email. By default this enabled, when a mail server is configured.
|
||||
# If this option is activated, IP addresses are anonymized to be GPDR compliant
|
||||
gpdr_compliance: true
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
@ -28,6 +30,7 @@ services:
|
|||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
bind:
|
||||
bool $demo_mode: '%demo_mode%'
|
||||
bool $gpdr_compliance : '%gpdr_compliance%'
|
||||
|
||||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
|
@ -47,6 +50,17 @@ services:
|
|||
$email: '%sender_email%'
|
||||
$name: '%sender_name%'
|
||||
|
||||
App\Services\LogSystem\EventLogger:
|
||||
arguments:
|
||||
# By default only log events which has minimum info level (debug levels are not logged)
|
||||
# 7 is lowest level (debug), 0 highest (emergency
|
||||
$minimum_log_level: 6
|
||||
# Event classes specified here are not saved to DB
|
||||
$blacklist: []
|
||||
# Only the event classes specified here are saved to DB (set to []) to log all events
|
||||
$whitelist: []
|
||||
|
||||
|
||||
Liip\ImagineBundle\Service\FilterService:
|
||||
alias: 'liip_imagine.service.filter'
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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'));
|
||||
}
|
||||
|
||||
|
|
57
src/EventSubscriber/LogoutListener.php
Normal file
57
src/EventSubscriber/LogoutListener.php
Normal 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);
|
||||
}
|
||||
}
|
137
src/Services/LogSystem/EventLogger.php
Normal file
137
src/Services/LogSystem/EventLogger.php
Normal 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;
|
||||
}
|
||||
}
|
69
tests/Services/LogSystem/EventLoggerTest.php
Normal file
69
tests/Services/LogSystem/EventLoggerTest.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?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\Tests\Services\LogSystem;
|
||||
|
||||
use App\Entity\LogSystem\AbstractLogEntry;
|
||||
use App\Entity\LogSystem\UserLoginLogEntry;
|
||||
use App\Entity\LogSystem\UserLogoutLogEntry;
|
||||
use App\Services\LogSystem\EventLogger;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class EventLoggerTest extends WebTestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EventLogger
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp(); // TODO: Change the autogenerated stub
|
||||
|
||||
//Get an service instance.
|
||||
self::bootKernel();
|
||||
$this->service = self::$container->get(EventLogger::class);
|
||||
}
|
||||
|
||||
public function testShouldBeAdded()
|
||||
{
|
||||
$event1 = new UserLoginLogEntry('127.0.0.1');
|
||||
$event2 = new UserLogoutLogEntry('127.0.0.1');
|
||||
$event2->setLevel(AbstractLogEntry::LEVEL_CRITICAL);
|
||||
|
||||
|
||||
//Test without restrictions
|
||||
$this->assertTrue($this->service->shouldBeAdded($event1, 7, [], []));
|
||||
|
||||
//Test minimum log level
|
||||
$this->assertFalse($this->service->shouldBeAdded($event1, 2, [], []));
|
||||
$this->assertTrue($this->service->shouldBeAdded($event2, 2, [], []));
|
||||
|
||||
//Test blacklist
|
||||
$this->assertFalse($this->service->shouldBeAdded($event1, 7, [UserLoginLogEntry::class], []));
|
||||
$this->assertTrue($this->service->shouldBeAdded($event2, 7, [UserLoginLogEntry::class], []));
|
||||
|
||||
//Test whitelist
|
||||
$this->assertFalse($this->service->shouldBeAdded($event1, 7, [], [UserLogoutLogEntry::class]));
|
||||
$this->assertTrue($this->service->shouldBeAdded($event2, 7, [], [UserLogoutLogEntry::class]));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue