Django object level permission using default auto-created model permissions - django

By default, Django adds 4 permissions for each registered model:
add_modelname
change_modelname
delete_modelname
view_modelname
When a user has one of these permissions it applies to all instances of that model. That's not what I want, I want per instance permission.
When the creator (created_by in database, or some related table of users with access) has delete_modelname permission he can delete only that instance, and not some other created by another user.
I've been looking at other answers and none of them mention the auto-created CRUD permissions that are created for all models, it's mostly new tables or third-party libraries.
What about giving creators of models these permissions (add, change, delete, view) which normally would give them access to all other models, but also having a custom auth backend which checks the object:
from django.contrib.auth.backends import BaseBackend
class ObjectPermissionBackend(BaseBackend):
def has_perm(self, user_obj, perm, obj=None):
if not obj:
return False
return obj.created_by == user_obj.pk # or something like this
And then using it:
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend', 'path.to.ObjectPermissionBackend']
Is there some downside to this?

Related

How to associate “owner” with default User model?

I’m trying to teach myself Django and the Django Rest Framework, but I'm having a hard time understanding how object ownership is defined.
I want to apply the custom “IsOwnerOrReadOnly” permissions given in the DRF documentation with the default User model. My goal: Users will be able to PUT/PATCH/DELETE the information in their own account, but won't be able to change any other users’ information.
My user serializer:
class UserSerializer(serializers.ModelSerializer):
rides = serializers.PrimaryKeyRelatedField(many = True, queryset = Ride.objects.all())
class Meta:
model = User
fields = [
'pk',
'username',
'first_name',
'last_name',
'email',
'password',
'rides'
]
write_only_fields = ['password']
read_only_fields = ['pk', 'username']
The IsOwnerOrReadOnly code:
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.owner == request.user
The problem is with the line return obj.owner == request.user. It always returns AttributeError: 'User' object has no attribute 'owner’. Why is the "owner" of a user not the user itself? How can I say “owner = this object” so that my permissions classes work?
Follow-up question: I would like to eventually extend the default User model to add more fields. Would my permissions still work if I made a new User model by inheriting from AbstractBaseUser? (I just started learning Django, so I'm not entirely sure what the process for creating a new User model would be. But I thought it might be good to mention in case that would change how I define permissions.)
You're getting the error because there's no such abstract concept as owner for all models. You will have to define the relationship between the object and the user in the model and apply that in the permission for each model.
In this case, you want to be sure that the user is trying to modify his own user object so you can just do this:
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj == request.user
So in essence, this will only work for the User model and you will have to extend it to work for other models based on the relationship of the model with the user.
You may want to define different permission classes for each resource than trying to put all the logic in the same permission class.
As for your question on extending the user model. This page in the docs explains the different methods of extending the existing user model which is basically by either extending from the AbstractUser or AbstractbaseUser. The Django permissions framework will still work as it is implemenmted against those base classes.
Bear in mind however that DRF permission classes are different from Django permissions. You can Django permissions to implement the logic inside the permission classes but they can be implemented without the permissions.
EDIT FROM COMMENT
If you have a Ride model with owner, you can do this to combine them.
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
if isinstance(obj, Ride):
return obj.owner == request.user
return obj == request.user
Ownership
Say there is a Model A. To define ownership, you need to create a field in Model A that will point to User model. You can do this easily. Make sure you use django.contrib.auth.get_user_model for same.
Getting a list of owned objects from API
When it comes to getting a list of objects owned by the authenticated user (request.user), you are looking at creating a filter. To do same there are two ways:
Create a filter class and use in it generic views. This is more extensible. I have done same in drfaddons.filters.IsOwnerFilterBackend. Here's the source code.
Override def filter_queryset(self, queryset) in each API Class.
Ensuring in Retrieve/Update/Delete
This time you want to make sure that permission check is being applied. Here, you will want to implement has_object_permission and has_permission from permissions.BasePermission which you're doing.
In general, you'll want to use both filtering and permission check.
Implementing across the project
To implement this across the whole project, you need to set your filter and permission as default one in settings.py's REST_FRAMEWORK configuration. You'll also need to ensure that an owner field (with same name preferably) is present in each model. (Check Abstract model).
I have done same in all my project and hence, I have created a package of the same. Check DRF Addons. You can also install it via pip: pip install drfaddons. It will do all of the above said task.

Django REST auth - users stored in external service

I've been wondering the best way to handle the case where a Django is used in a service-oriented architecture, so individual Django applications do not maintain their own user stores. One service maintains the user store, and others must call it in order to retrieve user information.
So far example, here is how I was thinking of building a custom authentication class in Django REST to handle this:
class SOAAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
token = request.get_token_from_auth_header()
if not remote_auth_service.is_token_valid(token):
raise AuthFailed('Token is invalid')
user_properties = remote_users_service.get_user(token):
# user_properties is a dict of properties
if not user_properties:
raise AuthFailed('User does not exist.')
user = MyCustomUserClass(**user_properties)
return (user, 'soa')
So no user info would get persisted in the Django application's database, and permission classes could interrogate the instance of MyCustomUserClass to figure out what groups a user belongs to.
Would this approach work for simple group-based authorization? My think is that I don't need object-level permissions, so there's no need to create rows for my users in the Django database.

How to get user permissions for non supervisor user by using request user attribute

I developed an django app which register user and give resources based on resource level permissionIn this I am using django basic level permissions on my model and templates, there for view permission I set permission tuple in my model like:
class Model(AbstractUser):
group = models.ForeignKey(AppGroup)
class Meta:
permissions = ( ('view_app', 'user can view app'), )
and I migrate my model after create my model like above.
Now for permissions, I created a group from admin and including all app view/change/delete permissions, using that group I generated a drop down in form class. Now user(admin) can create other users based on selected permissions and after register successfully the new user able to login successfully and access all resources but when I am trying to access user permissions which is a many-to-many relationship using like
class UserListView(ListView):
def get_queryset(self):
print(self.request.user.user_permissions.all())
return super(UserListView, self).get_queryset()
When I list my view, it gives me a relation error (500 error):
relation views_list_user_permission does not exist
Now when I access the same view by superuser it gives me all permissions, but from a user which is neither superuser nor staff it spit out the above error. By reviewing djancgo.contrib.auth.models PermissionMixin class code it seems like to me the user_permissions m2m field can only access by superuser but I doubt it. So this is what I am doing and got the issue, please correct me if I take this in wrong way
The superuser has all the set of permissions granted. Therefore you are able to see all the permissions. But when a new user is created he will not have any of the permissions set therefore there is no relation between the user and permissions so you are getting the above error.
Note:-
You can check for the available permissions for the logged in user inside template by using
{{ perms }}
For a specific app:-
{{ perms.app_name }}
For a specific model:-
{{ perms.app_name.model_name }}
Suppose you want to grant access to a user with specific permission to a particular model for a view you can use the permission required decorator like this:-
from django.contrib.auth.decorators import permission_required
#permission_required('polls.can_vote')
def my_view(request):
...
Now here the user with the permission can_vote on "polls" will be allowed the access grant.
For further detailed use you can refer:-
Django documentation on permissions.
The authentication back-end is responsible for user permissions. I guess you are using your own custom authentication back-end. However if you are doing so you may have forgot to import ModelBackend.
from django.contrib.auth.backends import ModelBackend
Now make sure to extend this back-end into your own custom back-end
class EmailBackend(ModelBackend):

Django Custom Permission for Authorization

I am working on a Django Project, where one model (lets say Document) has the following field:
#In models.py
class Document (models.Model):
choice = (('Yes','Yes'), ('No','No'))
authorized = models.CharField (max_length=3, choices=choice, default='No')
Now, as a normal user creates a Document object, the authorized field is no. However, the superior needs to authorize the same before it is actually approved. Now, is there a permission system in django where one user can create an object but not authorize, whereas some other user - who has the permission - can authorize? If not, is the only way to do it is to create a custom field in user model and check it every time?
First of all, why you need to store possible values in CharField and not in BooleanField? I think you should consider changing to BooleanField.
You can do that by providing custom ModelAmin class in admin.py:
from django.contrib import admin
from .models import Document
#admin.register(Document)
class DocumentModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if not request.user.is_superuser:
self.exclude = ['authorized']
return super(DocumentModelAdmin, self).get_form(request, obj, **kwargs)
So now on admin page of Document if it is not a superuser, user won't see authorized field. You can change that code for checking if it should be specific user, or has some permissions, or if user belongs to some Group and etc.
UPDATE
If you want it to be in general views, you can just pass different forms to users, depend on their roles|permissions|groups(i don't know how your so called senior is different from rest of the users). So the answer would be: create two forms, then pass on of them in template based on your request.user attributes.
Django has awesome auth system. I couldn't understand you scenario.
But you could try something like this below
By default every Model object comes with three Permission object like (add_document, change_document and delete_document in your case).
If you want some custom permission you can add it in model Meta class like this:
You can add these permission to User object or Group object.
models.py
class Document (models.Model):
######
class Meta:
permissions = (("Can see document dashbaord", "see_document" ),)
and run python manage.py migrate to create new Permission object with codename as "see_document".
You can implement permissions in request handled by view like this:
view.py
from django.contrib.auth.mixins import PermissionRequiredMixin, permission_required
# For function based view
#pemission_required('document.see_document')
def someview(request):
######
pass
# For class based views
class SomeView(PermissionRequiredMixin, BaseView):
permission_required = 'document.see_document'
This could redirect any user with out the permssion to permission denied page. For more go through this https://docs.djangoproject.com/en/1.10/topics/auth/

Django restrict views by User permissions which are editable in the Admin

I have a Django application where I need to restrict specific Views to subset of Users. I also want to be bale to edit which Users have this permission via the Django Admin. So in the admin I would like to be able to see all users and have a check box which can be checked to give permission to see this specific Views.
I believe the way to approach to this is to a permissions decorator on the Views in question:
from django.contrib.auth.decorators import permission_required
#login_required
#permission_required('user.can_view_restricted', login_url='/accounts/login/')
def Restrictedview(request, template_name='restricted.html'):
...
# restricted stuff
Now I know I need to define this permission (in permissions.py?), and register it with the Admin. I am unsure of how to do this and how to properly associate the permission with a specific User instance. Should this be an extra field on 'User', or a separate model to hold model to hole Users and Permissions?
You can read in details about django permissions in the docs
https://docs.djangoproject.com/en/dev/topics/auth/default/#permissions-and-authorization
Basically Django permissions use the Permission model, which is found at django.contrib.auth.models, but for most applications you don't need to directly import or use that model.
By default Django creates 3 default permissions for any model you have in your app. If you have a model named MyModel in an app named myapp, then Django will create create_mymodel, change_mymodel, and delete_mymodel permissions by default.
You can check if the user has a certain permission by calling
user.has_perm('myapp.create_mymodel')
if you're checking for the create permission for example. Or, like you did, you can use the decorator
permission_required('myapp.create_mymodel')
In addition to the default permissions provided by django, you can define custom permissions on your models by specifying the permissions attribute in the Meta class of your model like this:
class MyModel(models.Model):
[...]
class Meta:
permissions = (
("can_deliver_pizzas", "Can deliver pizzas"),
)
More on defining custom permissions here: https://docs.djangoproject.com/en/dev/ref/models/options/#permissions
By default, permissions can be easily edited for every user using the admin interface. Just visit a certain user's page and there will be a field named User Permissions with a list of all permissions in your project, from which you can add or remove permissions for your particular user.