Authorization: Permissions and Groups
The authorization system in this codebase is built around three core entities: Users, Groups, and Permissions. This system allows for granular access control by defining specific actions (permissions), grouping them into logical sets (groups), and associating them with users.
The implementation is primarily found in django/contrib/auth/models.py and integrated into templates via django/contrib/auth/context_processors.py.
Core Authorization Entities
Permission
The Permission model represents a specific action that can be performed on a model. It is defined by a codename (e.g., add_user) and a content_type which links it to a specific model in the system.
Permissions are typically referenced as strings in the format app_label.codename. The Permission class provides a user_perm_str property to generate 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 PermissionManager supports lookups via natural keys, which consist of the codename, app_label, and model name.
Group
The Group model is a collection of permissions. When a user is added to a group, they automatically inherit all permissions assigned to that group. This allows for bulk management of user rights (e.g., creating an "Editors" 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()
PermissionsMixin
The PermissionsMixin is an abstract model designed to be mixed into the User model. It provides the necessary fields and methods to bridge users with the Group and Permission models.
Key fields added by the mixin:
is_superuser: A boolean that, if true, grants all permissions automatically.groups: A ManyToMany relationship toGroup.user_permissions: A ManyToMany relationship toPermissionfor direct assignment.
Checking Access Rights
The PermissionsMixin provides several methods to check for authorization in both synchronous and asynchronous contexts.
Permission Checks
The primary method for checking access is has_perm(perm, obj=None). It first checks if the user is an active superuser; if so, it returns True immediately. Otherwise, it queries the configured authentication backends.
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)
Other useful methods include:
has_perms(perm_list, obj=None): ReturnsTrueif the user has every permission in the list.has_module_perms(app_label): ReturnsTrueif the user has any permission within the specified application.
Async Support
For asynchronous environments, the mixin provides ahas_perm, ahas_perms, and ahas_module_perms. These methods mirror their synchronous counterparts but are designed to be awaited.
async def ahas_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.")
for perm in perm_list:
if not await self.ahas_perm(perm, obj):
return False
return True
Retrieving Permissions
To inspect a user's rights, the mixin provides methods to retrieve permission strings:
get_user_permissions(): Permissions assigned directly to the user.get_group_permissions(): Permissions inherited from groups.get_all_permissions(): The union of both user and group permissions.
Template Integration
Authorization checks in Django templates are facilitated by the PermWrapper and PermLookupDict classes found in django/contrib/auth/context_processors.py. These classes allow for intuitive attribute-based lookups within templates using the perms context variable.
PermWrapper
The PermWrapper acts as the entry point for the perms variable. It allows checking for app-level permissions or specific permissions using dot notation.
class PermWrapper:
def __getitem__(self, app_label):
return PermLookupDict(self.user, app_label)
def __contains__(self, perm_name):
if "." not in perm_name:
# The name refers to module.
return bool(self[perm_name])
app_label, perm_name = perm_name.split(".", 1)
return self[app_label][perm_name]
PermLookupDict
When you access perms.myapp, it returns a PermLookupDict for that specific app. Accessing a further attribute (e.g., perms.myapp.change_record) triggers a call to user.has_perm.
class PermLookupDict:
def __getitem__(self, perm_name):
return self.user.has_perm("%s.%s" % (self.app_label, perm_name))
def __bool__(self):
return self.user.has_module_perms(self.app_label)
Usage Example in Templates
In a template, you can perform checks like these:
{% if perms.blog.add_entry %}
<!-- User has 'blog.add_entry' permission -->
<a href="{% url 'entry_add' %}">Add entry</a>
{% endif %}
{% if 'blog.change_entry' in perms %}
<!-- Alternative syntax using 'in' -->
<p>You can edit entries in the blog app.</p>
{% endif %}
Programmatic Management
Permissions and groups can be managed programmatically using their respective models and managers.
Creating and Assigning Permissions
Permissions are usually created automatically for models, but they can be created manually and assigned to groups or users:
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
# Create a group
editors = Group.objects.create(name='Editors')
# Assign an existing permission to the group
# Permissions are identified by app_label and codename
perm = Permission.objects.get(
codename='can_publish',
content_type__app_label='myapp'
)
editors.permissions.add(perm)
# Add a user to the group
user.groups.add(editors)
Natural Key Lookups
The GroupManager and PermissionManager provide natural key support, which is particularly useful during data migrations or when loading fixtures.
# Lookup a group by name
group = Group.objects.get_by_natural_key('Editors')
# Lookup a permission by codename, app_label, and model
perm = Permission.objects.get_by_natural_key('add_user', 'auth', 'user')