Closed reubano closed 3 years ago
Did some more investigation. It appears that rq-dashboard
blueprints work, but flask-restless
and the associated flask-admin
blueprints don't.
Update: Here's the documentation for flask-restless
authentication. It uses flask-login
though, so I'll update once I figure out what changes are needed for this lib.
I copied over the flask-restless/flask-login
example and came up with this...
"""
Authentication example using Flask-HTTPAuth
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This provides a simple example of using Flask-HTTPAuth as the authentication
framework which can guard access to certain API endpoints.
This requires the following Python libraries to be installed:
* Flask
* Flask-HTTPAuth
* Flask-Restless
* Flask-SQLAlchemy
To install them using ``pip``, do::
pip install Flask Flask-SQLAlchemy Flask-Restless Flask-HTTPAuth
To use this example, run this package from the command-line::
python -m authentication
Attempts to access the URL of the API for the :class:`User` class at
``http://localhost:5000/api/user`` will prompt you to authenticate with username
``admin`` and password ``admin``.
:copyright: 2012 Jeffrey Finkelstein <jeffrey.finkelstein@gmail.com>
:copyright: 2021 Reuben Cummings <rcummings@nerevu.com>
:license: GNU AGPLv3+ or BSD
"""
from os import urandom, path as p
from flask import Flask, redirect, session, request
from flask_httpauth import HTTPBasicAuth
from flask_restless import APIManager, ProcessingException
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.hybrid import hybrid_property
from werkzeug.security import generate_password_hash, check_password_hash
# Step 0: the database in this example is at './test.db'.
PARENT_DIR = p.abspath(p.dirname(__file__))
DB_PATH = p.join(PARENT_DIR, "test.db")
# Step 1: setup the Flask application.
app = Flask(__name__)
app.config["DEBUG"] = True
app.config["TESTING"] = True
app.config["SECRET_KEY"] = urandom(24)
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{DB_PATH}?check_same_thread=False"
# Step 2: initialize extensions.
db = SQLAlchemy(app)
api_manager = APIManager(app, flask_sqlalchemy_db=db)
auth = HTTPBasicAuth()
# Step 3: create the user database model.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Unicode, unique=True, nullable=False)
_password = db.Column("password", db.Unicode, nullable=False)
@hybrid_property
def password(self):
return self._password
@password.setter
def password(self, password):
self._password = generate_password_hash(password)
# Step 4: create the database and add a test user.
db.drop_all()
db.create_all()
user = User(username="admin", password="admin")
db.session.add(user)
db.session.commit()
# Step 5: create password validator.
@auth.verify_password
def verify_password(username, password):
matched_user = User.query.filter_by(username=username).first()
if matched_user and check_password_hash(matched_user.password, password):
return matched_user
# Step 6: create the authenticate endpoint
@app.route("/authenticate")
@auth.login_required
def authenticate():
username = auth.current_user().username
session["current_username"] = username
return redirect("/api/user", 307)
@app.before_request
def check_auth():
if request.path != "/authenticate" and not session.get("current_username"):
return redirect("/authenticate", 307)
# Step 7: create the API for User with the authentication guard.
def auth_func(*args, **kwargs):
current_username = session.get("current_username")
session["current_username"] = None
if not current_username:
raise ProcessingException(description="Not authenticated!", code=401)
preprocessors = {"GET_SINGLE": [auth_func], "GET_MANY": [auth_func]}
api_manager.create_api(User, preprocessors=preprocessors)
# Step 8: configure and run the application
app.run()
# Step 9: visit http://localhost:5000/api/user in a Web browser. You will
# be prompted to authenticate via basic authentication.
#
# Step 10: Enter username "admin" and password "admin". You will then be
# authenticated and get a response showing the objects in the User table of
# the database.
I copied over the flask-admin/flask-login
example and came up with this...
from os import urandom, path as p
from flask import Flask, url_for, redirect, request, session
from flask_httpauth import HTTPBasicAuth
from flask_sqlalchemy import SQLAlchemy
from flask_admin import expose, Admin, AdminIndexView
from flask_admin.contrib.sqla import ModelView
from sqlalchemy.ext.hybrid import hybrid_property
from werkzeug.security import generate_password_hash, check_password_hash
PARENT_DIR = p.abspath(p.dirname(__file__))
DB_PATH = p.join(PARENT_DIR, "admin.db")
# Create Flask application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config["SECRET_KEY"] = urandom(24)
# Create in-memory database
app.config["DEBUG"] = True
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{DB_PATH}?check_same_thread=False"
app.config["SQLALCHEMY_ECHO"] = True
db = SQLAlchemy(app)
auth = HTTPBasicAuth()
# Create user model.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Unicode, unique=True, nullable=False)
_password = db.Column("password", db.Unicode, nullable=False)
@hybrid_property
def password(self):
return self._password
@password.setter
def password(self, password):
self._password = generate_password_hash(password)
def get_id(self):
return self.id
# Required for administrative interface
def __unicode__(self):
return self.username
# create password validator.
@auth.verify_password
def verify_password(username, password):
matched_user = User.query.filter_by(username=username).first()
if matched_user and check_password_hash(matched_user.password, password):
return matched_user
# Create customized model view class
class MyModelView(ModelView):
def is_accessible(self):
return session.get("current_username")
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for(".authenticate", next=request.url))
# Create customized index view class that handles authenticate
class MyAdminIndexView(AdminIndexView):
@expose("/")
def index(self):
if session.get("current_username"):
return super(MyAdminIndexView, self).index()
else:
return redirect(url_for(".authenticate"))
@expose("/authenticate")
@auth.login_required
def authenticate(self):
session["current_username"] = username = auth.current_user().username
if username:
return redirect(url_for(".index"))
else:
return super(MyAdminIndexView, self).index()
# Flask views
@app.route("/")
def index():
return f"Welcome {session.get('current_username')}!"
# Create admin
admin = Admin(app, "Example: Auth", index_view=MyAdminIndexView())
# Add view
admin.add_view(MyModelView(User, db.session))
if __name__ == "__main__":
# create db
db.drop_all()
db.create_all()
user = User(username="admin", password="admin")
db.session.add(user)
db.session.commit()
# Start app
app.run(debug=True)
It seems like
before_request
only works withapp
...works as expected
doesn't work
cr #106 and #107