test_checks.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. from django.contrib.auth.checks import (
  2. check_middleware,
  3. check_models_permissions,
  4. check_user_model,
  5. )
  6. from django.contrib.auth.middleware import (
  7. AuthenticationMiddleware,
  8. LoginRequiredMiddleware,
  9. )
  10. from django.contrib.auth.models import AbstractBaseUser
  11. from django.contrib.sessions.middleware import SessionMiddleware
  12. from django.core import checks
  13. from django.db import models
  14. from django.db.models import Q, UniqueConstraint
  15. from django.test import SimpleTestCase, override_settings, override_system_checks
  16. from django.test.utils import isolate_apps
  17. from .models import CustomUserNonUniqueUsername
  18. @isolate_apps("auth_tests", attr_name="apps")
  19. @override_system_checks([check_user_model])
  20. class UserModelChecksTests(SimpleTestCase):
  21. @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserNonListRequiredFields")
  22. def test_required_fields_is_list(self):
  23. """REQUIRED_FIELDS should be a list."""
  24. class CustomUserNonListRequiredFields(AbstractBaseUser):
  25. username = models.CharField(max_length=30, unique=True)
  26. date_of_birth = models.DateField()
  27. USERNAME_FIELD = "username"
  28. REQUIRED_FIELDS = "date_of_birth"
  29. errors = checks.run_checks(app_configs=self.apps.get_app_configs())
  30. self.assertEqual(
  31. errors,
  32. [
  33. checks.Error(
  34. "'REQUIRED_FIELDS' must be a list or tuple.",
  35. obj=CustomUserNonListRequiredFields,
  36. id="auth.E001",
  37. ),
  38. ],
  39. )
  40. @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserBadRequiredFields")
  41. def test_username_not_in_required_fields(self):
  42. """USERNAME_FIELD should not appear in REQUIRED_FIELDS."""
  43. class CustomUserBadRequiredFields(AbstractBaseUser):
  44. username = models.CharField(max_length=30, unique=True)
  45. date_of_birth = models.DateField()
  46. USERNAME_FIELD = "username"
  47. REQUIRED_FIELDS = ["username", "date_of_birth"]
  48. errors = checks.run_checks(self.apps.get_app_configs())
  49. self.assertEqual(
  50. errors,
  51. [
  52. checks.Error(
  53. "The field named as the 'USERNAME_FIELD' for a custom user model "
  54. "must not be included in 'REQUIRED_FIELDS'.",
  55. hint=(
  56. "The 'USERNAME_FIELD' is currently set to 'username', you "
  57. "should remove 'username' from the 'REQUIRED_FIELDS'."
  58. ),
  59. obj=CustomUserBadRequiredFields,
  60. id="auth.E002",
  61. ),
  62. ],
  63. )
  64. @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserNonUniqueUsername")
  65. def test_username_non_unique(self):
  66. """
  67. A non-unique USERNAME_FIELD raises an error only if the default
  68. authentication backend is used. Otherwise, a warning is raised.
  69. """
  70. errors = checks.run_checks()
  71. self.assertEqual(
  72. errors,
  73. [
  74. checks.Error(
  75. "'CustomUserNonUniqueUsername.username' must be "
  76. "unique because it is named as the 'USERNAME_FIELD'.",
  77. obj=CustomUserNonUniqueUsername,
  78. id="auth.E003",
  79. ),
  80. ],
  81. )
  82. with self.settings(AUTHENTICATION_BACKENDS=["my.custom.backend"]):
  83. errors = checks.run_checks()
  84. self.assertEqual(
  85. errors,
  86. [
  87. checks.Warning(
  88. "'CustomUserNonUniqueUsername.username' is named as "
  89. "the 'USERNAME_FIELD', but it is not unique.",
  90. hint=(
  91. "Ensure that your authentication backend(s) can handle "
  92. "non-unique usernames."
  93. ),
  94. obj=CustomUserNonUniqueUsername,
  95. id="auth.W004",
  96. ),
  97. ],
  98. )
  99. @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserPartiallyUnique")
  100. def test_username_partially_unique(self):
  101. class CustomUserPartiallyUnique(AbstractBaseUser):
  102. username = models.CharField(max_length=30)
  103. USERNAME_FIELD = "username"
  104. class Meta:
  105. constraints = [
  106. UniqueConstraint(
  107. fields=["username"],
  108. name="partial_username_unique",
  109. condition=Q(password__isnull=False),
  110. ),
  111. ]
  112. errors = checks.run_checks(app_configs=self.apps.get_app_configs())
  113. self.assertEqual(
  114. errors,
  115. [
  116. checks.Error(
  117. "'CustomUserPartiallyUnique.username' must be unique because "
  118. "it is named as the 'USERNAME_FIELD'.",
  119. obj=CustomUserPartiallyUnique,
  120. id="auth.E003",
  121. ),
  122. ],
  123. )
  124. with self.settings(AUTHENTICATION_BACKENDS=["my.custom.backend"]):
  125. errors = checks.run_checks(app_configs=self.apps.get_app_configs())
  126. self.assertEqual(
  127. errors,
  128. [
  129. checks.Warning(
  130. "'CustomUserPartiallyUnique.username' is named as the "
  131. "'USERNAME_FIELD', but it is not unique.",
  132. hint=(
  133. "Ensure that your authentication backend(s) can "
  134. "handle non-unique usernames."
  135. ),
  136. obj=CustomUserPartiallyUnique,
  137. id="auth.W004",
  138. ),
  139. ],
  140. )
  141. @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserUniqueConstraint")
  142. def test_username_unique_with_model_constraint(self):
  143. class CustomUserUniqueConstraint(AbstractBaseUser):
  144. username = models.CharField(max_length=30)
  145. USERNAME_FIELD = "username"
  146. class Meta:
  147. constraints = [
  148. UniqueConstraint(fields=["username"], name="username_unique"),
  149. ]
  150. self.assertEqual(checks.run_checks(app_configs=self.apps.get_app_configs()), [])
  151. with self.settings(AUTHENTICATION_BACKENDS=["my.custom.backend"]):
  152. errors = checks.run_checks(app_configs=self.apps.get_app_configs())
  153. self.assertEqual(errors, [])
  154. @override_settings(AUTH_USER_MODEL="auth_tests.BadUser")
  155. def test_is_anonymous_authenticated_methods(self):
  156. """
  157. <User Model>.is_anonymous/is_authenticated must not be methods.
  158. """
  159. class BadUser(AbstractBaseUser):
  160. username = models.CharField(max_length=30, unique=True)
  161. USERNAME_FIELD = "username"
  162. def is_anonymous(self):
  163. return True
  164. def is_authenticated(self):
  165. return True
  166. errors = checks.run_checks(app_configs=self.apps.get_app_configs())
  167. self.assertEqual(
  168. errors,
  169. [
  170. checks.Critical(
  171. "%s.is_anonymous must be an attribute or property rather than "
  172. "a method. Ignoring this is a security issue as anonymous "
  173. "users will be treated as authenticated!" % BadUser,
  174. obj=BadUser,
  175. id="auth.C009",
  176. ),
  177. checks.Critical(
  178. "%s.is_authenticated must be an attribute or property rather "
  179. "than a method. Ignoring this is a security issue as anonymous "
  180. "users will be treated as authenticated!" % BadUser,
  181. obj=BadUser,
  182. id="auth.C010",
  183. ),
  184. ],
  185. )
  186. @isolate_apps("auth_tests", attr_name="apps")
  187. @override_system_checks([check_models_permissions])
  188. class ModelsPermissionsChecksTests(SimpleTestCase):
  189. def test_clashing_default_permissions(self):
  190. class Checked(models.Model):
  191. class Meta:
  192. permissions = [("change_checked", "Can edit permission (duplicate)")]
  193. errors = checks.run_checks(self.apps.get_app_configs())
  194. self.assertEqual(
  195. errors,
  196. [
  197. checks.Error(
  198. "The permission codenamed 'change_checked' clashes with a builtin "
  199. "permission for model 'auth_tests.Checked'.",
  200. obj=Checked,
  201. id="auth.E005",
  202. ),
  203. ],
  204. )
  205. def test_non_clashing_custom_permissions(self):
  206. class Checked(models.Model):
  207. class Meta:
  208. permissions = [
  209. ("my_custom_permission", "Some permission"),
  210. ("other_one", "Some other permission"),
  211. ]
  212. errors = checks.run_checks(self.apps.get_app_configs())
  213. self.assertEqual(errors, [])
  214. def test_clashing_custom_permissions(self):
  215. class Checked(models.Model):
  216. class Meta:
  217. permissions = [
  218. ("my_custom_permission", "Some permission"),
  219. ("other_one", "Some other permission"),
  220. (
  221. "my_custom_permission",
  222. "Some permission with duplicate permission code",
  223. ),
  224. ]
  225. errors = checks.run_checks(self.apps.get_app_configs())
  226. self.assertEqual(
  227. errors,
  228. [
  229. checks.Error(
  230. "The permission codenamed 'my_custom_permission' is duplicated for "
  231. "model 'auth_tests.Checked'.",
  232. obj=Checked,
  233. id="auth.E006",
  234. ),
  235. ],
  236. )
  237. def test_verbose_name_max_length(self):
  238. class Checked(models.Model):
  239. class Meta:
  240. verbose_name = (
  241. "some ridiculously long verbose name that is out of control" * 5
  242. )
  243. errors = checks.run_checks(self.apps.get_app_configs())
  244. self.assertEqual(
  245. errors,
  246. [
  247. checks.Error(
  248. "The verbose_name of model 'auth_tests.Checked' must be at most "
  249. "244 characters for its builtin permission names to be at most 255 "
  250. "characters.",
  251. obj=Checked,
  252. id="auth.E007",
  253. ),
  254. ],
  255. )
  256. def test_model_name_max_length(self):
  257. model_name = "X" * 94
  258. model = type(model_name, (models.Model,), {"__module__": self.__module__})
  259. errors = checks.run_checks(self.apps.get_app_configs())
  260. self.assertEqual(
  261. errors,
  262. [
  263. checks.Error(
  264. "The name of model 'auth_tests.%s' must be at most 93 "
  265. "characters for its builtin permission codenames to be at "
  266. "most 100 characters." % model_name,
  267. obj=model,
  268. id="auth.E011",
  269. ),
  270. ],
  271. )
  272. def test_custom_permission_name_max_length(self):
  273. custom_permission_name = (
  274. "some ridiculously long verbose name that is out of control" * 5
  275. )
  276. class Checked(models.Model):
  277. class Meta:
  278. permissions = [
  279. ("my_custom_permission", custom_permission_name),
  280. ]
  281. errors = checks.run_checks(self.apps.get_app_configs())
  282. self.assertEqual(
  283. errors,
  284. [
  285. checks.Error(
  286. "The permission named '%s' of model 'auth_tests.Checked' is longer "
  287. "than 255 characters." % custom_permission_name,
  288. obj=Checked,
  289. id="auth.E008",
  290. ),
  291. ],
  292. )
  293. def test_custom_permission_codename_max_length(self):
  294. custom_permission_codename = "x" * 101
  295. class Checked(models.Model):
  296. class Meta:
  297. permissions = [
  298. (custom_permission_codename, "Custom permission"),
  299. ]
  300. errors = checks.run_checks(self.apps.get_app_configs())
  301. self.assertEqual(
  302. errors,
  303. [
  304. checks.Error(
  305. "The permission codenamed '%s' of model 'auth_tests.Checked' "
  306. "is longer than 100 characters." % custom_permission_codename,
  307. obj=Checked,
  308. id="auth.E012",
  309. ),
  310. ],
  311. )
  312. def test_empty_default_permissions(self):
  313. class Checked(models.Model):
  314. class Meta:
  315. default_permissions = ()
  316. self.assertEqual(checks.run_checks(self.apps.get_app_configs()), [])
  317. class LoginRequiredMiddlewareSubclass(LoginRequiredMiddleware):
  318. redirect_field_name = "redirect_to"
  319. class AuthenticationMiddlewareSubclass(AuthenticationMiddleware):
  320. pass
  321. class SessionMiddlewareSubclass(SessionMiddleware):
  322. pass
  323. @override_system_checks([check_middleware])
  324. class MiddlewareChecksTests(SimpleTestCase):
  325. @override_settings(
  326. MIDDLEWARE=[
  327. "auth_tests.test_checks.SessionMiddlewareSubclass",
  328. "auth_tests.test_checks.AuthenticationMiddlewareSubclass",
  329. "auth_tests.test_checks.LoginRequiredMiddlewareSubclass",
  330. ]
  331. )
  332. def test_middleware_subclasses(self):
  333. errors = checks.run_checks()
  334. self.assertEqual(errors, [])
  335. @override_settings(
  336. MIDDLEWARE=[
  337. "auth_tests.test_checks",
  338. "auth_tests.test_checks.NotExist",
  339. ]
  340. )
  341. def test_invalid_middleware_skipped(self):
  342. errors = checks.run_checks()
  343. self.assertEqual(errors, [])
  344. @override_settings(
  345. MIDDLEWARE=[
  346. "django.contrib.does.not.Exist",
  347. "django.contrib.sessions.middleware.SessionMiddleware",
  348. "django.contrib.auth.middleware.AuthenticationMiddleware",
  349. "django.contrib.auth.middleware.LoginRequiredMiddleware",
  350. ]
  351. )
  352. def test_check_ignores_import_error_in_middleware(self):
  353. errors = checks.run_checks()
  354. self.assertEqual(errors, [])
  355. @override_settings(
  356. MIDDLEWARE=[
  357. "django.contrib.sessions.middleware.SessionMiddleware",
  358. "django.contrib.auth.middleware.AuthenticationMiddleware",
  359. "django.contrib.auth.middleware.LoginRequiredMiddleware",
  360. ]
  361. )
  362. def test_correct_order_with_login_required_middleware(self):
  363. errors = checks.run_checks()
  364. self.assertEqual(errors, [])
  365. @override_settings(
  366. MIDDLEWARE=[
  367. "django.contrib.auth.middleware.LoginRequiredMiddleware",
  368. "django.contrib.auth.middleware.AuthenticationMiddleware",
  369. "django.contrib.sessions.middleware.SessionMiddleware",
  370. ]
  371. )
  372. def test_incorrect_order_with_login_required_middleware(self):
  373. errors = checks.run_checks()
  374. self.assertEqual(
  375. errors,
  376. [
  377. checks.Error(
  378. "In order to use django.contrib.auth.middleware."
  379. "LoginRequiredMiddleware, django.contrib.auth.middleware."
  380. "AuthenticationMiddleware must be defined before it in MIDDLEWARE.",
  381. id="auth.E013",
  382. )
  383. ],
  384. )
  385. @override_settings(
  386. MIDDLEWARE=[
  387. "django.contrib.auth.middleware.LoginRequiredMiddleware",
  388. ]
  389. )
  390. def test_missing_authentication_with_login_required_middleware(self):
  391. errors = checks.run_checks()
  392. self.assertEqual(
  393. errors,
  394. [
  395. checks.Error(
  396. "In order to use django.contrib.auth.middleware."
  397. "LoginRequiredMiddleware, django.contrib.auth.middleware."
  398. "AuthenticationMiddleware must be defined before it in MIDDLEWARE.",
  399. id="auth.E013",
  400. )
  401. ],
  402. )