Ocultar enlaces inaccesibles en plantillas de Jinja2

Estamos escribiendo una aplicación web en Flask + Jinja2 en el trabajo. La aplicación tiene usuarios registrados que pueden acceder a ciertas páginas según sus roles. Para lograr esto en el lado del servidor solo usamos decorar las páginas:

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

El decorador verifica si el usuario que inició sesión tiene ‘some_role’ en su lista de roles y decide si pasar la llamada a la función decorada o simplemente redirigir al usuario a la página de “acceso denegado”.

La aplicación también tiene una barra de navegación implementada usando bootstrap. La barra de navegación se muestra en cada página utilizando una plantilla base. Por ahora, todas las páginas de la aplicación tienen una entrada en la barra de navegación, independientemente de si el usuario actual puede acceder o no. A pesar de que esto no es un agujero de seguridad, me gustaría ocultar a los usuarios las páginas a las que no pueden acceder. Además, me gustaría lograr esta funcionalidad sin duplicar las listas de roles permitidas dentro de las plantillas de Jinja. ¿Es posible lograr esta funcionalidad en Jinja de alguna manera usando mi decorador actual?

Uso Flask-Security , que une muchos de los módulos de inicio de sesión / seguridad en un paquete agradable. Viene con la administración de roles, cortesía de Flask-Principal, que le permitirá hacer:

 {% if current_user.has_role('admin') %} 
  • Manage Site
  • {% endif %}

    Puede ver cómo se implementa en la fuente , el proxy current_user proviene de Flask-Login

    Cambié el decorador security_requirements para tener este aspecto:

     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 ... 

    La única diferencia real con respecto a la versión anterior de este decorador es la línea que almacena los atributos de seguridad dentro del objeto de función. Esta línea es inútil desde el interior del decorador. Sin embargo, ahora puedo implementar la siguiente acción desde la plantilla de Jinja:

     {% if can_access(func) %} 
  • ...
  • {% endif %}

    La función can_access se define en el módulo de la aplicación Flask. Recibe una cadena que debe convertir en un objeto de función. Lo hace llamando a app.view_functions :

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

    Esta función debe ser llamada directamente desde una plantilla de Jinja. Por lo tanto, debe agregarse a los globales de Jinja:

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

    Finalmente, 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 

    Esta solución significa que el control de acceso se define en un solo lugar, que es la función decoradora.