1import unittest
2
3import pytz
4
5from django.conf import settings
6from django.contrib.auth import get_user_model
7from django.contrib.auth import views as auth_views
8from django.contrib.auth.models import Group, Permission
9from django.contrib.auth.tokens import PasswordResetTokenGenerator
10from django.core import mail
11from django.core.files.uploadedfile import SimpleUploadedFile
12from django.test import RequestFactory, TestCase, override_settings
13from django.urls import reverse
14
15from wagtail.admin.localization import (
16    WAGTAILADMIN_PROVIDED_LANGUAGES, get_available_admin_languages, get_available_admin_time_zones)
17from wagtail.admin.views.account import account, profile_tab
18from wagtail.images.tests.utils import get_test_image_file
19from wagtail.tests.utils import WagtailTestUtils
20from wagtail.users.models import UserProfile
21
22
23class TestAuthentication(TestCase, WagtailTestUtils):
24    """
25    This tests that users can login and logout of the admin interface
26    """
27    def test_login_view(self):
28        """
29        This tests that the login view responds with a login page
30        """
31        # Get login page
32        response = self.client.get(reverse('wagtailadmin_login'))
33
34        # Check that the user received a login page
35        self.assertEqual(response.status_code, 200)
36        self.assertTemplateUsed(response, 'wagtailadmin/login.html')
37
38    def test_login_view_post(self):
39        """
40        This posts user credentials to the login view and checks that
41        the user was logged in successfully
42        """
43        # Create user to log in with
44        self.create_superuser(username='test', email='test@email.com', password='password')
45
46        # Post credentials to the login page
47        response = self.client.post(reverse('wagtailadmin_login'), {
48            'username': 'test@email.com' if settings.AUTH_USER_MODEL == 'emailuser.EmailUser' else 'test',
49            'password': 'password',
50
51            # NOTE: This is set using a hidden field in reality
52            'next': reverse('wagtailadmin_home'),
53        })
54
55        # Check that the user was redirected to the dashboard
56        self.assertRedirects(response, reverse('wagtailadmin_home'))
57
58        # Check that the user was logged in
59        self.assertTrue('_auth_user_id' in self.client.session)
60        self.assertEqual(
61            str(self.client.session['_auth_user_id']),
62            str(get_user_model().objects.get(email='test@email.com').pk)
63        )
64
65    def test_already_logged_in_redirect(self):
66        """
67        This tests that a user who is already logged in is automatically
68        redirected to the admin dashboard if they try to access the login
69        page
70        """
71        # Login
72        self.login()
73
74        # Get login page
75        response = self.client.get(reverse('wagtailadmin_login'))
76
77        # Check that the user was redirected to the dashboard
78        self.assertRedirects(response, reverse('wagtailadmin_home'))
79
80    def test_logged_in_as_non_privileged_user_doesnt_redirect(self):
81        """
82        This tests that if the user is logged in but hasn't got permission
83        to access the admin, they are not redirected to the admin
84
85        This tests issue #431
86        """
87        # Login as unprivileged user
88        self.create_user(username='unprivileged', password='123')
89        self.login(username='unprivileged', password='123')
90
91        # Get login page
92        response = self.client.get(reverse('wagtailadmin_login'))
93
94        # Check that the user received a login page and was not redirected
95        self.assertEqual(response.status_code, 200)
96        self.assertTemplateUsed(response, 'wagtailadmin/login.html')
97
98    def test_logout(self):
99        """
100        This tests that the user can logout
101        """
102        # Login
103        self.login()
104
105        # Get logout page
106        response = self.client.get(reverse('wagtailadmin_logout'))
107
108        # Check that the user was redirected to the login page
109        self.assertRedirects(response, reverse('wagtailadmin_login'))
110
111        # Check that the user was logged out
112        self.assertFalse('_auth_user_id' in self.client.session)
113
114    def test_not_logged_in_redirect(self):
115        """
116        This tests that a not logged in user is redirected to the
117        login page
118        """
119        # Get dashboard
120        response = self.client.get(reverse('wagtailadmin_home'))
121
122        # Check that the user was redirected to the login page and that next was set correctly
123        self.assertRedirects(response, reverse('wagtailadmin_login') + '?next=' + reverse('wagtailadmin_home'))
124
125    def test_not_logged_in_gives_403_to_ajax_requests(self):
126        """
127        This tests that a not logged in user is given a 403 error on AJAX requests
128        """
129        # Get dashboard
130        response = self.client.get(reverse('wagtailadmin_home'), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
131
132        # AJAX requests should be given a 403 error instead of being redirected
133        self.assertEqual(response.status_code, 403)
134
135    def test_not_logged_in_redirect_default_settings(self):
136        """
137        This does the same as the above test but checks that it
138        redirects to the correct place when the user has not set
139        the LOGIN_URL setting correctly
140        """
141        # Get dashboard with default LOGIN_URL setting
142        with self.settings(LOGIN_URL='django.contrib.auth.views.login'):
143            response = self.client.get(reverse('wagtailadmin_home'))
144
145        # Check that the user was redirected to the login page and that next was set correctly
146        # Note: The user will be redirected to 'django.contrib.auth.views.login' but
147        # this must be the same URL as 'wagtailadmin_login'
148        self.assertEqual(response.status_code, 302)
149        self.assertRedirects(response, reverse('wagtailadmin_login') + '?next=' + reverse('wagtailadmin_home'))
150
151    def test_logged_in_no_permission_redirect(self):
152        """
153        This tests that a logged in user without admin access permissions is
154        redirected to the login page, with an error message
155        """
156        # Login as unprivileged user
157        self.create_user(username='unprivileged', password='123')
158        self.login(username='unprivileged', password='123')
159
160        # Get dashboard
161        response = self.client.get(reverse('wagtailadmin_home'), follow=True)
162
163        # Check that the user was redirected to the login page and that next was set correctly
164        self.assertRedirects(response, reverse('wagtailadmin_login') + '?next=' + reverse('wagtailadmin_home'))
165        self.assertContains(response, 'You do not have permission to access the admin')
166
167    def test_logged_in_no_permission_gives_403_to_ajax_requests(self):
168        """
169        This tests that a logged in user without admin access permissions is
170        given a 403 error on ajax requests
171        """
172        # Login as unprivileged user
173        self.create_user(username='unprivileged', password='123')
174        self.login(username='unprivileged', password='123')
175
176        # Get dashboard
177        response = self.client.get(reverse('wagtailadmin_home'), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
178
179        # AJAX requests should be given a 403 error instead of being redirected
180        self.assertEqual(response.status_code, 403)
181
182
183class TestAccountSectionUtilsMixin:
184    def assertPanelActive(self, response, name):
185        panels = set()
186        for panelset in response.context['panels_by_tab'].values():
187            for panel in panelset:
188                panels.add(panel.name)
189        self.assertIn(name, panels, "Panel %s not active in response" % name)
190
191    def assertPanelNotActive(self, response, name):
192        panels = set()
193        for panelset in response.context['panels_by_tab'].values():
194            for panel in panelset:
195                panels.add(panel.name)
196        self.assertNotIn(name, panels, "Panel %s active in response" % name)
197
198    def post_form(self, extra_post_data):
199        post_data = {
200            'name_email-first_name': 'Test',
201            'name_email-last_name': 'User',
202            'name_email-email': self.user.email,
203            'notifications-submitted_notifications': 'false',
204            'notifications-approved_notifications': 'false',
205            'notifications-rejected_notifications': 'true',
206            'notifications-updated_comments_notifications': 'true',
207            'locale-preferred_language': 'es',
208            'locale-current_time_zone': 'Europe/London',
209        }
210        post_data.update(extra_post_data)
211        return self.client.post(reverse('wagtailadmin_account'), post_data)
212
213
214class TestAccountSection(TestCase, WagtailTestUtils, TestAccountSectionUtilsMixin):
215    """
216    This tests that the accounts section is working
217    """
218    def setUp(self):
219        self.user = self.login()
220
221    def test_account_view(self):
222        """
223        This tests that the accounts view responds with an index page
224        """
225        # Get account page
226        response = self.client.get(reverse('wagtailadmin_account'))
227
228        # Check that the user received an account page
229        self.assertEqual(response.status_code, 200)
230        self.assertTemplateUsed(response, 'wagtailadmin/account/account.html')
231
232        self.assertPanelActive(response, 'name_email')
233        self.assertPanelActive(response, 'notifications')
234        self.assertPanelActive(response, 'locale')
235        self.assertPanelActive(response, 'password')
236
237        # These fields may hide themselves
238        self.assertContains(response, "Email:")
239        self.assertContains(response, "Preferred language:")
240
241        if settings.USE_TZ:
242            self.assertContains(response, "Current time zone:")
243        else:
244            self.assertNotContains(response, "Current time zone:")
245
246        # Form media should be included on the page
247        self.assertContains(response, 'vendor/colorpicker.js')
248
249    def test_change_name_post(self):
250        response = self.post_form({
251            'name_email-first_name': 'Fox',
252            'name_email-last_name': 'Mulder',
253        })
254
255        # Check that the user was redirected to the account page
256        self.assertRedirects(response, reverse('wagtailadmin_account'))
257
258        # Check that the name was changed
259        self.user.refresh_from_db()
260        self.assertEqual(self.user.first_name, 'Fox')
261        self.assertEqual(self.user.last_name, 'Mulder')
262
263    def test_change_email_post(self):
264        response = self.post_form({
265            'name_email-email': 'test@email.com',
266        })
267
268        # Check that the user was redirected to the account page
269        self.assertRedirects(response, reverse('wagtailadmin_account'))
270
271        # Check that the email was changed
272        self.user.refresh_from_db()
273        self.assertEqual(self.user.email, 'test@email.com')
274
275    def test_change_email_not_valid(self):
276        response = self.post_form({
277            'name_email-email': 'test@email',
278        })
279
280        # Check that the user wasn't redirected
281        self.assertEqual(response.status_code, 200)
282
283        # Check that a validation error was raised
284        self.assertTrue('email' in response.context['panels_by_tab'][profile_tab][0].get_form().errors.keys())
285
286        # Check that the email was not changed
287        self.user.refresh_from_db()
288        self.assertNotEqual(self.user.email, 'test@email')
289
290    @override_settings(WAGTAIL_EMAIL_MANAGEMENT_ENABLED=False)
291    def test_with_email_management_disabled(self):
292        # Get account page
293        response = self.client.get(reverse('wagtailadmin_account'))
294
295        self.assertEqual(response.status_code, 200)
296        self.assertTemplateUsed(response, 'wagtailadmin/account/account.html')
297        self.assertNotContains(response, "Email:")
298
299    @override_settings(WAGTAIL_PASSWORD_MANAGEMENT_ENABLED=False)
300    def test_account_view_with_password_management_disabled(self):
301        # Get account page
302        response = self.client.get(reverse('wagtailadmin_account'))
303
304        self.assertEqual(response.status_code, 200)
305        self.assertTemplateUsed(response, 'wagtailadmin/account/account.html')
306        # Page should NOT contain a 'Change password' option
307        self.assertNotContains(response, "Change password")
308
309    @override_settings(WAGTAIL_PASSWORD_MANAGEMENT_ENABLED=False)
310    def test_change_password_view_disabled(self):
311        response = self.client.get(reverse('wagtailadmin_account'))
312        self.assertPanelNotActive(response, 'password')
313
314    def test_change_password(self):
315        response = self.post_form({
316            'password-old_password': 'password',
317            'password-new_password1': 'newpassword',
318            'password-new_password2': 'newpassword',
319        })
320
321        # Check that the user was redirected to the account page
322        self.assertRedirects(response, reverse('wagtailadmin_account'))
323
324        # Check that the password was changed
325        self.user.refresh_from_db()
326        self.assertTrue(self.user.check_password('newpassword'))
327
328    def test_change_password_post_password_mismatch(self):
329        response = self.post_form({
330            'password-old_password': 'password',
331            'password-new_password1': 'newpassword',
332            'password-new_password2': 'badpassword',
333        })
334
335        # Check that the user wasn't redirected
336        self.assertEqual(response.status_code, 200)
337
338        # Find password panel through context
339        password_panel = None
340        for panelset in response.context['panels_by_tab'].values():
341            for panel in panelset:
342                if panel.name == 'password':
343                    password_panel = panel
344                    break
345
346        # Check that a validation error was raised
347        password_form = password_panel.get_form()
348        self.assertTrue('new_password2' in password_form.errors.keys())
349        self.assertTrue("The two password fields didn’t match." in password_form.errors['new_password2'])
350
351        # Check that the password was not changed
352        self.user.refresh_from_db()
353        self.assertTrue(self.user.check_password('password'))
354
355    def test_change_notifications(self):
356        response = self.post_form({
357            'submitted_notifications': 'false',
358            'approved_notifications': 'false',
359            'rejected_notifications': 'true',
360            'updated_comments_notifications': 'true',
361        })
362
363        # Check that the user was redirected to the account page
364        self.assertRedirects(response, reverse('wagtailadmin_account'))
365
366        profile = UserProfile.get_for_user(get_user_model().objects.get(pk=self.user.pk))
367
368        # Check that the notification preferences are as submitted
369        self.assertFalse(profile.submitted_notifications)
370        self.assertFalse(profile.approved_notifications)
371        self.assertTrue(profile.rejected_notifications)
372        self.assertTrue(profile.updated_comments_notifications)
373
374    def test_change_language_preferences(self):
375        response = self.post_form({
376            'locale-preferred_language': 'es',
377        })
378
379        # Check that the user was redirected to the account page
380        self.assertRedirects(response, reverse('wagtailadmin_account'))
381
382        profile = UserProfile.get_for_user(self.user)
383        profile.refresh_from_db()
384
385        # Check that the language preferences are stored
386        self.assertEqual(profile.preferred_language, 'es')
387
388        # check that the updated language preference is now indicated in HTML header
389        response = self.client.get(reverse('wagtailadmin_home'))
390        self.assertContains(response, '<html class="no-js" lang="es" dir="ltr">')
391
392    def test_unset_language_preferences(self):
393        profile = UserProfile.get_for_user(self.user)
394        profile.preferred_language = 'en'
395        profile.save()
396
397        response = self.post_form({
398            'locale-preferred_language': '',
399        })
400
401        # Check that the user was redirected to the account page
402        self.assertRedirects(response, reverse('wagtailadmin_account'))
403
404        # Check that the language preferences are stored
405        profile.refresh_from_db()
406        self.assertEqual(profile.preferred_language, '')
407
408        # Check that the current language is assumed as English
409        self.assertEqual(profile.get_preferred_language(), "en")
410
411    @override_settings(WAGTAILADMIN_PERMITTED_LANGUAGES=[('en', 'English'), ('es', 'Spanish')])
412    def test_available_admin_languages_with_permitted_languages(self):
413        self.assertListEqual(get_available_admin_languages(), [('en', 'English'), ('es', 'Spanish')])
414
415    def test_available_admin_languages_by_default(self):
416        self.assertListEqual(get_available_admin_languages(), WAGTAILADMIN_PROVIDED_LANGUAGES)
417
418    @override_settings(WAGTAILADMIN_PERMITTED_LANGUAGES=[('en', 'English')])
419    def test_not_show_options_if_only_one_language_is_permitted(self):
420        response = self.client.get(reverse('wagtailadmin_account'))
421        self.assertNotContains(response, "Preferred language:")
422
423    @unittest.skipUnless(settings.USE_TZ, "Timezone support is disabled")
424    def test_change_current_time_zone(self):
425        response = self.post_form({
426            'locale-current_time_zone': 'Pacific/Fiji',
427        })
428
429        # Check that the user was redirected to the account page
430        self.assertRedirects(response, reverse('wagtailadmin_account'))
431
432        profile = UserProfile.get_for_user(self.user)
433        profile.refresh_from_db()
434
435        # Check that the current time zone is stored
436        self.assertEqual(profile.current_time_zone, 'Pacific/Fiji')
437
438    @unittest.skipUnless(settings.USE_TZ, "Timezone support is disabled")
439    def test_unset_current_time_zone(self):
440        response = self.post_form({
441            'locale-current_time_zone': '',
442        })
443
444        # Check that the user was redirected to the account page
445        self.assertRedirects(response, reverse('wagtailadmin_account'))
446
447        profile = UserProfile.get_for_user(self.user)
448        profile.refresh_from_db()
449
450        # Check that the current time zone are stored
451        self.assertEqual(profile.current_time_zone, '')
452
453    @unittest.skipUnless(settings.USE_TZ, "Timezone support is disabled")
454    @override_settings(WAGTAIL_USER_TIME_ZONES=['Africa/Addis_Ababa', 'America/Argentina/Buenos_Aires'])
455    def test_available_admin_time_zones_with_permitted_time_zones(self):
456        self.assertListEqual(get_available_admin_time_zones(),
457                             ['Africa/Addis_Ababa', 'America/Argentina/Buenos_Aires'])
458
459    @unittest.skipUnless(settings.USE_TZ, "Timezone support is disabled")
460    def test_available_admin_time_zones_by_default(self):
461        self.assertListEqual(get_available_admin_time_zones(), pytz.common_timezones)
462
463    @unittest.skipUnless(settings.USE_TZ, "Timezone support is disabled")
464    @override_settings(WAGTAIL_USER_TIME_ZONES=['Europe/London'])
465    def test_not_show_options_if_only_one_time_zone_is_permitted(self):
466        response = self.client.get(reverse('wagtailadmin_account'))
467        self.assertNotContains(response, "Current time zone:")
468
469    @unittest.skipIf(settings.USE_TZ, "Timezone support is enabled")
470    def test_not_show_options_if_timezone_support_disabled(self):
471        response = self.client.get(reverse('wagtailadmin_account'))
472        self.assertNotContains(response, "Current time zone:")
473
474    @unittest.skipUnless(settings.USE_TZ, "Timezone support is disabled")
475    @override_settings(
476        WAGTAIL_USER_TIME_ZONES=['Europe/London'],
477        WAGTAILADMIN_PERMITTED_LANGUAGES=[('en', 'English')]
478    )
479    def test_doesnt_render_locale_panel_when_only_one_timezone_and_one_locale_permitted(self):
480        response = self.client.get(reverse('wagtailadmin_account'))
481        self.assertPanelNotActive(response, 'locale')
482
483    def test_sensitive_post_parameters(self):
484        request = RequestFactory().post('wagtailadmin_account', data={})
485        request.user = self.user
486        account(request)
487        self.assertTrue(hasattr(request, 'sensitive_post_parameters'))
488        self.assertEqual(request.sensitive_post_parameters, '__ALL__')
489
490
491class TestAccountUploadAvatar(TestCase, WagtailTestUtils, TestAccountSectionUtilsMixin):
492    def setUp(self):
493        self.user = self.login()
494        self.avatar = get_test_image_file()
495        self.other_avatar = get_test_image_file()
496
497    def test_account_view(self):
498        """
499        This tests that the account view renders a "Upload a profile picture:" field
500        """
501        response = self.client.get(reverse('wagtailadmin_account'))
502
503        self.assertEqual(response.status_code, 200)
504        self.assertContains(response, "Upload a profile picture:")
505
506    def test_set_custom_avatar_stores_and_get_custom_avatar(self):
507        response = self.post_form({
508            'avatar-avatar': SimpleUploadedFile('other.png', self.other_avatar.file.getvalue())
509        })
510        # Check that the user was redirected to the account page
511        self.assertRedirects(response, reverse('wagtailadmin_account'))
512
513        profile = UserProfile.get_for_user(self.user)
514        profile.refresh_from_db()
515        self.assertIn('other.png', profile.avatar.url)
516
517    def test_user_upload_another_image_removes_previous_one(self):
518        profile = UserProfile.get_for_user(self.user)
519        profile.avatar = self.avatar
520        profile.save()
521
522        old_avatar_path = profile.avatar.path
523
524        # Upload a new avatar
525        response = self.post_form({
526            'avatar-avatar': SimpleUploadedFile('other.png', self.other_avatar.file.getvalue())
527        })
528        # Check that the user was redirected to the account page
529        self.assertRedirects(response, reverse('wagtailadmin_account'))
530
531        # Check the avatar was changed
532        profile.refresh_from_db()
533        self.assertIn('other.png', profile.avatar.url)
534
535        # Check old avatar doesn't exist anymore in filesystem
536        with self.assertRaises(FileNotFoundError):
537            open(old_avatar_path)
538
539    def test_no_value_preserves_current_avatar(self):
540        """
541        Tests that submitting a blank value for avatar doesn't remove it.
542        """
543        profile = UserProfile.get_for_user(self.user)
544        profile.avatar = self.avatar
545        profile.save()
546
547        # Upload a new avatar
548        response = self.post_form({})
549        # Check that the user was redirected to the account page
550        self.assertRedirects(response, reverse('wagtailadmin_account'))
551
552        # Check the avatar was changed
553        profile.refresh_from_db()
554        self.assertIn('test.png', profile.avatar.url)
555
556    def test_clear_removes_current_avatar(self):
557        """
558        Tests that submitting a blank value for avatar doesn't remove it.
559        """
560        profile = UserProfile.get_for_user(self.user)
561        profile.avatar = self.avatar
562        profile.save()
563
564        # Upload a new avatar
565        response = self.post_form({
566            'avatar-clear': 'on'
567        })
568        # Check that the user was redirected to the account page
569        self.assertRedirects(response, reverse('wagtailadmin_account'))
570
571        # Check the avatar was changed
572        profile.refresh_from_db()
573        self.assertIn('test.png', profile.avatar.url)
574
575
576class TestAccountManagementForNonModerator(TestCase, WagtailTestUtils):
577    """
578    Tests of reduced-functionality for editors
579    """
580    def setUp(self):
581        # Create a non-moderator user
582        self.submitter = self.create_user('submitter', 'submitter@example.com', 'password')
583        self.submitter.groups.add(Group.objects.get(name='Editors'))
584
585        self.login(username='submitter', password='password')
586
587    def test_notification_preferences_panel_reduced_for_non_moderators(self):
588        """
589        This tests that a user without publish permissions is not shown the
590        notification preference for 'submitted' items
591        """
592        response = self.client.get(reverse('wagtailadmin_account'))
593
594        # Find notifications panel through context
595        notifications_panel = None
596        for panelset in response.context['panels_by_tab'].values():
597            for panel in panelset:
598                if panel.name == 'notifications':
599                    notifications_panel = panel
600                    break
601
602        notifications_form = notifications_panel.get_form()
603        self.assertIn('approved_notifications', notifications_form.fields.keys())
604        self.assertIn('rejected_notifications', notifications_form.fields.keys())
605        self.assertNotIn('submitted_notifications', notifications_form.fields.keys())
606        self.assertIn('updated_comments_notifications', notifications_form.fields.keys())
607
608
609class TestAccountManagementForAdminOnlyUser(TestCase, WagtailTestUtils, TestAccountSectionUtilsMixin):
610    """
611    Tests for users with no edit/publish permissions at all
612    """
613    def setUp(self):
614        # Create a non-moderator user
615        admin_only_group = Group.objects.create(name='Admin Only')
616        admin_only_group.permissions.add(Permission.objects.get(codename='access_admin'))
617
618        self.admin_only_user = self.create_user(
619            'admin_only_user',
620            'admin_only_user@example.com',
621            'password'
622        )
623        self.admin_only_user.groups.add(admin_only_group)
624
625        self.login(username='admin_only_user', password='password')
626
627    def test_notification_preferences_not_rendered_for_admin_only_users(self):
628        """
629        Test that the user is not shown the notification preferences panel
630        """
631        response = self.client.get(reverse('wagtailadmin_account'))
632        self.assertPanelNotActive(response, 'notifications')
633
634
635class TestPasswordReset(TestCase, WagtailTestUtils):
636    """
637    This tests that the password reset is working
638    """
639    def setUp(self):
640        # Create a user
641        self.create_superuser(username='test', email='test@email.com', password='password')
642
643    def test_password_reset_view(self):
644        """
645        This tests that the password reset view returns a password reset page
646        """
647        # Get password reset page
648        response = self.client.get(reverse('wagtailadmin_password_reset'))
649
650        # Check that the user received a password reset page
651        self.assertEqual(response.status_code, 200)
652        self.assertTemplateUsed(response, 'wagtailadmin/account/password_reset/form.html')
653
654    def test_password_reset_view_post(self):
655        """
656        This posts an email address to the password reset view and
657        checks that a password reset email was sent
658        """
659        # Post email address to password reset view
660        post_data = {
661            'email': 'test@email.com',
662        }
663        response = self.client.post(reverse('wagtailadmin_password_reset'), post_data)
664
665        # Check that the user was redirected to the done page
666        self.assertRedirects(response, reverse('wagtailadmin_password_reset_done'))
667
668        # Check that a password reset email was sent to the user
669        self.assertEqual(len(mail.outbox), 1)
670        self.assertEqual(mail.outbox[0].to, ['test@email.com'])
671        self.assertEqual(mail.outbox[0].subject, "Password reset")
672
673    def test_password_reset_view_post_unknown_email(self):
674        """
675        This posts an unknown email address to the password reset view and
676        checks that the password reset form raises a validation error
677        """
678        post_data = {
679            'email': 'unknown@email.com',
680        }
681        response = self.client.post(reverse('wagtailadmin_password_reset'), post_data)
682
683        # Check that the user was redirected to the done page
684        self.assertRedirects(response,
685                             reverse('wagtailadmin_password_reset_done'))
686
687        # Check that an email was not sent
688        self.assertEqual(len(mail.outbox), 0)
689
690    def test_password_reset_view_post_invalid_email(self):
691        """
692        This posts an incalid email address to the password reset view and
693        checks that the password reset form raises a validation error
694        """
695        post_data = {
696            'email': 'Hello world!',
697        }
698        response = self.client.post(reverse('wagtailadmin_password_reset'), post_data)
699
700        # Check that the user wasn't redirected
701        self.assertEqual(response.status_code, 200)
702
703        # Check that a validation error was raised
704        self.assertTrue('email' in response.context['form'].errors.keys())
705        self.assertTrue("Enter a valid email address." in response.context['form'].errors['email'])
706
707        # Check that an email was not sent
708        self.assertEqual(len(mail.outbox), 0)
709
710    def setup_password_reset_confirm_tests(self):
711        from django.utils.encoding import force_bytes, force_str
712        from django.utils.http import urlsafe_base64_encode
713
714        # Get user
715        self.user = get_user_model().objects.get(email='test@email.com')
716
717        # Generate a password reset token
718        self.password_reset_token = PasswordResetTokenGenerator().make_token(self.user)
719
720        # Generate a password reset uid
721        self.password_reset_uid = force_str(urlsafe_base64_encode(force_bytes(self.user.pk)))
722
723        # Create url_args
724        token = auth_views.PasswordResetConfirmView.reset_url_token
725
726        self.url_kwargs = dict(uidb64=self.password_reset_uid, token=token)
727
728        # Add token to session object
729        s = self.client.session
730        s.update({
731            auth_views.INTERNAL_RESET_SESSION_TOKEN: self.password_reset_token,
732        })
733        s.save()
734
735    def test_password_reset_confirm_view_invalid_link(self):
736        """
737        This tests that the password reset view shows an error message if the link is invalid
738        """
739        self.setup_password_reset_confirm_tests()
740
741        # Create invalid url_args
742        self.url_kwargs = dict(uidb64=self.password_reset_uid, token="invalid-token")
743
744        # Get password reset confirm page
745        response = self.client.get(reverse('wagtailadmin_password_reset_confirm', kwargs=self.url_kwargs))
746
747        # Check that the user received a password confirm done page
748        self.assertEqual(response.status_code, 200)
749        self.assertTemplateUsed(response, 'wagtailadmin/account/password_reset/confirm.html')
750        self.assertFalse(response.context['validlink'])
751        self.assertContains(response, 'The password reset link was invalid, possibly because it has already been used.')
752        self.assertContains(response, 'Request a new password reset')
753
754    def test_password_reset_confirm_view(self):
755        """
756        This tests that the password reset confirm view returns a password reset confirm page
757        """
758        self.setup_password_reset_confirm_tests()
759
760        # Get password reset confirm page
761        response = self.client.get(reverse('wagtailadmin_password_reset_confirm', kwargs=self.url_kwargs))
762
763        # Check that the user received a password confirm done page
764        self.assertEqual(response.status_code, 200)
765        self.assertTemplateUsed(response, 'wagtailadmin/account/password_reset/confirm.html')
766
767    def test_password_reset_confirm_view_post(self):
768        """
769        This posts a new password to the password reset confirm view and checks
770        that the users password was changed
771        """
772        self.setup_password_reset_confirm_tests()
773
774        # Post new password to change password page
775        post_data = {
776            'new_password1': 'newpassword',
777            'new_password2': 'newpassword',
778        }
779        response = self.client.post(reverse('wagtailadmin_password_reset_confirm', kwargs=self.url_kwargs), post_data)
780
781        # Check that the user was redirected to the complete page
782        self.assertRedirects(response, reverse('wagtailadmin_password_reset_complete'))
783
784        # Check that the password was changed
785        self.assertTrue(get_user_model().objects.get(email='test@email.com').check_password('newpassword'))
786
787    def test_password_reset_confirm_view_post_password_mismatch(self):
788        """
789        This posts a two passwords that don't match to the password reset
790        confirm view and checks that a validation error was raised
791        """
792        self.setup_password_reset_confirm_tests()
793
794        # Post new password to change password page
795        post_data = {
796            'new_password1': 'newpassword',
797            'new_password2': 'badpassword',
798        }
799        response = self.client.post(reverse('wagtailadmin_password_reset_confirm', kwargs=self.url_kwargs), post_data)
800
801        # Check that the user wasn't redirected
802        self.assertEqual(response.status_code, 200)
803
804        # Check that a validation error was raised
805        self.assertTrue('new_password2' in response.context['form'].errors.keys())
806        self.assertTrue("The two password fields didn’t match." in response.context['form'].errors['new_password2'])
807
808        # Check that the password was not changed
809        self.assertTrue(get_user_model().objects.get(email='test@email.com').check_password('password'))
810
811    def test_password_reset_done_view(self):
812        """
813        This tests that the password reset done view returns a password reset done page
814        """
815        # Get password reset done page
816        response = self.client.get(reverse('wagtailadmin_password_reset_done'))
817
818        # Check that the user received a password reset done page
819        self.assertEqual(response.status_code, 200)
820        self.assertTemplateUsed(response, 'wagtailadmin/account/password_reset/done.html')
821
822    def test_password_reset_complete_view(self):
823        """
824        This tests that the password reset complete view returns a password reset complete page
825        """
826        # Get password reset complete page
827        response = self.client.get(reverse('wagtailadmin_password_reset_complete'))
828
829        # Check that the user received a password reset complete page
830        self.assertEqual(response.status_code, 200)
831        self.assertTemplateUsed(response, 'wagtailadmin/account/password_reset/complete.html')
832