From 3273832da717aed487955be82bff71407991842a Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 30 Nov 2021 10:50:47 +1100 Subject: [PATCH] Make the saving of the automatically-obtained bridges reusable (move it to CensorshipCircumvention class). Add the same functionality used in TorConnectionWidget to TorConnectionDialog. --- cli/onionshare_cli/censorship.py | 62 ++++++++++++++++++++ desktop/src/onionshare/main_window.py | 6 +- desktop/src/onionshare/tor_connection.py | 74 ++++++++++-------------- 3 files changed, 99 insertions(+), 43 deletions(-) diff --git a/cli/onionshare_cli/censorship.py b/cli/onionshare_cli/censorship.py index f84b1058..89513faa 100644 --- a/cli/onionshare_cli/censorship.py +++ b/cli/onionshare_cli/censorship.py @@ -167,3 +167,65 @@ class CensorshipCircumvention(object): return False return result + + def save_settings(self, settings, bridge_settings): + """ + Checks the bridges and saves them in settings. + """ + bridges_ok = False + self.settings = settings + + # @TODO there might be several bridge types recommended. + # Should we attempt to iterate over each type if one of them fails to connect? + # But if so, how to stop it starting 3 separate Tor connection threads? + # for bridges in request_bridges["settings"]: + bridges = bridge_settings["settings"][0]["bridges"] + self.common.log( + "CensorshipCircumvention", + "save_settings", + f"Obtained bridges: {bridges}", + ) + bridge_strings = bridges["bridge_strings"] + bridge_type = bridges["type"] + bridge_source = bridges["source"] + + # If the recommended bridge source is to use the built-in + # bridges, set that in our settings, as if the user had + # selected the built-in bridges for a specific PT themselves. + # + if bridge_source == "builtin": + self.settings.set("bridges_type", "built-in") + if bridge_type == "obfs4": + self.settings.set("bridges_builtin_pt", "obfs4") + if bridge_type == "snowflake": + self.settings.set("bridges_builtin_pt", "snowflake") + if bridge_type == "meek": + self.settings.set("bridges_builtin_pt", "meek-azure") + bridges_ok = True + else: + # Any other type of bridge we can treat as custom. + self.settings.set("bridges_type", "custom") + + # Sanity check the bridges provided from the Tor API before saving + bridges_checked = self.common.check_bridges_valid(bridge_strings) + + if bridges_checked: + self.settings.set("bridges_custom", "\n".join(bridges_checked)) + bridges_ok = True + + # If we got any good bridges, save them to settings and return. + if bridges_ok: + self.common.log( + "CensorshipCircumvention", + "save_settings", + "Saving settings with automatically-obtained bridges", + ) + self.settings.save() + return True + else: + self.common.log( + "CensorshipCircumvention", + "save_settings", + "Could not use any of the obtained bridges.", + ) + return False diff --git a/desktop/src/onionshare/main_window.py b/desktop/src/onionshare/main_window.py index 546592a1..79738d38 100644 --- a/desktop/src/onionshare/main_window.py +++ b/desktop/src/onionshare/main_window.py @@ -30,6 +30,7 @@ from .tab_widget import TabWidget from .gui_common import GuiCommon from .threads import OnionCleanupThread +from onionshare_cli.meek import Meek class MainWindow(QtWidgets.QMainWindow): """ @@ -160,8 +161,11 @@ class MainWindow(QtWidgets.QMainWindow): self.setCentralWidget(central_widget) self.show() + # Instantiate Meek, which the TorConnectionDialog may use to resolve + # connection issues by automatically obtaining bridges. + self.meek = Meek(self.common, get_tor_paths=self.common.gui.get_tor_paths) # Start the "Connecting to Tor" dialog, which calls onion.connect() - tor_con = TorConnectionDialog(self.common) + tor_con = TorConnectionDialog(self.common, self.meek) tor_con.canceled.connect(self.tor_connection_canceled) tor_con.open_tor_settings.connect(self.tor_connection_open_tor_settings) if not self.common.gui.local_only: diff --git a/desktop/src/onionshare/tor_connection.py b/desktop/src/onionshare/tor_connection.py index a0025623..5c427d3d 100644 --- a/desktop/src/onionshare/tor_connection.py +++ b/desktop/src/onionshare/tor_connection.py @@ -53,13 +53,15 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): success = QtCore.Signal() def __init__( - self, common, custom_settings=False, testing_settings=False, onion=None + self, common, meek, custom_settings=False, testing_settings=False, onion=None ): super(TorConnectionDialog, self).__init__(None) self.common = common self.testing_settings = testing_settings + self.meek = meek + if custom_settings: self.settings = custom_settings else: @@ -138,6 +140,30 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): def alert(): Alert(self.common, msg, QtWidgets.QMessageBox.Warning, title=self.title) + # If we are allowed to try automatically resolving connection issues + # (e.g possible censorship) by obtaining bridges for the user, do so + elif self.settings.get("censorship_circumvention"): + def alert(): + return + + # Automatically try to obtain bridges from the Censorship Circumvention API + self.common.log( + "TorConnectionDialog", + "_error_connecting_to_tor", + "Trying to automatically obtain bridges", + ) + self.meek.start() + self.censorship_circumvention = CensorshipCircumvention( + self.common, self.meek + ) + bridge_settings = self.censorship_circumvention.request_settings( + country="tm" + ) + self.meek.cleanup() + + if bridge_settings and self.censorship_circumvention.save_settings(self.settings, bridge_settings): + # Try and connect again + self.start() else: # If not testing, open settings after displaying the error def alert(): @@ -266,6 +292,7 @@ class TorConnectionWidget(QtWidgets.QWidget): def _error_connecting_to_tor(self, msg): self.common.log("TorConnectionWidget", "_error_connecting_to_tor") self.active = False + # 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"): @@ -279,50 +306,13 @@ class TorConnectionWidget(QtWidgets.QWidget): self.censorship_circumvention = CensorshipCircumvention( self.common, self.meek ) - request_bridges = self.censorship_circumvention.request_settings( + bridge_settings = self.censorship_circumvention.request_settings( country="tm" ) - if request_bridges: - # @TODO there might be several bridge types recommended. - # Should we attempt to iterate over each type if one of them fails to connect? - # But if so, how to stop it starting 3 separate Tor connection threads? - # for bridges in request_bridges["settings"]: - bridges = request_bridges["settings"][0]["bridges"] - self.common.log( - "TorConnectionWidget", - "_error_connecting_to_tor", - f"Obtained bridges: {bridges}", - ) - bridge_strings = bridges["bridge_strings"] - bridge_type = bridges["type"] - bridge_source = bridges["source"] + self.meek.cleanup() - # If the recommended bridge source is to use the built-in - # bridges, set that in our settings, as if the user had - # selected the built-in bridges for a specific PT themselves. - # - if bridge_source == "builtin": - self.settings.set("bridges_type", "built-in") - if bridge_type == "obfs4": - self.settings.set("bridges_builtin_pt", "obfs4") - if bridge_type == "snowflake": - self.settings.set("bridges_builtin_pt", "snowflake") - if bridge_type == "meek": - self.settings.set("bridges_builtin_pt", "meek-azure") - else: - self.settings.set("bridges_type", "custom") - # Sanity check the bridges provided from the Tor API before saving - bridges_checked = self.common.check_bridges_valid(bridge_strings) - if bridges_checked: - self.settings.set("bridges_custom", "\n".join(bridges_checked)) - - self.common.log( - "TorConnectionWidget", - "_error_connecting_to_tor", - "Starting Tor again", - ) - self.settings.save() - # Now try and connect again + if bridge_settings and self.censorship_circumvention.save_settings(self.settings, bridge_settings): + # Try and connect again self.start() else: self.fail.emit()