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:
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
{
* @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();
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()
$userAdmin->setName('Big boss');
$userAdmin->setEmail('bigboss@wallabag.org');
$userAdmin->setUsername('admin');
- $userAdmin->setPassword('test');
+ $userAdmin->setPassword('mypassword');
$manager->persist($userAdmin);
$bobUser->setName('Bobby');
$bobUser->setEmail('bobby@wallabag.org');
$bobUser->setUsername('bob');
- $bobUser->setPassword('test');
+ $bobUser->setPassword('mypassword');
$manager->persist($bobUser);
--- /dev/null
+<?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';
+ }
+}
{% 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 %}
*/
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.');
}
*/
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;
}
--- /dev/null
+<?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);
+ }
+ }
+}
$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()
$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',
$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);
$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]);
+ }
}
$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')
$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')
$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);
$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')
$form = $crawler->filter('button[type=submit]')->form();
$data = array(
'_username' => $username,
- '_password' => 'test',
+ '_password' => 'mypassword',
);
$this->client->submit($form, $data);