From b727a9651f065379a9725a01398b247aa4a2ef0e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 11 Nov 2021 17:33:19 +1100 Subject: [PATCH] Initial work on supporting the option to automatically attempt to fetch bridges based on the user's location if Tor fails to connect (censorship circumvention) --- cli/onionshare_cli/settings.py | 1 + cli/tests/test_cli_settings.py | 1 + .../src/onionshare/resources/locale/en.json | 3 +- desktop/src/onionshare/tor_connection.py | 31 +++++++++++++++++-- desktop/src/onionshare/tor_settings_tab.py | 23 +++++++++++++- 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/cli/onionshare_cli/settings.py b/cli/onionshare_cli/settings.py index c7d74a70..020c2776 100644 --- a/cli/onionshare_cli/settings.py +++ b/cli/onionshare_cli/settings.py @@ -113,6 +113,7 @@ class Settings(object): "persistent_tabs": [], "locale": None, # this gets defined in fill_in_defaults() "theme": 0, + "censorship_circumvention": False, } self._settings = {} self.fill_in_defaults() diff --git a/cli/tests/test_cli_settings.py b/cli/tests/test_cli_settings.py index 9513b013..9486e8d1 100644 --- a/cli/tests/test_cli_settings.py +++ b/cli/tests/test_cli_settings.py @@ -36,6 +36,7 @@ class TestSettings: "bridges_custom": "", "persistent_tabs": [], "theme": 0, + "censorship_circumvention": False, } for key in settings_obj._settings: # Skip locale, it will not always default to the same thing diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 868a6fa9..a69a7101 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -63,6 +63,7 @@ "gui_settings_tor_bridges": "Connect using a Tor bridge?", "gui_settings_tor_bridges_label": "Bridges help you access the Tor Network in places where Tor is blocked. Depending on where you are, one bridge may work better than another.", "gui_settings_bridge_use_checkbox": "Use a bridge", + "gui_settings_censorship_circumvention_checkbox": "Attempt to automatically find a bridge based on your country if Tor fails to connect", "gui_settings_bridge_radio_builtin": "Select a built-in bridge", "gui_settings_bridge_none_radio_option": "Don't use a bridge", "gui_settings_meek_lite_expensive_warning": "Warning: The meek-azure bridges are very costly for the Tor Project to run.

Only use them if unable to connect to Tor directly, via obfs4 transports, or other normal bridges.", @@ -231,4 +232,4 @@ "moat_captcha_error": "The solution is not correct. Please try again.", "moat_solution_empty_error": "You must enter the characters from the image", "mode_tor_not_connected_label": "OnionShare is not connected to the Tor network" -} \ No newline at end of file +} diff --git a/desktop/src/onionshare/tor_connection.py b/desktop/src/onionshare/tor_connection.py index 2cc599c4..0f3b7b2b 100644 --- a/desktop/src/onionshare/tor_connection.py +++ b/desktop/src/onionshare/tor_connection.py @@ -41,7 +41,7 @@ from onionshare_cli.onion import ( from . import strings from .gui_common import GuiCommon from .widgets import Alert - +from onionshare_cli.censorship import CensorshipCircumvention class TorConnectionDialog(QtWidgets.QProgressDialog): """ @@ -165,7 +165,7 @@ class TorConnectionWidget(QtWidgets.QWidget): success = QtCore.Signal() fail = QtCore.Signal(str) - def __init__(self, common, status_bar): + def __init__(self, common, status_bar, meek): super(TorConnectionWidget, self).__init__(None) self.common = common self.common.log("TorConnectionWidget", "__init__") @@ -181,6 +181,8 @@ class TorConnectionWidget(QtWidgets.QWidget): ) self.cancel_button.clicked.connect(self.cancel_clicked) + self.meek = meek + progress_layout = QtWidgets.QHBoxLayout() progress_layout.addWidget(self.progress) progress_layout.addWidget(self.cancel_button) @@ -263,7 +265,30 @@ class TorConnectionWidget(QtWidgets.QWidget): def _error_connecting_to_tor(self, msg): self.common.log("TorConnectionWidget", "_error_connecting_to_tor") self.active = False - self.fail.emit(msg) + # If we are allowed to try automatically resolving connection issues + # (e.g possible censorship) by obtaining bridges for the user, do so + if self.settings.get("censorship_circumvention"): + # Automatically try to obtain bridges from the Censorship Circumvention API + self.common.log("TorConnectionWidget", "_error_connecting_to_tor", "Trying to automatically obtain bridges") + self.meek.start() + self.censorship_circumvention = CensorshipCircumvention(self.common, self.meek) + request_bridges = self.censorship_circumvention.request_settings(country="cn") + if request_bridges: + # @TODO there might be several bridges + bridges = request_bridges["settings"][0]["bridges"]["bridge_strings"][0] + self.common.log("TorConnectionWidget", "_error_connecting_to_tor", f"Obtained bridges: {bridges}") + self.settings.set("bridges_enabled", True) + self.settings.set("bridges_type", "custom") + # @TODO there might be several bridges + self.settings.set("bridges_custom", bridges) + self.common.log("TorConnectionWidget", "_error_connecting_to_tor", "Starting Tor again") + self.settings.save() + # Now try and connect again + self.start() + else: + self.fail.emit() + else: + self.fail.emit() class TorConnectionThread(QtCore.QThread): diff --git a/desktop/src/onionshare/tor_settings_tab.py b/desktop/src/onionshare/tor_settings_tab.py index e28e5260..382b34fd 100644 --- a/desktop/src/onionshare/tor_settings_tab.py +++ b/desktop/src/onionshare/tor_settings_tab.py @@ -91,6 +91,12 @@ class TorSettingsTab(QtWidgets.QWidget): self.bridge_use_checkbox.stateChanged.connect( self.bridge_use_checkbox_state_changed ) + self.censorship_circumvention_checkbox = QtWidgets.QCheckBox( + strings._("gui_settings_censorship_circumvention_checkbox") + ) + self.censorship_circumvention_checkbox.stateChanged.connect( + self.censorship_circumvention_checkbox_state_changed + ) # Built-in bridge self.bridge_builtin_radio = QtWidgets.QRadioButton( @@ -164,6 +170,7 @@ class TorSettingsTab(QtWidgets.QWidget): bridges_layout = QtWidgets.QVBoxLayout() bridges_layout.addWidget(bridges_label) bridges_layout.addWidget(self.bridge_use_checkbox) + bridges_layout.addWidget(self.censorship_circumvention_checkbox) bridges_layout.addWidget(self.bridge_settings) self.bridges = QtWidgets.QWidget() @@ -330,7 +337,7 @@ class TorSettingsTab(QtWidgets.QWidget): columns_wrapper.setLayout(columns_layout) # Tor connection widget - self.tor_con = TorConnectionWidget(self.common, self.status_bar) + self.tor_con = TorConnectionWidget(self.common, self.status_bar, self.meek) self.tor_con.success.connect(self.tor_con_success) self.tor_con.fail.connect(self.tor_con_fail) self.tor_con.hide() @@ -430,6 +437,7 @@ class TorSettingsTab(QtWidgets.QWidget): if self.old_settings.get("bridges_enabled"): self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked) + self.censorship_circumvention_checkbox.setCheckState(QtCore.Qt.Checked) self.bridge_settings.show() bridges_type = self.old_settings.get("bridges_type") @@ -506,6 +514,16 @@ class TorSettingsTab(QtWidgets.QWidget): else: self.bridge_settings.hide() + def censorship_circumvention_checkbox_state_changed(self, state): + """ + 'Allow censorship circumvention (automatic bridges)' checkbox changed + """ + # Turning on censorship circumvention through the act of + # automatic bridge selection, implicitly means enabling + # bridges. + if state == QtCore.Qt.Checked: + self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked) + def bridge_builtin_radio_toggled(self, checked): """ 'Select a built-in bridge' radio button toggled @@ -812,6 +830,9 @@ class TorSettingsTab(QtWidgets.QWidget): if self.bridge_use_checkbox.checkState() == QtCore.Qt.Checked: settings.set("bridges_enabled", True) + if self.censorship_circumvention_checkbox.checkState() == QtCore.Qt.Checked: + settings.set("censorship_circumvention", True) + if self.bridge_builtin_radio.isChecked(): settings.set("bridges_type", "built-in")