]> git.immae.eu Git - github/wallabag/wallabag.git/commitdiff
Handle password change
authorJeremy <jeremy.benoist@gmail.com>
Tue, 17 Feb 2015 20:03:23 +0000 (21:03 +0100)
committerJeremy <jeremy.benoist@gmail.com>
Tue, 17 Feb 2015 20:03:23 +0000 (21:03 +0100)
app/config/services.yml
src/Wallabag/CoreBundle/Controller/ConfigController.php
src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php
src/Wallabag/CoreBundle/Form/Type/ChangePasswordType.php [new file with mode: 0644]
src/Wallabag/CoreBundle/Resources/views/Config/index.html.twig
src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php
src/Wallabag/CoreBundle/Security/Validator/WallabagUserPasswordValidator.php [new file with mode: 0644]
src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php
src/Wallabag/CoreBundle/Tests/Controller/WallabagRestControllerTest.php
src/Wallabag/CoreBundle/Tests/WallabagTestCase.php

index d4485e4290b20c672afdae4e53477ade0ea67508..91a03e10e13d5d03612c1e18a910db5e2143456a 100644 (file)
@@ -3,6 +3,7 @@
 parameters:
     security.authentication.provider.dao.class: Wallabag\CoreBundle\Security\Authentication\Provider\WallabagAuthenticationProvider
     security.encoder.digest.class: Wallabag\CoreBundle\Security\Authentication\Encoder\WallabagPasswordEncoder
+    security.validator.user_password.class: Wallabag\CoreBundle\Security\Validator\WallabagUserPasswordValidator
 
 services:
 #    service_name:
index f48a9cb19ad506cd8f2531cd9755e453e4c7cc30..7540f756c4ab525699efd605ad292b9942d6b561 100644 (file)
@@ -7,6 +7,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 use Symfony\Component\HttpFoundation\Request;
 use Wallabag\CoreBundle\Entity\Config;
 use Wallabag\CoreBundle\Form\Type\ConfigType;
+use Wallabag\CoreBundle\Form\Type\ChangePasswordType;
 
 class ConfigController extends Controller
 {
@@ -14,19 +15,18 @@ class ConfigController extends Controller
      * @param Request $request
      *
      * @Route("/config", name="config")
-     *
-     * @return \Symfony\Component\HttpFoundation\Response
      */
     public function indexAction(Request $request)
     {
+        $em = $this->getDoctrine()->getManager();
         $config = $this->getConfig();
 
-        $form = $this->createForm(new ConfigType(), $config);
+        // handle basic config detail
+        $configForm = $this->createForm(new ConfigType(), $config);
+        $configForm->handleRequest($request);
 
-        $form->handleRequest($request);
+        if ($configForm->isValid()) {
 
-        if ($form->isValid()) {
-            $em = $this->getDoctrine()->getManager();
             $em->persist($config);
             $em->flush();
 
@@ -38,11 +38,36 @@ class ConfigController extends Controller
             return $this->redirect($this->generateUrl('config'));
         }
 
+        // handle changing password
+        $pwdForm = $this->createForm(new ChangePasswordType());
+        $pwdForm->handleRequest($request);
+
+        if ($pwdForm->isValid()) {
+            $user = $this->getUser();
+            $user->setPassword($pwdForm->get('new_password')->getData());
+            $em->persist($user);
+            $em->flush();
+
+            $this->get('session')->getFlashBag()->add(
+                'notice',
+                'Password updated'
+            );
+
+            return $this->redirect($this->generateUrl('config'));
+        }
+
         return $this->render('WallabagCoreBundle:Config:index.html.twig', array(
-            'form' => $form->createView(),
+            'configForm' => $configForm->createView(),
+            'pwdForm' => $pwdForm->createView(),
         ));
     }
 
+    /**
+     * Retrieve config for the current user.
+     * If no config were found, create a new one.
+     *
+     * @return Wallabag\CoreBundle\Entity\Config
+     */
     private function getConfig()
     {
         $config = $this->getDoctrine()
index e4751f20206b09b353bc67272654b75f6002ef39..d99412f482e906f7be584716d5cee81e90c5f0a7 100644 (file)
@@ -18,7 +18,7 @@ class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
         $userAdmin->setName('Big boss');
         $userAdmin->setEmail('bigboss@wallabag.org');
         $userAdmin->setUsername('admin');
-        $userAdmin->setPassword('test');
+        $userAdmin->setPassword('mypassword');
 
         $manager->persist($userAdmin);
 
@@ -28,7 +28,7 @@ class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
         $bobUser->setName('Bobby');
         $bobUser->setEmail('bobby@wallabag.org');
         $bobUser->setUsername('bob');
-        $bobUser->setPassword('test');
+        $bobUser->setPassword('mypassword');
 
         $manager->persist($bobUser);
 
diff --git a/src/Wallabag/CoreBundle/Form/Type/ChangePasswordType.php b/src/Wallabag/CoreBundle/Form/Type/ChangePasswordType.php
new file mode 100644 (file)
index 0000000..8bf4ca1
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace Wallabag\CoreBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
+use Symfony\Component\Validator\Constraints;
+
+class ChangePasswordType extends AbstractType
+{
+    public function buildForm(FormBuilderInterface $builder, array $options)
+    {
+        $builder
+            ->add('old_password', 'password', array(
+                'constraints' => new UserPassword(array('message' => 'Wrong value for your current password')),
+            ))
+            ->add('new_password', 'repeated', array(
+                'type' => 'password',
+                'invalid_message' => 'The password fields must match.',
+                'required' => true,
+                'first_options'  => array('label' => 'New password'),
+                'second_options' => array('label' => 'Repeat new password'),
+                'constraints' => array(
+                    new Constraints\Length(array(
+                        'min' => 6,
+                        'minMessage' => 'Password should by at least 6 chars long'
+                    )),
+                    new Constraints\NotBlank()
+                )
+            ))
+            ->add('save', 'submit')
+        ;
+    }
+
+    public function getName()
+    {
+        return 'change_passwd';
+    }
+}
index 9c04ff206218926936a0ee74bb57fba8f4f73828..941451778edf4a73ef4427e28b98f258747adcfb 100644 (file)
@@ -7,35 +7,67 @@
 {% endblock %}
 
 {% block content %}
-    <h2>Basic config</h2>
+    <h2>{% trans %}Basic config{% endtrans %}</h2>
 
-    <form action="{{ path('config') }}" method="post" {{ form_enctype(form) }}>
-        {{ form_errors(form) }}
+    <form action="{{ path('config') }}" method="post" {{ form_enctype(configForm) }}>
+        {{ form_errors(configForm) }}
 
         <fieldset class="w500p inline">
             <div class="row">
-                {{ form_label(form.theme) }}
-                {{ form_errors(form.theme) }}
-                {{ form_widget(form.theme) }}
+                {{ form_label(configForm.theme) }}
+                {{ form_errors(configForm.theme) }}
+                {{ form_widget(configForm.theme) }}
             </div>
         </fieldset>
 
         <fieldset class="w500p inline">
             <div class="row">
-                {{ form_label(form.items_per_page) }}
-                {{ form_errors(form.items_per_page) }}
-                {{ form_widget(form.items_per_page) }}
+                {{ form_label(configForm.items_per_page) }}
+                {{ form_errors(configForm.items_per_page) }}
+                {{ form_widget(configForm.items_per_page) }}
             </div>
         </fieldset>
 
         <fieldset class="w500p inline">
             <div class="row">
-                {{ form_label(form.language) }}
-                {{ form_errors(form.language) }}
-                {{ form_widget(form.language) }}
+                {{ form_label(configForm.language) }}
+                {{ form_errors(configForm.language) }}
+                {{ form_widget(configForm.language) }}
             </div>
         </fieldset>
 
-        {{ form_rest(form) }}
+        {{ form_rest(configForm) }}
+    </form>
+
+    <h2>{% trans %}Change your password{% endtrans %}</h2>
+
+    <form action="{{ path('config') }}" method="post" {{ form_enctype(pwdForm) }}>
+        {{ form_errors(pwdForm) }}
+
+        <fieldset class="w500p inline">
+            <div class="row">
+                {{ form_label(pwdForm.old_password) }}
+                {{ form_errors(pwdForm.old_password) }}
+                {{ form_widget(pwdForm.old_password) }}
+            </div>
+        </fieldset>
+
+        <fieldset class="w500p inline">
+            <div class="row">
+                {{ form_label(pwdForm.new_password.first) }}
+                {{ form_errors(pwdForm.new_password.first) }}
+                {{ form_widget(pwdForm.new_password.first) }}
+            </div>
+        </fieldset>
+
+        <fieldset class="w500p inline">
+            <div class="row">
+                {{ form_label(pwdForm.new_password.second) }}
+                {{ form_errors(pwdForm.new_password.second) }}
+                {{ form_widget(pwdForm.new_password.second) }}
+            </div>
+        </fieldset>
+
+        {{ form_rest(pwdForm) }}
     </form>
 {% endblock %}
index 56f1affe32b8b6f615f07528b15037dd9f505db6..fcfe418bf966d076bb4c0e24f826010527ac47c8 100644 (file)
@@ -41,10 +41,6 @@ class WallabagPasswordEncoder extends BasePasswordEncoder
      */
     public function encodePassword($raw, $salt)
     {
-        if (null === $this->username) {
-            throw new \LogicException('We can not check the password without a username.');
-        }
-
         if ($this->isPasswordTooLong($raw)) {
             throw new BadCredentialsException('Invalid password.');
         }
@@ -71,6 +67,10 @@ class WallabagPasswordEncoder extends BasePasswordEncoder
      */
     protected function mergePasswordAndSalt($password, $salt)
     {
+        if (null === $this->username) {
+            throw new \LogicException('We can not check the password without a username.');
+        }
+
         if (empty($salt)) {
             return $password;
         }
diff --git a/src/Wallabag/CoreBundle/Security/Validator/WallabagUserPasswordValidator.php b/src/Wallabag/CoreBundle/Security/Validator/WallabagUserPasswordValidator.php
new file mode 100644 (file)
index 0000000..5586f97
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+namespace Wallabag\CoreBundle\Security\Validator;
+
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Core\SecurityContextInterface;
+use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
+
+class WallabagUserPasswordValidator extends ConstraintValidator
+{
+    private $securityContext;
+    private $encoderFactory;
+
+    public function __construct(SecurityContextInterface $securityContext, EncoderFactoryInterface $encoderFactory)
+    {
+        $this->securityContext = $securityContext;
+        $this->encoderFactory = $encoderFactory;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function validate($password, Constraint $constraint)
+    {
+        if (!$constraint instanceof UserPassword) {
+            throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UserPassword');
+        }
+
+        $user = $this->securityContext->getToken()->getUser();
+
+        if (!$user instanceof UserInterface) {
+            throw new ConstraintDefinitionException('The User object must implement the UserInterface interface.');
+        }
+
+        // give username, it's used to hash the password
+        $encoder = $this->encoderFactory->getEncoder($user);
+        $encoder->setUsername($user->getUsername());
+
+        if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
+            $this->context->addViolation($constraint->message);
+        }
+    }
+}
index 30809a040273c1a690ae52de89104ce892653ec4..4aceed1506c5ccba21b6aab4c7e5ace06bf86a65 100644 (file)
@@ -26,7 +26,8 @@ class ConfigControllerTest extends WallabagTestCase
         $this->assertEquals(200, $client->getResponse()->getStatusCode());
 
         $this->assertCount(1, $crawler->filter('input[type=number]'));
-        $this->assertCount(1, $crawler->filter('button[type=submit]'));
+        $this->assertCount(1, $crawler->filter('button[id=config_save]'));
+        $this->assertCount(1, $crawler->filter('button[id=change_passwd_save]'));
     }
 
     public function testUpdate()
@@ -38,7 +39,7 @@ class ConfigControllerTest extends WallabagTestCase
 
         $this->assertEquals(200, $client->getResponse()->getStatusCode());
 
-        $form = $crawler->filter('button[type=submit]')->form();
+        $form = $crawler->filter('button[id=config_save]')->form();
 
         $data = array(
             'config[theme]' => 'baggy',
@@ -84,7 +85,7 @@ class ConfigControllerTest extends WallabagTestCase
 
         $this->assertEquals(200, $client->getResponse()->getStatusCode());
 
-        $form = $crawler->filter('button[type=submit]')->form();
+        $form = $crawler->filter('button[id=config_save]')->form();
 
         $crawler = $client->submit($form, $data);
 
@@ -93,4 +94,91 @@ class ConfigControllerTest extends WallabagTestCase
         $this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(array('_text')));
         $this->assertContains('This value should not be blank', $alert[0]);
     }
+
+    public function dataForChangePasswordFailed()
+    {
+        return array(
+            array(
+                array(
+                    'change_passwd[old_password]' => 'baggy',
+                    'change_passwd[new_password][first]' => '',
+                    'change_passwd[new_password][second]' => '',
+                ),
+                'Wrong value for your current password'
+            ),
+            array(
+                array(
+                    'change_passwd[old_password]' => 'mypassword',
+                    'change_passwd[new_password][first]' => '',
+                    'change_passwd[new_password][second]' => '',
+                ),
+                'This value should not be blank'
+            ),
+            array(
+                array(
+                    'change_passwd[old_password]' => 'mypassword',
+                    'change_passwd[new_password][first]' => 'hop',
+                    'change_passwd[new_password][second]' => '',
+                ),
+                'The password fields must match'
+            ),
+            array(
+                array(
+                    'change_passwd[old_password]' => 'mypassword',
+                    'change_passwd[new_password][first]' => 'hop',
+                    'change_passwd[new_password][second]' => 'hop',
+                ),
+                'Password should by at least 6 chars long'
+            ),
+        );
+    }
+
+    /**
+     * @dataProvider dataForChangePasswordFailed
+     */
+    public function testChangePasswordFailed($data, $expectedMessage)
+    {
+        $this->logInAs('admin');
+        $client = $this->getClient();
+
+        $crawler = $client->request('GET', '/config');
+
+        $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+        $form = $crawler->filter('button[id=change_passwd_save]')->form();
+
+        $crawler = $client->submit($form, $data);
+
+        $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+        $this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(array('_text')));
+        $this->assertContains($expectedMessage, $alert[0]);
+    }
+
+    public function testChangePassword()
+    {
+        $this->logInAs('admin');
+        $client = $this->getClient();
+
+        $crawler = $client->request('GET', '/config');
+
+        $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+        $form = $crawler->filter('button[id=change_passwd_save]')->form();
+
+        $data = array(
+            'change_passwd[old_password]' => 'mypassword',
+            'change_passwd[new_password][first]' => 'mypassword',
+            'change_passwd[new_password][second]' => 'mypassword',
+        );
+
+        $client->submit($form, $data);
+
+        $this->assertEquals(302, $client->getResponse()->getStatusCode());
+
+        $crawler = $client->followRedirect();
+
+        $this->assertGreaterThan(1, $alert = $crawler->filter('div.flash-notice')->extract(array('_text')));
+        $this->assertContains('Password updated', $alert[0]);
+    }
 }
index d77e23037edfd5491e93f0774e494bd0146f3dd4..fcfa8ccf9a233aa69eb310312f61d5c0805d6ad3 100644 (file)
@@ -47,7 +47,7 @@ class WallabagRestControllerTest extends WallabagTestCase
         $client->request('GET', '/api/salts/admin.json');
         $salt = json_decode($client->getResponse()->getContent());
 
-        $headers = $this->generateHeaders('admin', 'test', $salt[0]);
+        $headers = $this->generateHeaders('admin', 'mypassword', $salt[0]);
 
         $entry = $client->getContainer()
             ->get('doctrine.orm.entity_manager')
@@ -73,7 +73,7 @@ class WallabagRestControllerTest extends WallabagTestCase
         $client->request('GET', '/api/salts/admin.json');
         $salt = json_decode($client->getResponse()->getContent());
 
-        $headers = $this->generateHeaders('admin', 'test', $salt[0]);
+        $headers = $this->generateHeaders('admin', 'mypassword', $salt[0]);
 
         $entry = $client->getContainer()
             ->get('doctrine.orm.entity_manager')
@@ -101,7 +101,7 @@ class WallabagRestControllerTest extends WallabagTestCase
         $client->request('GET', '/api/salts/admin.json');
         $salt = json_decode($client->getResponse()->getContent());
 
-        $headers = $this->generateHeaders('admin', 'test', $salt[0]);
+        $headers = $this->generateHeaders('admin', 'mypassword', $salt[0]);
 
         $client->request('GET', '/api/entries', array(), array(), $headers);
 
@@ -125,7 +125,7 @@ class WallabagRestControllerTest extends WallabagTestCase
         $client->request('GET', '/api/salts/admin.json');
         $salt = json_decode($client->getResponse()->getContent());
 
-        $headers = $this->generateHeaders('admin', 'test', $salt[0]);
+        $headers = $this->generateHeaders('admin', 'mypassword', $salt[0]);
 
         $entry = $client->getContainer()
             ->get('doctrine.orm.entity_manager')
index 397945459e9e85dff8a73f4177e5ba4372da5bf8..22016d8edd7c04727c416a7bf8a9faa338693200 100644 (file)
@@ -24,7 +24,7 @@ abstract class WallabagTestCase extends WebTestCase
         $form = $crawler->filter('button[type=submit]')->form();
         $data = array(
             '_username' => $username,
-            '_password' => 'test',
+            '_password' => 'mypassword',
         );
 
         $this->client->submit($form, $data);