checks.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. from itertools import chain
  2. from types import MethodType
  3. from django.apps import apps
  4. from django.conf import settings
  5. from django.core import checks
  6. from django.utils.module_loading import import_string
  7. from .management import _get_builtin_permissions
  8. def _subclass_index(class_path, candidate_paths):
  9. """
  10. Return the index of dotted class path (or a subclass of that class) in a
  11. list of candidate paths. If it does not exist, return -1.
  12. """
  13. cls = import_string(class_path)
  14. for index, path in enumerate(candidate_paths):
  15. try:
  16. candidate_cls = import_string(path)
  17. if issubclass(candidate_cls, cls):
  18. return index
  19. except (ImportError, TypeError):
  20. continue
  21. return -1
  22. def check_user_model(app_configs=None, **kwargs):
  23. if app_configs is None:
  24. cls = apps.get_model(settings.AUTH_USER_MODEL)
  25. else:
  26. app_label, model_name = settings.AUTH_USER_MODEL.split(".")
  27. for app_config in app_configs:
  28. if app_config.label == app_label:
  29. cls = app_config.get_model(model_name)
  30. break
  31. else:
  32. # Checks might be run against a set of app configs that don't
  33. # include the specified user model. In this case we simply don't
  34. # perform the checks defined below.
  35. return []
  36. errors = []
  37. # Check that REQUIRED_FIELDS is a list
  38. if not isinstance(cls.REQUIRED_FIELDS, (list, tuple)):
  39. errors.append(
  40. checks.Error(
  41. "'REQUIRED_FIELDS' must be a list or tuple.",
  42. obj=cls,
  43. id="auth.E001",
  44. )
  45. )
  46. # Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS.
  47. if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS:
  48. errors.append(
  49. checks.Error(
  50. "The field named as the 'USERNAME_FIELD' "
  51. "for a custom user model must not be included in 'REQUIRED_FIELDS'.",
  52. hint=(
  53. "The 'USERNAME_FIELD' is currently set to '%s', you "
  54. "should remove '%s' from the 'REQUIRED_FIELDS'."
  55. % (cls.USERNAME_FIELD, cls.USERNAME_FIELD)
  56. ),
  57. obj=cls,
  58. id="auth.E002",
  59. )
  60. )
  61. # Check that the username field is unique
  62. if not cls._meta.get_field(cls.USERNAME_FIELD).unique and not any(
  63. constraint.fields == (cls.USERNAME_FIELD,)
  64. for constraint in cls._meta.total_unique_constraints
  65. ):
  66. if settings.AUTHENTICATION_BACKENDS == [
  67. "django.contrib.auth.backends.ModelBackend"
  68. ]:
  69. errors.append(
  70. checks.Error(
  71. "'%s.%s' must be unique because it is named as the "
  72. "'USERNAME_FIELD'." % (cls._meta.object_name, cls.USERNAME_FIELD),
  73. obj=cls,
  74. id="auth.E003",
  75. )
  76. )
  77. else:
  78. errors.append(
  79. checks.Warning(
  80. "'%s.%s' is named as the 'USERNAME_FIELD', but it is not unique."
  81. % (cls._meta.object_name, cls.USERNAME_FIELD),
  82. hint=(
  83. "Ensure that your authentication backend(s) can handle "
  84. "non-unique usernames."
  85. ),
  86. obj=cls,
  87. id="auth.W004",
  88. )
  89. )
  90. if isinstance(cls().is_anonymous, MethodType):
  91. errors.append(
  92. checks.Critical(
  93. "%s.is_anonymous must be an attribute or property rather than "
  94. "a method. Ignoring this is a security issue as anonymous "
  95. "users will be treated as authenticated!" % cls,
  96. obj=cls,
  97. id="auth.C009",
  98. )
  99. )
  100. if isinstance(cls().is_authenticated, MethodType):
  101. errors.append(
  102. checks.Critical(
  103. "%s.is_authenticated must be an attribute or property rather "
  104. "than a method. Ignoring this is a security issue as anonymous "
  105. "users will be treated as authenticated!" % cls,
  106. obj=cls,
  107. id="auth.C010",
  108. )
  109. )
  110. return errors
  111. def check_models_permissions(app_configs=None, **kwargs):
  112. if app_configs is None:
  113. models = apps.get_models()
  114. else:
  115. models = chain.from_iterable(
  116. app_config.get_models() for app_config in app_configs
  117. )
  118. Permission = apps.get_model("auth", "Permission")
  119. permission_name_max_length = Permission._meta.get_field("name").max_length
  120. permission_codename_max_length = Permission._meta.get_field("codename").max_length
  121. errors = []
  122. for model in models:
  123. opts = model._meta
  124. builtin_permissions = dict(_get_builtin_permissions(opts))
  125. # Check builtin permission name length.
  126. max_builtin_permission_name_length = (
  127. max(len(name) for name in builtin_permissions.values())
  128. if builtin_permissions
  129. else 0
  130. )
  131. if max_builtin_permission_name_length > permission_name_max_length:
  132. verbose_name_max_length = permission_name_max_length - (
  133. max_builtin_permission_name_length - len(opts.verbose_name_raw)
  134. )
  135. errors.append(
  136. checks.Error(
  137. "The verbose_name of model '%s' must be at most %d "
  138. "characters for its builtin permission names to be at "
  139. "most %d characters."
  140. % (opts.label, verbose_name_max_length, permission_name_max_length),
  141. obj=model,
  142. id="auth.E007",
  143. )
  144. )
  145. # Check builtin permission codename length.
  146. max_builtin_permission_codename_length = (
  147. max(len(codename) for codename in builtin_permissions.keys())
  148. if builtin_permissions
  149. else 0
  150. )
  151. if max_builtin_permission_codename_length > permission_codename_max_length:
  152. model_name_max_length = permission_codename_max_length - (
  153. max_builtin_permission_codename_length - len(opts.model_name)
  154. )
  155. errors.append(
  156. checks.Error(
  157. "The name of model '%s' must be at most %d characters "
  158. "for its builtin permission codenames to be at most %d "
  159. "characters."
  160. % (
  161. opts.label,
  162. model_name_max_length,
  163. permission_codename_max_length,
  164. ),
  165. obj=model,
  166. id="auth.E011",
  167. )
  168. )
  169. codenames = set()
  170. for codename, name in opts.permissions:
  171. # Check custom permission name length.
  172. if len(name) > permission_name_max_length:
  173. errors.append(
  174. checks.Error(
  175. "The permission named '%s' of model '%s' is longer "
  176. "than %d characters."
  177. % (
  178. name,
  179. opts.label,
  180. permission_name_max_length,
  181. ),
  182. obj=model,
  183. id="auth.E008",
  184. )
  185. )
  186. # Check custom permission codename length.
  187. if len(codename) > permission_codename_max_length:
  188. errors.append(
  189. checks.Error(
  190. "The permission codenamed '%s' of model '%s' is "
  191. "longer than %d characters."
  192. % (
  193. codename,
  194. opts.label,
  195. permission_codename_max_length,
  196. ),
  197. obj=model,
  198. id="auth.E012",
  199. )
  200. )
  201. # Check custom permissions codename clashing.
  202. if codename in builtin_permissions:
  203. errors.append(
  204. checks.Error(
  205. "The permission codenamed '%s' clashes with a builtin "
  206. "permission for model '%s'." % (codename, opts.label),
  207. obj=model,
  208. id="auth.E005",
  209. )
  210. )
  211. elif codename in codenames:
  212. errors.append(
  213. checks.Error(
  214. "The permission codenamed '%s' is duplicated for "
  215. "model '%s'." % (codename, opts.label),
  216. obj=model,
  217. id="auth.E006",
  218. )
  219. )
  220. codenames.add(codename)
  221. return errors
  222. def check_middleware(app_configs, **kwargs):
  223. errors = []
  224. login_required_index = _subclass_index(
  225. "django.contrib.auth.middleware.LoginRequiredMiddleware",
  226. settings.MIDDLEWARE,
  227. )
  228. if login_required_index != -1:
  229. auth_index = _subclass_index(
  230. "django.contrib.auth.middleware.AuthenticationMiddleware",
  231. settings.MIDDLEWARE,
  232. )
  233. if auth_index == -1 or auth_index > login_required_index:
  234. errors.append(
  235. checks.Error(
  236. "In order to use django.contrib.auth.middleware."
  237. "LoginRequiredMiddleware, django.contrib.auth.middleware."
  238. "AuthenticationMiddleware must be defined before it in MIDDLEWARE.",
  239. id="auth.E013",
  240. )
  241. )
  242. return errors