Fixed #14249 -- Added support for inactive users to the auth backend system. Thanks, Harro van der Klauw.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15010 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2010-12-21 19:18:12 +00:00
parent 5830477e46
commit 745c255a19
9 changed files with 156 additions and 20 deletions

View File

@ -30,6 +30,11 @@ def load_backend(path):
warn("Authentication backends without a `supports_anonymous_user` attribute are deprecated. Please define it in %s." % cls,
DeprecationWarning)
cls.supports_anonymous_user = False
if not hasattr(cls, 'supports_inactive_user'):
warn("Authentication backends without a `supports_inactive_user` attribute are deprecated. Please define it in %s." % cls,
DeprecationWarning)
cls.supports_inactive_user = False
return cls()
def get_backends():

View File

@ -8,6 +8,7 @@ class ModelBackend(object):
"""
supports_object_permissions = False
supports_anonymous_user = True
supports_inactive_user = True
# TODO: Model, login attribute name and password attribute name should be
# configurable.
@ -42,12 +43,16 @@ class ModelBackend(object):
return user_obj._perm_cache
def has_perm(self, user_obj, perm):
if not user_obj.is_active:
return False
return perm in self.get_all_permissions(user_obj)
def has_module_perms(self, user_obj, app_label):
"""
Returns True if user_obj has any permissions in the given app_label.
"""
if not user_obj.is_active:
return False
for perm in self.get_all_permissions(user_obj):
if perm[:perm.index('.')] == app_label:
return True

View File

@ -170,8 +170,10 @@ def _user_get_all_permissions(user, obj):
def _user_has_perm(user, perm, obj):
anon = user.is_anonymous()
active = user.is_active
for backend in auth.get_backends():
if not anon or backend.supports_anonymous_user:
if (not active and not anon and backend.supports_inactive_user) or \
(not anon or backend.supports_anonymous_user):
if hasattr(backend, "has_perm"):
if obj is not None:
if (backend.supports_object_permissions and
@ -185,8 +187,10 @@ def _user_has_perm(user, perm, obj):
def _user_has_module_perms(user, app_label):
anon = user.is_anonymous()
active = user.is_active
for backend in auth.get_backends():
if not anon or backend.supports_anonymous_user:
if (not active and not anon and backend.supports_inactive_user) or \
(not anon or backend.supports_anonymous_user):
if hasattr(backend, "has_module_perms"):
if backend.has_module_perms(user, app_label):
return True
@ -310,12 +314,9 @@ class User(models.Model):
auth backend is assumed to have permission in general. If an object
is provided, permissions for this specific object are checked.
"""
# Inactive users have no permissions.
if not self.is_active:
return False
# Superusers have all permissions.
if self.is_superuser:
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
# Otherwise we need to check the backends.
@ -337,10 +338,8 @@ class User(models.Model):
Returns True if the user has any permissions in the given app
label. Uses pretty much the same logic as has_perm, above.
"""
if not self.is_active:
return False
if self.is_superuser:
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
return _user_has_module_perms(self, app_label)

View File

@ -1,14 +1,18 @@
from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest, AnonymousUserBackendTest, NoAnonymousUserBackendTest, NoBackendsTest
from django.contrib.auth.tests.auth_backends import (BackendTest,
RowlevelBackendTest, AnonymousUserBackendTest, NoAnonymousUserBackendTest,
NoBackendsTest, InActiveUserBackendTest, NoInActiveUserBackendTest)
from django.contrib.auth.tests.basic import BasicTestCase
from django.contrib.auth.tests.decorators import LoginRequiredTestCase
from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest
from django.contrib.auth.tests.remote_user \
import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest
from django.contrib.auth.tests.forms import (UserCreationFormTest,
AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest,
UserChangeFormTest, PasswordResetFormTest)
from django.contrib.auth.tests.remote_user import (RemoteUserTest,
RemoteUserNoCreateTest, RemoteUserCustomTest)
from django.contrib.auth.tests.models import ProfileTestCase
from django.contrib.auth.tests.signals import SignalTestCase
from django.contrib.auth.tests.tokens import TokenGeneratorTest
from django.contrib.auth.tests.views import PasswordResetTest, \
ChangePasswordTest, LoginTest, LogoutTest, LoginURLSettings
from django.contrib.auth.tests.views import (PasswordResetTest,
ChangePasswordTest, LoginTest, LogoutTest, LoginURLSettings)
from django.contrib.auth.tests.permissions import TestAuthPermissions
# The password for the fixture data users is 'password'

View File

@ -102,9 +102,12 @@ class TestObj(object):
class SimpleRowlevelBackend(object):
supports_object_permissions = True
supports_inactive_user = False
# This class also supports tests for anonymous user permissions, and
# inactive user permissions via subclasses which just set the
# 'supports_anonymous_user' or 'supports_inactive_user' attribute.
# This class also supports tests for anonymous user permissions,
# via subclasses which just set the 'supports_anonymous_user' attribute.
def has_perm(self, user, perm, obj=None):
if not obj:
@ -116,9 +119,13 @@ class SimpleRowlevelBackend(object):
elif user.is_anonymous() and perm == 'anon':
# not reached due to supports_anonymous_user = False
return True
elif not user.is_active and perm == 'inactive':
return True
return False
def has_module_perms(self, user, app_label):
if not user.is_anonymous() and not user.is_active:
return False
return app_label == "app1"
def get_all_permissions(self, user, obj=None):
@ -192,11 +199,13 @@ class RowlevelBackendTest(TestCase):
class AnonymousUserBackend(SimpleRowlevelBackend):
supports_anonymous_user = True
supports_inactive_user = False
class NoAnonymousUserBackend(SimpleRowlevelBackend):
supports_anonymous_user = False
supports_inactive_user = False
class AnonymousUserBackendTest(TestCase):
@ -258,6 +267,7 @@ class NoAnonymousUserBackendTest(TestCase):
def test_get_all_permissions(self):
self.assertEqual(self.user1.get_all_permissions(TestObj()), set())
class NoBackendsTest(TestCase):
"""
Tests that an appropriate error is raised if no auth backends are provided.
@ -272,3 +282,67 @@ class NoBackendsTest(TestCase):
def test_raises_exception(self):
self.assertRaises(ImproperlyConfigured, self.user.has_perm, ('perm', TestObj(),))
class InActiveUserBackend(SimpleRowlevelBackend):
supports_anonymous_user = False
supports_inactive_user = True
class NoInActiveUserBackend(SimpleRowlevelBackend):
supports_anonymous_user = False
supports_inactive_user = False
class InActiveUserBackendTest(TestCase):
"""
Tests for a inactive user delegating to backend if it has 'supports_inactive_user' = True
"""
backend = 'django.contrib.auth.tests.auth_backends.InActiveUserBackend'
def setUp(self):
self.curr_auth = settings.AUTHENTICATION_BACKENDS
settings.AUTHENTICATION_BACKENDS = (self.backend,)
self.user1 = User.objects.create_user('test', 'test@example.com', 'test')
self.user1.is_active = False
self.user1.save()
def tearDown(self):
settings.AUTHENTICATION_BACKENDS = self.curr_auth
def test_has_perm(self):
self.assertEqual(self.user1.has_perm('perm', TestObj()), False)
self.assertEqual(self.user1.has_perm('inactive', TestObj()), True)
def test_has_module_perms(self):
self.assertEqual(self.user1.has_module_perms("app1"), False)
self.assertEqual(self.user1.has_module_perms("app2"), False)
class NoInActiveUserBackendTest(TestCase):
"""
Tests that an inactive user does not delegate to backend if it has 'supports_inactive_user' = False
"""
backend = 'django.contrib.auth.tests.auth_backends.NoInActiveUserBackend'
def setUp(self):
self.curr_auth = settings.AUTHENTICATION_BACKENDS
settings.AUTHENTICATION_BACKENDS = tuple(self.curr_auth) + (self.backend,)
self.user1 = User.objects.create_user('test', 'test@example.com', 'test')
self.user1.is_active = False
self.user1.save()
def tearDown(self):
settings.AUTHENTICATION_BACKENDS = self.curr_auth
def test_has_perm(self):
self.assertEqual(self.user1.has_perm('perm', TestObj()), False)
self.assertEqual(self.user1.has_perm('inactive', TestObj()), True)
def test_has_module_perms(self):
self.assertEqual(self.user1.has_module_perms("app1"), False)
self.assertEqual(self.user1.has_module_perms("app2"), False)

View File

@ -98,6 +98,9 @@ their deprecation, as per the :ref:`Django deprecation policy
* The ``no`` language code has been deprecated in favor of the ``nb``
language code.
* Authentication backends need to define the boolean attribute
``supports_inactive_user``.
* 1.5
* The ``mod_python`` request handler has been deprecated since the 1.3
release. The ``mod_wsgi`` handler should be used instead.
@ -139,6 +142,11 @@ their deprecation, as per the :ref:`Django deprecation policy
* The :djadmin:`reset` and :djadmin:`sqlreset` management commands
are deprecated.
* Authentication backends need to support a inactive user
being passed to all methods dealing with permissions.
The ``supports_inactive_user`` variable is not checked any
longer and can be removed.
* 2.0
* ``django.views.defaults.shortcut()``. This function has been moved
to ``django.contrib.contenttypes.views.shortcut()`` as part of the

View File

@ -55,6 +55,14 @@ displayed by most translation tools.
For more information, see :ref:`translator-comments`.
Permissions for inactive users
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you provide a custom auth backend with ``supports_inactive_user`` set to
``True``, an inactive user model will check the backend for permissions.
This is useful for further centralizing the permission handling. See the
:ref:`authentication docs <topics-auth>` for more details.
Backwards-incompatible changes in 1.3 alpha 2
=============================================

View File

@ -177,6 +177,14 @@ caching in Django<topics/cache>`.
.. _pylibmc: http://sendapatch.se/projects/pylibmc/
Permissions for inactive users
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you provide a custom auth backend with ``supports_inactive_user`` set to
``True``, an inactive user model will check the backend for permissions.
This is useful for further centralizing the permission handling. See the
:ref:`authentication docs <topics-auth>` for more details.
Everything else
~~~~~~~~~~~~~~~

View File

@ -741,7 +741,7 @@ The login_required decorator
@login_required
def my_view(request):
...
:func:`~django.contrib.auth.decorators.login_required` does the following:
* If the user isn't logged in, redirect to
@ -1645,6 +1645,31 @@ loudly. Additionally ``supports_anonymous_user`` will be set to ``False``.
Django 1.4 will assume that every backend supports anonymous users being
passed to the authorization methods.
Authorization for inactive users
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 1.3
An inactive user is a one that is authenticated but has its attribute
``is_active`` set to ``False``. However this does not mean they are not
authrozied to do anything. For example they are allowed to activate their
account.
The support for anonymous users in the permission system allows for
anonymous users to have permissions to do something while inactive
authenticated users do not.
To enable this on your own backend, you must set the class attribute
``supports_inactive_user`` to ``True``.
A nonexisting ``supports_inactive_user`` attribute will raise a
``PendingDeprecationWarning`` if used in Django 1.3. In Django 1.4, this
warning will be updated to a ``DeprecationWarning`` which will be displayed
loudly. Additionally ``supports_inactive_user`` will be set to ``False``.
Django 1.5 will assume that every backend supports inactive users being
passed to the authorization methods.
Handling object permissions
---------------------------