Authorization: Permissions and Groups
The authorization system in this codebase provides a robust framework for managing user access levels through a combination of individual permissions and logical groups. This system is primarily implemented in django/contrib/auth/models.py and integrated into the admin interface via django/contrib/auth/admin.py.
Core Concepts
The authorization architecture relies on three primary components:
- Permissions: Granular access rights assigned to specific models.
- Groups: Collections of permissions that can be assigned to multiple users.
- PermissionsMixin: An abstract model that connects users to both permissions and groups.
The Permission Model
The Permission class represents a single action that can be performed on a model. Each permission is defined by a codename (e.g., add_user) and linked to a ContentType, which identifies the specific model it applies to.
class Permission(models.Model):
name = models.CharField(_("name"), max_length=255)
content_type = models.ForeignKey(
ContentType,
models.CASCADE,
verbose_name=_("content type"),
)
codename = models.CharField(_("codename"), max_length=100)
objects = PermissionManager()
# ...
Permissions are typically identified using a string format: <app_label>.<codename>. The user_perm_str property provides this representation:
@property
def user_perm_str(self):
"""String representation for the user permission check."""
return f"{self.content_type.app_label}.{self.codename}"
The Group Model
The Group model allows for the categorization of users to simplify permission management. Instead of assigning permissions to users individually, you can assign them to a group and then add users to that group.
class Group(models.Model):
name = models.CharField(_("name"), max_length=150, unique=True)
permissions = models.ManyToManyField(
Permission,
verbose_name=_("permissions"),
blank=True,
)
objects = GroupManager()
# ...
A user assigned to a group automatically inherits all permissions associated with that group.
Integrating Authorization with Users
The PermissionsMixin is the bridge that adds authorization capabilities to a User model. It provides the necessary fields and methods to check for permissions across both direct assignments and group memberships.
Fields and Relationships
PermissionsMixin adds three critical fields:
is_superuser: A boolean that, if true, bypasses all permission checks (granting all permissions).groups: A ManyToMany relationship to theGroupmodel.user_permissions: A ManyToMany relationship to thePermissionmodel for direct assignments.
Permission Checking Logic
The core method for checking access is has_perm(perm, obj=None). Its implementation in PermissionsMixin follows a specific hierarchy:
- Active Superuser Check: If the user is active (
is_active=True) and is a superuser (is_superuser=True), the method immediately returnsTrue. - Backend Delegation: If the user is not a superuser, the check is delegated to the configured authentication backends via the internal
_user_has_permhelper.
def has_perm(self, perm, obj=None):
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
# Otherwise we need to check the backends.
return _user_has_perm(self, perm, obj)
The _user_has_perm function iterates through all registered backends (like ModelBackend) and returns True if any backend confirms the permission:
def _user_has_perm(user, perm, obj):
for backend in auth.get_backends():
if not hasattr(backend, "has_perm"):
continue
try:
if backend.has_perm(user, perm, obj):
return True
except PermissionDenied:
return False
return False
Programmatic Usage
Developers can interact with the authorization system using the managers and mixin methods.
Retrieving Permissions by Natural Key
The PermissionManager allows retrieving permissions without knowing the specific ContentType ID, which is useful for setup scripts or migrations:
# Example from tests/auth_tests/test_models.py
perm = Permission.objects.get_by_natural_key(
"delete_examplemodelb",
"app_b",
"examplemodelb",
)
Checking Multiple Permissions
The has_perms method accepts an iterable of permission strings and returns True only if the user has all of them:
def has_perms(self, perm_list, obj=None):
if not isinstance(perm_list, Iterable) or isinstance(perm_list, str):
raise ValueError("perm_list must be an iterable of permissions.")
return all(self.has_perm(perm, obj) for perm in perm_list)
Admin Interface Integration
The GroupAdmin class in django/contrib/auth/admin.py manages how groups and their permissions are handled in the Django admin.
Performance Optimization
Because a system can have hundreds of permissions, GroupAdmin optimizes the permission selection field by using select_related("content_type"). This prevents a "N+1" query problem where the database would otherwise be queried for every single permission's content type during form rendering.
class GroupAdmin(admin.ModelAdmin):
search_fields = ("name",)
ordering = ("name",)
filter_horizontal = ("permissions",)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
if db_field.name == "permissions":
qs = kwargs.get("queryset", db_field.remote_field.model.objects)
# Avoid a major performance hit resolving permission names
kwargs["queryset"] = qs.select_related("content_type")
return super().formfield_for_manytomany(db_field, request=request, **kwargs)
Important Considerations
- Model-Level Scope: Permissions in this system are global per model type. For example, a "change" permission on a "News" model allows a user to change any news story. The system does not natively support object-level permissions (e.g., "only change stories I created"), though the
has_permmethod accepts anobjparameter for backends that do support it. - Inactive Users: Permission checks (
has_perm,has_module_perms) always returnFalsefor inactive users (is_active=False), even if they are superusers or have explicit permissions assigned. - Superuser Status: The
is_superuserflag is a "god mode" toggle. It does not require the user to have any actualPermissionobjects linked to them. - Natural Keys: Both
GroupandPermissionimplementnatural_key()methods, facilitating the serialization and transfer of authorization data between environments without relying on primary keys.