We're writing a web application in Flask + Jinja2 at work. The application has registered users which can access certain pages based on their roles. In order to achieve this on the server side we just use decorate the pages:
@app.route('/action1')
@security_requirements(roles=['some_role'])
def action1():
...
The decorator checks if the logged on user has 'some_role' in its role list and decides whether to pass the call to the decorated function or just redirect the user to the "access denied" page.
The application also has a navigation bar implemented using bootstrap. The navigation bar is displayed in each page using a base template. As for now, every page in the application has an entry in the navigation bar, regardless if the current user can access it or not. Despite the fact that this is not a security hole, I would like to hide from users pages which they cannot access. Furthermore, I would like to achieve this functionality without duplicating the allowed roles lists inside the Jinja templates. Is it possible to achieve this functionality in Jinja somehow by using my current decorator?
I changed the
security_requirements
decorator to look like this: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 from inside the decorator. However, now I can implement the following action to be called from the Jinja template:
The can_access function is defined in the Flask application module. It receives a string which it must convert to a function object. It does that by calling
app.view_functions
:This function should be called directly from a Jinja template. So it needs to be added to Jinja's globals:
Finally,
auth.can_access
:This solution means that the access control is defined in a single place - which is the function decorator.
I use Flask-Security, which ties a lot of the login/security modules together in a nice package. It comes with role management courtesy of Flask-Principal, which will allow you to do:
You can see how that's implemented in the source, the
current_user
proxy comes from Flask-Login