Hide inaccessible links in Jinja2 templates

We are writing a web application in Flask + Jinja2 at work. The application has registered users who can access certain pages depending on their roles. To achieve this on the server side, we simply use page decorating:

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

The decorator checks whether the registered user has "some_role" in his role list and decides whether to transfer the call to the decorated function or simply redirect the user to the "access denied" page.

The application also has a navigation bar implemented using bootstrap. A navigation bar is displayed on each page using a basic template. At the moment, each page of the application has an entry in the navigation bar, regardless of whether the current user can access it or not. Although this is not a security kernel, I would like to hide user pages that they cannot access. In addition, I would like to achieve this functionality without duplicating the lists of allowed roles inside Jinja templates. Is it possible to implement this functionality in Jinja somehow using my current decorator?

+7
python flask jinja2 web permissions
source share
2 answers

I changed the security_requirements decorator to look like this:

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

The only real difference from the previous version of this decorator is the line that stores the security attributes inside the function object. This line is useless inside the decorator. However, now I can implement the following action for calling from a Jinja template:

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

The can_access function is defined in the Flask application module. It gets the string that it must convert to a function object. He does this by calling app.view_functions :

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

This function should be called directly from the Jinja template. Therefore, it should be added to Jinja global variables:

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

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

This solution means that access control is defined in one place - which is the function decorator.

+2
source share

I use Flask-Security , which bundles many login / security modules together in a nice package. It includes managing the role of the kindly provided Flask-Principal, which allows you to do:

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

You can see how this is implemented in the source , the current_user proxy comes from Flask-Login

+5
source share

All Articles