How to set permissions for a POST request in ModelViewSet's - permissions

How can I write my own permission class for POST requests when using ModelViewSet?
I already tried to write my own permission_classe with no success. Even if my permission class is returning false it is still granting access to the post request
models.py
class Building(models.Model, HitCountMixin):
user = models.ForeignKey(User, on_delete=models.CASCADE) limit_choices_to=Q(country=2921044) | Q(country=798544), on_delete=models.SET_NULL) #<------------ Eltern Element
name = models.CharField(max_length=200, null=True, blank=True)
description = models.TextField(max_length=2000,null=True, blank=True)
facilities = models.TextField(max_length=2000, null=True, blank=True)
...
views.py
class BuildingImageViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
serializer_class = BuildingImageSerializer
permission_classes = (permissions.IsAuthenticated, IsOwner,)
def get_queryset(self):
if self.request.user.is_authenticated:
return BuildingImage.objects.filter(building__user=self.request.user)
return None
permissions.py
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
print("TEST")
return False
urls.py
router = routers.DefaultRouter()
router.register(r'buildingimages', myrest_views.BuildingImageViewSet, base_name="buildingimage")
If I I try to upload an image it is working, Why?
My IsOwner permission class is evaluated because I can see the print line with "TEST" in the console.
MY SOLUTION:
def has_permission(self, request, view):
if view.action == 'create':
building_url = request.POST.get('building')
building_path = urlparse(building_url).path
building_id = resolve(building_path).kwargs['pk']
building = Building.objects.get(id=building_id)
return building.user == request.user
return True

Pass list of classes, you used has_object_permission(), You need to write code inside has_permission() method.
permission_classes = [<class 'rest_framework.permissions.AllowAny'>]
you have to pass class that derive BasePermission class
permission.py
from rest_framework import permissions
class IsOwner(permissions.BasePermission):
def has_permission(self, request, view):
if <CONDITION>:
return True
else:
return False

Related

Authentication with sub routes in SQLAlchemy in flask

Hi I was implementing a login verification to restrict certain routes on a website. Users are created successfully and if a user does not exist, they cannot log in. However, when attempting to access a route that requires a login, changing the username still allows access to the route. Do you know why this might be happening?
This is the code:
from flask import Flask, redirect, flash, request, render_template, url_for, make_response
import secrets
from flask_login import login_required, LoginManager, login_user, UserMixin
from flask_sqlalchemy import SQLAlchemy
app = Flask("test")
secret_key = secrets.token_hex(16)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
app.config['SECRET_KEY'] = secret_key
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.init_app(app)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
games = db.relationship('Game', backref='user', lazy=True)
class Game(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
moves= db.Column(db.String(500), nullable=False)
status = db.Column(db.String(5), nullable=False)#Si o no
color = db.Column(db.String(20), nullable=False)
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
#login_manager.unauthorized_handler
def unauthorized():
return redirect(url_for("login"))
#app.route('/')
def home():
return render_template('begin.html')
#app.route('/login',methods=['GET','POST'])
def login():
if request.method == 'POST':
name= request.form['user']
psw = request.form['psw']
user = User.query.filter_by(name=name).first()
if user and user.password==psw:
login_user(user)
route = '/'+str(user.name)+'/home'
return redirect(route)
else:
return render_template('login.html',resultado='user not found')
else:
return render_template('login.html')
#app.route('/registrar',methods=['GET','POST'])
def registrar():
if request.method == 'POST':
user = request.form['user']
email = request.form['email']
psw = request.form['psw']
confirm_psw = request.form['confirm_psw']
if psw != confirm_psw:
return render_template('registrar.html',validacion="Passwords do not match")
new_user = User(name=name, email=email, password=psw)
db.session.add(new_user)
db.session.commit()
return redirect('/login')
else:
return render_template('registrar.html')
#app.route('/<user>/home',methods=['GET','POST'])
#login_required
def main_page(user):
return render_template('home.html',user=user)
UPDATE
After some research I know the problem is with the url in the main_page. When I change the user in the url the current_user doesn't change so the current_user is the user that make login and the login_required doesn't look for the new user. I don't know if I am correct because I tried to fixed it but it doesn't work
The first thing to underestand is that your logged in user is being loaded every time they go to a route, and we define this behaviour in this seccion of code:
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
Whats happening here is that we are loading the user using it's user_id which is stored in flask's session object, as you can see in this line of code User.query.get(int(user_id))
So the reazon it doesn't prompt the user to login again is because the user_id hasn't changed even when the username was changed.
What you could do if you want that to happen is to clear the session when you change the username, like this:
from flask import session
# Your code where you change the username
session.clear()

DRF update view with many to many field

Am trying to write update view,but got an error please help me to find the problem,thanks :)
At first I have many to many field in my model.It is my model
class Portfolio(models.Model):
name = models.CharField(max_length=50, unique=True, blank=False, null=True)
market = models.ForeignKey(Market, on_delete=models.DO_NOTHING, related_name='market')
investor = models.ForeignKey('accounts.User', on_delete=models.DO_NOTHING, related_name='investor')
assets = models.ManyToManyField(Assets, related_name='assets')
def __str__(self):
return self.name
After that I have a serializer for my view:
class PortfolioSerializer(serializers.ModelSerializer):
class Meta:
model = Portfolio
fields = ['name', 'market', 'investor', 'assets']
And it's my view:
class PortfolioUpdateView(APIView):
serializer_class = PortfolioSerializer
def put(self, request, *args,):
data = request.data
portfo = Portfolio.objects.get(id=id)
print(portfo)
serilize = self.serializer_class(instance=request.user, data=request.POST)
if serilize.is_valid():
name = serilize.data['name']
market = Market.objects.get(pk=int(request.POST.get('market', '')))
assets = Assets.objects.get(pk=int(request.POST.get('assets', '')))
Portfolio.objects.update(name=name, market=market,
assets=assets,
)
return portfo
else:
pass
and at the end it is my error:
TypeError at /market/update/1
put() got an unexpected keyword argument 'id'
I found the answer by my self,because I needed to use id for get obj so I used request.data that is body's data of object include obj's id and added query-set method for getting the class objs
class PortfolioUpdateView(viewsets.ModelViewSet):
serializer_class = PortfolioSerializer
def get_queryset(self):
portfolio = Portfolio.objects.all()
return portfolio
def put(self, request, *args, **kwargs):
data = Portfolio.objects.get(id=request.data['id'])
update_portfolio = Portfolio.objects.update(name=data['name']
, market=Market.objects.get(pk=int(request.POST.get('market', ''))))
update_portfolio.save()
for asset in data['assets']:
asset_obj = Assets.objects.update(asset_name=asset['asset_name'])
update_portfolio.assets.add(asset_obj)
serializer = PortfolioSerializer(update_portfolio)
return Response(serializer.data)
And this is the URL
router.register("update", PortfolioUpdateView, basename="update")

Flask is_authenticated user status changes while redirecting to another endpoint

Im trying to make and endpoint (addurl) which will be only avaliable for logged in users. The problem that occurs is that when Im loggin in on /login endpoint current_user.is_authenticated returns '<bound method User.is_authenticated of <User 2>>' (after login_user(user)). However while redirecting to /addurl current_user.is_authenticated is somehow overwritten and changes to False. How can i solve this?
CODE
model:
from app import db, login_manager
#login_manager.user_loader
def load_user(user_id):
return User.query.get(user_id)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50), unique=True)
password = db.Column(db.String(1024))
def is_active(self):
"""True, as all users are active."""
return True
def get_id(self):
"""Return the email to satisfy Flask-Login's requirements."""
return self.email
def is_authenticated(self):
"""Return True if the user is authenticated."""
return self.authenticated
def is_anonymous(self):
"""False, as anonymous users aren't supported."""
return False
form:
class LoginForm(FlaskForm):
email = StringField('Your Username: ', validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired()])
submit = SubmitField('Log In')
views:
#app.route("/login", methods=["GET", "POST"])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password, form.password.data):
login_user(user)
return redirect('/addurl')
else:
return render_template('login.html', form=form)
return render_template('login.html', form=form)
#app.route('/addurl', methods=['GET', 'POST'])
def addurl():
form = CompanyForm()
if current_user.is_authenticated:
if form.validate_on_submit():
foo()
return redirect('/base')
else:
flash('you have to be logged in')
return render_template('AddUrl.html', form=form)
It looks like you have forgotten to make addurl a 'login_required' route. Try something like this:
from flask_login import login_required
# original source code...
#app.route('/addurl', methods=['GET', 'POST'])
#login_required
def addurl():
# original source code...
The #login_required decorator should do all the magic you need.
Also, be sure to initialize the LoginManager correctly in app.py:
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.init_app(app)

permission_classes doesn't work . where is problem?

I'm trying to found how Restframework's permission work , so i try write some code that response a simple Json. But the existence or absence of a ā€¨permission_classes does not affect the execution of the code and endpoint will response all request without checking any permission.
here is my code:
class TeacherStatisticPost(generics.RetrieveAPIView):
permission_classes = (ClassOwnerPermission)
queryset = ClassRoom.objects.all()
lookup_field = "id"
lookup_url_kwarg = 'classRoom_id'
def get_klass(self):
class_id = self.kwargs['classRoom_id']
return ClassRoom.objects.get(id=classRoom_id)
def get(self, request, *arg, **kwargs):
klass = self.get_klass()
response ={
'class_room_grade' : klass.grade,
'class_room_name' : klass.name,
}
return JsonResponse(response, safe=False)
and here is my permission.py:
class ClassOwnerPermission(permissions.BasePermission):
def has_perm(self, user, klass):
print("now in class perm") # never print out any thing!
return klass.owner == user
def has_object_permission(self, request, view, obj): # where is come from 'obj' ?
return self.has_perm(request.user, obj)
im try to set permission that just owner of ClassRoom can access to this endpoint.
Firstly, DRF expects permission_classes to be list or tuple. permission_classes at the line permission_classes = (ClassOwnerPermission) is not neither tuple not list. Put comma after the ClassOwnerPermission.
The line permission_classes = (ClassOwnerPermission, ) should work.
Secondly, you do not use get_object method which checks the permissions. Remove def get_klass(self): method and use get_object
Your view should look like following:
class TeacherStatisticPost(generics.RetrieveAPIView):
permission_classes = (ClassOwnerPermission, )
queryset = ClassRoom.objects.all()
lookup_field = "id"
lookup_url_kwarg = 'classRoom_id'
def get(self, request, *arg, **kwargs):
klass = self.get_object()
response ={
'class_room_grade' : klass.grade,
'class_room_name' : klass.name,
}
return JsonResponse(response, safe=False)

ApiKey authentication in tastypie with mongoengine

Has anybody had success implementing ApiKey for User from mongoengine.django.auth for use with tastypie ApiKeyAuthentication?
I'm aware of the previous posts on the matter, but they address ORM only, while i'm trying to set it up for mongoengine. Also, it seems that tastypie's own ApiKey class heavily relies on relational structure (using related field api_key of User)
thanks in advance!
following this thread https://github.com/mitar/django-tastypie-mongoengine/issues/25 i've created MongoUser class with api_key field
# models.py (or documents.py)
from mongoengine.django.auth import User
class MongoUser(User):
"""
Subclass of mongoengine.django.auth.User with email as username
and API key for authentication.
"""
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['password']
api_key = StringField(max_length=256, default='')
api_key_created = DateTimeField(help_text=_(u'Created'))
def save(self, *args, **kwargs):
if not self.api_key:
self.set_api_key()
return super(MongoUser, self).save(*args, **kwargs)
def set_api_key(self):
self.api_key = self.generate_key()
self.api_key_created = datetime.now()
def generate_key(self):
new_uuid = uuid.uuid4()
return hmac.new(str(new_uuid), digestmod=sha1).hexdigest()
added a signal (the usual):
# resources.py
from mongoengine import signals
from myapp import models
signals.post_save.connect(create_api_key, sender=models.MongoUser)
and then subclassed tastypie.ApiKeyAuthentication with the following:
# resources.py
class CustomApiKeyAuthentication(ApiKeyAuthentication):
"""
Authenticates everyone if the request is GET otherwise performs
ApiKeyAuthentication.
"""
def is_mongouser_authenticated(self, request):
"""
Custom solution for MongoUser ApiKey authentication.
ApiKey here is not a class (as it is realized in ORM approach),
but a field MongoUser class.
"""
username, api_key = super(CustomApiKeyAuthentication,
self).extract_credentials(request)
try:
models.MongoUser.objects.get(username=username, api_key=api_key)
except models.MongoUser.DoesNotExist:
return False
return True
def is_authenticated(self, request, **kwargs):
"""
Custom solution for `is_authenticated` function: MongoUsers has got
authenticated through custom api_key check.
"""
if request.method == 'GET':
return True
try:
is_authenticated = super(CustomApiKeyAuthentication,
self).is_authenticated(request, **kwargs)
except TypeError as e:
if "MongoUser" in str(e):
is_authenticated = self.is_mongouser_authenticated(request)
else:
is_authenticated = False
return is_authenticated