mirror of
https://github.com/onionshare/onionshare.git
synced 2024-10-01 01:35:40 -04:00
Make setting the Content-Security-Policy header optional so it doesn't break website mode shares
This commit is contained in:
parent
957d3e9c6d
commit
2524ddaf94
@ -114,6 +114,7 @@ class Settings(object):
|
|||||||
'password': '',
|
'password': '',
|
||||||
'hidservauth_string': '',
|
'hidservauth_string': '',
|
||||||
'data_dir': self.build_default_data_dir(),
|
'data_dir': self.build_default_data_dir(),
|
||||||
|
'csp_header_enabled': True,
|
||||||
'locale': None # this gets defined in fill_in_defaults()
|
'locale': None # this gets defined in fill_in_defaults()
|
||||||
}
|
}
|
||||||
self._settings = {}
|
self._settings = {}
|
||||||
|
@ -91,15 +91,6 @@ class Web:
|
|||||||
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
|
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
|
||||||
Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape
|
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.q = queue.Queue()
|
||||||
self.password = None
|
self.password = None
|
||||||
|
|
||||||
@ -293,6 +284,20 @@ class Web:
|
|||||||
pass
|
pass
|
||||||
self.running = False
|
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):
|
def start(self, port, stay_open=False, public_mode=False, password=None):
|
||||||
"""
|
"""
|
||||||
Start the flask web server.
|
Start the flask web server.
|
||||||
@ -315,6 +320,7 @@ class Web:
|
|||||||
host = '127.0.0.1'
|
host = '127.0.0.1'
|
||||||
|
|
||||||
self.running = True
|
self.running = True
|
||||||
|
self.set_security_headers()
|
||||||
self.app.run(host=host, port=port, threaded=True)
|
self.app.run(host=host, port=port, threaded=True)
|
||||||
|
|
||||||
def stop(self, port):
|
def stop(self, port):
|
||||||
|
@ -214,10 +214,28 @@ class SettingsDialog(QtWidgets.QDialog):
|
|||||||
self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option"))
|
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"))
|
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 options layout
|
||||||
sharing_group_layout = QtWidgets.QVBoxLayout()
|
sharing_group_layout = QtWidgets.QVBoxLayout()
|
||||||
sharing_group_layout.addWidget(self.close_after_first_download_checkbox)
|
sharing_group_layout.addWidget(self.close_after_first_download_checkbox)
|
||||||
sharing_group_layout.addWidget(individual_downloads_label)
|
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 = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label"))
|
||||||
sharing_group.setLayout(sharing_group_layout)
|
sharing_group.setLayout(sharing_group_layout)
|
||||||
|
|
||||||
@ -517,6 +535,12 @@ class SettingsDialog(QtWidgets.QDialog):
|
|||||||
else:
|
else:
|
||||||
self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
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')
|
autostart_timer = self.old_settings.get('autostart_timer')
|
||||||
if autostart_timer:
|
if autostart_timer:
|
||||||
self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked)
|
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.load() # To get the last update timestamp
|
||||||
|
|
||||||
settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
|
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('autostart_timer', self.autostart_timer_checkbox.isChecked())
|
||||||
settings.set('autostop_timer', self.autostop_timer_checkbox.isChecked())
|
settings.set('autostop_timer', self.autostop_timer_checkbox.isChecked())
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
"gui_settings_onion_label": "Onion settings",
|
"gui_settings_onion_label": "Onion settings",
|
||||||
"gui_settings_sharing_label": "Sharing settings",
|
"gui_settings_sharing_label": "Sharing settings",
|
||||||
"gui_settings_close_after_first_download_option": "Stop sharing after files have been sent",
|
"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_individual_downloads_label": "Uncheck to allow downloading individual files",
|
||||||
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
|
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
|
||||||
"gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare",
|
"gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare",
|
||||||
|
@ -65,6 +65,20 @@ class GuiWebsiteTest(GuiShareTest):
|
|||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
self.assertTrue('This is a test website hosted by OnionShare' in r.text)
|
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):
|
def run_all_website_mode_setup_tests(self):
|
||||||
"""Tests in website mode prior to starting a share"""
|
"""Tests in website mode prior to starting a share"""
|
||||||
self.click_mode(self.gui.website_mode)
|
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_setup_tests()
|
||||||
self.run_all_website_mode_started_tests(public_mode, startup_time=2000)
|
self.run_all_website_mode_started_tests(public_mode, startup_time=2000)
|
||||||
self.view_website(public_mode)
|
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.history_widgets_present(self.gui.website_mode)
|
||||||
self.server_is_stopped(self.gui.website_mode, False)
|
self.server_is_stopped(self.gui.website_mode, False)
|
||||||
self.web_server_is_stopped()
|
self.web_server_is_stopped()
|
||||||
|
26
tests/local_onionshare_website_mode_csp_enabled_test.py
Normal file
26
tests/local_onionshare_website_mode_csp_enabled_test.py
Normal file
@ -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()
|
@ -8,6 +8,7 @@ class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {
|
||||||
|
"csp_header_enabled": False
|
||||||
}
|
}
|
||||||
cls.gui = GuiWebsiteTest.set_up(test_settings)
|
cls.gui = GuiWebsiteTest.set_up(test_settings)
|
||||||
|
|
||||||
|
@ -66,7 +66,8 @@ class TestSettings:
|
|||||||
'password': '',
|
'password': '',
|
||||||
'hidservauth_string': '',
|
'hidservauth_string': '',
|
||||||
'data_dir': os.path.expanduser('~/OnionShare'),
|
'data_dir': os.path.expanduser('~/OnionShare'),
|
||||||
'public_mode': False
|
'public_mode': False,
|
||||||
|
'csp_header_enabled': True
|
||||||
}
|
}
|
||||||
for key in settings_obj._settings:
|
for key in settings_obj._settings:
|
||||||
# Skip locale, it will not always default to the same thing
|
# Skip locale, it will not always default to the same thing
|
||||||
|
Loading…
Reference in New Issue
Block a user