diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 5c7a2262..127605c6 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -169,71 +169,6 @@ 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'); - } - } else { - $this->addFlash('error','csfr_invalid'); - } - - return $this->redirectToRoute('user_settings'); - } - - /** - * @Route("/user/invalidate_trustedDevices", name="tfa_trustedDevices_invalidate", methods={"DELETE"}) - */ - public function resetTrustedDevices(Request $request, EntityManagerInterface $entityManager) - { - $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('devices_reset'.$user->getId(), $request->request->get('_token'))) { - $user->invalidateTrustedDeviceTokens(); - $entityManager->flush(); - $this->addFlash('success', 'tfa_trustedDevice.invalidate.success'); - } else { - $this->addFlash('error','csfr_invalid'); - } - - return $this->redirectToRoute('user_settings'); - } /** * @Route("/logout", name="logout") diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index ccfcbad8..0f07ee14 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -153,178 +153,6 @@ class UserController extends AdminPages\BaseAdminController ]); } - /** - * @Route("/2fa_backup_codes", name="show_backup_codes") - */ - public function showBackupCodes() - { - $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 (empty($user->getBackupCodes())) { - $this->addFlash('error', 'You do not have any backup codes enabled, therefore you can not view them!'); - throw new Exception('You do not have any backup codes enabled, therefore you can not view them!'); - } - - return $this->render('Users/backup_codes.html.twig', [ - 'user' => $user - ]); - } - - /** - * @Route("/settings", name="user_settings") - */ - public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordEncoderInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager) - { - /** - * @var User - */ - $user = $this->getUser(); - - $page_need_reload = false; - - 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'); - - /*************************** - * User settings form - ***************************/ - - $form = $this->createForm(UserSettingsType::class, $user); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - //Check if user theme setting has changed - if ($user->getTheme() !== $em->getUnitOfWork()->getOriginalEntityData($user)['theme']) { - $page_need_reload = true; - } - - $em->flush(); - $this->addFlash('success', 'user.settings.saved_flash'); - } - - /***************************** - * Password change form - ****************************/ - - $demo_mode = $this->getParameter('demo_mode'); - - $pw_form = $this->createFormBuilder() - //Username field for autocomplete - ->add('username', TextType::class, [ - 'data' => $user->getName(), - 'attr' => ['autocomplete' => 'username'], - 'disabled' => true, - 'row_attr' => ['class' => 'd-none'] - ]) - ->add('old_password', PasswordType::class, [ - 'label' => 'user.settings.pw_old.label', - 'disabled' => $demo_mode, - 'attr' => ['autocomplete' => 'current-password'], - 'constraints' => [new UserPassword()], ]) //This constraint checks, if the current user pw was inputted. - ->add('new_password', RepeatedType::class, [ - 'disabled' => $demo_mode, - 'type' => PasswordType::class, - 'first_options' => ['label' => 'user.settings.pw_new.label'], - 'second_options' => ['label' => 'user.settings.pw_confirm.label'], - 'invalid_message' => 'password_must_match', - 'options' => [ - 'attr' => ['autocomplete' => 'new-password'] - ], - 'constraints' => [new Length([ - 'min' => 6, - 'max' => 128, - ])], - ]) - ->add('submit', SubmitType::class, ['label' => 'save']) - ->getForm(); - - $pw_form->handleRequest($request); - - //Check if password if everything was correct, then save it to User and DB - if ($pw_form->isSubmitted() && $pw_form->isValid()) { - $password = $passwordEncoder->encodePassword($user, $pw_form['new_password']->getData()); - $user->setPassword($password); - - //After the change reset the password change needed setting - $user->setNeedPwChange(false); - - $em->persist($user); - $em->flush(); - $this->addFlash('success', 'user.settings.pw_changed_flash'); - } - - //Handle 2FA things - $google_form = $this->createForm(TFAGoogleSettingsType::class, $user); - $google_enabled = $user->isGoogleAuthenticatorEnabled(); - if (!$form->isSubmitted() && !$google_enabled) { - $user->setGoogleAuthenticatorSecret($googleAuthenticator->generateSecret()); - $google_form->get('googleAuthenticatorSecret')->setData($user->getGoogleAuthenticatorSecret()); - } - $google_form->handleRequest($request); - - if($google_form->isSubmitted() && $google_form->isValid()) { - if (!$google_enabled) { - //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'); - return $this->redirectToRoute('user_settings'); - } elseif ($google_enabled) { - //Remove secret to disable google authenticator - $user->setGoogleAuthenticatorSecret(null); - $backupCodeManager->disableBackupCodesIfUnused($user); - $em->flush(); - $this->addFlash('success', 'user.settings.2fa.google.disabled'); - return $this->redirectToRoute('user_settings'); - } - } - - $backup_form = $this->get('form.factory')->createNamedBuilder('backup_codes')->add('reset_codes', SubmitType::class,[ - 'label' => 'tfa_backup.regenerate_codes', - 'attr' => ['class' => 'btn-danger'], - 'disabled' => empty($user->getBackupCodes()) - ])->getForm(); - - $backup_form->handleRequest($request); - if ($backup_form->isSubmitted() && $backup_form->isValid()) { - $backupCodeManager->regenerateBackupCodes($user); - $em->flush(); - $this->addFlash('success', 'user.settings.2fa.backup_codes.regenerated'); - } - - - /****************************** - * Output both forms - *****************************/ - - return $this->render('Users/user_settings.html.twig', [ - 'user' => $user, - 'settings_form' => $form->createView(), - 'pw_form' => $pw_form->createView(), - 'page_need_reload' => $page_need_reload, - - 'google_form' => $google_form->createView(), - 'backup_form' => $backup_form->createView(), - 'tfa_google' => [ - 'enabled' => $google_enabled, - 'qrContent' => $googleAuthenticator->getQRContent($user), - 'secret' => $user->getGoogleAuthenticatorSecret(), - 'username' => $user->getGoogleAuthenticatorUsername() - ] - ]); - } /** * Get either a Gravatar URL or complete image tag for a specified email address. diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php new file mode 100644 index 00000000..b337fe7d --- /dev/null +++ b/src/Controller/UserSettingsController.php @@ -0,0 +1,270 @@ +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 (empty($user->getBackupCodes())) { + $this->addFlash('error', 'You do not have any backup codes enabled, therefore you can not view them!'); + throw new Exception('You do not have any backup codes enabled, therefore you can not view them!'); + } + + return $this->render('Users/backup_codes.html.twig', [ + 'user' => $user + ]); + } + + /** + * @Route("/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) { + throw 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'); + } + } else { + $this->addFlash('error','csfr_invalid'); + } + + return $this->redirectToRoute('user_settings'); + } + + /** + * @Route("/invalidate_trustedDevices", name="tfa_trustedDevices_invalidate", methods={"DELETE"}) + */ + public function resetTrustedDevices(Request $request, EntityManagerInterface $entityManager) + { + $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('devices_reset'.$user->getId(), $request->request->get('_token'))) { + $user->invalidateTrustedDeviceTokens(); + $entityManager->flush(); + $this->addFlash('success', 'tfa_trustedDevice.invalidate.success'); + } else { + $this->addFlash('error','csfr_invalid'); + } + + return $this->redirectToRoute('user_settings'); + } + + /** + * @Route("/settings", name="user_settings") + */ + public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordEncoderInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager) + { + /** + * @var User + */ + $user = $this->getUser(); + + $page_need_reload = false; + + 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'); + + /*************************** + * User settings form + ***************************/ + + $form = $this->createForm(UserSettingsType::class, $user); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + //Check if user theme setting has changed + if ($user->getTheme() !== $em->getUnitOfWork()->getOriginalEntityData($user)['theme']) { + $page_need_reload = true; + } + + $em->flush(); + $this->addFlash('success', 'user.settings.saved_flash'); + } + + /***************************** + * Password change form + ****************************/ + + $demo_mode = $this->getParameter('demo_mode'); + + $pw_form = $this->createFormBuilder() + //Username field for autocomplete + ->add('username', TextType::class, [ + 'data' => $user->getName(), + 'attr' => ['autocomplete' => 'username'], + 'disabled' => true, + 'row_attr' => ['class' => 'd-none'] + ]) + ->add('old_password', PasswordType::class, [ + 'label' => 'user.settings.pw_old.label', + 'disabled' => $demo_mode, + 'attr' => ['autocomplete' => 'current-password'], + 'constraints' => [new UserPassword()], ]) //This constraint checks, if the current user pw was inputted. + ->add('new_password', RepeatedType::class, [ + 'disabled' => $demo_mode, + 'type' => PasswordType::class, + 'first_options' => ['label' => 'user.settings.pw_new.label'], + 'second_options' => ['label' => 'user.settings.pw_confirm.label'], + 'invalid_message' => 'password_must_match', + 'options' => [ + 'attr' => ['autocomplete' => 'new-password'] + ], + 'constraints' => [new Length([ + 'min' => 6, + 'max' => 128, + ])], + ]) + ->add('submit', SubmitType::class, ['label' => 'save']) + ->getForm(); + + $pw_form->handleRequest($request); + + //Check if password if everything was correct, then save it to User and DB + if ($pw_form->isSubmitted() && $pw_form->isValid()) { + $password = $passwordEncoder->encodePassword($user, $pw_form['new_password']->getData()); + $user->setPassword($password); + + //After the change reset the password change needed setting + $user->setNeedPwChange(false); + + $em->persist($user); + $em->flush(); + $this->addFlash('success', 'user.settings.pw_changed_flash'); + } + + //Handle 2FA things + $google_form = $this->createForm(TFAGoogleSettingsType::class, $user); + $google_enabled = $user->isGoogleAuthenticatorEnabled(); + if (!$form->isSubmitted() && !$google_enabled) { + $user->setGoogleAuthenticatorSecret($googleAuthenticator->generateSecret()); + $google_form->get('googleAuthenticatorSecret')->setData($user->getGoogleAuthenticatorSecret()); + } + $google_form->handleRequest($request); + + if($google_form->isSubmitted() && $google_form->isValid()) { + if (!$google_enabled) { + //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'); + return $this->redirectToRoute('user_settings'); + } elseif ($google_enabled) { + //Remove secret to disable google authenticator + $user->setGoogleAuthenticatorSecret(null); + $backupCodeManager->disableBackupCodesIfUnused($user); + $em->flush(); + $this->addFlash('success', 'user.settings.2fa.google.disabled'); + return $this->redirectToRoute('user_settings'); + } + } + + $backup_form = $this->get('form.factory')->createNamedBuilder('backup_codes')->add('reset_codes', SubmitType::class,[ + 'label' => 'tfa_backup.regenerate_codes', + 'attr' => ['class' => 'btn-danger'], + 'disabled' => empty($user->getBackupCodes()) + ])->getForm(); + + $backup_form->handleRequest($request); + if ($backup_form->isSubmitted() && $backup_form->isValid()) { + $backupCodeManager->regenerateBackupCodes($user); + $em->flush(); + $this->addFlash('success', 'user.settings.2fa.backup_codes.regenerated'); + } + + + /****************************** + * Output both forms + *****************************/ + + return $this->render('Users/user_settings.html.twig', [ + 'user' => $user, + 'settings_form' => $form->createView(), + 'pw_form' => $pw_form->createView(), + 'page_need_reload' => $page_need_reload, + + 'google_form' => $google_form->createView(), + 'backup_form' => $backup_form->createView(), + 'tfa_google' => [ + 'enabled' => $google_enabled, + 'qrContent' => $googleAuthenticator->getQRContent($user), + 'secret' => $user->getGoogleAuthenticatorSecret(), + 'username' => $user->getGoogleAuthenticatorUsername() + ] + ]); + } +} \ No newline at end of file