diff --git a/assets/ts_src/ajax_ui.ts b/assets/ts_src/ajax_ui.ts index af6661bb..ef29879a 100644 --- a/assets/ts_src/ajax_ui.ts +++ b/assets/ts_src/ajax_ui.ts @@ -297,11 +297,17 @@ class AjaxUI { /** * Submits the given form via ajax. * @param form The form that will be submmitted. + * @param btn The btn via which the form is submitted */ - public submitForm(form) + public submitForm(form, btn = null) { let options = ajaxUI.getFormOptions(); + if(btn) { + options.data = {}; + options.data[$(btn).attr('name')] = $(btn).attr('value'); + } + $(form).ajaxSubmit(options); } diff --git a/assets/ts_src/event_listeners.ts b/assets/ts_src/event_listeners.ts index 7b4b256b..91e82f96 100644 --- a/assets/ts_src/event_listeners.ts +++ b/assets/ts_src/event_listeners.ts @@ -190,6 +190,9 @@ $(document).on("ajaxUI:start ajaxUI:reload", function() { let form = this; + //Get the submit button + let btn = document.activeElement; + let title = $(this).data("title"); let message = $(this).data("message"); @@ -199,7 +202,7 @@ $(document).on("ajaxUI:start ajaxUI:reload", function() { callback: function(result) { //If the dialog was confirmed, then submit the form. if(result) { - ajaxUI.submitForm(form); + ajaxUI.submitForm(form, btn); } }}); diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 6cba6a67..7ce8d6e5 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -21,7 +21,11 @@ namespace App\Controller; +use App\Entity\Parts\Part; +use App\Entity\UserSystem\U2FKey; +use App\Entity\UserSystem\User; use App\Services\PasswordResetManager; +use App\Services\TFA\BackupCodeManager; use Doctrine\ORM\EntityManagerInterface; use Gregwar\CaptchaBundle\Type\CaptchaType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -29,6 +33,7 @@ use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\Mailer\MailerInterface; @@ -164,6 +169,46 @@ class SecurityController extends AbstractController 'form' => $form->createView() ]); } + /** + * @Route("/user/u2f_delete", name="u2f_delete", methods={"DELETE"}) + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function removeU2FToken(Request $request, EntityManagerInterface $entityManager, BackupCodeManager $backupCodeManager) + { + $user = $this->getUser(); + if (!$user instanceof User) { + return new \RuntimeException('This controller only works only for Part-DB User objects!'); + } + //When user change its settings, he should be logged in fully. + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); + + if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) { + if($request->request->has('key_id')) { + $key_id = $request->request->get('key_id'); + $key_repo = $entityManager->getRepository(U2FKey::class); + /** @var U2FKey|null $u2f */ + $u2f = $key_repo->find($key_id); + if($u2f === null) { + $this->addFlash('danger','tfa_u2f.u2f_delete.not_existing'); + throw new \RuntimeException('Key not existing!'); + } + + //User can only delete its own U2F keys + if ($u2f->getUser() !== $user) { + $this->addFlash('danger', 'tfa_u2f.u2f_delete.access_denied'); + throw new \RuntimeException('You can only delete your own U2F keys!'); + } + + $backupCodeManager->disableBackupCodesIfUnused($user); + $entityManager->remove($u2f); + $entityManager->flush(); + $this->addFlash('success', 'tfa.u2f.u2f_delete.success'); + } + } + + return $this->redirectToRoute('user_settings'); + } /** * @Route("/logout", name="logout") diff --git a/src/Entity/UserSystem/U2FKey.php b/src/Entity/UserSystem/U2FKey.php index d75e44dd..f9d96606 100644 --- a/src/Entity/UserSystem/U2FKey.php +++ b/src/Entity/UserSystem/U2FKey.php @@ -161,6 +161,15 @@ class U2FKey implements TwoFactorKeyInterface return $this->user; } + /** + * The primary key ID of this key + * @return int + */ + public function getID() : int + { + return $this->id; + } + /** * Sets the user this U2F key belongs to. * @param TwoFactorInterface $new_user diff --git a/src/Services/TFA/BackupCodeManager.php b/src/Services/TFA/BackupCodeManager.php index ec73e367..47cd1be1 100644 --- a/src/Services/TFA/BackupCodeManager.php +++ b/src/Services/TFA/BackupCodeManager.php @@ -37,7 +37,7 @@ class BackupCodeManager */ public function disableBackupCodesIfUnused(User $user) { - if($user->isU2FAuthEnabled() || $user->isGoogleAuthenticatorEnabled()) { + if($user->isGoogleAuthenticatorEnabled()) { return; } diff --git a/templates/Users/_2fa_settings.html.twig b/templates/Users/_2fa_settings.html.twig index 7b9592ac..d2598335 100644 --- a/templates/Users/_2fa_settings.html.twig +++ b/templates/Users/_2fa_settings.html.twig @@ -100,24 +100,31 @@ {% if user.u2FKeys is not empty %} {% trans %}tfa_u2f.table_caption{% endtrans %}: -
# | -{% trans %}tfa_u2f.keys.name{% endtrans %} | -{% trans %}tfa_u2f.keys.added_date{% endtrans %} | -
---|
{% trans %}tfa_u2f.no_keys_registered{% endtrans %}
{% endif %} diff --git a/tests/Services/TFA/BackupCodeManagerTest.php b/tests/Services/TFA/BackupCodeManagerTest.php index 45d9198f..8a4e4d21 100644 --- a/tests/Services/TFA/BackupCodeManagerTest.php +++ b/tests/Services/TFA/BackupCodeManagerTest.php @@ -60,10 +60,5 @@ class BackupCodeManagerTest extends WebTestCase $user->setGoogleAuthenticatorSecret('jskf'); $this->service->disableBackupCodesIfUnused($user); $this->assertEquals($codes, $user->getBackupCodes()); - - $user->setGoogleAuthenticatorSecret(''); - $user->addU2FKey(new U2FKey()); - $this->service->disableBackupCodesIfUnused($user); - $this->assertEquals($codes, $user->getBackupCodes()); } }