django-rest-framework: globally restrict requests to GET? - python

I am using django-rest-framework and I would like to to allow only GET requests to my API.
Is there some global setting for this?
Currently I'm decorating individual views, like this:
#api_view(['GET'])
def my_api_view(request, format=None):
# get data

You can write a custom Permission class IsRequestMethodGet which will grant access to only GET requests.
To implement the custom permission IsRequestMethodGet, override the BasePermission class and implement .has_permission(self, request, view) method. The method should return True if the request should be granted access, and False otherwise.
from rest_framework import permissions
class IsRequestMethodGet(permissions.BasePermission):
"""
The request is a GET request.
"""
def has_permission(self, request, view):
return request.method == 'GET' # Returns True if GET request
So, a request will be granted access if the request method is GET, otherwise not.
Now, you need to include this custom permission class in your global settings.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'my_app.permissions.IsRequestMethodGet',
)
}

Related

Override the IsAuthenticated permission class with one that checks firebase

I am using firebase(pyrebase library) for my authentication with a django backend and a react frontend.For this to work I had to override the DRF auth class TokenAuthentication with my FirebaseAuthentication. But I still get 401 unauthorised when I try to access a view since I also need to override the drf permission class isAuthenticated.But I have been searching for a way to do this with python without success.Any help would be appreciated. Below is a snippet of the permission class and where its applied on my views
DRF permissions.py
class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
views.py
class FinanceTransactionList(GenericAPIView):
authentication_classes = [FirebaseAuthentication]
permission_classes = [IsAuthenticated]
#classmethod
#encryption_check
def post(self, request, *args, **kwargs):
...
To implement custom permission, override BasePermission and implement either, or both, of the following methods:
.has_permission(self, request, view)
.has_object_permission(self, request, view, obj)
The methods should return True if the request should be granted access, and False otherwise.
If you need to test if a request is a read operation or a write operation, you should check the request method against the constant SAFE_METHODS, which is a tuple containing 'GET', 'OPTIONS', and 'HEAD'. For example:
if request.method in permissions.SAFE_METHODS:
# Check permissions for the read-only request
else:
# Check permissions for writing request
Custom permissions will raise a PermissionDenied exception if the test fails. To change the error message associated with the exception, implement a message attribute directly on your custom permission. Otherwise, the default_detail attribute from PermissionDenied will be used. Similarly, to change the code identifier associated with the exception, implement a code attribute directly on your custom permission - otherwise, the default_code attribute from PermissionDenied will be used.
from rest_framework import permissions
class CustomerAccessPermission(permissions.BasePermission):
message = 'Firebase Auth Required.'
def has_permission(self, request, view):
...

How to use has_object_permission to check if a user can access an object in function based views

I have a note object which can be accessd from notes/{pk}. If the method is GET or a read only method I was to allow anyone access to the note as long as the note is public (note.is_private = False)
I've implemented this as:
#api_view(['GET', 'DELETE', 'PUT'])
def detail_notes(request, pk):
try:
note = Note.objects.get(pk=pk)
except Note.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
response = NoteSerializer(note)
return Response(response.data)
...
If the method is PUT or DELETE I want to allow access to the note only if the current user is the owner of the note. I implemented this permission (according to the docs) as follows:
class IsOwnerOrIsPublic(BasePermission):
def has_object_permission(self, request, view, obj):
user = obj.user
privacy = obj.is_private
if request.method in SAFE_METHODS:
return not privacy # is the note public and is this a read only request ?
return request.user == obj.user
However, when I add the #permission_classes([IsOwnerOrIsPublic]) decorator to my view the permission doesn't restrict access to an unauthorized user. I'm able to view any note with a pk.
I tried explicitly calling IsOwnerOrIsPublic.has_object_permissions(), with this code in my view:
authorized = IsOwnerOrIsPublic.has_object_permission(request, note)
if not authorized:
return Response(status=HTTP_401_UNAUTHORIZED)
But I get the error has_object_permission() missing 2 required positional arguments: 'view' and 'obj' (obviously), and I do not know what other arguments to pass in. For example, what is the view argument?
How do I make this permission work on this view? Alternatively, how do I make this constraint work?
Note that my solution is based on generic views which is more simpler to implement.
Try the following view class:
from rest_framework import generics
from django.shortcuts import get_object_or_404
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
...
# generics.RetrieveUpdateDestroyAPIView will allow Get, put, delete
class NoteDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
"""
Retrieve, update, or delete note by its author
Retrieve only for readers
"""
# I'll assume you are using token authentication
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated, IsOwnerOrIsPublic,)
serializer_class = NoteSerializer
def get_object(self):
note = get_object_or_404(Note, pk=self.kwargs['pk'])
self.check_object_permissions(self.request, note)
return note

Django-REST: custom permission doesn't work

I'm trying to make a custom permission using this guide
views.py
class CustomModelList(generics.ListAPIView):
queryset = CustomModel.objects.all()
serializer_class = CustomModelSerializer
permission_classes = [IsAuthenticatedOrReadOnly, IsCustomOrReadOnly]
def get(self, request, format=None):
# some logic
def post(self, request, format=None):
# some logic
Just for experiment I've created this permission not to apply anyway
pesmissions.py
class IsCustomOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
return False
But when POST request sends to server it takes no effect -- I'm able to create new model instance.
I think that since you are using a list view, custom object level permissions are not checked automatically.
Also note that the generic views will only check the object-level permissions for views that retrieve a single model instance. If you require object-level filtering of list views, you'll need to filter the queryset separately. See the filtering documentation for more details.
You can try overriding the has_permission method instead and see if that works, or check the permissions manually.

Django Rest Framework - Using Session and Token Auth

I am trying to get this working but don't know if this is possible. It should be doing it like this.
I developed a web app using Django + Rest-Framework + jQuery, and I want to have an external application to consume the same REST API, using JWT Tokens for authentication.
My current config is like this.
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_RENDERER_CLASS': [
'rest_framework.renderers.JSONRenderer',
]
}
SIMPLE_JWT = {
'AUTH_HEADER_TYPES': ('Bearer',),
}
views.py
class ListFileView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
user = request.user
if user:
obj = Foo.objects.filter(owner=user)
serializer = FooSerializer(obj, many=True)
data = serializer.data
return Response(data, status=status.HTTP_200_OK)
return Response({'detail': 'You have no access to files.'}, status=status.HTTP_400_BAD_REQUEST)
The tricky part is that when I use:
permission_classes = (IsAuthenticated,)
I can perform ajax calls from an external application (using a valid JWT token), but jQuery calls from within the app (with an authenticated user) fail with:
{"detail":"Authentication credentials were not provided."}
And on the other hand, if I use autentication_classes instead of permission_classes:
authentication_classes = (SessionAuthentication, BasicAuthentication)
I can perform ajax calls from within the web app using jQuery, but external calls fail with the same 403 error.
I tried using both like this:
class ListFileView(APIView):
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
...
but the external calls are also rejected.
Is it possible to have these two types of Auth working together in the same class view, or should I separate into two endpoints?
EDIT
Example calls from within the app with jQuery:
<script type="text/javascript">
function loadTblDocs() {
$("#tbl-docs > tbody").empty();
$.ajaxSetup({
headers: { "X-CSRFToken": '{{csrf_token}}' }
});
$.ajax({
type: 'GET',
contentType: "application/json; charset=utf-8",
url: "/api/list/",
dataType: "json",
success: function (data) {
console.log(data);
}
});
};
</script>
And externally via VBA code:
Set web = CreateObject("WinHttp.WinHttpRequest.5.1")
web.Open "GET", "/api/list/", False
web.SetRequestHeader "Authorization", "Bearer <JWT_TOKEN_HERE>"
web.Send
I couldn't work out exactly what is going on in your case, because the behavior in the 3 cases you shared does not seem to be consistent, but going to explain simply how authentication and authorization is determined in DRF, this might help you figure the issue out.
You should be able to use two authentication classes with no problems. With DRF, authentication works like this:
At each request, DRF goes over the provided authentication classes, in the order they are defined. For each class, there are 3 cases:
If it can authenticate the request with the current class, DRF sets request.user. From this point on, this request is authenticated.
If no authentication credentials are present, DRF skips that class
If authentication credentials are present but invalid, such as an invalid JWT token in Authorization header, DRF raises an exception and returns a 403 response.
DRF views normally use the DEFAULT_AUTHENTICATION_CLASSES variable defined in the settings file, but if you provide them in a view, settings are overridden.
Authorization comes into play when you add permission_classes to your views. permission_classes control access to your views, so when you add IsAuthenticated to a view's permission classes, DRF rejects the request with a 403 response if you try to access that view without an authenticated user.
So in your initial case, your internal AJAX request failing in this case suggests that there is no authenticated user data in your request session. I do not know what could be causing this. Perhaps you do not update request session in your login view (Django's login method should do this automatically).
In your second case, you remove permission_classes from the view, so the view will serve the request regardless if there is an authenticated user in the request or not. So it is expected that your internal AJAX request succeeds here, but I do not know why your external request fails in this case.
In your third case, from the point of your internal AJAX request, the scenario seems to be the same as the first case, so I do not know why your internal AJAX request succeeds in this case but not in the first case. The external request failing here is expected because you added the IsAuthenticated permission class to the view, but did not include JWTAuthentication in the authentication_classes, so your request can not be authenticated with your JWT token here.

Django restframework test permissions

Which is the proper way to test object based permissions?
Sample:
from rest_framework import permissions
class IsOfficeAdmin(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
office = obj
return office.admin == request.user
Which are the "asserts" that I shouldn't miss?
Do I need to create a view?
To your questions:
it's up to you to write the logic which will allow a user to access the object. As a result, you have to return a Boolean.
yes. You will specify to the view which permission classes you want to apply. In case of object permissions they will be queried on detail routes (get, update, delete)

Categories