3

I want to have easy way to check if somebody is owner or admin of post, proposal and so on he's trying to edit \ delete.

So, every time I use IsAuthenticated permission and in the method of ModelViewSet I get instance and check if instance.author or sometimes instance.owner is the user who requested it (request.user == instance.owner on some objects it's request.user == instance.author).

Question

The main question is: how can I create permission class that can check this kind of ownership with dynamic user attribute name on instance?

One of mine solutions (not the best, i think)

I've created function that take user attribute instance name returns permission class:

def is_owner_or_admin_permission_factory(owner_prop_name):
    class IsOwnerOrAdmin(BasePermission):
        def has_permission(self, request, view, *args, **kwargs):
            instance = view.get_object()
            try:
                owner = getattr(instance, owner_prop_name)
            except AttributeError:
                return False
            return (
                request.user and request.user.id and (owner == request.user or request.user.is_staff)
            )

    return IsOwnerOrAdmin
Dionid
  • 97
  • 8

2 Answers2

2

I have also been frustrated over the same exact problem for days, and I've managed to find a suitable work-around (at least for me, of course), when dealing with multiple models with different lookup names for user attributes.

The work-around was something like this, in the ModelViewSet defined a separate attribute user_lookup_kwarg in the view, which could be used for checking the appropriate permissions.

Eg,

class YourViewSet(viewsets.ModelViewSet):
    queryset = YourModel.objects.all()
    serializer_class = YourSerializer
    user_lookup_kwarg = 'user' #or 'account/created_by' whatever.

Now, your permission_class would be somewhat like this,

class CustomPermission(BasePermission):

    def has_object_permission(self, request, view, obj):
        try:
            return request.user.is_superuser or getattr(obj, view.user_lookup_kwarg) == request.user
        except:
            return False
        return request.user.is_superuser

You only just need to override has_object_permission() method to check instance level permissions.

zaidfazil
  • 9,017
  • 2
  • 24
  • 47
  • One moment: when you check `request.user.is_superuser` and its returns `True`, it will be returned from method, if it's `False` and the second argument raise exception you can just return `False`, because `request.user.is_superuser` must already be `False`. – Dionid Jul 29 '17 at 08:54
  • Also, I think its better to check that the user is authorized, because (in most cases) `AnonymousUser` can't be owner of object, so he needs to be authorized. – Dionid Jul 29 '17 at 08:57
  • You said `IsAuthenticated` is your default permission, then you only just need to add that in your permission class. Donot try to push everything into single class, divide it and reuse. – zaidfazil Jul 29 '17 at 10:17
  • For your former question, if `request.user.is_superuser` return `False`, then if you define `user_lookup_kwarg`, the exception won't be raised, but the result of expression would be returned. – zaidfazil Jul 29 '17 at 12:12
0

For people who found this question my variant:

class IsInGroup:
    """
    Usage:

    class MyView(ReadOnlyModelViewSet):
        permission_classes = (IsInGroup('Admins'),)
    """

    def __new__(cls, group_name):
        return type(
            f'IsInGroup{group_name}',
            (IsAuthenticated,),
            {
                'group_name': group_name,
                **{key: value for key, value in cls.__dict__.items() if not key.startswith('__')},
            },
        )

    group_name: str

    def has_permission(self, request, view):
        return request.user.groups.filter(name=self.group_name).exists()

AHTOH007
  • 1
  • 3