diff --git a/src/Command/SetPasswordCommand.php b/src/Command/SetPasswordCommand.php index 2ea14b5e..8603c17e 100644 --- a/src/Command/SetPasswordCommand.php +++ b/src/Command/SetPasswordCommand.php @@ -43,12 +43,16 @@ declare(strict_types=1); namespace App\Command; use App\Entity\UserSystem\User; +use App\Events\SecurityEvent; +use App\Events\SecurityEvents; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; class SetPasswordCommand extends Command @@ -57,11 +61,13 @@ class SetPasswordCommand extends Command protected $entityManager; protected $encoder; + protected $eventDispatcher; - public function __construct(EntityManagerInterface $entityManager, UserPasswordEncoderInterface $passwordEncoder) + public function __construct(EntityManagerInterface $entityManager, UserPasswordEncoderInterface $passwordEncoder, EventDispatcherInterface $eventDispatcher) { $this->entityManager = $entityManager; $this->encoder = $passwordEncoder; + $this->eventDispatcher = $eventDispatcher; parent::__construct(); } @@ -126,6 +132,9 @@ class SetPasswordCommand extends Command $io->success('Password was set successful! You can now log in using the new password.'); + $security_event = new SecurityEvent($user); + $this->eventDispatcher->dispatch($security_event, SecurityEvents::PASSWORD_CHANGED); + return 0; } } diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php index d5e0e4ee..eda5e2d5 100644 --- a/src/Controller/AdminPages/BaseAdminController.php +++ b/src/Controller/AdminPages/BaseAdminController.php @@ -46,10 +46,11 @@ use App\DataTables\LogDataTable; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\UserSystem\User; +use App\Events\SecurityEvent; +use App\Events\SecurityEvents; use App\Exceptions\AttachmentDownloadException; use App\Form\AdminPages\ImportType; use App\Form\AdminPages\MassCreationForm; -use App\Services\Attachments\AttachmentManager; use App\Services\Attachments\AttachmentSubmitHandler; use App\Services\EntityExporter; use App\Services\EntityImporter; @@ -61,6 +62,8 @@ use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use Omines\DataTablesBundle\DataTableFactory; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -87,11 +90,13 @@ abstract class BaseAdminController extends AbstractController protected $historyHelper; protected $timeTravel; protected $dataTableFactory; + /** @var EventDispatcher */ + protected $eventDispatcher; public function __construct(TranslatorInterface $translator, UserPasswordEncoderInterface $passwordEncoder, AttachmentSubmitHandler $attachmentSubmitHandler, EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel, - DataTableFactory $dataTableFactory) + DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher) { if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) { throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!'); @@ -112,6 +117,7 @@ abstract class BaseAdminController extends AbstractController $this->historyHelper = $historyHelper; $this->timeTravel = $timeTravel; $this->dataTableFactory = $dataTableFactory; + $this->eventDispatcher = $eventDispatcher; } protected function _edit(AbstractNamedDBElement $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response @@ -164,6 +170,9 @@ abstract class BaseAdminController extends AbstractController $entity->setPassword($password); //By default the user must change the password afterwards $entity->setNeedPwChange(true); + + $event = new SecurityEvent($entity); + $this->eventDispatcher->dispatch($event, SecurityEvents::PASSWORD_CHANGED); } //Upload passed files diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 347ac459..9e8f0ac8 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -42,7 +42,11 @@ declare(strict_types=1); namespace App\Controller; +use App\Entity\UserSystem\User; +use App\Events\SecurityEvent; +use App\Events\SecurityEvents; use App\Services\PasswordResetManager; +use Doctrine\ORM\EntityManagerInterface; use Gregwar\CaptchaBundle\Type\CaptchaType; use RuntimeException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -56,6 +60,7 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\Translation\TranslatorInterface; class SecurityController extends AbstractController @@ -137,7 +142,7 @@ class SecurityController extends AbstractController * * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response */ - public function pwResetNewPw(PasswordResetManager $passwordReset, Request $request, ?string $user = null, ?string $token = null) + public function pwResetNewPw(PasswordResetManager $passwordReset, Request $request, EntityManagerInterface $em, EventDispatcherInterface $eventDispatcher, ?string $user = null, ?string $token = null) { if (! $this->allow_email_pw_reset) { throw new AccessDeniedHttpException('The password reset via email is disabled!'); @@ -189,6 +194,11 @@ class SecurityController extends AbstractController } else { $this->addFlash('success', 'pw_reset.new_pw.success'); + $repo = $em->getRepository(User::class); + $u = $repo->findOneBy(['name' => $data['username']]); + $event = new SecurityEvent($u); + $eventDispatcher->dispatch($event, SecurityEvents::PASSWORD_RESET); + return $this->redirectToRoute('login'); } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index a7d9ef5a..4f3c0068 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -44,6 +44,8 @@ namespace App\Controller; use App\Entity\Attachments\UserAttachment; use App\Entity\UserSystem\User; +use App\Events\SecurityEvent; +use App\Events\SecurityEvents; use App\Form\Permissions\PermissionsType; use App\Form\UserAdminForm; use App\Services\EntityExporter; @@ -52,6 +54,7 @@ use App\Services\StructuralElementRecursionHelper; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use Symfony\Component\Asset\Packages; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -97,6 +100,9 @@ class UserController extends AdminPages\BaseAdminController $entity->invalidateTrustedDeviceTokens(); $em->flush(); + $event = new SecurityEvent($entity); + $this->eventDispatcher->dispatch($event, SecurityEvents::TFA_ADMIN_RESET); + $this->addFlash('success', 'user.edit.reset_success'); } else { $this->addFlash('danger', 'csfr_invalid'); diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index 75df0b2c..58e0fabf 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -44,6 +44,8 @@ namespace App\Controller; use App\Entity\UserSystem\U2FKey; use App\Entity\UserSystem\User; +use App\Events\SecurityEvent; +use App\Events\SecurityEvents; use App\Form\TFAGoogleSettingsType; use App\Form\UserSettingsType; use App\Services\TFA\BackupCodeManager; @@ -51,6 +53,8 @@ use Doctrine\ORM\EntityManagerInterface; use RuntimeException; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -68,10 +72,13 @@ use Symfony\Component\Validator\Constraints\Length; class UserSettingsController extends AbstractController { protected $demo_mode; + /** @var EventDispatcher */ + protected $eventDispatcher; - public function __construct(bool $demo_mode) + public function __construct(bool $demo_mode, EventDispatcherInterface $eventDispatcher) { $this->demo_mode = $demo_mode; + $this->eventDispatcher = $eventDispatcher; } /** @@ -142,6 +149,9 @@ class UserSettingsController extends AbstractController $entityManager->remove($u2f); $entityManager->flush(); $this->addFlash('success', 'tfa.u2f.u2f_delete.success'); + + $security_event = new SecurityEvent($user); + $this->eventDispatcher->dispatch($security_event, SecurityEvents::U2F_REMOVED); } } else { $this->addFlash('error', 'csfr_invalid'); @@ -174,6 +184,9 @@ class UserSettingsController extends AbstractController $user->invalidateTrustedDeviceTokens(); $entityManager->flush(); $this->addFlash('success', 'tfa_trustedDevice.invalidate.success'); + + $security_event = new SecurityEvent($user); + $this->eventDispatcher->dispatch($security_event, SecurityEvents::TRUSTED_DEVICE_RESET); } else { $this->addFlash('error', 'csfr_invalid'); } @@ -200,6 +213,8 @@ class UserSettingsController extends AbstractController throw new RuntimeException('This controller only works only for Part-DB User objects!'); } + $security_event = new SecurityEvent($user); + /*************************** * User settings form ***************************/ @@ -278,6 +293,7 @@ class UserSettingsController extends AbstractController $em->persist($user); $em->flush(); $this->addFlash('success', 'user.settings.pw_changed_flash'); + $this->eventDispatcher->dispatch($security_event, SecurityEvents::PASSWORD_CHANGED); } //Handle 2FA things @@ -294,9 +310,12 @@ class UserSettingsController extends AbstractController //Save 2FA settings (save secrets) $user->setGoogleAuthenticatorSecret($google_form->get('googleAuthenticatorSecret')->getData()); $backupCodeManager->enableBackupCodes($user); + $em->flush(); $this->addFlash('success', 'user.settings.2fa.google.activated'); + $this->eventDispatcher->dispatch($security_event, SecurityEvents::GOOGLE_ENABLED); + return $this->redirectToRoute('user_settings'); } @@ -305,6 +324,7 @@ class UserSettingsController extends AbstractController $backupCodeManager->disableBackupCodesIfUnused($user); $em->flush(); $this->addFlash('success', 'user.settings.2fa.google.disabled'); + $this->eventDispatcher->dispatch($security_event, SecurityEvents::GOOGLE_DISABLED); return $this->redirectToRoute('user_settings'); } @@ -322,6 +342,7 @@ class UserSettingsController extends AbstractController $backupCodeManager->regenerateBackupCodes($user); $em->flush(); $this->addFlash('success', 'user.settings.2fa.backup_codes.regenerated'); + $this->eventDispatcher->dispatch($security_event, SecurityEvents::BACKUP_KEYS_RESET); } /****************************** diff --git a/src/Entity/LogSystem/AbstractLogEntry.php b/src/Entity/LogSystem/AbstractLogEntry.php index 0e376565..deb39a6b 100644 --- a/src/Entity/LogSystem/AbstractLogEntry.php +++ b/src/Entity/LogSystem/AbstractLogEntry.php @@ -83,7 +83,8 @@ use Psr\Log\LogLevel; * 8 = "ConfigChangedLogEntry", * 9 = "InstockChangedLogEntry", * 10 = "DatabaseUpdatedLogEntry", - * 11 = "CollectionElementDeleted" + * 11 = "CollectionElementDeleted", + * 12 = "SecurityEventLogEntry", * }) */ abstract class AbstractLogEntry extends AbstractDBElement diff --git a/src/Entity/LogSystem/SecurityEventLogEntry.php b/src/Entity/LogSystem/SecurityEventLogEntry.php new file mode 100644 index 00000000..ae2b8d05 --- /dev/null +++ b/src/Entity/LogSystem/SecurityEventLogEntry.php @@ -0,0 +1,126 @@ +. + */ + +namespace App\Entity\LogSystem; + + +use App\Entity\Base\AbstractDBElement; +use App\Entity\UserSystem\User; +use App\Events\SecurityEvents; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\HttpFoundation\IpUtils; + +/** + * This log entry is created when something security related to a user happens. + * + * @ORM\Entity() + */ +class SecurityEventLogEntry extends AbstractLogEntry +{ + public const SECURITY_TYPE_MAPPING = [ + 0 => SecurityEvents::PASSWORD_CHANGED, + 1 => SecurityEvents::PASSWORD_RESET, + 2 => SecurityEvents::BACKUP_KEYS_RESET, + 3 => SecurityEvents::U2F_ADDED, + 4 => SecurityEvents::U2F_REMOVED, + 5 => SecurityEvents::GOOGLE_ENABLED, + 6 => SecurityEvents::GOOGLE_DISABLED, + 7 => SecurityEvents::TRUSTED_DEVICE_RESET, + 8 => SecurityEvents::TFA_ADMIN_RESET, + ]; + + public function __construct(string $type, string $ip_address, bool $anonymize = true) + { + parent::__construct(); + $this->level = self::LEVEL_INFO; + $this->setIPAddress($ip_address, $anonymize); + $this->setEventType($type); + $this->level = self::LEVEL_NOTICE; + } + + public function setTargetElement(?AbstractDBElement $element): AbstractLogEntry + { + if (!$element instanceof User) { + throw new \InvalidArgumentException('Target element must be a User object!'); + } + return parent::setTargetElement($element); + } + + /** + * Sets the type of this log entry. + * @param string $type + * @return $this + */ + public function setEventType(string $type): self + { + $key = array_search($type, static::SECURITY_TYPE_MAPPING); + if ($key === false) { + throw new \InvalidArgumentException('Given event type is not existing!'); + } + $this->extra['e'] = $key; + return $this; + } + + public function getType(): string + { + return $this->getEventType(); + } + + /** + * Return what event this log entry represents (e.g. password_reset) + * @return string + */ + public function getEventType(): string + { + $key = $this->extra['e']; + if (isset(static::SECURITY_TYPE_MAPPING[$key])) { + return static::SECURITY_TYPE_MAPPING[$key]; + } + + return 'unkown'; + } + + /** + * Return the (anonymized) IP address used to login the user. + * + * @return string + */ + public function getIPAddress(): string + { + return $this->extra['i']; + } + + /** + * 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, bool $anonymize = true): self + { + if ($anonymize) { + $ip = IpUtils::anonymize($ip); + } + $this->extra['i'] = $ip; + return $this; + } +} \ No newline at end of file diff --git a/src/EventSubscriber/SecurityEventLoggerSubscriber.php b/src/EventSubscriber/SecurityEventLoggerSubscriber.php new file mode 100644 index 00000000..77f2f3ca --- /dev/null +++ b/src/EventSubscriber/SecurityEventLoggerSubscriber.php @@ -0,0 +1,126 @@ +. + */ + +namespace App\EventSubscriber; + + +use App\Entity\LogSystem\SecurityEventLogEntry; +use App\Events\SecurityEvent; +use App\Events\SecurityEvents; +use App\Services\LogSystem\EventLogger; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +final class SecurityEventLoggerSubscriber implements EventSubscriberInterface +{ + + private $requestStack; + private $gpdr_compliant; + private $eventLogger; + + public function __construct(RequestStack $requestStack, EventLogger $eventLogger, bool $gpdr_compliance) + { + $this->requestStack = $requestStack; + $this->gpdr_compliant = $gpdr_compliance; + $this->eventLogger = $eventLogger; + } + + protected function addLog(string $type, SecurityEvent $event): void + { + $anonymize = $this->gpdr_compliant; + + $request = $this->requestStack->getCurrentRequest(); + if ($request !== null) { + $ip = $request->getClientIp() ?? 'unknown'; + } else { + $ip = "Console"; + //Dont try to apply IP filter rules to non numeric string + $anonymize = false; + } + + $log = new SecurityEventLogEntry($type, $ip, $anonymize); + $log->setTargetElement($event->getTargetUser()); + $this->eventLogger->logAndFlush($log); + } + + /** + * @inheritDoc + */ + public static function getSubscribedEvents() + { + return [ + SecurityEvents::U2F_ADDED => 'u2f_added', + SecurityEvents::PASSWORD_CHANGED => 'password_changed', + SecurityEvents::TRUSTED_DEVICE_RESET => 'trusted_device_reset', + SecurityEvents::U2F_REMOVED => 'u2f_removed', + SecurityEvents::BACKUP_KEYS_RESET => 'backup_keys_reset', + SecurityEvents::PASSWORD_RESET => 'password_reset', + SecurityEvents::GOOGLE_DISABLED => 'google_disabled', + SecurityEvents::GOOGLE_ENABLED => 'google_enabled', + SecurityEvents::TFA_ADMIN_RESET => 'tfa_admin_reset', + ]; + } + + public function tfa_admin_reset(SecurityEvent $event): void + { + $this->addLog(SecurityEvents::TFA_ADMIN_RESET, $event); + } + + public function google_enabled(SecurityEvent $event): void + { + $this->addLog(SecurityEvents::GOOGLE_ENABLED, $event); + } + + public function google_disabled(SecurityEvent $event): void + { + $this->addLog(SecurityEvents::GOOGLE_DISABLED, $event); + } + + public function password_reset(SecurityEvent $event): void + { + $this->addLog(SecurityEvents::PASSWORD_RESET, $event); + } + + public function backup_keys_reset(SecurityEvent $event): void + { + $this->addLog(SecurityEvents::BACKUP_KEYS_RESET, $event); + } + + public function u2f_removed(SecurityEvent $event): void + { + $this->addLog(SecurityEvents::U2F_REMOVED, $event); + } + + public function u2f_added(SecurityEvent $event): void + { + $this->addLog(SecurityEvents::U2F_ADDED, $event); + } + + public function password_changed(SecurityEvent $event): void + { + $this->addLog(SecurityEvents::PASSWORD_CHANGED, $event); + } + + public function trusted_device_reset(SecurityEvent $event): void + { + $this->addLog(SecurityEvents::TRUSTED_DEVICE_RESET, $event); + } +} \ No newline at end of file diff --git a/src/EventSubscriber/U2FRegistrationSubscriber.php b/src/EventSubscriber/U2FRegistrationSubscriber.php index 12a70185..78092b66 100644 --- a/src/EventSubscriber/U2FRegistrationSubscriber.php +++ b/src/EventSubscriber/U2FRegistrationSubscriber.php @@ -44,12 +44,16 @@ namespace App\EventSubscriber; use App\Entity\UserSystem\U2FKey; use App\Entity\UserSystem\User; +use App\Events\SecurityEvent; +use App\Events\SecurityEvents; use Doctrine\ORM\EntityManagerInterface; use R\U2FTwoFactorBundle\Event\RegisterEvent; +use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; final class U2FRegistrationSubscriber implements EventSubscriberInterface { @@ -62,12 +66,16 @@ final class U2FRegistrationSubscriber implements EventSubscriberInterface */ private $router; - public function __construct(UrlGeneratorInterface $router, EntityManagerInterface $entityManager, FlashBagInterface $flashBag, bool $demo_mode) + /** @var EventDispatcher */ + private $eventDispatcher; + + public function __construct(UrlGeneratorInterface $router, EntityManagerInterface $entityManager, FlashBagInterface $flashBag, EventDispatcherInterface $eventDispatcher, bool $demo_mode) { $this->router = $router; $this->em = $entityManager; $this->demo_mode = $demo_mode; $this->flashBag = $flashBag; + $this->eventDispatcher = $eventDispatcher; } public static function getSubscribedEvents(): array @@ -96,6 +104,9 @@ final class U2FRegistrationSubscriber implements EventSubscriberInterface $this->em->persist($newKey); $this->em->flush(); $this->flashBag->add('success', 'tfa_u2f.key_added_successful'); + + $security_event = new SecurityEvent($user); + $this->eventDispatcher->dispatch($security_event, SecurityEvents::U2F_ADDED); } // generate new response, here we redirect the user to the fos user diff --git a/src/Events/SecurityEvent.php b/src/Events/SecurityEvent.php new file mode 100644 index 00000000..618a5f11 --- /dev/null +++ b/src/Events/SecurityEvent.php @@ -0,0 +1,50 @@ +. + */ + +namespace App\Events; + + +use App\Entity\UserSystem\User; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * This event is triggered when something security related to a user happens. + * For example when the password is reset or the an two factor authentication method was disabled. + * @package App\Events + */ +class SecurityEvent extends Event +{ + protected $targetUser; + protected $from_cli; + + public function __construct(User $targetUser) + { + $this->targetUser = $targetUser; + } + + /** + * Returns the affected user. + * @return User + */ + public function getTargetUser() + { + return $this->targetUser; + } +} \ No newline at end of file diff --git a/src/Events/SecurityEvents.php b/src/Events/SecurityEvents.php new file mode 100644 index 00000000..dfdacbdb --- /dev/null +++ b/src/Events/SecurityEvents.php @@ -0,0 +1,35 @@ +. + */ + +namespace App\Events; + + +class SecurityEvents +{ + public const PASSWORD_CHANGED = 'security.password_changed'; + public const PASSWORD_RESET = 'security.password_reset'; + public const BACKUP_KEYS_RESET = 'security.backup_keys_reset'; + public const U2F_ADDED = 'security.u2f_added'; + public const U2F_REMOVED = 'security.u2f_removed'; + public const GOOGLE_ENABLED = 'security.google_enabled'; + public const GOOGLE_DISABLED = 'security.google_disabled'; + public const TRUSTED_DEVICE_RESET = 'security.trusted_device_reset'; + public const TFA_ADMIN_RESET = 'security.2fa_admin_reset'; +} \ No newline at end of file diff --git a/src/Services/LogSystem/LogEntryExtraFormatter.php b/src/Services/LogSystem/LogEntryExtraFormatter.php index 74402f3c..a49489da 100644 --- a/src/Services/LogSystem/LogEntryExtraFormatter.php +++ b/src/Services/LogSystem/LogEntryExtraFormatter.php @@ -52,6 +52,7 @@ use App\Entity\LogSystem\ElementDeletedLogEntry; use App\Entity\LogSystem\ElementEditedLogEntry; use App\Entity\LogSystem\ExceptionLogEntry; use App\Entity\LogSystem\InstockChangedLogEntry; +use App\Entity\LogSystem\SecurityEventLogEntry; use App\Entity\LogSystem\UserLoginLogEntry; use App\Entity\LogSystem\UserLogoutLogEntry; use App\Entity\LogSystem\UserNotAllowedLogEntry; @@ -127,7 +128,7 @@ class LogEntryExtraFormatter protected function getInternalFormat(AbstractLogEntry $context): array { $array = []; - if ($context instanceof UserLoginLogEntry || $context instanceof UserLogoutLogEntry) { + if ($context instanceof UserLoginLogEntry || $context instanceof UserLogoutLogEntry || $context instanceof SecurityEventLogEntry) { $array['log.user_login.ip'] = htmlspecialchars($context->getIPAddress()); } diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index f3dbf19b..cbc479b6 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -8166,5 +8166,77 @@ Element 3 Preview picture + + + tfa_u2f.key_added_successful + Security key added successfully. + + + + + Username + Username + + + + + log.type.security.google_disabled + Authenticator App disabled + + + + + log.type.security.u2f_removed + Security key removed + + + + + log.type.security.u2f_added + Security key added + + + + + log.type.security.backup_keys_reset + Backup keys regenerated + + + + + log.type.security.google_enabled + Authenticator App enabled + + + + + log.type.security.password_changed + Password changed + + + + + log.type.security.trusted_device_reset + Trusted devices resetted + + + + + log.type.collection_element_deleted + Element of Collection deleted + + + + + log.type.security.password_reset + Password reset + + + + + log.type.security.2fa_admin_reset + Two Factor Reset by Administrator + +