aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/Wallabag/UserBundle
diff options
context:
space:
mode:
authorJeremy Benoist <jeremy.benoist@gmail.com>2018-12-02 12:43:05 +0100
committerJeremy Benoist <jeremy.benoist@gmail.com>2019-01-23 13:28:02 +0100
commita6b242a1fd6f8900d80354361449f1bf62506ef9 (patch)
treef69d87208d0ebbdb8517529582280b174af74a16 /src/Wallabag/UserBundle
parentacd4412080dfb73ecaa7f9983728d1d55bc27ea4 (diff)
downloadwallabag-a6b242a1fd6f8900d80354361449f1bf62506ef9.tar.gz
wallabag-a6b242a1fd6f8900d80354361449f1bf62506ef9.tar.zst
wallabag-a6b242a1fd6f8900d80354361449f1bf62506ef9.zip
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
Diffstat (limited to 'src/Wallabag/UserBundle')
-rw-r--r--src/Wallabag/UserBundle/Controller/ManageController.php67
-rw-r--r--src/Wallabag/UserBundle/Entity/User.php94
-rw-r--r--src/Wallabag/UserBundle/Form/UserType.php9
-rw-r--r--src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php2
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig14
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig17
6 files changed, 156 insertions, 47 deletions
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;
8use Pagerfanta\Exception\OutOfRangeCurrentPageException; 8use Pagerfanta\Exception\OutOfRangeCurrentPageException;
9use Pagerfanta\Pagerfanta; 9use Pagerfanta\Pagerfanta;
10use Symfony\Bundle\FrameworkBundle\Controller\Controller; 10use Symfony\Bundle\FrameworkBundle\Controller\Controller;
11use Symfony\Component\Form\FormInterface;
11use Symfony\Component\HttpFoundation\Request; 12use Symfony\Component\HttpFoundation\Request;
12use Symfony\Component\Routing\Annotation\Route; 13use Symfony\Component\Routing\Annotation\Route;
13use Wallabag\UserBundle\Entity\User; 14use Wallabag\UserBundle\Entity\User;
@@ -31,10 +32,10 @@ class ManageController extends Controller
31 // enable created user by default 32 // enable created user by default
32 $user->setEnabled(true); 33 $user->setEnabled(true);
33 34
34 $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user); 35 $form = $this->createEditForm('NewUserType', $user, $request);
35 $form->handleRequest($request);
36 36
37 if ($form->isSubmitted() && $form->isValid()) { 37 if ($form->isSubmitted() && $form->isValid()) {
38 $user = $this->handleOtp($form, $user);
38 $userManager->updateUser($user); 39 $userManager->updateUser($user);
39 40
40 // dispatch a created event so the associated config will be created 41 // dispatch a created event so the associated config will be created
@@ -62,14 +63,14 @@ class ManageController extends Controller
62 */ 63 */
63 public function editAction(Request $request, User $user) 64 public function editAction(Request $request, User $user)
64 { 65 {
66 $userManager = $this->container->get('fos_user.user_manager');
67
65 $deleteForm = $this->createDeleteForm($user); 68 $deleteForm = $this->createDeleteForm($user);
66 $editForm = $this->createForm('Wallabag\UserBundle\Form\UserType', $user); 69 $form = $this->createEditForm('UserType', $user, $request);
67 $editForm->handleRequest($request);
68 70
69 if ($editForm->isSubmitted() && $editForm->isValid()) { 71 if ($form->isSubmitted() && $form->isValid()) {
70 $em = $this->getDoctrine()->getManager(); 72 $user = $this->handleOtp($form, $user);
71 $em->persist($user); 73 $userManager->updateUser($user);
72 $em->flush();
73 74
74 $this->get('session')->getFlashBag()->add( 75 $this->get('session')->getFlashBag()->add(
75 'notice', 76 'notice',
@@ -81,7 +82,7 @@ class ManageController extends Controller
81 82
82 return $this->render('WallabagUserBundle:Manage:edit.html.twig', [ 83 return $this->render('WallabagUserBundle:Manage:edit.html.twig', [
83 'user' => $user, 84 'user' => $user,
84 'edit_form' => $editForm->createView(), 85 'edit_form' => $form->createView(),
85 'delete_form' => $deleteForm->createView(), 86 'delete_form' => $deleteForm->createView(),
86 'twofactor_auth' => $this->getParameter('twofactor_auth'), 87 'twofactor_auth' => $this->getParameter('twofactor_auth'),
87 ]); 88 ]);
@@ -157,7 +158,7 @@ class ManageController extends Controller
157 } 158 }
158 159
159 /** 160 /**
160 * Creates a form to delete a User entity. 161 * Create a form to delete a User entity.
161 * 162 *
162 * @param User $user The User entity 163 * @param User $user The User entity
163 * 164 *
@@ -171,4 +172,50 @@ class ManageController extends Controller
171 ->getForm() 172 ->getForm()
172 ; 173 ;
173 } 174 }
175
176 /**
177 * Create a form to create or edit a User entity.
178 *
179 * @param string $type Might be NewUserType or UserType
180 * @param User $user The new / edit user
181 * @param Request $request The request
182 *
183 * @return FormInterface
184 */
185 private function createEditForm($type, User $user, Request $request)
186 {
187 $form = $this->createForm('Wallabag\UserBundle\Form\\' . $type, $user);
188 $form->handleRequest($request);
189
190 // `googleTwoFactor` isn't a field within the User entity, we need to define it's value in a different way
191 if (true === $user->isGoogleAuthenticatorEnabled() && false === $form->isSubmitted()) {
192 $form->get('googleTwoFactor')->setData(true);
193 }
194
195 return $form;
196 }
197
198 /**
199 * Handle OTP update, taking care to only have one 2fa enable at a time.
200 *
201 * @see ConfigController
202 *
203 * @param FormInterface $form
204 * @param User $user
205 *
206 * @return User
207 */
208 private function handleOtp(FormInterface $form, User $user)
209 {
210 if (true === $form->get('googleTwoFactor')->getData() && false === $user->isGoogleAuthenticatorEnabled()) {
211 $user->setGoogleAuthenticatorSecret($this->get('scheb_two_factor.security.google_authenticator')->generateSecret());
212 $user->setEmailTwoFactor(false);
213
214 return $user;
215 }
216
217 $user->setGoogleAuthenticatorSecret(null);
218
219 return $user;
220 }
174} 221}
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;
8use JMS\Serializer\Annotation\Accessor; 8use JMS\Serializer\Annotation\Accessor;
9use JMS\Serializer\Annotation\Groups; 9use JMS\Serializer\Annotation\Groups;
10use JMS\Serializer\Annotation\XmlRoot; 10use JMS\Serializer\Annotation\XmlRoot;
11use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; 11use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface as EmailTwoFactorInterface;
12use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; 12use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface as GoogleTwoFactorInterface;
13use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 13use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
14use Symfony\Component\Security\Core\User\UserInterface; 14use Symfony\Component\Security\Core\User\UserInterface;
15use Wallabag\ApiBundle\Entity\Client; 15use Wallabag\ApiBundle\Entity\Client;
@@ -28,7 +28,7 @@ use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
28 * @UniqueEntity("email") 28 * @UniqueEntity("email")
29 * @UniqueEntity("username") 29 * @UniqueEntity("username")
30 */ 30 */
31class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface 31class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorInterface
32{ 32{
33 use EntityTimestampsTrait; 33 use EntityTimestampsTrait;
34 34
@@ -123,16 +123,16 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
123 private $authCode; 123 private $authCode;
124 124
125 /** 125 /**
126 * @var bool 126 * @ORM\Column(name="googleAuthenticatorSecret", type="string", nullable=true)
127 *
128 * @ORM\Column(type="boolean")
129 */ 127 */
130 private $twoFactorAuthentication = false; 128 private $googleAuthenticatorSecret;
131 129
132 /** 130 /**
133 * @ORM\Column(type="json_array", nullable=true) 131 * @var bool
132 *
133 * @ORM\Column(type="boolean")
134 */ 134 */
135 private $trusted; 135 private $emailTwoFactor = false;
136 136
137 public function __construct() 137 public function __construct()
138 { 138 {
@@ -233,49 +233,89 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
233 /** 233 /**
234 * @return bool 234 * @return bool
235 */ 235 */
236 public function isTwoFactorAuthentication() 236 public function isEmailTwoFactor()
237 {
238 return $this->emailTwoFactor;
239 }
240
241 /**
242 * @param bool $emailTwoFactor
243 */
244 public function setEmailTwoFactor($emailTwoFactor)
237 { 245 {
238 return $this->twoFactorAuthentication; 246 $this->emailTwoFactor = $emailTwoFactor;
239 } 247 }
240 248
241 /** 249 /**
242 * @param bool $twoFactorAuthentication 250 * Used in the user config form to be "like" the email option.
243 */ 251 */
244 public function setTwoFactorAuthentication($twoFactorAuthentication) 252 public function isGoogleTwoFactor()
245 { 253 {
246 $this->twoFactorAuthentication = $twoFactorAuthentication; 254 return $this->isGoogleAuthenticatorEnabled();
247 } 255 }
248 256
249 public function isEmailAuthEnabled() 257 /**
258 * {@inheritdoc}
259 */
260 public function isEmailAuthEnabled(): bool
250 { 261 {
251 return $this->twoFactorAuthentication; 262 return $this->emailTwoFactor;
252 } 263 }
253 264
254 public function getEmailAuthCode() 265 /**
266 * {@inheritdoc}
267 */
268 public function getEmailAuthCode(): string
255 { 269 {
256 return $this->authCode; 270 return $this->authCode;
257 } 271 }
258 272
259 public function setEmailAuthCode($authCode) 273 /**
274 * {@inheritdoc}
275 */
276 public function setEmailAuthCode(string $authCode): void
260 { 277 {
261 $this->authCode = $authCode; 278 $this->authCode = $authCode;
262 } 279 }
263 280
264 public function addTrustedComputer($token, \DateTime $validUntil) 281 /**
282 * {@inheritdoc}
283 */
284 public function getEmailAuthRecipient(): string
265 { 285 {
266 $this->trusted[$token] = $validUntil->format('r'); 286 return $this->email;
267 } 287 }
268 288
269 public function isTrustedComputer($token) 289 /**
290 * {@inheritdoc}
291 */
292 public function isGoogleAuthenticatorEnabled(): bool
270 { 293 {
271 if (isset($this->trusted[$token])) { 294 return $this->googleAuthenticatorSecret ? true : false;
272 $now = new \DateTime(); 295 }
273 $validUntil = new \DateTime($this->trusted[$token]);
274 296
275 return $now < $validUntil; 297 /**
276 } 298 * {@inheritdoc}
299 */
300 public function getGoogleAuthenticatorUsername(): string
301 {
302 return $this->username;
303 }
277 304
278 return false; 305 /**
306 * {@inheritdoc}
307 */
308 public function getGoogleAuthenticatorSecret(): string
309 {
310 return $this->googleAuthenticatorSecret;
311 }
312
313 /**
314 * {@inheritdoc}
315 */
316 public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): void
317 {
318 $this->googleAuthenticatorSecret = $googleAuthenticatorSecret;
279 } 319 }
280 320
281 /** 321 /**
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
35 'required' => false, 35 'required' => false,
36 'label' => 'user.form.enabled_label', 36 'label' => 'user.form.enabled_label',
37 ]) 37 ])
38 ->add('twoFactorAuthentication', CheckboxType::class, [ 38 ->add('emailTwoFactor', CheckboxType::class, [
39 'required' => false, 39 'required' => false,
40 'label' => 'user.form.twofactor_label', 40 'label' => 'user.form.twofactor_email_label',
41 ])
42 ->add('googleTwoFactor', CheckboxType::class, [
43 'required' => false,
44 'label' => 'user.form.twofactor_google_label',
45 'mapped' => false,
41 ]) 46 ])
42 ->add('save', SubmitType::class, [ 47 ->add('save', SubmitType::class, [
43 'label' => 'user.form.save', 48 '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
78 * 78 *
79 * @param TwoFactorInterface $user 79 * @param TwoFactorInterface $user
80 */ 80 */
81 public function sendAuthCode(TwoFactorInterface $user) 81 public function sendAuthCode(TwoFactorInterface $user): void
82 { 82 {
83 $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig'); 83 $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig');
84 84
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 @@
1{# Override `vendor/scheb/two-factor-bundle/Resources/views/Authentication/form.html.twig` #}
1{% extends "WallabagUserBundle::layout.html.twig" %} 2{% extends "WallabagUserBundle::layout.html.twig" %}
2 3
3{% block fos_user_content %} 4{% block fos_user_content %}
4<form class="form" action="" method="post"> 5<form class="form" action="{{ path("2fa_login_check") }}" method="post">
5 <div class="card-content"> 6 <div class="card-content">
6 <div class="row"> 7 <div class="row">
7 8
@@ -9,14 +10,19 @@
9 <p class="error">{{ flashMessage|trans }}</p> 10 <p class="error">{{ flashMessage|trans }}</p>
10 {% endfor %} 11 {% endfor %}
11 12
13 {# Authentication errors #}
14 {% if authenticationError %}
15 <p class="error">{{ authenticationError|trans(authenticationErrorData) }}</p>
16 {% endif %}
17
12 <div class="input-field col s12"> 18 <div class="input-field col s12">
13 <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label> 19 <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label>
14 <input id="_auth_code" type="text" autocomplete="off" name="_auth_code" /> 20 <input id="_auth_code" type="text" autocomplete="off" name="{{ authCodeParameterName }}" />
15 </div> 21 </div>
16 22
17 {% if useTrustedOption %} 23 {% if displayTrustedOption %}
18 <div class="input-field col s12"> 24 <div class="input-field col s12">
19 <input id="_trusted" type="checkbox" name="_trusted" /> 25 <input id="_trusted" type="checkbox" name="{{ trustedParameterName }}" />
20 <label for="_trusted">{{ "scheb_two_factor.trusted"|trans }}</label> 26 <label for="_trusted">{{ "scheb_two_factor.trusted"|trans }}</label>
21 </div> 27 </div>
22 {% endif %} 28 {% 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 @@
50 {% if twofactor_auth %} 50 {% if twofactor_auth %}
51 <div class="row"> 51 <div class="row">
52 <div class="input-field col s12"> 52 <div class="input-field col s12">
53 {{ form_widget(edit_form.twoFactorAuthentication) }} 53 {{ form_widget(edit_form.emailTwoFactor) }}
54 {{ form_label(edit_form.twoFactorAuthentication) }} 54 {{ form_label(edit_form.emailTwoFactor) }}
55 {{ form_errors(edit_form.twoFactorAuthentication) }} 55 {{ form_errors(edit_form.emailTwoFactor) }}
56 </div> 56 </div>
57 <div class="input-field col s12">
58 {{ form_widget(edit_form.googleTwoFactor) }}
59 {{ form_label(edit_form.googleTwoFactor) }}
60 {{ form_errors(edit_form.googleTwoFactor) }}
61 </div>
62
63 {% if user.isGoogleAuthenticatorEnabled %}
64 <div class="input-field col s12">
65 <p><strong>OTP Secret</strong>: {{ user.googleAuthenticatorSecret }}</p>
66 </div>
67 {% endif %}
57 </div> 68 </div>
58 {% endif %} 69 {% endif %}
59 70