Apply common custom authentication functionality to multiple Flask projects - flask

I've created a class which authenticates users based on our company's user server. I'd like to apply it to any of our Flask apps which use Flask-Login rather than repeating the code in each project. I'm not sure what the right pattern for this is, or how to implement it.
I thought of a few options:
Python module - simply authentication, the module would do the login then return something (maybe credentials or token).
Flask 'app' - authenticates, includes a login and logout screen, and somehow gets linked in with #login_manager.user_loader. The issue I see is that the user loaded could have any application's User schema.
What is a good pattern for implementing this common authentication for multiple projects?

Extract the common functions of setting up a Flask-Login manger and the custom login views/functions you need to a simple Flask extension package. Install this package with pip in the environment of each project and use it when creating that project's Flask app.
company_auth/company_auth.py
from flask import Blueprint, redirect, url_for, render_template
from flask_login import LoginManager
from flask_wtf import Form
bp = Blueprint('auth', __name__)
class LoginForm(Form):
# define your login form
#bp.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# do custom login stuff
return redirect(url_for('index'))
return render_template('auth/login.html', form=form)
def init_app(app, user_model):
# have to pass in the user model since it's different between apps
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
#login_manager.user_loader
def company_user_loader(id):
user = user_model.query.get(id)
# do custom user loading stuff
return user
app.register_blueprint(bp, url_prefix='/auth')
company_auth/setup.py
#!/usr/bin/env python
from setuptools import setup, find_packages
setup(
name='company_auth',
version='1.0',
py_modules=['company_auth'],
url='http://davidism.com/',
license='BSD',
author='davidism',
author_email='davidism#gmail.com',
description='Flask extension for company auth',
requires=['flask']
)
Create a distribution of the package to install in other projects.
$ python setup.py sdist
For each project, install the package, import and run the init_app function, and provide the auth templates. (Your extension could include default templates too, but this answer would get gigantic if I go down that path. See Flask-Security for an example of default templates.)
$ project_env/bin/activate
$ pip install /path/to/company_auth/dist/company_auth-1.0.tar.gz
Create the auth templates:
project/
templates/
auth/
login.html
app.py
Set up the app with the custom auth:
import company_auth
company_auth.init_app()

Related

Securing Flask-Admin When Using Blueprints and Application Factory

I've set up Flask Admin and it is working, but am struggling with adding authentication. There are several tutorials I've followed but can't seem to get them to work with how I've set up my app. Per the documentation on Flask-Admin regarding authentication (and slightly tweaked based on how I'm importing various elements), you just need to add:
class AdminView(ModelView):
def is_accessible(self):
return current_user.is_authenticated
def inaccessible_callback(self, name, **kwargs):
# redirect to login page if user doesn't have access
return redirect(url_for('login', next=request.url))
But I can still access the /admin route without logging in. (I also would like to add an additional conditional that checks that the user is listed as an admin, which is a boolean column in the user table, but I haven't gotten this first part to work).
I've tried putting the above inside and outside of the create_app() function. Does this have to do with my blueprints? If so, where would I put this code?
# __init__.py
from flask import Flask
from dotenv import load_dotenv
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager, current_user
from flask_migrate import Migrate
from SIMS_Portal.config import Config
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from flaskext.markdown import Markdown
load_dotenv()
db = SQLAlchemy()
bcrypt = Bcrypt()
login_manager = LoginManager()
login_manager.login_view = 'users.login'
login_manager.login_message_category = 'danger'
mail = Mail()
from SIMS_Portal import models
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)
bcrypt.init_app(app)
login_manager.init_app(app)
mail.init_app(app)
admin = Admin(app, name='SIMS Admin Portal', template_mode='bootstrap4', endpoint='admin')
Markdown(app)
from SIMS_Portal.main.routes import main
from SIMS_Portal.assignments.routes import assignments
from SIMS_Portal.emergencies.routes import emergencies
from SIMS_Portal.portfolios.routes import portfolios
from SIMS_Portal.users.routes import users
from SIMS_Portal.errors.handlers import errors
app.register_blueprint(main)
app.register_blueprint(assignments)
app.register_blueprint(emergencies)
app.register_blueprint(portfolios)
app.register_blueprint(users)
app.register_blueprint(errors)
from SIMS_Portal.models import User, Assignment, Emergency, Portfolio, NationalSociety
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Assignment, db.session))
admin.add_view(ModelView(Emergency, db.session))
admin.add_view(ModelView(Portfolio, db.session))
admin.add_view(ModelView(NationalSociety, db.session))
return app
Got some help from the r/flask community which I'll share here for anyone else that has set their app up the same way and found existing tutorials unhelpful. The key when using an app factory like in my case is the swap out the ModelView for the AdminView you define, which can go before the create_app() function. In my __init__.py, I first defined that custom class that inherits from ModelView (and add a check that the user is not only logged in but also listed as an admin in my database):
class AdminView(ModelView):
def is_accessible(self):
if current_user.is_admin == 1:
return current_user.is_authenticated
def inaccessible_callback(self, name, **kwargs):
return render_template('errors/403.html'), 403
Then within the create_app() I swap out what I had previously included as admin.add_view(ModelView(User, db.session)) for admin.add_view(AdminView(User, db.session))
That little difference makes obvious sense, but again, I couldn't find tutorials that covered this.

Using Flask-HTTPAuth when serving a static folder

I'm using Flask to serve a static folder:
from flask import Flask, send_from_directory
from flask_httpauth import HTTPBasicAuth
app = Flask(__name__,
static_url_path='',
static_folder='html_files')
...
#app.route('/')
#auth.login_required
def send_html_files():
return send_from_directory('html_files', 'main.html')
I used the first example in Flask-HTTPAuth docs in order to add basic authentication to my website. Just a regular username and password is enough for me.
The problem is that the authentication dialog is not showing when the user go directly to http://localhost:5000/a/b/c (it works on http://localhost:5000/)
What is the proper way of doing this? On the other hand, what is the quick and dirty way?
#app.route('/') matches your root path only.
Try something like this to match every path:
#app.route('/<path:filename>')
#auth.login_required
def send_html_files(filename):
return send_from_directory('html_files', filename)

Moving Custom CLI commands to another file

I have a few custom cli commands for a flask app I'm writing. I'm following the instructions here:
Command Line Interface
The problem is I do not want to put them all in my app.py file, it will get overbloated. What I would like to do is have my project structure:
project
|_ app.py
|_ cli.py
I thought about using a blueprint, but I get "Blueprint has no attribute 'cli'"
This is what I tried:
cli = Blueprint('cli', __name__) # I knew this would not work but I had to try
#cli.cli.command()
#click.argument('name')
def create_user(name):
print("hello")
Thanks
I would do something like this:
cli.py:
from flask import Flask
import click
def register_cli(app: Flask):
#app.cli.command()
#click.argument('name')
def create_user(name):
print("hello", name)
app.py:
from flask import Flask
from cli import register_cli
app = Flask(__name__)
register_cli(app)
It's common to create and configure (or just configure) app in factory functions.

How to understand tornado response request cycle in Django

I want to create a real time twitter streaming application using tornado and Django. The problem is I am not able to understand the role of Tornado here, and how will I use view.py models.py of Django in Tornado Web Server.
Below if the request response cycle of Django, could anybody explain to me how the tornado web server will play its role here.
Few questions:
1- What will be role of urls.py file in Django since we will be routing all the urls from Tornado itself.
2- How will I connect to models.py to fetch rows for my tornado application.
I am looking into this github project link
Tornado fits roughly in the "web server" and "wsgi" parts of this diagram, and adds another section for Tornado RequestHandlers attached to the web server. When you create your tornado.web.Application, you will send some URLs to Tornado RequestHandlers and some to the Django WSGIContainer (which will in turn use the Django urls.py).
Using Django models from Tornado code is more challenging; my code from the last time I tried doing this is at https://gist.github.com/bdarnell/654157 (but note that this is quite old and I don't know if it will work any more)
This is tornado_main.py stored in one level with manage.py ... I've tested it with Django 1.8 ...
# coding=utf-8
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project_dir.settings")
import django
django.setup()
from django.core.urlresolvers import reverse_lazy
from django.contrib.auth.models import User
from tornado.options import options, define, parse_command_line
import logging
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.websocket
import tornado.wsgi
define('port', type=int, default=8004)
# tornado.options.options['log_file_prefix'].set(
# '/var/www/myapp/logs/tornado_server.log')
tornado.options.parse_command_line()
class SomeHandler(tornado.websocket.WebSocketHandler):
pass
def main():
logger = logging.getLogger(__name__)
tornado_app = tornado.web.Application(
[
(r'/some_url/(?P<user_id>[0-9]+)', SomeHandler),
],
debug=True
)
logger.info("Tornado server starting...")
server = tornado.httpserver.HTTPServer(tornado_app)
server.listen(options.port)
try:
tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
logger.info("Tornado server has stopped")
if __name__ == '__main__':
main()

Importing Flask app when using app factory and flask script

This is Flask app context
app = Flask(__name__)
with app.app_context():
# insert code here
Most of the use cases of app context involves having 'app' initialized on the same script or importing app from the base.
My application is structured as the following:
# application/__init__.py
def create_app(config):
app = Flask(__name__)
return app
# manage.py
from application import create_app
from flask_script import Manager
manager = Manager(create_app)
manager.add_command("debug", Server(host='0.0.0.0', port=7777))
This might be really trivial issue, but how I should call 'with app.app_context()' if my application is structured like this?
Flask-Script calls everything inside the test context, so you can use current_app and other idioms:
The Manager runs the command inside a Flask test context. This means that you can access request-local proxies where appropriate, such as current_app, which may be used by extensions.
http://flask-script.readthedocs.org/en/latest/#accessing-local-proxies
So you don't need to use with app.app_context() with Manager scripts. If you're trying to do something else, then you'd have to create the app first:
from application import create_app
app = create_app()
with app.app_context():
# stuff here