How to secure the flask-admin panel with flask-security
Asked Answered
Y

4

23

I'm looking to secure Web API made using Flask and integrated with flask-admin to provide an admin interface. I searched and found that flask-admin has an admin panel at /admin and by default anyone can have access to it. It provides no authentication system and completely open (without any security) since they didn't assume what would be used to provide security. This API has to be used in production, so we can't have an open /admin route for everyone hitting the url. Proper authentication is needed.

In views.py I can't simply put the /admin route and provide authentication through decorator as that would be over-writing the existing route already created by flask-admin so that would cause an error.

Further research shows that there are two modules flask-admin and flask-security. I know that flask-admin has is_accessible method to secure it, but it doesn't provide much functionality which is provided by flask-security.

I've not found any method there to secure the end-point /admin plus all other end-points beginning with /admin such as /admin/<something>.

I'm looking specifically to do this task with flask-security. If it's not possible, please suggest alternatives.

PS: I know I can lock ngnix itself, but that would be the last option. If I can have an authentication system through flask-security that would be good.

Yasmin answered 27/6, 2015 at 17:44 Comment(1)
Flask-Security's authentication mechanism (through Flask-Login) controls the return value of current_user.is_authenticated(). Returning this (possibly combined with some kind of role / permission checking) in your is_accessible implementation should give you the ability to use Flask-Security's protection within Flask-Admin.Loram
M
11

You should check out the Flask-Security-Admin project, I think it covers pretty clearly what you are looking for.

Taken directly from the link above:

  • When you first visit the app's home page, you'll be prompted to log in, thanks to Flask-Security.
  • If you log in with [email protected] and password=password, you'll have the "end-user" role.
  • If you log in with [email protected] and password=password, you'll have the "admin" role.
  • Either role is permitted to access the home page.
  • Either role is permitted to access the /admin page. However, unless you have the "admin" role, you won't see the tabs for administration of users and roles on this page.
  • Only the admin role is permitted to access sub-pages of /admin page such as /admin/userview. Otherwise, you'll get a "forbidden" response.
  • Note that, when editing a user, the names of roles are automatically populated thanks to Flask-Admin.
  • You can add and edit users and roles. The resulting users will be able to log in (unless you set active=false) and, if they have the "admin" role, will be able to perform administration.

The relevant code is located in main.py, and is clearly commented to explain how to replicate the process of securing the flask-admin panel using flask-security.

The most basic, relevant piece to you is the following (line 152-):

# Prevent administration of Users unless the currently logged-in user has the "admin" role
def is_accessible(self):
    return current_user.has_role('admin')

I hope this is helpful.

Masaccio answered 29/6, 2015 at 11:57 Comment(2)
Thank you! After some further digging and research I got it to work.Yasmin
Thank you for providing this. I have been searching for the answer for an hour and finally found it with you here.Concatenate
S
15

Since this is the first result for the "flask-security secure admin" google search, and there is no out-of-the-box solution yet, I think I can contribute.

A similiar question was asked on the flask-admin project Issue List and a simple example using flask-login and mogodb is provided here.

I made an example using SQLAchemy for a sqlite database and flask-security. See the sample flask app below:

 #!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import os.path as op
from flask import Flask, render_template, url_for, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.event import listens_for
from flask.ext.security import current_user, login_required, RoleMixin, Security, SQLAlchemyUserDatastore, UserMixin
from flask_admin import Admin, AdminIndexView
from flask_admin.contrib import sqla

# Create application
app = Flask(__name__)

# Create dummy secrety key so we can use sessions
app.config['SECRET_KEY'] = '123456790'

# Create in-memory database
app.config['DATABASE_FILE'] = 'sample_db.sqlite'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE']
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)

# Create directory for file fields to use
file_path = op.join(op.dirname(__file__), 'static/files')

# flask-security 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'))

# Create Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)

# Only needed on first execution to create first user
#@app.before_first_request
#def create_user():
#    db.create_all()
#    user_datastore.create_user(email='[email protected]', password='pass')
#    db.session.commit()

class AnyModel(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode(64))

    def __unicode__(self):
        return self.name

class MyAdminIndexView(AdminIndexView):
    def is_accessible(self):
        return current_user.is_authenticated() # This does the trick rendering the view only if the user is authenticated

# Create admin. In this block you pass your custom admin index view to your admin area 
admin = Admin(app, 'Admin Area', template_mode='bootstrap3', index_view=MyAdminIndexView())


# Add views
admin.add_view(sqla.ModelView(AnyModel, db.session)) 

# To acess the logout just type the route /logout on browser. That redirects you to the index
@login_required
@app.route('/login')
def login():
    return redirect('/admin')

@app.route('/')
def index():
    return render_template('index.html')


if __name__ == '__main__':

    # Build sample db on the fly, if one does not exist yet.
    db.create_all() 
    app.run(debug=True)

Please refer to the flask-security docs to learn how to customize the login page.

Hope this helps.

Shanly answered 7/10, 2015 at 12:24 Comment(3)
This is incredibly useful. The default behavior makes no sense to me. I don't understand why you'd want unauthenticated users to have any access to your admin panel.Noreennorene
Your code works but is not redirecting automatically to login in case the user is not authenticated, the official documentation offers one way to implement that: flask-admin.readthedocs.io/en/latest/introduction/…Hanhhank
for redirecting automatically to login add to MyAdminIndexView class method: def inaccessible_callback(self, name, **kwargs): return redirect(url_for('login', next=request.url))Lotetgaronne
M
11

You should check out the Flask-Security-Admin project, I think it covers pretty clearly what you are looking for.

Taken directly from the link above:

  • When you first visit the app's home page, you'll be prompted to log in, thanks to Flask-Security.
  • If you log in with [email protected] and password=password, you'll have the "end-user" role.
  • If you log in with [email protected] and password=password, you'll have the "admin" role.
  • Either role is permitted to access the home page.
  • Either role is permitted to access the /admin page. However, unless you have the "admin" role, you won't see the tabs for administration of users and roles on this page.
  • Only the admin role is permitted to access sub-pages of /admin page such as /admin/userview. Otherwise, you'll get a "forbidden" response.
  • Note that, when editing a user, the names of roles are automatically populated thanks to Flask-Admin.
  • You can add and edit users and roles. The resulting users will be able to log in (unless you set active=false) and, if they have the "admin" role, will be able to perform administration.

The relevant code is located in main.py, and is clearly commented to explain how to replicate the process of securing the flask-admin panel using flask-security.

The most basic, relevant piece to you is the following (line 152-):

# Prevent administration of Users unless the currently logged-in user has the "admin" role
def is_accessible(self):
    return current_user.has_role('admin')

I hope this is helpful.

Masaccio answered 29/6, 2015 at 11:57 Comment(2)
Thank you! After some further digging and research I got it to work.Yasmin
Thank you for providing this. I have been searching for the answer for an hour and finally found it with you here.Concatenate
R
3

I use @RamiMac's answer for all sub-views, but for the index one (by default /admin), I use this method, re-wrap the method with an admin role required to view.

@app.before_first_request
def restrict_admin_url():
    endpoint = 'admin.index'
    url = url_for(endpoint)
    admin_index = app.view_functions.pop(endpoint)

    @app.route(url, endpoint=endpoint)
    @roles_required('admin')
    def secure_admin_index():
        return admin_index()

In my project, this goes directly after all my Flask-Admin code, which is itself in its own startup script, custom_flaskadmin.py.

Realistic answered 13/12, 2017 at 1:9 Comment(1)
That's interesting, I've never seen that app.view_functions.pop method used before to do that.Oreste
R
1

There's section about security in Flask-Admin documentation: http://flask-admin.readthedocs.io/en/latest/introduction/#authorization-permissions

Recountal answered 28/6, 2015 at 0:56 Comment(3)
I've already looked into it before posting here and in this section the examples shows about using flask-admin and not flask-security. I'm specifically looking to do it via flask-security since it provide more functionality that flask-login. Any help would be appreciated.Yasmin
@Recountal #46914554Acedia
The documentation is also completely backwards.You shouldn't have to override security templates to extend admin templates since security templates are for both admins and non-admins...Massey

© 2022 - 2024 — McMap. All rights reserved.