Return QuerySet based on User group/permissions - python

I'm trying to figure out what's the "best practice" to limit QuerySets based on Users permissions.
For example, there is a table of invoices on the dashboard. User that has Group called Admin can see all invoices but User that has group Broker can see only their own invoices. That means only invoices that have user = ....
My idea is to create two permissions can_see_all_invoices and can_see_own_invoices.
Now when I call the QuerySet using Django Rest Framework, I'll check for the permissions and return the filtered QuerySet.
Or should I filter the QuerySet on the frontend and if Broker asks for all invoices I would raise PermissionError?
Which of these approaches are being used or is there a different approach?

IMO, this would be a clean method
class MyInvoiceAPI:
def get_queryset(self):
qs = Invoice.objects.all()
if self.request.user.has_perm('can_see_all_invoices'):
return qs
return qs.filter(user=self.request.user)
Notes
You don't need two permissions, only one which is can_see_all_invoices
I wouldn't raise any permission denied errors in this case, since it a List API, and evaluation of object is an expensive process

Related

Django queryset permissions

I am building a quite complex Django application to be used on top of and email scanning service. The Django application is written using Python 3.5+
This application primarily uses Django Rest Framework to handle communication with the frontend in the browser.
The issue that I am currently having is that I try to implement the concept of a System Administrator, Domain Administrator and Application User
The System Administrator is basically the "normal" django superuser and is therefore capable of doing everything and see every record in the system.
The Domain Administrator is user who manages one or more email domains. I keep track of this using a Many2Many relationship between the users and the domains. The idea would then be to predefine a filter, so that the log of messages processed, will be automatically filtered to show only messages where the sender domain or the recipient domain equal a domain in the list of domains that the given user is assigned to.
The same would be true for blacklisting/whitelisting policies.
If the Domain Administrator is not assigned to any domains, then no data is shown.
The Application User is basically any authenticated user with one or more domains assigned to them, using the same Many2Many relationship as the Domain Administrator. If no domains are assigned, then no data is shown.
I have found some other solution here on Stackoverflow on making the request.user available to the QuerySet in the ModelManager, but that does not seem like the correct way to handle it.
I have looked at django-guardian, django-authority and django-permissions, but none of them seem to be affecting the QuerySet or the resulting list of objects.
Does anyone have a suggestion for Django package/addon that can be used to handle this or maybe an idea for how this could be handled?
I'm the author of django-cancan library https://github.com/pgorecki/django-cancan which strives to solve the exact problem you are describing.
The philosophy is as following: first, you determine per-user abilities, then in a view, you can check user abilities for a given object, model, or you can retrieve a queryset based on those abilities.
The declaration part looks like this:
def declare_abilities(user, ability):
if not user.is_authenticated:
# Allow anonymous users to view only published articles
ability.can('view', Article, published=True)
else:
# logged in user can view any article...
ability.can('view', Article)
# ... and change his own
ability.can('change', Article, author=user)
# ... and add new ones
ability.can('add', Article)
if user.is_superuser:
# Allow superuser to view and change any article
ability.can('view', Article)
ability.can('change', Article)
Then you can you can check for abilites on a per-object level:
def article_detail_view(request, pk):
article = Article.objects.get(pk=pk)
if request.ability.can('view', article):
...
or on a model level:
def article_create_view(request, pk):
if request.ability.can('add', Article):
...
or get a queryset with accessible objects:
def another_list_view(request, pk):
articles = request.ability.queryset_for('view', Article)
...
DRF's GenericAPIView has a get_queryset method that you can override to perform custom filtering:
def get_queryset(self):
qs = super(YourView, self).get_queryset()
return self.filter_queryset_for_user(qs, request.user)
def filter_queryset_for_user(self, qs, user):
pass # Your logic here
This is not necessarily a bad idea; DRF docstrings recommend overriding this:
You may want to override this if you need to provide different
querysets depending on the incoming request.
I think you are misunderstanding the concept of permission in Django. django-guardian, django-authority and django-permissions these all packages are for handling permission inside your Django application. What permission does is it checks certain model or instance of model, if the user has permission to view that particular model or object, otherwise it will return 403 Unauthorized response. Permission does not change or filter your queryset to return only the valid results.
Rather if you want to apply filter your queryset, you can do so by the above answer, or you can move that code to a Mixin to follow DRY Style. For Mixin Reference you can see this link:
https://reinout.vanrees.org/weblog/2015/06/03/10-mixins.html
https://www.youtube.com/watch?v=0513I6_f2Tc
My answer to this question also provides an alternative to filter your queryset by subclassing rest_framework.filters.BaseFilterBackend and implement filter_queryset() based on your permission pattern , which would be suitable for more complicated use cases.

Django custom manager filter query set (by parameter)

I would like my models to automatically filter by current user.
I did this by defining:
class UserFilterManager(models.Manager):
def get_queryset(self):
return super(UserFilterManager, self).get_queryset().filter( owner=get_current_user() )
where get_current_user() is a middleware which extracts the current user from the request passed to Django.
However, I need to use the models from Celery which does not go through the middleware. In these cases
MyModel.objects.all()
needs to become
MyModel.objects.filter(user=<some user>)
To avoid wrong queries caused by forgetting to filter by user, I would like the model/manager/queryset to assert when a query (any query) is performed without a filter on user.
Is there a way to achieve this?
From what I see get_queryset() cannot receive parameters and models.QuerySet won't provide aid here.

User roles schema on Django

A great greetings community
My question is related with the kind of manage users and the schema users in Django, In beginning I ask to you apologize just in case that my questions can will be too "newbies" or without sense, I am starting to related me with the Django Users schemas and their different possibilities of work in the projects.
I have the following situation.
I am building an application in which I will have three differents user types:
Medical
Patient
Physiotherapist
I am using the default Django authentication scheme (django.contrib.auth).
Initially, I did think in this scheme of entities in which the User table is the auth_user table in where Django save the users created:
I have the is_patient, is_medical and is_physiotherapist fields like boolean attributes in the User table.
Like a particular detail I understand that in the Django default model User is not possible modify or add attributes or fields.
This is an important and powerful reason to I cannot add the is_patient, is_medical and is_physiotherapist boolean fields in User table.
A classic recommendation is extend the User model with a Userprofile table in which I add fields or attributes to User Model through of OneToOne relationship. A basic sample is such as follow:
Is of this way that I get that my users in Django can have the field photo and upload one in a given moment ...
Taking advantage of the previous,
The following schema can be suited or can be an alternative for manage user roles (patient, medical and physiotherapist user types) ?
I will have relationships between:
User medical and user patients
user physiotherapist and user patients
and so between them and other tables ...
With this approach these relationships don't will be affected?
The different users will be saved between the Users and UserProfile table.
Is this a good practice in the scalability sense? My tables could be crash or my database?
In addition, I also have seen other alternatives such as:
Role Table/Model
I will have a role table/model independent or separate and that this can be related with the Django User model (One User can will have many roles by example)
This approach can be useful when I want store exclusive information about of a role in specia?
Django Permissions and Authorization
I ignore or unknown the granularity grade that let will me work. Of a single way I have been see that the permissions and authorizations system let will me work with create and edit and remove operations ....
Here, can I see the groups creation?
For example a medical group and allocate them permissions and linked this permissions to the users that compose the group ? Is this another good alternative?
This option seem more single although I don't know if an user could make some operations according to the group privileges that have ... I don't know if this thinking is correct/right
AUTH_USER_MODEL Creating a Custom User model
My requirements for patient, medical and physiotherapist users, require build a custom user model?
In this situation, especially if you want to store different infos for Patients, Medics and Physiotherapists you can create a Model for each and have a OneToOne field for each to the User model.
class Medic(models.Model):
user = models.OneToOneField(User, primary_key=True)
# other fields
class Physio(models.Model):
user = models.OneToOneField(User, primary_key=True)
# other fields
class Patient(models.Model):
user = models.OneToOneField(User, primary_key=True)
# other fields
This way you can give different permissions/roles implicitly in your application logic for each type of user (and still use the groups and permissions that Django offers if you need them for special cases, eg ChiefMedical...).
You will have to define some methods for your application logic like
def user_is_patient(user):
...
If you follow this path it is a good idea to have good tests to make sure that you don't get unexpected things like a user who is a Medic and a Physio...
Django lets you subclass the user model as well. Under the covers it would do the same thing as the code above, so it is probably better to do it explicitly as shown above (this way it is less probable that you access attributes that don't exist in that object!)
Taking advantage of the previous, The following schema can be suited or can be an alternative for manage user roles (patient, medical and physiotherapist user types) ?
The schema you show isn't great because it makes you store the information for all user types in the same table (and with the same fields). For example, Medics and Physios will have a blood type field type like Patients which will probably not be defined.
The different users will be saved between the Users and UserProfile table. Is this a good practice in the scalability sense? My tables could be crash or my database?
There should be no scalability problems with this solution (as long as you don't have millions new entries writes every day) and you can always optimise the database at a further point. However, you will have to make sure that your app doesn't accept 'forbidden' entries (e.g. users with no Medic, Physio or Patient profile)
Here, can I see the groups creation? For example a medical group and allocate them permissions and linked this permissions to the users that compose the group ? Is this another good alternative? This option seem more single although I don't know if an user could make some operations according to the group privileges that have ... I don't know if this thinking is correct/right
You can (should) use Django's permission system to give permissions to your users. You can use them to give different rights to users of the same type (for example Medics that have more permissions than others... or have groups for chief physios...)
Django lets you assign permissions to a group.
But I don't think groups can replace the custom models for each user, since you want to store information for them. Having custom models and groups would be redundant and make your app harder to maintain.
My requirements for patient, medical and physiotherapist users, require build a custom user model?
This option wouldn't be great (unless it is your only option) because your app won't be reusable and you might have problems with some packages as well.
You can create a custom User model or not, in any case, you could have three separate models for storing pertinent data, depending on whether the user is patient, medical, physiotherapist, or any combination of these.
If your permissions scheme is determined solely by the role (patient, medical, physiotherapist) then you don't need to use Django's permissions system, because you know the role(s) of any user and you can, in the worst scenario, hardcode authorization rules.
I gave a glance at question's comments and I view some issues:
()
I realized that your user model does not match with the original data model since having get_medical_profile, get_patient_profile and get_physiotherapist_profile functions inside user model, with that you are assuming that any user could have multiple profiles at the same time, which isn't reflected neither in your profile models (Medical, Patient and Physiotherapist) using OneToOneField nor in original data model of the question, it's an important thing about abstraction and class-responsibility. The requirement (according the model below) seems to say "one user can have only one profile".
So.. I think this can be solved in a straightforward and clean way, you don't need to involve in overall authentication esquema like groups and permissions or adding additional attributes to user model:
class UserProfile(models.Model):
user = models.OneToOneField(User)
# common fields shared by medical, patient and physiotherapist profiles
class MedicalUser(models.Model):
profile = models.OneToOneField(UserProfile)
# medical fields here
class PatientUser(models.Model):
profile = models.OneToOneField(UserProfile)
# patient fields here
class PhysiotherapistUser(models.Model):
profile = models.ForeignKey(UserProfile)
# patient fields here
As you see, you can have a profile which contains common fields shared by all profiles. and each profile has an specific model.
In addition, you can check if user is medical by this small function below, then if there is no an medical profile associated with profile then it will raise exception and it means it's a profile unspecified:
def is_medical_profile(profile):
try:
profile.medical_user
return True
except:
return False
You can also use it in your templates (as a custom template tag) in this way:
{% if profile | is_medical_profile %}
With this approach you don't need to setup AUTH_USER_MODEL
I hope this improves your solution.
Additional notes:
Just in case you decide to have a custom user model, setup settings.AUTH_USER_MODEL and use it for foreign keys to User.
On a piece of text of awesome book Two scoops of Django says:
From Django 1.5 onwards, the official preferred way to attach
ForeignKey, OneToOneField, or ManyToManyField to User
Therefore, your user profile model would change as follows:
from django.conf import settings
from django.db import models
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
Yes, it looks a bit strange, but that's what the official Django docs advice.
#geoom #Ire #lorenzo-peña I 've created an user through Django admin site and I checked their attributes (is_medical, is_patient, is_physiotherapist) via python shell
In [6]: User.objects.filter(username='agarcial').values('is_medical','is_patient','is_physiotherapist')
Out[6]: [{'is_physiotherapist': True, 'is_patient': True, 'is_medical': True}]
For the moment in my views.py I am doing that an user sign in only when this be one of three user types (medical, patient or physiotherapist)
# Create your views here.
class ProfileView(LoginRequiredMixin, TemplateView):
template_name = 'profile.html'
def get_context_data(self, **kwargs):
self.request.session['Hi'] = True
context = super(ProfileView, self).get_context_data(**kwargs)
is_auth = False
name = None
# Check if in the request goes the user
user = self.request.user
# Check about of possible cases (For now is one profile)
if user.is_medical:
#if self.request.user.is_authenticated():
print (user.is_medical)
is_auth = True
profile=user.get_medical_profile()
#name = self.request.user.username
data = {
'is_auth':is_auth,
'profile':profile,
}
context.update({'userprofile':profile, 'data':data})
elif user.is_patient:
print (user.is_patient)
is_auth=True
profile=user.get_patient_profile()
data = {
'is_auth':is_auth,
'profile':profile,
}
context.update({'userprofile':profile,'data':data})
elif user.is_physiotherapist:
print (user.is_physiotherapist)
is_auth=True
profile=user.get_physiotherapist_profile()
data = {
'is_auth':is_auth,
'profile':profile,
}
context.update({'userprofile':profile,'data':data})
return context
def get_userprofile(self):
return self.request.user.userprofile
If I check the other possible combinations (User patient,medical and physiotherapist) this could work?
I think create groups for (Medicals, Patients, Physiotherapists) and binding users for the authorization topic, although I should review other things for authorization process such as django guardian for example?
How about this?

Django: Filter request results to only contain data related to the requesting user

I'm a Django beginner (though I do have experience in web development using Sails.js + Angular) so bear with me.
I have an existing application that uses REST API in communicating between Sails.js backend and AngularJS frontend. Now, we've found the backend to be unsuited for our purposes, and we're going to swap to using Django in near-future. Sails.js automatically creates the REST methods for the controllers while Django doesn't, so I suppose I'm going to use something like Django Rest Framework to create the API.
So yeah, I've found corresponding features for most things. The on thing I haven't found yet is a replacement for a Sails.js feature called "policies". They are functions that can be executed on queries to certain controller actions, and can be defined as model-specific, model-controller action-specific, and request type specific. For example, you can have an "authAccess" policy that checks that the user of a request is authenticated, and the policy gets executed before the actual requested controller method gets executed. You can also use these to modify request objects before passing them to the controller. Now to my actual problem:
Let's say I have a User model that has a many-to-one relation with another model, let's call it Book, meaning a user can own many books, but a book can only have one owner. Goody good. Now, we have a logged-in user that is making a query to find all of his books. He makes a GET request to /book. I want to ensure that the returned Book objects are filtered so that ONLY HIS BOOKS are returned.
So basically in Sails I was able to write a policy that altered the request parameters to be like {user: loggedInUser} so the resulting Book results were automatically filtered. This was handy, since I was able to use the same policy to filter other models, too, like DVD or Friend or whatnot. My question is - what would be the best way to implement the same functionality in Django?
Have a look at the documentation:
http://www.django-rest-framework.org/api-guide/filtering/#filtering-against-the-current-user
Most likely you are better off overwriting the get_queryset method in a model viewset. And you can make this a generic approach by creating a base class for your views, something like:
from rest_framework import generics, viewsets, mixins, generics
class OwnerModelViewSet(viewsets.ModelViewSet):
def get_queryset(self):
"""
This view should return a list of all the records
for the currently authenticated user.
"""
return self.model.objects.filter(user=self.request.user)
All your model viewset classes can inherit from that class. It would require the foreign key field to be always named "user" though. If that is not the case here is a slightly hacky way how you could find a foreign key field to the User table. Use with care.
from django.db.models.fields.related import ForeignKey
from accounts.models import User
def _get_related_user(self, obj):
'''
Search for FK to user model and return field name or False if no FK.
This can lead to wrong results when the model has more than one user FK.
'''
for f in self.model._meta.fields:
if isinstance(f, ForeignKey) and f.rel.to == User:
return f.name
return False

django admin - group permissions to edit or view models

I'm searching for a way to customize the Django Administration to support permissions based on the user group.
For example, I've just created the Developers group, now I've also created the Tickets model, with AdminModel to specify how to list data.
I'd like to have this model visible only by Developers, and hidden to each other not in this group (eg filter the view based on groups).
I've read a lot of documentations, but couldn't really find and understand what to do to have it working.
For security purposes I'd also need to check user groups at runtime when adding-deleting objects for a specific model (the one I've hidden to people outside the Developers group), otherwise it would only need to know the URL to use the model :s
It looks like a simple task, but maybe I'm missing something... any 3rd party middleware, or just a way to do it? I'm also ready to edit the administration views if needed, but I need to know what do to.
Thank you :-)
ModelAdmin has three methods dealing with user permission: has_add_permission, has_change_permission and has_delete_permission. All three should return boolean (True/False).
So you could do something like:
class TicketAdmin(admin.ModelAdmin):
...
def has_add_permission(self, request):
return request.user.groups.filter(name='Developers').exists()
def has_change_permission(self, request, obj=None):
return request.user.groups.filter(name='Developers').exists()
def has_delete_permission(self, request, obj=None):
return request.user.groups.filter(name='Developers').exists()
When False is returned from one of these, it's results in a 403 Forbidden.
I had tried hours to find a way to edit custom admin's(based on my custom model) permission by some click on screen,without many coding.
Use Django's /admin/auth/user/ "User permissions:part"
Finally I find this:
Just to install django-admin-view-permission
and I can change the staff's custom models' permission here
Also in the group part /admin/auth/group/add/ I can create a group has certain permission, and assign specific staff to their permission group.

Categories

Resources