From 67126a34976c4d036873323b1bf5a23c1d46340e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 15 Oct 2021 16:53:40 -0700 Subject: [PATCH] Start making MoatDialog --- desktop/src/onionshare/moat_dialog.py | 174 ++++++++++++++++++ .../src/onionshare/resources/locale/en.json | 10 +- desktop/src/onionshare/tor_settings_dialog.py | 47 +---- 3 files changed, 186 insertions(+), 45 deletions(-) create mode 100644 desktop/src/onionshare/moat_dialog.py diff --git a/desktop/src/onionshare/moat_dialog.py b/desktop/src/onionshare/moat_dialog.py new file mode 100644 index 00000000..ba4223eb --- /dev/null +++ b/desktop/src/onionshare/moat_dialog.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2021 Micah Lee, et al. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from PySide2 import QtCore, QtWidgets, QtGui +import requests + +from . import strings +from .gui_common import GuiCommon + + +class MoatDialog(QtWidgets.QDialog): + """ + Moat dialog: Request a bridge from torproject.org + """ + + def __init__(self, common): + super(MoatDialog, self).__init__() + + self.common = common + + self.common.log("MoatDialog", "__init__") + + self.setModal(True) + self.setWindowTitle(strings._("gui_settings_bridge_moat_button")) + self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))) + + # Label + self.label = QtWidgets.QLabel(strings._("moat_contact_label")) + + # CAPTCHA image + self.captcha = QtWidgets.QLabel() + self.captcha.setFixedSize(400, 125) # this is the size of the CAPTCHA image + + # Solution input + self.solution_lineedit = QtWidgets.QLineEdit() + self.solution_lineedit.editingFinished.connect(self.solution_editing_finished) + self.reload_button = QtWidgets.QPushButton(strings._("moat_captcha_reload")) + self.reload_button.clicked.connect(self.reload_clicked) + solution_layout = QtWidgets.QHBoxLayout() + solution_layout.addWidget(self.solution_lineedit) + solution_layout.addWidget(self.reload_button) + + # Error label + self.error_label = QtWidgets.QLabel() + self.error_label.hide() + + # Buttons + self.submit_button = QtWidgets.QPushButton(strings._("moat_captcha_submit")) + self.submit_button.clicked.connect(self.submit_clicked) + self.submit_button.setEnabled(False) + self.cancel_button = QtWidgets.QPushButton( + strings._("gui_settings_button_cancel") + ) + self.cancel_button.clicked.connect(self.cancel_clicked) + buttons_layout = QtWidgets.QHBoxLayout() + buttons_layout.addStretch() + buttons_layout.addWidget(self.submit_button) + buttons_layout.addWidget(self.cancel_button) + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label) + layout.addWidget(self.captcha) + layout.addLayout(solution_layout) + layout.addWidget(self.error_label) + layout.addLayout(buttons_layout) + + self.setLayout(layout) + self.cancel_button.setFocus() + + self.reload_clicked() + self.exec_() + + def solution_editing_finished(self): + """ + Finished typing something in the solution field. + """ + self.common.log("MoatDialog", "solution_editing_finished") + pass + + def reload_clicked(self): + """ + Reload button clicked. + """ + self.common.log("MoatDialog", "reload_clicked") + pass + + def submit_clicked(self): + """ + Submit button clicked. + """ + self.common.log("MoatDialog", "submit_clicked") + pass + + def cancel_clicked(self): + """ + Cancel button clicked. + """ + self.common.log("MoatDialog", "cancel_clicked") + pass + + +class MoatThread(QtCore.QThread): + """ + This does all of the communicating with BridgeDB in a separate thread. + + Valid actions are: + - "fetch": requests a new CAPTCHA + - "check": sends a CAPTCHA solution + + """ + + tor_status_update = QtCore.Signal(str, str) + + def __init__(self, common, action, data): + super(MoatThread, self).__init__() + self.common = common + self.common.log("MoatThread", "__init__", f"action={action}") + + self.action = action + self.data = data + + def run(self): + self.common.log("MoatThread", "run") + + # TODO: Do all of this using domain fronting + + # Request a bridge + r = requests.post( + "https://bridges.torproject.org/moat/fetch", + headers={"Content-Type": "application/vnd.api+json"}, + json={ + "data": [ + { + "version": "0.1.0", + "type": "client-transports", + "supported": ["obfs4"], + } + ] + }, + ) + if r.status_code != 200: + return moat_error() + + try: + moat_res = r.json() + if "errors" in moat_res or "data" not in moat_res: + return moat_error() + if moat_res["type"] != "moat-challenge": + return moat_error() + + moat_type = moat_res["type"] + moat_transport = moat_res["transport"] + moat_image = moat_res["image"] + moat_challenge = moat_res["challenge"] + except: + return moat_error() diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 97ce5585..6bc424e0 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -69,7 +69,6 @@ "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.", "gui_settings_bridge_moat_radio_option": "Request a bridge from torproject.org", "gui_settings_bridge_moat_button": "Request a New Bridge...", - "gui_settings_bridge_moat_error": "Error requesting a bridge from torproject.org.", "gui_settings_bridge_custom_radio_option": "Provide a bridge you learned about from a trusted source", "gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.", "gui_settings_button_save": "Save", @@ -220,5 +219,12 @@ "gui_rendezvous_cleanup_quit_early": "Quit Early", "error_port_not_available": "OnionShare port not available", "history_receive_read_message_button": "Read Message", - "error_tor_protocol_error": "There was an error with Tor: {}" + "error_tor_protocol_error": "There was an error with Tor: {}", + "moat_contact_label": "Contacting BridgeDB...", + "moat_captcha_label": "Solve the CAPTCHA to request a bridge.", + "moat_captcha_placeholder": "Enter the characters from the image", + "moat_captcha_submit": "Submit", + "moat_captcha_reload": "Reload", + "moat_bridgedb_error": "Error contacting BridgeDB.", + "moat_captcha_error": "The solution is not correct. Please try again." } \ No newline at end of file diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index 9f08d767..f5f0a302 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -23,15 +23,14 @@ import sys import platform import re import os -import requests from onionshare_cli.settings import Settings from onionshare_cli.onion import Onion from . import strings from .widgets import Alert -from .update_checker import UpdateThread from .tor_connection_dialog import TorConnectionDialog +from .moat_dialog import MoatDialog from .gui_common import GuiCommon @@ -146,16 +145,16 @@ class TorSettingsDialog(QtWidgets.QDialog): self.bridge_moat_button = QtWidgets.QPushButton( strings._("gui_settings_bridge_moat_button") ) - self.bridge_moat_button.setMinimumHeight(20) self.bridge_moat_button.clicked.connect(self.bridge_moat_button_clicked) self.bridge_moat_textbox = QtWidgets.QPlainTextEdit() - self.bridge_moat_textbox.setMaximumHeight(200) + self.bridge_moat_textbox.setMaximumHeight(100) self.bridge_moat_textbox.setEnabled(False) self.bridge_moat_textbox.hide() bridge_moat_textbox_options_layout = QtWidgets.QVBoxLayout() bridge_moat_textbox_options_layout.addWidget(self.bridge_moat_button) bridge_moat_textbox_options_layout.addWidget(self.bridge_moat_textbox) self.bridge_moat_textbox_options = QtWidgets.QWidget() + self.bridge_moat_textbox_options.setMinimumHeight(50) self.bridge_moat_textbox_options.setLayout(bridge_moat_textbox_options_layout) self.bridge_moat_textbox_options.hide() @@ -508,45 +507,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ self.common.log("TorSettingsDialog", "bridge_moat_button_clicked") - def moat_error(): - Alert( - self.common, - strings._("gui_settings_bridge_moat_error"), - title=strings._("gui_settings_bridge_moat_button"), - ) - - # TODO: Do all of this using domain fronting - - # Request a bridge - r = requests.post( - "https://bridges.torproject.org/moat/fetch", - headers={"Content-Type": "application/vnd.api+json"}, - json={ - "data": [ - { - "version": "0.1.0", - "type": "client-transports", - "supported": ["obfs4"], - } - ] - }, - ) - if r.status_code != 200: - return moat_error() - - try: - moat_res = r.json() - if "errors" in moat_res or "data" not in moat_res: - return moat_error() - if moat_res["type"] != "moat-challenge": - return moat_error() - - moat_type = moat_res["type"] - moat_transport = moat_res["transport"] - moat_image = moat_res["image"] - moat_challenge = moat_res["challenge"] - except: - return moat_error() + moat_dialog = MoatDialog(self.common) def bridge_custom_radio_toggled(self, checked): """