Create custom permission classes - django

I have designed my own RBAC system for my Django app. Unlike the original Django's role-based that uses only permissions, content_types, groups, and users table, I designed mine so that it includes a model for defined operations (i.e. read, write, print), the objects (i.e. Post, Events, Documents), the roles (Writer Level 1, Events Manager) and the necessary relationships i.e. my own permissions table which takes in the object reference and a matching operation reference (i.e. Post | add).
My problem now is I am not quite sure how to implement this in DRF. When I use permission_class = (DjangoObjectPermissions,) and someone sends a 'POST' request to the Post model/table, we all know Django will check if the user has Can add post permission. I want to do this as well but I want Django to refer to my RBAC models/tables.
I want to write my own permission classes in my permissions.py but I might need a bit hint on how to implement this.
I've read you can create custom permissions, but I am not sure if that also means you can enforce your own RBAC tables with it.

Alright after checking rest_frameworks' permissions.py, I think I can enforce my own permission class behavior this way:
Create a custom permission class that subclasses rest_framework.permissions.BasePermission
override has_object_permission() method
Inside the method, write a logic that maps the requesting user into my custom RBAC models to check whether or not he has given the appropriate permission
Return the appropriate boolean

According to the DRF Permissions doc
As with DjangoModelPermissions, this permission must only be applied to views that have a .queryset property or .get_queryset() method. Authorization will only be granted if the user is authenticated and has the relevant per-object permissions and relevant model permissions assigned.
So, you should define either queryset attribute or get_queryset(...) method in your view class.
Example:
class MyViewClass(viewsets.ModelViewSet):
queryset = SomeModel.objects.all()
...
...
...
def get_queryset(self):
return SomeModel.objects.filter(some_field='foo')
...
...
...
The permissions to a User or groups can be controlled via Django's Admin site as well as Django shell

Related

Django permissions view access for only one permission in the list

I have a Django class based view in which I am using the PermissionRequiredMixin. I would like a user who has at least one of the permissions in the permissions_required attribute to be able to access the view. From the Django documentation:
The decorator may also take an iterable of permissions, in which case
the user must have all of the permissions in order to access the view.
So, if User1 has permission, "add_polls" and User2 has "change_polls", and I have a form view that lets a user add a poll and another view that lets a user change a poll (with permissions_required="add_polls" and permissions_required="change_polls" on each of those views, respectively), it works great. But, what if I have an interim page/view that has links to both of those views with permissions_required = ("add_polls", "change_polls") that I need the user to pass through first? Now, neither user will be able to access to that page because the users only have access to one or the other permission, not both - if I understand the documentation correctly, a user will need BOTH those permissions to access the interim page. If I did give both users access to both those permissions so they could access the interim page, this would then give both users access to both the add and change polls as well which I don't want.
Will I need to write a custom authorization to handle this, or am I missing something simple?
One possible solution would be to create another privilege - something like "interim-polls" which would be the permissions_required on the interim view. User1 would be assigned "add-polls" and "interim-polls", and User2 would be assigned "change-polls" and "interim-polls".
This would work, but is there an easier or more intuitive way than to have to add additional permissions? Maybe a way to specify that only one item in the list of "permissions_required" is actually required to access the form instead of all of them? i.e. permission1 OR permission2 as opposed to permission1 AND permission2 required to access the view.
Thank you for any insight.
[Edit - adding additional information]
I'll use some pseudocode here to give an idea of the views:
class ChooseAddorEdit(LoginRequiredMixin, PermissionRequiredMixin, TemplateView):
permission_required=('app.add_item', 'app.change_item')
Display Add (links to AddItem) or Edit (links to EditItem) Links
...
class AddItemFormView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
permission_required='app.add_item'
Manages form to add an item
...
class EditItemFormView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
permission_required='app.change_item'
Manages form to edit an item
...
The user would be required to have both permissions to access the ChooseAddorEdit view. If the user has both permissions, then that user can access both the Add and Edit views. What I would like to accomplish some users to be able to only add items and other users to be able to only edit items, but still be able to see the ChooseAddorEdit view.
As I mentioned above, this could be accomplished by adding another custom permission in the model's Meta: but is there a way to accomplish this without adding yet more individual permissions and using the ones available?

Django DRF role level permission

I have developed API's using DRF. I am struggling to relate the authorization part from Django's default permission which we define in the admin section for each and every role to the API.
Let's say I have two API's Customer Management and Customer Sales and have two roles created from them at the Django admin portal. manager role will only manage customer (add, view, delete and update) whereas sales role will manage sales (add, view, delete and update) for every customer.
When I try testing them in the admin portal the permissions work fine. The corresponding role has corresponding access. If I use the same with REST API it fails to comply with permission which is defined in the backend. It is like both the roles are able to access both the API's.
How do I handle this? Should I implement my own permission system ignoring old one (auth_permission, auth_group_permissions, auth_user_user_permissions) used in Django or is there any workaround to use Django permissions to make this work?
You can make your permission class as below
class CustomPermission(BasePermission):
def has_permission(self, request, view):
if request.user.is_authenticated():
return True if request.has_perm('can_read') else False # or stuff similar to this
return False
And use this CustomPermission class to your APIView 's attribute.
For more information on DRF permissions visit https://www.django-rest-framework.org/api-guide/permissions/

Django: derive permissions from user_type

I am implementing permissions functionality into my webapp. The requirements specified by the customer are that users can perform some actions based on what kind of user they are. The kind of user is defined based on a combination of user_type:user_role. This is the model:
class User(AbstractUser):
usertype = models.IntegerField(choices=UserType.CHOICES, default=UserType.BUYER_USER)
role = models.IntegerField(choices=UserRole.CHOICES, default=UserRole.NORMAL_USER)
For example we can have:
adviser:regular: can manager_orders
adviser:admin: can do what an adviser:regular does and also manage_advisers (this is no django admin, but an admin for advisers!)
(there are more user_type:user_role)
This seems to conflict with the django permission system, which is based on what permission a user has, instead of what kind of user it is.
That is, when using the django permission system I need to define all possible permissions and assign those permissions to the affected user. Instead, with the approach that I am planning, I would need to derive the permission from the user_type:user_role.
Does it make sense to use the django permission system in this context? How can I derive the permission from the user_type:user_role
If you don't want to stick to #ShangWangs approach in the comments, I would recommend taking a look at Django Object Based Permissions.
With this, you are able to grant permissions to users for objects that derive from a super Model you want to apply the permissions to.
Per the Django docs, you can do this with a custom authentication backend:
https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#handling-authorization-in-custom-backends
Any time Django needs to check if a user has a particular permission it will delegate to the auth backend, passing the current user object as an arg. At this point you can check the usertype and role fields and return appropriate boolean value.
example from the docs:
class SettingsBackend(object):
...
def has_perm(self, user_obj, perm, obj=None):
if user_obj.username == settings.ADMIN_LOGIN:
return True
else:
return False
From what you describe in your question you do not need the complication of 'object-level' permissions as mentioned by others (since you don't have unique permissions for each user, they are based on the type and role)

restrict fields according to user permissions in django

I'm building an API using Django rest framework. I have to restrict access to fields (both read or write access) according to the kind of user logged in. How do I go about it ? I'm considering writing separate serializers for different user roles (I will get an access token with every request using which I can authenticate the user, the next step will get me the user's roles, according to which I want to restrict what fields the user can see/edit).
In case you want to give certain users model level permissions to conduct certain actions you can do this with custom permissions like so:
class T21Turma(models.Model):
class Meta:
permissions = (("can_view_boletim", "Can view boletim"),
("can_view_mensalidades", "Can view mensalidades"),)
Then you can either make several serializers and swap them out in the views based on the permissions, or you can modify the fields of the serializer dynamically.

row level permissions with django-guardian - no effect on admin interface observed

I have added row level permissions with django-guardian to my project.
From the set-up it seems everything worked fine:
Guardian specific tables have been created (guardian_groupobjectpermission, guardian_userobjectpermission)
Models with GuardedModelAdmin show the "Object permissions" feature next to "History"
It lets me assign "Add", "Change", "Delete" permissions for users/groups
But assigning (resp. not assigning) permissions shows no impact at all on the admin interface. Every user is allowed to do everything with all objects.
I have tried with
user_can_access_owned_objects_only = True
but this only affects the ability to view objects. Once a user sees it, he can also change and delete it. Regardless what is set in the permissions.
And I followed another discussion suggesting this in the ModelAdmin
def queryset(self, request):
if request.user.is_superuser:
return get_objects_for_user(user=request.user, perms=['change_program'], klass=Program)
But this has a similar effect as above, it only limits the visible items.
I would have hoped to see the admin "save" and "delete" buttons (and functions) listening to django-guardian. Is this a misunderstanding? Or did I simply not walk down the entire road yet?
Thanks for any hint!
R
Guardian allows you to create your own permissions to assign to user/object combinations, but limiting access to resources based on those object permissions still requires you to write code in your views. As such, there is no automatic enforcing within the Admin views. The admin integration is for allowing users with access to the admin interface to manage object-level permissions, see the guardian docs:
http://django-guardian.readthedocs.org/en/latest/userguide/admin-integration.html