# HG changeset patch # User Ludovic Chabant # Date 1442917041 25200 # Node ID 831b22e93f94c1eea0422be3d1d39c2efe315dff # Parent 73e0564e4feae2083f633c3258a99879054f2670 Implement error and permission handling for UI views. * Views are protected by decorators. * Decorators check for permission errors and render error pages. * Global Flask error handling is for API endpoints. diff -r 73e0564e4fea -r 831b22e93f94 wikked/auth.py --- a/wikked/auth.py Tue Sep 22 03:15:42 2015 -0700 +++ b/wikked/auth.py Tue Sep 22 03:17:21 2015 -0700 @@ -61,6 +61,19 @@ def isPageWritable(self, page, username): return self._isAllowedForMeta(page, 'writers', username) + def hasPermission(self, meta_name, username): + perm = self._permissions.get(meta_name) + if perm is not None: + # Permissions are declared at the wiki level. + if username is None and 'anonymous' in perm: + return True + if username is not None and ( + '*' in perm or username in perm): + return True + return False + + return True + def _isAllowedForMeta(self, page, meta_name, username): perm = page.getMeta(meta_name) if perm is not None: @@ -77,17 +90,7 @@ return False - perm = self._permissions.get(meta_name) - if perm is not None: - # Permissions are declared at the wiki level. - if username is None and 'anonymous' in perm: - return True - if username is not None and ( - '*' in perm or username in perm): - return True - return False - - return True + return self.hasPermission(meta_name, username) def _updatePermissions(self, config): self._permissions = { diff -r 73e0564e4fea -r 831b22e93f94 wikked/templates/error-unauthorized.html --- a/wikked/templates/error-unauthorized.html Tue Sep 22 03:15:42 2015 -0700 +++ b/wikked/templates/error-unauthorized.html Tue Sep 22 03:17:21 2015 -0700 @@ -2,11 +2,19 @@ {% block content %}
-

You're not authorized for this

+

{{error|default("You're not authorized for this")}}

-

The page you're trying to access is protected. - Please log into an account that has access to it.

+ {%if error_details%} +

{{error_details}}

+ {%else%} +

The page you're trying to access is protected.

+ {%endif%} + {%if auth.is_logged_in%} +

User {{auth.username}} is not authorized for this. Please log into a different account to have access to it.

+ {%else%} +

Please log into an account that has access to it.

+ {%endif%}
{% endblock %} diff -r 73e0564e4fea -r 831b22e93f94 wikked/views/__init__.py --- a/wikked/views/__init__.py Tue Sep 22 03:15:42 2015 -0700 +++ b/wikked/views/__init__.py Tue Sep 22 03:17:21 2015 -0700 @@ -1,6 +1,56 @@ -from flask import request +import functools +from flask import request, render_template from flask.ext.login import current_user -from wikked.web import app +from wikked.web import app, get_wiki +from wikked.webimpl import PermissionError + + +def show_unauthorized_error(error=None, error_details=None, tpl_name=None): + if error is not None: + error = str(error) + + data = {} + if error: + data['error'] = error + if error_details: + data['error_details'] = error_details + + add_auth_data(data) + add_navigation_data(None, data) + tpl_name = tpl_name or 'error-unauthorized.html' + return render_template(tpl_name, **data) + + +def errorhandling_ui(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except PermissionError as ex: + return show_unauthorized_error(ex) + return wrapper + + +def errorhandling_ui2(tpl_name): + def decorator(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except PermissionError as ex: + return show_unauthorized_error(ex, tpl_name=tpl_name) + return wrapper + return decorator + + +def requires_reader_auth(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + wiki = get_wiki() + if not wiki.auth.hasPermission('readers', current_user.get_id()): + return show_unauthorized_error() + return f(*args, **kwargs) + return wrapper def add_auth_data(data): diff -r 73e0564e4fea -r 831b22e93f94 wikked/views/edit.py --- a/wikked/views/edit.py Tue Sep 22 03:15:42 2015 -0700 +++ b/wikked/views/edit.py Tue Sep 22 03:17:21 2015 -0700 @@ -1,6 +1,7 @@ from flask import redirect, url_for, request, render_template from flask.ext.login import current_user from wikked.views import ( + errorhandling_ui2, show_unauthorized_error, add_auth_data, add_navigation_data) from wikked.web import app, get_wiki from wikked.webimpl import url_from_viewarg @@ -14,6 +15,11 @@ @app.route('/create/') def create_page(url): + wiki = get_wiki() + if not wiki.auth.hasPermission('writers', current_user.get_id()): + return show_unauthorized_error( + error="You're not authorized to create new pages.") + data = { 'is_new': True, 'create_in': url.lstrip('/'), @@ -30,12 +36,14 @@ @app.route('/edit', methods=['POST']) +@errorhandling_ui2('error-unauthorized-edit.html') def edit_new_page(): url = request.form['title'] return edit_page(url) @app.route('/edit/', methods=['GET', 'POST']) +@errorhandling_ui2('error-unauthorized-edit.html') def edit_page(url): wiki = get_wiki() user = current_user.get_id() diff -r 73e0564e4fea -r 831b22e93f94 wikked/views/error.py --- a/wikked/views/error.py Tue Sep 22 03:15:42 2015 -0700 +++ b/wikked/views/error.py Tue Sep 22 03:17:21 2015 -0700 @@ -23,7 +23,7 @@ resp = { 'error': { 'type': 'permission', - 'message': error.message + 'message': str(error) } } return jsonify(resp), 403 diff -r 73e0564e4fea -r 831b22e93f94 wikked/views/history.py --- a/wikked/views/history.py Tue Sep 22 03:15:42 2015 -0700 +++ b/wikked/views/history.py Tue Sep 22 03:17:21 2015 -0700 @@ -1,7 +1,9 @@ import urllib.parse from flask import request, abort, render_template from flask.ext.login import current_user -from wikked.views import add_auth_data, add_navigation_data +from wikked.views import ( + errorhandling_ui, requires_reader_auth, + add_auth_data, add_navigation_data) from wikked.web import app, get_wiki from wikked.webimpl import url_from_viewarg from wikked.webimpl.history import ( @@ -10,6 +12,7 @@ @app.route('/special/history') +@requires_reader_auth def site_history(): wiki = get_wiki() user = current_user.get_id() @@ -26,6 +29,7 @@ @app.route('/hist/') +@errorhandling_ui def page_history(url): wiki = get_wiki() user = current_user.get_id() @@ -40,6 +44,7 @@ @app.route('/rev/') +@errorhandling_ui def page_rev(url): rev = request.args.get('rev') if rev is None: @@ -62,6 +67,7 @@ @app.route('/diff/') +@errorhandling_ui def diff_page(url): rev1 = request.args.get('rev1') rev2 = request.args.get('rev2') @@ -89,6 +95,7 @@ @app.route('/diff_rev/') +@errorhandling_ui def diff_revision(rev): wiki = get_wiki() user = current_user.get_id() diff -r 73e0564e4fea -r 831b22e93f94 wikked/views/read.py --- a/wikked/views/read.py Tue Sep 22 03:15:42 2015 -0700 +++ b/wikked/views/read.py Tue Sep 22 03:17:21 2015 -0700 @@ -2,7 +2,7 @@ from flask import ( render_template, request, abort) from flask.ext.login import current_user -from wikked.views import add_auth_data, add_navigation_data +from wikked.views import add_auth_data, add_navigation_data, errorhandling_ui from wikked.web import app, get_wiki from wikked.webimpl import url_from_viewarg from wikked.webimpl.read import ( @@ -18,6 +18,7 @@ @app.route('/read/') +@errorhandling_ui def read(url): wiki = get_wiki() url = url_from_viewarg(url) @@ -33,6 +34,7 @@ @app.route('/search') +@errorhandling_ui def search(): query = request.args.get('q') if query is None or query == '': @@ -55,6 +57,7 @@ @app.route('/inlinks/') +@errorhandling_ui def incoming_links(url): wiki = get_wiki() user = current_user.get_id() diff -r 73e0564e4fea -r 831b22e93f94 wikked/views/special.py --- a/wikked/views/special.py Tue Sep 22 03:15:42 2015 -0700 +++ b/wikked/views/special.py Tue Sep 22 03:17:21 2015 -0700 @@ -1,6 +1,8 @@ from flask import render_template from flask.ext.login import current_user -from wikked.views import add_auth_data, add_navigation_data +from wikked.views import ( + requires_reader_auth, + add_auth_data, add_navigation_data) from wikked.web import app, get_wiki from wikked.webimpl.special import ( get_orphans, get_broken_redirects, get_double_redirects, @@ -70,6 +72,7 @@ @app.route('/special') +@requires_reader_auth def special_pages_dashboard(): data = { 'is_special_page': True, @@ -106,24 +109,28 @@ @app.route('/special/list/orphans') +@requires_reader_auth def special_list_orphans(): return call_api('orphans', get_orphans, raw_url='/api/orphans') @app.route('/special/list/broken-redirects') +@requires_reader_auth def special_list_broken_redirects(): return call_api('broken-redirects', get_broken_redirects, raw_url='/api/broken-redirects') @app.route('/special/list/double-redirects') +@requires_reader_auth def special_list_double_redirects(): return call_api('double-redirects', get_double_redirects, raw_url='/api/double-redirects') @app.route('/special/list/dead-ends') +@requires_reader_auth def special_list_dead_ends(): return call_api('dead-ends', get_dead_ends, raw_url='/api/dead-ends')