mirror of
https://github.com/onionshare/onionshare.git
synced 2024-12-25 07:19:41 -05:00
Actually get bridges from moat
This commit is contained in:
parent
6bf839f826
commit
2212589625
@ -392,6 +392,12 @@ class GuiCommon:
|
|||||||
QPushButton {
|
QPushButton {
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}""",
|
}""",
|
||||||
|
# Moat dialog
|
||||||
|
"moat_error": """
|
||||||
|
QLabel {
|
||||||
|
color: #990000;
|
||||||
|
}
|
||||||
|
""",
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_tor_paths(self):
|
def get_tor_paths(self):
|
||||||
|
@ -20,6 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
from PySide2 import QtCore, QtWidgets, QtGui
|
from PySide2 import QtCore, QtWidgets, QtGui
|
||||||
import requests
|
import requests
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
|
||||||
from . import strings
|
from . import strings
|
||||||
from .gui_common import GuiCommon
|
from .gui_common import GuiCommon
|
||||||
@ -30,6 +32,8 @@ class MoatDialog(QtWidgets.QDialog):
|
|||||||
Moat dialog: Request a bridge from torproject.org
|
Moat dialog: Request a bridge from torproject.org
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
got_bridges = QtCore.Signal(str)
|
||||||
|
|
||||||
def __init__(self, common):
|
def __init__(self, common):
|
||||||
super(MoatDialog, self).__init__()
|
super(MoatDialog, self).__init__()
|
||||||
|
|
||||||
@ -42,7 +46,7 @@ class MoatDialog(QtWidgets.QDialog):
|
|||||||
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
|
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
|
||||||
|
|
||||||
# Label
|
# Label
|
||||||
self.label = QtWidgets.QLabel(strings._("moat_contact_label"))
|
self.label = QtWidgets.QLabel()
|
||||||
|
|
||||||
# CAPTCHA image
|
# CAPTCHA image
|
||||||
self.captcha = QtWidgets.QLabel()
|
self.captcha = QtWidgets.QLabel()
|
||||||
@ -50,7 +54,7 @@ class MoatDialog(QtWidgets.QDialog):
|
|||||||
|
|
||||||
# Solution input
|
# Solution input
|
||||||
self.solution_lineedit = QtWidgets.QLineEdit()
|
self.solution_lineedit = QtWidgets.QLineEdit()
|
||||||
self.solution_lineedit.editingFinished.connect(self.solution_editing_finished)
|
self.solution_lineedit.setPlaceholderText(strings._("moat_captcha_placeholder"))
|
||||||
self.reload_button = QtWidgets.QPushButton(strings._("moat_captcha_reload"))
|
self.reload_button = QtWidgets.QPushButton(strings._("moat_captcha_reload"))
|
||||||
self.reload_button.clicked.connect(self.reload_clicked)
|
self.reload_button.clicked.connect(self.reload_clicked)
|
||||||
solution_layout = QtWidgets.QHBoxLayout()
|
solution_layout = QtWidgets.QHBoxLayout()
|
||||||
@ -59,12 +63,12 @@ class MoatDialog(QtWidgets.QDialog):
|
|||||||
|
|
||||||
# Error label
|
# Error label
|
||||||
self.error_label = QtWidgets.QLabel()
|
self.error_label = QtWidgets.QLabel()
|
||||||
|
self.error_label.setStyleSheet(self.common.gui.css["moat_error"])
|
||||||
self.error_label.hide()
|
self.error_label.hide()
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
self.submit_button = QtWidgets.QPushButton(strings._("moat_captcha_submit"))
|
self.submit_button = QtWidgets.QPushButton(strings._("moat_captcha_submit"))
|
||||||
self.submit_button.clicked.connect(self.submit_clicked)
|
self.submit_button.clicked.connect(self.submit_clicked)
|
||||||
self.submit_button.setEnabled(False)
|
|
||||||
self.cancel_button = QtWidgets.QPushButton(
|
self.cancel_button = QtWidgets.QPushButton(
|
||||||
strings._("gui_settings_button_cancel")
|
strings._("gui_settings_button_cancel")
|
||||||
)
|
)
|
||||||
@ -79,6 +83,7 @@ class MoatDialog(QtWidgets.QDialog):
|
|||||||
layout.addWidget(self.label)
|
layout.addWidget(self.label)
|
||||||
layout.addWidget(self.captcha)
|
layout.addWidget(self.captcha)
|
||||||
layout.addLayout(solution_layout)
|
layout.addLayout(solution_layout)
|
||||||
|
layout.addStretch()
|
||||||
layout.addWidget(self.error_label)
|
layout.addWidget(self.error_label)
|
||||||
layout.addLayout(buttons_layout)
|
layout.addLayout(buttons_layout)
|
||||||
|
|
||||||
@ -86,35 +91,95 @@ class MoatDialog(QtWidgets.QDialog):
|
|||||||
self.cancel_button.setFocus()
|
self.cancel_button.setFocus()
|
||||||
|
|
||||||
self.reload_clicked()
|
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):
|
def reload_clicked(self):
|
||||||
"""
|
"""
|
||||||
Reload button clicked.
|
Reload button clicked.
|
||||||
"""
|
"""
|
||||||
self.common.log("MoatDialog", "reload_clicked")
|
self.common.log("MoatDialog", "reload_clicked")
|
||||||
pass
|
|
||||||
|
self.label.setText(strings._("moat_contact_label"))
|
||||||
|
self.error_label.hide()
|
||||||
|
|
||||||
|
self.captcha.hide()
|
||||||
|
self.solution_lineedit.hide()
|
||||||
|
self.reload_button.hide()
|
||||||
|
self.submit_button.hide()
|
||||||
|
|
||||||
|
# BridgeDB fetch
|
||||||
|
self.t_fetch = MoatThread(self.common, "fetch")
|
||||||
|
self.t_fetch.bridgedb_error.connect(self.bridgedb_error)
|
||||||
|
self.t_fetch.captcha_ready.connect(self.captcha_ready)
|
||||||
|
self.t_fetch.start()
|
||||||
|
|
||||||
def submit_clicked(self):
|
def submit_clicked(self):
|
||||||
"""
|
"""
|
||||||
Submit button clicked.
|
Submit button clicked.
|
||||||
"""
|
"""
|
||||||
self.common.log("MoatDialog", "submit_clicked")
|
self.error_label.hide()
|
||||||
pass
|
|
||||||
|
solution = self.solution_lineedit.text().strip()
|
||||||
|
if len(solution) == 0:
|
||||||
|
self.common.log("MoatDialog", "submit_clicked", "solution is blank")
|
||||||
|
self.error_label.setText(strings._("moat_solution_empty_error"))
|
||||||
|
self.error_label.show()
|
||||||
|
return
|
||||||
|
|
||||||
|
# BridgeDB check
|
||||||
|
self.t_check = MoatThread(
|
||||||
|
self.common,
|
||||||
|
"check",
|
||||||
|
{"challenge": self.challenge, "solution": self.solution_lineedit.text()},
|
||||||
|
)
|
||||||
|
self.t_check.bridgedb_error.connect(self.bridgedb_error)
|
||||||
|
self.t_check.captcha_error.connect(self.captcha_error)
|
||||||
|
self.t_check.bridges_ready.connect(self.bridges_ready)
|
||||||
|
self.t_check.start()
|
||||||
|
|
||||||
def cancel_clicked(self):
|
def cancel_clicked(self):
|
||||||
"""
|
"""
|
||||||
Cancel button clicked.
|
Cancel button clicked.
|
||||||
"""
|
"""
|
||||||
self.common.log("MoatDialog", "cancel_clicked")
|
self.common.log("MoatDialog", "cancel_clicked")
|
||||||
pass
|
self.close()
|
||||||
|
|
||||||
|
def bridgedb_error(self):
|
||||||
|
self.common.log("MoatDialog", "bridgedb_error")
|
||||||
|
self.error_label.setText(strings._("moat_bridgedb_error"))
|
||||||
|
self.error_label.show()
|
||||||
|
|
||||||
|
def captcha_error(self, msg):
|
||||||
|
self.common.log("MoatDialog", "captcha_error")
|
||||||
|
if msg == "":
|
||||||
|
self.error_label.setText(strings._("moat_captcha_error"))
|
||||||
|
else:
|
||||||
|
self.error_label.setText(msg)
|
||||||
|
self.error_label.show()
|
||||||
|
|
||||||
|
def captcha_ready(self, image, challenge):
|
||||||
|
self.common.log("MoatDialog", "captcha_ready")
|
||||||
|
|
||||||
|
self.challenge = challenge
|
||||||
|
|
||||||
|
# Save captcha image to disk, so we can load it
|
||||||
|
captcha_data = base64.b64decode(image)
|
||||||
|
captcha_filename = os.path.join(self.common.build_tmp_dir(), "captcha.jpg")
|
||||||
|
with open(captcha_filename, "wb") as f:
|
||||||
|
f.write(captcha_data)
|
||||||
|
|
||||||
|
self.captcha.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(captcha_filename)))
|
||||||
|
os.remove(captcha_filename)
|
||||||
|
|
||||||
|
self.label.setText(strings._("moat_captcha_label"))
|
||||||
|
self.captcha.show()
|
||||||
|
self.solution_lineedit.show()
|
||||||
|
self.reload_button.show()
|
||||||
|
self.submit_button.show()
|
||||||
|
|
||||||
|
def bridges_ready(self, bridges):
|
||||||
|
self.common.log("MoatDialog", "bridges_ready", bridges)
|
||||||
|
self.got_bridges.emit(bridges)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
class MoatThread(QtCore.QThread):
|
class MoatThread(QtCore.QThread):
|
||||||
@ -127,48 +192,126 @@ class MoatThread(QtCore.QThread):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tor_status_update = QtCore.Signal(str, str)
|
bridgedb_error = QtCore.Signal()
|
||||||
|
captcha_error = QtCore.Signal(str)
|
||||||
|
captcha_ready = QtCore.Signal(str, str)
|
||||||
|
bridges_ready = QtCore.Signal(str)
|
||||||
|
|
||||||
def __init__(self, common, action, data):
|
def __init__(self, common, action, data={}):
|
||||||
super(MoatThread, self).__init__()
|
super(MoatThread, self).__init__()
|
||||||
self.common = common
|
self.common = common
|
||||||
self.common.log("MoatThread", "__init__", f"action={action}")
|
self.common.log("MoatThread", "__init__", f"action={action}")
|
||||||
|
|
||||||
|
self.transport = "obfs4"
|
||||||
self.action = action
|
self.action = action
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.common.log("MoatThread", "run")
|
|
||||||
|
|
||||||
# TODO: Do all of this using domain fronting
|
# TODO: Do all of this using domain fronting
|
||||||
|
|
||||||
# Request a bridge
|
if self.action == "fetch":
|
||||||
r = requests.post(
|
self.common.log("MoatThread", "run", f"starting fetch")
|
||||||
"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:
|
# Request a bridge
|
||||||
moat_res = r.json()
|
r = requests.post(
|
||||||
if "errors" in moat_res or "data" not in moat_res:
|
"https://bridges.torproject.org/moat/fetch",
|
||||||
return moat_error()
|
headers={"Content-Type": "application/vnd.api+json"},
|
||||||
if moat_res["type"] != "moat-challenge":
|
json={
|
||||||
return moat_error()
|
"data": [
|
||||||
|
{
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "client-transports",
|
||||||
|
"supported": [self.transport],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
self.common.log("MoatThread", "run", f"status_code={r.status_code}")
|
||||||
|
self.bridgedb_error.emit()
|
||||||
|
return
|
||||||
|
|
||||||
moat_type = moat_res["type"]
|
try:
|
||||||
moat_transport = moat_res["transport"]
|
moat_res = r.json()
|
||||||
moat_image = moat_res["image"]
|
if "errors" in moat_res:
|
||||||
moat_challenge = moat_res["challenge"]
|
self.common.log("MoatThread", "run", f"errors={moat_res['errors']}")
|
||||||
except:
|
self.bridgedb_error.emit()
|
||||||
return moat_error()
|
return
|
||||||
|
if "data" not in moat_res:
|
||||||
|
self.common.log("MoatThread", "run", f"no data")
|
||||||
|
self.bridgedb_error.emit()
|
||||||
|
return
|
||||||
|
if moat_res["data"][0]["type"] != "moat-challenge":
|
||||||
|
self.common.log("MoatThread", "run", f"type != moat-challange")
|
||||||
|
self.bridgedb_error.emit()
|
||||||
|
return
|
||||||
|
if moat_res["data"][0]["transport"] != self.transport:
|
||||||
|
self.common.log(
|
||||||
|
"MoatThread", "run", f"transport != {self.transport}"
|
||||||
|
)
|
||||||
|
self.bridgedb_error.emit()
|
||||||
|
return
|
||||||
|
|
||||||
|
image = moat_res["data"][0]["image"]
|
||||||
|
challenge = moat_res["data"][0]["challenge"]
|
||||||
|
|
||||||
|
self.captcha_ready.emit(image, challenge)
|
||||||
|
except Exception as e:
|
||||||
|
self.common.log("MoatThread", "run", f"hit exception: {e}")
|
||||||
|
self.bridgedb_error.emit()
|
||||||
|
return
|
||||||
|
|
||||||
|
elif self.action == "check":
|
||||||
|
self.common.log("MoatThread", "run", f"starting check")
|
||||||
|
|
||||||
|
# Check the CAPTCHA
|
||||||
|
r = requests.post(
|
||||||
|
"https://bridges.torproject.org/moat/check",
|
||||||
|
headers={"Content-Type": "application/vnd.api+json"},
|
||||||
|
json={
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"type": "moat-solution",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"transport": self.transport,
|
||||||
|
"challenge": self.data["challenge"],
|
||||||
|
"solution": self.data["solution"],
|
||||||
|
"qrcode": "false",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
self.common.log("MoatThread", "run", f"status_code={r.status_code}")
|
||||||
|
self.bridgedb_error.emit()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
moat_res = r.json()
|
||||||
|
|
||||||
|
if "errors" in moat_res:
|
||||||
|
self.common.log("MoatThread", "run", f"errors={moat_res['errors']}")
|
||||||
|
if moat_res["errors"][0]["code"] == 419:
|
||||||
|
self.captcha_error.emit("")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
errors = " ".join([e["detail"] for e in moat_res["errors"]])
|
||||||
|
self.captcha_error.emit(errors)
|
||||||
|
return
|
||||||
|
|
||||||
|
if moat_res["data"][0]["type"] != "moat-bridges":
|
||||||
|
self.common.log("MoatThread", "run", f"type != moat-bridges")
|
||||||
|
self.bridgedb_error.emit()
|
||||||
|
return
|
||||||
|
|
||||||
|
bridges = moat_res["data"][0]["bridges"]
|
||||||
|
self.bridges_ready.emit("\n".join(bridges))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.common.log("MoatThread", "run", f"hit exception: {e}")
|
||||||
|
self.bridgedb_error.emit()
|
||||||
|
return
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.common.log("MoatThread", "run", f"invalid action: {self.action}")
|
||||||
|
@ -226,5 +226,6 @@
|
|||||||
"moat_captcha_submit": "Submit",
|
"moat_captcha_submit": "Submit",
|
||||||
"moat_captcha_reload": "Reload",
|
"moat_captcha_reload": "Reload",
|
||||||
"moat_bridgedb_error": "Error contacting BridgeDB.",
|
"moat_bridgedb_error": "Error contacting BridgeDB.",
|
||||||
"moat_captcha_error": "The solution is not correct. Please try again."
|
"moat_captcha_error": "The solution is not correct. Please try again.",
|
||||||
|
"moat_solution_empty_error": "You must enter the characters from the image"
|
||||||
}
|
}
|
@ -148,7 +148,8 @@ class TorSettingsDialog(QtWidgets.QDialog):
|
|||||||
self.bridge_moat_button.clicked.connect(self.bridge_moat_button_clicked)
|
self.bridge_moat_button.clicked.connect(self.bridge_moat_button_clicked)
|
||||||
self.bridge_moat_textbox = QtWidgets.QPlainTextEdit()
|
self.bridge_moat_textbox = QtWidgets.QPlainTextEdit()
|
||||||
self.bridge_moat_textbox.setMaximumHeight(100)
|
self.bridge_moat_textbox.setMaximumHeight(100)
|
||||||
self.bridge_moat_textbox.setEnabled(False)
|
self.bridge_moat_textbox.setReadOnly(True)
|
||||||
|
self.bridge_moat_textbox.setWordWrapMode(QtGui.QTextOption.NoWrap)
|
||||||
self.bridge_moat_textbox.hide()
|
self.bridge_moat_textbox.hide()
|
||||||
bridge_moat_textbox_options_layout = QtWidgets.QVBoxLayout()
|
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_button)
|
||||||
@ -508,6 +509,16 @@ class TorSettingsDialog(QtWidgets.QDialog):
|
|||||||
self.common.log("TorSettingsDialog", "bridge_moat_button_clicked")
|
self.common.log("TorSettingsDialog", "bridge_moat_button_clicked")
|
||||||
|
|
||||||
moat_dialog = MoatDialog(self.common)
|
moat_dialog = MoatDialog(self.common)
|
||||||
|
moat_dialog.got_bridges.connect(self.bridge_moat_got_bridges)
|
||||||
|
moat_dialog.exec_()
|
||||||
|
|
||||||
|
def bridge_moat_got_bridges(self, bridges):
|
||||||
|
"""
|
||||||
|
Got new bridges from moat
|
||||||
|
"""
|
||||||
|
self.common.log("TorSettingsDialog", "bridge_moat_got_bridges")
|
||||||
|
self.bridge_moat_textbox.document().setPlainText(bridges)
|
||||||
|
self.bridge_moat_textbox.show()
|
||||||
|
|
||||||
def bridge_custom_radio_toggled(self, checked):
|
def bridge_custom_radio_toggled(self, checked):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user