ApiKey authentication in tastypie with mongoengine - api

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

Related

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)

Cant handle a response error on django-rest-framework

I am trying to return 400 bad requets response when a user with a company created tries to create another one on my API
I tried with a Response but it does not work, it seems that it does not enter on the if
class CompanyViewSet(generics.ListCreateAPIView):
serializer_class = CompanySerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return UserCompany.objects.filter(owner=self.request.user.id)
def perform_create(self, serializer):
queryset = UserCompany.objects.filter(owner=self.request.user.id)
if queryset.exists():
content = {'API response error:': 'Can have only a one company for every user'}
return Response(content, status=status.HTTP_400_BAD_REQUEST)
else:
serializer.save(owner=self.request.user)
When I create a company in user that already has one returns state 200 but don't create it, I expected it to return state 400
Because perform_create only save serializer . If you want control status response, you should override create. Try like this:
def create(self, request, *args, **kwargs):
queryset = UserCompany.objects.filter(owner=self.request.user.id)
if queryset.exists():
content = {'API response error:': 'Can have only a one company for every user'}
return Response(content, status=status.HTTP_400_BAD_REQUEST)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

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)

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

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

How do I structure this medium sized flask application?

Using the FLASK framework in Python, my application needs to:
register and log in users (with either a sqlite or postgres database)
access a specific google spreadsheet that the logged in user owns and output that data in a json format.
I am required to have my own authorization & authentication system
I am having a lot of trouble figuring out how to even structure the application - what directories and sub-directories should I have?
I have done A LOT of playing around (about 1 months worth). I am using a virtual environment but don't know how to test my code well either. In general, my code runs but I have no idea how they work together really.** I am completely new to flask.**
Structuring the app:
|app
|----run.py
|----config.py
|----database
|---------database.db
|----app
|---------views.py
|---------models.py
|---------forms.py
|---------extensions.py
|----templates
|---------....
|----static
|--------....
Authorization / Authentication:
I have looked at Flask-Login, Flask-Auth, Flask-Security. I understand the general idea but do not know how to securely implement a complete authorization & authentication system.
app = Flask(__name__)
app.config.from_object(config)
login_manager = LoginManager()
login_manager.init_app(app)
def create_app():
db.init_app()
db.app = app
db.create_all()
return app
#app.route('/')
def index():
#needs to render the homepage template
#app.route('/signup', methods = ['GET', 'POST'])
def register():
form = SignupForm()
if request.method == 'GET':
return render_template('signup.html', form=form)
elif request.method == 'POST':
if form.validate_on_submit():
if User.query.filter_by(email=form.email.data).first():
return "email exists"
else:
newuser = User(form.email.data, form.password.data)
db.session.add(newuser)
db.session.commit()
login_user(newuser)
return "New User created"
else:
return "form didn't validate"
return "Signup"
#app.route('/login', methods = ['GET', 'POST'])
def login():
form = SignupForm()
if request.method == 'GET':
return render_template('login.html', form=form)
elif request.method == 'POST':
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user:
if user.password == form.password.data:
login_user(user)
return "you are logged in"
else:
return "wrong password"
else:
return "user doesnt exist"
else:
return "form did not validate"
#login_manager.user_loader
def load_user(email):
return User.query.filter_by(email = email).first()
#app.route('/protected')
#login_required
def protected():
return "protected area for logged in users only"
if __name__ == '__main__':
#app.create_app()
app.run(port=5000, host='localhost')`
from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, login_required
import os
# Create app
app = Flask(__name__)
#app.config['DEBUG'] = True
app.config['SECRET_KEY'] = ''
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////'
app.config['SECURITY_PASSWORD_HASH'] = 'sha512_crypt'
app.config['SECURITY_PASSWORD_SALT'] = str(os.urandom(24))
# Create database connection object
db = SQLAlchemy(app)
# Define models
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic'))
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
# Create a user to test with
#app.before_first_request
def create_user():
db.create_all()
user_datastore.create_user(email='', password='')
db.session.commit()
#app.route('/')
#login_required
def home():
#password = encrypt_password('mypass')
#print verify_and_update_password('mypass', password)
return "hello"
if __name__ == '__main__':
app.run(debug=True, use_reloader=False)
** I would really appreciate any guidance!**
Project structure:
If you're planning to build a larger Flask application, you should consider decomposing the functionality into Blueprints.
The official Flask documentation has a tutorial on how to structure larger applications:
http://flask.pocoo.org/docs/0.12/patterns/packages/
Also, take a look at the Hitchhiker's guide to organizing your project. It has some very good points: http://python-guide-pt-br.readthedocs.io/en/latest/writing/structure/
If you're designing an REST API consider using Flask-RESTful (which also works nicely with Blueprints)
ya'll, i figured it out & my app is LOOKING GOOD :)I am using blueprints & an application factory pattern.
APP
|_runserver.py
|_/app
|---__init__.py
|---config.py
|---extensions.py
|---forms.py
|---models.py
|---/login_dashboard #blueprint
|------__init__.py
|------views.py
|------/templates
|---------base.html
.
.
|------/static
|-----------/css
.
.
|-----------/js
.
.
|-----------/img
.
.