Fixed $5457 - the auth system now delegates permission checking to auth backend(s). As an added bonus, the auth backends now have some unit tests! Thanks, Florian Apolloner.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@6375 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jacob Kaplan-Moss 2007-09-19 16:50:30 +00:00
parent 466871ec1b
commit f857e37776
7 changed files with 198 additions and 40 deletions

View File

@ -49,6 +49,7 @@ answer newbie questions, and generally made Django that much better:
andy@jadedplanet.net
Fabrice Aneche <akh@nobugware.com>
ant9000@netwise.it
Florian Apolloner
David Ascher <http://ascher.ca/>
david@kazserve.org
Arthur <avandorp@gmail.com>

View File

@ -1,3 +1,4 @@
from django.db import connection
from django.contrib.auth.models import User
class ModelBackend:
@ -14,6 +15,49 @@ class ModelBackend:
except User.DoesNotExist:
return None
def get_group_permissions(self, user_obj):
"Returns a list of permission strings that this user has through his/her groups."
if not hasattr(user_obj, '_group_perm_cache'):
cursor = connection.cursor()
# The SQL below works out to the following, after DB quoting:
# cursor.execute("""
# SELECT ct."app_label", p."codename"
# FROM "auth_permission" p, "auth_group_permissions" gp, "auth_user_groups" ug, "django_content_type" ct
# WHERE p."id" = gp."permission_id"
# AND gp."group_id" = ug."group_id"
# AND ct."id" = p."content_type_id"
# AND ug."user_id" = %s, [self.id])
qn = connection.ops.quote_name
sql = """
SELECT ct.%s, p.%s
FROM %s p, %s gp, %s ug, %s ct
WHERE p.%s = gp.%s
AND gp.%s = ug.%s
AND ct.%s = p.%s
AND ug.%s = %%s""" % (
qn('app_label'), qn('codename'),
qn('auth_permission'), qn('auth_group_permissions'),
qn('auth_user_groups'), qn('django_content_type'),
qn('id'), qn('permission_id'),
qn('group_id'), qn('group_id'),
qn('id'), qn('content_type_id'),
qn('user_id'),)
cursor.execute(sql, [user_obj.id])
user_obj._group_perm_cache = set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
return user_obj._group_perm_cache
def get_all_permissions(self, user_obj):
if not hasattr(user_obj, '_perm_cache'):
user_obj._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in user_obj.user_permissions.select_related()])
user_obj._perm_cache.update(self.get_group_permissions(user_obj))
return user_obj._perm_cache
def has_perm(self, user_obj, perm):
return perm in self.get_all_permissions(user_obj)
def has_module_perms(self, user_obj, app_label):
return bool(len([p for p in self.get_all_permissions(user_obj) if p[:p.index('.')] == app_label]))
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)

View File

@ -1,6 +1,7 @@
from django.contrib import auth
from django.core import validators
from django.core.exceptions import ImproperlyConfigured
from django.db import connection, models
from django.db import models
from django.db.models.manager import EmptyManager
from django.contrib.contenttypes.models import ContentType
from django.utils.encoding import smart_str
@ -210,64 +211,68 @@ class User(models.Model):
return self.password != UNUSABLE_PASSWORD
def get_group_permissions(self):
"Returns a list of permission strings that this user has through his/her groups."
if not hasattr(self, '_group_perm_cache'):
cursor = connection.cursor()
# The SQL below works out to the following, after DB quoting:
# cursor.execute("""
# SELECT ct."app_label", p."codename"
# FROM "auth_permission" p, "auth_group_permissions" gp, "auth_user_groups" ug, "django_content_type" ct
# WHERE p."id" = gp."permission_id"
# AND gp."group_id" = ug."group_id"
# AND ct."id" = p."content_type_id"
# AND ug."user_id" = %s, [self.id])
qn = connection.ops.quote_name
sql = """
SELECT ct.%s, p.%s
FROM %s p, %s gp, %s ug, %s ct
WHERE p.%s = gp.%s
AND gp.%s = ug.%s
AND ct.%s = p.%s
AND ug.%s = %%s""" % (
qn('app_label'), qn('codename'),
qn('auth_permission'), qn('auth_group_permissions'),
qn('auth_user_groups'), qn('django_content_type'),
qn('id'), qn('permission_id'),
qn('group_id'), qn('group_id'),
qn('id'), qn('content_type_id'),
qn('user_id'),)
cursor.execute(sql, [self.id])
self._group_perm_cache = set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
return self._group_perm_cache
"""
Returns a list of permission strings that this user has through
his/her groups. This method queries all available auth backends.
"""
permissions = set()
for backend in auth.get_backends():
if hasattr(backend, "get_group_permissions"):
permissions.update(backend.get_group_permissions(self))
return permissions
def get_all_permissions(self):
if not hasattr(self, '_perm_cache'):
self._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in self.user_permissions.select_related()])
self._perm_cache.update(self.get_group_permissions())
return self._perm_cache
permissions = set()
for backend in auth.get_backends():
if hasattr(backend, "get_all_permissions"):
permissions.update(backend.get_all_permissions(self))
return permissions
def has_perm(self, perm):
"Returns True if the user has the specified permission."
"""
Returns True if the user has the specified permission. This method
queries all available auth backends, but returns immediately if any
backend returns True. Thus, a user who has permission from a single
auth backend is assumed to have permission in general.
"""
# Inactive users have no permissions.
if not self.is_active:
return False
# Superusers have all permissions.
if self.is_superuser:
return True
return perm in self.get_all_permissions()
# Otherwise we need to check the backends.
for backend in auth.get_backends():
if hasattr(backend, "has_perm"):
if backend.has_perm(self, perm):
return True
return False
def has_perms(self, perm_list):
"Returns True if the user has each of the specified permissions."
"""Returns True if the user has each of the specified permissions."""
for perm in perm_list:
if not self.has_perm(perm):
return False
return True
def has_module_perms(self, app_label):
"Returns True if the user has any permissions in the given app label."
"""
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:
return True
return bool(len([p for p in self.get_all_permissions() if p[:p.index('.')] == app_label]))
for backend in auth.get_backends():
if hasattr(backend, "has_module_perms"):
if backend.has_module_perms(self, app_label):
return True
return False
def get_and_delete_messages(self):
messages = []
@ -300,7 +305,12 @@ class User(models.Model):
class Message(models.Model):
"""
The message system is a lightweight way to queue messages for given users. A message is associated with a User instance (so it is only applicable for registered users). There's no concept of expiration or timestamps. Messages are created by the Django admin after successful actions. For example, "The poll Foo was created successfully." is a message.
The message system is a lightweight way to queue messages for given
users. A message is associated with a User instance (so it is only
applicable for registered users). There's no concept of expiration or
timestamps. Messages are created by the Django admin after successful
actions. For example, "The poll Foo was created successfully." is a
message.
"""
user = models.ForeignKey(User)
message = models.TextField(_('message'))

View File

@ -1062,3 +1062,40 @@ object the first time a user authenticates::
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Handling authorization in custom backends
-----------------------------------------
Custom auth backends can provide their own permissions.
The user model will delegate permission lookup functions
(``get_group_permissions()``, ``get_all_permissions()``, ``has_perm()``, and
``has_module_perms()``) to any authentication backend that implements these
functions.
The permissions given to the user will be the superset of all permissions
returned by all backends. That is, Django grants a permission to a user that any
one backend grants.
The simple backend above could implement permissions for the magic admin fairly
simply::
class SettingsBackend:
# ...
def has_perm(self, user_obj, perm):
if user_obj.username == settings.ADMIN_LOGIN:
return True
else:
return False
This gives full permissions to user granted access in the above example. Notice
that the backend auth functions all take the user object as an argument, and
also accept the same arguments given to the associated ``User`` functions.
A full authorization implementation can be found in
``django/contrib/auth/backends.py`` _, which is the default backend and queries
the ``auth_permission``-table most of the time.
.. _django/contrib/auth/backends.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/backends.py

View File

@ -0,0 +1,66 @@
"""
>>> from django.contrib.auth.models import User, Group, Permission
>>> from django.contrib.contenttypes.models import ContentType
# No Permissions assigned yet, should return False except for superuser
>>> user = User.objects.create_user('test', 'test@example.com', 'test')
>>> user.has_perm("auth.test")
False
>>> user.is_staff=True
>>> user.save()
>>> user.has_perm("auth.test")
False
>>> user.is_superuser=True
>>> user.save()
>>> user.has_perm("auth.test")
True
>>> user.is_staff = False
>>> user.is_superuser = False
>>> user.save()
>>> user.has_perm("auth.test")
False
>>> content_type=ContentType.objects.get_for_model(Group)
>>> perm = Permission.objects.create(name="test", content_type=content_type, codename="test")
>>> user.user_permissions.add(perm)
>>> user.save()
# reloading user to purge the _perm_cache
>>> user = User.objects.get(username="test")
>>> user.get_all_permissions()
set([u'auth.test'])
>>> user.get_group_permissions()
set([])
>>> user.has_module_perms("Group")
False
>>> user.has_module_perms("auth")
True
>>> perm = Permission.objects.create(name="test2", content_type=content_type, codename="test2")
>>> user.user_permissions.add(perm)
>>> user.save()
>>> perm = Permission.objects.create(name="test3", content_type=content_type, codename="test3")
>>> user.user_permissions.add(perm)
>>> user.save()
>>> user = User.objects.get(username="test")
>>> user.get_all_permissions()
set([u'auth.test2', u'auth.test', u'auth.test3'])
>>> user.has_perm('test')
False
>>> user.has_perm('auth.test')
True
>>> user.has_perms(['auth.test2', 'auth.test3'])
True
>>> perm = Permission.objects.create(name="test_group", content_type=content_type, codename="test_group")
>>> group = Group.objects.create(name='test_group')
>>> group.permissions.add(perm)
>>> group.save()
>>> user.groups.add(group)
>>> user = User.objects.get(username="test")
>>> user.get_all_permissions()
set([u'auth.test2', u'auth.test', u'auth.test3', u'auth.test_group'])
>>> user.get_group_permissions()
set([u'auth.test_group'])
>>> user.has_perms(['auth.test3', 'auth.test_group'])
True
"""