diff options
15 files changed, 526 insertions, 8 deletions
diff --git a/app/config/config_dev.yml b/app/config/config_dev.yml index efaf396e..205e0f66 100644 --- a/app/config/config_dev.yml +++ b/app/config/config_dev.yml | |||
@@ -44,5 +44,11 @@ monolog: | |||
44 | assetic: | 44 | assetic: |
45 | use_controller: true | 45 | use_controller: true |
46 | 46 | ||
47 | #swiftmailer: | 47 | swiftmailer: |
48 | # delivery_address: me@example.com | 48 | # see http://mailcatcher.me/ |
49 | transport: smtp | ||
50 | host: 'localhost' | ||
51 | port: 1025 | ||
52 | username: null | ||
53 | password: null | ||
54 | |||
diff --git a/app/config/config_test.yml b/app/config/config_test.yml index 32156963..4dca39d2 100644 --- a/app/config/config_test.yml +++ b/app/config/config_test.yml | |||
@@ -13,7 +13,9 @@ web_profiler: | |||
13 | intercept_redirects: false | 13 | intercept_redirects: false |
14 | 14 | ||
15 | swiftmailer: | 15 | swiftmailer: |
16 | disable_delivery: true | 16 | # to be able to read emails sent |
17 | spool: | ||
18 | type: file | ||
17 | 19 | ||
18 | doctrine: | 20 | doctrine: |
19 | dbal: | 21 | dbal: |
diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist index 44969750..8f967011 100644 --- a/app/config/parameters.yml.dist +++ b/app/config/parameters.yml.dist | |||
@@ -41,3 +41,4 @@ parameters: | |||
41 | items_on_page: 12 | 41 | items_on_page: 12 |
42 | theme: baggy | 42 | theme: baggy |
43 | language: en_US | 43 | language: en_US |
44 | from_email: no-reply@wallabag.org | ||
diff --git a/app/config/security.yml b/app/config/security.yml index e06c8967..90903f31 100644 --- a/app/config/security.yml +++ b/app/config/security.yml | |||
@@ -59,4 +59,5 @@ security: | |||
59 | - { path: ^/api/salt, roles: IS_AUTHENTICATED_ANONYMOUSLY } | 59 | - { path: ^/api/salt, roles: IS_AUTHENTICATED_ANONYMOUSLY } |
60 | - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY } | 60 | - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY } |
61 | - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } | 61 | - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } |
62 | - { path: ^/forgot-password, roles: IS_AUTHENTICATED_ANONYMOUSLY } | ||
62 | - { path: ^/, roles: ROLE_USER } | 63 | - { path: ^/, roles: ROLE_USER } |
diff --git a/src/Wallabag/CoreBundle/Controller/SecurityController.php b/src/Wallabag/CoreBundle/Controller/SecurityController.php index c2901da2..fe511db5 100644 --- a/src/Wallabag/CoreBundle/Controller/SecurityController.php +++ b/src/Wallabag/CoreBundle/Controller/SecurityController.php | |||
@@ -2,9 +2,12 @@ | |||
2 | 2 | ||
3 | namespace Wallabag\CoreBundle\Controller; | 3 | namespace Wallabag\CoreBundle\Controller; |
4 | 4 | ||
5 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | ||
6 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; | ||
5 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; | 7 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; |
6 | use Symfony\Component\HttpFoundation\Request; | 8 | use Symfony\Component\HttpFoundation\Request; |
7 | use Symfony\Component\Security\Core\SecurityContext; | 9 | use Symfony\Component\Security\Core\SecurityContext; |
10 | use Wallabag\CoreBundle\Form\Type\ResetPasswordType; | ||
8 | 11 | ||
9 | class SecurityController extends Controller | 12 | class SecurityController extends Controller |
10 | { | 13 | { |
@@ -25,4 +28,123 @@ class SecurityController extends Controller | |||
25 | 'error' => $error, | 28 | 'error' => $error, |
26 | )); | 29 | )); |
27 | } | 30 | } |
31 | |||
32 | /** | ||
33 | * Request forgot password: show form | ||
34 | * | ||
35 | * @Route("/forgot-password", name="forgot_password") | ||
36 | * @Method({"GET", "POST"}) | ||
37 | */ | ||
38 | public function forgotPasswordAction(Request $request) | ||
39 | { | ||
40 | $form = $this->createForm('forgot_password'); | ||
41 | $form->handleRequest($request); | ||
42 | |||
43 | if ($form->isValid()) { | ||
44 | $user = $this->getDoctrine()->getRepository('WallabagCoreBundle:User')->findOneByEmail($form->get('email')->getData()); | ||
45 | |||
46 | // generate "hard" token | ||
47 | $user->setConfirmationToken(rtrim(strtr(base64_encode(hash('sha256', uniqid(mt_rand(), true), true)), '+/', '-_'), '=')); | ||
48 | $user->setPasswordRequestedAt(new \DateTime()); | ||
49 | |||
50 | $em = $this->getDoctrine()->getManager(); | ||
51 | $em->persist($user); | ||
52 | $em->flush(); | ||
53 | |||
54 | $message = \Swift_Message::newInstance() | ||
55 | ->setSubject('Reset Password') | ||
56 | ->setFrom($this->container->getParameter('from_email')) | ||
57 | ->setTo($user->getEmail()) | ||
58 | ->setBody($this->renderView('WallabagCoreBundle:Mail:forgotPassword.txt.twig', array( | ||
59 | 'username' => $user->getUsername(), | ||
60 | 'confirmationUrl' => $this->generateUrl('forgot_password_reset', array('token' => $user->getConfirmationToken()), true), | ||
61 | ))) | ||
62 | ; | ||
63 | $this->get('mailer')->send($message); | ||
64 | |||
65 | return $this->redirect($this->generateUrl('forgot_password_check_email', | ||
66 | array('email' => $this->getObfuscatedEmail($user->getEmail())) | ||
67 | )); | ||
68 | } | ||
69 | |||
70 | return $this->render('WallabagCoreBundle:Security:forgotPassword.html.twig', array( | ||
71 | 'form' => $form->createView(), | ||
72 | )); | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * Tell the user to check his email provider | ||
77 | * | ||
78 | * @Route("/forgot-password/check-email", name="forgot_password_check_email") | ||
79 | * @Method({"GET"}) | ||
80 | */ | ||
81 | public function checkEmailAction(Request $request) | ||
82 | { | ||
83 | $email = $request->query->get('email'); | ||
84 | |||
85 | if (empty($email)) { | ||
86 | // the user does not come from the forgotPassword action | ||
87 | return $this->redirect($this->generateUrl('forgot_password')); | ||
88 | } | ||
89 | |||
90 | return $this->render('WallabagCoreBundle:Security:checkEmail.html.twig', array( | ||
91 | 'email' => $email, | ||
92 | )); | ||
93 | } | ||
94 | |||
95 | /** | ||
96 | * Reset user password | ||
97 | * | ||
98 | * @Route("/forgot-password/{token}", name="forgot_password_reset") | ||
99 | * @Method({"GET", "POST"}) | ||
100 | */ | ||
101 | public function resetAction(Request $request, $token) | ||
102 | { | ||
103 | $user = $this->getDoctrine()->getRepository('WallabagCoreBundle:User')->findOneByConfirmationToken($token); | ||
104 | |||
105 | if (null === $user) { | ||
106 | throw $this->createNotFoundException(sprintf('No user found with token "%s"', $token)); | ||
107 | } | ||
108 | |||
109 | $form = $this->createForm(new ResetPasswordType()); | ||
110 | $form->handleRequest($request); | ||
111 | |||
112 | if ($form->isValid()) { | ||
113 | $user->setPassword($form->get('new_password')->getData()); | ||
114 | |||
115 | $em = $this->getDoctrine()->getManager(); | ||
116 | $em->persist($user); | ||
117 | $em->flush(); | ||
118 | |||
119 | $this->get('session')->getFlashBag()->add( | ||
120 | 'notice', | ||
121 | 'The password has been reset successfully' | ||
122 | ); | ||
123 | |||
124 | return $this->redirect($this->generateUrl('login')); | ||
125 | } | ||
126 | |||
127 | return $this->render('WallabagCoreBundle:Security:reset.html.twig', array( | ||
128 | 'token' => $token, | ||
129 | 'form' => $form->createView(), | ||
130 | )); | ||
131 | } | ||
132 | |||
133 | /** | ||
134 | * Get the truncated email displayed when requesting the resetting. | ||
135 | * | ||
136 | * Keeping only the part following @ in the address. | ||
137 | * | ||
138 | * @param string $email | ||
139 | * | ||
140 | * @return string | ||
141 | */ | ||
142 | protected function getObfuscatedEmail($email) | ||
143 | { | ||
144 | if (false !== $pos = strpos($email, '@')) { | ||
145 | $email = '...'.substr($email, $pos); | ||
146 | } | ||
147 | |||
148 | return $email; | ||
149 | } | ||
28 | } | 150 | } |
diff --git a/src/Wallabag/CoreBundle/Entity/User.php b/src/Wallabag/CoreBundle/Entity/User.php index f05c8760..6a7619ac 100644 --- a/src/Wallabag/CoreBundle/Entity/User.php +++ b/src/Wallabag/CoreBundle/Entity/User.php | |||
@@ -78,6 +78,16 @@ class User implements AdvancedUserInterface, \Serializable | |||
78 | private $isActive = true; | 78 | private $isActive = true; |
79 | 79 | ||
80 | /** | 80 | /** |
81 | * @ORM\Column(name="confirmation_token", type="string", nullable=true) | ||
82 | */ | ||
83 | private $confirmationToken; | ||
84 | |||
85 | /** | ||
86 | * @ORM\Column(name="password_requested_at", type="datetime", nullable=true) | ||
87 | */ | ||
88 | private $passwordRequestedAt; | ||
89 | |||
90 | /** | ||
81 | * @var date | 91 | * @var date |
82 | * | 92 | * |
83 | * @ORM\Column(name="created_at", type="datetime") | 93 | * @ORM\Column(name="created_at", type="datetime") |
@@ -377,4 +387,50 @@ class User implements AdvancedUserInterface, \Serializable | |||
377 | { | 387 | { |
378 | return $this->config; | 388 | return $this->config; |
379 | } | 389 | } |
390 | |||
391 | /** | ||
392 | * Set confirmationToken | ||
393 | * | ||
394 | * @param string $confirmationToken | ||
395 | * @return User | ||
396 | */ | ||
397 | public function setConfirmationToken($confirmationToken) | ||
398 | { | ||
399 | $this->confirmationToken = $confirmationToken; | ||
400 | |||
401 | return $this; | ||
402 | } | ||
403 | |||
404 | /** | ||
405 | * Get confirmationToken | ||
406 | * | ||
407 | * @return string | ||
408 | */ | ||
409 | public function getConfirmationToken() | ||
410 | { | ||
411 | return $this->confirmationToken; | ||
412 | } | ||
413 | |||
414 | /** | ||
415 | * Set passwordRequestedAt | ||
416 | * | ||
417 | * @param \DateTime $passwordRequestedAt | ||
418 | * @return User | ||
419 | */ | ||
420 | public function setPasswordRequestedAt($passwordRequestedAt) | ||
421 | { | ||
422 | $this->passwordRequestedAt = $passwordRequestedAt; | ||
423 | |||
424 | return $this; | ||
425 | } | ||
426 | |||
427 | /** | ||
428 | * Get passwordRequestedAt | ||
429 | * | ||
430 | * @return \DateTime | ||
431 | */ | ||
432 | public function getPasswordRequestedAt() | ||
433 | { | ||
434 | return $this->passwordRequestedAt; | ||
435 | } | ||
380 | } | 436 | } |
diff --git a/src/Wallabag/CoreBundle/Form/Type/ForgotPasswordType.php b/src/Wallabag/CoreBundle/Form/Type/ForgotPasswordType.php new file mode 100644 index 00000000..c278b84f --- /dev/null +++ b/src/Wallabag/CoreBundle/Form/Type/ForgotPasswordType.php | |||
@@ -0,0 +1,52 @@ | |||
1 | <?php | ||
2 | namespace Wallabag\CoreBundle\Form\Type; | ||
3 | |||
4 | use Symfony\Component\Form\AbstractType; | ||
5 | use Symfony\Component\Form\FormBuilderInterface; | ||
6 | use Symfony\Component\Validator\Constraints; | ||
7 | use Symfony\Component\Validator\ExecutionContextInterface; | ||
8 | use Doctrine\Bundle\DoctrineBundle\Registry; | ||
9 | |||
10 | class ForgotPasswordType extends AbstractType | ||
11 | { | ||
12 | private $doctrine = null; | ||
13 | |||
14 | public function __construct(Registry $doctrine) | ||
15 | { | ||
16 | $this->doctrine = $doctrine; | ||
17 | } | ||
18 | |||
19 | public function buildForm(FormBuilderInterface $builder, array $options) | ||
20 | { | ||
21 | $builder | ||
22 | ->add('email', 'email', array( | ||
23 | 'constraints' => array( | ||
24 | new Constraints\Email(), | ||
25 | new Constraints\NotBlank(), | ||
26 | new Constraints\Callback(array(array($this, 'validateEmail'))), | ||
27 | ), | ||
28 | )) | ||
29 | ; | ||
30 | } | ||
31 | |||
32 | public function getName() | ||
33 | { | ||
34 | return 'forgot_password'; | ||
35 | } | ||
36 | |||
37 | public function validateEmail($email, ExecutionContextInterface $context) | ||
38 | { | ||
39 | $user = $this->doctrine | ||
40 | ->getRepository('WallabagCoreBundle:User') | ||
41 | ->findOneByEmail($email); | ||
42 | |||
43 | if (!$user) { | ||
44 | $context->addViolationAt( | ||
45 | 'email', | ||
46 | 'No user found with this email', | ||
47 | array(), | ||
48 | |||
49 | ); | ||
50 | } | ||
51 | } | ||
52 | } | ||
diff --git a/src/Wallabag/CoreBundle/Form/Type/ResetPasswordType.php b/src/Wallabag/CoreBundle/Form/Type/ResetPasswordType.php new file mode 100644 index 00000000..50ae800b --- /dev/null +++ b/src/Wallabag/CoreBundle/Form/Type/ResetPasswordType.php | |||
@@ -0,0 +1,34 @@ | |||
1 | <?php | ||
2 | namespace Wallabag\CoreBundle\Form\Type; | ||
3 | |||
4 | use Symfony\Component\Form\AbstractType; | ||
5 | use Symfony\Component\Form\FormBuilderInterface; | ||
6 | use Symfony\Component\Validator\Constraints; | ||
7 | |||
8 | class ResetPasswordType extends AbstractType | ||
9 | { | ||
10 | public function buildForm(FormBuilderInterface $builder, array $options) | ||
11 | { | ||
12 | $builder | ||
13 | ->add('new_password', 'repeated', array( | ||
14 | 'type' => 'password', | ||
15 | 'invalid_message' => 'The password fields must match.', | ||
16 | 'required' => true, | ||
17 | 'first_options' => array('label' => 'New password'), | ||
18 | 'second_options' => array('label' => 'Repeat new password'), | ||
19 | 'constraints' => array( | ||
20 | new Constraints\Length(array( | ||
21 | 'min' => 8, | ||
22 | 'minMessage' => 'Password should by at least 8 chars long', | ||
23 | )), | ||
24 | new Constraints\NotBlank(), | ||
25 | ), | ||
26 | )) | ||
27 | ; | ||
28 | } | ||
29 | |||
30 | public function getName() | ||
31 | { | ||
32 | return 'change_passwd'; | ||
33 | } | ||
34 | } | ||
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index c734a3a5..062e1651 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml | |||
@@ -22,9 +22,17 @@ services: | |||
22 | - @security.context | 22 | - @security.context |
23 | - %theme% # default theme from parameters.yml | 23 | - %theme% # default theme from parameters.yml |
24 | 24 | ||
25 | # custom form type | ||
25 | wallabag_core.form.type.config: | 26 | wallabag_core.form.type.config: |
26 | class: Wallabag\CoreBundle\Form\Type\ConfigType | 27 | class: Wallabag\CoreBundle\Form\Type\ConfigType |
27 | arguments: | 28 | arguments: |
28 | - %liip_theme.themes% | 29 | - %liip_theme.themes% |
29 | tags: | 30 | tags: |
30 | - { name: form.type, alias: config } | 31 | - { name: form.type, alias: config } |
32 | |||
33 | wallabag_core.form.type.forgot_password: | ||
34 | class: Wallabag\CoreBundle\Form\Type\ForgotPasswordType | ||
35 | arguments: | ||
36 | - @doctrine | ||
37 | tags: | ||
38 | - { name: form.type, alias: forgot_password } | ||
diff --git a/src/Wallabag/CoreBundle/Resources/views/Mail/forgotPassword.txt.twig b/src/Wallabag/CoreBundle/Resources/views/Mail/forgotPassword.txt.twig new file mode 100644 index 00000000..631bcb88 --- /dev/null +++ b/src/Wallabag/CoreBundle/Resources/views/Mail/forgotPassword.txt.twig | |||
@@ -0,0 +1,6 @@ | |||
1 | Hello {{username}}! | ||
2 | |||
3 | To reset your password - please visit {{confirmationUrl}} | ||
4 | |||
5 | Regards, | ||
6 | Wallabag bot | ||
diff --git a/src/Wallabag/CoreBundle/Resources/views/Security/checkEmail.html.twig b/src/Wallabag/CoreBundle/Resources/views/Security/checkEmail.html.twig new file mode 100644 index 00000000..056d65b5 --- /dev/null +++ b/src/Wallabag/CoreBundle/Resources/views/Security/checkEmail.html.twig | |||
@@ -0,0 +1,17 @@ | |||
1 | {% extends "WallabagCoreBundle::layout.html.twig" %} | ||
2 | |||
3 | {% block title %}{% trans %}Forgot password{% endtrans %}{% endblock %} | ||
4 | |||
5 | {% block body_class %}login{% endblock %} | ||
6 | |||
7 | {% block menu %}{% endblock %} | ||
8 | |||
9 | {% block content %} | ||
10 | <form> | ||
11 | <fieldset class="w500p center"> | ||
12 | <h2 class="mbs txtcenter">{% trans %}Forgot password{% endtrans %}</h2> | ||
13 | |||
14 | <p>{{ 'An email has been sent to %email%. It contains a link you must click to reset your password.'|trans({'%email%': email}) }}</p> | ||
15 | </fieldset> | ||
16 | </form> | ||
17 | {% endblock %} | ||
diff --git a/src/Wallabag/CoreBundle/Resources/views/Security/forgotPassword.html.twig b/src/Wallabag/CoreBundle/Resources/views/Security/forgotPassword.html.twig new file mode 100644 index 00000000..4476ea7b --- /dev/null +++ b/src/Wallabag/CoreBundle/Resources/views/Security/forgotPassword.html.twig | |||
@@ -0,0 +1,31 @@ | |||
1 | {% extends "WallabagCoreBundle::layout.html.twig" %} | ||
2 | |||
3 | {% block title %}{% trans %}Forgot password{% endtrans %}{% endblock %} | ||
4 | |||
5 | {% block body_class %}login{% endblock %} | ||
6 | |||
7 | {% block menu %}{% endblock %} | ||
8 | |||
9 | {% block content %} | ||
10 | <form action="{{ path('forgot_password') }}" method="post" name="forgotPasswordform"> | ||
11 | <fieldset class="w500p center"> | ||
12 | <h2 class="mbs txtcenter">{% trans %}Forgot password{% endtrans %}</h2> | ||
13 | |||
14 | {{ form_errors(form) }} | ||
15 | |||
16 | <p>Enter your email address below and we'll send you password reset instructions.</p> | ||
17 | |||
18 | <div class="row"> | ||
19 | {{ form_label(form.email) }} | ||
20 | {{ form_errors(form.email) }} | ||
21 | {{ form_widget(form.email) }} | ||
22 | </div> | ||
23 | |||
24 | <div class="row mts txtcenter"> | ||
25 | <button type="submit">Send me reset instructions</button> | ||
26 | </div> | ||
27 | </fieldset> | ||
28 | |||
29 | {{ form_rest(form) }} | ||
30 | </form> | ||
31 | {% endblock %} | ||
diff --git a/src/Wallabag/CoreBundle/Resources/views/Security/login.html.twig b/src/Wallabag/CoreBundle/Resources/views/Security/login.html.twig index eb8f08c8..f669574e 100644 --- a/src/Wallabag/CoreBundle/Resources/views/Security/login.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/Security/login.html.twig | |||
@@ -5,15 +5,19 @@ | |||
5 | {% block body_class %}login{% endblock %} | 5 | {% block body_class %}login{% endblock %} |
6 | 6 | ||
7 | {% block menu %}{% endblock %} | 7 | {% block menu %}{% endblock %} |
8 | {% block messages %}{% endblock %} | ||
8 | 9 | ||
9 | {% block content %} | 10 | {% block content %} |
10 | {% if error %} | ||
11 | <div>{{ error.message }}</div> | ||
12 | {% endif %} | ||
13 | |||
14 | <form action="{{ path('login_check') }}" method="post" name="loginform"> | 11 | <form action="{{ path('login_check') }}" method="post" name="loginform"> |
15 | <fieldset class="w500p center"> | 12 | <fieldset class="w500p center"> |
16 | <h2 class="mbs txtcenter">{% trans %}Login to wallabag{% endtrans %}</h2> | 13 | <h2 class="mbs txtcenter">{% trans %}Login to wallabag{% endtrans %}</h2> |
14 | {% if error %} | ||
15 | <div>{{ error.message }}</div> | ||
16 | {% endif %} | ||
17 | |||
18 | {% for flashMessage in app.session.flashbag.get('notice') %} | ||
19 | <p>{{ flashMessage }}</p> | ||
20 | {% endfor %} | ||
17 | 21 | ||
18 | <div class="row"> | 22 | <div class="row"> |
19 | <label class="col w150p" for="username">{% trans %}Username{% endtrans %}</label> | 23 | <label class="col w150p" for="username">{% trans %}Username{% endtrans %}</label> |
@@ -26,7 +30,8 @@ | |||
26 | </div> | 30 | </div> |
27 | 31 | ||
28 | <div class="row mts txtcenter"> | 32 | <div class="row mts txtcenter"> |
29 | <button type="submit">login</button> | 33 | <button type="submit">Login</button> |
34 | <a href="{{ path('forgot_password') }}" class="small">Forgot your password?</a> | ||
30 | </div> | 35 | </div> |
31 | </fieldset> | 36 | </fieldset> |
32 | </form> | 37 | </form> |
diff --git a/src/Wallabag/CoreBundle/Resources/views/Security/reset.html.twig b/src/Wallabag/CoreBundle/Resources/views/Security/reset.html.twig new file mode 100644 index 00000000..fda88af2 --- /dev/null +++ b/src/Wallabag/CoreBundle/Resources/views/Security/reset.html.twig | |||
@@ -0,0 +1,35 @@ | |||
1 | {% extends "WallabagCoreBundle::layout.html.twig" %} | ||
2 | |||
3 | {% block title %}{% trans %}Change password{% endtrans %}{% endblock %} | ||
4 | |||
5 | {% block body_class %}login{% endblock %} | ||
6 | |||
7 | {% block menu %}{% endblock %} | ||
8 | |||
9 | {% block content %} | ||
10 | <form action="{{ path('forgot_password_reset', {'token': token}) }}" method="post" name="loginform"> | ||
11 | <fieldset class="w500p center"> | ||
12 | <h2 class="mbs txtcenter">{% trans %}Change password{% endtrans %}</h2> | ||
13 | |||
14 | {{ form_errors(form) }} | ||
15 | |||
16 | <div class="row"> | ||
17 | {{ form_label(form.new_password.first) }} | ||
18 | {{ form_errors(form.new_password.first) }} | ||
19 | {{ form_widget(form.new_password.first) }} | ||
20 | </div> | ||
21 | |||
22 | <div class="row"> | ||
23 | {{ form_label(form.new_password.second) }} | ||
24 | {{ form_errors(form.new_password.second) }} | ||
25 | {{ form_widget(form.new_password.second) }} | ||
26 | </div> | ||
27 | |||
28 | <div class="row mts txtcenter"> | ||
29 | <button type="submit">Change password</button> | ||
30 | </div> | ||
31 | </fieldset> | ||
32 | |||
33 | {{ form_rest(form) }} | ||
34 | </form> | ||
35 | {% endblock %} | ||
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php index 54cf5073..1dd05f89 100644 --- a/src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php +++ b/src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php | |||
@@ -3,6 +3,8 @@ | |||
3 | namespace Wallabag\CoreBundle\Tests\Controller; | 3 | namespace Wallabag\CoreBundle\Tests\Controller; |
4 | 4 | ||
5 | use Wallabag\CoreBundle\Tests\WallabagTestCase; | 5 | use Wallabag\CoreBundle\Tests\WallabagTestCase; |
6 | use Symfony\Component\Filesystem\Filesystem; | ||
7 | use Symfony\Component\Finder\Finder; | ||
6 | 8 | ||
7 | class SecurityControllerTest extends WallabagTestCase | 9 | class SecurityControllerTest extends WallabagTestCase |
8 | { | 10 | { |
@@ -37,4 +39,144 @@ class SecurityControllerTest extends WallabagTestCase | |||
37 | 39 | ||
38 | $this->assertContains('Bad credentials', $client->getResponse()->getContent()); | 40 | $this->assertContains('Bad credentials', $client->getResponse()->getContent()); |
39 | } | 41 | } |
42 | |||
43 | public function testForgotPassword() | ||
44 | { | ||
45 | $client = $this->getClient(); | ||
46 | |||
47 | $crawler = $client->request('GET', '/forgot-password'); | ||
48 | |||
49 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
50 | |||
51 | $this->assertContains('Forgot password', $client->getResponse()->getContent()); | ||
52 | |||
53 | $form = $crawler->filter('button[type=submit]'); | ||
54 | |||
55 | $this->assertCount(1, $form); | ||
56 | |||
57 | return array( | ||
58 | 'form' => $form->form(), | ||
59 | 'client' => $client, | ||
60 | ); | ||
61 | } | ||
62 | |||
63 | /** | ||
64 | * @depends testForgotPassword | ||
65 | */ | ||
66 | public function testSubmitForgotPasswordFail($parameters) | ||
67 | { | ||
68 | $form = $parameters['form']; | ||
69 | $client = $parameters['client']; | ||
70 | |||
71 | $data = array( | ||
72 | 'forgot_password[email]' => 'baggy', | ||
73 | ); | ||
74 | |||
75 | $client->submit($form, $data); | ||
76 | |||
77 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
78 | $this->assertContains('No user found with this email', $client->getResponse()->getContent()); | ||
79 | } | ||
80 | |||
81 | /** | ||
82 | * @depends testForgotPassword | ||
83 | * | ||
84 | * Instead of using collector which slow down the test suite | ||
85 | * http://symfony.com/doc/current/cookbook/email/testing.html | ||
86 | * | ||
87 | * Use a different way where Swift store email as file | ||
88 | */ | ||
89 | public function testSubmitForgotPassword($parameters) | ||
90 | { | ||
91 | $form = $parameters['form']; | ||
92 | $client = $parameters['client']; | ||
93 | |||
94 | $spoolDir = $client->getKernel()->getContainer()->getParameter('swiftmailer.spool.default.file.path'); | ||
95 | |||
96 | // cleanup pool dir | ||
97 | $filesystem = new Filesystem(); | ||
98 | $filesystem->remove($spoolDir); | ||
99 | |||
100 | // to use `getCollector` since `collect: false` in config_test.yml | ||
101 | $client->enableProfiler(); | ||
102 | |||
103 | $data = array( | ||
104 | 'forgot_password[email]' => 'bobby@wallabag.org', | ||
105 | ); | ||
106 | |||
107 | $client->submit($form, $data); | ||
108 | |||
109 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
110 | |||
111 | $crawler = $client->followRedirect(); | ||
112 | |||
113 | $this->assertContains('An email has been sent to', $client->getResponse()->getContent()); | ||
114 | |||
115 | // find every files (ie: emails) inside the spool dir except hidden files | ||
116 | $finder = new Finder(); | ||
117 | $finder | ||
118 | ->in($spoolDir) | ||
119 | ->ignoreDotFiles(true) | ||
120 | ->files(); | ||
121 | |||
122 | $this->assertCount(1, $finder, 'Only one email has been sent'); | ||
123 | |||
124 | foreach ($finder as $file) { | ||
125 | $message = unserialize(file_get_contents($file)); | ||
126 | |||
127 | $this->assertInstanceOf('Swift_Message', $message); | ||
128 | $this->assertEquals('Reset Password', $message->getSubject()); | ||
129 | $this->assertEquals('no-reply@wallabag.org', key($message->getFrom())); | ||
130 | $this->assertEquals('bobby@wallabag.org', key($message->getTo())); | ||
131 | $this->assertContains( | ||
132 | 'To reset your password - please visit', | ||
133 | $message->getBody() | ||
134 | ); | ||
135 | } | ||
136 | } | ||
137 | |||
138 | public function testReset() | ||
139 | { | ||
140 | $client = $this->getClient(); | ||
141 | $user = $client->getContainer() | ||
142 | ->get('doctrine.orm.entity_manager') | ||
143 | ->getRepository('WallabagCoreBundle:User') | ||
144 | ->findOneByEmail('bobby@wallabag.org'); | ||
145 | |||
146 | $crawler = $client->request('GET', '/forgot-password/'.$user->getConfirmationToken()); | ||
147 | |||
148 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
149 | $this->assertCount(2, $crawler->filter('input[type=password]')); | ||
150 | $this->assertCount(1, $form = $crawler->filter('button[type=submit]')); | ||
151 | $this->assertCount(1, $form); | ||
152 | |||
153 | $data = array( | ||
154 | 'change_passwd[new_password][first]' => 'mypassword', | ||
155 | 'change_passwd[new_password][second]' => 'mypassword', | ||
156 | ); | ||
157 | |||
158 | $client->submit($form->form(), $data); | ||
159 | |||
160 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
161 | $this->assertContains('login', $client->getResponse()->headers->get('location')); | ||
162 | } | ||
163 | |||
164 | public function testResetBadToken() | ||
165 | { | ||
166 | $client = $this->getClient(); | ||
167 | |||
168 | $client->request('GET', '/forgot-password/UIZOAU29UE902IEPZO'); | ||
169 | |||
170 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
171 | } | ||
172 | |||
173 | public function testCheckEmailWithoutEmail() | ||
174 | { | ||
175 | $client = $this->getClient(); | ||
176 | |||
177 | $client->request('GET', '/forgot-password/check-email'); | ||
178 | |||
179 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
180 | $this->assertContains('forgot-password', $client->getResponse()->headers->get('location')); | ||
181 | } | ||
40 | } | 182 | } |