17

I'm developing a Django Rest Framework backend for a mobile app. The API is private and will only ever be used internally.

The browsable API is convenient for helping developers working on the project but I would like to prevent anyone who's not set as an admin on the project from using the browsable interface.

I realize that the browsable admin doesn't grant any permissions that user wouldn't otherwise have, but it does have some security gray areas (e.g. for models with a foreign key relationship, the HTML selector field gets populated with all the possible related objects in the DB unless you specifically instruct it not to).

Because this app handles sensitive user data, I'd prefer to expose the smallest surface area possible to the public to reduce the risk of my own potential mistakes oversights.

Is there any way to disable the browsable API for non-admin users without disabling it for everyone? I've done a fair amount of Google searching and looked on SO and haven't found an answer. This question is close How to disable admin-style browsable interface of django-rest-framework? but not the same because those instructions disable the interface for everyone.

Community
  • 1
  • 1
rogueleaderr
  • 4,671
  • 2
  • 33
  • 40
  • How do you add html selector fields to the browsable API? It's only text-entry html forms. – Mark Galloway Jul 20 '15 at 00:53
  • But, if you really think you need to preform this disabling, you will most likely have to subclass the BrowsableApiRenderer, find a nice place to hook in and check request.user for their admin status and then render nothing. I think this is a bad solution, though. https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/renderers.py#L367 – Mark Galloway Jul 20 '15 at 00:59
  • 3
    I'd _highly_ recommend instead removing the browsable API in production (only adding it as a default renderer when `DEBUG` is `True`). Also note that if your dropdowns expose too much information, it's possible for clever people (read: other bored developers) too link up objects and expose that data anyway. – Kevin Brown-Silva Jul 20 '15 at 01:08
  • 1
    @MarkGalloway if you set a serializer field as PrimaryKeyRelated or the like, the browsable API will throw in an HTML select dropdown with ALL the possible foreign keys (e.g. all the users in your system if you have a "users" foreign key field). That surprised me and lead to me worrying about information leaking in the browsable API. – rogueleaderr Jul 20 '15 at 05:15
  • That's pretty cool. I always declare explicit related serializers, so I guess that's why I've never seen it. – Mark Galloway Jul 20 '15 at 05:16

3 Answers3

11

Is `DEFAULT_PERMISSION_CLASSES' setting not enough? This sets a default restriction on all views DRF docs on default permission classes

In settings.py:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAdminUser',
    ]
}

They will 'reach' the browsable interface but all types of requests will be denied if not authorized.

If for some reason various end-points needed to be reached by non-admin users, you could loosen the restriction on a view-by-view basis.

rkengler
  • 416
  • 5
  • 6
8

Assuming you're using DRF's built in views, I think you can just override get_renderers().

In your settings file:

REST_FRAMEWORK = {
    # Only enable JSON renderer by default.
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ],
}

And then in your views.py:

from rest_framework import generics, renderers

class StaffBrowsableMixin(object):
    def get_renderers(self):
        """
        Add Browsable API renderer if user is staff.
        """
        rends = self.renderer_classes
        if self.request.user and self.request.user.is_staff:
            rends.append(renderers.BrowsableAPIRenderer)
        return [renderer() for renderer in rends]

class CustomListApiView(StaffBrowsableMixin, generics.ListAPIView):
    """
    List view.
    """
    # normal stuff here
getup8
  • 6,949
  • 1
  • 27
  • 31
  • thanks! helped me fix my issue https://stackoverflow.com/q/58884611/420953. you are welcome to respond the same answer to it. how would you unit test that BrowsableAPIRenderer is used? – udo Nov 16 '19 at 12:08
  • Glad it helped @udo ! Unit tests are not my expertise.. might be best to just ask another question :) – getup8 Nov 16 '19 at 19:04
0

In rest_framework views we have a attribute called renderes_classes Usually we have a method get_<something> as we do with queryset/get_queryset but in this case we didn't have that, so i needed to implement a property.

from tasks.models import Task
from tasks.serializers import TaskSerializer

from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.renderers import CoreJSONRenderer


class CustomRendererView:
    permission_classes = (IsAuthenticatedOrReadOnly,)

    @property
    def renderer_classes(self):
        renderers = super(ListTask, self).renderer_classes

        if not self.request.user.is_staff:
            renderers = [CoreJSONRenderer]

        return renderers


class ListTask(CustomRendererView, ListAPIView):
    queryset = Task.objects.all()
    serializer_class = FullTaskSerializer
Luan Fonseca
  • 1,467
  • 1
  • 13
  • 22