pallets-eco / flask-admin

Simple and extensible administrative interface framework for Flask
https://flask-admin.readthedocs.io
BSD 3-Clause "New" or "Revised" License
5.79k stars 1.57k forks source link

allow for middleware to be added to blueprints for authentication #2297

Open scratchmex opened 2 years ago

scratchmex commented 2 years ago

currently, I have to do this to add authentication to my admin panel

admin = Admin()
basic_auth = BasicAuth()

for i, v in enumerate(admin._views):
    v._handle_view = basic_auth.required(v._handle_view)
admin.init_app(app)

I would be nice if you expose the blueprint before you create it inside init_app by calling view.create_blueprint on each view.

What I am saying is to change the signature of init_app to

def init_app(self, ..., setup_each_blueprint: Callable[[Blueprint], None]] | None = None):
    ...

# or

def init_app(self, ..., before_request: Callable[[], Response | None] | None = None):
     ...

In the first case we can register middleware with blueprint.before_request and in the second case, init_app do it manually. I prefer the first to have all the flexibility. The middleware could be

def restrict_admin_url():
    if not basic_auth.authenticate():
        return basic_auth.challenge()

def setup_restrict_admin_url(blueprint):
    blueprint.before_request(restrict_admin_url)

I am happy to do a PR

ElLorans commented 3 months ago

You can use OOP for that.

from flask_admin.contrib.sqla import ModelView
from flask import redirect, url_for, request
from flask_login import current_user

class SecureModelView(ModelView):
    def is_accessible(self):
        # Example: Only allow access if the user is authenticated and has a specific role
        return current_user.is_authenticated and current_user.has_role('admin')

    def inaccessible_callback(self, name, **kwargs):
        # Redirect to login page if the user doesn't have access
        return redirect(url_for('login', next=request.url))

Then you only need your views to inherit from SecureModelView.

class View1(SecureModelView):
    pass

class View2(SecureModelView):
    pass