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

Adds v2 and v3 source interface urls to metadata endpoint. #5074

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion securedrop/source_app/api.py
Expand Up @@ -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


Expand All @@ -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'
Expand Down
29 changes: 29 additions & 0 deletions securedrop/source_app/utils.py
Expand Up @@ -9,6 +9,7 @@
from threading import Thread

import i18n
import re

from crypto_util import CryptoException
from models import Source
Expand Down Expand Up @@ -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):
zenmonkeykstop marked this conversation as resolved.
Show resolved Hide resolved
"""
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$")
26 changes: 26 additions & 0 deletions securedrop/tests/test_source.py
Expand Up @@ -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


zenmonkeykstop marked this conversation as resolved.
Show resolved Hide resolved
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):
Expand Down
30 changes: 30 additions & 0 deletions 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)