5

in my case I am using Django REST Framework (DRF) as internal api. it is not intended to be consumed by regular users. therefore I would like to disable it for regular users.

an admin (is_staff=True) should be able to access it and see it:
https://restframework.herokuapp.com/ enter image description here

a non staff user (is_staff=False) should just get the JSON response of a GET request like:
https://restframework.herokuapp.com/?format=json enter image description here he should not(!) see the browsable api. this applies for the root view and all endpoints.

to configure this, I applied the following:

# settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.SessionAuthentication'],
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
}

my endpoints are the following (to keep the example simple I just show 1):

# api/urls.py
from django.urls import include, path

from rest_framework import routers

from . import views

app_name = 'api'

router = routers.DefaultRouter()  # browseable api
router.register('segments', views.SegmentViewSet)
# there are a lot more...

urlpatterns = [
    path('', include(router.urls)),
]

based on answer https://stackoverflow.com/a/58894198/420953 my settings.py looks like this:

# settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.SessionAuthentication'],
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
    # enable JSON renderer by default
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ],
}

and my api/views.py:

# api/views.py

from django_filters import rest_framework as drf_filters
from rest_framework import filters, renderers, viewsets

from . import serializers
from segment.models import Segment

class StaffBrowsableAPIMixin:
    def get_renderers(self):
        """
        add BrowsableAPIRenderer if user is staff (regular users see JSONRenderer response)
        """
        # explicitly set renderer to JSONRenderer (the default for non staff users)
        rends = [renderers.JSONRenderer]
        if self.request.user.is_staff:
            # staff users see browsable API
            rends.append(renderers.BrowsableAPIRenderer)
        return [renderer() for renderer in rends]

class SegmentViewSet(StaffBrowsableAPIMixin, viewsets.ReadOnlyModelViewSet):
    queryset = Segment.objects.all()
    serializer_class = serializers.SegmentSerializer

this works fine for all endpoints (when a regular user calls the endpoint via GET, they only see the JSON, not the browsable API). Unfortunately it does not work for APIRootView (the root view of the api, e.g. https://restframework.herokuapp.com/).

how to get this to work for APIRootView as well?

Braiam
  • 1
  • 11
  • 47
  • 78
udo
  • 4,832
  • 4
  • 54
  • 82
  • What result you're getting? – Aashay Amballi Nov 16 '19 at 05:36
  • Could you please check the user you're logging in has the attribute is_staff set to true or false? – Aashay Amballi Nov 16 '19 at 05:44
  • regular users (`is_staff=false`) can still navigate to the browsable API (which I would like to prevent). – udo Nov 16 '19 at 07:07
  • And in the browsable api check are you logged in as a normal user or an admin user – Aashay Amballi Nov 16 '19 at 07:14
  • I am using a regular user (and would not like to see the browsable API). my understanding is that `APIRootView` is the default basic root view for DefaultRouter (which you have to use for browsable API) -> https://github.com/encode/django-rest-framework/blob/master/rest_framework/routers.py#L291 – udo Nov 16 '19 at 07:27
  • 1
    Check this post https://stackoverflow.com/questions/31507211/how-to-restrict-django-rest-framework-browsable-api-interface-to-admin-users – Aashay Amballi Nov 16 '19 at 07:37
  • 1
    thank you Aashay! exactly what I was looking for. missed this post. – udo Nov 16 '19 at 08:42

1 Answers1

6

I believe you can lock the base URL of your API down pretty simply (the mixin should probably be moved to another file but just kept everything together for clarity):

# api/urls.py
from django.urls import include, path

from rest_framework import permissions, renderers, routers

from . import views

app_name = 'api'

class StaffBrowsableAPIMixin:
    def get_renderers(self):
        """
        add BrowsableAPIRenderer if user is staff (regular users see JSONRenderer response)
        """
        # explicitly set renderer to JSONRenderer (the default for non staff users)
        rends = [renderers.JSONRenderer]
        if self.request.user.is_staff:
            # staff users see browsable API
            rends.append(renderers.BrowsableAPIRenderer)
        return [renderer() for renderer in rends]


class CustomAPIRootView(StaffBrowsableAPIMixin, routers.APIRootView):
    permission_classes = (permissions.IsAdminUser,)


class CustomDefaultRouter(routers.DefaultRouter):
    APIRootView = CustomAPIRootView

router = CustomDefaultRouter()  # browseable api
router.register('segments', views.SegmentViewSet)
# there are a lot more...

urlpatterns = [
    path('', include(router.urls)),
]

The permission_classes will handle not showing any of your endpoints to non-Admin users but the Browsable API template will still be shown. To remove that as well, you need to change the renderer using the StaffBrowsableAPIMixin.


Original Answer

One way to do this is using DRF's renderer settings and methods.

In your settings.py:

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

And 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

Basically, use StaffBrowsableMixin for any APIView you want the BrowsableAPI to be enabled for staff.

Similar question, as linked above in comments, and my answer there as well: https://stackoverflow.com/a/58762483/4599228

Braiam
  • 1
  • 11
  • 47
  • 78
getup8
  • 6,949
  • 1
  • 27
  • 31
  • do you also know how how to apply this to APIRootView? – udo Nov 16 '19 at 21:09
  • @udo can you update your question to include your `urls.py` and how you tried to use your custom `APIRootView` you wrote above? I can then update my answer to (hopefully) suit your needs. – getup8 Nov 17 '19 at 05:04
  • I rephrased the question. hopefully it is clearer now. – udo Nov 17 '19 at 08:07
  • :) wrong understanding regarding your last edit. no worries. to clarify: all(!) users have permission to access the API. it's just the **browsable API** which has to be restricted to staff users. – udo Nov 17 '19 at 08:46
  • @udo yes, realized after posting so deleted by comment :) I re-edited, check it out and let me know if that works. – getup8 Nov 17 '19 at 08:50