diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..8b84e389adfc97cfeba38ca6ad6c68f7e986438d --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +ignore = E402,W503 + diff --git a/back.py b/back.py index 57c91f332a845c619caf29d9d3fb41dcc305201c..8afa00be5a19e22d2688e6bc0c908512dac217a8 100644 --- a/back.py +++ b/back.py @@ -19,9 +19,17 @@ def anchor(func, cookie=cookie): return result +def default_url(default, **url_args): + return url_for(default, **url_args) + + def url(default=default_view, cookie=cookie, **url_args): - return session.get(cookie, url_for(default, **url_args)) + return session.get(cookie, default_url(default, **url_args)) def redirect(default=default_view, cookie=cookie, **url_args): - return flask_redirect(url(default, cookie, **url_args)) + print(request.url, request.url_rule, default, session.get(cookie)) + target = url(default, cookie, **url_args) + if target == request.url: + target = default_url(default, **url_args) + return flask_redirect(target) diff --git a/config.py.example b/config.py.example index 581ca2258cc1f74ba69b02a9fdcafc4f7e33565d..5c4cd1502fe4f3799bffb79448207999fd7f031d 100644 --- a/config.py.example +++ b/config.py.example @@ -25,6 +25,9 @@ CELERY_BROKER_URL = "redis://localhost:6379/0" # change this if you do not use r CELERY_TASK_SERIALIZER = "pickle" # do not change CELERY_ACCEPT_CONTENT = ["pickle"] # do not change +# Send exceptions to sentry (optional) +# SENTRY_DSN = "https://********:********@sentry.example.com//1" + # CUPS printserver (optional) PRINTING_ACTIVE = True PRINTING_SERVER = "printsrv.example.com:631" diff --git a/decorators.py b/decorators.py index 17b73569653f0b514b910aa6ef152884252d2fb8..7057c004aeaa05894f4f5f40568b221daa082473 100644 --- a/decorators.py +++ b/decorators.py @@ -1,6 +1,7 @@ from flask import request, flash, abort from functools import wraps +from hmac import compare_digest from models.database import ALL_MODELS from shared import current_user @@ -97,8 +98,8 @@ def protect_csrf(function): @wraps(function) def _decorated_function(*args, **kwargs): token = request.args.get("csrf_token") - if token != get_csrf_token(): - print(token, get_csrf_token()) + true_token = get_csrf_token() + if token is None or not compare_digest(token, true_token): abort(400) return function(*args, **kwargs) return _decorated_function diff --git a/models/database.py b/models/database.py index 69f2bde49162f95a58e579655af750dee82158e3..b9f64aa9f18016f9d3644b83a88f9b26f0964b26 100644 --- a/models/database.py +++ b/models/database.py @@ -376,7 +376,6 @@ class Protocol(DatabaseModel): candidates = [ document for document in self.documents if document.is_compiled - and (private is None or document.is_private == private) ] private_candidates = [ document for document in candidates @@ -386,10 +385,14 @@ class Protocol(DatabaseModel): document for document in candidates if not document.is_private ] - if len(private_candidates) > 0: - return private_candidates[0] - elif len(public_candidates) > 0: - return public_candidates[0] + + def _get_candidates(): + if private is None or private: + return private_candidates + public_candidates + return public_candidates + candidates = _get_candidates() + if candidates: + return candidates[0] return None def get_template(self): diff --git a/requirements.txt b/requirements.txt index a5143e52aaaae6b419859f9a008800d03e3121a7..899198691c333e40b7c20c5c28d2de26ddaa798c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ curtsies==0.3.0 enum-compat==0.0.2 eventlet==0.22.1 feedgen==0.6.1 +flake8==3.5.0 Flask==0.12.2 Flask-Migrate==2.1.1 Flask-Script==2.0.6 @@ -34,15 +35,18 @@ ldap3==2.4.1 lxml==4.1.1 Mako==1.0.7 MarkupSafe==1.0 +mccabe==0.6.1 nose==1.3.7 packaging==16.8 pathtools==0.1.2 psycopg2==2.7.4 pyasn1==0.4.2 +pycodestyle==2.3.1 +pyflakes==1.6.0 Pygments==2.2.0 pyldap==2.4.45 pyparsing==2.2.0 -python-dateutil==2.6.1 +python-dateutil==2.7.0 python-editor==1.0.3 python-engineio==2.0.2 python-Levenshtein==0.12.0 @@ -50,6 +54,7 @@ python-pam==1.8.2 python-socketio==1.8.4 pytz==2018.3 PyYAML==3.12 +raven==6.6.0 redis==2.10.6 regex==2018.2.8 requests==2.18.4 diff --git a/server.py b/server.py index 11c1b460aabce265d9ef8fd49a65ed0db90b7354..639e8f80169a1b2861a146995fa13df82c14447d 100755 --- a/server.py +++ b/server.py @@ -31,7 +31,7 @@ from shared import ( from utils import ( get_first_unused_int, get_etherpad_text, split_terms, optional_int_arg, fancy_join, footnote_hash, get_git_revision, get_max_page_length_exp, - get_internal_filename, get_csrf_token) + get_internal_filename, get_csrf_token, get_current_ip) from decorators import ( db_lookup, protect_csrf, require_private_view_right, require_modify_right, require_publish_right, @@ -64,10 +64,37 @@ migrate = Migrate(app, db) manager = Manager(app) manager.add_command("db", MigrateCommand) +try: + from raven.contrib.flask import Sentry + sentry = Sentry(app, dsn=config.SENTRY_DSN) + + def get_user_info(request): + return { + "is_authenticated": check_login(), + "ip_address": get_current_ip(), + "release": get_git_revision(), + } + sentry.get_user_info = get_user_info +except ModuleNotFoundError: + print("Raven not installed. Not sending issues to Sentry.") +except AttributeError: + print("DSN not configured. Not sending issues to Sentry.") + def make_celery(app, config): celery = Celery(app.import_name, broker=config.CELERY_BROKER_URL) celery.conf.update(app.config) + try: + from raven import Client as RavenClient + from raven.contrib.celery import ( + register_signal, register_logger_signal) + raven_client = RavenClient(config.SENTRY_DSN) + register_logger_signal(raven_client) + register_signal(raven_client) + except ModuleNotFoundError: + print("Raven not installed. Not sending celery issues to Sentry.") + except AttributeError: + print("DSN not configured. Not sending celery issues to Sentry.") return celery diff --git a/templates/decisions-list.html b/templates/decisions-list.html index 667eef2d66d228a07ce673c80ab372b4fbaf42af..dc0aed8edb58bc5801b029d2364809e272782753 100644 --- a/templates/decisions-list.html +++ b/templates/decisions-list.html @@ -2,16 +2,6 @@ {% from "macros.html" import render_table, render_form %} {% block title %}Beschlüsse{% endblock %} -{% macro page_link(text, _page=None, _page_length=None) %} - {% if _page is none %} - {% set _page = page %} - {% endif %} - {% if _page_length is none %} - {% set _page_length = page_length %} - {% endif %} - <a href="{{url_for(request.endpoint, page=_page, protocoltype_id=protocoltype_id, search=search_term, decisioncategory_id=decisioncategory_id, page_length=_page_length)}}">{{text}}</a> -{% endmacro %} - {% block content %} <div class="container"> {{render_form(search_form, class_="form-inline", action_url=url_for("list_decisions"), action_text="Suchen", labels_visible=False, method="GET")}} diff --git a/templates/macros.html b/templates/macros.html index e6d0edd7c26a03c329daaccd5abd0e6f33815956..230a10073978ed63fe17a60cf77735d312e874ab 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -165,6 +165,18 @@ to not render a label for the CRSFTokenField --> </table> {%- endmacro %} +{% macro page_link(text, _page=None, _page_length=None) -%} + {% set args = request.view_args.copy() %} + {% set _ = args.update(request.args) %} + {% if _page is not none %} + {% set _ = args.update({"page": _page}) %} + {% endif %} + {% if _page_length is not none %} + {% set _ = args.update({"page_length": _page_length}) %} + {% endif %} + <a href="{{url_for(request.endpoint, **args)}}">{{text}}</a> +{%- endmacro %} + {% macro render_likes(likes) -%} {% set timestamp = now() %} {% if timestamp.month == 4 and timestamp.day == 1 %} diff --git a/templates/pagination-footer.html b/templates/pagination-footer.html index 1f34556743d148f291e56d4c2fcbb1a313dce21b..19ae96bd5ce505313397b7d85e8a847dc7335cf1 100644 --- a/templates/pagination-footer.html +++ b/templates/pagination-footer.html @@ -1,3 +1,4 @@ +{% from "macros.html" import page_link %} <div class="centered"> {% if page > page_diff %} {{page_link("<<", _page=0)}} diff --git a/templates/protocol-show.html b/templates/protocol-show.html index ba615a8260f9bb0158c176c04bbffb21a03a09c6..b5546fe387a9786922c32abfee4a616d452431e1 100644 --- a/templates/protocol-show.html +++ b/templates/protocol-show.html @@ -51,7 +51,7 @@ {% endif %} {% if has_admin_right %} <a class="btn btn-default" href="{{url_for("recompile_protocol", protocol_id=protocol.id, csrf_token=get_csrf_token())}}">Neu kompilieren</a> - <a class="btn btn-danger" href="{{url_for("delete_protocol", protocol_id=protocol.id)}}" onclick="return confirm('Bist du dir sicher, dass du das Protokoll {{protocol.get_short_identifier()}} löschen möchtest?');">Löschen</a> + <a class="btn btn-danger" href="{{url_for("delete_protocol", protocol_id=protocol.id, csrf_token=get_csrf_token())}}" onclick="return confirm('Bist du dir sicher, dass du das Protokoll {{protocol.get_short_identifier()}} löschen möchtest?');">Löschen</a> {% endif %} {% endif %} </div> diff --git a/templates/protocols-list.html b/templates/protocols-list.html index 02d3c81ee505e06950b72655f09d17243aa5a886..e019a255ae23589f55fb9df9d220c223d21d8ea9 100644 --- a/templates/protocols-list.html +++ b/templates/protocols-list.html @@ -2,18 +2,6 @@ {% from "macros.html" import render_table, render_form %} {% block title %}Protokolle{% endblock %} -{% macro page_link(text, _page=None, _page_length=None) %} - {% if _page is none %} - {% set _page = page %} - {% endif %} - {% if _page_length is none %} - {% set _page_length = page_length %} - {% endif %} - <a href="{{url_for(request.endpoint, page=_page, protocoltype_id=protocoltype_id, search=search_term, state_open=state_open, page_length=_page_length)}}">{{text}}</a> -{% endmacro %} - - - {% block content %} <div class="container"> {{render_form(search_form, class_="form-inline", action_url=url_for("list_protocols"), action_text="Suchen", labels_visible=False, method="GET")}} diff --git a/templates/todos-list.html b/templates/todos-list.html index 0734992cdf861046927284fef46743b8d8338a3b..0c9923ee51cd34f9147f7c506a84abd73690b877 100644 --- a/templates/todos-list.html +++ b/templates/todos-list.html @@ -2,16 +2,6 @@ {% from "macros.html" import render_table, render_form %} {% block title %}Todos{% endblock %} -{% macro page_link(text, _page=None, _page_length=None) %} - {% if _page is none %} - {% set _page = page %} - {% endif %} - {% if _page_length is none %} - {% set _page_length = page_length %} - {% endif %} - <a href="{{url_for(request.endpoint, page=_page, protocoltype_id=protocoltype_id, search=search_term, state_open=state_open, page_length=_page_length)}}">{{text}}</a> -{% endmacro %} - {% block content %} <div class="container"> {{render_form(search_form, class_="form-inline", action_url=url_for("list_todos"), action_text="Suchen", labels_visible=False, method="GET")}} diff --git a/utils.py b/utils.py index 5e80b9cbebc40ae8cacf6cff9201fa12c1577e17..116a2f157c623617bc501c5fb089c35bdf54fde2 100644 --- a/utils.py +++ b/utils.py @@ -193,11 +193,16 @@ def add_line_numbers(text): return "\n".join(lines) -def check_ip_in_networks(networks_string): +def get_current_ip(): address = ipaddress.ip_address(request.remote_addr) if (address == ipaddress.ip_address("127.0.0.1") and "X-Real-Ip" in request.headers): address = ipaddress.ip_address(request.headers["X-Real-Ip"]) + return address + + +def check_ip_in_networks(networks_string): + address = get_current_ip() try: for network_string in networks_string.split(","): network = ipaddress.ip_network(network_string.strip()) diff --git a/views/tables.py b/views/tables.py index e3f60f581c0c7a6e44ef31506be60da537e425bc..7c75dd158a481b14c39730764b852cbc03494fc6 100644 --- a/views/tables.py +++ b/views/tables.py @@ -144,7 +144,9 @@ class ProtocolsTable(Table): if protocol.protocoltype.has_admin_right(user): buttons.append(Table.button( - url_for("delete_protocol", protocol_id=protocol.id), + url_for( + "delete_protocol", protocol_id=protocol.id, + csrf_token=get_csrf_token()), icon="trash", style="danger", confirm="Bist du dir sicher, dass du das Protokoll {} "