[Others]Django Source Code Analysis: Permission System _ Capture the Ringleader First
Created#More Posted time:Sep 2, 2016 14:20 PM
The built-in permission system of Django has been excellent, and, with the addition of the functions provided by django-guardian, it can basically meet most of the permission demands. Rather than get into django-guardian, let's talk about the built-in permission system of Django: django.contrib.auth package first.
The permission system generally covers global and object permissions. Django only provides a framework of object permissions, which are to be specifically implemented by the third-party library django-gardian. We will only talk about the global permissions.
First, let's see what interfaces are revealed by auth package.
backends = 
for backend_path in settings.AUTHENTICATION_BACKENDS:
backend = load_backend(backend_path)
backends.append((backend, backend_path) if return_tuples else backend)
if not backends:
'No authentication backends have been defined. Does '
'AUTHENTICATION_BACKENDS contain anything?'
All the first three methods are to load backends. A backend is actually a class, which must implement the two methods: authenticate and get_user. When we authenticate users with these methods,
django will invoke these backend classes, and authenticate the user permissions with the methods provided. But how does django know what backend classes to invoke? The answer is in settings.py, which is, by default, as follows:
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']
And how does django invoke these backend classes?
If the given credentials are valid, return a User object.
for backend, backend_path in _get_backends(return_tuples=True):
# This backend doesn't accept these credentials as arguments. Try the next one.
user = backend.authenticate(**credentials)
# This backend says to stop in our tracks - this user should not be allowed in at all.
if user is None:
# Annotate the user object with the path of the backend.
user.backend = backend_path
# The credentials supplied are invalid to all backends, fire signal
From above we can see that Django will stop after the first backend class verified to be correct is invoked, or when a PermissionDenied exception occurs, so the sequence of backend classes is very important. Also, we can add custom backend classes.
def login(request, user):
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.
session_auth_hash = ''
if user is None:
user = request.user
if hasattr(user, 'get_session_auth_hash'):
session_auth_hash = user.get_session_auth_hash()
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
request.session.get(HASH_SESSION_KEY) != session_auth_hash):
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = user.backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, 'user'):
request.user = user
user_logged_in.send(sender=user.__class__, request=request, user=user)
The login method, as the name implies, is to log in to the user account, set the session, and finally send the login success notice.
Removes the authenticated user's ID from the request and flushes their
# Dispatch the signal before the user is logged out so the receivers have a
# chance to find out *who* logged out.
user = getattr(request, 'user', None)
if hasattr(user, 'is_authenticated') and not user.is_authenticated():
user = None
user_logged_out.send(sender=user.__class__, request=request, user=user)
# remember language choice saved to session
language = request.session.get(LANGUAGE_SESSION_KEY)
if language is not None:
request.session[LANGUAGE_SESSION_KEY] = language
if hasattr(request, 'user'):
from django.contrib.auth.models import AnonymousUser
request.user = AnonymousUser()
The logout method is to log out of the user account, clear the session, and finally set the current user as anonymous.
Returns the User model that is active in this project.
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
"AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL
Django will get the current user class with the get_user_model method (or settings.AUTH_USER_MODEL) instead of using the User class directly. This is to avoid any information error due to use of custom user class by the developer.
def update_session_auth_hash(request, user):
Updating a user's password logs out all sessions for the user if
django.contrib.auth.middleware.SessionAuthenticationMiddleware is enabled.
This function takes the current request and the updated user object from
which the new session hash will be derived and updates the session hash
appropriately to prevent a password change from logging out the session
from which the password was changed.
if hasattr(user, 'get_session_auth_hash') and request.user == user:
request.session[HASH_SESSION_KEY] = user.get_session_auth_hash()
Finally, this method applies to few scenarios. Generally, when the user password is updated, the user login information in the session will be cleared, so that the user needs to log in again. But with update_session_auth_hash, we can update the user session information while updating the user password, and the user will not have to log in again.