summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiguel Jacq <mig@mig5.net>2019-09-16 12:10:17 +1000
committerMiguel Jacq <mig@mig5.net>2019-09-16 12:10:17 +1000
commit2524ddaf9485e484c87f2cea51414fc0d362187b (patch)
treed120ce70a6d987bc4bfcf137bd004ed58f34b40b
parent957d3e9c6d424fdfc394bef529b87f52e16f371f (diff)
downloadonionshare-2524ddaf9485e484c87f2cea51414fc0d362187b.tar.gz
onionshare-2524ddaf9485e484c87f2cea51414fc0d362187b.zip
Make setting the Content-Security-Policy header optional so it doesn't break website mode shares
-rw-r--r--onionshare/settings.py1
-rw-r--r--onionshare/web/web.py24
-rw-r--r--onionshare_gui/settings_dialog.py25
-rw-r--r--share/locale/en.json1
-rw-r--r--tests/GuiWebsiteTest.py15
-rw-r--r--tests/local_onionshare_website_mode_csp_enabled_test.py26
-rw-r--r--tests/local_onionshare_website_mode_test.py1
-rw-r--r--tests/test_onionshare_settings.py3
8 files changed, 86 insertions, 10 deletions
diff --git a/onionshare/settings.py b/onionshare/settings.py
index 762c6dc2..28523b89 100644
--- a/onionshare/settings.py
+++ b/onionshare/settings.py
@@ -114,6 +114,7 @@ class Settings(object):
'password': '',
'hidservauth_string': '',
'data_dir': self.build_default_data_dir(),
+ 'csp_header_enabled': True,
'locale': None # this gets defined in fill_in_defaults()
}
self._settings = {}
diff --git a/onionshare/web/web.py b/onionshare/web/web.py
index ecd9edc2..825e690c 100644
--- a/onionshare/web/web.py
+++ b/onionshare/web/web.py
@@ -91,15 +91,6 @@ class Web:
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape
- self.security_headers = [
- ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'),
- ('X-Frame-Options', 'DENY'),
- ('X-Xss-Protection', '1; mode=block'),
- ('X-Content-Type-Options', 'nosniff'),
- ('Referrer-Policy', 'no-referrer'),
- ('Server', 'OnionShare')
- ]
-
self.q = queue.Queue()
self.password = None
@@ -293,6 +284,20 @@ class Web:
pass
self.running = False
+ def set_security_headers(self):
+ """
+ Set the security headers for the web service each time we start it.
+ """
+ self.security_headers = [
+ ('X-Frame-Options', 'DENY'),
+ ('X-Xss-Protection', '1; mode=block'),
+ ('X-Content-Type-Options', 'nosniff'),
+ ('Referrer-Policy', 'no-referrer'),
+ ('Server', 'OnionShare')
+ ]
+ if self.common.settings.get('csp_header_enabled'):
+ self.security_headers.append(('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'))
+
def start(self, port, stay_open=False, public_mode=False, password=None):
"""
Start the flask web server.
@@ -315,6 +320,7 @@ class Web:
host = '127.0.0.1'
self.running = True
+ self.set_security_headers()
self.app.run(host=host, port=port, threaded=True)
def stop(self, port):
diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py
index 25165688..432645e6 100644
--- a/onionshare_gui/settings_dialog.py
+++ b/onionshare_gui/settings_dialog.py
@@ -214,10 +214,28 @@ class SettingsDialog(QtWidgets.QDialog):
self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option"))
individual_downloads_label = QtWidgets.QLabel(strings._("gui_settings_individual_downloads_label"))
+ # Option to disable Content Security Policy (for website sharing)
+ self.csp_header_enabled_checkbox = QtWidgets.QCheckBox()
+ self.csp_header_enabled_checkbox.setCheckState(QtCore.Qt.Checked)
+ self.csp_header_enabled_checkbox.setText(strings._("gui_settings_csp_header_enabled_option"))
+ csp_header_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Content-Security-Policy"))
+ csp_header_label.setStyleSheet(self.common.css['settings_whats_this'])
+ csp_header_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ csp_header_label.setOpenExternalLinks(True)
+ csp_header_label.setMinimumSize(csp_header_label.sizeHint())
+ csp_header_layout = QtWidgets.QHBoxLayout()
+ csp_header_layout.addWidget(self.csp_header_enabled_checkbox)
+ csp_header_layout.addWidget(csp_header_label)
+ csp_header_layout.addStretch()
+ csp_header_layout.setContentsMargins(0,0,0,0)
+ self.csp_header_widget = QtWidgets.QWidget()
+ self.csp_header_widget.setLayout(csp_header_layout)
+
# Sharing options layout
sharing_group_layout = QtWidgets.QVBoxLayout()
sharing_group_layout.addWidget(self.close_after_first_download_checkbox)
sharing_group_layout.addWidget(individual_downloads_label)
+ sharing_group_layout.addWidget(self.csp_header_widget)
sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label"))
sharing_group.setLayout(sharing_group_layout)
@@ -517,6 +535,12 @@ class SettingsDialog(QtWidgets.QDialog):
else:
self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked)
+ csp_header_enabled = self.old_settings.get('csp_header_enabled')
+ if csp_header_enabled:
+ self.csp_header_enabled_checkbox.setCheckState(QtCore.Qt.Checked)
+ else:
+ self.csp_header_enabled_checkbox.setCheckState(QtCore.Qt.Unchecked)
+
autostart_timer = self.old_settings.get('autostart_timer')
if autostart_timer:
self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked)
@@ -982,6 +1006,7 @@ class SettingsDialog(QtWidgets.QDialog):
settings.load() # To get the last update timestamp
settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
+ settings.set('csp_header_enabled', self.csp_header_enabled_checkbox.isChecked())
settings.set('autostart_timer', self.autostart_timer_checkbox.isChecked())
settings.set('autostop_timer', self.autostop_timer_checkbox.isChecked())
diff --git a/share/locale/en.json b/share/locale/en.json
index aab6153d..6ca5c01f 100644
--- a/share/locale/en.json
+++ b/share/locale/en.json
@@ -52,6 +52,7 @@
"gui_settings_onion_label": "Onion settings",
"gui_settings_sharing_label": "Sharing settings",
"gui_settings_close_after_first_download_option": "Stop sharing after files have been sent",
+ "gui_settings_csp_header_enabled_option": "Enable Content Security Policy header",
"gui_settings_individual_downloads_label": "Uncheck to allow downloading individual files",
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
"gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare",
diff --git a/tests/GuiWebsiteTest.py b/tests/GuiWebsiteTest.py
index 7b88bfdf..f58f4aa2 100644
--- a/tests/GuiWebsiteTest.py
+++ b/tests/GuiWebsiteTest.py
@@ -65,6 +65,20 @@ class GuiWebsiteTest(GuiShareTest):
QtTest.QTest.qWait(2000)
self.assertTrue('This is a test website hosted by OnionShare' in r.text)
+ def check_csp_header(self, public_mode, csp_header_enabled):
+ '''Test that the CSP header is present when enabled or vice versa'''
+ url = "http://127.0.0.1:{}/".format(self.gui.app.port)
+ if public_mode:
+ r = requests.get(url)
+ else:
+ r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.website_mode.server_status.web.password))
+
+ QtTest.QTest.qWait(2000)
+ if csp_header_enabled:
+ self.assertTrue('Content-Security-Policy' in r.headers)
+ else:
+ self.assertFalse('Content-Security-Policy' in r.headers)
+
def run_all_website_mode_setup_tests(self):
"""Tests in website mode prior to starting a share"""
self.click_mode(self.gui.website_mode)
@@ -92,6 +106,7 @@ class GuiWebsiteTest(GuiShareTest):
self.run_all_website_mode_setup_tests()
self.run_all_website_mode_started_tests(public_mode, startup_time=2000)
self.view_website(public_mode)
+ self.check_csp_header(public_mode, self.gui.common.settings.get('csp_header_enabled'))
self.history_widgets_present(self.gui.website_mode)
self.server_is_stopped(self.gui.website_mode, False)
self.web_server_is_stopped()
diff --git a/tests/local_onionshare_website_mode_csp_enabled_test.py b/tests/local_onionshare_website_mode_csp_enabled_test.py
new file mode 100644
index 00000000..3cf79440
--- /dev/null
+++ b/tests/local_onionshare_website_mode_csp_enabled_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiWebsiteTest import GuiWebsiteTest
+
+class LocalWebsiteModeCSPEnabledTest(unittest.TestCase, GuiWebsiteTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {
+ "csp_header_enabled": True,
+ }
+ cls.gui = GuiWebsiteTest.set_up(test_settings)
+
+ @classmethod
+ def tearDownClass(cls):
+ GuiWebsiteTest.tear_down()
+
+ @pytest.mark.gui
+ @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
+ def test_gui(self):
+ #self.run_all_common_setup_tests()
+ self.run_all_website_mode_download_tests(False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/local_onionshare_website_mode_test.py b/tests/local_onionshare_website_mode_test.py
index 051adb3c..5a7334a4 100644
--- a/tests/local_onionshare_website_mode_test.py
+++ b/tests/local_onionshare_website_mode_test.py
@@ -8,6 +8,7 @@ class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest):
@classmethod
def setUpClass(cls):
test_settings = {
+ "csp_header_enabled": False
}
cls.gui = GuiWebsiteTest.set_up(test_settings)
diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py
index 05878899..d46c599b 100644
--- a/tests/test_onionshare_settings.py
+++ b/tests/test_onionshare_settings.py
@@ -66,7 +66,8 @@ class TestSettings:
'password': '',
'hidservauth_string': '',
'data_dir': os.path.expanduser('~/OnionShare'),
- 'public_mode': False
+ 'public_mode': False,
+ 'csp_header_enabled': True
}
for key in settings_obj._settings:
# Skip locale, it will not always default to the same thing