Fixed #30226 -- Added BaseBackend for authentication.

This commit is contained in:
Tobias Bengfort 2019-03-08 17:48:50 +01:00 committed by Mariusz Felisiak
parent 4b6dfe1622
commit 75337a6050
5 changed files with 79 additions and 14 deletions

View File

@ -8,7 +8,24 @@ from django.utils.deprecation import RemovedInDjango31Warning
UserModel = get_user_model() UserModel = get_user_model()
class ModelBackend: class BaseBackend:
def authenticate(self, request, **kwargs):
return None
def get_user(self, user_id):
return None
def get_group_permissions(self, user_obj, obj=None):
return set()
def get_all_permissions(self, user_obj, obj=None):
return self.get_group_permissions(user_obj, obj=obj)
def has_perm(self, user_obj, perm, obj=None):
return perm in self.get_all_permissions(user_obj, obj=obj)
class ModelBackend(BaseBackend):
""" """
Authenticates against settings.AUTH_USER_MODEL. Authenticates against settings.AUTH_USER_MODEL.
""" """
@ -86,7 +103,7 @@ class ModelBackend:
return user_obj._perm_cache return user_obj._perm_cache
def has_perm(self, user_obj, perm, obj=None): def has_perm(self, user_obj, perm, obj=None):
return user_obj.is_active and perm in self.get_all_permissions(user_obj, obj) return user_obj.is_active and super().has_perm(user_obj, perm, obj=obj)
def has_module_perms(self, user_obj, app_label): def has_module_perms(self, user_obj, app_label):
""" """

View File

@ -460,6 +460,27 @@ Available authentication backends
The following backends are available in :mod:`django.contrib.auth.backends`: The following backends are available in :mod:`django.contrib.auth.backends`:
.. class:: BaseBackend
.. versionadded:: 3.0
A base class that provides default implementations for all required
methods. By default, it will reject any user and provide no permissions.
.. method:: get_group_permissions(user_obj, obj=None)
Returns an empty set.
.. method:: get_all_permissions(user_obj, obj=None)
Uses :meth:`get_group_permissions` to get the set of permission strings
the ``user_obj`` has.
.. method:: has_perm(user_obj, perm, obj=None)
Uses :meth:`get_all_permissions` to check if ``user_obj`` has the
permission string ``perm``.
.. class:: ModelBackend .. class:: ModelBackend
This is the default authentication backend used by Django. It This is the default authentication backend used by Django. It

View File

@ -69,6 +69,9 @@ Minor features
:class:`~django.contrib.auth.views.PasswordResetConfirmView` allows specifying :class:`~django.contrib.auth.views.PasswordResetConfirmView` allows specifying
a token parameter displayed as a component of password reset URLs. a token parameter displayed as a component of password reset URLs.
* Added :class:`~django.contrib.auth.backends.BaseBackend` class to ease
customization of authentication backends.
:mod:`django.contrib.contenttypes` :mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -100,14 +100,18 @@ and returns a user object or ``None``.
The ``authenticate`` method takes a ``request`` argument and credentials as The ``authenticate`` method takes a ``request`` argument and credentials as
keyword arguments. Most of the time, it'll just look like this:: keyword arguments. Most of the time, it'll just look like this::
class MyBackend: from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, username=None, password=None): def authenticate(self, request, username=None, password=None):
# Check the username/password and return a user. # Check the username/password and return a user.
... ...
But it could also authenticate a token, like so:: But it could also authenticate a token, like so::
class MyBackend: from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, token=None): def authenticate(self, request, token=None):
# Check the token and return a user. # Check the token and return a user.
... ...
@ -132,10 +136,11 @@ variable defined in your ``settings.py`` file and creates a Django ``User``
object the first time a user authenticates:: object the first time a user authenticates::
from django.conf import settings from django.conf import settings
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User from django.contrib.auth.models import User
class SettingsBackend: class SettingsBackend(BaseBackend):
""" """
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD. Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
@ -190,11 +195,11 @@ exception in :meth:`~django.contrib.auth.models.User.has_perm()` or
:meth:`~django.contrib.auth.models.User.has_module_perms()`, the authorization :meth:`~django.contrib.auth.models.User.has_module_perms()`, the authorization
will immediately fail and Django won't check the backends that follow. will immediately fail and Django won't check the backends that follow.
The simple backend above could implement permissions for the magic admin A backend could implement permissions for the magic admin fairly simply::
fairly simply::
class SettingsBackend: from django.contrib.auth.backends import BaseBackend
...
class MagicAdminBackend(BaseBackend):
def has_perm(self, user_obj, perm, obj=None): def has_perm(self, user_obj, perm, obj=None):
return user_obj.username == settings.ADMIN_LOGIN return user_obj.username == settings.ADMIN_LOGIN
@ -205,10 +210,7 @@ all take the user object, which may be an anonymous user, as an argument.
A full authorization implementation can be found in the ``ModelBackend`` class A full authorization implementation can be found in the ``ModelBackend`` class
in :source:`django/contrib/auth/backends.py`, which is the default backend and in :source:`django/contrib/auth/backends.py`, which is the default backend and
queries the ``auth_permission`` table most of the time. If you wish to provide queries the ``auth_permission`` table most of the time.
custom behavior for only part of the backend API, you can take advantage of
Python inheritance and subclass ``ModelBackend`` instead of implementing the
complete API in a custom backend.
.. _anonymous_auth: .. _anonymous_auth:

View File

@ -4,7 +4,7 @@ from unittest import mock
from django.contrib.auth import ( from django.contrib.auth import (
BACKEND_SESSION_KEY, SESSION_KEY, authenticate, get_user, signals, BACKEND_SESSION_KEY, SESSION_KEY, authenticate, get_user, signals,
) )
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import BaseBackend, ModelBackend
from django.contrib.auth.hashers import MD5PasswordHasher from django.contrib.auth.hashers import MD5PasswordHasher
from django.contrib.auth.models import AnonymousUser, Group, Permission, User from django.contrib.auth.models import AnonymousUser, Group, Permission, User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -20,6 +20,28 @@ from .models import (
) )
class SimpleBackend(BaseBackend):
def get_group_permissions(self, user_obj, obj=None):
return ['group_perm']
@override_settings(AUTHENTICATION_BACKENDS=['auth_tests.test_auth_backends.SimpleBackend'])
class BaseBackendTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user('test', 'test@example.com', 'test')
def test_get_group_permissions(self):
self.assertEqual(self.user.get_group_permissions(), {'group_perm'})
def test_get_all_permissions(self):
self.assertEqual(self.user.get_all_permissions(), {'group_perm'})
def test_has_perm(self):
self.assertIs(self.user.has_perm('group_perm'), True)
self.assertIs(self.user.has_perm('other_perm', TestObj()), False)
class CountingMD5PasswordHasher(MD5PasswordHasher): class CountingMD5PasswordHasher(MD5PasswordHasher):
"""Hasher that counts how many times it computes a hash.""" """Hasher that counts how many times it computes a hash."""