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 f5f34c9d6f..cac98dc0df 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 @@ -135,6 +135,8 @@ /var/lib/securedrop/shredder/*/ w, /var/lib/securedrop/store/** rw, /var/lib/securedrop/store/*/ w, + /var/lib/securedrop/source_v2_url r, + /var/lib/securedrop/source_v3_url r, /var/lib/securedrop/tmp/** rw, /var/lib/ssl/* r, /var/log/apache2/* w, diff --git a/securedrop/source_app/api.py b/securedrop/source_app/api.py index e6171211c7..07f600fb97 100644 --- a/securedrop/source_app/api.py +++ b/securedrop/source_app/api.py @@ -3,6 +3,8 @@ from flask import Blueprint, current_app, make_response +from source_app.utils import get_sourcev2_url, get_sourcev3_url + import version @@ -16,7 +18,9 @@ def metadata(): 'gpg_fpr': config.JOURNALIST_KEY, 'sd_version': version.__version__, 'server_os': platform.linux_distribution()[1], - 'supported_languages': config.SUPPORTED_LOCALES + 'supported_languages': config.SUPPORTED_LOCALES, + 'v2_source_url': get_sourcev2_url(), + 'v3_source_url': get_sourcev3_url() } resp = make_response(json.dumps(meta)) resp.headers['Content-Type'] = 'application/json' diff --git a/securedrop/source_app/utils.py b/securedrop/source_app/utils.py index 39acd00b04..0a5475567f 100644 --- a/securedrop/source_app/utils.py +++ b/securedrop/source_app/utils.py @@ -9,6 +9,7 @@ from threading import Thread import i18n +import re from crypto_util import CryptoException from models import Source @@ -112,3 +113,31 @@ def normalize_timestamps(filesystem_id): "Couldn't normalize submission " "timestamps (touch exited with %d)" % rc) + + +def check_url_file(path, regexp): + """ + Check that a file exists at the path given and contains a single line + matching the regexp. Used for checking the source interface address + files at /var/lib/securedrop/source_{v2,v3}_url. + """ + try: + f = open(path, "r") + contents = f.readline().strip() + f.close() + if re.match(regexp, contents): + return contents + else: + return None + except IOError: + return None + + +def get_sourcev2_url(): + return check_url_file("/var/lib/securedrop/source_v2_url", + r"^[a-z0-9]{16}\.onion$") + + +def get_sourcev3_url(): + return check_url_file("/var/lib/securedrop/source_v3_url", + r"^[a-z0-9]{56}\.onion$") diff --git a/securedrop/tests/test_source.py b/securedrop/tests/test_source.py index 7cf06ae7f4..767c1245c7 100644 --- a/securedrop/tests/test_source.py +++ b/securedrop/tests/test_source.py @@ -558,6 +558,32 @@ def test_metadata_route(config, source_app): assert resp.json.get('server_os') == '16.04' assert resp.json.get('supported_languages') ==\ config.SUPPORTED_LOCALES + assert resp.json.get('v2_source_url') is None + assert resp.json.get('v3_source_url') is None + + +def test_metadata_v2_url(config, source_app): + onion_test_url = "abcdabcdabcdabcd.onion" + with patch.object(source_app_api, "get_sourcev2_url") as mocked_v2_url: + mocked_v2_url.return_value = (onion_test_url) + with source_app.test_client() as app: + resp = app.get(url_for('api.metadata')) + assert resp.status_code == 200 + assert resp.headers.get('Content-Type') == 'application/json' + assert resp.json.get('v2_source_url') == onion_test_url + assert resp.json.get('v3_source_url') is None + + +def test_metadata_v3_url(config, source_app): + onion_test_url = "abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh.onion" + with patch.object(source_app_api, "get_sourcev3_url") as mocked_v3_url: + mocked_v3_url.return_value = (onion_test_url) + with source_app.test_client() as app: + resp = app.get(url_for('api.metadata')) + assert resp.status_code == 200 + assert resp.headers.get('Content-Type') == 'application/json' + assert resp.json.get('v2_source_url') is None + assert resp.json.get('v3_source_url') == onion_test_url def test_login_with_overly_long_codename(source_app): diff --git a/securedrop/tests/test_source_utils.py b/securedrop/tests/test_source_utils.py new file mode 100644 index 0000000000..1a5b998b00 --- /dev/null +++ b/securedrop/tests/test_source_utils.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +import os + +from source_app.utils import check_url_file + + +def test_check_url_file(config): + + assert check_url_file("nosuchfile", "whatever") is None + + try: + def write_url_file(path, content): + url_file = open(path, "w") + url_file.write("{}\n".format(content)) + + url_path = "test_source_url" + + onion_test_url = "abcdabcdabcdabcd.onion" + write_url_file(url_path, onion_test_url) + assert check_url_file(url_path, r"^[a-z0-9]{16}\.onion$") == onion_test_url + + onion_test_url = "abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh.onion" + write_url_file(url_path, onion_test_url) + assert check_url_file(url_path, r"^[a-z0-9]{56}\.onion$") == onion_test_url + + write_url_file(url_path, "NO.onion") + assert check_url_file(url_path, r"^[a-z0-9]{56}\.onion$") is None + finally: + if os.path.exists(url_path): + os.unlink(url_path)