Handle password change

This commit is contained in:
Jeremy 2015-02-17 21:03:23 +01:00
parent 7781faa0b0
commit d9085c63e3
10 changed files with 268 additions and 34 deletions

View 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:

View 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()

View 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);

View file

@ -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';
}
}

View 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 %}

View 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;
}

View file

@ -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);
}
}
}

View 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]);
}
}

View 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')

View 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);