4

I am using django-guardian to implement per-object permissions in my Django project. I've run into an issue where registered users are unable to view objects that anonymous users are able to view. I had thought that if the anonymous user has a permission, then a registered user should have the same permission (I can't imagine a part of my website where I would want anonymous users to be able to do something and registered users not be able to do something).

from core.models import MyObject
from django.contrib.auth.models import User
from guardian.shortcuts import 
from guardian.utils import get_anonymous_user
m = MyObject.objects.get(id=1)
u = User.objects.get(username="MyUser")
anon = get_anonymous_user()
anon.has_perm('view_object', m)
# ^ Prints True
u.has_perm('view_object', m)
# ^ Prints False

In my project, I have some objects that can be "public" or "private". When a user marks the object as "Public", I grant the "view_object" permission to the anonymous user. My view's are protected using the PermissionRequiredMixin, like so:

class MyObjectDetailsView(PermissionRequiredMixin, DetailView):
    model = MyObject
    permission_required = 'view_object'

Does Django Guardian provide some way of giving registered users the same permissions as anonymous users? Or maybe there is some way to subclass the PermissionRequiredMixin to allow the action if the user doesn't have the permission but the anonymous user does?

Joseph
  • 12,678
  • 19
  • 76
  • 115

1 Answers1

3

I've come up with a workaround by subclassing PermissionRequiredMixin like so:

from guardian.mixins import PermissionRequiredMixin
from guardian.utils import get_anonymous_user


class PermissionRequiredMixinWithAnonymous(PermissionRequiredMixin):
    def check_permissions(self, request):
        forbidden = super(PermissionRequiredMixinWithAnonymous, self).check_permissions(request)
        if forbidden:
            perms = self.get_required_permissions(request)
            anon = get_anonymous_user()
            obj = self.get_permission_object()
            has_permissions = all(anon.has_perm(perm, obj) for perm in perms)
            if has_permissions:
                forbidden = None
        return forbidden

If the permission check fails for the logged in user (this is the call to super()), then the check is basically re-run for the anonymous user. I based the code in the if forbidden: block on PermissionRequiredMixin.check_permissions and guardian.utils.get_403_or_None (the latter of which is called from the former). I had to do it this way because both check_permissions and get_403_or_None use request.user.

Joseph
  • 12,678
  • 19
  • 76
  • 115
  • 1
    Wouldn't this fail in the case where permissions p1 and p2 are required, with user having p1 and anonymous having p2 but not vice versa? I.e. shouldn't it be `all(anon.has_perm(...) or self.has_perm(...) for perm in perms)`? – Tim Diels Jan 03 '19 at 09:30