隐藏Jinja2模板中无法访问的链接

rei*_*ish 7 python permissions jinja2 flask web

我们正在Flask + Jinja2中编写Web应用程序.该应用程序已注册用户,可以根据其角色访问某些页面.为了在服务器端实现这一点,我们只使用装饰页面:

@app.route('/action1')
@security_requirements(roles=['some_role'])
def action1():
    ...
Run Code Online (Sandbox Code Playgroud)

装饰器检查登录的用户是否在其角色列表中具有"some_role",并决定是将调用传递给修饰函数还是仅将用户重定向到"拒绝访问"页面.

该应用程序还有一个使用bootstrap实现的导航栏.使用基本模板在每个页面中显示导航栏.至于现在,应用程序中的每个页面都在导航栏中有一个条目,无论当前用户是否可以访问它.尽管这不是一个安全漏洞,但我想隐藏他们无法访问的用户页面.此外,我希望在不重复Jinja模板中允许的角色列表的情况下实现此功能.是否可以通过使用我当前的装饰器以某种方式在Jinja中实现此功能?

Doo*_*beh 6

我使用Flask-Security,它将很多登录/安全模块绑定在一个很好的包中.它配备了Flask-Principal提供的角色管理,允许您执行以下操作:

{% if current_user.has_role('admin') %}
    <li><a href="#">Manage Site</a></li>
{% endif %}
Run Code Online (Sandbox Code Playgroud)

您可以在源代码中看到它是如何实现的,current_user代理来自Flask-Login

  • 理想的是扩展flask的`url_map`本身,并且实际上在那里保存权限信息,所以当你实际定义app.route时你可以在那里指定角色,例如:`@ app.route('/', methods = ['post','get'],roles = ['admin'])`.通过查看url_map,可以更好地使用jinja2.这是一个有趣的问题,当我获得一些空闲时间时,我会看一下它. (2认同)

rei*_*ish 2

我将security_requirements装饰器更改为如下所示:

def security_requirements(logged_in=True,
                          roles=None):
def wrapper(f):
    # Store the security attributes as a member of the function object
    f.access_control = dict(logged_in=logged_in, roles=roles)
    @functools.wraps(f)
    def wrapped(*args, **kwargs):
        access_result = _eval_access(logged_in, roles)
        # Redirect the user to the appropriate page (Access denied / Login Required / Actual Page) based on the result
        ...
Run Code Online (Sandbox Code Playgroud)

与此装饰器之前版本的唯一真正区别是在函数对象内存储安全属性的行。这条线在装饰器内部是没有用的。但是,现在我可以实现从 Jinja 模板调用的以下操作:

{% if can_access(func) %}
<li><a>...</a></li>
{% endif %}
Run Code Online (Sandbox Code Playgroud)

can_access函数在Flask应用程序模块中定义。它接收一个字符串,必须将其转换为函数对象。它通过调用来做到这一点app.view_functions

def can_access(func):
    return auth.can_access(app.view_functions[func])
Run Code Online (Sandbox Code Playgroud)

该函数应该直接从 Jinja 模板调用。所以需要将其添加到 Jinja 的全局变量中:

app.jinja_env.globals.update(can_access=can_access)
Run Code Online (Sandbox Code Playgroud)

最后,auth.can_access

def can_access(f):
    if not hasattr(f, 'access_control'):
        return True

    # Use the access_control member set by the decorator
    return _eval_access(**f.access_control) == AccessResult.ALLOWED
Run Code Online (Sandbox Code Playgroud)

此解决方案意味着访问控制是在单个位置定义的 - 即函数装饰器。