From 8a286d369bc612c4047d9219e065969f5e66349a Mon Sep 17 00:00:00 2001 From: heartsucker Date: Sat, 23 Sep 2017 11:13:15 +0200 Subject: [PATCH 01/31] moved source app into source_app/__init__.py, change resource paths --- Makefile | 1 + .../files/usr.sbin.apache2 | 3 +++ securedrop/.rsync-filter | 2 ++ securedrop/i18n.py | 14 +++++++++++++- securedrop/source.py | 8 +++----- securedrop/source_app/__init__.py | 18 ++++++++++++++++++ securedrop/tests/test_i18n.py | 3 +-- 7 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 securedrop/source_app/__init__.py diff --git a/Makefile b/Makefile index 5fd3b0e934..eff59fb7c1 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,7 @@ docs: ## Build project documentation in live reload for editing flake8: ## Validates PEP8 compliance for Python source files. flake8 --exclude='config.py' testinfra securedrop-admin \ securedrop/*.py securedrop/management \ + securedrop/source_app/*.py \ securedrop/tests/functional securedrop/tests/*.py .PHONY: html-lint diff --git a/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 b/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 index 166d6a023b..3ddcfd181a 100644 --- a/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 +++ b/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 @@ -154,6 +154,9 @@ /var/www/securedrop/i18n.pyc rw, /var/www/securedrop/source.py r, /var/www/securedrop/source.pyc rw, + /var/www/securedrop/source_app/__init__.py r, + /var/www/securedrop/source_app/__init__.pyc rw, + /var/www/securedrop/source_app/__pycache__/** rw, /var/www/securedrop/source_templates/banner_warning_flashed.html r, /var/www/securedrop/source_templates/base.html r, /var/www/securedrop/source_templates/error.html r, diff --git a/securedrop/.rsync-filter b/securedrop/.rsync-filter index 0b740682f1..3b29a14012 100644 --- a/securedrop/.rsync-filter +++ b/securedrop/.rsync-filter @@ -10,6 +10,8 @@ include management/ include management/*.py include requirements/ include requirements/**.txt +include source_app/ +include source_app/*.py include source_templates/ include source_templates/*.html include static/ diff --git a/securedrop/i18n.py b/securedrop/i18n.py index 7e2e5c929b..2dd6fa56a9 100644 --- a/securedrop/i18n.py +++ b/securedrop/i18n.py @@ -24,6 +24,8 @@ import os import re +from os import path + LOCALE_SPLIT = re.compile('(-|_)') LOCALES = set(['en_US']) babel = None @@ -34,8 +36,18 @@ class LocaleNotFound(Exception): """Raised when the desired locale is not in the translations directory""" -def setup_app(app): +def setup_app(app, translation_dirs=None): global babel + + if translation_dirs is None: + translation_dirs = \ + path.join(path.dirname(path.realpath(__file__)), + 'translations') + + # `babel.translation_directories` is a nightmare + # We need to set this manually via an absolute path + app.config['BABEL_TRANSLATION_DIRECTORIES'] = translation_dirs + babel = Babel(app) assert len(list(babel.translation_directories)) == 1 for dirname in os.listdir(next(babel.translation_directories)): diff --git a/securedrop/source.py b/securedrop/source.py index e7181cda73..6d298a8f72 100644 --- a/securedrop/source.py +++ b/securedrop/source.py @@ -6,7 +6,7 @@ import subprocess from threading import Thread import operator -from flask import (Flask, request, render_template, session, redirect, url_for, +from flask import (request, render_template, session, redirect, url_for, flash, abort, g, send_file, Markup, make_response) from flask_wtf.csrf import CSRFProtect from flask_assets import Environment @@ -24,17 +24,15 @@ import store import template_filters from db import db_session, Source, Submission, Reply, get_one_or_else -from request_that_secures_file_uploads import RequestThatSecuresFileUploads from jinja2 import evalcontextfilter +from source_app import create_app import logging # This module's logger is explicitly labeled so the correct logger is used, # even when this is run from the command line (e.g. during development) log = logging.getLogger('source') -app = Flask(__name__, template_folder=config.SOURCE_TEMPLATES_DIR) -app.request_class = RequestThatSecuresFileUploads -app.config.from_object(config.SourceInterfaceFlaskConfig) +app = create_app() i18n.setup_app(app) diff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py new file mode 100644 index 0000000000..ebeac32a76 --- /dev/null +++ b/securedrop/source_app/__init__.py @@ -0,0 +1,18 @@ +from flask import Flask +from os import path + +import config as global_config +from request_that_secures_file_uploads import RequestThatSecuresFileUploads + + +def create_app(config=None): + if config is None: + config = global_config + + app = Flask(__name__, + template_folder=config.SOURCE_TEMPLATES_DIR, + static_folder=path.join(config.SECUREDROP_ROOT, 'static')) + app.request_class = RequestThatSecuresFileUploads + app.config.from_object(config.SourceInterfaceFlaskConfig) + + return app diff --git a/securedrop/tests/test_i18n.py b/securedrop/tests/test_i18n.py index 3dd93ec19e..b65c630716 100644 --- a/securedrop/tests/test_i18n.py +++ b/securedrop/tests/test_i18n.py @@ -206,8 +206,7 @@ def test_i18n(self): if supported: del config.SUPPORTED_LOCALES for app in (journalist.app, source.app): - app.config['BABEL_TRANSLATION_DIRECTORIES'] = config.TEMP_DIR - i18n.setup_app(app) + i18n.setup_app(app, translation_dirs=config.TEMP_DIR) self.verify_i18n(app) finally: if supported: From 5c759e50bfd126c0004812f7dfd97b933e0c0417 Mon Sep 17 00:00:00 2001 From: heartsucker Date: Sat, 23 Sep 2017 11:20:44 +0200 Subject: [PATCH 02/31] move csrf protection, assets into create_app function --- securedrop/source.py | 9 --------- securedrop/source_app/__init__.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/securedrop/source.py b/securedrop/source.py index 6d298a8f72..c7cbb9d9fd 100644 --- a/securedrop/source.py +++ b/securedrop/source.py @@ -8,8 +8,6 @@ import operator from flask import (request, render_template, session, redirect, url_for, flash, abort, g, send_file, Markup, make_response) -from flask_wtf.csrf import CSRFProtect -from flask_assets import Environment from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound from sqlalchemy.exc import IntegrityError @@ -33,15 +31,8 @@ log = logging.getLogger('source') app = create_app() - i18n.setup_app(app) -assets = Environment(app) - -# The default CSRF token expiration is 1 hour. Since large uploads can -# take longer than an hour over Tor, we increase the valid window to 24h. -app.config['WTF_CSRF_TIME_LIMIT'] = 60 * 60 * 24 -CSRFProtect(app) app.jinja_env.globals['version'] = version.__version__ if getattr(config, 'CUSTOM_HEADER_IMAGE', None): diff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py index ebeac32a76..5027943e60 100644 --- a/securedrop/source_app/__init__.py +++ b/securedrop/source_app/__init__.py @@ -1,4 +1,6 @@ from flask import Flask +from flask_assets import Environment +from flask_wtf.csrf import CSRFProtect from os import path import config as global_config @@ -15,4 +17,12 @@ def create_app(config=None): app.request_class = RequestThatSecuresFileUploads app.config.from_object(config.SourceInterfaceFlaskConfig) + # The default CSRF token expiration is 1 hour. Since large uploads can + # take longer than an hour over Tor, we increase the valid window to 24h. + app.config['WTF_CSRF_TIME_LIMIT'] = 60 * 60 * 24 + CSRFProtect(app) + + assets = Environment(app) + app.config['assets'] = assets + return app From 943a117a31935e5332c54cf2c1697a7a2eeb7e87 Mon Sep 17 00:00:00 2001 From: heartsucker Date: Sat, 23 Sep 2017 12:31:48 +0200 Subject: [PATCH 03/31] moved jinja filters into create_app --- securedrop/source.py | 16 ---------------- securedrop/source_app/__init__.py | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/securedrop/source.py b/securedrop/source.py index c7cbb9d9fd..f5a4684fff 100644 --- a/securedrop/source.py +++ b/securedrop/source.py @@ -20,9 +20,7 @@ from rm import srm import i18n import store -import template_filters from db import db_session, Source, Submission, Reply, get_one_or_else -from jinja2 import evalcontextfilter from source_app import create_app import logging @@ -31,20 +29,6 @@ log = logging.getLogger('source') app = create_app() -i18n.setup_app(app) - - -app.jinja_env.globals['version'] = version.__version__ -if getattr(config, 'CUSTOM_HEADER_IMAGE', None): - app.jinja_env.globals['header_image'] = config.CUSTOM_HEADER_IMAGE - app.jinja_env.globals['use_custom_header_image'] = True -else: - app.jinja_env.globals['header_image'] = 'logo.png' - app.jinja_env.globals['use_custom_header_image'] = False - -app.jinja_env.filters['datetimeformat'] = template_filters.datetimeformat -app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br) -app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat @app.teardown_appcontext diff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py index 5027943e60..62babc5407 100644 --- a/securedrop/source_app/__init__.py +++ b/securedrop/source_app/__init__.py @@ -1,10 +1,14 @@ from flask import Flask from flask_assets import Environment from flask_wtf.csrf import CSRFProtect +from jinja2 import evalcontextfilter from os import path import config as global_config +import i18n from request_that_secures_file_uploads import RequestThatSecuresFileUploads +import template_filters +import version def create_app(config=None): @@ -25,4 +29,20 @@ def create_app(config=None): assets = Environment(app) app.config['assets'] = assets + # this set needs to happen *before* we set the jinja filters otherwise + # we get name collisions + i18n.setup_app(app) + + app.jinja_env.globals['version'] = version.__version__ + if getattr(config, 'CUSTOM_HEADER_IMAGE', None): + app.jinja_env.globals['header_image'] = config.CUSTOM_HEADER_IMAGE + app.jinja_env.globals['use_custom_header_image'] = True + else: + app.jinja_env.globals['header_image'] = 'logo.png' + app.jinja_env.globals['use_custom_header_image'] = False + + app.jinja_env.filters['datetimeformat'] = template_filters.datetimeformat + app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br) + app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat + return app From f5f0c934418edc4910cb31d1f66273534ed0bce0 Mon Sep 17 00:00:00 2001 From: heartsucker Date: Sat, 23 Sep 2017 12:44:57 +0200 Subject: [PATCH 04/31] move error handlers to create_app --- securedrop/source.py | 10 ---------- securedrop/source_app/__init__.py | 10 +++++++++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/securedrop/source.py b/securedrop/source.py index f5a4684fff..e77dce547e 100644 --- a/securedrop/source.py +++ b/securedrop/source.py @@ -429,16 +429,6 @@ def metadata(): return resp -@app.errorhandler(404) -def page_not_found(error): - return render_template('notfound.html'), 404 - - -@app.errorhandler(500) -def internal_error(error): - return render_template('error.html'), 500 - - if __name__ == "__main__": # pragma: no cover debug = getattr(config, 'env', 'prod') != 'prod' app.run(debug=debug, host='0.0.0.0', port=8080) diff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py index 62babc5407..dfb5f946ed 100644 --- a/securedrop/source_app/__init__.py +++ b/securedrop/source_app/__init__.py @@ -1,4 +1,4 @@ -from flask import Flask +from flask import Flask, render_template from flask_assets import Environment from flask_wtf.csrf import CSRFProtect from jinja2 import evalcontextfilter @@ -45,4 +45,12 @@ def create_app(config=None): app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br) app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat + @app.errorhandler(404) + def page_not_found(error): + return render_template('notfound.html'), 404 + + @app.errorhandler(500) + def internal_error(error): + return render_template('error.html'), 500 + return app From b617ec22ccf4a2a4df6a75ecc4fd2f967b363be0 Mon Sep 17 00:00:00 2001 From: heartsucker Date: Sat, 23 Sep 2017 13:06:12 +0200 Subject: [PATCH 05/31] moved index route into blueprint, init blueprints --- securedrop/source.py | 9 ++------- securedrop/source_app/__init__.py | 6 +++++- securedrop/source_app/views.py | 15 +++++++++++++++ securedrop/source_templates/base.html | 2 +- securedrop/source_templates/login.html | 2 +- 5 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 securedrop/source_app/views.py diff --git a/securedrop/source.py b/securedrop/source.py index e77dce547e..e7237d6461 100644 --- a/securedrop/source.py +++ b/securedrop/source.py @@ -92,7 +92,7 @@ def setup_g(): (e,)) del session['logged_in'] del session['codename'] - return redirect(url_for('index')) + return redirect(url_for('main.index')) g.loc = store.path(g.filesystem_id) @@ -109,11 +109,6 @@ def check_tor2web(): "banner-warning") -@app.route('/') -def index(): - return render_template('index.html') - - def generate_unique_codename(): """Generate random codenames until we get an unused one""" while True: @@ -392,7 +387,7 @@ def logout(): session.clear() msg = render_template('logout_flashed_message.html') flash(Markup(msg), "important hide-if-not-tor-browser") - return redirect(url_for('index')) + return redirect(url_for('main.index')) @app.route('/tor2web-warning') diff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py index dfb5f946ed..63cad4c13e 100644 --- a/securedrop/source_app/__init__.py +++ b/securedrop/source_app/__init__.py @@ -6,10 +6,12 @@ import config as global_config import i18n -from request_that_secures_file_uploads import RequestThatSecuresFileUploads import template_filters import version +from request_that_secures_file_uploads import RequestThatSecuresFileUploads +from source_app import views + def create_app(config=None): if config is None: @@ -45,6 +47,8 @@ def create_app(config=None): app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br) app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat + views.add_blueprints(app) + @app.errorhandler(404) def page_not_found(error): return render_template('notfound.html'), 404 diff --git a/securedrop/source_app/views.py b/securedrop/source_app/views.py new file mode 100644 index 0000000000..ee18962698 --- /dev/null +++ b/securedrop/source_app/views.py @@ -0,0 +1,15 @@ +from flask import Blueprint, render_template + + +def add_blueprints(app): + app.register_blueprint(_main_blueprint()) + + +def _main_blueprint(): + view = Blueprint('main', 'main') + + @view.route('/') + def index(): + return render_template('index.html') + + return view diff --git a/securedrop/source_templates/base.html b/securedrop/source_templates/base.html index b8e6f7ddc1..81289bf6e6 100644 --- a/securedrop/source_templates/base.html +++ b/securedrop/source_templates/base.html @@ -15,7 +15,7 @@
{% block header %}