Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable Source Interface for Trusty when Trusty is end-of-life #4325

Merged
merged 3 commits into from
Apr 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@
/var/www/securedrop/source_app/api.pyc rw,
/var/www/securedrop/source_app/decorators.py r,
/var/www/securedrop/source_app/decorators.pyc rw,
/var/www/securedrop/source_app/disable.py r,
/var/www/securedrop/source_app/disable.pyc rw,
/var/www/securedrop/source_app/forms.py r,
/var/www/securedrop/source_app/forms.pyc rw,
/var/www/securedrop/source_app/info.py r,
Expand All @@ -211,6 +213,7 @@
/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,
/var/www/securedrop/source_templates/disabled.html r,
/var/www/securedrop/source_templates/first_submission_flashed_message.html r,
/var/www/securedrop/source_templates/flashed.html r,
/var/www/securedrop/source_templates/generate.html r,
Expand Down
18 changes: 13 additions & 5 deletions securedrop/source_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from db import db
from models import Source
from request_that_secures_file_uploads import RequestThatSecuresFileUploads
from source_app import main, info, api
from source_app import main, info, api, disable
from source_app.decorators import ignore_static
from source_app.utils import logged_in
from store import Storage
Expand Down Expand Up @@ -106,6 +106,18 @@ def handle_csrf_error(e):
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()

# Disables the app if the server is running Trusty past its EOL date.
disable.disable_app(app)

@app.before_request
@ignore_static
def check_tor2web():
Expand All @@ -125,10 +137,6 @@ def check_tor2web():
@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')
Expand Down
2 changes: 1 addition & 1 deletion securedrop/source_app/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def ignore_static(f):
a static resource."""
@wraps(f)
def decorated_function(*args, **kwargs):
if request.path.startswith('/static'):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would moving the org-logo route behind /static make more sense here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could make code changes to create a Blueprint whose /static route is set to be behind /static/org and whose content is exactly the single file that is uploaded. Doing this cleanly is actually just a little tricky, and our current code does not easily support this at the moment.

if request.path.startswith('/static') or request.path == '/org-logo':
return # don't execute the decorated function
return f(*args, **kwargs)
return decorated_function
17 changes: 17 additions & 0 deletions securedrop/source_app/disable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from datetime import date
from flask import render_template
from source_app.decorators import ignore_static
import platform

XENIAL_VER = "16.04"
TRUSTY_DISABLE_DATE = date(2019, 4, 30)


def disable_app(app):

@app.before_request
@ignore_static
def disable_ui():
if(platform.linux_distribution()[1] != XENIAL_VER and
date.today() > TRUSTY_DISABLE_DATE):
return render_template('disabled.html')
7 changes: 7 additions & 0 deletions securedrop/source_templates/disabled.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block body %}
<h1>{{ gettext("We're sorry, our SecureDrop is currently offline.") }}</h1>

<p>{{ gettext("Please try again later. Check our website for more information.") }}</p>

{% endblock %}
58 changes: 58 additions & 0 deletions securedrop/tests/test_source.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import gzip
import json
import platform
import re
import subprocess

Expand All @@ -13,14 +14,21 @@
import utils
import version

from datetime import date
from db import db
from models import Source, Reply
from source_app import main as source_app_main
from source_app import api as source_app_api
from source_app import disable as source_app_disable
from utils.db_helper import new_codename
from utils.instrument import InstrumentedApp

overly_long_codename = 'a' * (Source.MAX_CODENAME_LEN + 1)
TRUSTY_DISABLED_ENDPOINTS = ['main.index', 'main.lookup', 'main.generate', 'main.login',
'info.download_journalist_pubkey', 'info.tor2web_warning',
'info.recommend_tor_browser', 'info.why_download_journalist_pubkey']
STATIC_ASSETS = ['css/source.css', 'i/custom_logo.png', 'i/font-awesome/fa-globe-black.png',
'i/favicon.png']


def test_page_not_found(source_app):
Expand Down Expand Up @@ -696,3 +704,53 @@ def test_source_can_only_delete_own_replies(source_app):

reply = Reply.query.filter_by(filename=filename).one()
assert reply.deleted_by_source


def test_source_disabled_when_trusty_is_eol(config, source_app):
with patch.object(source_app_api.platform, "linux_distribution") as mocked_platform:
mocked_platform.return_value = ("Ubuntu", "14.04", "Trusty")

with source_app.test_client() as app:
source_app_disable.TRUSTY_DISABLE_DATE = date(2001, 1, 1)

assert platform.linux_distribution()[1] == "14.04"
for endpoint in TRUSTY_DISABLED_ENDPOINTS:
resp = app.get(url_for(endpoint))
assert resp.status_code == 200
text = resp.data.decode('utf-8')
assert "We're sorry, our SecureDrop is currently offline." in text
# Ensure static assets are properly served
for asset in STATIC_ASSETS:
resp = app.get(url_for('static', filename=asset))
assert resp.status_code == 200
text = resp.data.decode('utf-8')
assert "We're sorry, our SecureDrop is currently offline." not in text


def test_source_not_disabled_before_trusty_eol(config, source_app):
with patch.object(source_app_api.platform, "linux_distribution") as mocked_platform:
mocked_platform.return_value = ("Ubuntu", "14.04", "Trusty")

with source_app.test_client() as app:
source_app_disable.TRUSTY_DISABLE_DATE = date(2097, 1, 1)
assert platform.linux_distribution()[1] == "14.04"
for endpoint in TRUSTY_DISABLED_ENDPOINTS:
resp = app.get(url_for(endpoint), follow_redirects=True)
assert resp.status_code == 200
text = resp.data.decode('utf-8')
assert "We're sorry, our SecureDrop is currently offline." not in text


def test_source_not_disabled_xenial(config, source_app):
with patch.object(source_app_api.platform, "linux_distribution") as mocked_platform:
mocked_platform.return_value = ("Ubuntu", "16.04", "Xenial")

with source_app.test_client() as app:
source_app_disable.TRUSTY_DISABLE_DATE = date(2001, 1, 1)

assert platform.linux_distribution()[1] == "16.04"
for endpoint in TRUSTY_DISABLED_ENDPOINTS:
resp = app.get(url_for(endpoint), follow_redirects=True)
assert resp.status_code == 200
text = resp.data.decode('utf-8')
assert "We're sorry, our SecureDrop is currently offline." not in text