Permission denied in Browsable API when logged-in user has GET permission but not POST permission

38 views
Skip to first unread message

Kevin Partington

unread,
Nov 14, 2020, 3:29:06 PM11/14/20
to Django REST framework
I've created a special permission class which checks that the current user is either accessing the resource readonly, or is an admin user, or is a specific user according to a different property on the object.

When I try to view the resource on the browsable API, I get a permission denied error. Strangely, when I looked at request.method in the permission class at the time of the exception, I was seeing POST rather than GET. I debugged using pdb and saw that the view was basically being called twice-- once with request.method == GET, once with request.method == POST. The second permission check throws an exception which breaks the page.

According to the pdb listings, the reason this happens is the response template needs to be able to render a POST, PUT, and PATCH form. It seems it might be calling the request again in order to see what the resource "shape" is, and/or to double check permissions. But it seems strange that the exception is allowed to go all the way through rather than being caught in that template (and maybe choosing not to render the form for users who have GET permission but not POST permission).

I have to assume this isn't the intentional design, and I might be accidentally tripping something up with some of the renderers etc. I am using. I am using DRF and also the Django REST Framework JSON API library.

My views:

class JamSessionRelationshipView(RelationshipView):
    queryset = JamSession.objects.all()

class JamSessionMembersRelationshipView(JamSessionRelationshipView):
    permission_classes = [IsConductorOrAdminOrReadOnly]

My permission class:

class IsConductorOrAdminOrReadOnly(permissions.BasePermission):
    message = "User must be an admin or conductor to modify the jam session members."

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True

        if request.user.is_staff:
            return True

        return request.user == obj.conductor

My DRF settings:

REST_FRAMEWORK = {
    'PAGE_SIZE': 10,
    'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler',
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ],
    'DEFAULT_PAGINATION_CLASS':
        'rest_framework_json_api.pagination.JsonApiLimitOffsetPagination',
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework_json_api.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ),
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework_json_api.renderers.JSONRenderer',
        # If you're performance testing, you will want to use the browseable API
        # without forms, as the forms can generate their own queries.
        # If performance testing, enable:
        # 'example.utils.BrowsableAPIRendererWithoutForms',
        # Otherwise, to play around with the browseable API, enable:
        'rest_framework.renderers.BrowsableAPIRenderer'
    ),
    'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
    'DEFAULT_FILTER_BACKENDS': (
        'rest_framework_json_api.filters.QueryParameterValidationFilter',
        'rest_framework_json_api.filters.OrderingFilter',
        #'rest_framework_json_api.django_filters.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
    ),
    'SEARCH_PARAM': 'filter[search]',
    'TEST_REQUEST_RENDERER_CLASSES': (
        'rest_framework_json_api.renderers.JSONRenderer',
    ),
    'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json',
}


Please let me know if I can provide any other helpful information.

Thanks,
Kevin

Kevin Partington

unread,
Nov 14, 2020, 3:59:19 PM11/14/20
to Django REST framework
I figured it out: It's a bug in Django REST Framework JSON API. I found a workaround by specifying a resource name on my view, which avoids the call to self.get_object() and in turn avoids the permission check.

Thanks,
Kevin
Reply all
Reply to author
Forward
0 new messages