DRF - Prevent users from referencing objects that do not belong to them - python

I have two models like so with a parent child relation:
models.py
class Bank(...):
user = models.ForeignKey('User', ...)
class Account(...)
bank = models.ForeignKey('Bank', ...)
user = models.ForeignKey('User', ...)
I am using DRF and want to provide API access to these models. I want to ensure that Users can only access their own data. On the viewsets I can retrict the querysets to just the objects the user "owns" like so:
views.py
class BankViewSet(...):
def get_queryset(self):
return self.queryset.filter(
user = request.user
)
And I can do the same for Accounts.
However, how can I stop a user from creating an Account via POST request with a Bank that they do not own? I want to ensure that users can only create Accounts that belong to a Bank that they own.
How can I enforce/check that the Bank in the Account POST request contains the same user as the requestor?

You can create a field-level validation on the AccountSerializer class, as
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = (
"bank",
"field_1",
"field_2"
)
def validate_bank(self, bank_instance: Bank):
if bank_instance.user == self.context["request"].user:
return bank_instance
raise serializers.ValidationError("Not belongs to you!!!")

Related

New ManyToManyField object is not adding

I am building a BlogApp AND i made a feature of Favorite Users in the ManyToManyField.
It means user_1 can add multiple favorite users in one field. I build a view to add users.
BUT when i create an instance to store favorite users from Admin before adding favorite users from site AND then if i add favorite users from site then they are adding. BUT if there is not already a instance of request.user's FavouriteUsers in Admin then it is not adding and creating a new object.
So, A new object for storing favorite users of request.user is not adding, AND if object is already there then they are adding.
models.py
class FavoriteUsers(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
favorite_users = models.ManyToManyField(User, related_name='favorite_users', blank=True)
views.py
def AddFavUsers(request,user_id):
obj = FavoriteUsers.objects.filter(user=request.user)
user = get_object_or_404(User,id=user_id)
for ob in obj:
ob.favorite_users.add(user)
ob.user.add(request.user)
ob.save()
return redirect('home')
Image of already created object
When object is already , manually created then favorite users are adding BUT if it is not then new object is not creating.
Any help would be much Appreciated.
Thank You in Advance.
Your view is basically adding the user to all FavoriteUsers that are linked to the User. But it is not said at all that there are such FavoriteUsers.
We thus can construct a FavroiteObject with:
def AddFavUsers(request,user_id):
obj = FavoriteUsers.objects.create(
user=request.user
)
obj.favorite_users.add(user_id)
# …
the modeling is also a bit odd, since we here create a third table, it makes more sense to construct a model Favorite with two ForeignKeys that thus acts as a junction table. So this should look like:
from django.conf import settings
class Favorite(models.Model):
user_to = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name='favorites_from'
)
user_from = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name='favorites_to'
)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user_from', 'user_to'], name='favorite_only_once')
]
If you then want to make a user_id a favorite of the logged in user, you can work with:
from django.contrib.auth.decorators import login_required
#login_required
def AddFavUsers(request, user_id):
Favorite.objects.create(user_from=request.user, user_to_id=user_id)
# …
You can then obtain the favorite users of a user with:
User.objects.filter(favorites_from__user_from=my_user)
where my_user is the user from which you want to obtain the favorite users.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
The ManyToMany field already handles this. You can add a favorite_users property to your User model, which will point to the User model and Django will create the connecting table. Beware of the symmetrical parameter, if you leave it out it'll be a bidirectional relation.
class MyUser(models.Model):
...
favorite_users = models.ManyToManyField("MyUser", symmetrical=False, blank=True)
If you still want to write the connecting model yourself, check out the through parameter.

Separate permissions for records in a table in Django Admin

Let's suppose I have different users in my Django project and a model such that each record belongs to a certain user. The users are supposed to have an access to Admin panel.
class MyModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
...
Is there a standard way in Django to make any user list, create and edit ONLY HIS instances of MyModel in Admin panel, hiding the ones created by other users?
You can override queryset for your model admin. like this:
class MyModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.filter(user=request.user)
return qs
This will filter out instances based on the user they belong to.

Implement roles in django rest framework

I am building an API that should have the following kind of users
super_user - create/manage admins
admin - manage events(model) and event participants
participants - participate in events, invited to events by admins
Additional i want to have each type of user to have phone number field
I tried
class SuperUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=20)
class Admin(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=20)
class Participant(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=20)
But gut is telling me its a wrong way to handle this. Can someone please help.
One possible solution is:
Have only one User Model with role field, which defines what user role is.
Create a User Group and add each group needed permissions.
Add User to User Group
Limit access using a Django REST Framework (later DRF) Permission Class.
Explanation:
Using only one user model is a more simple and flexible solution. You can query all users, or filtered by feature (like user role). Standart Django auth system expects one UserModel.
Read more about Django user groups. See "Django Permissions Docs #1" and "Django Groups Docs #2". Also useful is "User groups and permissions".
You need to create a group for each user role, and add needed permissions for each group. (Django has a default model permission, created automatically, look at the docs on the given links) or create the needed permission manually in the model definition.
Manually or using a script, add User to the needed group by defining his role when a user is created or manually by Django Admin interface.
Now everything should be ready for limited access by the user's role. You can easily limit access to the DRF View using a permission class. See more information in the "DRF Permission Docs".
Let's define our own:
from rest_framework.permissions import DjangoModelPermissions
# Using DjangoModelPermissions we can limit access by checking user permissions.
# Rights need only for CreateUpdateDelete actions.
class CUDModelPermissions(DjangoModelPermissions):
perms_map = {
'GET': [],
'OPTIONS': [],
'HEAD': ['%(app_label)s.read_%(model_name)s'],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
# Or you can inherit from BasePermission class and define your own rule for access
from rest_framework.permissions import BasePermission
class AdminsPermissions(BasePermission):
allowed_user_roles = (User.SUPERVISOR, User.ADMINISTRATOR)
def has_permission(self, request, view):
is_allowed_user = request.user.role in self.allowed_user_roles
return is_allowed_user
# ----
# on views.py
from rest_framework import generics
from .mypermissions import CUDModelPermissions, AdminsPermissions
class MyViewWithPermissions(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [CUDModelPermissions, ]
queryset = SomeModel.objects.all()
serializer_class = MyModelSerializer
You can add additional permission class to combine access limitation.
So in Django any user has a flag is_superuser that corresponds to your 'superuser'. So just use that - e.g. User.objects.create(is_superuser=True).
For the rest you can simply use a field for a normal User model to differentiate between subroles of a normal user.
class User(AbstractBaseUser):
can_participate_event = models.Boolean(default=False)
can_create_event = models.Boolean(default=False)
Or
class User(AbstractBaseUser):
permissions = models.CharField(default='') # and populate with e.g. 'create_event,participate_event'
Still you will need to check all those fields in your view probably. The more you add to your application, the hairier this becomes so I would suggest using a 3rd party library like rest-framework-roles (I'm the author) or guardian.

Django: Extending 'User' via OneToOneField - Accessing first_name

When I extend the Django User using a OneToOneField to create a Manager, the form to create a Manager in Django Admin only has fields for username and password.
class Manager(models.Model):
"""
Users who need to login to manage the listing of a Outlet
are represented by this model.
"""
manager = models.OneToOneField(User)
internal_notes = models.TextField(blank=True)
def __unicode__(self):
return self.manager.username
What is the right way to add the other built-in User fields like first_name etc to my Manager model?
It's not clear what you are asking here. The form to create a User (not a Manager) displays only three fields at first, to allow you to set the password, but then continues on to a second form where you can set other fields including first name.
Your Manager class doesn't need to define those fields, since they are attributes of the User class which you access via the one-to-one relation.

Basic Django REST framework - Write only resource

I am trying to create a simple service which allows anonymous users to submit their name and email. I want to AllowAny on adding their info, and IsAuthenticated on everything else. I'm having trouble getting this granularity.
models.py
from django.db import models
class Invitee(models.Model):
name = models.CharField(max_length=255)
email = models.EmailField(max_length=70,blank=True)
modified = models.DateTimeField(auto_now=True)
serializers.py
class InviteeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Invitee
fields = ('name', 'email')
def create(self, validated_data):
return Invitee(**validated_data)
views.py
class InviteeViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Invitee.objects.all()
serializer_class = InviteeSerializer
What and where should I put to make it so users can submit their name and email, but only admins can read, update, delete? Thanks any help.
The easiest and safest way to do this is with multiple serializers, one of each user class you need. You will also need to use custom permissions to enforce the read/write difference between authenticated and anonymous users.
class InviteeSerializer(serializers.HyperlinkedModelSerializer):
def create(self, validated_data):
return Invitee(**validated_data)
class LimitedInviteeSerializer(InviteeSerializer):
class Meta:
model = Invitee
fields = ('name', 'email', ) # a limited subset of the fields
class FullInviteeSerializer(InviteeSerializer):
class Meta:
model = Invitee
fields = ('name', 'email', "modified", ) # all of the fields
While right now it only looks like you need read/write, if you need full read/write/delete permissions I would recommend reading this Stack Overflow question.
You will also need to control what serializer is being used on the view level. This needs to be done using a combination of permissions and overridden view methods.
class InviteeViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Invitee.objects.all()
serializer_class = LimitedInviteeSerializer
def get_serializer_class(self):
if self.request.user.is_active:
return FullInviteeSerializer
return super(InviteeViewSet, self).get_serializer_class()
On the view you will need to override get_serializer_class to determine what serializer should be used based on if the user is active. Anonymous users should never be marked as active, so this is the best way to check while excluding deactivated accounts.
You will also need to create a custom permissions class that will do the opposite of the built-in IsAuthenticatedOrReadOnly permission. You are looking for authenticated users to do everything, and anonymous users to only be write-only. I've called this class IsAuthenticatedOrWriteOnly to match the other permission class.
class IsAuthenticatedOrWriteOnly(BasePermission):
"""
The request is authenticated as a user, or is a write-only request.
"""
def has_permission(self, request, view):
WRITE_METHODS = ["POST", ]
return (
request.method in WRITE_METHODS or
request.user and
request.user.is_authenticated()
)
You just need to add this your existing list of permission classes, or override it manually it on the view using permission_classes.

Categories

Resources