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)
Related
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.
class StoryViewSet(viewsets.ModelViewSet):
serializer_class = StorySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
I have viewset with all CRUD functions. The problem is that user can edit or delete stories of another user, I found DjangoModelPermissionsOrAnonReadOnly in permissions, but I cannot even create. I am using author as foreign key in Storymodel. Also I am using rest_framework.authtoken. So, I think there is two options, create own permission or rewrite some permission. Which one is better?
Write a customer object-level permission. Here is an example:
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
"""
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.author == request.user
And include it to permission_classes list.
I created in wagtail a model called Regbox in model.py and also RegboxModelAdmin in wagtail_hooks.py. Wagtail admin includes item Regbox in wagtail side bar menu. Then I programmatically created a new collection, a new group with permissions add, edit, delete Regbox and this group is assigned to new user after registration. New user can add (edit and delete) new Regbox (model Regbox has forein key User) and this new user can see in wagtail admin only his own regboxes (I used queryset filter so that superuser could see all regboxes in wagtail admin and the current user only his own regboxes). But if this new user plays with urls in his browser he can see also other regboxes (not only his own regboxes). Simply he can change url from for example /regbox/edit/5/ to /regbox/edit/8/ and he can see this page although this page/regbox belongs to another user (it would need here Permission denied). Could someone please advice me how can I do it in wagtail admin so that any user can see only his own regbox pages ? Thanks
Permissions in Django (and Wagtail by extension) are handled on a per model basis, not per instance. Therefore, giving edit permissions on the Regbox to a user will allow him/her to edit every instances of that model. There are a few exceptions in Wagtail (like the Page model).
Anyway, you should be able to achieve what you want and add custom permission check by attaching a custom permission helper class to you ModelAdmin definition.
from wagtail.contrib.modeladmin.helpers import PermissionHelper
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from .models import Regbox
class RegboxPermissionHelper(PermissionHelper):
def user_is_owner(self, user, obj):
if user.pk == obj.owner:
return True
else:
return False
def user_can_inspect_obj(self, user, obj):
"""
Return a boolean to indicate whether `user` is permitted to 'inspect'
a specific `self.model` instance.
"""
return self.user_is_owner(user, obj) && super().user_can_inspect_obj(user, obj)
def user_can_edit_obj(self, user, obj):
"""
Return a boolean to indicate whether `user` is permitted to 'change'
a specific `self.model` instance.
"""
return self.user_is_owner(user, obj) && super().user_can_edit_obj(user, obj)
def user_can_delete_obj(self, user, obj):
"""
Return a boolean to indicate whether `user` is permitted to 'delete'
a specific `self.model` instance.
"""
return self.user_is_owner(user, obj) && super().user_can_delete_obj(user, obj)
class RegboxModelAdmin(ModelAdmin):
model = Regbox
permission_helper_class = RegboxPermissionHelper
modeladmin_register(RegboxModelAdmin)
As you can see, we create a new RegboxPermissionHelper helper class and define methods for the inspect, edit and delete permissions which first check that the user is the owner (this has been extracter to its own method) an then call super to let the original permission check happen. The call to super is important so it return false if the user is not the owner, but it will also return false the user is the owner but doesn't have a particular permission (e.g. you can create and edit but not delete).
FWIW, I think you are using the correct mechanism to filter the list view by filtering the queryset so don't change anything there.
I have added some URLs in my Django API for posting deleting and putting data and I don't know how to authenticate users first and give some of them to use these methods and ban some of them
As far as I know, you can use inbuilt decorator
#user_passes_test
then you can specify who can access your views just like below,
from django.contrib.auth.decorators import user_passes_test
def admin_user(user):
return user.is_superuser # write your logic here
#user_passes_test(admin_user)
def your_view(request):
--------
Have a look at the documentation for more clarification: https://docs.djangoproject.com/en/1.11/topics/auth/default/#django.contrib.auth.decorators.user_passes_test
Since you are using the tag django-rest-framework, I assume that your view is being created with Django REST Framework.
First, you should force users to be authenticated to use the API. Second, you need to define what types of permissions are needed to perform the actions.
You stated that Django Super Users should be able to perform these actions. Thus, you could create a custom permission to make sure that only a user that is a Django Super User will have permission:
from rest_framework.permissions import BasePermission
class IsSuperUser(BasePermission):
"""
Allows access only to admin users.
"""
def has_permission(self, request, view):
is_superuser = request.user and request.user.is_superuser
if not is_superuser and request.user:
# Your ban logic goes here
pass
return is_superuser
Then on your view, you can do:
from rest_framework.views import APIView
from your_app.permissions import IsSuperUser
class YourApiView(APIView):
permission_classes = [IsSuperUser]
If this is not enough information for you, I would suggest that you follow Django REST Framework's tutorial.
I looked at the code for CreateAPIView and nowhere in the creation process are permissions checked. So I decided to check them in the perform_create hook as follows:
class CourseList(generics.CreateAPIView):
"""
Create a course.
A user can create a course if he/she is an instructor in
the academy the course is to be created in.
"""
serializer_class = CourseRef
queryset = Course.objects.all()
permission_classes = (IsAuthenticated, IsInstructorInAcademy)
def perform_create(self, serializer):
self.check_object_permissions(self.request, serializer.validated_data.get('academy'))
serializer.save()
where the permission looks like:
class IsInstructorInAcademy(BasePermission):
def has_object_permission(self, request, view, academy):
return request.user.instructor_set.filter(academy=academy).exists()
When I try to create a course, the initial permission check runs fine and the course is saved but immediately afterwards I get the following error:
ValueError at /api/courses/
Cannot query "Mathematics 11A": Must be "Academy" instance.
and this is raised at the permissions check, as a result of which the POST returns a 500 ISE.
(This is slightly hacky because I'm passing in the academy instance in the permissions check in the perform_create hook whereas the view really concerns courses. I considered using a permission IsInstructorInAcademyOfObject but for that, I would need to pass in an object which I cannot get unless the serializer is saved.)
What is the best way to check for permissions here?
You have an issue because you're tying business logic checks with permissions.
Instead of calling check_object_permission, perform your own check once it's deserialized and raise a PermissionDenied if it fails.