Make PermissionRequiredMixin also check for object level permission - django

I am using Django Guardian to have object-level permission along with global permissions. Some of the users have a group with global permissions, and some have object-level permissions. With this, I seem to need to modify the PermissionRequiredMixin to check also for the object-level permission.
views.py
class MainPageView(PermissionRequiredMixin, TemplateView):
permission_required = "app.view_mainpage"
template_name = "web/mainpage.html"
This one works if the user has global permission but if the user is under a group with object-level permission, it won't. With guardian, to check for object-level permission, you also have to pass the object instance.
example:
self.request.user.has_perm('view_mainpage', obj)
And on PermissionRequiredMixin, the checking goes only like this, self.request.user.has_perms(perms)
So if the user has a group with permission of view_mainpage of a specific object, how can I check it too? By the way, all my permissions are of the same content_type. Is there any way I can execute this? Like I have to pass the object instance to PermissionRequiredMixin if the user is under an object-level group and None if the user is under a global group.

You can implement such mixin yourself with:
from django.core.exceptions import PermissionDenied
class ObjectPermissionRequiredMixin:
object_permission_required = None
object_permission_denied_message = None
def has_object_permission(self, obj):
return self.request.user.has_perm(self.object_permission_required, obj)
def get_object_permission_denied_message(self):
return self.object_permission_denied_message
def handle_no_object_permission(self):
raise PermissionDenied(self.get_object_permission_denied_message())
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
if not self.has_object_permission(obj)
return self.handle_no_object_permission()
return obj
Then you can mix this mixin into a view, for example:
class MyUpdateView(ObjectPermissionRequiredMixin, UpdateView):
model = MyModel
object_permission_required = 'app.update_my_model'
object_permission_denied_message = 'You can not edit this object'
This will work for all Views that usee the SingleObjectMixin [Django-doc] such as a DetailView, UpdateView, and the DeleteView.

Related

Is it possible to check the group affiliation in custom permissions (BasePermission)?

Let's say I want to fetch a user profile. I want to allow it to the profile owners or users belonging to the administrator group. I know how to do these two things separately.
For owners:
class IsOwner(BasePermission):
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, object):
if request.user.is_authenticated:
return object.name == request.user.name
return False
And for the admin group:
from django.contrib.auth.mixins import PermissionRequiredMixin
class MyView(PermissionRequiredMixin, View):
permission_required = ('polls.can_open', 'polls.can_edit')
But how to allow either of the above? Is it possible to check user groups and permissions inside the has_object_permission method, without additional database calls?

Django permission's specific to templateview

How to create multiple permission classes that would restrict user from accessing a template view ( that doesnt use any model).
For example: I have 4 user categories - ( admin, management, principal, teacher). I have a Admin dashboard template view which should be restricted to user type=Admin.
I would like to be able to write multiple permission classes, which I can then use in combination in any view.
Following code generates 403 error:
class AdministratorPermission(AccessMixin):
def has_permission(self):
return True
class GeneralPerm1(AccessMixin):
def has_permission(self):
return True
class DashboardView(PermissionRequiredMixin,
LoginRequiredMixin, TemplateView):
template_name = 'administrator/dashboard.html'
permission_required = (AdministratorPermission,GeneralPerm1)
Is there a way to do something like DRF permissions.
Thanks
Permission1Mixin.py:
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import PermissionDenied
class IsAdministratorMixin(AccessMixin):
""" if user is not administrator, decline permission """
def dispatch(self, request, *args, **kwargs):
"""
if user is authenticated and administrator
we are good. otherwise permission denied
"""
if request.user.is_authenticated and \
request.user.category.category == \
UserCategoryEnum.ADMINISTRATOR.value:
return super().dispatch(request, *args, **kwargs)
raise PermissionDenied('Permission denied') # decline permission
view.py
class DashboardView(IsAdministratorMixin, TemplateView):
template_name = 'administrator/dashboard.html'
This way we can create multiple independent permission mixins and use them in combination.

Looking for wagtail modeladmin PermissionHelper example

In "wagtail_hooks.py" I have the code below. As wagtail admin I can see the StudentModelAdmin, but as a user with restricted access to the admin interface I can't.
I would like to allow users with wagtail admin access and the specific permission below to access the student model admin. How do I go about creating the "CourseRegisterPermission" class?
from wagtail.contrib.modeladmin.options import (ModelAdmin, modeladmin_register)
from wagtail.wagtailcore import hooks
from .models import Participant
#hooks.register('register_permissions')
def view_course_registrations():
return Permission.objects.filter(codename="view_course_registrations")
class CourseRegisterPermission(PermissionHelper):
# how do I allow users with the permission to view course registrations
# to see the 'StudentModelAdmin" below?
class StudentModelAdmin(ModelAdmin):
model = Participant
menu_label = "Student Registrations"
menu_icon = "group"
search_fields = ('name', 'supervisor_name')
list_display = ('name', 'email')
list_filter = ('course',)
permission_helper_class = CourseRegisterPermission
I tried to find some examples of wagtail PermissionHelper but wasn't able to find any.
Any hint would be appreciated!
You can use the wagtail.contrib.modeladmin.helpers.PermissionHelper or wagtail.contrib.modeladmin.helpers.PagePermissionHelper permission helper classes from Wagtail's sources as an example. See methods like user_can_list, user_can_create, etc.
But... Are you sure that you need to define your own permission helper class? It seems to me that you can just create a new (or edit existing) group in the Wagtail admin and give required object permissions to your Participant model.
On my screenshot Programme is the model that I manage using ModelAdmin.
You can override some functions inside CourseRegisterPermission
class CourseRegisterPermission(PermissionHelper):
def user_can_list(self, user):
"""
Return a boolean to indicate whether `user` is permitted to access the
list view for self.model
"""
# this is just an example
return user.role == "driver"
def user_can_delete_obj(self, user, obj):
"""
Return a boolean to indicate whether `user` is permitted to 'delete'
a specific `self.model` instance.
"""
perm_codename = self.get_perm_codename('delete')
if obj.status > 0:
return False
if not self.user_has_specific_permission(user, perm_codename):
return False
if user.id == obj.id:
# users may not delete themselves
return False
You can also override the following functions:
def user_can_list(self, user):
def user_can_create(self, user):
def user_can_inspect_obj(self, user, obj):
def user_can_edit_obj(self, user, obj):
def user_can_delete_obj(self, user, obj):
def user_can_unpublish_obj(self, user, obj):
def user_can_copy_obj(self, user, obj):

Registering Different types of Users in Django

I wont be needing to write codes much,I just need an idea.I am trying to make users in two different categories.That is a User can either have a Profile or Shop both not both.
class Profile(models.Model):
user=models.OneToOneField(User,on_delete=models.CASCADE,primary_key=True,editable=True)
class Shop(models.Model):
user=models.OneToOneField(User,on_delete=models.CASCADE,primary_key=True,editable=True)
I want all users to either have one of both,They will register with the same form without being able to switch from one to another once they have registered for it.As at now what I'm doing is If i dont want a shop-owned User to have the previlege of a profile i do
def post_product(request):
try:
if bool(request.user.profile)==False:
.....
except:
pass
But I have to do this for all views that require previledges. I want something site-wide that i can put in settings or put at the view like #login_required .Please how can I go about this.Thanks
As per answer given by Prabhakar, you should use django auth groups.
But, if you want to write your custom permission class, you could also do it.
1) Assuming file name -> self_defined_permission.py
from rest_framework import permissions
from django.contrib.auth.models import Group
class ShopPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
'''
check user group. Assuming you keep users having profile in group 1 and shop in group 2
'''
shop_gp = Group.objects.get(id=2)
if shop_gp in request.user.groups.all():
return True
else:
return False
'''
similarly you could write permission class for profile owners
'''
2) In your class based view
from self_defined_permission import ShopPermissions
class SomeView(generics.ListCreateAPIView):
...
permission_classes = (ShopPermissions, )
3) In case you are using function based view.
from rest_framework.decorators import permission_classes
#permission_classes((ShopPermissions, ))
def some_view(request):
....
Try the user_passes_test decorator:
from django.contrib.auth.decorators import user_passes_test
def is_shop(user):
return Shop.objects.filter(user=user).exists()
def is_normal_user(user):
return Profile.objects.filter(user=user).exists()
#user_passes_test(is_shop)
def only_for_shops(request):
return render(request, 'edit_price_list.html')

Django Rest Framework authentication - how to implement custom decorator

I am trying to implement TokenAuthentication using the Rest Framework, but it seems that I can't add my own custom decorators to my ViewSets because they are evaluated BEFORE the authentication. Consider this:
from django.utils.decorators import method_decorator
from django.http.response import HttpResponseForbidden
def require_staff(View):
def staffOnly(function):
def wrap(request, *args, **kwargs):
if request.user.is_active and request.user.is_staff:
return function(request, *args, **kwargs)
else:
return HttpResponseForbidden()
return wrap
View.dispatch = method_decorator(staffOnly)(View.dispatch)
return View
When I try to implement this, it seems the decorator code fires first, so the authentication is never run.
#require_staff
class CustomerViewSet(ModelViewSet):
model = Customer
filter_class = CustomerFilter
filter_backends = (DjangoFilterBackend,)
Since request.user is never set, introducing the decorator breaks authentication.
I think the issue is that Authentication is occuring the rest_frameworks dispatch() function and it is not clear to me how I could add additional (say) custom security if authentication is done that late in the game.
Am I missing something here, or what is the proper way to implement this customization?
Someone suggested using Permissions for this instead. I assume they mean custom DRF Permissions, right?
Everything you need to know is DRF permissions is here: http://www.django-rest-framework.org/api-guide/permissions
DRF provides a built in permission that is similar to yours, called IsAdminUser
The IsAdminUser permission class will deny permission to any user,
unless user.is_staff is True in which case permission will be allowed.
This permission is suitable is you want your API to only be accessible
to a subset of trusted administrators.
To use this permission in a Class Based View:
class ExampleView(APIView):
permission_classes = (IsAdminUser,)
Now you have two options to do an extra check for user.is_active.
The first is override the IsAdminUser permission, like so:
from rest_framework import permissions
class IsActiveAndAdminUser(permissions.IsAdminUser):
"""Only allow a user who is Admin and Active to view this endpoint. """
def has_permission(self, request, view):
is_admin = super(IsAdminAndActiveUser, self).has_permission(request, view)
return request.user.is_active and is_admin
The second is to create an IsActiveUser permission, and chain them in your view.
IsActiveUser Permission:
from rest_framework import permissions
class IsActiveUser(permissions.BasePermission):
""" Only Active Users have permission """
def has_permission(self, request, view):
return request.user.is_active
Your new permission list in your class based view:
class ExampleView(APIView):
permission_classes = (IsActiveUser, IsAdminUser,)