diff --git a/securedrop/journalist_app/__init__.py b/securedrop/journalist_app/__init__.py index ba83b1d04b..ddd1ff01d2 100644 --- a/securedrop/journalist_app/__init__.py +++ b/securedrop/journalist_app/__init__.py @@ -65,6 +65,10 @@ def create_app(config: 'SDConfig') -> Flask: app.config['SQLALCHEMY_DATABASE_URI'] = db_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) + app.storage = Storage(config.STORE_DIR, config.TEMP_DIR, config.JOURNALIST_KEY) @@ -169,6 +173,9 @@ def setup_g() -> 'Optional[Response]': 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 diff --git a/securedrop/journalist_templates/base.html b/securedrop/journalist_templates/base.html index cb020c7913..bcaa57b60e 100644 --- a/securedrop/journalist_templates/base.html +++ b/securedrop/journalist_templates/base.html @@ -19,6 +19,12 @@ {% if g.user %} + {% if g.show_v2_onion_eol_warning %} +
+ {{ gettext('Update Required: Your SecureDrop servers are still running v2 Onion Services, which are being phased out for security reasons. In February 2021, v2 Onion Services will be disabled, and your SecureDrop servers may become unreachable. Learn More') }} +
+ {% endif %} +
{{ gettext('Logged on as') }} {{ g.user.username }} | {% if g.user and g.user.is_admin %} diff --git a/securedrop/sass/_base.sass b/securedrop/sass/_base.sass index db2de2d4db..7a7423c939 100644 --- a/securedrop/sass/_base.sass +++ b/securedrop/sass/_base.sass @@ -38,6 +38,8 @@ @import modules/panel // Warning - Messages to user e.g. about protecting your security by turning Tor settings to high @import modules/warning +// Warning - Warning messages that show up in a banner to the user. +@import modules/warning-banner // Confirm prompt - When deleting something this prompt is shown @import modules/confirm-prompt // 'Serious' text - Seems to be unused. Delete? @@ -119,6 +121,7 @@ +cols +panel +warning + +warning-banner +confirm-prompt +serious-text +code diff --git a/securedrop/sass/modules/_warning-banner.sass b/securedrop/sass/modules/_warning-banner.sass new file mode 100644 index 0000000000..2087ee35a5 --- /dev/null +++ b/securedrop/sass/modules/_warning-banner.sass @@ -0,0 +1,17 @@ +=warning-banner + .warning-banner + display: block + background-color: $color_warning_purple + color: white + padding: 10px 0 + width: 100% + text-align: center + font-size: small + box-sizing: border-box + -moz-box-sizing: border-box + + a + color: white + + .close + cursor: pointer diff --git a/securedrop/tests/test_journalist.py b/securedrop/tests/test_journalist.py index 9b3032fac6..a7230da205 100644 --- a/securedrop/tests/test_journalist.py +++ b/securedrop/tests/test_journalist.py @@ -52,6 +52,66 @@ def _login_user(app, username, password, otp_secret): assert hasattr(g, 'user') # ensure logged in +def test_user_sees_v2_eol_warning_if_only_v2_is_enabled(config, journalist_app, test_journo): + journalist_app.config.update(V2_ONION_ENABLED=True, V3_ONION_ENABLED=False) + with journalist_app.test_client() as app: + _login_user( + app, + test_journo['username'], + test_journo['password'], + test_journo['otp_secret']) + + resp = app.get(url_for('main.index')) + + text = resp.data.decode('utf-8') + assert "v2-onion-eol" in text, text + + +def test_user_sees_v2_eol_warning_if_both_v2_and_v3_enabled(config, journalist_app, test_journo): + journalist_app.config.update(V2_ONION_ENABLED=True, V3_ONION_ENABLED=True) + with journalist_app.test_client() as app: + _login_user( + app, + test_journo['username'], + test_journo['password'], + test_journo['otp_secret']) + + resp = app.get(url_for('main.index')) + + text = resp.data.decode('utf-8') + assert "v2-onion-eol" in text, text + + +def test_user_does_not_see_v2_eol_warning_if_only_v3_enabled(config, journalist_app, test_journo): + journalist_app.config.update(V2_ONION_ENABLED=False, V3_ONION_ENABLED=True) + with journalist_app.test_client() as app: + _login_user( + app, + test_journo['username'], + test_journo['password'], + test_journo['otp_secret']) + + resp = app.get(url_for('main.index')) + + text = resp.data.decode('utf-8') + assert "v2-onion-eol" not in text, text + + +def test_user_sees_v2_eol_warning_if_both_urls_do_not_exist(config, journalist_app, test_journo): + journalist_app.config.update(V2_ONION_ENABLED=False, V3_ONION_ENABLED=False) + with journalist_app.test_client() as app: + _login_user( + app, + test_journo['username'], + test_journo['password'], + test_journo['otp_secret']) + + resp = app.get(url_for('main.index')) + + text = resp.data.decode('utf-8') + assert "v2-onion-eol" in text, text + + def test_user_with_whitespace_in_username_can_login(journalist_app): # Create a user with whitespace at the end of the username with journalist_app.app_context():