I defined a view function with the #api_view decorator. Now I would like to test object-level permissions in this object.
I know there is a check_object_permissions that would let me check the permissions I defined on my view, but I don't know how to call it. The documentation gives examples for classes, not functions.
#api_view(["GET"])
#permission_classes([IsAuthenticated & IsOwnerOfItem])
def query_something_related_to_items(request, item_id):
item = get_object_or_404(Item, id=item_id)
# I want to test IsOwnerOfItem here,
# I guess I need to call something like:
# "self".check_object_permissions(request, item)
# custom code here
return something
class IsOwnerOfItem(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
the documentation says:
If you're writing your own views and want to enforce object level permissions, or if you override the get_object method on a generic view, then you'll need to explicitly call the .check_object_permissions(request, obj) method on the view at the point at which you've retrieved the object.
But how do you call this in an view function?
Part of the documentation you referred says:
Object level permissions are run by REST framework's generic views
when .get_object()
So, class based views are meant in there. In order to check object permissions in function based view, you should write your logic and raise PermissionDenied if you decide that request shouldn't access item. Pseudocode:
from rest_framework.exceptions import PermissionDenied
# I want to test IsOwnerOfItem here,
# I guess I need to call something like:
# `has_permission_for_item` is your own function
if not has_permission_for_item(request, item):
raise PermissionDenied()
Related
I implemented DRF as per the document. At one point I figured out, once the user is authenticated, the user is allowed to fetch data of any user in the systems.
I have implemented filtering as per this document.
I read through the permission document and could not find a way to filter out queryset based on the owner. In my one of the views, I am checking if the owner is same as the user who requested.
My question is, Do I have to do the same in all viewsets? or There is a general way where I can check this condition?
Not sure, if it is the best way, but I do it by overriding get_queryset
def get_queryset(self):
queryset = YOUR_MODEL.objects.filter(user_id=self.request.user.id)
return queryset
Doing it, using permisson class
class IsInUserHierarchy(permissons.BasePermission):
def has_permission(self, request, view):
return bool(isinstance(request.user, UserClassHierarchy))
Some explanations. IsInUserHierarchy class is very similar to IsAdminUser. It checks, if request.user is in the required class (import UserClassHierarchy from models), using simple python isinstance() method
Just create a permissions file, and add something like this:
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Instance must have an attribute named `owner`.
return obj.owner == request.user
Then, in your ViewSet, use this permission class:
class MyViewSet(viewsets.ViewSet):
permission_classes = (IsOwner,)
Now, just import your permissions file anywhere you want to use this logic and you don't have to duplicate any code
Old question but for anyone curious, you can still create follow the general procedure as outlined by Dalvtor and Django/DRF docs.
Your viewset makes a call to check the object through:
self.check_object_permissions(self.request, obj)
With your custom permission, you need to check if it is iterable and iterate and check each object in the queryset:
from rest_framework import permissions
from collections.abc import Iterable
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# For Get Queryset (List)
if isinstance(obj, Iterable):
for o in obj:
if o.user != request.user:
return False
# For Get Object (Single)
elif obj != request.user:
return False
return True
I want to write a Mixin (or use a third-party one) for checking if the logged user is the owner of certain object.
url(r'^api/mymodel/(?P<pk>\d)/?', CreateUpdateMyModel.as_view(), name='create_or_update')
class MyModel(models.Model):
owner = models.OneToOneField('auth.User')
class OwnerRequired(SingleObjectMixin):
# do this self.object = self.get_object() for getting the object
#
# some logic for checking if request.user == self.object.owner
# otherwise return something like Response(status=status.HTTP_403_FORBIDDEN)
Inheriting from SingleObjectMixin is important to me, since I want to be able to do something like:
class CreateUpdateMyModel(APIView, OwnerRequired):
model = MyModel
def post(self, request, *args, **kwargs):
# self.object should be available here
# so that write some code taking it into account
How OwnerRequired should look for fulfilling this?
I'm open to another alternatives, in fact, I've checked PermissionRequiredMixin from django-braces and I'd like to use it, but I'm not sure how to do it
permission_required = ?? # I can code a method for, but how can I pass the model instance and the request.user?
Is there another simple alternative?
Take a look at object level permissions. There's also a relevant example on that page in the examples sections - see the IsOwnerOrReadOnly example.
Also note that object level permissions are only run either:
You are using the GenericAPIView or a subclass of it, and calling get_object() to retrieve the instance.
You explicitly call self.check_object_permissions(request, instance within your view code.
often when i define permissions on my django views I would use something like this
#permission_required('comment.add_thread', raise_exception=True)
def save_comment(request, id=None):
""" """
But, when rest framework, how can I tell the API method to check for comment.add_thread permission before performing the operation?
It all depends how you create your api views, if your view is not generic, then you need to create your own permission like this:
from rest_framework import permissions
class AddCommentPermission(permissions.BasePermission):
def has_permission(self, request, view):
return request.user.has_perm('comment.add_thread')
but if your view has a model property set, then you just simply use DjangoModelPermissions, as documented here:
http://django-rest-framework.org/api-guide/permissions.html#djangomodelpermissions
For my site I created an abstract Model which implements model-level read permissions. That part of the system is completed and works correctly. One of the methods the permissioned model exposes is is_safe(user) which can manually test if a user is allowed to view that model or not.
What I would like to do is add a method to the effect of continue_if_safe which can be called on any model instance, and instead of returning a boolean value like is_safe it would first test if the model can be viewed or not, then in the case of False, it would redirect the user, either to the login page if they aren't already logged in or return a 403 error if they are logged in.
Ideal usage:
model = get_object_or_404(Model, slug=slug)
model.continue_if_safe(request.user)
# ... remainder of code to be run if it's safe down here ...
I peeked at how the get_object_or_404 works, and it throws an Http404 error which seems to make sense. However, the problem is that there don't seem to be equivalent redirect or 403 errors. What's the best way to go about this?
(non-working) continue_if_safe method:
def continue_if_safe(self, user):
if not self.is_safe(user):
if user.is_authenticated():
raise HttpResponseForbidden()
else:
raise HttpResponseRedirect('/account/')
return
Edit -- The Solution
The code for the final solution, in case other "stackers" need some help with this:
In the Abstract Model:
def continue_if_safe(self, user):
if not self.is_safe(user):
raise PermissionDenied()
return
Views are caught by the middleware:
class PermissionDeniedToLoginMiddleware(object):
def process_exception(self, request, exception):
if type(exception) == PermissionDenied:
if not request.user.is_authenticated():
return HttpResponseRedirect('/account/?next=' + request.path)
return None
Usage in the view (very short and sweet):
model = get_object_or_404(Model, slug=slug)
model.continue_if_safe(request.user)
For the forbidden (403) error, you could raise a PermissionDenied exception (from django.core.exceptions).
For the redirecting behaviour, there's no built-in way to deal with it the way you describe in your question. You could write a custom middleware that will catch your exception and redirect in process_exception.
I've made a little middleware that return whatever your Exception class's render method returns. Now you can throw custom exception's (with render methods) in any of your views.
class ProductNotFound(Exception):
def render(self, request):
return HttpResponse("You could ofcourse use render_to_response or any response object")
pip install -e git+http://github.com/jonasgeiregat/django-excepted.git#egg=django_excepted
And add django_excepted.middleware.ExceptionHandlingMiddleware to your MIDDLEWARE_CLASSES.
Django annoying has a solution for this:
from annoying.exceptions import Redirect
...
raise Redirect('/') # or a url name, etc
You want to use decorators for this. Look up login_required for an example. Basically the decorator will allow you check check the safety and then return HttpResponseRedirect() if its not safe.
Your code will end up looking something like this:
#safety_check:
def some_view(request):
#Do Stuff
I'm wondering how I might accomplish a simple 'object ownership' system with django models, such that, by default, only the owner of an object may edit it.
I am attempting to allow a 'Management' group to edit all objects on behalf of the object owners, and have at this point added a custom permission:
class Meta:
permissions = (
("manage_object", "Can manage objects"),
)
To establish 'ownership' I've toyed with the idea of adding a def to the model:
def owner(self):
return self.user
But then, how might I go further? I could implement the permissions in a view and display relevant UI with a template, i.e.:
if request.user is object.owner:
# ... do stuff
elseif request.user.has_perm.can_manage: # this line is probably not right
# ... do something else
... and then present different UI elements on a template level.
So, the question is:
what faults/benefits are there with this approach?
are there recommendations?
or, any other previously implement methods?
Best thanks!
My approach would be adding a method to the model:
class YourModelWithOwnership(models.model):
...
def user_can_manage_me(self, user):
return user == self.user or user.has_perm('your_app.manage_object')
I'd then call that method whenever a permission check is required, and take some action based on the outcome. So for a view that would be
from django.shortcuts import get_object_or_404
...
def view_func(request, item_id):
item = get_object_or_404(YourModelWithOwnership, id=item_id) # or whatever is needed to get the object
if not item.user_can_manage_me(request.user):
# user not allowed to manage
...
else:
...
Later I'd probably realize that that's still quite some boilerplate code to write in every view that needs that test, so I'd implement an exception that's thrown when a user can't manage an object...
class CannotManage(Exception):
pass
...and add another method to the model:
from django.db import models
from django.shortcuts import get_object_or_404
class YourModelWithOwnership(models.model):
...
#classmethod
def get_manageable_object_or_404(cls, user, *args, **kwds):
item = get_object_or_404(cls, *args, **kwds)
if not item.user_can_manage_me(user):
raise CannotManage
return item
Then, in the view functions, this can be used:
def view_func(request, item_id):
item = YourModelWithOwnership.get_manageable_object_or_404(request.user, id=item_id)
...
This will of course raise an exception when the user isn't the owner and does not have the proper permission. That exception can be handled in the process_exception() method of a custom middleware class so that there's a single handler for all instances where a user is not allowed to mess with the object.
A while back I wrote up the usual technique for doing this in the admin. You may want to read through that to see how the implementation works.
You can look into RowLevelPermissions branch. It hasn't been included even in 1.1 beta though, I guess it still needs some development.