From 2f163ffe9aca6a9527163435f4514d79908aa456 Mon Sep 17 00:00:00 2001 From: Joseph Kocherhans Date: Mon, 6 Nov 2006 21:25:29 +0000 Subject: [PATCH] Applied patch from http://code.djangoproject.org/wiki/GenericAuthorization things are working now, but ugly. git-svn-id: http://code.djangoproject.com/svn/django/branches/generic-auth@4025 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../admin/templatetags/adminapplist.py | 9 ++-- django/contrib/admin/views/main.py | 51 ++++++++++--------- django/contrib/auth/__init__.py | 29 +++++++++++ django/db/models/options.py | 18 +++++-- .../regressiontests/authorization/__init__.py | 0 tests/regressiontests/authorization/models.py | 50 ------------------ 6 files changed, 78 insertions(+), 79 deletions(-) delete mode 100644 tests/regressiontests/authorization/__init__.py delete mode 100644 tests/regressiontests/authorization/models.py diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py index 10e09ca0b6..ed9038f83a 100644 --- a/django/contrib/admin/templatetags/adminapplist.py +++ b/django/contrib/admin/templatetags/adminapplist.py @@ -1,5 +1,7 @@ from django import template from django.db.models import get_models +from django.contrib.auth import has_permission +from django.contrib.auth.models import Permission register = template.Library() @@ -26,10 +28,11 @@ class AdminApplistNode(template.Node): model_list = [] for m in app_models: if m._meta.admin: + opts = m._meta perms = { - 'add': user.has_perm("%s.%s" % (app_label, m._meta.get_add_permission())), - 'change': user.has_perm("%s.%s" % (app_label, m._meta.get_change_permission())), - 'delete': user.has_perm("%s.%s" % (app_label, m._meta.get_delete_permission())), + 'add': has_permission(user, opts.get_add_permission()), + 'change': has_permission(user, opts.get_change_permission()), + 'delete': has_permission(user, opts.get_delete_permission()), } # Check whether user has any perm for this module. diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 705dfad6c8..ec2a20c82b 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -1,5 +1,6 @@ from django import forms, template from django.conf import settings +from django.contrib.auth import has_permission from django.contrib.admin.filterspecs import FilterSpec from django.contrib.admin.views.decorators import staff_member_required from django.views.decorators.cache import never_cache @@ -199,8 +200,8 @@ def render_change_form(model, manipulator, context, add=False, change=False, for extra_context = { 'add': add, 'change': change, - 'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()], - 'has_change_permission': context['perms'][app_label][opts.get_change_permission()], + 'has_delete_permission': context['perms'][app_label][opts.get_delete_permission().codename], + 'has_change_permission': context['perms'][app_label][opts.get_change_permission().codename], 'has_file_field': opts.has_field_type(models.FileField), 'has_absolute_url': hasattr(model, 'get_absolute_url'), 'auto_populated_fields': auto_populated_fields, @@ -229,17 +230,9 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po raise Http404, "App %r, model %r, not found" % (app_label, model_name) opts = model._meta - if not request.user.has_perm(app_label + '.' + opts.get_add_permission()): + if not has_permission(request.user, opts.get_add_permission()): raise PermissionDenied - if post_url is None: - if request.user.has_perm(app_label + '.' + opts.get_change_permission()): - # redirect to list view - post_url = '../' - else: - # Object list will give 'Permission Denied', so go back to admin home - post_url = '../../../' - manipulator = model.AddManipulator() if request.POST: new_data = request.POST.copy() @@ -255,6 +248,18 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po pk_value = new_object._get_pk_val() LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), ADDITION) msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object} + + if post_url is None: + # We want to call has permission WITHOUT passing it the new + # object here. We're concerned with whether the user can edit + # ANY instances of this model, not just the one we created. + if has_permission(request.user, opts.get_change_permission()): + # redirect to list view + post_url = '../' + else: + # Object list will give 'Permission Denied', so go back to admin home + post_url = '../../../' + # Here, we distinguish between different save types by checking for # the presence of keys in request.POST. if request.POST.has_key("_continue"): @@ -303,9 +308,6 @@ def change_stage(request, app_label, model_name, object_id): raise Http404, "App %r, model %r, not found" % (app_label, model_name) opts = model._meta - if not request.user.has_perm(app_label + '.' + opts.get_change_permission()): - raise PermissionDenied - if request.POST and request.POST.has_key("_saveasnew"): return add_stage(request, app_label, model_name, form_url='../../add/') @@ -314,6 +316,9 @@ def change_stage(request, app_label, model_name, object_id): except ObjectDoesNotExist: raise Http404 + if not has_permission(request.user, opts.get_change_permission(), manipulator.original_object): + raise PermissionDenied + if request.POST: new_data = request.POST.copy() @@ -419,8 +424,7 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current pass else: if related.opts.admin: - p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) - if not user.has_perm(p): + if not has_permission(user, related.opts.get_delete_permission(), related): perms_needed.add(related.opts.verbose_name) # We don't care about populating deleted_objects now. continue @@ -450,8 +454,7 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current # If there were related objects, and the user doesn't have # permission to delete them, add the missing perm to perms_needed. if related.opts.admin and has_related_objs: - p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) - if not user.has_perm(p): + if not has_permission(user, related.opts.get_delete_permission(), related): perms_needed.add(rel_opts_name) for related in opts.get_all_related_many_to_many_objects(): if related.opts in opts_seen: @@ -479,8 +482,7 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current # If there were related objects, and the user doesn't have # permission to change them, add the missing perm to perms_needed. if related.opts.admin and has_related_objs: - p = '%s.%s' % (related.opts.app_label, related.opts.get_change_permission()) - if not user.has_perm(p): + if not has_permission(user, related.opts.get_delete_permission(), related): perms_needed.add(related.opts.verbose_name) def delete_stage(request, app_label, model_name, object_id): @@ -490,9 +492,9 @@ def delete_stage(request, app_label, model_name, object_id): if model is None: raise Http404, "App %r, model %r, not found" % (app_label, model_name) opts = model._meta - if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()): - raise PermissionDenied obj = get_object_or_404(model, pk=object_id) + if not has_permission(request.user, opts.get_delete_permission(), obj): + raise PermissionDenied # Populate deleted_objects, a data structure of all related objects that # will also be deleted. @@ -730,7 +732,10 @@ def change_list(request, app_label, model_name): model = models.get_model(app_label, model_name) if model is None: raise Http404, "App %r, model %r, not found" % (app_label, model_name) - if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()): + # There isn't a specific object to check here, so don't pass one to + # has_permission. There should be a has_permission implementation + # registered that knows when the obj arg is missing. + if not has_permission(request.user, model._meta.get_change_permission()): raise PermissionDenied try: cl = ChangeList(request, model) diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index a0097a01ed..ca5f1f12bc 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -5,6 +5,35 @@ BACKEND_SESSION_KEY = '_auth_user_backend' LOGIN_URL = '/accounts/login/' REDIRECT_FIELD_NAME = 'next' +def default_has_permission(user, permission, obj): + p_name = "%s.%s" % (permission.content_type.app_label, permission.codename) + return user.has_perm(p_name) + +class HasPermission(object): + """ + Function that supports multiple implementations via a type registry. The + implemetation called depends on the argument types. + """ + def __init__(self): + self.registry = {} + + def __call__(self, user, permission, obj=None): + # TODO: this isn't very robust. Only matches on exact types. Support + # for matching subclasses and caching registry hits would be helpful, + # but we'll add that later + types = (type(user), type(permission), type(obj)) + func = self.registry.get(types) + if func is not None: + return func(user, permission, obj) + else: + return default_has_permission(user, permission, obj) + + def register(self, func, user_type, permission_type, obj_type=type(None)): + types = (user_type, permission_type, obj_type) + self.registry[types] = func + +has_permission = HasPermission() + def load_backend(path): i = path.rfind('.') module, attr = path[:i], path[i+1:] diff --git a/django/db/models/options.py b/django/db/models/options.py index ff0d112d16..4bf5b42e55 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -106,13 +106,25 @@ class Options(object): return 'ORDER BY ' + orderlist2sql(self.ordering, self, pre) def get_add_permission(self): - return 'add_%s' % self.object_name.lower() + from django.contrib.auth.models import Permission + codename = 'add_%s' % self.object_name.lower() + return Permission.objects.get( + content_type__app_label__exact=self.app_label, + codename=codename) def get_change_permission(self): - return 'change_%s' % self.object_name.lower() + from django.contrib.auth.models import Permission + codename = 'change_%s' % self.object_name.lower() + return Permission.objects.get( + content_type__app_label__exact=self.app_label, + codename=codename) def get_delete_permission(self): - return 'delete_%s' % self.object_name.lower() + from django.contrib.auth.models import Permission + codename = 'delete_%s' % self.object_name.lower() + return Permission.objects.get( + content_type__app_label__exact=self.app_label, + codename=codename) def get_all_related_objects(self): try: # Try the cache first. diff --git a/tests/regressiontests/authorization/__init__.py b/tests/regressiontests/authorization/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/regressiontests/authorization/models.py b/tests/regressiontests/authorization/models.py deleted file mode 100644 index 8a54b45f99..0000000000 --- a/tests/regressiontests/authorization/models.py +++ /dev/null @@ -1,50 +0,0 @@ -from django.db import models - -class TestModel(models.Model): - name = models.CharField(maxlength=255) - - class Admin: - pass - -API_TESTS = """ -# Let's create a default implementation of has_permission. For now, It should -# just call user.has_permission(permission) for the given django.contrib.auth.models.User. -# Eventually the user.has_permission implementation should be extracted here. ->>> from django.contrib.auth import has_permission ->>> def user_has_permission(user, permission, object=None): -... return user.has_perm(permission) - -# Then let's register that function to be called when we get an instance of -# django.contrib.auth.models.User and a string as the permission. We use str -# as the permission type for convenience. It would be annoying to grab the -# actual Permission object instead of just using the codename. This feels kind -# of limiting, but can be revisited later. ->>> from django.contrib.auth.models import User ->>> has_permission.register(User, str, TestModel, user_has_permission) - -# Now make sure it works. ->>> admin = User(username='admin', password='test', email='test@example.com', is_superuser=True) ->>> admin.save() ->>> has_permission(admin, 'testmodel.add', TestModel()) -True - -# Now let's create an implemetation for AnonymousUsers... it should always -# return False. ->>> def anon_has_permission(user, permission, object=None): -... return False - -# Register it like before, but for AnonymousUser rather than User. ->>> from django.contrib.auth.models import AnonymousUser ->>> has_permission.register(AnonymousUser, str, TestModel, anon_has_permission) - -# And make sure it works. ->>> anonymous = AnonymousUser() ->>> has_permission(anonymous, 'testmodel.add', TestModel()) -False - -# Let's double check that the function we registered for User still works (we're -# not just replacing the implementation of has_permission) ->>> has_permission(admin, 'testmodel.add', TestModel()) -True - -"""