Problems with Django rest-framework DjangoModelPermissions allowing any authenticated user - django

I am trying to use DjangoModelPermissions and it does not seem to work properly.
This is the code:
class TestViewSet(viewsets.ModelViewSet):
model = Test
serializer_class = serializers.TestSerializer
permission_classes = (permissions.DjangoModelPermissions,)
def create(self, request):
response_data = {}
response_data['type'] = 'error'
data=json.loads(request.raw_post_data)
test = Test.objects.create(name=data['name'],\
description=data['description'],\
start_date=data['start_date'],\
end_date=data['end_date'])
#save changes
test.save()
return Response({'status': 'ok', "result": test.id})
I don't think it makes any difference in this case but I am using django_mongodb_engine.
I have a user that has no permissions, and it is able to create Test instances. On the other hand, how can I block also GET so just users with the right permissions can perform that action?
Thanks

The reason for DjangoModelPermissions is not working here is clearly explained in the DRF docs
"This permission must only be applied to views that have a .queryset property or get_queryset() method."
Check the docs here
The solution is:
Add queryset to your model
class TestViewSet(viewsets.ModelViewSet):
serializer_class = serializers.TestSerializer
permission_classes = (permissions.DjangoModelPermissions,)
queryset = Test.objects.all()
or if you want to override the default queryset method use this method as you like
def get_queryset(self):
return super().get_queryset()
Also, I noticed you don't have to specify the model in your ModelViewSet. If you specify your Test model in TestSerializer you only have to specify the serializer in ModelViewSet that's how DRF works

My problem was the same. The user could create new instance in the database despite of the permission class. I looked into the django-guardian and found that this back-end should be default:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
So I added it in my settings.py file and now it works and a user without a permission cannot create new instance. I hope it helps.

You need to have django-guardian with DRF for DjangoModelPermissions to work correctly. It's also mentioned in the original docs http://www.django-rest-framework.org/api-guide/permissions under the DjangoModelPermissions paragraph
If it still doesn't work as it should then let us know

Related

Django Rest Framework: Update and delete current user without providing pk

I know a lot of people already asked about handling with current user but I couldn't find solution so I post this.
What I want to do is to get, put and delete current user without providing pk.
I want to set endpoint like users/my_account
My current code is here
class MyAccountDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = CustomUser.objects.all()
serializer_class = UserSerializer
def get(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
And now I can get current user's info but when I try to update or delete the current user,
AssertionError: Expected view MyAccountDetail to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly.
How can I solve this?
Update
urlpatterns = [
path('users/my_account', views.MyAccountDetail.as_view()),
]
In this case, you will need to override get_object() method in your MyAccountDetail view. For example:
from rest_framework.permissions import IsAuthenticated
class MyAccountDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = CustomUser.objects.all()
serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
def get_object(self):
return self.request.user
You need to do that, because by default get_object method looks for lookup_url_kwarg or lookup_field in the URL, and from that it will try to fetch the object using pk or whatever you have configured in lookup_field or lookup_url_kwarg.
FYI, I have added a permission class as well, because without it, self.request.user will be an anonymous user, hence will throw error.

make view accessible to only specific users (i.e. who created that model) in django rest

I have one model which has user as its ForeignKey attribute which is auto fill ie. logged in user is filled there. I have made token authentication. Only Authenticated // i mean authorized users can visit that view. But i am planning to make such that only the user which had created that model object can only update the content of that object.
For example:
class Something(models.Model):
sth_name = models.CharField(max_length=18)
sth_qty = models.IntegerField()
user = models.ForeignKey(User)
on my View:
I override perform_create() to associate to above model automaticall.
def perform_create(self, serializer):
return serializer.save(user=self.request.user)
What do i exactly need to do? I have to write some permissions method, But I am really stuck.
Yes, you need to create an object level permission. The DRF tutorial covers this nicely here: http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/#object-level-permissions
Specifically, create a file permissions.py in your app, and add this permission there:
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.user == request.user
Then, in your view class which has the update resource for the Something model (probably SomethingDetail), add the permission_classes field:
class SomethingDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Something.objects.all()
serializer_class = SomethingSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
Just add the user when retrieving the object
obj = get_object_or_404(Something, pk=pk, user=request.user)
Note that this will throw 404. If you want 403 error, use custom condition to check the user and raise PermissionDenied. If you want to do this for multiple views, put the condition logic in a decorator.

django rest permissions allow both IsAdmin and custom permission

I have a views.py as below,
from webapi.permissions import IsOwner
class MemberDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = members.objects.all()
serializer_class = MemberSerializer
permission_classes = (permissions.IsAdminUser,IsOwner)
And the following is custom permission to check if the user is ower of object in webapi.permissions,
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
Now the issue is it is check if he is a admin user and gives permissions to update / delete, But if the owner is the user it should actually give permission to edit he data but in this case it is failing.
On seeing the question "Django Rest Framework won't let me have more than one permission" I tried as below also still it did not work when I use Or,
class MemberDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = members.objects.all()
serializer_class = MemberSerializer
permission_classes = (Or(permissions.IsAdminUser,IsOwner))
If I use Or it is failing with error during run time as "'Condition' object is not iterable"
Since DRF 3.9, you can use the logical bitwise operators | and & (~ was added in 3.9.2).
As outlined in the docs you would just need
permission_classes = (permissions.IsAdminUser|IsOwner,)
If you need give edit permissions for admin and owner users only, you can implement custom permission class:
class IsOwnerOrAdmin(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.owner == request.user or request.user.is_admin
And use only this one in the view:
permission_classes = [IsOwnerOrAdmin]
This method is documented here.
you are using tuple syntax but you need comma to act as a tuple
replace
permission_classes = (Or(permissions.IsAdminUser,IsOwner))
with
permission_classes = (Or(permissions.IsAdminUser,IsOwner), )

Unable to implement django-rules authorization

I'm a new django user looking for some help with django-rules. I'm trying to set up an authorization system that is 'OR' based. I have a 'File' model. I would like only the creator to be able to delete it, but a specific set of users to edit it. I've been able to followed their tutorial and implementation throughout; it works in the shell but not on my site. At the moment no one can delete or update anything.
My view currently looks like:
class FileUpdateView(PermissionRequiredMixin, generics.RetrieveUpdateAPIView):
"""
View updating details of a bill
"""
queryset = File.objects.all()
serializer_class = FileSerializer
permission_required = 'fileupload.change_file'
raise_exception = True
class FileDeleteView(PermissionRequiredMixin, generics.RetrieveDestroyAPIView):
"""
View for deleting a bill
"""
queryset = File.objects.all()
serializer_class = FileSerializer
permission_required = 'fileupload.delete_file'
raise_exception = True
The rules themselves are:
import rules
#rules.predicate
def is_creator(user, file):
"""Checks if user is file's creator"""
return file.owner == user
is_editor = rules.is_group_member('ReadAndWrite')
rules.add_perm('fileupload.change_file', is_editor | is_creator)
rules.add_perm('fileupload.delete_file', is_creator)
I know I'm close I feel like I'm just missing one step.
Thanks in advance!
please check & add settings file authentication backend for Django-rules. Also, you are mixing Django rest permissions with Django-rules permission. you need to check Django-rules permission in Django-rest permissions on view.
in short.
define a custom permission in rest-framework like this.
from rest_framework import permissions
class RulesPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return request.user.has_perm('books.edit_book', obj)
in viewset.
class BookView(viewsets.ModelViewSet):
permission_classes = (RulesPermissions,)
I've been working with the django REST framework and django-rules for a project and found an answer to your question.
The django REST framework uses an API view which is not compatible with rules' views.PermissionRequiredMixin, the authorization workflow and methods called during the API dispatch is different from django's class based view.
Try the following mixin for django REST framework API views and their subclasses:
import six
from django.core.exceptions import ImproperlyConfigured
class PermissionRequiredMixin:
permission_required = None
def get_permission_object(self):
object_getter = getattr(self, 'get_object', lambda: None)
return object_getter()
def get_permission_required(self):
if self.permission_required is None:
raise ImproperlyConfigured(
'{0} is missing the permission_required attribute. Define '
'{0}.permission_required, or override '
'{0}.get_permission_required().'
.format(self.__class__.__name__)
)
if isinstance(self.permission_required, six.string_types):
perms = (self.permission_required, )
else:
perms = self.permission_required
return perms
def check_permissions(self, request):
obj = self.get_permission_object()
user = request.user
missing_permissions = [perm for perm in self.get_permission_required()
if not user.has_perm(perm, obj)]
if any(missing_permissions):
self.permission_denied(
request,
message=('MISSING: {}'.format(', '.join(missing_permissions))))
With this mixin you're not forced to write a REST framework permission for each rules permission.

Django Rest Framework ignoring my IsOwnerOrReadOnly permissions

I have created a permissions file for the isOwnerOrReadOnly permission but the has_object_permission function is not being called at all (I have place a print statement there to check).
This is how I am using this permission in my view:
class CarDetail(generics.RetrieveUpdateDestroyAPIView):
.....
serializer_class = car_serializers.CarSerializer
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticatedOrReadOnly,IsOwnerOrReadOnly,)
What am I missing?
#adeleinr I am guessing you have declared your own get_object method( i would have asked you this in the comment but don't have sufficient points to do that :D), in that case you have to use check_object_permissions in the get_object ( also in PUT, DELETE ) .Use this in your get_object
obj = get_object_or_404(queryset, **filter)
self.check_object_permissions(self.request, obj)
I was inspired by article How I could delete any video on YouTube
and wanted to check if in my django project everything is working safe, and ended up here.
This is pretty important question!
And the answer is very good.
The Django Rest Framework makes false impression that everything is working fine, when one looks at it through browsable API view.
Object, which authenticate user owns:
Object, which authenticate user does NOT owns:
Hidden DELETE button makes you feel, that everything is fine.
You are authenticated, delete button hidden.
Cool! You stay unaware until you test it wit CURL or some other tool and notice this huge security hole.
Django is sometimes too much magic....
Example:
views.py
#authentication_classes((ExpiringTokenAuthentication, SessionAuthentication))
#permission_classes((IsOwnerOrReadOnly, ))
class UserFavouritesSpotDetail(RetrieveUpdateDestroyAPIView):
model = UsersSpotsList
serializer_class = FavouritesSpotsListSerializer
def get_queryset(self):
queryset = UsersSpotsList.objects.filter(
role=1)
return queryset
def get_object(self):
queryset = self.get_queryset()
obj = get_object_or_404(
queryset,
pk=self.kwargs['pk'],
role=1)
self.check_object_permissions(self.request, obj)
return obj
Notice the crucial line mentioned by Shivansh:
self.check_object_permissions(self.request, obj)
When I was missing it the vulnerability was existing.
permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
Assumes the model instance has an `user` attribute.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
return obj.user == request.user
TEST it e.g with http://www.getpostman.com/
provide Token of user not owning the object.
if everything is fine you should see "detail": "You do not have permission to perform this action."