How do I structure this medium sized flask application? - authentication

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
.
.

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()

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)

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

Problems when connecting to Appfog's SQL service from Flask

I'm currently trying to get my Flask app working with Appfog. I'm having 500 error every time I'm trying to do something with my DB. My connection code looks like:
services = json.loads(os.environ.get("VCAP_SERVICES"))
pg = services["postgresql-9.1"][0]["credentials"]
app.config.update({
'SQLALCHEMY_DATABASE_URI': "postgresql://%s:%s#%s:%i/%s" % \
(pg["username"], pg["password"], pg["host"], pg["port"], pg["name"])
})
# I've tried MySQL too
Model:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True)
passhash = db.Column(db.String(140))
def __init__(self, username, password):
self.username = username
self.passhash = pwd_context.encrypt(password)
def check_password(self, password):
return pwd_context.verify(password, self.passhash) # from passlib
# Flask-Login properties
def is_authenticated(self): return True
def is_active(self): return True
def is_anonymous(self): return False
def get_id(self): return unicode(self.id)
And some example route that utilizes DB:
#app.route("/login", methods=['GET', 'POST'])
def login():
if current_user.is_authenticated():
logout_user()
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter_by(username=username).first()
if not user or not user.check_password(password):
flash('Wrong username or password.')
return render_template('login.html')
else:
login_user(user)
return redirect(request.args.get("next") or "/")
else:
return render_template('login.html')
This works with sqlite database on localhost. So, am I doing something wrong?

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