Skip to main content

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:

  1. Permissions: Granular access rights assigned to specific models.
  2. Groups: Collections of permissions that can be assigned to multiple users.
  3. 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 the Group model.
  • user_permissions: A ManyToMany relationship to the Permission model 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:

  1. Active Superuser Check: If the user is active (is_active=True) and is a superuser (is_superuser=True), the method immediately returns True.
  2. Backend Delegation: If the user is not a superuser, the check is delegated to the configured authentication backends via the internal _user_has_perm helper.
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_perm method accepts an obj parameter for backends that do support it.
  • Inactive Users: Permission checks (has_perm, has_module_perms) always return False for inactive users (is_active=False), even if they are superusers or have explicit permissions assigned.
  • Superuser Status: The is_superuser flag is a "god mode" toggle. It does not require the user to have any actual Permission objects linked to them.
  • Natural Keys: Both Group and Permission implement natural_key() methods, facilitating the serialization and transfer of authorization data between environments without relying on primary keys.