diff --git a/assets/controllers/elements/link_confirm_controller.js b/assets/controllers/elements/link_confirm_controller.js
new file mode 100644
index 00000000..3d59b492
--- /dev/null
+++ b/assets/controllers/elements/link_confirm_controller.js
@@ -0,0 +1,72 @@
+/*
+ * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
+ *
+ * Copyright (C) 2019 - 2022 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 Affero General Public License as published
+ * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {Controller} from "@hotwired/stimulus";
+
+import * as bootbox from "bootbox";
+import "../../css/components/bootbox_extensions.css";
+
+export default class extends Controller
+{
+
+ static values = {
+ message: String,
+ title: String
+ }
+
+
+
+ connect()
+ {
+ this._confirmed = false;
+
+ this.element.addEventListener('click', this._onClick.bind(this));
+ }
+
+ _onClick(event)
+ {
+
+ //If a user has not already confirmed the deletion, just let turbo do its work
+ if (this._confirmed) {
+ this._confirmed = false;
+ return;
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ const that = this;
+
+ bootbox.confirm({
+ title: this.titleValue,
+ message: this.messageValue,
+ callback: (result) => {
+ if (result) {
+ //Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
+ that._confirmed = true;
+
+ //Click the link
+ that.element.click();
+ } else {
+ that._confirmed = false;
+ }
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php b/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php
index 0f12c6d9..b74c8c5f 100644
--- a/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php
+++ b/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php
@@ -78,6 +78,11 @@ final class PasswordChangeNeededSubscriber implements EventSubscriberInterface
return;
}
+ //If the user is impersonated, we don't need to redirect him
+ if ($this->security->isGranted('IS_IMPERSONATOR')) {
+ return;
+ }
+
//Abort if we dont need to redirect the user.
if (!$user->isNeedPwChange() && !static::TFARedirectNeeded($user)) {
return;
diff --git a/src/Twig/UserExtension.php b/src/Twig/UserExtension.php
index 1d5c6588..0a06ef2d 100644
--- a/src/Twig/UserExtension.php
+++ b/src/Twig/UserExtension.php
@@ -47,7 +47,9 @@ use App\Entity\LogSystem\AbstractLogEntry;
use App\Repository\LogEntryRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
+use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
@@ -59,7 +61,9 @@ final class UserExtension extends AbstractExtension
{
private readonly LogEntryRepository $repo;
- public function __construct(EntityManagerInterface $em, private readonly Security $security)
+ public function __construct(EntityManagerInterface $em,
+ private readonly Security $security,
+ private readonly UrlGeneratorInterface $urlGenerator)
{
$this->repo = $em->getRepository(AbstractLogEntry::class);
}
@@ -80,6 +84,7 @@ final class UserExtension extends AbstractExtension
new TwigFunction('creating_user', fn(AbstractDBElement $element): ?User => $this->repo->getCreatingUser($element)),
new TwigFunction('impersonator_user', $this->getImpersonatorUser(...)),
new TwigFunction('impersonation_active', $this->isImpersonationActive(...)),
+ new TwigFunction('impersonation_path', $this->getImpersonationPath(...)),
];
}
@@ -103,6 +108,15 @@ final class UserExtension extends AbstractExtension
return $this->security->isGranted('IS_IMPERSONATOR');
}
+ public function getImpersonationPath(User $user, string $route_name = 'homepage'): string
+ {
+ if (! $this->security->isGranted('CAN_SWITCH_USER', $user)) {
+ throw new AccessDeniedException('You are not allowed to impersonate this user!');
+ }
+
+ return $this->urlGenerator->generate($route_name, ['_switch_user' => $user->getUsername()]);
+ }
+
/**
* This function/filter generates a path.
*/
diff --git a/templates/admin/base_admin.html.twig b/templates/admin/base_admin.html.twig
index 72a6ec60..51790c3c 100644
--- a/templates/admin/base_admin.html.twig
+++ b/templates/admin/base_admin.html.twig
@@ -77,21 +77,22 @@
{{ form_start(form) }}
-
-
-
-
{% endblock %}
{% block additional_panes %}
diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf
index e6533ba7..c00b68c9 100644
--- a/translations/messages.en.xlf
+++ b/translations/messages.en.xlf
@@ -11409,5 +11409,25 @@ Element 3
Stop impersonation
+
+
+ user.impersonate.btn
+ Impersonate
+
+
+
+
+ user.impersonate.confirm.title
+ Do you really want to impersonate this user?
+
+
+
+
+ user.impersonate.confirm.message
+ This will be logged. You should only do this with a good reason.
+
+Please note, that you can not impersonate a disabled user. If you try you will get an "Access Denied" message.
+
+