sandman2 icon indicating copy to clipboard operation
sandman2 copied to clipboard

Add role based security or any kind of access control

Open supersexy opened this issue 4 years ago • 4 comments

Interesting project, but I can not find any hint about how to control access to data, also the documentation does not provide any concept regarding data security.

Some kind of access control system seems like a very basic requirement for any data access software - is this something that is planned for the future?

supersexy avatar Jul 11 '19 16:07 supersexy

This would be a killer feature. Currently using nginx as a proxy.

dkatz23238 avatar Oct 12 '19 16:10 dkatz23238

If one cracks open the code one finds the following for the create application method

def get_app(
        database_uri,
        exclude_tables=None,
        user_models=None,
        reflect_all=True,
        read_only=False,
        schema=None):
    """..."""
    app = Flask('sandman2')
    app.config['SQLALCHEMY_DATABASE_URI'] = database_uri
    app.config['SANDMAN2_READ_ONLY'] = read_only
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    app.classes = []
    db.init_app(app)
    admin = Admin(app, base_template='layout.html', template_mode='bootstrap3')
    _register_error_handlers(app)
    if user_models:
        with app.app_context():
            _register_user_models(user_models, admin, schema=schema)
    elif reflect_all:
        with app.app_context():
            _reflect_all(exclude_tables, admin, read_only, schema=schema)

    @app.route('/')
    def index():
        """Return a list of routes to the registered classes."""
        routes = {}
        for cls in app.classes:
            routes[cls.__model__.__name__] = '{}{{/{}}}'.format(
                cls.__model__.__url__,
                cls.__model__.primary_key())
        return jsonify(routes)
    return app

Which one can restructure as follows

def create_app(
        database_uri,
        exclude_tables=None,
        user_models=None,
        reflect_all=True,
        read_only=False,
        schema=None):
    app = Flask('sandman2')
    app.config['SQLALCHEMY_DATABASE_URI'] = database_uri
    app.config['SANDMAN2_READ_ONLY'] = read_only
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    app.classes = []
    sandman(
        application,
        database_uri,
        exclude_tables=None,
        user_models=None,
        reflect_all=True,
        read_only=False,
        schema=None)
    
def sandman(
        application,
        database_uri,
        exclude_tables=None,
        user_models=None,
        reflect_all=True,
        read_only=False,
        schema=None):
    """..."""
    db.init_app(application)
    admin = Admin(application, base_template='layout.html', template_mode='bootstrap3')
    _register_error_handlers(application)
    if user_models:
        with application.app_context():
            _register_user_models(user_models, admin, schema=schema)
    elif reflect_all:
        with application.app_context():
            _reflect_all(exclude_tables, admin, read_only, schema=schema)

    @application.route('/')
    def index():
        """Return a list of routes to the registered classes."""
        routes = {}
        for cls in application.classes:
            routes[cls.__model__.__name__] = '{}{{/{}}}'.format(
                cls.__model__.__url__,
                cls.__model__.primary_key())
        return jsonify(routes)

Once this is done it becomes rather trivial to add security to the application.

from flask_jwt_extended import JWTManager

def create_app(
        database_uri,
        exclude_tables=None,
        user_models=None,
        reflect_all=True,
        read_only=False,
        schema=None):
    app = Flask('sandman2')
    app.config['SQLALCHEMY_DATABASE_URI'] = database_uri
    app.config['SANDMAN2_READ_ONLY'] = read_only
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    app.classes = []
    jwt = JWTManager(app)
    sandman(
        application,
        database_uri,
        exclude_tables=None,
        user_models=None,
        reflect_all=True,
        read_only=False,
        schema=None)

Then add the endpoints one wants as necessary. Similarly it is possible to secure ones administration interface by adding say Flask-Login but one must also then subclass the AdminView and AdminIndexView and pass these through the the Admin invocation in the "new" sandman method.

I have submitted a PR that already does this refactoring and am awaiting its acceptance.

Carelvd avatar Nov 05 '19 22:11 Carelvd

Yes, authentication is a must. I believe sandman1 had it. Was expecting sandman2 to do it better... not less... But I as a beggar cannot be a boss. But we can choose another approach, such as dreamfactory. Will start playing with that now... hopefully it's not crazy hard to setup.

zeluspudding avatar Jan 28 '20 17:01 zeluspudding

@zeluspudding My example above was meant to illustrate that forcing a security solution upon the user is unnecessary and that, with minor refactoring of the code, it becomes trivial for persons using the library to set this up.

To setup security layer for the interim one need only copy the create_app/get_app code and extend it yourself, as I have above. There are multiple libraries that deal with this e.g. flask_jwt, flask_jwt_extended, flask-login and the like.

@jeffknupp When my PR is accepted/rejected I will push up my documentation for the changes and I can happily document the methods by which security can be added.

Carelvd avatar Jan 28 '20 18:01 Carelvd