【问题标题】:Hiding inaccessible links in Jinja2 templates在 Jinja2 模板中隐藏不可访问的链接
【发布时间】:2013-12-10 03:35:40
【问题描述】:

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

@app.route('/action1')
@security_requirements(roles=['some_role'])
def action1():
    ...

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

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

【问题讨论】:

  • security_requirements你的装饰师吗?可以改吗?
  • @twil - 是的,这是我的

标签: python web permissions flask jinja2


【解决方案1】:

我使用Flask-Security,它将许多登录/安全模块捆绑在一个漂亮的包中。它带有 Flask-Principal 提供的角色管理功能,您可以这样做:

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

你可以see how that's implemented in the sourcecurrent_user代理来自Flask-Login

【讨论】:

  • 我想使用一个地方来存储每个操作允许的角色列表。如果我理解正确,即使使用 Flask-Security,我也必须为每个操作编写两个列表 - 在服务器端代码和模板中。
  • 所以您的目标是:{% if current_user.can_access(url_for('protected_endpoint')) %} ADMIN ONLY {% endif %}?
  • 理想的做法是扩展flask的url_map本身,并在其中实际保存权限信息,因此当您实际定义app.route时,您可以在那里指定角色,例如:@987654328 @。通过查看 url_map,从 jinja2 使用起来会更好,也更容易查找。这是一个有趣的问题,我会在有空的时候研究一下。
  • 对于一个可能更简单的解决方案(但不是那么漂亮),您可以创建一个将端点链接到角色的商店。然后,它不是在装饰器中指定角色,而是在数据存储中查找它。您的 jinja2 模板过滤器也可以执行相同的操作(current_user.roles 与数据存储区列为必需角色的内容)。
【解决方案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
        ...

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

{% if can_access(func) %}
<li><a>...</a></li>
{% endif %}

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

def can_access(func):
    return auth.can_access(app.view_functions[func])

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

app.jinja_env.globals.update(can_access=can_access)

最后,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

这个解决方案意味着访问控制被定义在一个地方——就是函数装饰器。

【讨论】:

  • 我不完全理解,但是,很好!
  • 你应该把它作为一个补丁提交,这将是一个很棒的功能。
  • 有人告诉我 Python 生态系统有满足各种需求的 pip 库,但我发现自己每天都必须实现这样的基本功能。干得好,你真的应该接受 Flask-Security 或 Flask-Principal 并提交这个。
猜你喜欢
  • 2012-12-15
  • 1970-01-01
  • 1970-01-01
  • 2013-01-30
  • 2020-07-13
  • 2014-01-23
  • 1970-01-01
  • 1970-01-01
  • 2012-06-28
相关资源
最近更新 更多