From a6b242a1fd6f8900d80354361449f1bf62506ef9 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sun, 2 Dec 2018 12:43:05 +0100 Subject: Enable OTP 2FA - Update SchebTwoFactorBundle to version 3 - Enable Google 2fa on the bundle - Disallow ability to use both email and google as 2fa - Update Ocramius Proxy Manager to handle typed function & attributes (from PHP 7) - use `$this->addFlash` shortcut instead of `$this->get('session')->getFlashBag()->add` - update admin to be able to create/reset the 2fa --- .../UserBundle/Controller/ManageController.php | 67 ++++++++++++--- src/Wallabag/UserBundle/Entity/User.php | 94 +++++++++++++++------- src/Wallabag/UserBundle/Form/UserType.php | 9 ++- src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php | 2 +- .../Resources/views/Authentication/form.html.twig | 14 +++- .../Resources/views/Manage/edit.html.twig | 17 +++- 6 files changed, 156 insertions(+), 47 deletions(-) (limited to 'src/Wallabag/UserBundle') diff --git a/src/Wallabag/UserBundle/Controller/ManageController.php b/src/Wallabag/UserBundle/Controller/ManageController.php index a9746fb4..08ed25dd 100644 --- a/src/Wallabag/UserBundle/Controller/ManageController.php +++ b/src/Wallabag/UserBundle/Controller/ManageController.php @@ -8,6 +8,7 @@ use Pagerfanta\Adapter\DoctrineORMAdapter; use Pagerfanta\Exception\OutOfRangeCurrentPageException; use Pagerfanta\Pagerfanta; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; use Wallabag\UserBundle\Entity\User; @@ -31,10 +32,10 @@ class ManageController extends Controller // enable created user by default $user->setEnabled(true); - $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user); - $form->handleRequest($request); + $form = $this->createEditForm('NewUserType', $user, $request); if ($form->isSubmitted() && $form->isValid()) { + $user = $this->handleOtp($form, $user); $userManager->updateUser($user); // dispatch a created event so the associated config will be created @@ -62,14 +63,14 @@ class ManageController extends Controller */ public function editAction(Request $request, User $user) { + $userManager = $this->container->get('fos_user.user_manager'); + $deleteForm = $this->createDeleteForm($user); - $editForm = $this->createForm('Wallabag\UserBundle\Form\UserType', $user); - $editForm->handleRequest($request); + $form = $this->createEditForm('UserType', $user, $request); - if ($editForm->isSubmitted() && $editForm->isValid()) { - $em = $this->getDoctrine()->getManager(); - $em->persist($user); - $em->flush(); + if ($form->isSubmitted() && $form->isValid()) { + $user = $this->handleOtp($form, $user); + $userManager->updateUser($user); $this->get('session')->getFlashBag()->add( 'notice', @@ -81,7 +82,7 @@ class ManageController extends Controller return $this->render('WallabagUserBundle:Manage:edit.html.twig', [ 'user' => $user, - 'edit_form' => $editForm->createView(), + 'edit_form' => $form->createView(), 'delete_form' => $deleteForm->createView(), 'twofactor_auth' => $this->getParameter('twofactor_auth'), ]); @@ -157,7 +158,7 @@ class ManageController extends Controller } /** - * Creates a form to delete a User entity. + * Create a form to delete a User entity. * * @param User $user The User entity * @@ -171,4 +172,50 @@ class ManageController extends Controller ->getForm() ; } + + /** + * Create a form to create or edit a User entity. + * + * @param string $type Might be NewUserType or UserType + * @param User $user The new / edit user + * @param Request $request The request + * + * @return FormInterface + */ + private function createEditForm($type, User $user, Request $request) + { + $form = $this->createForm('Wallabag\UserBundle\Form\\' . $type, $user); + $form->handleRequest($request); + + // `googleTwoFactor` isn't a field within the User entity, we need to define it's value in a different way + if (true === $user->isGoogleAuthenticatorEnabled() && false === $form->isSubmitted()) { + $form->get('googleTwoFactor')->setData(true); + } + + return $form; + } + + /** + * Handle OTP update, taking care to only have one 2fa enable at a time. + * + * @see ConfigController + * + * @param FormInterface $form + * @param User $user + * + * @return User + */ + private function handleOtp(FormInterface $form, User $user) + { + if (true === $form->get('googleTwoFactor')->getData() && false === $user->isGoogleAuthenticatorEnabled()) { + $user->setGoogleAuthenticatorSecret($this->get('scheb_two_factor.security.google_authenticator')->generateSecret()); + $user->setEmailTwoFactor(false); + + return $user; + } + + $user->setGoogleAuthenticatorSecret(null); + + return $user; + } } diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php index 48446e3c..6e305719 100644 --- a/src/Wallabag/UserBundle/Entity/User.php +++ b/src/Wallabag/UserBundle/Entity/User.php @@ -8,8 +8,8 @@ use FOS\UserBundle\Model\User as BaseUser; use JMS\Serializer\Annotation\Accessor; use JMS\Serializer\Annotation\Groups; use JMS\Serializer\Annotation\XmlRoot; -use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; -use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; +use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface as EmailTwoFactorInterface; +use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface as GoogleTwoFactorInterface; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\UserInterface; use Wallabag\ApiBundle\Entity\Client; @@ -28,7 +28,7 @@ use Wallabag\CoreBundle\Helper\EntityTimestampsTrait; * @UniqueEntity("email") * @UniqueEntity("username") */ -class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface +class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorInterface { use EntityTimestampsTrait; @@ -123,16 +123,16 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf private $authCode; /** - * @var bool - * - * @ORM\Column(type="boolean") + * @ORM\Column(name="googleAuthenticatorSecret", type="string", nullable=true) */ - private $twoFactorAuthentication = false; + private $googleAuthenticatorSecret; /** - * @ORM\Column(type="json_array", nullable=true) + * @var bool + * + * @ORM\Column(type="boolean") */ - private $trusted; + private $emailTwoFactor = false; public function __construct() { @@ -233,49 +233,89 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf /** * @return bool */ - public function isTwoFactorAuthentication() + public function isEmailTwoFactor() + { + return $this->emailTwoFactor; + } + + /** + * @param bool $emailTwoFactor + */ + public function setEmailTwoFactor($emailTwoFactor) { - return $this->twoFactorAuthentication; + $this->emailTwoFactor = $emailTwoFactor; } /** - * @param bool $twoFactorAuthentication + * Used in the user config form to be "like" the email option. */ - public function setTwoFactorAuthentication($twoFactorAuthentication) + public function isGoogleTwoFactor() { - $this->twoFactorAuthentication = $twoFactorAuthentication; + return $this->isGoogleAuthenticatorEnabled(); } - public function isEmailAuthEnabled() + /** + * {@inheritdoc} + */ + public function isEmailAuthEnabled(): bool { - return $this->twoFactorAuthentication; + return $this->emailTwoFactor; } - public function getEmailAuthCode() + /** + * {@inheritdoc} + */ + public function getEmailAuthCode(): string { return $this->authCode; } - public function setEmailAuthCode($authCode) + /** + * {@inheritdoc} + */ + public function setEmailAuthCode(string $authCode): void { $this->authCode = $authCode; } - public function addTrustedComputer($token, \DateTime $validUntil) + /** + * {@inheritdoc} + */ + public function getEmailAuthRecipient(): string { - $this->trusted[$token] = $validUntil->format('r'); + return $this->email; } - public function isTrustedComputer($token) + /** + * {@inheritdoc} + */ + public function isGoogleAuthenticatorEnabled(): bool { - if (isset($this->trusted[$token])) { - $now = new \DateTime(); - $validUntil = new \DateTime($this->trusted[$token]); + return $this->googleAuthenticatorSecret ? true : false; + } - return $now < $validUntil; - } + /** + * {@inheritdoc} + */ + public function getGoogleAuthenticatorUsername(): string + { + return $this->username; + } - return false; + /** + * {@inheritdoc} + */ + public function getGoogleAuthenticatorSecret(): string + { + return $this->googleAuthenticatorSecret; + } + + /** + * {@inheritdoc} + */ + public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): void + { + $this->googleAuthenticatorSecret = $googleAuthenticatorSecret; } /** diff --git a/src/Wallabag/UserBundle/Form/UserType.php b/src/Wallabag/UserBundle/Form/UserType.php index 56fea640..026db9a2 100644 --- a/src/Wallabag/UserBundle/Form/UserType.php +++ b/src/Wallabag/UserBundle/Form/UserType.php @@ -35,9 +35,14 @@ class UserType extends AbstractType 'required' => false, 'label' => 'user.form.enabled_label', ]) - ->add('twoFactorAuthentication', CheckboxType::class, [ + ->add('emailTwoFactor', CheckboxType::class, [ 'required' => false, - 'label' => 'user.form.twofactor_label', + 'label' => 'user.form.twofactor_email_label', + ]) + ->add('googleTwoFactor', CheckboxType::class, [ + 'required' => false, + 'label' => 'user.form.twofactor_google_label', + 'mapped' => false, ]) ->add('save', SubmitType::class, [ 'label' => 'user.form.save', diff --git a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php index aed805c9..e8e29aa9 100644 --- a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php +++ b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php @@ -78,7 +78,7 @@ class AuthCodeMailer implements AuthCodeMailerInterface * * @param TwoFactorInterface $user */ - public function sendAuthCode(TwoFactorInterface $user) + public function sendAuthCode(TwoFactorInterface $user): void { $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig'); diff --git a/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig b/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig index c8471bdd..47a5cb78 100644 --- a/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig +++ b/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig @@ -1,7 +1,8 @@ +{# Override `vendor/scheb/two-factor-bundle/Resources/views/Authentication/form.html.twig` #} {% extends "WallabagUserBundle::layout.html.twig" %} {% block fos_user_content %} -
+
@@ -9,14 +10,19 @@

{{ flashMessage|trans }}

{% endfor %} + {# Authentication errors #} + {% if authenticationError %} +

{{ authenticationError|trans(authenticationErrorData) }}

+ {% endif %} +
- +
- {% if useTrustedOption %} + {% if displayTrustedOption %}
- +
{% endif %} diff --git a/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig b/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig index 3ffd15f5..8be37e79 100644 --- a/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig +++ b/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig @@ -50,10 +50,21 @@ {% if twofactor_auth %}
- {{ form_widget(edit_form.twoFactorAuthentication) }} - {{ form_label(edit_form.twoFactorAuthentication) }} - {{ form_errors(edit_form.twoFactorAuthentication) }} + {{ form_widget(edit_form.emailTwoFactor) }} + {{ form_label(edit_form.emailTwoFactor) }} + {{ form_errors(edit_form.emailTwoFactor) }}
+
+ {{ form_widget(edit_form.googleTwoFactor) }} + {{ form_label(edit_form.googleTwoFactor) }} + {{ form_errors(edit_form.googleTwoFactor) }} +
+ + {% if user.isGoogleAuthenticatorEnabled %} +
+

OTP Secret: {{ user.googleAuthenticatorSecret }}

+
+ {% endif %}
{% endif %} -- cgit v1.2.3