1

I am trying to add object level permission to my django REST project using django-guardian, but I am getting

http://127.0.0.1:8000/api/v1/tasks/

HTTP 403 Forbidden Allow: GET, POST, HEAD, OPTIONS Content-Type: application/json Vary: Accept

{ "detail": "You do not have permission to perform this action." }

The user joe is logged in.

settings.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',

    'guardian',
    'rest_framework',
    'rest_framework.authtoken',
    'rest_auth',

    'task.apps.TaskConfig',
]

models.py:

class Task(models.Model):
    summary = models.CharField(max_length=32)
    content = models.TextField()
    reported_by = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        permissions = (
            ('view_task', 'View task'),
        )

serializers.py:

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = '__all__'

permissions.py:

class CustomObjectPermissions(permissions.DjangoObjectPermissions):
    perms_map = {
        'GET': ['%(app_label)s.view_%(model_name)s'],
        'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
        'HEAD': ['%(app_label)s.view_%(model_name)s'],
        'POST': ['%(app_label)s.add_%(model_name)s'],
        'PUT': ['%(app_label)s.change_%(model_name)s'],
        'PATCH': ['%(app_label)s.change_%(model_name)s'],
        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
    }

filters.py:

class DjangoObjectPermissionsFilter(BaseFilterBackend):
    perm_format = '%(app_label)s.view_%(model_name)s'
    shortcut_kwargs = {
        'accept_global_perms': False,
    }

    def __init__(self):
        assert 'guardian' in settings.INSTALLED_APPS, (
            'Using DjangoObjectPermissionsFilter, '
            'but django-guardian is not installed.')

    def filter_queryset(self, request, queryset, view):
        from guardian.shortcuts import get_objects_for_user

        user = request.user
        permission = self.perm_format % {
            'app_label': queryset.model._meta.app_label,
            'model_name': queryset.model._meta.model_name,
        }

        return get_objects_for_user(
            user, permission, queryset,
            **self.shortcut_kwargs)

views.py:

class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    permission_classes = (CustomObjectPermissions,)
    filter_backends = (DjangoObjectPermissionsFilter,)

urls.py:

router = DefaultRouter()

router.register('tasks', TaskViewSet, base_name='tasks')

urlpatterns = router.urls

But it works fine in shell

> python manage.py shell -i ipython
In [1]: from django.contrib.auth.models import User

In [2]: joe = User.objects.all().filter(username="joe")[0]

In [3]: import task.models as task_models

In [4]: task = task_models.Task.objects.all()[0]

In [5]: joe.has_perm('view_task', task)
Out[5]: True
Risadinha
  • 16,058
  • 2
  • 88
  • 91

1 Answers1

4

The API first checks model-level permissions, then object-level permissions if they apply. Since the custom permissions class requires the user to have read-permissions, you need to ensure that Joe has been assigned model-level read access. If you check joe.has_perm('tasks.view_task'), I would bet that it returns False. To fix this, you either need to directly assign his user the permission, or add him to a group that has been assigned the appropriate permissions.

Also, note that Django 2.1 recently added the "view" permission, and it shouldn't be necessary to add it to your models anymore.

Sherpa
  • 1,948
  • 13
  • 25