From 0ed49544bfa356470a0dfb418c43b04351b1f77e Mon Sep 17 00:00:00 2001 From: Kevin O'Gorman Date: Sun, 21 Jul 2019 12:53:02 -0700 Subject: [PATCH 1/2] Refactored send-to-usb script as a python module --- MANIFEST.in | 12 ++ build-requirements.txt | 0 changelog.md | 6 + files/application-x-sd-export.xml | 7 + files/sd-logo.png | Bin 0 -> 8606 bytes files/send-to-usb.desktop | 5 + requirements.txt | 0 securedrop_export/VERSION | 1 + securedrop_export/__init__.py | 0 securedrop_export/entrypoint.py | 23 +++ securedrop_export/export.py | 327 ++++++++++++++++++++++++++++++ securedrop_export/main.py | 37 ++++ setup.py | 35 ++++ test-requirements.txt | 1 + tests/__init__.py | 0 tests/test_export.py | 171 ++++++++++++++++ 16 files changed, 625 insertions(+) create mode 100644 MANIFEST.in create mode 100644 build-requirements.txt create mode 100644 changelog.md create mode 100644 files/application-x-sd-export.xml create mode 100644 files/sd-logo.png create mode 100644 files/send-to-usb.desktop create mode 100644 requirements.txt create mode 100644 securedrop_export/VERSION create mode 100644 securedrop_export/__init__.py create mode 100755 securedrop_export/entrypoint.py create mode 100755 securedrop_export/export.py create mode 100755 securedrop_export/main.py create mode 100644 setup.py create mode 100644 test-requirements.txt create mode 100644 tests/__init__.py create mode 100644 tests/test_export.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..8104f05fc --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,12 @@ +include LICENSE +include README.md +include securedrop_export/VERSION +include changelog.md +include build-requirements.txt +include requirements.txt +include securedrop_export/*.py +include setup.py +include files/send-to-usb.desktop +include files/application-x-sd-export.xml +include files/sd-logo.png + diff --git a/build-requirements.txt b/build-requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.md b/changelog.md new file mode 100644 index 000000000..02c74ef5a --- /dev/null +++ b/changelog.md @@ -0,0 +1,6 @@ +securedrop-export (0.1.1-1) unstable; urgency=medium + + [ Freedom Of The Press Foundation ] + * Initial release + + -- SecureDrop Team Thu, 18 Jul 2019 10:47:38 -0700 diff --git a/files/application-x-sd-export.xml b/files/application-x-sd-export.xml new file mode 100644 index 000000000..9e36ef08b --- /dev/null +++ b/files/application-x-sd-export.xml @@ -0,0 +1,7 @@ + + + + Archive for transfering files from the SecureDrop workstation to an external USB device. + + + diff --git a/files/sd-logo.png b/files/sd-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..531cbf26c3426493616b7060338cc68d714bad1d GIT binary patch literal 8606 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\n".format(test_msg) + assert captured.out == "" + + +def test_empty_config(capsys): + submission = export.SDExport("testfile") + temp_folder = tempfile.mkdtemp() + metadata = os.path.join(temp_folder, export.Metadata.METADATA_FILE) + with open(metadata, "w") as f: + f.write("{}") + config = export.Metadata(temp_folder) + assert not config.is_valid() + + +def test_valid_printer_test_config(capsys): + submission = export.SDExport("testfile") + temp_folder = tempfile.mkdtemp() + metadata = os.path.join(temp_folder, export.Metadata.METADATA_FILE) + with open(metadata, "w") as f: + f.write('{"device": "printer-test"}') + config = export.Metadata(temp_folder) + assert config.is_valid() + assert config.encryption_key is None + assert config.encryption_method is None + + +def test_valid_printer_config(capsys): + submission = export.SDExport("") + temp_folder = tempfile.mkdtemp() + metadata = os.path.join(temp_folder, export.Metadata.METADATA_FILE) + with open(metadata, "w") as f: + f.write('{"device": "printer"}') + config = export.Metadata(temp_folder) + assert config.is_valid() + assert config.encryption_key is None + assert config.encryption_method is None + + +def test_invalid_encryption_config(capsys): + submission = export.SDExport("testfile") + + temp_folder = tempfile.mkdtemp() + metadata = os.path.join(temp_folder, export.Metadata.METADATA_FILE) + with open(metadata, "w") as f: + f.write( + '{"device": "disk", "encryption_method": "base64", "encryption_key": "hunter1"}' + ) + config = export.Metadata(temp_folder) + assert config.encryption_key == "hunter1" + assert config.encryption_method == "base64" + assert not config.is_valid() + + +def test_valid_encryption_config(capsys): + submission = export.SDExport("testfile") + temp_folder = tempfile.mkdtemp() + metadata = os.path.join(temp_folder, export.Metadata.METADATA_FILE) + with open(metadata, "w") as f: + f.write( + '{"device": "disk", "encryption_method": "luks", "encryption_key": "hunter1"}' + ) + config = export.Metadata(temp_folder) + assert config.encryption_key == "hunter1" + assert config.encryption_method == "luks" + assert config.is_valid() + + +@mock.patch("subprocess.check_call") +def test_popup_message(mocked_call): + submission = export.SDExport("testfile") + submission.popup_message("hello!") + mocked_call.assert_called_once_with([ + "notify-send", + "--expire-time", "3000", + "--icon", "/usr/share/securedrop/icons/sd-logo.png", + "SecureDrop: hello!" + ]) + + +@mock.patch("subprocess.check_output", return_value=SAMPLE_OUTPUT_BOTHER_PRINTER) +def test_get_good_printer_uri(mocked_call): + submission = export.SDExport("testfile") + result = submission.get_printer_uri() + assert result == "usb://Brother/HL-L2320D%20series?serial=A00000A000000" + + +@mock.patch("subprocess.check_output", return_value=SAMPLE_OUTPUT_NO_PRINTER) +def test_get_bad_printer_uri(mocked_call, capsys): + submission = export.SDExport("testfile") + expected_message = "USB Printer not found" + mocked_exit = mock.patch("export.exit_gracefully", return_value=0) + + with pytest.raises(SystemExit) as sysexit: + result = submission.get_printer_uri() + assert result == "" + mocked_exit.assert_called_once_with(expected_message) + + assert sysexit.value.code == 0 + captured = capsys.readouterr() + assert captured.err == "{}\n".format(expected_message) + assert captured.out == "" + + +@pytest.mark.parametrize('open_office_paths', [ + "/tmp/whatver/thisisadoc.doc" + "/home/user/Downloads/thisisadoc.xlsx" + "/home/user/Downloads/file.odt" + "/tmp/tmpJf83j9/secret.pptx" +]) +def test_is_open_office_file(capsys, open_office_paths): + submission = export.SDExport("") + assert submission.is_open_office_file(open_office_paths) + + +@pytest.mark.parametrize('open_office_paths', [ + "/tmp/whatver/thisisadoc.doccc" + "/home/user/Downloads/thisisa.xlsx.zip" + "/home/user/Downloads/file.odz" + "/tmp/tmpJf83j9/secret.gpg" +]) +def test_is_not_open_office_file(capsys, open_office_paths): + submission = export.SDExport("") + assert not submission.is_open_office_file(open_office_paths) From 8806ea1968a5f2d75f448f3652089c2d3bdc81ba Mon Sep 17 00:00:00 2001 From: Kevin O'Gorman Date: Tue, 23 Jul 2019 14:52:35 -0700 Subject: [PATCH 2/2] fixed disk export location --- securedrop_export/export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/securedrop_export/export.py b/securedrop_export/export.py index c40ee2f1d..f5ce56a95 100755 --- a/securedrop_export/export.py +++ b/securedrop_export/export.py @@ -168,7 +168,7 @@ def copy_submission(self): target_path = os.path.join(self.mountpoint, self.target_dirname) subprocess.check_call(["mkdir", target_path]) export_data = os.path.join( - self.tmpdir, self.submission_dirname, "export_data/" + self.tmpdir, "export_data/" ) subprocess.check_call(["cp", "-r", export_data, target_path]) self.popup_message("Files exported successfully to disk.")