from itertools import chain from types import MethodType from django.apps import apps from django.conf import settings from django.core import checks from .management import _get_builtin_permissions def check_user_model(app_configs=None, **kwargs): if app_configs is None: cls = apps.get_model(settings.AUTH_USER_MODEL) else: app_label, model_name = settings.AUTH_USER_MODEL.split(".") for app_config in app_configs: if app_config.label == app_label: cls = app_config.get_model(model_name) break else: # Checks might be run against a set of app configs that don't # include the specified user model. In this case we simply don't # perform the checks defined below. return [] errors = [] # Check that REQUIRED_FIELDS is a list if not isinstance(cls.REQUIRED_FIELDS, (list, tuple)): errors.append( checks.Error( "'REQUIRED_FIELDS' must be a list or tuple.", obj=cls, id="auth.E001", ) ) # Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS. if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS: errors.append( checks.Error( "The field named as the 'USERNAME_FIELD' " "for a custom user model must not be included in 'REQUIRED_FIELDS'.", hint=( "The 'USERNAME_FIELD' is currently set to '%s', you " "should remove '%s' from the 'REQUIRED_FIELDS'." % (cls.USERNAME_FIELD, cls.USERNAME_FIELD) ), obj=cls, id="auth.E002", ) ) # Check that the username field is unique if not cls._meta.get_field(cls.USERNAME_FIELD).unique and not any( constraint.fields == (cls.USERNAME_FIELD,) for constraint in cls._meta.total_unique_constraints ): if settings.AUTHENTICATION_BACKENDS == [ "django.contrib.auth.backends.ModelBackend" ]: errors.append( checks.Error( "'%s.%s' must be unique because it is named as the " "'USERNAME_FIELD'." % (cls._meta.object_name, cls.USERNAME_FIELD), obj=cls, id="auth.E003", ) ) else: errors.append( checks.Warning( "'%s.%s' is named as the 'USERNAME_FIELD', but it is not unique." % (cls._meta.object_name, cls.USERNAME_FIELD), hint=( "Ensure that your authentication backend(s) can handle " "non-unique usernames." ), obj=cls, id="auth.W004", ) ) if isinstance(cls().is_anonymous, MethodType): errors.append( checks.Critical( "%s.is_anonymous must be an attribute or property rather than " "a method. Ignoring this is a security issue as anonymous " "users will be treated as authenticated!" % cls, obj=cls, id="auth.C009", ) ) if isinstance(cls().is_authenticated, MethodType): errors.append( checks.Critical( "%s.is_authenticated must be an attribute or property rather " "than a method. Ignoring this is a security issue as anonymous " "users will be treated as authenticated!" % cls, obj=cls, id="auth.C010", ) ) return errors def check_models_permissions(app_configs=None, **kwargs): if app_configs is None: models = apps.get_models() else: models = chain.from_iterable( app_config.get_models() for app_config in app_configs ) Permission = apps.get_model("auth", "Permission") permission_name_max_length = Permission._meta.get_field("name").max_length permission_codename_max_length = Permission._meta.get_field("codename").max_length errors = [] for model in models: opts = model._meta builtin_permissions = dict(_get_builtin_permissions(opts)) # Check builtin permission name length. max_builtin_permission_name_length = ( max(len(name) for name in builtin_permissions.values()) if builtin_permissions else 0 ) if max_builtin_permission_name_length > permission_name_max_length: verbose_name_max_length = permission_name_max_length - ( max_builtin_permission_name_length - len(opts.verbose_name_raw) ) errors.append( checks.Error( "The verbose_name of model '%s' must be at most %d " "characters for its builtin permission names to be at " "most %d characters." % (opts.label, verbose_name_max_length, permission_name_max_length), obj=model, id="auth.E007", ) ) # Check builtin permission codename length. max_builtin_permission_codename_length = ( max(len(codename) for codename in builtin_permissions.keys()) if builtin_permissions else 0 ) if max_builtin_permission_codename_length > permission_codename_max_length: model_name_max_length = permission_codename_max_length - ( max_builtin_permission_codename_length - len(opts.model_name) ) errors.append( checks.Error( "The name of model '%s' must be at most %d characters " "for its builtin permission codenames to be at most %d " "characters." % ( opts.label, model_name_max_length, permission_codename_max_length, ), obj=model, id="auth.E011", ) ) codenames = set() for codename, name in opts.permissions: # Check custom permission name length. if len(name) > permission_name_max_length: errors.append( checks.Error( "The permission named '%s' of model '%s' is longer " "than %d characters." % ( name, opts.label, permission_name_max_length, ), obj=model, id="auth.E008", ) ) # Check custom permission codename length. if len(codename) > permission_codename_max_length: errors.append( checks.Error( "The permission codenamed '%s' of model '%s' is " "longer than %d characters." % ( codename, opts.label, permission_codename_max_length, ), obj=model, id="auth.E012", ) ) # Check custom permissions codename clashing. if codename in builtin_permissions: errors.append( checks.Error( "The permission codenamed '%s' clashes with a builtin " "permission for model '%s'." % (codename, opts.label), obj=model, id="auth.E005", ) ) elif codename in codenames: errors.append( checks.Error( "The permission codenamed '%s' is duplicated for " "model '%s'." % (codename, opts.label), obj=model, id="auth.E006", ) ) codenames.add(codename) return errors