def test_filters(self): sources = [ 'tests/i18n/code.py', ] kwargs = { 'translations_dir': config.TEMP_DIR, 'mapping': 'tests/i18n/babel.cfg', 'source': sources, 'extract_update': True, 'compile': True, 'verbose': logging.DEBUG, 'version': version.__version__, } args = argparse.Namespace(**kwargs) manage.setup_verbosity(args) manage.translate_messages(args) manage.sh(""" pybabel init -i {d}/messages.pot -d {d} -l en_US pybabel init -i {d}/messages.pot -d {d} -l fr_FR """.format(d=config.TEMP_DIR)) supported = getattr(config, 'SUPPORTED_LOCALES', None) try: if supported: del config.SUPPORTED_LOCALES for app in (journalist.app, source.app): config.SUPPORTED_LOCALES = ['en_US', 'fr_FR'] app.config['BABEL_TRANSLATION_DIRECTORIES'] = config.TEMP_DIR i18n.setup_app(app) self.verify_filesizeformat(app) self.verify_rel_datetime_format(app) finally: if supported: config.SUPPORTED_LOCALES = supported
def test_i18n(self): sources = [ 'tests/i18n/code.py', 'tests/i18n/template.html', ] kwargs = { 'translations_dir': config.TEMP_DIR, 'mapping': 'tests/i18n/babel.cfg', 'source': sources, 'extract_update': True, 'compile': True, 'verbose': logging.DEBUG, 'version': version.__version__, } args = argparse.Namespace(**kwargs) manage.setup_verbosity(args) manage.translate_messages(args) manage.sh(""" pybabel init -i {d}/messages.pot -d {d} -l en_US pybabel init -i {d}/messages.pot -d {d} -l fr_FR sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code bonjour"/' \ {d}/fr_FR/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l zh_Hans_CN sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code chinese"/' \ {d}/zh_Hans_CN/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l ar sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code arabic"/' \ {d}/ar/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l nb_NO sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code norwegian"/' \ {d}/nb_NO/LC_MESSAGES/messages.po """.format(d=config.TEMP_DIR)) manage.translate_messages(args) supported = getattr(config, 'SUPPORTED_LOCALES', None) try: if supported: del config.SUPPORTED_LOCALES for app in (journalist.app, source.app): config.SUPPORTED_LOCALES = [ 'en_US', 'fr_FR', 'zh_Hans_CN', 'ar', 'nb_NO' ] i18n.setup_app(app, translation_dirs=config.TEMP_DIR) self.verify_i18n(app) finally: if supported: config.SUPPORTED_LOCALES = supported
def create_app(config): # type: (SDConfig) -> Flask 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) # type: ignore app.sdconfig = config # 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) if config.DATABASE_ENGINE == "sqlite": db_uri = (config.DATABASE_ENGINE + ":///" + config.DATABASE_FILE) else: db_uri = (config.DATABASE_ENGINE + '://' + config.DATABASE_USERNAME + ':' + config.DATABASE_PASSWORD + '@' + config.DATABASE_HOST + '/' + config.DATABASE_NAME) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = db_uri db.init_app(app) app.storage = Storage(config.STORE_DIR, config.TEMP_DIR, config.JOURNALIST_KEY) app.crypto_util = CryptoUtil( scrypt_params=config.SCRYPT_PARAMS, scrypt_id_pepper=config.SCRYPT_ID_PEPPER, scrypt_gpg_pepper=config.SCRYPT_GPG_PEPPER, securedrop_root=config.SECUREDROP_ROOT, word_list=config.WORD_LIST, nouns_file=config.NOUNS, adjectives_file=config.ADJECTIVES, gpg_key_dir=config.GPG_KEY_DIR, ) @app.errorhandler(CSRFError) def handle_csrf_error(e): msg = render_template('session_timeout.html') session.clear() flash(Markup(msg), "important") return redirect(url_for('main.index')) assets = Environment(app) app.config['assets'] = assets i18n.setup_app(config, app) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True app.jinja_env.globals['version'] = version.__version__ if getattr(config, 'CUSTOM_HEADER_IMAGE', None): app.jinja_env.globals['header_image'] = \ config.CUSTOM_HEADER_IMAGE # type: ignore 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['rel_datetime_format'] = \ template_filters.rel_datetime_format app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br) app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat for module in [main, info, api]: app.register_blueprint(module.make_blueprint(config)) # type: ignore @app.before_request @ignore_static def setup_i18n(): """Store i18n-related values in Flask's special g object""" g.locale = i18n.get_locale(config) g.text_direction = i18n.get_text_direction(g.locale) g.html_lang = i18n.locale_to_rfc_5646(g.locale) g.locales = i18n.get_locale2name() @app.before_request @ignore_static def check_tor2web(): # ignore_static here so we only flash a single message warning # about Tor2Web, corresponding to the initial page load. if 'X-tor2web' in request.headers: flash( Markup( gettext( '<strong>WARNING: </strong> ' 'You appear to be using Tor2Web. ' 'This <strong> does not </strong> ' 'provide anonymity. ' '<a href="{url}">Why is this dangerous?</a>').format( url=url_for('info.tor2web_warning'))), "banner-warning") @app.before_request @ignore_static def load_instance_config(): app.instance_config = InstanceConfig.get_current() @app.before_request @ignore_static def setup_g(): """Store commonly used values in Flask's special g object""" if 'expires' in session and datetime.utcnow() >= session['expires']: msg = render_template('session_timeout.html') # clear the session after we render the message so it's localized session.clear() # Redirect to index with flashed message flash(Markup(msg), "important") return redirect(url_for('main.index')) session['expires'] = datetime.utcnow() + \ timedelta(minutes=getattr(config, 'SESSION_EXPIRATION_MINUTES', 120)) # ignore_static here because `crypto_util.hash_codename` is scrypt # (very time consuming), and we don't need to waste time running if # we're just serving a static resource that won't need to access # these common values. if logged_in(): g.codename = session['codename'] g.filesystem_id = app.crypto_util.hash_codename(g.codename) try: g.source = Source.query \ .filter(Source.filesystem_id == g.filesystem_id) \ .one() except NoResultFound as e: app.logger.error("Found no Sources when one was expected: %s" % (e, )) del session['logged_in'] del session['codename'] return redirect(url_for('main.index')) g.loc = app.storage.path(g.filesystem_id) @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
def create_app(config: 'SDConfig') -> Flask: app = Flask(__name__, template_folder=config.JOURNALIST_TEMPLATES_DIR, static_folder=path.join(config.SECUREDROP_ROOT, 'static')) app.config.from_object(config.JOURNALIST_APP_FLASK_CONFIG_CLS) app.session_interface = JournalistInterfaceSessionInterface() csrf = CSRFProtect(app) Environment(app) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = config.DATABASE_URI db.init_app(app) v2_enabled = path.exists( path.join(config.SECUREDROP_DATA_ROOT, 'source_v2_url')) v3_enabled = path.exists( path.join(config.SECUREDROP_DATA_ROOT, 'source_v3_url')) app.config.update(V2_ONION_ENABLED=v2_enabled, V3_ONION_ENABLED=v3_enabled) # TODO: Attaching a Storage dynamically like this disables all type checking (and # breaks code analysis tools) for code that uses current_app.storage; it should be refactored app.storage = Storage(config.STORE_DIR, config.TEMP_DIR, config.JOURNALIST_KEY) # TODO: Attaching a CryptoUtil dynamically like this disables all type checking (and # breaks code analysis tools) for code that uses current_app.storage; it should be refactored app.crypto_util = CryptoUtil( scrypt_params=config.SCRYPT_PARAMS, scrypt_id_pepper=config.SCRYPT_ID_PEPPER, scrypt_gpg_pepper=config.SCRYPT_GPG_PEPPER, securedrop_root=config.SECUREDROP_ROOT, word_list=config.WORD_LIST, nouns_file=config.NOUNS, adjectives_file=config.ADJECTIVES, gpg_key_dir=config.GPG_KEY_DIR, ) @app.errorhandler(CSRFError) def handle_csrf_error(e: CSRFError) -> 'Response': # render the message first to ensure it's localized. msg = gettext('You have been logged out due to inactivity.') session.clear() flash(msg, 'error') return redirect(url_for('main.login')) def _handle_http_exception( error: 'HTTPException' ) -> 'Tuple[Union[Response, str], Optional[int]]': # Workaround for no blueprint-level 404/5 error handlers, see: # https://github.com/pallets/flask/issues/503#issuecomment-71383286 handler = list(app.error_handler_spec['api'][error.code].values())[0] if request.path.startswith('/api/') and handler: return handler(error) return render_template('error.html', error=error), error.code for code in default_exceptions: app.errorhandler(code)(_handle_http_exception) i18n.setup_app(config, app) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True app.jinja_env.globals['version'] = version.__version__ app.jinja_env.filters['rel_datetime_format'] = \ template_filters.rel_datetime_format app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat @app.before_first_request def expire_blacklisted_tokens() -> None: cleanup_expired_revoked_tokens() @app.before_request def load_instance_config() -> None: app.instance_config = InstanceConfig.get_current() @app.before_request def setup_g() -> 'Optional[Response]': """Store commonly used values in Flask's special g object""" if 'expires' in session and datetime.utcnow() >= session['expires']: session.clear() flash(gettext('You have been logged out due to inactivity.'), 'error') uid = session.get('uid', None) if uid: user = Journalist.query.get(uid) if user and 'nonce' in session and \ session['nonce'] != user.session_nonce: session.clear() flash( gettext('You have been logged out due to password change'), 'error') session['expires'] = datetime.utcnow() + \ timedelta(minutes=getattr(config, 'SESSION_EXPIRATION_MINUTES', 120)) # Work around https://github.com/lepture/flask-wtf/issues/275 # -- after upgrading from Python 2 to Python 3, any existing # session's csrf_token value will be retrieved as bytes, # causing a TypeError. This simple fix, deleting the existing # token, was suggested in the issue comments. This code will # be safe to remove after Python 2 reaches EOL in 2020, and no # supported SecureDrop installations can still have this # problem. if sys.version_info.major > 2 and type( session.get('csrf_token')) is bytes: del session['csrf_token'] uid = session.get('uid', None) if uid: g.user = Journalist.query.get(uid) g.locale = i18n.get_locale(config) g.text_direction = i18n.get_text_direction(g.locale) g.html_lang = i18n.locale_to_rfc_5646(g.locale) g.locales = i18n.get_locale2name() if not app.config['V3_ONION_ENABLED'] or app.config['V2_ONION_ENABLED']: g.show_v2_onion_eol_warning = True if request.path.split('/')[1] == 'api': pass # We use the @token_required decorator for the API endpoints else: # We are not using the API if request.endpoint not in _insecure_views and not logged_in(): return redirect(url_for('main.login')) if request.method == 'POST': filesystem_id = request.form.get('filesystem_id') if filesystem_id: g.filesystem_id = filesystem_id g.source = get_source(filesystem_id) return None app.register_blueprint(main.make_blueprint(config)) app.register_blueprint(account.make_blueprint(config), url_prefix='/account') app.register_blueprint(admin.make_blueprint(config), url_prefix='/admin') app.register_blueprint(col.make_blueprint(config), url_prefix='/col') api_blueprint = api.make_blueprint(config) app.register_blueprint(api_blueprint, url_prefix='/api/v1') csrf.exempt(api_blueprint) return app
def create_app(config): app = Flask(__name__, template_folder=config.JOURNALIST_TEMPLATES_DIR, static_folder=path.join(config.SECUREDROP_ROOT, 'static')) app.config.from_object(config.JournalistInterfaceFlaskConfig) CSRFProtect(app) Environment(app) @app.errorhandler(CSRFError) def handle_csrf_error(e): # render the message first to ensure it's localized. msg = gettext('You have been logged out due to inactivity') session.clear() flash(msg, 'error') return redirect(url_for('main.login')) i18n.setup_app(app) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True app.jinja_env.globals['version'] = version.__version__ if hasattr(config, 'CUSTOM_HEADER_IMAGE'): 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['rel_datetime_format'] = \ template_filters.rel_datetime_format app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat @app.teardown_appcontext def shutdown_session(exception=None): """Automatically remove database sessions at the end of the request, or when the application shuts down""" db_session.remove() @app.before_request def setup_g(): """Store commonly used values in Flask's special g object""" if 'expires' in session and datetime.utcnow() >= session['expires']: session.clear() flash(gettext('You have been logged out due to inactivity'), 'error') session['expires'] = datetime.utcnow() + \ timedelta(minutes=getattr(config, 'SESSION_EXPIRATION_MINUTES', 120)) uid = session.get('uid', None) if uid: g.user = Journalist.query.get(uid) g.locale = i18n.get_locale() g.text_direction = i18n.get_text_direction(g.locale) g.html_lang = i18n.locale_to_rfc_5646(g.locale) g.locales = i18n.get_locale2name() if request.endpoint not in _insecure_views and not logged_in(): return redirect(url_for('main.login')) if request.method == 'POST': filesystem_id = request.form.get('filesystem_id') if filesystem_id: g.filesystem_id = filesystem_id g.source = get_source(filesystem_id) app.register_blueprint(main.make_blueprint(config)) app.register_blueprint(account.make_blueprint(config), url_prefix='/account') app.register_blueprint(admin.make_blueprint(config), url_prefix='/admin') app.register_blueprint(col.make_blueprint(config), url_prefix='/col') return app
def create_app(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) # 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.errorhandler(CSRFError) def handle_csrf_error(e): msg = render_template('session_timeout.html') session.clear() flash(Markup(msg), "important") return redirect(url_for('main.index')) assets = Environment(app) app.config['assets'] = assets i18n.setup_app(config, app) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True 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['rel_datetime_format'] = \ template_filters.rel_datetime_format app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br) app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat for module in [main, info, api]: app.register_blueprint(module.make_blueprint(config)) @app.before_request @ignore_static def check_tor2web(): # ignore_static here so we only flash a single message warning # about Tor2Web, corresponding to the initial page load. if 'X-tor2web' in request.headers: flash( Markup( gettext( '<strong>WARNING: </strong> ' 'You appear to be using Tor2Web. ' 'This <strong> does not </strong> ' 'provide anonymity. ' '<a href="{url}">Why is this dangerous?</a>').format( url=url_for('info.tor2web_warning'))), "banner-warning") @app.before_request @ignore_static def setup_g(): """Store commonly used values in Flask's special g object""" g.locale = i18n.get_locale(config) g.text_direction = i18n.get_text_direction(g.locale) g.html_lang = i18n.locale_to_rfc_5646(g.locale) g.locales = i18n.get_locale2name() if 'expires' in session and datetime.utcnow() >= session['expires']: msg = render_template('session_timeout.html') # clear the session after we render the message so it's localized session.clear() flash(Markup(msg), "important") session['expires'] = datetime.utcnow() + \ timedelta(minutes=getattr(config, 'SESSION_EXPIRATION_MINUTES', 120)) # ignore_static here because `crypto_util.hash_codename` is scrypt # (very time consuming), and we don't need to waste time running if # we're just serving a static resource that won't need to access # these common values. if logged_in(): g.codename = session['codename'] g.filesystem_id = crypto_util.hash_codename(g.codename) try: g.source = Source.query \ .filter(Source.filesystem_id == g.filesystem_id) \ .one() except NoResultFound as e: app.logger.error("Found no Sources when one was expected: %s" % (e, )) del session['logged_in'] del session['codename'] return redirect(url_for('main.index')) g.loc = store.path(g.filesystem_id) @app.teardown_appcontext def shutdown_session(exception=None): """Automatically remove database sessions at the end of the request, or when the application shuts down""" db_session.remove() @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
def create_app(config: SDConfig) -> Flask: 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.SOURCE_APP_FLASK_CONFIG_CLS) # 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.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = config.DATABASE_URI db.init_app(app) # TODO: Attaching a Storage dynamically like this disables all type checking (and # breaks code analysis tools) for code that uses current_app.storage; it should be refactored app.storage = Storage(config.STORE_DIR, config.TEMP_DIR, config.JOURNALIST_KEY) # TODO: Attaching a CryptoUtil dynamically like this disables all type checking (and # breaks code analysis tools) for code that uses current_app.storage; it should be refactored app.crypto_util = CryptoUtil( scrypt_params=config.SCRYPT_PARAMS, scrypt_id_pepper=config.SCRYPT_ID_PEPPER, scrypt_gpg_pepper=config.SCRYPT_GPG_PEPPER, securedrop_root=config.SECUREDROP_ROOT, word_list=config.WORD_LIST, nouns_file=config.NOUNS, adjectives_file=config.ADJECTIVES, gpg_key_dir=config.GPG_KEY_DIR, ) @app.errorhandler(CSRFError) def handle_csrf_error(e: CSRFError) -> werkzeug.Response: msg = render_template('session_timeout.html') session.clear() flash(Markup(msg), "important") return redirect(url_for('main.index')) assets = Environment(app) app.config['assets'] = assets i18n.setup_app(config, app) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True app.jinja_env.globals['version'] = version.__version__ # Exported to source templates for being included in instructions app.jinja_env.globals['submission_key_fpr'] = config.JOURNALIST_KEY app.jinja_env.filters['rel_datetime_format'] = \ template_filters.rel_datetime_format app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br) app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat for module in [main, info, api]: app.register_blueprint(module.make_blueprint(config)) # type: ignore @app.before_request @ignore_static def setup_i18n() -> None: """Store i18n-related values in Flask's special g object""" g.locale = i18n.get_locale(config) g.text_direction = i18n.get_text_direction(g.locale) g.html_lang = i18n.locale_to_rfc_5646(g.locale) g.locales = i18n.get_locale2name() @app.before_request @ignore_static def check_tor2web() -> None: # ignore_static here so we only flash a single message warning # about Tor2Web, corresponding to the initial page load. if 'X-tor2web' in request.headers: flash(Markup(gettext( '<strong>WARNING: </strong> ' 'You appear to be using Tor2Web. ' 'This <strong> does not </strong> ' 'provide anonymity. ' '<a href="{url}">Why is this dangerous?</a>') .format(url=url_for('info.tor2web_warning'))), "banner-warning") @app.before_request @ignore_static def load_instance_config() -> None: app.instance_config = InstanceConfig.get_current() @app.before_request @ignore_static def setup_g() -> Optional[werkzeug.Response]: """Store commonly used values in Flask's special g object""" if 'expires' in session and datetime.utcnow() >= session['expires']: msg = render_template('session_timeout.html') # Show expiration message only if the user was # either in the codename generation flow or logged in show_expiration_message = any([ session.get('show_expiration_message'), logged_in(), was_in_generate_flow(), ]) # clear the session after we render the message so it's localized session.clear() # Persist this properety across sessions to distinguish users whose sessions expired # from users who never logged in or generated a codename session['show_expiration_message'] = show_expiration_message # Redirect to index with flashed message if session['show_expiration_message']: flash(Markup(msg), "important") return redirect(url_for('main.index')) session['expires'] = datetime.utcnow() + \ timedelta(minutes=getattr(config, 'SESSION_EXPIRATION_MINUTES', 120)) # ignore_static here because `crypto_util.hash_codename` is scrypt # (very time consuming), and we don't need to waste time running if # we're just serving a static resource that won't need to access # these common values. if logged_in(): g.codename = session['codename'] g.filesystem_id = app.crypto_util.hash_codename(g.codename) try: g.source = Source.query \ .filter(Source.filesystem_id == g.filesystem_id) \ .filter_by(deleted_at=None) \ .one() except NoResultFound as e: app.logger.error( "Found no Sources when one was expected: %s" % (e,)) del session['logged_in'] del session['codename'] return redirect(url_for('main.index')) g.loc = app.storage.path(g.filesystem_id) return None @app.errorhandler(404) def page_not_found(error: werkzeug.exceptions.HTTPException) -> Tuple[str, int]: return render_template('notfound.html'), 404 @app.errorhandler(500) def internal_error(error: werkzeug.exceptions.HTTPException) -> Tuple[str, int]: return render_template('error.html'), 500 return app
def create_app(config): app = Flask(__name__, template_folder=config.JOURNALIST_TEMPLATES_DIR, static_folder=path.join(config.SECUREDROP_ROOT, 'static')) app.config.from_object(config.JournalistInterfaceFlaskConfig) CSRFProtect(app) Environment(app) @app.errorhandler(CSRFError) def handle_csrf_error(e): # render the message first to ensure it's localized. msg = gettext('You have been logged out due to inactivity') session.clear() flash(msg, 'error') return redirect(url_for('main.login')) i18n.setup_app(config, app) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True app.jinja_env.globals['version'] = version.__version__ if hasattr(config, 'CUSTOM_HEADER_IMAGE'): 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['rel_datetime_format'] = \ template_filters.rel_datetime_format app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat @app.template_filter('autoversion') def autoversion_filter(filename): """Use this template filter for cache busting""" absolute_filename = path.join(config.SECUREDROP_ROOT, filename[1:]) if path.exists(absolute_filename): timestamp = str(path.getmtime(absolute_filename)) else: return filename versioned_filename = "{0}?v={1}".format(filename, timestamp) return versioned_filename @app.teardown_appcontext def shutdown_session(exception=None): """Automatically remove database sessions at the end of the request, or when the application shuts down""" db_session.remove() @app.before_request def setup_g(): """Store commonly used values in Flask's special g object""" if 'expires' in session and datetime.utcnow() >= session['expires']: session.clear() flash(gettext('You have been logged out due to inactivity'), 'error') session['expires'] = datetime.utcnow() + \ timedelta(minutes=getattr(config, 'SESSION_EXPIRATION_MINUTES', 120)) uid = session.get('uid', None) if uid: g.user = Journalist.query.get(uid) g.locale = i18n.get_locale(config) g.text_direction = i18n.get_text_direction(g.locale) g.html_lang = i18n.locale_to_rfc_5646(g.locale) g.locales = i18n.get_locale2name() if request.endpoint not in _insecure_views and not logged_in(): return redirect(url_for('main.login')) if request.method == 'POST': filesystem_id = request.form.get('filesystem_id') if filesystem_id: g.filesystem_id = filesystem_id g.source = get_source(filesystem_id) app.register_blueprint(main.make_blueprint(config)) app.register_blueprint(account.make_blueprint(config), url_prefix='/account') app.register_blueprint(admin.make_blueprint(config), url_prefix='/admin') app.register_blueprint(col.make_blueprint(config), url_prefix='/col') return app
def create_app(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) # 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.errorhandler(CSRFError) def handle_csrf_error(e): msg = render_template('session_timeout.html') session.clear() flash(Markup(msg), "important") return redirect(url_for('main.index')) assets = Environment(app) app.config['assets'] = assets i18n.setup_app(config, app) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True 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['rel_datetime_format'] = \ template_filters.rel_datetime_format app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br) app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat for module in [main, info, api]: app.register_blueprint(module.make_blueprint(config)) @app.before_request @ignore_static def check_tor2web(): # ignore_static here so we only flash a single message warning # about Tor2Web, corresponding to the initial page load. if 'X-tor2web' in request.headers: flash(Markup(gettext( '<strong>WARNING: </strong> ' 'You appear to be using Tor2Web. ' 'This <strong> does not </strong> ' 'provide anonymity. ' '<a href="{url}">Why is this dangerous?</a>') .format(url=url_for('info.tor2web_warning'))), "banner-warning") @app.before_request @ignore_static def setup_g(): """Store commonly used values in Flask's special g object""" g.locale = i18n.get_locale(config) g.text_direction = i18n.get_text_direction(g.locale) g.html_lang = i18n.locale_to_rfc_5646(g.locale) g.locales = i18n.get_locale2name() if 'expires' in session and datetime.utcnow() >= session['expires']: msg = render_template('session_timeout.html') # clear the session after we render the message so it's localized session.clear() flash(Markup(msg), "important") session['expires'] = datetime.utcnow() + \ timedelta(minutes=getattr(config, 'SESSION_EXPIRATION_MINUTES', 120)) # ignore_static here because `crypto_util.hash_codename` is scrypt # (very time consuming), and we don't need to waste time running if # we're just serving a static resource that won't need to access # these common values. if logged_in(): g.codename = session['codename'] g.filesystem_id = crypto_util.hash_codename(g.codename) try: g.source = Source.query \ .filter(Source.filesystem_id == g.filesystem_id) \ .one() except NoResultFound as e: app.logger.error( "Found no Sources when one was expected: %s" % (e,)) del session['logged_in'] del session['codename'] return redirect(url_for('main.index')) g.loc = store.path(g.filesystem_id) @app.teardown_appcontext def shutdown_session(exception=None): """Automatically remove database sessions at the end of the request, or when the application shuts down""" db_session.remove() @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