Coverage for ivatar/ivataraccount/test_views.py: 100%
640 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-12 23:12 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-12 23:12 +0000
1# -*- coding: utf-8 -*-
2"""
3Test our views in ivatar.ivataraccount.views and ivatar.views
4"""
6import contextlib
8# pylint: disable=too-many-lines
9from urllib.parse import urlsplit
10from io import BytesIO
11from contextlib import suppress
12import io
13import os
14import gzip
15import xml.etree.ElementTree
16import base64
17import django
18from django.test import TestCase
19from django.test import Client
20from django.test import override_settings
21from django.urls import reverse
22from django.core import mail
23from django.core.cache import caches
24from django.contrib.auth.models import User
25from django.contrib.auth import authenticate
26import hashlib
28from libravatar import libravatar_url
30from PIL import Image
32os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
33django.setup()
35# pylint: disable=wrong-import-position
36from ivatar import settings
37from ivatar.ivataraccount.forms import MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT
38from ivatar.ivataraccount.models import Photo, ConfirmedOpenId, ConfirmedEmail
39from ivatar.utils import random_string
41# pylint: enable=wrong-import-position
43TEST_IMAGE_FILE = os.path.join(settings.STATIC_ROOT, "img", "deadbeef.png")
46@override_settings()
47class Tester(TestCase): # pylint: disable=too-many-public-methods
48 """
49 Main test class
50 """
52 client = Client()
53 user = None
54 username = random_string()
55 password = random_string()
56 email = "%s@%s.org" % (username, random_string())
57 # Dunno why random tld doesn't work, but I'm too lazy now to investigate
58 openid = "http://%s.%s.%s/" % (username, random_string(), "org")
59 first_name = random_string()
60 last_name = random_string()
62 def login(self):
63 """
64 Login as user
65 """
66 self.client.login(username=self.username, password=self.password)
68 def setUp(self):
69 """
70 Prepare for tests.
71 - Create user
72 """
73 self.user = User.objects.create_user(
74 username=self.username,
75 password=self.password,
76 first_name=self.first_name,
77 last_name=self.last_name,
78 )
79 # Disable caching
80 settings.CACHES["default"] = {
81 "BACKEND": "django.core.cache.backends.dummy.DummyCache",
82 }
83 caches._settings = None
84 with suppress(AttributeError):
85 # clear the existing cache connection
86 delattr(caches._connections, "default")
88 def test_new_user(self):
89 """
90 Create a new user
91 """
92 response = self.client.get(reverse("new_account"))
93 self.assertEqual(response.status_code, 200, "no 200 ok?")
94 # Empty database / eliminate existing users
95 User.objects.all().delete()
96 url = reverse("new_account")
97 response = self.client.post(
98 url,
99 {
100 "username": self.username,
101 "password1": self.password,
102 "password2": self.password,
103 },
104 follow=True,
105 )
106 self.assertEqual(response.status_code, 200, "unable to create user?")
107 self.assertEqual(response.context[0]["user"].username, self.username)
109 def test_new_user_twice(self):
110 """
111 Try to create a user that already exists
112 """
113 response = self.client.get(reverse("new_account"))
114 self.assertEqual(response.status_code, 200, "no 200 ok?")
115 # Due to setUp(), we already have this user!
116 url = reverse("new_account")
117 response = self.client.post(
118 url,
119 {
120 "username": self.username,
121 "password1": self.password,
122 "password2": self.password,
123 },
124 follow=True,
125 )
126 self.assertEqual(response.status_code, 200, "unable to create user?")
127 self.assertEqual(response.context[0]["user"].username, "")
128 self.assertContains(
129 response,
130 "A user with that username already exists.",
131 1,
132 200,
133 "can we create a user a second time???",
134 )
136 def test_set_password(self):
137 """
138 Change the user password
139 """
140 self.login()
141 response = self.client.get(reverse("password_set"))
142 self.assertEqual(response.status_code, 200, "no 200 ok?")
143 self.password = random_string()
144 response = self.client.post(
145 reverse("password_set"),
146 {
147 "new_password1": self.password,
148 "new_password2": self.password,
149 },
150 follow=True,
151 )
153 self.assertEqual(response.status_code, 200, "cannot change password?")
154 self.assertEqual(
155 str(list(response.context[0]["messages"])[0]),
156 "password changed successfully - please login again",
157 "password change not successful?",
158 )
160 self.assertIsNotNone(
161 authenticate(
162 username=self.username,
163 password=self.password,
164 ),
165 "cannot authenticate with new password!?",
166 )
168 self.login()
169 response = self.client.get(reverse("profile"))
170 self.assertEqual(response.context[0]["user"].is_anonymous, False)
172 def test_add_email(self):
173 """
174 Add e-mail address
175 """
176 self.login()
177 response = self.client.get(reverse("add_email"))
178 self.assertEqual(response.status_code, 200, "no 200 ok?")
179 # Avoid sending out mails
180 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
181 response = self.client.post(
182 reverse("add_email"),
183 {
184 "email": self.email,
185 },
186 follow=True,
187 )
188 self.assertEqual(response.status_code, 200, "cannot add email?")
189 self.assertEqual(
190 len(response.context[0]["messages"]),
191 1,
192 "there must not be more or less than ONE (1) message",
193 )
194 self.assertEqual(
195 str(list(response.context[0]["messages"])[0]),
196 "Address added successfully",
197 "unable to add mail address?",
198 )
200 def test_confirm_email(self):
201 """
202 Confirm unconfirmed email
203 """
204 self.login()
205 # Avoid sending out mails
206 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
207 response = self.client.post(
208 reverse("add_email"),
209 {
210 "email": self.email,
211 },
212 follow=True,
213 )
214 unconfirmed = self.user.unconfirmedemail_set.first()
215 verification_key = unconfirmed.verification_key
216 url = reverse("confirm_email", args=[verification_key])
217 response = self.client.get(url)
218 self.assertEqual(response.status_code, 200, "unable to confirm mail address?")
220 self.assertEqual(
221 self.user.unconfirmedemail_set.count(),
222 0,
223 "there must not be any unconfirmed address, after confirming it",
224 )
225 self.assertEqual(
226 self.user.confirmedemail_set.count(),
227 1,
228 "there must not be more or less than ONE (1) confirmed address!",
229 )
231 def test_confirm_email_w_invalid_auth_key(self): # pylint: disable=invalid-name
232 """
233 Test confirmation with invalid auth key
234 """
235 self.login()
236 # Avoid sending out mails
237 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
238 response = self.client.post(
239 reverse("add_email"),
240 {
241 "email": self.email,
242 },
243 follow=True,
244 )
245 url = reverse("confirm_email", args=["x"])
246 response = self.client.get(url, follow=True)
247 self.assertEqual(
248 response.status_code,
249 200,
250 "Not able to request confirmation - without verification key?",
251 )
252 self.assertEqual(
253 str(list(response.context[0]["messages"])[-1]),
254 "Verification key incorrect",
255 "Confirm w/o verification key does not produce error message?",
256 )
258 def test_confirm_email_w_non_existing_auth_key(
259 self,
260 ): # pylint: disable=invalid-name
261 """
262 Test confirmation with non existing auth key
263 """
264 self.login()
265 # Avoid sending out mails
266 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
267 response = self.client.post(
268 reverse("add_email"),
269 {
270 "email": self.email,
271 },
272 follow=True,
273 )
274 url = reverse("confirm_email", args=["x" * 64])
275 response = self.client.get(url, follow=True)
276 self.assertEqual(
277 response.status_code,
278 200,
279 "Not able to request confirmation - without verification key?",
280 )
281 self.assertEqual(
282 str(list(response.context[0]["messages"])[-1]),
283 "Verification key does not exist",
284 "Confirm w/o non existing key does not produce error message?",
285 )
287 def test_remove_confirmed_email(self):
288 """
289 Remove confirmed email
290 """
291 self.login()
292 # Avoid sending out mails
293 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
294 response = self.client.post(
295 reverse("add_email"),
296 {
297 "email": self.email,
298 },
299 ) # Create test address
300 unconfirmed = self.user.unconfirmedemail_set.first()
301 verification_key = unconfirmed.verification_key
302 url = reverse("confirm_email", args=[verification_key])
303 self.client.get(url) # Confirm
304 url = reverse(
305 "remove_confirmed_email", args=[self.user.confirmedemail_set.first().id]
306 )
307 response = self.client.post(url, follow=True)
308 self.assertEqual(
309 response.status_code, 200, "unable to remove confirmed address?"
310 )
311 self.assertEqual(
312 str(list(response.context[0]["messages"])[-1]),
313 "Address removed",
314 "Removing confirmed mail does not work?",
315 )
317 def test_remove_not_existing_confirmed_email(self): # pylint: disable=invalid-name
318 """
319 Try removing confirmed mail that doesn't exist
320 """
321 self.login()
322 url = reverse("remove_confirmed_email", args=[1234])
323 response = self.client.post(url, follow=True)
324 self.assertEqual(
325 response.status_code, 200, "removing email does not redirect to profile?"
326 )
327 self.assertEqual(
328 str(list(response.context[0]["messages"])[0]),
329 "Address does not exist",
330 "Removing not existing (confirmed) address, should produce an\
331 error message!",
332 )
334 def test_remove_unconfirmed_email(self):
335 """
336 Remove unconfirmed email
337 """
338 self.login()
339 # Avoid sending out mails
340 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
341 response = self.client.post(
342 reverse("add_email"),
343 {
344 "email": self.email,
345 },
346 ) # Create test address
347 url = reverse(
348 "remove_unconfirmed_email", args=[self.user.unconfirmedemail_set.first().id]
349 )
350 response = self.client.post(url, follow=True)
351 self.assertEqual(
352 response.status_code, 200, "unable to remove unconfirmed address?"
353 )
354 # Take care, since we do not fetch any page now, the message we need
355 # to check is the _second_ (aka [1], since first is [0])
356 self.assertEqual(
357 str(list(response.context[0]["messages"])[1]),
358 "Address removed",
359 "Removing unconfirmed mail does not work?",
360 )
362 def test_gravatar_photo_import(self):
363 """
364 import photo from Gravatar (with known mail address)
365 """
366 self.login()
367 # Avoid sending out mails
368 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
369 response = self.client.post(
370 reverse("add_email"),
371 {
372 "email": "oliver@linux-kernel.at", # Wow, static :-[
373 },
374 ) # Create test address
375 unconfirmed = self.user.unconfirmedemail_set.first()
376 verification_key = unconfirmed.verification_key
377 url = reverse("confirm_email", args=[verification_key])
378 self.client.get(url) # Confirm
380 url = reverse("import_photo", args=[self.user.confirmedemail_set.first().id])
381 response = self.client.post(
382 url,
383 {
384 "photo_Gravatar": 1,
385 },
386 follow=True,
387 )
388 self.assertEqual(
389 response.status_code, 200, "unable to import photo from Gravatar?"
390 )
391 self.assertEqual(
392 str(list(response.context[0]["messages"])[-1]),
393 "Gravatar image successfully imported",
394 "Importing gravatar photo did not work?",
395 )
396 self.assertIsInstance(
397 self.user.photo_set.first(), Photo, "why is there no Photo (instance)?"
398 )
400 def test_raw_image(self):
401 """
402 test raw image view (as seen in profile <img src=
403 """
405 # Ensure we have a photo
406 self.test_gravatar_photo_import()
407 response = self.client.get(
408 reverse("raw_image", args=[self.user.photo_set.first().id])
409 )
410 self.assertEqual(response.status_code, 200, "cannot fetch photo?")
411 # Probably not the best way to access the content type
412 self.assertEqual(response["Content-Type"], "image/jpg", "Content type wrong!?")
414 self.assertEqual(
415 bytes(response.content),
416 bytes(self.user.photo_set.first().data),
417 "raw_image should return the same content as if we\
418 read it directly from the DB",
419 )
421 def test_delete_photo(self):
422 """
423 test deleting the photo
424 """
426 # Ensure we have a photo
427 self.test_gravatar_photo_import()
429 url = reverse("delete_photo", args=[self.user.photo_set.first().id])
430 response = self.client.get(url, follow=True)
431 self.assertEqual(response.status_code, 200, "deleting photo does not work?")
432 self.assertEqual(
433 str(list(response.context[0]["messages"])[-1]),
434 "Photo deleted successfully",
435 "Photo deletion did not work?",
436 )
438 def test_delete_non_existing_photo(self):
439 """
440 test deleting the photo
441 """
443 # Ensure we have a photo
444 self.test_gravatar_photo_import()
446 url = reverse("delete_photo", args=[1234])
447 response = self.client.get(url, follow=True)
448 self.assertEqual(response.status_code, 200, "post to delete does not work?")
449 self.assertEqual(
450 str(list(response.context[0]["messages"])[-1]),
451 "No such image or no permission to delete it",
452 "Deleting photo that does not exist, should return error message",
453 )
455 def test_too_many_unconfirmed_email(self):
456 """
457 Request too many unconfirmed email addresses, make sure we
458 cannot add more
459 """
460 self.login()
461 # Avoid sending out mails
462 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
464 max_num_unconfirmed = getattr(
465 settings, "MAX_NUM_UNCONFIRMED_EMAILS", MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT
466 )
468 for i in range(max_num_unconfirmed + 1):
469 response = self.client.post( # noqa: F841
470 reverse("add_email"),
471 {
472 "email": "%i.%s" % (i, self.email),
473 },
474 follow=True,
475 ) # Create test addresses + 1 too much
476 return self._check_form_validity(
477 response, "Too many unconfirmed mail addresses!", "__all__"
478 )
480 def test_add_mail_address_twice(self):
481 """
482 Request the same mail address two times, should not lead to
483 having the same address twice
484 """
485 self.login()
486 # Avoid sending out mails
487 settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
489 for _ in range(2):
490 response = self.client.post( # noqa: F841
491 reverse("add_email"),
492 {
493 "email": self.email,
494 },
495 follow=True,
496 )
497 return self._check_form_validity(
498 response, "Address already added, currently unconfirmed", "email"
499 )
501 def test_add_already_confirmed_email_self(self): # pylint: disable=invalid-name
502 """
503 Request adding mail address that is already confirmed (by someone)
504 """
505 # Create test mail and confirm it, reuse test code
506 # Should set EMAIL_BACKEND, so no need to do it here
507 self.test_confirm_email()
509 response = self.client.post( # noqa: F841
510 reverse("add_email"),
511 {
512 "email": self.email,
513 },
514 follow=True,
515 )
517 return self._check_form_validity(
518 response, "Address already confirmed (by you)", "email"
519 )
521 def test_add_already_confirmed_email_other(self): # pylint: disable=invalid-name
522 """
523 Request adding mail address that is already confirmed (by someone)
524 """
525 # Create test mail and confirm it, reuse test code
526 # Should set EMAIL_BACKEND, so no need to do it here
527 self.test_confirm_email()
529 # Create another user and assign the mail address to that one
530 # in order to test the correct error message
531 otheruser = User.objects.create(username="otheruser")
532 confirmedemail = ConfirmedEmail.objects.last()
533 confirmedemail.user = otheruser
534 confirmedemail.save()
536 response = self.client.post( # noqa: F841
537 reverse("add_email"),
538 {
539 "email": self.email,
540 },
541 follow=True,
542 )
544 return self._check_form_validity(
545 response, "Address already confirmed (by someone else)", "email"
546 )
548 def test_remove_unconfirmed_non_existing_email(
549 self,
550 ): # pylint: disable=invalid-name
551 """
552 Remove unconfirmed email that doesn't exist
553 """
554 self.login()
555 url = reverse("remove_unconfirmed_email", args=[1234])
556 response = self.client.post(url, follow=True)
557 self.assertEqual(
558 response.status_code, 200, "unable to remove non existing address?"
559 )
560 self.assertEqual(
561 str(list(response.context[0]["messages"])[0]),
562 "Address does not exist",
563 "Removing address that does not\
564 exist, should return error message!",
565 )
567 def test_upload_image(
568 self, test_only_one=True
569 ): # pylint: disable=inconsistent-return-statements
570 """
571 Test uploading image
572 """
573 self.login()
574 url = reverse("upload_photo")
575 # rb => Read binary
576 with open(TEST_IMAGE_FILE, "rb") as photo:
577 response = self.client.post(
578 url,
579 {
580 "photo": photo,
581 "not_porn": True,
582 "can_distribute": True,
583 },
584 follow=True,
585 )
586 if not test_only_one:
587 return response
588 self.assertEqual(
589 self.user.photo_set.count(), 1, "there must be exactly one photo now!"
590 )
591 self.assertEqual(
592 str(list(response.context[0]["messages"])[-1]),
593 "Successfully uploaded",
594 "A valid image should return a success message!",
595 )
596 self.assertEqual(
597 self.user.photo_set.first().format,
598 "png",
599 "Format must be png, since we uploaded a png!",
600 )
602 def test_upload_too_many_images(self):
603 """
604 Test uploading more images than we are allowed
605 """
606 for _ in range(settings.MAX_NUM_PHOTOS + 1):
607 response = self.test_upload_image(test_only_one=False)
608 self.assertEqual(
609 self.user.photo_set.count(),
610 settings.MAX_NUM_PHOTOS,
611 "there may not be more photos than allowed!",
612 )
613 # Take care we need to check the last message
614 self.assertEqual(
615 str(list(response.context[0]["messages"])[-1]),
616 "Maximum number of photos (%i) reached" % settings.MAX_NUM_PHOTOS,
617 "Adding more than allowed images, should return error message!",
618 )
620 def test_upload_too_big_image(self):
621 """
622 Test uploading image that is too big
623 """
624 self.login()
625 url = reverse("upload_photo")
626 # rb => Read binary
627 response = self.client.post(
628 url,
629 {
630 "photo": io.StringIO("x" * (settings.MAX_PHOTO_SIZE + 1)),
631 "not_porn": True,
632 "can_distribute": True,
633 },
634 follow=True,
635 )
636 self.assertEqual(
637 str(list(response.context[0]["messages"])[0]),
638 "Image too big",
639 "Uploading too big image, should return error message!",
640 )
642 def test_upload_invalid_image(self):
643 """
644 Test invalid image data
645 """
646 self.login()
647 url = reverse("upload_photo")
648 # rb => Read binary
649 response = self.client.post(
650 url,
651 {
652 "photo": io.StringIO("x"),
653 "not_porn": True,
654 "can_distribute": True,
655 },
656 follow=True,
657 )
658 self.assertEqual(
659 str(list(response.context[0]["messages"])[0]),
660 "Invalid Format",
661 "Invalid img data should return error message!",
662 )
664 def test_upload_invalid_image_format(self): # pylint: disable=invalid-name
665 """
666 Test if invalid format is correctly detected
667 """
668 self.login()
669 url = reverse("upload_photo")
670 # rb => Read binary
671 with open(os.path.join(settings.STATIC_ROOT, "img", "mm.svg"), "rb") as photo:
672 response = self.client.post(
673 url,
674 {
675 "photo": photo,
676 "not_porn": True,
677 "can_distribute": True,
678 },
679 follow=True,
680 )
681 self.assertEqual(
682 str(list(response.context[0]["messages"])[0]),
683 "Invalid Format",
684 "Invalid img data should return error message!",
685 )
687 def test_upload_gif_image(self):
688 """
689 Test if gif is correctly detected and can be viewed
690 """
691 self._extracted_from_test_upload_webp_image_5(
692 "broken.gif",
693 "GIF upload failed?!",
694 "gif",
695 "Format must be gif, since we uploaded a GIF!",
696 )
698 def test_upload_jpg_image(self):
699 """
700 Test if jpg is correctly detected and can be viewed
701 """
702 self._extracted_from_test_upload_webp_image_5(
703 "broken.jpg",
704 "JPEG upload failed?!",
705 "jpg",
706 "Format must be jpeg, since we uploaded a jpeg!",
707 )
709 def test_upload_webp_image(self):
710 """
711 Test if webp is correctly detected and can be viewed
712 """
713 self._extracted_from_test_upload_webp_image_5(
714 "broken.webp",
715 "WEBP upload failed?!",
716 "webp",
717 "Format must be webp, since we uploaded a webp!",
718 )
720 def _extracted_from_test_upload_webp_image_5(
721 self, filename, message1, format, message2
722 ):
723 """
724 Helper function for common checks for gif, jpg, webp
725 """
726 self.login()
727 url = reverse("upload_photo")
728 with open(os.path.join(settings.STATIC_ROOT, "img", filename), "rb") as photo:
729 response = self.client.post(
730 url,
731 {"photo": photo, "not_porn": True, "can_distribute": True},
732 follow=True,
733 )
734 self.assertEqual(
735 str(list(response.context[0]["messages"])[0]),
736 "Successfully uploaded",
737 message1,
738 )
739 self.assertEqual(self.user.photo_set.first().format, format, message2)
740 self.test_confirm_email()
741 self.user.confirmedemail_set.first().photo = self.user.photo_set.first()
742 urlobj = urlsplit(
743 libravatar_url(email=self.user.confirmedemail_set.first().email)
744 )
745 url = f"{urlobj.path}?{urlobj.query}"
746 response = self.client.get(url, follow=True)
747 self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
749 def test_upload_unsupported_tif_image(self): # pylint: disable=invalid-name
750 """
751 Test if unsupported format is correctly detected
752 """
753 self.login()
754 url = reverse("upload_photo")
755 # rb => Read binary
756 with open(
757 os.path.join(settings.STATIC_ROOT, "img", "broken.tif"), "rb"
758 ) as photo:
759 response = self.client.post(
760 url,
761 {
762 "photo": photo,
763 "not_porn": True,
764 "can_distribute": True,
765 },
766 follow=True,
767 )
768 self.assertEqual(
769 str(list(response.context[0]["messages"])[0]),
770 "Invalid Format",
771 "Invalid img data should return error message!",
772 )
774 def test_automatic_photo_assign_to_confirmed_mail(
775 self,
776 ): # pylint: disable=invalid-name
777 """
778 Test if automatic assignment of photo works
779 """
780 self.test_upload_image()
781 self.test_confirm_email()
782 confirmed = self.user.confirmedemail_set.first()
783 self.assertEqual(confirmed.photo, self.user.photo_set.first())
785 def test_assign_photo_to_email(self):
786 """
787 Test assigning photo to mail address
788 """
789 self.test_confirm_email()
790 self.test_upload_image()
791 self.assertIsNone(self.user.confirmedemail_set.first().photo)
792 url = reverse(
793 "assign_photo_email", args=[self.user.confirmedemail_set.first().id]
794 )
795 # The get is for the view - test context data
796 self.client.get(
797 url,
798 {
799 "photo_id": self.user.photo_set.first().id,
800 },
801 )
802 # The post is for the actual assigning
803 response = self.client.post(
804 url,
805 {
806 "photo_id": self.user.photo_set.first().id,
807 },
808 follow=True,
809 )
810 self.assertEqual(response.status_code, 200, "cannot assign photo?")
811 self.assertEqual(
812 self.user.confirmedemail_set.first().photo, self.user.photo_set.first()
813 )
815 def test_no_photo_to_email(self):
816 """
817 Test assigning photo to mail address
818 """
819 self.test_confirm_email()
820 url = reverse(
821 "assign_photo_email", args=[self.user.confirmedemail_set.first().id]
822 )
823 response = self.client.post(
824 url,
825 {
826 "photoNone": True,
827 },
828 follow=True,
829 )
830 self.assertEqual(response.status_code, 200, "cannot un-assign photo?")
831 self.assertEqual(self.user.confirmedemail_set.first().photo, None)
833 def test_assign_photo_to_email_wo_photo_for_testing_template(
834 self,
835 ): # pylint: disable=invalid-name
836 """
837 Test assign photo template
838 """
839 self.test_confirm_email()
840 url = reverse(
841 "assign_photo_email", args=[self.user.confirmedemail_set.first().id]
842 )
843 # The get is for the view - test context data
844 response = self.client.get(url)
845 self.assertEqual(response.status_code, 200, "cannot fetch page?")
847 def test_assign_invalid_photo_id_to_email(self): # pylint: disable=invalid-name
848 """
849 Test if assigning an invalid photo id returns the correct error message
850 """
851 self.test_confirm_email()
852 self.test_upload_image()
853 self.assertIsNone(self.user.confirmedemail_set.first().photo)
854 url = reverse(
855 "assign_photo_email", args=[self.user.confirmedemail_set.first().id]
856 )
857 response = self.client.post(
858 url,
859 {
860 "photo_id": 1234,
861 },
862 follow=True,
863 )
864 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
865 self.assertEqual(
866 str(list(response.context[0]["messages"])[-1]),
867 "Photo does not exist",
868 "Assign non existing photo, does not return error message?",
869 )
871 def test_post_to_assign_photo_without_photo_id(
872 self,
873 ): # pylint: disable=invalid-name
874 """
875 Test if assigning photo without id returns the correct error message
876 """
877 self.test_confirm_email()
878 self.test_upload_image()
879 self.assertIsNone(self.user.confirmedemail_set.first().photo)
880 url = reverse(
881 "assign_photo_email", args=[self.user.confirmedemail_set.first().id]
882 )
883 response = self.client.post(url, {}, follow=True)
884 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
885 self.assertEqual(
886 str(list(response.context[0]["messages"])[-1]),
887 "Invalid request [photo_id] missing",
888 "Assign non existing photo, does not return error message?",
889 )
891 def test_assign_photo_to_non_existing_mail(self): # pylint: disable=invalid-name
892 """
893 Test if assigning photo to mail address that doesn't exist returns
894 the correct error message
895 """
896 self.test_upload_image()
897 url = reverse("assign_photo_email", args=[1234])
898 response = self.client.post(
899 url,
900 {
901 "photo_id": self.user.photo_set.first().id,
902 },
903 follow=True,
904 )
905 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
906 self.assertEqual(
907 str(list(response.context[0]["messages"])[-1]),
908 "Invalid request",
909 "Assign non existing photo, does not return error message?",
910 )
912 def test_import_photo_with_non_existing_email(self): # pylint: disable=invalid-name
913 """
914 Test if import with non existing mail address returns
915 the correct error message
916 """
917 self.login()
918 url = reverse("import_photo", args=[1234])
919 response = self.client.post(url, {}, follow=True)
920 self.assertEqual(response.status_code, 200, "cannot post import photo request?")
921 self.assertEqual(
922 str(list(response.context[0]["messages"])[0]),
923 "Address does not exist",
924 "Import photo with non existing mail id,\
925 does not return error message?",
926 )
928 def test_import_nothing(self):
929 """
930 Test if importing nothing causes the correct
931 error message to be returned
932 """
933 self.test_confirm_email()
934 url = reverse("import_photo", args=[self.user.confirmedemail_set.first().id])
935 response = self.client.post(url, {}, follow=True)
936 self.assertEqual(response.status_code, 200, "cannot post import photo request?")
937 self.assertEqual(
938 str(list(response.context[0]["messages"])[-1]),
939 "Nothing importable",
940 "Importing with email that does not exist in Gravatar,\
941 should return an error message!",
942 )
944 def _manual_confirm(self):
945 """
946 Helper method to confirm manually, because testing is really hard
947 """
948 # Manual confirm, since testing is _really_ hard!
949 unconfirmed = self.user.unconfirmedopenid_set.first()
950 confirmed = ConfirmedOpenId()
951 confirmed.user = unconfirmed.user
952 confirmed.ip_address = "127.0.0.1"
953 confirmed.openid = unconfirmed.openid
954 confirmed.save()
955 unconfirmed.delete()
957 def test_add_openid(self, confirm=True):
958 """
959 Test if adding an OpenID works
960 """
962 self.login()
963 # Get page
964 response = self.client.get(reverse("add_openid"))
965 self.assertEqual(
966 response.status_code, 200, "Fetching page to add OpenID fails?"
967 )
969 response = self.client.post(
970 reverse("add_openid"),
971 {
972 "openid": self.openid,
973 },
974 )
975 self.assertEqual(response.status_code, 302, "OpenID must redirect")
977 if confirm:
978 self._manual_confirm()
980 def test_add_openid_twice(self):
981 """
982 Test if adding OpenID a second time works - it shouldn't
983 """
984 self.login()
985 # Get page
986 response = self.client.get(reverse("add_openid"))
987 self.assertEqual(
988 response.status_code, 200, "Fetching page to add OpenID fails?"
989 )
991 response = self.client.post(
992 reverse("add_openid"),
993 {
994 "openid": self.openid,
995 },
996 )
997 self.assertEqual(response.status_code, 302, "OpenID must redirect")
998 response = self.client.post(
999 reverse("add_openid"),
1000 {
1001 "openid": self.openid,
1002 },
1003 follow=True,
1004 )
1005 self.assertEqual(
1006 self.user.unconfirmedopenid_set.count(),
1007 1,
1008 "There must only be one unconfirmed ID!",
1009 )
1011 self._check_form_validity(
1012 response, "OpenID already added, but not confirmed yet!", "openid"
1013 )
1014 # Manual confirm, since testing is _really_ hard!
1015 unconfirmed = self.user.unconfirmedopenid_set.first()
1016 confirmed = ConfirmedOpenId()
1017 confirmed.user = unconfirmed.user
1018 confirmed.ip_address = "127.0.0.1"
1019 confirmed.openid = unconfirmed.openid
1020 confirmed.save()
1021 unconfirmed.delete()
1023 # Try adding it again - although already confirmed
1024 response = self.client.post(
1025 reverse("add_openid"),
1026 {
1027 "openid": self.openid,
1028 },
1029 follow=True,
1030 )
1032 return self._check_form_validity(
1033 response, "OpenID already added and confirmed!", "openid"
1034 )
1036 def _check_form_validity(self, response, message, field):
1037 """
1038 Helper method to check form, used in several test functions,
1039 deduplicating code
1040 """
1042 self.assertTrue(
1043 hasattr(response, "context"), "Response does not have a context"
1044 )
1045 result = response.context.get("form")
1046 self.assertIsNotNone(result, "No form found in response context")
1047 self.assertFalse(result.is_valid(), "Form should not be valid")
1048 self.assertIn(message, result.errors.get(field, []))
1049 return result
1051 def test_assign_photo_to_openid(self):
1052 """
1053 Test assignment of photo to openid
1054 """
1055 self.test_add_openid()
1056 self.test_upload_image()
1057 self.assertIsNone(self.user.confirmedopenid_set.first().photo)
1058 url = reverse(
1059 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id]
1060 )
1061 # The get is for the view - test context data
1062 self.client.get(
1063 url,
1064 {
1065 "photo_id": self.user.photo_set.first().id,
1066 },
1067 )
1068 # The post is for the actual assigning
1069 response = self.client.post(
1070 url,
1071 {
1072 "photo_id": self.user.photo_set.first().id,
1073 },
1074 follow=True,
1075 )
1076 self.assertEqual(response.status_code, 200, "cannot assign photo?")
1077 self.assertEqual(
1078 self.user.confirmedopenid_set.first().photo, self.user.photo_set.first()
1079 )
1081 def test_assign_photo_to_openid_wo_photo_for_testing_template(
1082 self,
1083 ): # pylint: disable=invalid-name
1084 """
1085 Test openid/photo assignment template
1086 """
1087 self.test_add_openid()
1088 url = reverse(
1089 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id]
1090 )
1091 response = self.client.get(url)
1092 self.assertEqual(response.status_code, 200, "cannot fetch page?")
1094 def test_assign_invalid_photo_id_to_openid(self): # pylint: disable=invalid-name
1095 """
1096 Test assigning invalid photo to openid returns
1097 the correct error message
1098 """
1099 self.test_add_openid()
1100 self.assertIsNone(self.user.confirmedopenid_set.first().photo)
1101 url = reverse(
1102 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id]
1103 )
1104 response = self.client.post(
1105 url,
1106 {
1107 "photo_id": 1234,
1108 },
1109 follow=True,
1110 )
1111 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
1112 self.assertEqual(
1113 str(list(response.context[0]["messages"])[-1]),
1114 "Photo does not exist",
1115 "Assign non existing photo, does not return error message?",
1116 )
1118 def test_post_to_assign_photo_openid_without_photo_id(
1119 self,
1120 ): # pylint: disable=invalid-name
1121 """
1122 Test POST assign photo to openid without photo id
1123 returns the correct error message
1124 """
1125 self.test_add_openid()
1126 self.test_upload_image()
1127 self.assertIsNone(self.user.confirmedopenid_set.first().photo)
1128 url = reverse(
1129 "assign_photo_openid", args=[self.user.confirmedopenid_set.first().id]
1130 )
1131 response = self.client.post(url, {}, follow=True)
1132 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
1133 self.assertEqual(
1134 str(list(response.context[0]["messages"])[-1]),
1135 "Invalid request [photo_id] missing",
1136 "Assign non existing photo, does not return error message?",
1137 )
1139 def test_assign_photo_to_openid_non_existing_openid(
1140 self,
1141 ): # pylint: disable=invalid-name
1142 """
1143 Test assigning photo to openid that doesn't exist
1144 returns the correct error message.
1145 """
1146 self.test_upload_image()
1147 url = reverse("assign_photo_openid", args=[1234])
1148 response = self.client.post(
1149 url,
1150 {
1151 "photo_id": self.user.photo_set.first().id,
1152 },
1153 follow=True,
1154 )
1155 self.assertEqual(response.status_code, 200, "cannot post assign photo request?")
1156 self.assertEqual(
1157 str(list(response.context[0]["messages"])[-1]),
1158 "Invalid request",
1159 "Assign non existing photo, does not return error message?",
1160 )
1162 def test_remove_confirmed_openid(self): # pylint: disable=invalid-name
1163 """
1164 Remove confirmed openid
1165 """
1166 self.test_add_openid()
1167 url = reverse(
1168 "remove_confirmed_openid", args=[self.user.confirmedopenid_set.first().id]
1169 )
1170 response = self.client.post(url, follow=True)
1171 self.assertEqual(
1172 response.status_code, 200, "unable to remove confirmed openid?"
1173 )
1174 self.assertEqual(
1175 str(list(response.context[0]["messages"])[-1]),
1176 "ID removed",
1177 "Removing confirmed openid does not work?",
1178 )
1180 def test_remove_not_existing_confirmed_openid(self): # pylint: disable=invalid-name
1181 """
1182 Try removing confirmed openid that doesn't exist
1183 """
1184 self.login()
1185 url = reverse("remove_confirmed_openid", args=[1234])
1186 response = self.client.post(url, follow=True)
1187 self.assertEqual(
1188 response.status_code, 200, "removing id does not redirect to profile?"
1189 )
1190 self.assertEqual(
1191 str(list(response.context[0]["messages"])[0]),
1192 "ID does not exist",
1193 "Removing not existing (confirmed) address, should produce an\
1194 error message!",
1195 )
1197 def test_remove_unconfirmed_openid(self):
1198 """
1199 Remove unconfirmed openid
1200 """
1201 self.test_add_openid(confirm=False)
1202 url = reverse(
1203 "remove_unconfirmed_openid",
1204 args=[self.user.unconfirmedopenid_set.first().id],
1205 )
1206 response = self.client.post(url, follow=True)
1207 self.assertEqual(
1208 response.status_code, 200, "unable to remove unconfirmed address?"
1209 )
1210 self.assertEqual(
1211 str(list(response.context[0]["messages"])[-1]),
1212 "ID removed",
1213 "Removing unconfirmed mail does not work?",
1214 )
1216 def test_remove_unconfirmed_non_existing_openid(
1217 self,
1218 ): # pylint: disable=invalid-name
1219 """
1220 Remove unconfirmed openid that doesn't exist
1221 """
1222 self.login()
1223 url = reverse("remove_unconfirmed_openid", args=[1234])
1224 response = self.client.post(url, follow=True)
1225 self.assertEqual(
1226 response.status_code, 200, "unable to remove unconfirmed address?"
1227 )
1228 self.assertEqual(
1229 str(list(response.context[0]["messages"])[0]),
1230 "ID does not exist",
1231 "Removing an non existing openid should return an error message",
1232 )
1234 def test_openid_redirect_view(self):
1235 """
1236 Test redirect view
1237 """
1238 self.test_add_openid(confirm=False)
1239 url = reverse(
1240 "openid_redirection", args=[self.user.unconfirmedopenid_set.first().id]
1241 )
1242 response = self.client.get(url, follow=True)
1243 self.assertEqual(
1244 response.status_code, 200, "unable to remove unconfirmed address?"
1245 )
1246 # self.assertContains(
1247 # response,
1248 # 'OpenID discovery failed: ', 1, 200,
1249 # 'This request must return an error in test mode'
1250 # )
1252 def test_set_photo_on_openid(self):
1253 """
1254 Test the set_photo function on our ConfirmedOpenId model.
1255 """
1256 self.test_add_openid()
1257 self.test_upload_image()
1258 self.assertIsNone(self.user.confirmedopenid_set.first().photo)
1259 self.user.confirmedopenid_set.first().set_photo(self.user.photo_set.first())
1260 self.assertEqual(
1261 self.user.confirmedopenid_set.first().photo,
1262 self.user.photo_set.first(),
1263 "set_photo did not work!?",
1264 )
1266 def test_avatar_url_mail(self, do_upload_and_confirm=True, size=(80, 80)):
1267 """
1268 Test fetching avatar via mail
1269 """
1270 if do_upload_and_confirm:
1271 self.test_upload_image()
1272 self.test_confirm_email()
1273 urlobj = urlsplit(
1274 libravatar_url(
1275 email=self.user.confirmedemail_set.first().email,
1276 size=size[0],
1277 )
1278 )
1279 url = f"{urlobj.path}?{urlobj.query}"
1280 response = self.client.get(url, follow=True)
1281 self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
1282 photodata = Image.open(BytesIO(response.content))
1283 self.assertEqual(photodata.size, size, "Why is this not the correct size?")
1285 def test_avatar_url_openid(self):
1286 """
1287 Test fetching avatar via openid
1288 """
1289 self.test_assign_photo_to_openid()
1290 urlobj = urlsplit(
1291 libravatar_url(
1292 openid=self.user.confirmedopenid_set.first().openid,
1293 size=80,
1294 )
1295 )
1296 url = f"{urlobj.path}?{urlobj.query}"
1297 response = self.client.get(url, follow=True)
1298 self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
1299 photodata = Image.open(BytesIO(response.content))
1300 self.assertEqual(photodata.size, (80, 80), "Why is this not the correct size?")
1302 def test_avatar_url_non_existing_mail_digest(self): # pylint: disable=invalid-name
1303 """
1304 Test fetching avatar via non existing mail digest
1305 """
1306 self.test_upload_image()
1307 self.test_confirm_email()
1308 urlobj = urlsplit(
1309 libravatar_url(
1310 email=self.user.confirmedemail_set.first().email,
1311 size=80,
1312 )
1313 )
1314 # Simply delete it, then it's digest is 'correct', but
1315 # the hash is no longer there
1316 addr = self.user.confirmedemail_set.first().email
1317 hashlib.md5(addr.strip().lower().encode("utf-8")).hexdigest()
1319 self.user.confirmedemail_set.first().delete()
1320 url = f"{urlobj.path}?{urlobj.query}"
1321 self.client.get(url, follow=True)
1322 # TODO: All these tests still fails under some circumstances - it needs further investigation
1323 # self.assertEqual(
1324 # response.redirect_chain[0][0],
1325 # f"/gravatarproxy/{digest}?s=80",
1326 # "Doesn't redirect to Gravatar?",
1327 # )
1328 # self.assertEqual(
1329 # response.redirect_chain[0][1], 302, "Doesn't redirect with 302?"
1330 # )
1331 # self.assertEqual(
1332 # response.redirect_chain[1][0],
1333 # f"/avatar/{digest}?s=80&forcedefault=y",
1334 # "Doesn't redirect with default forced on?",
1335 # )
1336 # self.assertEqual(
1337 # response.redirect_chain[1][1], 302, "Doesn't redirect with 302?"
1338 # )
1339 # self.assertEqual(
1340 # response.redirect_chain[2][0],
1341 # "/static/img/nobody/80.png",
1342 # "Doesn't redirect to static?",
1343 # )
1344 # self.assertRedirects(
1345 # response=response,
1346 # expected_url="/static/img/nobody/80.png",
1347 # msg_prefix="Why does this not redirect to Gravatar?",
1348 # )
1349 # Eventually one should check if the data is the same
1351 def test_avatar_url_non_existing_mail_digest_gravatarproxy_disabled(
1352 self,
1353 ): # pylint: disable=invalid-name
1354 """
1355 Test fetching avatar via non existing mail digest
1356 """
1357 self.test_upload_image()
1358 self.test_confirm_email()
1359 urlobj = urlsplit(
1360 libravatar_url(
1361 email=self.user.confirmedemail_set.first().email,
1362 size=80,
1363 )
1364 )
1365 # Simply delete it, then it digest is 'correct', but
1366 # the hash is no longer there
1367 self.user.confirmedemail_set.first().delete()
1368 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n"
1369 response = self.client.get(url, follow=True)
1370 self.assertEqual(
1371 response.redirect_chain[0][0],
1372 "/static/img/nobody/80.png",
1373 "Doesn't redirect to static?",
1374 )
1376 # self.assertRedirects(
1377 # response=response,
1378 # expected_url="/static/img/nobody/80.png",
1379 # msg_prefix="Why does this not redirect to the default img?",
1380 # )
1381 # Eventually one should check if the data is the same
1383 def test_avatar_url_non_existing_mail_digest_w_default_mm(
1384 self,
1385 ): # pylint: disable=invalid-name
1386 """
1387 Test fetching avatar via non existing mail digest and default 'mm'
1388 """
1389 urlobj = urlsplit(
1390 libravatar_url(
1391 email="asdf@company.local",
1392 size=80,
1393 default="mm",
1394 )
1395 )
1396 url = f"{urlobj.path}?{urlobj.query}"
1397 self.client.get(url, follow=False)
1399 def test_avatar_url_non_existing_mail_digest_w_default_mm_gravatarproxy_disabled(
1400 self,
1401 ): # pylint: disable=invalid-name
1402 """
1403 Test fetching avatar via non existing mail digest and default 'mm'
1404 """
1405 urlobj = urlsplit(
1406 libravatar_url(
1407 email="asdf@company.local",
1408 size=80,
1409 default="mm",
1410 )
1411 )
1412 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n"
1413 response = self.client.get(url, follow=True)
1414 self.assertEqual(
1415 response.redirect_chain[0][0],
1416 "/static/img/mm/80.png",
1417 "Doesn't redirect to static?",
1418 )
1420 # self.assertRedirects(
1421 # response=response,
1422 # expected_url="/static/img/mm/80.png",
1423 # msg_prefix="Why does this not redirect to the default img?",
1424 # )
1425 # Eventually one should check if the data is the same
1427 def test_avatar_url_non_existing_mail_digest_wo_default(
1428 self,
1429 ): # pylint: disable=invalid-name
1430 """
1431 Test fetching avatar via non existing mail digest and default 'mm'
1432 """
1433 urlobj = urlsplit(
1434 libravatar_url(
1435 email="asdf@company.local",
1436 size=80,
1437 )
1438 )
1439 digest = hashlib.md5("asdf@company.local".lower().encode("utf-8")).hexdigest()
1440 url = f"{urlobj.path}?{urlobj.query}"
1441 response = self.client.get(url, follow=True)
1442 self.assertEqual(
1443 response.redirect_chain[0][0],
1444 f"/gravatarproxy/{digest}?s=80",
1445 "Doesn't redirect to Gravatar?",
1446 )
1447 self.assertEqual(
1448 response.redirect_chain[0][1], 302, "Doesn't redirect with 302?"
1449 )
1450 self.assertEqual(
1451 response.redirect_chain[1][0],
1452 f"/avatar/{digest}?s=80&forcedefault=y",
1453 "Doesn't redirect with default forced on?",
1454 )
1455 self.assertEqual(
1456 response.redirect_chain[1][1], 302, "Doesn't redirect with 302?"
1457 )
1458 self.assertEqual(
1459 response.redirect_chain[2][0],
1460 "/static/img/nobody/80.png",
1461 "Doesn't redirect to static?",
1462 )
1464 # self.assertRedirects(
1465 # response=response,
1466 # expected_url="/static/img/nobody/80.png",
1467 # msg_prefix="Why does this not redirect to the default img?",
1468 # )
1469 # Eventually one should check if the data is the same
1471 def test_avatar_url_non_existing_mail_digest_wo_default_gravatarproxy_disabled(
1472 self,
1473 ): # pylint: disable=invalid-name
1474 """
1475 Test fetching avatar via non existing mail digest and default 'mm'
1476 """
1477 urlobj = urlsplit(
1478 libravatar_url(
1479 email="asdf@company.local",
1480 size=80,
1481 )
1482 )
1483 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n"
1484 response = self.client.get(url, follow=True)
1485 self.assertEqual(
1486 response.redirect_chain[0][0],
1487 "/static/img/nobody/80.png",
1488 "Doesn't redirect to static?",
1489 )
1491 # self.assertRedirects(
1492 # response=response,
1493 # expected_url="/static/img/nobody/80.png",
1494 # msg_prefix="Why does this not redirect to the default img?",
1495 # )
1496 # Eventually one should check if the data is the same
1498 def test_avatar_url_default(self): # pylint: disable=invalid-name
1499 """
1500 Test fetching avatar for not existing mail with default specified
1501 """
1502 urlobj = urlsplit(
1503 libravatar_url(
1504 "xxx@xxx.xxx",
1505 size=80,
1506 default="/static/img/nobody.png",
1507 )
1508 )
1509 url = f"{urlobj.path}?{urlobj.query}"
1510 url += "&gravatarproxy=n"
1511 response = self.client.get(url, follow=False)
1512 self.assertEqual(response.status_code, 302, "Doesn't redirect with 302?")
1513 self.assertEqual(
1514 response["Location"],
1515 "/static/img/nobody.png",
1516 "Doesn't redirect to static img?",
1517 )
1519 def test_avatar_url_default_gravatarproxy_disabled(
1520 self,
1521 ): # pylint: disable=invalid-name
1522 """
1523 Test fetching avatar for not existing mail with default specified
1524 """
1525 urlobj = urlsplit(
1526 libravatar_url(
1527 "xxx@xxx.xxx",
1528 size=80,
1529 default="/static/img/nobody.png",
1530 )
1531 )
1532 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n"
1533 response = self.client.get(url, follow=True)
1534 self.assertEqual(
1535 response.redirect_chain[0][0],
1536 "/static/img/nobody.png",
1537 "Doesn't redirect to static?",
1538 )
1540 def test_avatar_url_default_external(self): # pylint: disable=invalid-name
1541 """
1542 Test fetching avatar for not existing mail with external default specified
1543 This shall *not* redirect to the external site (CWE-601)!
1544 """
1545 default = "http://host.tld/img.png"
1546 size = 80
1547 urlobj = urlsplit(
1548 libravatar_url(
1549 "xxx@xxx.xxx",
1550 size=size,
1551 default=default,
1552 )
1553 )
1554 url = f"{urlobj.path}?{urlobj.query}"
1555 response = self.client.get(url, follow=False)
1556 self.assertRedirects(
1557 response=response,
1558 expected_url=f"/gravatarproxy/fb7a6d7f11365642d44ba66dc57df56f?s={size}",
1559 fetch_redirect_response=False,
1560 msg_prefix="Why does this not redirect to the default img?",
1561 )
1563 def test_avatar_url_default_external_trusted(self): # pylint: disable=invalid-name
1564 """
1565 Test fetching avatar for not existing mail with external default specified
1566 """
1567 default = "https://ui-avatars.com/api/blah"
1568 urlobj = urlsplit(
1569 libravatar_url(
1570 "xxx@xxx.xxx",
1571 size=80,
1572 default=default,
1573 )
1574 )
1575 url = f"{urlobj.path}?{urlobj.query}"
1576 response = self.client.get(url, follow=False)
1577 self.assertRedirects(
1578 response=response,
1579 expected_url="/gravatarproxy/fb7a6d7f11365642d44ba66dc57df56f?s=80&default=https://ui-avatars.com/api/blah",
1580 fetch_redirect_response=False,
1581 msg_prefix="Why does this not redirect to the default img?",
1582 )
1584 def test_avatar_url_default_external_gravatarproxy_disabled(
1585 self,
1586 ): # pylint: disable=invalid-name
1587 """
1588 Test fetching avatar for not existing mail with external default specified
1589 This shall *not* redirect to the external site (CWE-601)!
1590 """
1591 default = "http://host.tld/img.png"
1592 urlobj = urlsplit(
1593 libravatar_url(
1594 "xxx@xxx.xxx",
1595 size=80,
1596 default=default,
1597 )
1598 )
1599 url = f"{urlobj.path}?{urlobj.query}&gravatarproxy=n"
1600 response = self.client.get(url, follow=False)
1601 self.assertRedirects(
1602 response=response,
1603 expected_url="/static/img/nobody/80.png",
1604 fetch_redirect_response=False,
1605 msg_prefix="Why does this not redirect to the default img?",
1606 )
1608 def test_crop_photo(self):
1609 """
1610 Test cropping photo
1611 """
1612 self.test_upload_image()
1613 self.test_confirm_email()
1614 url = reverse("crop_photo", args=[self.user.photo_set.first().pk])
1615 response = self.client.post(
1616 url,
1617 {
1618 "x": 10,
1619 "y": 10,
1620 "w": 20,
1621 "h": 20,
1622 },
1623 follow=True,
1624 )
1625 self.assertEqual(response.status_code, 200, "unable to crop?")
1626 self.test_avatar_url_mail(do_upload_and_confirm=False, size=(20, 20))
1627 img = Image.open(BytesIO(self.user.photo_set.first().data))
1628 self.assertEqual(
1629 img.size, (20, 20), "cropped to 20x20, but resulting image isn't 20x20!?"
1630 )
1632 def test_password_change_view(self):
1633 """
1634 Test password change view
1635 """
1636 self.login()
1637 url = reverse("password_change")
1638 response = self.client.get(url)
1639 self.assertEqual(
1640 response.status_code, 200, "unable to view password change view?"
1641 )
1643 def test_password_change_view_post_wrong_old_pw(self):
1644 """
1645 Test password change view post
1646 """
1647 self.login()
1648 response = self.client.post(
1649 reverse("password_change"),
1650 {
1651 "old_password": "xxx",
1652 "new_password1": self.password,
1653 "new_password2": self.password,
1654 },
1655 follow=True,
1656 )
1658 self.assertContains(
1659 response,
1660 "Your old password was entered incorrectly. Please enter it again.",
1661 1,
1662 200,
1663 "Old password as entered incorrectly, site should raise an error",
1664 )
1666 def test_password_change_view_post_wrong_new_password1(self):
1667 """
1668 Test password change view post
1669 """
1670 self.login()
1671 response = self.client.post(
1672 reverse("password_change"),
1673 {
1674 "old_password": self.password,
1675 "new_password1": f"{self.password}.",
1676 "new_password2": self.password,
1677 },
1678 follow=True,
1679 )
1680 self.assertContains(
1681 response,
1682 "The two password fields did",
1683 1,
1684 200,
1685 "Old password was entered incorrectly, site should raise an error",
1686 )
1688 def test_password_change_view_post_wrong_new_password2(self):
1689 """
1690 Test password change view post
1691 """
1692 self.login()
1693 response = self.client.post(
1694 reverse("password_change"),
1695 {
1696 "old_password": self.password,
1697 "new_password1": self.password,
1698 "new_password2": f"{self.password}.",
1699 },
1700 follow=True,
1701 )
1703 self.assertContains(
1704 response,
1705 "The two password fields did",
1706 1,
1707 200,
1708 "Old password as entered incorrectly, site should raise an error",
1709 )
1711 def test_password_change_view_post_common_password(self):
1712 """
1713 Test password change view post
1714 """
1715 self.login()
1716 response = self.client.post(
1717 reverse("password_change"),
1718 {
1719 "old_password": self.password,
1720 "new_password1": "Hallo",
1721 "new_password2": "Hallo",
1722 },
1723 follow=True,
1724 )
1726 self.assertContains(
1727 response,
1728 "This password is too common.",
1729 1,
1730 200,
1731 "Common password, site should raise an error",
1732 )
1734 def test_profile_must_list_first_and_lastname(self):
1735 """
1736 Test if profile view correctly lists first -/last name
1737 """
1738 self.login()
1739 response = self.client.get(reverse("profile"))
1740 self.assertContains(
1741 response,
1742 self.first_name,
1743 1,
1744 200,
1745 "First name not listed in profile page",
1746 )
1747 self.assertContains(
1748 response,
1749 self.last_name,
1750 1,
1751 200,
1752 "Last name not listed in profile page",
1753 )
1754 self.assertContains(
1755 response,
1756 f"{self.first_name} {self.last_name}",
1757 1,
1758 200,
1759 "First and last name not correctly listed in profile page",
1760 )
1762 def test_password_reset_page(self):
1763 """
1764 Just test if the password reset page come up correctly
1765 """
1766 response = self.client.get(reverse("password_reset"))
1767 self.assertEqual(response.status_code, 200, "no 200 ok?")
1769 def test_password_reset_wo_mail(self):
1770 """
1771 Test if the password reset doesn't error out
1772 if the mail address doesn't exist
1773 """
1774 # Avoid sending out mails
1775 settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
1777 # Empty database / eliminate existing users
1778 User.objects.all().delete()
1779 url = reverse("password_reset")
1780 response = self.client.post(
1781 url,
1782 {
1783 "email": "asdf@asdf.local",
1784 },
1785 follow=True,
1786 )
1787 self.assertEqual(response.status_code, 200, "password reset page not working?")
1788 self.assertEqual(
1789 len(mail.outbox),
1790 0,
1791 "user does not exist, there should be no mail in the outbox!",
1792 )
1794 def test_password_reset_w_mail(self):
1795 """
1796 Test if the password reset works correctly with email in
1797 User object
1798 """
1799 # Avoid sending out mails
1800 settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
1802 url = reverse("password_reset")
1803 # Our test user doesn't have an email address by default - but we need one set
1804 self.user.email = "asdf@asdf.local"
1805 self.user.save()
1806 response = self.client.post(
1807 url,
1808 {
1809 "email": self.user.email,
1810 },
1811 follow=True,
1812 )
1813 self.assertEqual(response.status_code, 200, "password reset page not working?")
1814 self.assertEqual(
1815 len(mail.outbox), 1, "User exists, there should be a mail in the outbox!"
1816 )
1817 self.assertEqual(
1818 mail.outbox[0].to[0],
1819 self.user.email,
1820 "Sending mails to the wrong \
1821 mail address?",
1822 )
1824 def test_password_reset_w_confirmed_mail(self):
1825 """
1826 Test if the password reset works correctly with confirmed
1827 mail
1828 """
1829 # Avoid sending out mails
1830 settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
1832 url = reverse("password_reset")
1833 # Our test user doesn't have a confirmed mail identity - add one
1834 self.user.confirmedemail_set.create(email="asdf@asdf.local")
1835 self.user.save()
1837 response = self.client.post(
1838 url,
1839 {
1840 "email": self.user.confirmedemail_set.first().email,
1841 },
1842 follow=True,
1843 )
1844 # Since the object is touched in another process, we need to refresh it
1845 self.user.refresh_from_db()
1846 self.assertEqual(response.status_code, 200, "password reset page not working?")
1847 self.assertEqual(
1848 self.user.email,
1849 self.user.confirmedemail_set.first().email,
1850 "The password reset view, should have corrected this!",
1851 )
1852 self.assertEqual(
1853 len(mail.outbox), 1, "user exists, there should be a mail in the outbox!"
1854 )
1855 self.assertEqual(
1856 mail.outbox[0].to[0],
1857 self.user.email,
1858 "why are we sending mails to the wrong mail address?",
1859 )
1861 def test_export(self):
1862 """
1863 Test if export works
1864 """
1866 # Create well known strings to check if export
1867 # works as expected
1868 self.user.confirmedemail_set.create(email="asdf@asdf.local")
1869 self.user.confirmedopenid_set.create(openid="http://asdf.asdf.local")
1870 self.user.save()
1872 # Ensure we have a photo uploaded
1873 self.test_upload_image()
1875 self.login()
1876 self.client.get(reverse("export"))
1877 response = self.client.post(
1878 reverse("export"),
1879 {},
1880 follow=False,
1881 )
1882 self.assertIsInstance(response.content, bytes)
1883 fh = gzip.open(BytesIO(response.content), "rb")
1884 content = fh.read()
1885 fh.close()
1886 root = xml.etree.ElementTree.fromstring(content)
1887 self.assertEqual(root.tag, "{%s}user" % settings.SCHEMAROOT)
1888 self.assertEqual(
1889 root.findall("{%s}account" % settings.SCHEMAROOT)[0].items()[0][1],
1890 self.user.username,
1891 )
1892 self.assertEqual(
1893 root.findall("{%s}account" % settings.SCHEMAROOT)[0].items()[1][1],
1894 self.user.password,
1895 )
1897 self.assertEqual(
1898 root.findall("{%s}emails" % settings.SCHEMAROOT)[0][0].text,
1899 self.user.confirmedemail_set.first().email,
1900 )
1901 self.assertEqual(
1902 root.findall("{%s}openids" % settings.SCHEMAROOT)[0][0].text,
1903 self.user.confirmedopenid_set.first().openid,
1904 )
1906 data = root.findall("{%s}photos" % settings.SCHEMAROOT)[0][0].text
1908 data = data.strip("'")
1909 data = data.strip("\\n")
1910 data = data.lstrip("b'")
1911 bindata = base64.decodebytes(bytes(data, "utf-8"))
1912 image = Image.open(BytesIO(bindata))
1913 self.assertTrue(hasattr(image, "png"))
1915 def test_upload_export(self):
1916 """
1917 Test if uploading export works
1918 """
1920 # Ensure we have data in place
1921 self.test_export()
1923 self.login()
1924 self.client.get(reverse("export"))
1925 response = self.client.post(
1926 reverse("export"),
1927 {},
1928 follow=False,
1929 )
1930 self.assertIsInstance(response.content, bytes)
1932 fh_gzip = gzip.open(BytesIO(response.content), "rb")
1933 fh = BytesIO(response.content)
1935 response = self._uploading_export_check(
1936 fh_gzip, "Unable to parse file: Not a gzipped file"
1937 )
1938 response = self._uploading_export_check(fh, "Choose items to be imported")
1939 self.assertContains(
1940 response,
1941 "asdf@asdf.local",
1942 2,
1943 200,
1944 "Upload didn't work?",
1945 )
1947 def _uploading_export_check(self, fh, message):
1948 """
1949 Helper function to upload an export
1950 """
1951 result = self.client.post(
1952 reverse("upload_export"),
1953 data={"not_porn": "on", "can_distribute": "on", "export_file": fh},
1954 follow=True,
1955 )
1956 fh.close()
1957 self.assertEqual(result.status_code, 200, "Upload worked")
1958 self.assertContains(result, message, 1, 200, "Upload didn't work?")
1960 return result
1962 def test_preferences_page(self):
1963 """
1964 Test if preferences page works
1965 """
1967 self.login()
1968 self.client.get(reverse("user_preference"))
1970 def test_delete_user(self):
1971 """
1972 Test if deleting user profile works
1973 """
1975 self.login()
1976 self.client.get(reverse("delete"))
1977 response = self.client.post(
1978 reverse("delete"),
1979 data={"password": self.password},
1980 follow=True,
1981 )
1982 self.assertEqual(response.status_code, 200, "Deletion worked")
1983 self.assertEqual(User.objects.count(), 0, "No user there any more")
1985 def test_confirm_already_confirmed(self):
1986 """
1987 Try to confirm a mail address that has been confirmed (by another user)
1988 """
1990 # Add mail address (stays unconfirmed)
1991 self.test_add_email()
1993 # Create a second user that will conflict
1994 user2 = User.objects.create_user(
1995 username=f"{self.username}1",
1996 password=self.password,
1997 first_name=self.first_name,
1998 last_name=self.last_name,
1999 )
2000 ConfirmedEmail.objects.create(
2001 email=self.email,
2002 user=user2,
2003 )
2005 # Just to be sure
2006 self.assertEqual(
2007 self.user.unconfirmedemail_set.first().email,
2008 user2.confirmedemail_set.first().email,
2009 "Mail not the same?",
2010 )
2012 # This needs to be caught
2013 with contextlib.suppress(AssertionError):
2014 self.test_confirm_email()
2015 # Request a random page, so we can access the messages
2016 response = self.client.get(reverse("profile"))
2017 self.assertEqual(
2018 str(list(response.context[0]["messages"])[0]),
2019 "This mail address has been taken already and cannot be confirmed",
2020 "This should return an error message!",
2021 )