【问题标题】:Flask - allowing users into a group of projects, based on project being a variable and an @app.route idFlask - 允许用户进入一组项目,基于项目是一个变量和一个@app.route id
【发布时间】:2020-06-03 02:34:24
【问题描述】:

我正在使用烧瓶安全性。我想使用基于应用程序路由的变量设置@roles_accepted

例如,如果_id = Lon_2020 @roles_accepted 将允许Lon。这将根据 _id 将某些用户授予项目,但不授予其他用户。

@app.route('/<string:_id>')
@app.route('/<string:_id>/')
@roles_accepted('admin',_id[:3])
def home(_id):
    return redirect(url_for('tb',_id=_id))

目前,这会导致错误,因为 _id 未定义。 roles_accepted 依赖于 _id 数据。

如果这不可能,您是否介意让我知道允许某些用户进入某些项目的更好方法。例如

Lon_2019
Lon_2020
Lon_2021
Par_2019
Par_2020
Par_2021
Ber_2018
Ber_2019

where the identified is the first 3 characters 'Lon','Par','Ber'

非常感谢任何帮助。谢谢你。

【问题讨论】:

    标签: python flask flask-security


    【解决方案1】:

    用另一个装饰器包裹@roles_accepted 装饰器!在下面的代码中,用于提取角色的变量名被传递到外部装饰器中,尽管它可能是硬编码的。

    def roles_accepted_from_route(variable_name):
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                _variable = kwargs.get(variable_name, None)
                if _variable:
                    print(_variable, _variable[:3])
    
                    @roles_accepted('admin', _variable[:3])
                    def inner_wrapper(*args, **kwargs):
                        print(f'roles_accepted returned True, variable: {_variable[:3]}')
                        return func(*args, **kwargs)
                    return inner_wrapper(*args, **kwargs)
    
            return wrapper
    
        return decorator
    

    并按如下方式使用:

    @app.route('/<string:_id>')
    @roles_accepted_from_route(variable_name='_id')
    def some_route(_id):
        return f'Route Variable: {_id}'
    

    下面的单文件示例,几乎没有错误检查。在第一个 Flask 请求中创建一个具有email: 'fred@example.net', password: 'password' 角色的用户'LON'。

    from functools import wraps
    from flask import Flask
    from flask_security import roles_accepted
    from flask_security.utils import hash_password
    from flask_sqlalchemy import SQLAlchemy
    from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, current_user
    
    app = Flask(__name__)
    app.config['DEBUG'] = True
    app.config['SECRET_KEY'] = 'super-secret'
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db3'
    app.config['SECURITY_PASSWORD_SALT'] = '5ce39dc7add2284076de45b923d74dd00a052117cdf0ab900548565681e56fce'
    
    
    # Create database connection object
    db = SQLAlchemy(app)
    
    # Define 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'))
    
    
    # Setup Flask-Security
    user_datastore = SQLAlchemyUserDatastore(db, User, Role)
    security = Security(app, user_datastore)
    
    
    # Create a user to test with
    @app.before_first_request
    def create_user():
        db.drop_all()
        db.create_all()
        _lon_role = user_datastore.create_role(name='LON')
        _user = user_datastore.create_user(email='fred@example.net', password=hash_password('password'))
        user_datastore.add_role_to_user(_user, _lon_role)
        db.session.commit()
    
    
    def roles_accepted_from_route(variable_name):
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                _variable = kwargs.get(variable_name, None)
                if _variable:
                    print(_variable, _variable[:3])
    
                    @roles_accepted('admin', _variable[:3])
                    def inner_wrapper(*args, **kwargs):
                        print(f'roles_accepted returned True, variable: {_variable[:3]}')
                        return func(*args, **kwargs)
                    return inner_wrapper(*args, **kwargs)
    
            return wrapper
    
        return decorator
    
    
    @app.route('/')
    def home():
        if current_user.is_authenticated:
            return f'You are logged in as : {current_user.email} and have roles: {",".join([r.name for r in current_user.roles])}'
        else:
            return f'<a href="/login">Login</a>'
    
    
    @app.route('/<string:_id>')
    @roles_accepted_from_route(variable_name='_id')
    def some_route(_id):
        return f'Route Variable: {_id}'
    
    
    if __name__ == '__main__':
        app.run()
    

    【讨论】:

      【解决方案2】:

      以下是来自Flask-Security 库的roles_accepted 装饰器的文档。

      flask_security.decorators.roles_accepted(*roles): 指定用户必须至少具有指定角色之一的装饰器。示例:

      @app.route('/create_post')
      @roles_accepted('editor', 'author')
      def create_post():
          return 'Create Post'
      

      为了使用这个装饰器,你需要首先在你的角色表中创建角色,并且用户你的模型和角色模型需要遵循一定的格式,以便Flask-Security库可以识别现有的角色。

      模型示例

      # association table between user and role
      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'))
      )
      
      # role table
      class Role(db.Model, RoleMixin):
      
          __tablename__ = 'role'
      
          id = db.Column(db.Integer(), primary_key=True)
          name = db.Column(db.String(30), unique=True)
      
      # user table
      class User(db.Model, UserMixin):
      
          __tablename__ = 'user'
      
          id = db.Column(db.Integer, primary_key=True)
          email = db.Column(db.String(50), unique=True)
          password = db.Column(db.String(255))
      

      一旦您指定了上述模式,您就可以通过插入role 表来创建role。在视图中创建editorauthor 角色后,当具有editorauthor 角色的用户进入/create_post 视图时,它将能够控制基于访问控制关于角色。

      如果您从上面获取userrole 的ID,并希望基于它执行访问控制(我认为这是不必要的),因为Flask-Security 具有current_user 变量,您可以解析出有关该用户的所有信息,而无需您手动进行查询)。 您必须首先使用 ID 对 userrole 表进行查询,然后基于此,您将抛出 403 或允许返回内容,然后您不需要拥有自己的内容@roles_accepted('editor', 'author'),因为您是手动操作的。

      【讨论】:

      • 问题是我有数百个网页由一个应用程序路由创建。即这个应用路由生成了超过 300 个页面:@app.route('/<_id>'),因为有超过 300 个 id。然而,在 id 列表中几乎有“子角色”,所以我不能为应用程序路由定义一个角色。我需要一种在 @the app.route 之前返回 _id[:3] 的方法
      • 那么我认为你最好只手动检查角色而不使用烧瓶安全性。当收到请求时,您可以轻松地执行它们,只需检查 id 的前缀并决定是 403 还是返回正确的结果。
      【解决方案3】:

      在这种情况下 - 不要使用装饰器 - 在您的 app.route 中 - 让前几行计算“角色”,然后直接调用装饰器,例如:

      @app.route("/createpost")
      @auth_required()
      def createpost():
        /// figure out what role based on current_user or whatever
        return roles_required("author")(domyview)()
      
      def domyview():
          // Actual code for endpoint here.
      

      【讨论】:

        【解决方案4】:

        我有一个可行的解决方案,但并不完美。基于@Jessi 的 403 abort 建议:

        
        def check_user(c_id):
            if current_user.has_role('admin'):
                current = c_id
            to_check = ['Lon','Par’,’Ber'] #manual inserton of roles from db, could use sqlalchemy to query db instead?
            for n in to_check:
                if current_user.has_role(n):
                    current = n
            if c_id == current:
                return True
        
        @app.route('/<string:_id>')
        @app.route('/<string:_id>/')
        def home(_id):
            c_id = _id[:3])
            if check_user(c_id):
                return redirect(url_for('tb',_id=_id))
            else:
                return abort(403)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-12-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多