test_validators.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import os
  4. from django.contrib.auth import validators
  5. from django.contrib.auth.models import User
  6. from django.contrib.auth.password_validation import (
  7. CommonPasswordValidator, MinimumLengthValidator, NumericPasswordValidator,
  8. UserAttributeSimilarityValidator, get_default_password_validators,
  9. get_password_validators, password_changed,
  10. password_validators_help_text_html, password_validators_help_texts,
  11. validate_password,
  12. )
  13. from django.core.exceptions import ValidationError
  14. from django.test import TestCase, override_settings
  15. from django.utils._os import upath
  16. @override_settings(AUTH_PASSWORD_VALIDATORS=[
  17. {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
  18. {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {
  19. 'min_length': 12,
  20. }},
  21. ])
  22. class PasswordValidationTest(TestCase):
  23. def test_get_default_password_validators(self):
  24. validators = get_default_password_validators()
  25. self.assertEqual(len(validators), 2)
  26. self.assertEqual(validators[0].__class__.__name__, 'CommonPasswordValidator')
  27. self.assertEqual(validators[1].__class__.__name__, 'MinimumLengthValidator')
  28. self.assertEqual(validators[1].min_length, 12)
  29. def test_get_password_validators_custom(self):
  30. validator_config = [{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'}]
  31. validators = get_password_validators(validator_config)
  32. self.assertEqual(len(validators), 1)
  33. self.assertEqual(validators[0].__class__.__name__, 'CommonPasswordValidator')
  34. self.assertEqual(get_password_validators([]), [])
  35. def test_validate_password(self):
  36. self.assertIsNone(validate_password('sufficiently-long'))
  37. msg_too_short = 'This password is too short. It must contain at least 12 characters.'
  38. with self.assertRaises(ValidationError) as cm:
  39. validate_password('django4242')
  40. self.assertEqual(cm.exception.messages, [msg_too_short])
  41. self.assertEqual(cm.exception.error_list[0].code, 'password_too_short')
  42. with self.assertRaises(ValidationError) as cm:
  43. validate_password('password')
  44. self.assertEqual(cm.exception.messages, ['This password is too common.', msg_too_short])
  45. self.assertEqual(cm.exception.error_list[0].code, 'password_too_common')
  46. self.assertIsNone(validate_password('password', password_validators=[]))
  47. def test_password_changed(self):
  48. self.assertIsNone(password_changed('password'))
  49. def test_password_validators_help_texts(self):
  50. help_texts = password_validators_help_texts()
  51. self.assertEqual(len(help_texts), 2)
  52. self.assertIn('12 characters', help_texts[1])
  53. self.assertEqual(password_validators_help_texts(password_validators=[]), [])
  54. def test_password_validators_help_text_html(self):
  55. help_text = password_validators_help_text_html()
  56. self.assertEqual(help_text.count('<li>'), 2)
  57. self.assertIn('12 characters', help_text)
  58. @override_settings(AUTH_PASSWORD_VALIDATORS=[])
  59. def test_empty_password_validator_help_text_html(self):
  60. self.assertEqual(password_validators_help_text_html(), '')
  61. class MinimumLengthValidatorTest(TestCase):
  62. def test_validate(self):
  63. expected_error = "This password is too short. It must contain at least %d characters."
  64. self.assertIsNone(MinimumLengthValidator().validate('12345678'))
  65. self.assertIsNone(MinimumLengthValidator(min_length=3).validate('123'))
  66. with self.assertRaises(ValidationError) as cm:
  67. MinimumLengthValidator().validate('1234567')
  68. self.assertEqual(cm.exception.messages, [expected_error % 8])
  69. self.assertEqual(cm.exception.error_list[0].code, 'password_too_short')
  70. with self.assertRaises(ValidationError) as cm:
  71. MinimumLengthValidator(min_length=3).validate('12')
  72. self.assertEqual(cm.exception.messages, [expected_error % 3])
  73. def test_help_text(self):
  74. self.assertEqual(
  75. MinimumLengthValidator().get_help_text(),
  76. "Your password must contain at least 8 characters."
  77. )
  78. class UserAttributeSimilarityValidatorTest(TestCase):
  79. def test_validate(self):
  80. user = User.objects.create_user(
  81. username='testclient', password='password', email='testclient@example.com',
  82. first_name='Test', last_name='Client',
  83. )
  84. expected_error = "The password is too similar to the %s."
  85. self.assertIsNone(UserAttributeSimilarityValidator().validate('testclient'))
  86. with self.assertRaises(ValidationError) as cm:
  87. UserAttributeSimilarityValidator().validate('testclient', user=user),
  88. self.assertEqual(cm.exception.messages, [expected_error % "username"])
  89. self.assertEqual(cm.exception.error_list[0].code, 'password_too_similar')
  90. with self.assertRaises(ValidationError) as cm:
  91. UserAttributeSimilarityValidator().validate('example.com', user=user),
  92. self.assertEqual(cm.exception.messages, [expected_error % "email address"])
  93. with self.assertRaises(ValidationError) as cm:
  94. UserAttributeSimilarityValidator(
  95. user_attributes=['first_name'],
  96. max_similarity=0.3,
  97. ).validate('testclient', user=user)
  98. self.assertEqual(cm.exception.messages, [expected_error % "first name"])
  99. self.assertIsNone(
  100. UserAttributeSimilarityValidator(user_attributes=['first_name']).validate('testclient', user=user)
  101. )
  102. def test_help_text(self):
  103. self.assertEqual(
  104. UserAttributeSimilarityValidator().get_help_text(),
  105. "Your password can't be too similar to your other personal information."
  106. )
  107. class CommonPasswordValidatorTest(TestCase):
  108. def test_validate(self):
  109. expected_error = "This password is too common."
  110. self.assertIsNone(CommonPasswordValidator().validate('a-safe-password'))
  111. with self.assertRaises(ValidationError) as cm:
  112. CommonPasswordValidator().validate('godzilla')
  113. self.assertEqual(cm.exception.messages, [expected_error])
  114. def test_validate_custom_list(self):
  115. path = os.path.join(os.path.dirname(os.path.realpath(upath(__file__))), 'common-passwords-custom.txt')
  116. validator = CommonPasswordValidator(password_list_path=path)
  117. expected_error = "This password is too common."
  118. self.assertIsNone(validator.validate('a-safe-password'))
  119. with self.assertRaises(ValidationError) as cm:
  120. validator.validate('from-my-custom-list')
  121. self.assertEqual(cm.exception.messages, [expected_error])
  122. self.assertEqual(cm.exception.error_list[0].code, 'password_too_common')
  123. def test_help_text(self):
  124. self.assertEqual(
  125. CommonPasswordValidator().get_help_text(),
  126. "Your password can't be a commonly used password."
  127. )
  128. class NumericPasswordValidatorTest(TestCase):
  129. def test_validate(self):
  130. expected_error = "This password is entirely numeric."
  131. self.assertIsNone(NumericPasswordValidator().validate('a-safe-password'))
  132. with self.assertRaises(ValidationError) as cm:
  133. NumericPasswordValidator().validate('42424242')
  134. self.assertEqual(cm.exception.messages, [expected_error])
  135. self.assertEqual(cm.exception.error_list[0].code, 'password_entirely_numeric')
  136. def test_help_text(self):
  137. self.assertEqual(
  138. NumericPasswordValidator().get_help_text(),
  139. "Your password can't be entirely numeric."
  140. )
  141. class UsernameValidatorsTests(TestCase):
  142. def test_unicode_validator(self):
  143. valid_usernames = ['joe', 'René', 'ᴮᴵᴳᴮᴵᴿᴰ', 'أحمد']
  144. invalid_usernames = [
  145. "o'connell", "عبد ال",
  146. "zerowidth\u200Bspace", "nonbreaking\u00A0space",
  147. "en\u2013dash",
  148. ]
  149. v = validators.UnicodeUsernameValidator()
  150. for valid in valid_usernames:
  151. v(valid)
  152. for invalid in invalid_usernames:
  153. with self.assertRaises(ValidationError):
  154. v(invalid)
  155. def test_ascii_validator(self):
  156. valid_usernames = ['glenn', 'GLEnN', 'jean-marc']
  157. invalid_usernames = ["o'connell", 'Éric', 'jean marc', "أحمد"]
  158. v = validators.ASCIIUsernameValidator()
  159. for valid in valid_usernames:
  160. v(valid)
  161. for invalid in invalid_usernames:
  162. with self.assertRaises(ValidationError):
  163. v(invalid)