Raise conditional permission error in get_queryset DRF - python

I want to get all users of an organization by uuid. I am following REST standards so I want my url to look like 'organizations/uuid/users/'. If super admin hits this API, it should be allowed but If an admin user tries using this API, then it should only allow if the admin belongs to the same organization for which users are requested. I used ListAPIView generic view class and I was able to get list of all users in an organization from admin of different organization but it still returns info when it should return 403 error.
urls.py
path('organizations/<uuid:pk>/users/', OrganizationUsersView.as_view()),
views.py
class OrganizationUsersView(ListAPIView):
serializer_class = UserSerializer
permission_classes = (IsAuthenticated, IsSuperAdmin|IsAdmin,)
def get_queryset(self):
uuid = self.kwargs['pk']
if self.request.user.role == 'admin':
if self.request.user.org.id != uuid:
return IsOrganizationAdmin()
org = Organization.objects.get(id=uuid)
return User.objects.filter(org=org)
models.py
class Organization(models.Model):
name = models.CharField(max_length=255, null=False)
class User(AbstractBaseUser):
....
other fields
....
org = models.ForeignKey(Organization, on_delete=models.CASCADE, null=False, related_name='users')

Related

Edit permissions for Django Details View for api calls

I am very new to django and currently trying to generate api calls to localhost:8000/stateapi/id where id is an id for a single "State" object in a json (like 1, 2, etc).
It is using token authentication by passing a username and password to the "get-token" endpoint and then using that token for calls to the stateapi endpoint.
I mostly followed this tutorial https://scotch.io/tutorials/build-a-rest-api-with-django-a-test-driven-approach-part-2
and keep getting a "detail": "You do not have permission to perform this action."
Here are the views where CreateView handles the "localhost:8000/stateapi" endpoint and DetailsView handles the localhost:8000/stateapi/id endpoint.
class CreateView(generics.ListCreateAPIView):
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = (permissions.IsAuthenticated,IsOwner)
def perform_create(self, serializer):
"""Save the post data when creating a new State."""
serializer.save(owner=self.request.user)
class DetailsView(generics.RetrieveUpdateDestroyAPIView):
"""This class handles the http GET, PUT and DELETE requests."""
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = (permissions.IsAuthenticated,IsOwner)
I can't seem to figure out why the authenticated user has permission to access information from CreateView but not DetailsView.
Here is the permissions code:
class IsOwner(BasePermission):
"""Custom permission class to allow only bucketlist owners to edit them."""
def has_object_permission(self, request, view, obj):
# Our problem is that we do not have a owner property for the object
"""Return True if permission is granted to the bucketlist owner."""
return obj.owner == request.user
Upon testing what happens when DetailsView is called, i've found that obj.owner is "None" when DetailsView is called and obj.owner is correctly equal to request.user whenever CreateView is called which would explain why the authenticated user can make get requests to the endpoint without an id while it cannot for the endpoint with an id.
Are there an suggestions as to how I could either:
a) make sure obj has the correct "owner" property (something that CreateView is doing but DetailsView is not)
b) change my permissions in some way
c) something else I cannot think of.
Thanks!
Can you share your State model and StateSerializer – Iain Shelvington Jun 18 at 3:26
State Model:
from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from django.dispatch import receiver
# Create your models here.
# 1 is /, 2 is -, 3 is (, 4 is ), 5 is .
class State(models.Model):
STATE = models.CharField(max_length=30,blank=True,null=True)
Team_Contact = models.CharField(max_length=100,blank=True,null=True)
CONTACT_INFORMATION = models.TextField(blank=True,null=True)
LEGISLATION1EXECUTIVE_ORDER = models.TextField(blank=True,null=True)
TESTING = models.TextField(blank=True,null=True)
TESTING1DEPLOYMENT_REQUIREMENTS_3SELF_CERTIFICATION4 = models.TextField(blank=True,null=True)
PRE2EMPTION = models.TextField(blank=True,null=True)
owner = models.ForeignKey('auth.User', related_name='statelists', on_delete=models.CASCADE,blank=True,null=True)
OVERSIGHT_DEPARTMENT = models.TextField(blank=True,null=True)
INFRASTRUCTURE_DEVELOPMENTS = models.TextField(blank=True,null=True)
CRASHES1SAFETY_INCIDENTS = models.TextField(blank=True,null=True)
DATA1PRIVACY_CONCERNS = models.TextField(blank=True,null=True)
PUBLIC_EDUCATION_FOR_AVS = models.TextField(blank=True,null=True)
LIABILITY1INSURANCE_REQUIREMENTS = models.TextField(blank=True,null=True)
HEALTH1EQUITY_CONCERNS = models.TextField(blank=True,null=True)
MISC5 = models.TextField(blank=True,null=True)
def __str__(self):
"""Return a human readable representation of the model instance."""
return "{}".format(self.STATE)
# This receiver handles token creation immediately a new user is created.
#receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
Serializers:
from rest_framework import serializers
from .models import State
from django.contrib.auth.models import User
class StateSerializer(serializers.ModelSerializer):
"""Serializer to map the Model instance into JSON format."""
# understand exactly what this line does
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
"""Meta class to map serializer's fields with the model fields."""
model = State
fields = ('id','STATE','Team_Contact','CONTACT_INFORMATION','LEGISLATION1EXECUTIVE_ORDER','TESTING',
'TESTING1DEPLOYMENT_REQUIREMENTS_3SELF_CERTIFICATION4','PRE2EMPTION','OVERSIGHT_DEPARTMENT','INFRASTRUCTURE_DEVELOPMENTS',
'CRASHES1SAFETY_INCIDENTS','DATA1PRIVACY_CONCERNS','PUBLIC_EDUCATION_FOR_AVS','LIABILITY1INSURANCE_REQUIREMENTS',
'HEALTH1EQUITY_CONCERNS','MISC5', 'owner')
read_only_fields = ('STATE', 'Team_Contact','CONTACT_INFORMATION')
class UserSerializer(serializers.ModelSerializer):
"""A user serializer to aid in authentication and authorization."""
states = serializers.PrimaryKeyRelatedField(
many=True, queryset=State.objects.all())
class Meta:
"""Map this serializer to the default django user model."""
model = User
fields = ('id', 'username', 'states')
All things here seem to be normal. I think the problem comes from other parts of your code? Or your checked object actually doesn't have any owner linked to it?

Django - Custom Object Permission View

I'm trying to give shop owner permissions for a view. So I made a file in which I created different permissions. In my permission I first of all check if the user was logged in with a has_permission function. I am now trying to determine if a user actually owns the shop with the has_object_permission function. Unfortunately, I don't feel that my function was performed correctly.
I can always, despite my permission, make a request from any account, shop owner or not.
Here are the models I use:
models.py
class Shop(models.Model):
name = models.CharField(max_length=255)
category = models.ForeignKey(ShopCategory, on_delete=models.SET_NULL, null=True, blank=True)
description = models.TextField(blank=True, null=True)
path = models.CharField(max_length=255, unique=True, null=True, blank=True) # Set a null and blank = True for serializer
mustBeLogged = models.BooleanField(default=False)
deliveries = models.FloatField(default=7)
def __str__(self):
return self.name
class UserShop(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
shop = models.ForeignKey(Shop, on_delete=models.CASCADE)
def __str__(self):
return f"{self.user.name} {self.user.surname} - {self.shop.name}"
Here are my permissions :
utils.py
class IsOwner(BasePermission):
"""
Check if the user who made the request is owner.
Use like that : permission_classes = [IsOwner]
"""
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
try:
user_shop = UserShop.objects.get(user=request.user, shop=obj)
return True
except:
return False
class OwnerView(APIView):
"""
Check if a user is owner
"""
permission_classes = (IsOwner,)
Here is my view :
views.py
class ShopDetail(OwnerView):
"""Edit ou delete a shop"""
def put(self, request, path):
"""For admin or shop owner to edit a shop"""
shop = get_object_or_404(Shop, path=path)
serializer = ShopSerializer(shop, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
And here is my serializer :
serializers.py
class ShopSerializer(serializers.ModelSerializer):
class Meta:
model = Shop
fields = '__all__'
Thank you in advance for your help
As mentioned in the documentation for Custom permissions:
Note: The instance-level has_object_permission method will only be called if the view-level has_permission checks have already
passed. Also note that in order for the instance-level checks to run,
the view code should explicitly call
.check_object_permissions(request, obj). If you are using the
generic views then this will be handled for you by default.
(Function-based views will need to check object permissions
explicitly, raising PermissionDenied on failure.)
You have implemented the put method yourself and get the object yourself instead of using the get_object method (which calls check_object_permissions itself) so check_object_permissions is never called. Instead you should set lookup_field as path in your view class and use get_object:
class ShopDetail(OwnerView):
"""Edit ou delete a shop"""
queryset = Shop.objects.all()
lookup_field = 'path'
def put(self, request, path):
"""For admin or shop owner to edit a shop"""
shop = self.get_object()
serializer = ShopSerializer(shop, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
Also to do this OwnerView should inherit from GenericAPIView:
from rest_framework.generics import GenericAPIView
class OwnerView(GenericAPIView):
"""
Check if a user is owner
"""
permission_classes = (IsOwner,)

Django rest framework auto-populate filed with user.id/username

I'm trying to link 'owner' field of my model to an AbstractUser. I need it to be done automatically, the only think i'm able to do by myself is to allow user logged in to choice between every existing user with, what's not what i want. I would like to not have a field to manipulate, but a outcome serializer with id or username of User that added the model. I'm trying to find solutions for a few days, I've tried already combine ForeignKey, PrimaryKeys, OneToOneField, HiddenField, get_user, perform_create, but I'm for sure doing something wrong, and i'm almost lost with it. The last thing i tried is to def_perform in views like DRF QuickStart tutorial say, but without results.
I add some code sample to be more understandable:
There is my AbstractUser model:
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserProfile(AbstractUser):
username = models.CharField(max_length=20, unique=True)
...
i added it to AUTH_USER_MODEL = in the settings.
And there is other model which i want to link with User:
from django.db import models
from users.models.user import UserProfile
class MyPhoto(models.Model):
owner = models.ForeignKey(UserProfile, related_name='photos', on_delete=models.CASCADE, null=True)
image = models.ImageField(upload_to='Images')
serializer.py
class MyPhotoSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = MyPhoto
fields = ('pk', 'image', 'owner')
def create(self, validated_data):
photo = MyPhoto.objects.create(
image=validated_data['image']
)
photo.save()
return photo
views.py
class UpdateMyPhotoViewSet(viewsets.ModelViewSet):
queryset = MyPhoto.objects.all()
serializer_class = MyPhotoSerializer
permission_classes = (IsAuthenticated,)
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
and for now i can't see the owner field results.
Thanks in advance.

How to handle multiple user types with Django 1.7 user model

I am very new to Python and Django. I am trying to setup user model for different roles like Agents, Brokers, Company and Customer. Each of these roles can register with the site as a user. Then Agents and Brokers can have public facing profile pages.
Do I have to use custom user model or built in user model will work? I have additional properties like license, location, languages, photo etc for Agents and Brokers.
class MyUser(AbstractBaseUser):
AGENTS = 'AG'
BROKERS = 'BR'
COMPANY = 'CP'
CUSTOMER = 'CM'
ROLE_IN_CHOICES = (
(AGENTS, 'Agent'),
(BROKERS, 'Broker'),
(COMPANY, 'Company'),
(CUSTOMER, 'Customer'))
first_name = models.CharField(max_length=100, blank=True)
second_name = models.CharField(max_length=100, blank=True)
middle_name = models.CharField(max_length=100, blank=True)
dob = models.DateField(blank=True, null=True)
phone = models.CharField(max_length=10)
secondary_phone = models.CharField(max_length=10, blank=True, null=True)
......
#property
def is_agent(self):
return self.role in (self.AGENTS)
#property
def is_customer(self):
return self.role in (self.CUSTOMER)
#property
def is_broker(self):*
return self.role in (self.BROKER)
#property
def is_company(self):
return self.role in (self.COMPANY)
....
Can I not use base User model and achieve same? Am I on write track?
How do I create public facing pages for these roles (Agents, Brokers)?
This is my first attempt with Python and Django. I am using Django 1.7.7 with Python 3.4
You should extend from the Django User model instead and add the extra fields you need:
from django.contrib.auth.models import User as Auth_User
class User(Auth_User):
# add your extra fields here like roles, etc
phone = CharField(max_length=20, null=True, blank=True)
# add your extra functions
def extra_user_function(self):
return "This is an extra function"
This way you have your own fields and also the Django User fields...
After migrating, if you check the database, you'll have auth_user and your_app_user tables.
Just bare in mind that request.user will only give you the super fields... In order to get the extended class you'll need
User.objects.get(id=request.user.id)
The latest will only have the extra fields and its id will be the same as the auth.User if you don't add any auth_user by itself.
Attention: this is important!
Otherwise request.user.id and your_app.User.id don't match, therefore User.objects.get(id=request.user.id) won't work and you'll have to query the db to find the your_app.User.id
User.object.get(user_ptr_id = request.user.id)
Other things to consider
This will work:
# you_app.User objects gets vars from auth.User
user = User.objects.get(id=request.user.id)
first_name = user.first_name
But this won't work
# auth.User trying to get a function from your_app.User
user = request.user
user.extra_user_function()
So the User model could be something like this:
import os
from django.contrib.auth.models import User as Django_User
from django.db.models import CharField, ImageField
class User(Django_User):
phone = CharField(max_length=20, null=True, blank=True)
observations = CharField(max_length=2048, null=True, blank=True)
picture = ImageField(upload_to='users', default='default/avatar.jpg')
class Meta:
# adding extra permissions (default are: add_user, change_user, delete_user)
permissions = (
("access_user_list", "Can access user list"),
("access_user", "Can access user"),
)
ordering = ["-is_staff", "first_name"]
Instead of creating roles on the user, Django already has groups, so you should use them.
The groups follow the same logic:
from django.contrib.auth.models import Group as Auth_Group
from django.db import models
class Group(Auth_Group):
observations = models.CharField(max_length=2048, null=True, blank=True)
def get_users_in_group(self):
return self.user_set.filter(is_active=1).order_by('first_name', 'last_name')
def count_users_in_group(self):
return self.user_set.count()
def __unicode__(self):
return self.name
class Meta:
permissions = (
("access_group_list", "Can access group list"),
("access_group", "Can access group"),
)
ordering = ["name"]
You can clear / add users to a group:
user.groups.clear()
user.groups.add(group)
Clear / add permissions to the group:
group.permissions.clear()
group.permissions.add(permission)
There is also a decorator to check if a user has permissions
from django.contrib.auth.decorators import permission_required
#permission_required(("users.change_user","users.access_user",))
def your_view(request):
...
I've tried many things in the past, but I guess this is the way to go.
If you really need roles, and a user can have more than one role, mayb the best thing would be to create a model Role and add that to the user has a ManyToMany Field
roles = ManyToManyField(Role)
but you could do that with groups

Sending patch request to Django Rest Framework API with Foreign Key

I need to send put request to API for which I know only the foreign key.
How am supposed to do this.
models.py
class Company(models.Model):
name = models.CharField(max_length = 100)
user = models.OneToOneField(settings.AUTH_USER_MODEL, unique = True)
serializer.py
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('id', 'name','user')
views.py
class Company(object):
permission_classes = (IsAuthenticated,IsUserThenReadPatch)
serializer_class = CompanySerializer
def get_queryset(self):
user = self.request.user
return Company.objects.filter(user = user)
class CompanyDetails(Company, RetrieveUpdateAPIView, APIView):
pass
urls.py
url(r'^company/$',views.CompanyDetails.as_view()),
In order to enable all CRUD operations in DRF you probably want to use ViewSet instead of View:
# views.py
class CompanyViewSet(ViewSet):
permission_classes = (IsAuthenticated,IsUserThenReadPatch)
serializer_class = CompanySerializer
def get_queryset(self):
user = self.request.user
return Company.objects.filter(user = user)
# urls.py
router = routers.SimpleRouter()
router.register(r'company', CompanyViewSet)
urlpatterns = router.urls
The above will allow you do send all CRUD REST requests:
GET /company - list all companies
POST /company - create a company
GET /company/:id - get a single company
PUT/POST /company/:id - update a company
PATCH /company/:id - partially update a company
DELETE /company/:id - delete a company
You can read more in the DRF docs - viewsets and routers

Categories

Resources