From 3e61e8dd37d768cba35d7b7f4bc97f872d24aaef Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 11 Oct 2021 20:17:26 -0700 Subject: [PATCH 01/26] Write Linux script to download Tor Browser and extract binaries --- desktop/README.md | 14 +++- desktop/scripts/get-tor-linux.py | 128 +++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 2 deletions(-) create mode 100755 desktop/scripts/get-tor-linux.py diff --git a/desktop/README.md b/desktop/README.md index 4a59fe03..c8c51519 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -13,9 +13,19 @@ cd onionshare/desktop #### Linux -If you're using Linux, install `tor` and `obfs4proxy` from either the [official Debian repository](https://support.torproject.org/apt/tor-deb-repo/), or from your package manager. +In Ubuntu 20.04 you need the `libxcb-xinerama0` package installed. -In Ubuntu 20.04 you also need the `libxcb-xinerama0` package installed. +Install python dependencies: + +```sh +pip3 install --user poetry requests +``` + +Download Tor Browser and extract the binaries: + +```sh +./scripts/get-tor-linux.py +``` #### macOS diff --git a/desktop/scripts/get-tor-linux.py b/desktop/scripts/get-tor-linux.py new file mode 100755 index 00000000..4bd9ff13 --- /dev/null +++ b/desktop/scripts/get-tor-linux.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# -*- 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 . +""" + +""" +This script downloads a pre-built tor binary to bundle with OnionShare. +In order to avoid a Mac gnupg dependency, I manually verify the signature +and hard-code the sha256 hash. +""" +import inspect +import os +import sys +import hashlib +import shutil +import subprocess +import requests + + +def main(): + tarball_url = "https://dist.torproject.org/torbrowser/11.0a7/tor-browser-linux64-11.0a7_en-US.tar.xz" + tarball_filename = "tor-browser-linux64-11.0a7_en-US.tar.xz" + expected_tarball_sha256 = ( + "bc9861c692f899fe0344c960dc615ff0e275cf74c61066c8735c88e3ddc2b623" + ) + + # Build paths + root_path = os.path.dirname( + os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + ) + working_path = os.path.join(root_path, "build", "tor") + tarball_path = os.path.join(working_path, tarball_filename) + + # Make sure the dist path exists + dist_path = os.path.join(working_path, "dist") + if not os.path.exists(dist_path): + os.makedirs(dist_path, exist_ok=True) + + # Make sure the tarball is downloaded + if not os.path.exists(tarball_path): + print("Downloading {}".format(tarball_url)) + r = requests.get(tarball_url) + open(tarball_path, "wb").write(r.content) + tarball_sha256 = hashlib.sha256(r.content).hexdigest() + else: + tarball_data = open(tarball_path, "rb").read() + tarball_sha256 = hashlib.sha256(tarball_data).hexdigest() + + # Compare the hash + if tarball_sha256 != expected_tarball_sha256: + print("ERROR! The sha256 doesn't match:") + print("expected: {}".format(expected_tarball_sha256)) + print(" actual: {}".format(tarball_sha256)) + sys.exit(-1) + + # Delete extracted tarball, if it's there + shutil.rmtree(os.path.join(working_path, "tor-browser_en-US"), ignore_errors=True) + + # Extract the tarball + subprocess.call(["tar", "-xvf", tarball_path], cwd=working_path) + tarball_tor_path = os.path.join( + working_path, "tor-browser_en-US", "Browser", "TorBrowser" + ) + + # Copy into dist + shutil.copyfile( + os.path.join(tarball_tor_path, "Data", "Tor", "geoip"), + os.path.join(dist_path, "geoip"), + ) + shutil.copyfile( + os.path.join(tarball_tor_path, "Data", "Tor", "geoip6"), + os.path.join(dist_path, "geoip6"), + ) + shutil.copyfile( + os.path.join(tarball_tor_path, "Tor", "tor"), + os.path.join(dist_path, "tor"), + ) + os.chmod(os.path.join(dist_path, "tor"), 0o755) + shutil.copyfile( + os.path.join(tarball_tor_path, "Tor", "libcrypto.so.1.1"), + os.path.join(dist_path, "libcrypto.so.1.1"), + ) + shutil.copyfile( + os.path.join(tarball_tor_path, "Tor", "libevent-2.1.so.7"), + os.path.join(dist_path, "libevent-2.1.so.7"), + ) + shutil.copyfile( + os.path.join(tarball_tor_path, "Tor", "libssl.so.1.1"), + os.path.join(dist_path, "libssl.so.1.1"), + ) + shutil.copyfile( + os.path.join(tarball_tor_path, "Tor", "libstdc++", "libstdc++.so.6"), + os.path.join(dist_path, "libstdc++.so.6"), + ) + shutil.copyfile( + os.path.join(tarball_tor_path, "Tor", "PluggableTransports", "obfs4proxy"), + os.path.join(dist_path, "obfs4proxy"), + ) + os.chmod(os.path.join(dist_path, "obfs4proxy"), 0o755) + shutil.copyfile( + os.path.join( + tarball_tor_path, "Tor", "PluggableTransports", "snowflake-client" + ), + os.path.join(dist_path, "snowflake-client"), + ) + os.chmod(os.path.join(dist_path, "snowflake-client"), 0o755) + + print(f"Tor binaries extracted to: {dist_path}") + + +if __name__ == "__main__": + main() From 343a8cccc13319a8014ad7c12e49a5db52014b54 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 11 Oct 2021 20:44:42 -0700 Subject: [PATCH 02/26] Update dependencies --- cli/poetry.lock | 536 ++++++++++++++++++++++++------------------------ 1 file changed, 263 insertions(+), 273 deletions(-) diff --git a/cli/poetry.lock b/cli/poetry.lock index c51e1d62..9314b096 100644 --- a/cli/poetry.lock +++ b/cli/poetry.lock @@ -1,122 +1,120 @@ [[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.4.0" [[package]] -category = "dev" -description = "Classes Without Boilerplate" name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "21.2.0" [package.extras] -dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] [[package]] -category = "main" -description = "The bidirectional mapping library for Python." name = "bidict" +version = "0.21.3" +description = "The bidirectional mapping library for Python." +category = "main" optional = false python-versions = ">=3.6" -version = "0.21.2" - -[package.extras] -coverage = ["coverage (<6)", "pytest-cov (<3)"] -dev = ["setuptools-scm", "hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)", "coverage (<6)", "pytest-cov (<3)", "pre-commit (<3)", "tox (<4)"] -docs = ["Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] -precommit = ["pre-commit (<3)"] -test = ["hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = "*" -version = "2021.5.30" [[package]] -category = "main" -description = "Foreign Function Interface for Python calling C code." name = "cffi" +version = "1.14.6" +description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" -version = "1.14.6" [package.dependencies] pycparser = "*" [[package]] +name = "charset-normalizer" +version = "2.0.7" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" -description = "Universal encoding detector for Python 2 and 3" -name = "chardet" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "4.0.0" - -[[package]] -category = "main" -description = "Composable command line interface toolkit" -name = "click" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" - -[[package]] -category = "main" -description = "Cross-platform colored terminal text." -name = "colorama" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.4" - -[[package]] -category = "main" -description = "DNS toolkit" -name = "dnspython" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.16.0" +python-versions = ">=3.5.0" [package.extras] -DNSSEC = ["pycryptodome", "ecdsa (>=0.13)"] -IDNA = ["idna (>=2.1)"] +unicode_backport = ["unicodedata2"] [[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" category = "main" -description = "Highly concurrent networking library" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "dnspython" +version = "2.1.0" +description = "DNS toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dnssec = ["cryptography (>=2.6)"] +doh = ["requests", "requests-toolbelt"] +idna = ["idna (>=2.1)"] +curio = ["curio (>=1.2)", "sniffio (>=1.1)"] +trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"] + +[[package]] name = "eventlet" +version = "0.32.0" +description = "Highly concurrent networking library" +category = "main" optional = false python-versions = "*" -version = "0.31.0" [package.dependencies] -dnspython = ">=1.15.0,<2.0.0" +dnspython = ">=1.15.0" greenlet = ">=0.3" six = ">=1.10.0" [[package]] -category = "main" -description = "A simple framework for building complex web applications." name = "flask" +version = "1.1.4" +description = "A simple framework for building complex web applications." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "1.1.4" [package.dependencies] -Jinja2 = ">=2.10.1,<3.0" -Werkzeug = ">=0.15,<2.0" click = ">=5.1,<8.0" itsdangerous = ">=0.24,<2.0" +Jinja2 = ">=2.10.1,<3.0" +Werkzeug = ">=0.15,<2.0" [package.extras] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] @@ -124,79 +122,76 @@ docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx- dotenv = ["python-dotenv"] [[package]] -category = "main" -description = "Socket.IO integration for Flask applications" name = "flask-socketio" +version = "5.0.1" +description = "Socket.IO integration for Flask applications" +category = "main" optional = false python-versions = "*" -version = "5.0.1" [package.dependencies] Flask = ">=0.9" python-socketio = ">=5.0.2" [[package]] -category = "main" -description = "Lightweight in-process concurrent programming" name = "greenlet" +version = "1.1.2" +description = "Lightweight in-process concurrent programming" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "1.1.0" [package.extras] docs = ["sphinx"] [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "3.2" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" +python-versions = ">=3.5" [[package]] -category = "dev" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" name = "importlib-metadata" +version = "4.8.1" +description = "Read metadata from Python packages" +category = "dev" optional = false python-versions = ">=3.6" -version = "4.4.0" [package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" -[package.dependencies.typing-extensions] -python = "<3.8" -version = ">=3.6.4" - [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] -category = "dev" -description = "iniconfig: brain-dead simple config-ini parsing" name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = "*" -version = "1.1.1" [[package]] -category = "main" -description = "Various helpers to pass data to untrusted environments and back." name = "itsdangerous" +version = "1.1.0" +description = "Various helpers to pass data to untrusted environments and back." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.1.0" [[package]] -category = "main" -description = "A very fast and expressive template engine." name = "jinja2" +version = "2.11.3" +description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.11.3" [package.dependencies] MarkupSafe = ">=0.23" @@ -205,74 +200,73 @@ MarkupSafe = ">=0.23" i18n = ["Babel (>=0.8)"] [[package]] -category = "main" -description = "Safely add untrusted strings to HTML/XML markup." name = "markupsafe" +version = "2.0.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=3.6" -version = "2.0.1" [[package]] -category = "dev" -description = "Core utilities for Python packages" name = "packaging" +version = "21.0" +description = "Core utilities for Python packages" +category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.9" +python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2" [[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" +python-versions = ">=3.6" [package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] -category = "main" -description = "Cross-platform lib for process and system monitoring in Python." name = "psutil" +version = "5.8.0" +description = "Cross-platform lib for process and system monitoring in Python." +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "5.8.0" [package.extras] test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] [[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -category = "main" -description = "C parser in Python" name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.20" [[package]] -category = "main" -description = "Python binding to the Networking and Cryptography (NaCl) library" name = "pynacl" +version = "1.4.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.4.0" [package.dependencies] cffi = ">=1.4.1" @@ -280,186 +274,181 @@ six = "*" [package.extras] docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)", "hypothesis (>=3.27.0)"] +tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] [[package]] -category = "dev" -description = "Python parsing module" name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" [[package]] -category = "main" -description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.7.1" [[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.6" -version = "6.2.4" [package.dependencies] -atomicwrites = ">=1.0" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" -colorama = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] -category = "main" -description = "Engine.IO server" name = "python-engineio" +version = "4.2.1" +description = "Engine.IO server and client for Python" +category = "main" optional = false -python-versions = "*" -version = "4.2.0" +python-versions = ">=3.6" [package.extras] asyncio_client = ["aiohttp (>=3.4)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] [[package]] -category = "main" -description = "Socket.IO server" name = "python-socketio" +version = "5.4.0" +description = "Socket.IO server and client for Python" +category = "main" optional = false -python-versions = "*" -version = "5.3.0" +python-versions = ">=3.6" [package.dependencies] bidict = ">=0.21.0" python-engineio = ">=4.1.0" [package.extras] -asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"] +asyncio_client = ["aiohttp (>=3.4)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" +version = "2.26.0" +description = "Python HTTP for Humans." +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.25.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7", optional = true, markers = "extra == \"socks\""} urllib3 = ">=1.21.1,<1.27" -[package.dependencies.PySocks] -optional = true -version = ">=1.5.6,<1.5.7 || >1.5.7" - [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.16.0" [[package]] -category = "main" -description = "" name = "stem" +version = "1.8.1" +description = "Stem is a Python controller library that allows applications to interact with Tor (https://www.torproject.org/)." +category = "main" optional = false python-versions = "*" -version = "1.8.1" +develop = false [package.source] -reference = "de3d03a03c7ee57c74c80e9c63cb88072d833717" type = "git" url = "https://github.com/onionshare/stem.git" +reference = "1.8.1" +resolved_reference = "de3d03a03c7ee57c74c80e9c63cb88072d833717" [[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.10.2" [[package]] -category = "dev" -description = "Backported and Experimental Type Hints for Python 3.5+" -marker = "python_version < \"3.8\"" name = "typing-extensions" +version = "3.10.0.2" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" optional = false python-versions = "*" -version = "3.10.0.0" [[package]] -category = "main" -description = "ASCII transliterations of Unicode text" name = "unidecode" +version = "1.3.2" +description = "ASCII transliterations of Unicode text" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.2.0" +python-versions = ">=3.5" [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" +version = "1.26.7" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.26.5" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -category = "main" -description = "The comprehensive WSGI web application library." name = "werkzeug" +version = "1.0.1" +description = "The comprehensive WSGI web application library." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "1.0.1" [package.extras] dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] watchdog = ["watchdog"] [[package]] -category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" name = "zipp" +version = "3.6.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" optional = false python-versions = ">=3.6" -version = "3.4.1" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] -content-hash = "181891640e59dac730905019444d42ef8e99da0c34c96fb8a616781661bae537" +lock-version = "1.1" python-versions = "^3.6" +content-hash = "181891640e59dac730905019444d42ef8e99da0c34c96fb8a616781661bae537" [metadata.files] atomicwrites = [ @@ -471,12 +460,12 @@ attrs = [ {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] bidict = [ - {file = "bidict-0.21.2-py2.py3-none-any.whl", hash = "sha256:929d056e8d0d9b17ceda20ba5b24ac388e2a4d39802b87f9f4d3f45ecba070bf"}, - {file = "bidict-0.21.2.tar.gz", hash = "sha256:4fa46f7ff96dc244abfc437383d987404ae861df797e2fd5b190e233c302be09"}, + {file = "bidict-0.21.3-py3-none-any.whl", hash = "sha256:2cce0d01eb3db9b3fa85db501c00aaa3389ee4cab7ef82178604552dfa943a1b"}, + {file = "bidict-0.21.3.tar.gz", hash = "sha256:d50bd81fae75e34198ffc94979a0eb0939ff9adb3ef32bcc93a913d8b3e3ed1d"}, ] certifi = [ - {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, - {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] cffi = [ {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, @@ -525,9 +514,9 @@ cffi = [ {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +charset-normalizer = [ + {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, + {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -538,12 +527,12 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] dnspython = [ - {file = "dnspython-1.16.0-py2.py3-none-any.whl", hash = "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"}, - {file = "dnspython-1.16.0.zip", hash = "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01"}, + {file = "dnspython-2.1.0-py3-none-any.whl", hash = "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216"}, + {file = "dnspython-2.1.0.zip", hash = "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"}, ] eventlet = [ - {file = "eventlet-0.31.0-py2.py3-none-any.whl", hash = "sha256:27ae41fad9deed9bbf4166f3e3b65acc15d524d42210a518e5877da85a6b8c5d"}, - {file = "eventlet-0.31.0.tar.gz", hash = "sha256:b36ec2ecc003de87fc87b93197d77fea528aa0f9204a34fdf3b2f8d0f01e017b"}, + {file = "eventlet-0.32.0-py2.py3-none-any.whl", hash = "sha256:a3a67b02f336e97a1894b277bc33b695831525758781eb024f4da00e75ce5e25"}, + {file = "eventlet-0.32.0.tar.gz", hash = "sha256:2f0bb8ed0dc0ab21d683975d5d8ab3c054d588ce61def9faf7a465ee363e839b"}, ] flask = [ {file = "Flask-1.1.4-py2.py3-none-any.whl", hash = "sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22"}, @@ -554,63 +543,64 @@ flask-socketio = [ {file = "Flask_SocketIO-5.0.1-py2.py3-none-any.whl", hash = "sha256:5d9a4438bafd806c5a3b832e74b69758781a8ee26fb6c9b1dbdda9b4fced432e"}, ] greenlet = [ - {file = "greenlet-1.1.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c"}, - {file = "greenlet-1.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3"}, - {file = "greenlet-1.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922"}, - {file = "greenlet-1.1.0-cp27-cp27m-win32.whl", hash = "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821"}, - {file = "greenlet-1.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6"}, - {file = "greenlet-1.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f"}, - {file = "greenlet-1.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56"}, - {file = "greenlet-1.1.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16"}, - {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a"}, - {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831"}, - {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22"}, - {file = "greenlet-1.1.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5"}, - {file = "greenlet-1.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47"}, - {file = "greenlet-1.1.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08"}, - {file = "greenlet-1.1.0-cp35-cp35m-win32.whl", hash = "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131"}, - {file = "greenlet-1.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5"}, - {file = "greenlet-1.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc"}, - {file = "greenlet-1.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e"}, - {file = "greenlet-1.1.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc"}, - {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3"}, - {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919"}, - {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e"}, - {file = "greenlet-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8"}, - {file = "greenlet-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb"}, - {file = "greenlet-1.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857"}, - {file = "greenlet-1.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea"}, - {file = "greenlet-1.1.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed"}, - {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c"}, - {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319"}, - {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05"}, - {file = "greenlet-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f"}, - {file = "greenlet-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a"}, - {file = "greenlet-1.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5"}, - {file = "greenlet-1.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505"}, - {file = "greenlet-1.1.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae"}, - {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927"}, - {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99"}, - {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da"}, - {file = "greenlet-1.1.0-cp38-cp38-win32.whl", hash = "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad"}, - {file = "greenlet-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8"}, - {file = "greenlet-1.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e"}, - {file = "greenlet-1.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c"}, - {file = "greenlet-1.1.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e"}, - {file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959"}, - {file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f"}, - {file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832"}, - {file = "greenlet-1.1.0-cp39-cp39-win32.whl", hash = "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11"}, - {file = "greenlet-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535"}, - {file = "greenlet-1.1.0.tar.gz", hash = "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee"}, + {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"}, + {file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"}, + {file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"}, + {file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"}, + {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"}, + {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"}, + {file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"}, + {file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"}, + {file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"}, + {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"}, + {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"}, + {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"}, + {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"}, + {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"}, + {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"}, + {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"}, + {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"}, + {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"}, + {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"}, + {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, + {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, + {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.4.0-py3-none-any.whl", hash = "sha256:960d52ba7c21377c990412aca380bf3642d734c2eaab78a2c39319f67c6a5786"}, - {file = "importlib_metadata-4.4.0.tar.gz", hash = "sha256:e592faad8de1bda9fe920cf41e15261e7131bcf266c30306eec00e8e225c1dd5"}, + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -681,12 +671,12 @@ markupsafe = [ {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] psutil = [ {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, @@ -756,20 +746,20 @@ pysocks = [ {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, ] pytest = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] python-engineio = [ - {file = "python-engineio-4.2.0.tar.gz", hash = "sha256:4e97c1189c23923858f5bb6dc47cfcd915005383c3c039ff01c89f2c00d62077"}, - {file = "python_engineio-4.2.0-py2.py3-none-any.whl", hash = "sha256:c6c119c2039fcb6f64d260211ca92c0c61b2b888a28678732a961f2aaebcc848"}, + {file = "python-engineio-4.2.1.tar.gz", hash = "sha256:d510329b6d8ed5662547862f58bc73659ae62defa66b66d745ba021de112fa62"}, + {file = "python_engineio-4.2.1-py3-none-any.whl", hash = "sha256:f3ef9a2c048d08990f294c5f8991f6f162c3b12ecbd368baa0d90441de907d1c"}, ] python-socketio = [ - {file = "python-socketio-5.3.0.tar.gz", hash = "sha256:3dcc9785aaeef3a9eeb36c3818095662342744bdcdabd050fe697cdb826a1c2b"}, - {file = "python_socketio-5.3.0-py2.py3-none-any.whl", hash = "sha256:d74314fd4241342c8a55c4f66d5cfea8f1a8fffd157af216c67e1c3a649a2444"}, + {file = "python-socketio-5.4.0.tar.gz", hash = "sha256:ca807c9e1f168e96dea412d64dd834fb47c470d27fd83da0504aa4b248ba2544"}, + {file = "python_socketio-5.4.0-py3-none-any.whl", hash = "sha256:7ed57f6c024abdfeb9b25c74c0c00ffc18da47d903e8d72deecb87584370d1fc"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -781,23 +771,23 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] unidecode = [ - {file = "Unidecode-1.2.0-py2.py3-none-any.whl", hash = "sha256:12435ef2fc4cdfd9cf1035a1db7e98b6b047fe591892e81f34e94959591fad00"}, - {file = "Unidecode-1.2.0.tar.gz", hash = "sha256:8d73a97d387a956922344f6b74243c2c6771594659778744b2dbdaad8f6b727d"}, + {file = "Unidecode-1.3.2-py3-none-any.whl", hash = "sha256:215fe33c9d1c889fa823ccb66df91b02524eb8cc8c9c80f9c5b8129754d27829"}, + {file = "Unidecode-1.3.2.tar.gz", hash = "sha256:669898c1528912bcf07f9819dc60df18d057f7528271e31f8ec28cc88ef27504"}, ] urllib3 = [ - {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, - {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, ] werkzeug = [ {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, ] zipp = [ - {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, - {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, ] From 229da0aaab94ee84e8a0daf6d23251279a639f32 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 11 Oct 2021 20:45:28 -0700 Subject: [PATCH 03/26] Make get_tor_paths work properly now that in linux the tor binaries are bundled too --- cli/onionshare_cli/common.py | 53 ++++++++++++++++++++-------- cli/onionshare_cli/onion.py | 21 +++++++---- desktop/scripts/get-tor-linux.py | 7 ++-- desktop/scripts/rebuild-cli.py | 1 + desktop/src/onionshare/gui_common.py | 23 ++++++------ 5 files changed, 72 insertions(+), 33 deletions(-) diff --git a/cli/onionshare_cli/common.py b/cli/onionshare_cli/common.py index dd92eb0b..78da8882 100644 --- a/cli/onionshare_cli/common.py +++ b/cli/onionshare_cli/common.py @@ -309,38 +309,63 @@ class Common: def get_tor_paths(self): if self.platform == "Linux": - tor_path = shutil.which("tor") - if not tor_path: - raise CannotFindTor() - obfs4proxy_file_path = shutil.which("obfs4proxy") - prefix = os.path.dirname(os.path.dirname(tor_path)) - tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") - tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6") + # Look in resources first + base_path = self.get_resource_path("tor") + if os.path.exists(base_path): + tor_path = os.path.join(base_path, "tor") + tor_geo_ip_file_path = os.path.join(base_path, "geoip") + tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6") + obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy") + snowflake_file_path = os.path.join(base_path, "snowflake-client") + else: + # Fallback to looking in the path + tor_path = shutil.which("tor") + if not tor_path: + raise CannotFindTor() + obfs4proxy_file_path = shutil.which("obfs4proxy") + snowflake_file_path = shutil.which("snowflake-client") + prefix = os.path.dirname(os.path.dirname(tor_path)) + tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") + tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6") elif self.platform == "Windows": base_path = self.get_resource_path("tor") tor_path = os.path.join(base_path, "Tor", "tor.exe") obfs4proxy_file_path = os.path.join(base_path, "Tor", "obfs4proxy.exe") + snowflake_file_path = os.path.join(base_path, "Tor", "snowflake-client.exe") tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip") tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6") elif self.platform == "Darwin": - tor_path = shutil.which("tor") - if not tor_path: - raise CannotFindTor() - obfs4proxy_file_path = shutil.which("obfs4proxy") - prefix = os.path.dirname(os.path.dirname(tor_path)) - tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") - tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6") + # Look in resources first + base_path = self.get_resource_path("tor") + if os.path.exists(base_path): + tor_path = os.path.join(base_path, "tor") + tor_geo_ip_file_path = os.path.join(base_path, "geoip") + tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6") + obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy") + snowflake_file_path = os.path.join(base_path, "snowflake-client") + else: + # Fallback to looking in the path + tor_path = shutil.which("tor") + if not tor_path: + raise CannotFindTor() + obfs4proxy_file_path = shutil.which("obfs4proxy") + snowflake_file_path = shutil.which("snowflake-client") + prefix = os.path.dirname(os.path.dirname(tor_path)) + tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") + tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6") elif self.platform == "BSD": tor_path = "/usr/local/bin/tor" tor_geo_ip_file_path = "/usr/local/share/tor/geoip" tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6" obfs4proxy_file_path = "/usr/local/bin/obfs4proxy" + snowflake_file_path = "/usr/local/bin/snowflake-client" return ( tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path, + snowflake_file_path, ) def build_data_dir(self): diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index 7f6faa17..a0f967b9 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -153,6 +153,7 @@ class Onion(object): self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, + self.snowflake_file_path, ) = get_tor_paths() # The tor process @@ -178,10 +179,10 @@ class Onion(object): key_bytes = bytes(key) key_b32 = base64.b32encode(key_bytes) # strip trailing ==== - assert key_b32[-4:] == b'====' + assert key_b32[-4:] == b"====" key_b32 = key_b32[:-4] # change from b'ASDF' to ASDF - s = key_b32.decode('utf-8') + s = key_b32.decode("utf-8") return s def connect( @@ -650,16 +651,24 @@ class Onion(object): ) raise TorTooOldStealth() else: - if key_type == "NEW" or not mode_settings.get("onion", "client_auth_priv_key"): + if key_type == "NEW" or not mode_settings.get( + "onion", "client_auth_priv_key" + ): # Generate a new key pair for Client Auth on new onions, or if # it's a persistent onion but for some reason we don't them client_auth_priv_key_raw = nacl.public.PrivateKey.generate() client_auth_priv_key = self.key_str(client_auth_priv_key_raw) - client_auth_pub_key = self.key_str(client_auth_priv_key_raw.public_key) + client_auth_pub_key = self.key_str( + client_auth_priv_key_raw.public_key + ) else: # These should have been saved in settings from the previous run of a persistent onion - client_auth_priv_key = mode_settings.get("onion", "client_auth_priv_key") - client_auth_pub_key = mode_settings.get("onion", "client_auth_pub_key") + client_auth_priv_key = mode_settings.get( + "onion", "client_auth_priv_key" + ) + client_auth_pub_key = mode_settings.get( + "onion", "client_auth_pub_key" + ) try: if not self.supports_stealth: diff --git a/desktop/scripts/get-tor-linux.py b/desktop/scripts/get-tor-linux.py index 4bd9ff13..e47ae03d 100755 --- a/desktop/scripts/get-tor-linux.py +++ b/desktop/scripts/get-tor-linux.py @@ -46,9 +46,12 @@ def main(): ) working_path = os.path.join(root_path, "build", "tor") tarball_path = os.path.join(working_path, tarball_filename) + dist_path = os.path.join(root_path, "src", "onionshare", "resources", "tor") + + # Make sure dirs exist + if not os.path.exists(working_path): + os.makedirs(working_path, exist_ok=True) - # Make sure the dist path exists - dist_path = os.path.join(working_path, "dist") if not os.path.exists(dist_path): os.makedirs(dist_path, exist_ok=True) diff --git a/desktop/scripts/rebuild-cli.py b/desktop/scripts/rebuild-cli.py index 66582cf1..f9a43554 100755 --- a/desktop/scripts/rebuild-cli.py +++ b/desktop/scripts/rebuild-cli.py @@ -38,6 +38,7 @@ def main(): # Reinstall the new wheel subprocess.call(["pip", "uninstall", "onionshare-cli", "-y"]) subprocess.call(["pip", "install", os.path.join(desktop_path, wheel_basename)]) + subprocess.call(["pip", "install", "typing-extensions"]) if __name__ == "__main__": diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index 182d63f2..1dffab26 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -205,14 +205,14 @@ class GuiCommon: "downloads_uploads_not_empty": """ QWidget{ background-color: """ - + history_background_color - +"""; + + history_background_color + + """; }""", "downloads_uploads_empty": """ QWidget { background-color: """ - + history_background_color - +"""; + + history_background_color + + """; border: 1px solid #999999; } QWidget QLabel { @@ -263,7 +263,7 @@ class GuiCommon: + """; width: 10px; }""", - "history_default_label" : """ + "history_default_label": """ QLabel { color: """ + history_label_color @@ -415,21 +415,20 @@ class GuiCommon: def get_tor_paths(self): if self.common.platform == "Linux": - tor_path = shutil.which("tor") - obfs4proxy_file_path = shutil.which("obfs4proxy") - prefix = os.path.dirname(os.path.dirname(tor_path)) - tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") - tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6") - elif self.common.platform == "Windows": + return self.common.get_tor_paths() + + if self.common.platform == "Windows": base_path = self.get_resource_path("tor") tor_path = os.path.join(base_path, "Tor", "tor.exe") obfs4proxy_file_path = os.path.join(base_path, "Tor", "obfs4proxy.exe") + snowflake_file_path = os.path.join(base_path, "Tor", "snowflake-client.exe") tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip") tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6") elif self.common.platform == "Darwin": base_path = self.get_resource_path("tor") tor_path = os.path.join(base_path, "tor") obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy") + snowflake_file_path = os.path.join(base_path, "snowflake-client") tor_geo_ip_file_path = os.path.join(base_path, "geoip") tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6") elif self.common.platform == "BSD": @@ -437,12 +436,14 @@ class GuiCommon: tor_geo_ip_file_path = "/usr/local/share/tor/geoip" tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6" obfs4proxy_file_path = "/usr/local/bin/obfs4proxy" + snowflake_file_path = "/usr/local/bin/snowflake-client" return ( tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path, + snowflake_file_path, ) @staticmethod From 76659bdc6e8c54a014287a1bdb9f0716fea6203f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 12 Oct 2021 21:09:58 -0700 Subject: [PATCH 04/26] Start splitting settings into normal settings and Tor settings --- desktop/src/onionshare/main_window.py | 29 + .../resources/images/dark_tor_settings.png | Bin 0 -> 3600 bytes .../resources/images/light_tor_settings.png | Bin 0 -> 1503 bytes .../src/onionshare/resources/locale/en.json | 1 + desktop/src/onionshare/settings_dialog.py | 28 +- desktop/src/onionshare/tor_settings_dialog.py | 1112 +++++++++++++++++ 6 files changed, 1154 insertions(+), 16 deletions(-) create mode 100644 desktop/src/onionshare/resources/images/dark_tor_settings.png create mode 100644 desktop/src/onionshare/resources/images/light_tor_settings.png create mode 100644 desktop/src/onionshare/tor_settings_dialog.py diff --git a/desktop/src/onionshare/main_window.py b/desktop/src/onionshare/main_window.py index d87092b6..0e7e3f88 100644 --- a/desktop/src/onionshare/main_window.py +++ b/desktop/src/onionshare/main_window.py @@ -18,11 +18,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +import os import time from PySide2 import QtCore, QtWidgets, QtGui from . import strings from .tor_connection_dialog import TorConnectionDialog +from .tor_settings_dialog import TorSettingsDialog from .settings_dialog import SettingsDialog from .widgets import Alert from .update_checker import UpdateThread @@ -106,6 +108,24 @@ class MainWindow(QtWidgets.QMainWindow): ) self.status_bar.addPermanentWidget(self.status_bar.server_status_indicator) + # Tor settings button + self.tor_settings_button = QtWidgets.QPushButton() + self.tor_settings_button.setDefault(False) + self.tor_settings_button.setFixedSize(40, 50) + self.tor_settings_button.setIcon( + QtGui.QIcon( + GuiCommon.get_resource_path( + "images/{}_tor_settings.png".format(self.common.gui.color_mode) + ) + ) + ) + self.tor_settings_button.clicked.connect(self.open_tor_settings) + self.tor_settings_button.setStyleSheet(self.common.gui.css["settings_button"]) + self.status_bar.addPermanentWidget(self.tor_settings_button) + + if os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1": + self.tor_settings_button.hide() + # Settings button self.settings_button = QtWidgets.QPushButton() self.settings_button.setDefault(False) @@ -223,6 +243,15 @@ class MainWindow(QtWidgets.QMainWindow): # Wait 1ms for the event loop to finish closing the TorConnectionDialog QtCore.QTimer.singleShot(1, self.open_settings) + def open_tor_settings(self): + """ + Open the TorSettingsDialog. + """ + self.common.log("MainWindow", "open_tor_settings") + d = TorSettingsDialog(self.common) + d.settings_saved.connect(self.settings_have_changed) + d.exec_() + def open_settings(self): """ Open the SettingsDialog. diff --git a/desktop/src/onionshare/resources/images/dark_tor_settings.png b/desktop/src/onionshare/resources/images/dark_tor_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..0b44bd95e6ddd5b7075c585787c028efff9f3742 GIT binary patch literal 3600 zcmV+r4)5`aP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O?T!mK(bbh5vIESpsK}%i(xVc97-g151*n9{gl? z(z3)-RjCNx3tRxSS^xR>Hvi%;l$cFSQgY4N@)v8YzHw0O^;gfQv+;ahU*heWdp&O+ z7d)o|W4OLbyWQV7pML$|K8NeC=S{g?@#Xd)_jvI63p%qNuV*7U@2~TBLrlHhkZX}= zQ~T{f<8E>J^^Om-u0g+>mlKi)culbqa`G7HSMa=#1wr2~G~V`ndhT$caGZiO3_jc~ z0g#vF-A8Mm0eS)Xeq=tP|2X;pzHhhl;RBZWh7ku}UU2Ed`S6&=KTjMU7Wvl;W83*@ zIk)%Tv+TXD)wP)MyL|Jg1Gc+Ah8sJOhx4<{OL!-a<-97dVw)XMKJBob*KpN&(BC+u zo36R-))|Kzm>B)?!sz{gC_Pt#`07Vco}#|o_7ZBCu;#PxWdfS zajSPc48p&EncrRflb6|hC`8Vd2UghS)vqx_nbS}1f)KZFyrl#1{dJ??KKYM2bL2LK zxv;=y`}K&P+%Mk>hv&e3iSY^{XJprIeGTVp2^l zha7XtIZIZNYZ4?%k}M)cs801+2JSQBNFxs!Wz^B8pJB$CCPFdGtg|g=7AmpgN-Hl}W!2R-zP7`TJMFw> zmtA*zuy$kh)9Vjdb2rxfVM_0l2Wy-)BKJo)m6N2L!I+N@#zh%W1yjy!aWQ&jPC2vH z6BNl~WKwR-bjlbM#^rq4?!nzJ=HBAXr1BPT{-2mLO5Oj0IRkZ{dHaI3sd;W3#;zz- zOl=_gxV}m=zBEm0?XSn);cSn%aWXH0P?S!!A|N`u&`-tY$_kp?AD!#_Y-nOt1jQ3r2^sIRhs? zkskIuFm(MalR`u%bTMfek({|7{B->IAn8^X2=DEa-Td3#rdZXAh7LC6NG(c)lig}< z0;5r<=|wNBP*W$h^<4y9xQS&4Ze9hCkXij|u@oF!@6M&>IGRIRl>r}GD`t;Ys8Y(s z1@k_}KAiH5j6{%@5@isM2tsqK^__{K8sd&9WLU#&$B_h2u~bKI znb{5nn@XY7or&gx%Aj)Mm(6LmdNBUL?AheIB*n^#_Nn8B# zhDxx3lhjEx)C ziw@kz2RsDLGeGBeuy%gfml{AkY@I%FxgW+Y)0dYp|7Hp1$Kc#BFzK>EAvgdW8$Ltu zjKG)cH=h9b;2)QJysrL&Kg|n%Iq;je0C;0G-(BdPY#Ej#Oh+G4&@)FkQfvj8&8v#d zl?K|~N(z$AB*)PC(-_kZl@$}hcP|XSt_z~2)$Zpo{;$h79op70k4qqCur>lM}>TCWrzSwAk| z-HQt}vMCPYzlxk|G~ZqLZVanl`6a`ORUfjX+_B9*4t5EsYg9?PXc_LZ#8EAA3LEk0 zkz1#dI$?2Le>@FgyFE~Sl#g?n=aFj^sggm4{usD=oOTVb)FEo>-7a;{~WMbykb3TQm6-w7hWlm2+!b}bq7zgslnpJ-uYqq!I!hBfx z7cjnuqWJ`4`Mbb1`rCGJS3@Xtr8WxcSyGNyotvB%gh|EfH~G(eOn`qaBO26K)vb|^ zIhRsn5OZS=5ZufG?&%KfYEnFysZsm@lT5lw_&=~<4V9@?nDZXp1h$F9cvhRbquN=7 zq@Qb4YWU&#NlizktjgNHx&!JlWFj0VMyW386_XXdvb1nHw{^;Wf;Nm9VU@_H2S9zw zp|v}0Vz37_z~P%vF;L{rx+e}fb!TdfQE9aX6|UC_yy{rN1g{?d6V3ir#=JvD4rUjY z;U_9sdZ%Or7bHC(T@yk)ZEs?kr%==`6by=XTziQMp&fYHX)DOIgPxqu;kXvM2PAP# zwZo0O1m}w)2{!?b@RTnn*7_K?q&!xVR5hb7)L8il1x8lw@xpp5U2C>B|BH03qj_Is zzaMo{^OJo4obJs#0K8MPnO6w@p=SHdXx;|l+oJD6!Th}FyD%`97kujkgxf-dO7&M> z3UqCv)eP;0hUt`{`v4kibQ=FX(7k8a&k2OZfV^{b_Ul`D1^F3Ijm)EL-U16 zqn(*FZ&&_|#5=CFitBBWpmr&TO%n6zu5>%V(~TlOtkpE_;4(EA&uEiYG6N>Izl9TdMi0{DH2jr68p9KCdfqxAVEhA9f zei?IY+Zc200bovlnA1VMdqVjFUo-m~#{EWWucS;_ zB{;BtX-*x<$98d6)LF*q+e@K?Brj<#81f*c#W0ho-Xyo0ts!`sb%D12pFy<_M@lEM zDHHl|>qi|V1r^nTCSSF1+8|U3njrgjLDS)EsK?@Xq)Hw>8wrhW-*0%2Gvp%*UOMTz zCl;1Uw-VEElf{_8W5etdAc_hu!@oDq%X*Az&F1mHw9QELV zz=dmbR5yla%e4osXkq&73`goCRf2>el1tWugQ_-bh4R(NrS-zYc=kZx;auZ2$lPk7+|gP)S2WAaHVTW@&6?004NL zeUZCM!%!5)zotq{X>rg&q(g@4AQp;(2)bCsB2);qf>j6e=tD@-kfgXc3a$kQ(Z$!G ze}L%f;3^1$B8a&75BOT7#B-BCi?klN++V(&^KtKY0q$ItHLEKHXu56YGHHIfu)>Gl z5J3z9q%bMUmN6{|S#+%L2kNA{PcpC0eQhhI6fGGD@WgYnVcNtM;_*$}V0=W}Cs#y; z_=0%EqyrMaGF|fclX2N)F3i}>=VplG#C*Ahr52VerbawPoKQ5K{JC|H6~^0)HMJ)D z=VUjG7WJh?+J|Yyk%5X0XfR-*2p$q@^sT~1l6IXG3pwgaQ?~7W23#M3<|NlkrkPn( z$y0P4z~16-##U);)N_iGbN7qmd<+7iU7%5SoS$RIX`BGTXW&|I&Q~eNi7(RYO)YT< z^w2#oZ_)v)Gc~P4INELZp?1pt z$E4f^`gLrVxhGS{R4nhk`7G3CjO@j7K7&G19}H(!CTknVw|ZKXa%_s};mo11S)mM_ zBH?=l;kxXOZxJ{A3Q2jOZUcqG&@Nu8n*z}$PnHxF9JmICKo4jDufP&`Op@g1zqw!J W6R9P4kz4@)0000 zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=KFlH4E+hTl0wj({YD#Bnf7Rc?^u=f~T0&rG&T zo>FZJgM}=~`efW4#<#CCe8E8_b4Y5QOU@BTDyeYA#N%<4{Y)|K=i?%sYxFD+_X0yC z80EOMdg@om_3ehs2W`*t;O7eaX^3va`ytEonvcgIA?HJW1ro}skc-=6sM~3%T@m|R zPj^_yu&%?Cz;Z*rxLe9C?q`MuiDRxp0fWf(tVqby<6UEnj|qB8 z@-++ZvwzRtC79tDWPhR{sZ*s%V;>tegqV1+VPtLq zwPY^Fg_|r+X*HT`Qlx_#OXCs1ED7JS(C)YF{u((vcY-Pti~;zMTlm@HpR_P%TPdRG zcdif@uewGUi`+Ux3qWX|+;mUy)lU5Jp?+2|C#bD7J07sc?J%(^z15cNoCSJ{@py$O zSU(LQMewZ%h6Ds+CQ-^1joFBhM+cya$XOC^AV8|z5hN!O>_^7V%Qx;Z+I3ku^Aclk z0tl5X1~x@1V5Ov}A4`rJsw$dPHLIxyEn2hWlr`sUd2Mpl#FD9HGjl6eT|BvZc60aQ zwQv#afm(90;-!>YIaD~RuvMX7A=!A7Ep58x%{Jf4RvXHvrKT-6Yu-w$oxAkZwPT~` z-b=58!oV&u($JBI4IgFHiCUX7)6|)#O`m1fo7zeBL;C}2bW-D;)Y7w?8l=JOGC^xQ z(ZvkJI1z~3BCvL_#Vk6d#EaZw77MS9j1i!h3A8@b=&7Sw->8~=)2 zSm^!*xd3z@xqU*dukT#ji5+j@%4rn5eOMn}1AAQ`wfeW?%h1cv%h1cv%h1cv%g}#d zXyV5M{=|mw1}&YvuEX>4Tx0C=2zk-JO7P!z_$rb&^`R0y?#RR{CvLrBt)q_{W=t_26t#n+&JfavPrDhPrih`9I< z_*$gIbCW=ev>v$JU%s64aqo8l?p&2Mt1AU)x^3n%X@0q|!iU}vK@0(;Fe%EGF)avL zbgb_O>ZH0)GOx~kZ7Ze}Eg1;##B;J?+Qb#&@lD%cd_>$QS44&Qf_TKF0}{V7UGn&o zaoJ@q%-GE5W{Bg&e7S|C7M3ffMm$BFP&A$Vxpj{f#@mcFwI=)LWH*c!^`%AHhiS!; zfr<@iFkqnw9ujKwt-?lu~R|pWi$dApG0o zC-@6aAavOrz0eW>000JJOGiWi000000Qp0^e*gdg32;bRa{vGf6951U69E94oEQKA z00(qQO+^Rg3Jn1>B>OE3I{*Lx-AP12R5;76(lJT{K@`u(d?teZ)XzA. """ from PySide2 import QtCore, QtWidgets, QtGui -from PySide2.QtCore import Slot,Qt +from PySide2.QtCore import Slot, Qt from PySide2.QtGui import QPalette, QColor import sys import platform @@ -46,8 +46,7 @@ from onionshare_cli.onion import ( from . import strings from .widgets import Alert -from .update_checker import ( - UpdateThread) +from .update_checker import UpdateThread from .tor_connection_dialog import TorConnectionDialog from .gui_common import GuiCommon @@ -125,13 +124,13 @@ class SettingsDialog(QtWidgets.QDialog): language_layout.addWidget(self.language_combobox) language_layout.addStretch() - #Theme Settings + # Theme Settings theme_label = QtWidgets.QLabel(strings._("gui_settings_theme_label")) self.theme_combobox = QtWidgets.QComboBox() theme_choices = [ strings._("gui_settings_theme_auto"), strings._("gui_settings_theme_light"), - strings._("gui_settings_theme_dark") + strings._("gui_settings_theme_dark"), ] self.theme_combobox.addItems(theme_choices) theme_layout = QtWidgets.QHBoxLayout() @@ -165,14 +164,16 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_no_bridges_radio_toggled ) - # obfs4 option radio - # if the obfs4proxy binary is missing, we can't use obfs4 transports ( self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, + self.snowflake_file_path, ) = self.common.gui.get_tor_paths() + + # obfs4 option radio + # if the obfs4proxy binary is missing, we can't use obfs4 transports if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path ): @@ -190,12 +191,6 @@ class SettingsDialog(QtWidgets.QDialog): # meek_lite-azure option radio # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports - ( - self.tor_path, - self.tor_geo_ip_file_path, - self.tor_geo_ipv6_file_path, - self.obfs4proxy_file_path, - ) = self.common.gui.get_tor_paths() if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path ): @@ -802,7 +797,9 @@ class SettingsDialog(QtWidgets.QDialog): ) close_forced_update_thread() - forced_update_thread = UpdateThread(self.common, self.common.gui.onion, force=True) + forced_update_thread = UpdateThread( + self.common, self.common.gui.onion, force=True + ) forced_update_thread.update_available.connect(update_available) forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_error.connect(update_error) @@ -843,7 +840,6 @@ class SettingsDialog(QtWidgets.QDialog): notice = strings._("gui_settings_language_changed_notice") Alert(self.common, notice, QtWidgets.QMessageBox.Information) - # If color mode changed, inform user they need to restart OnionShare if changed(settings, self.old_settings, ["theme"]): notice = strings._("gui_color_mode_changed_notice") @@ -960,7 +956,7 @@ class SettingsDialog(QtWidgets.QDialog): # Theme theme_index = self.theme_combobox.currentIndex() - settings.set("theme",theme_index) + settings.set("theme", theme_index) # Language locale_index = self.language_combobox.currentIndex() diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py new file mode 100644 index 00000000..1ff46b3e --- /dev/null +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -0,0 +1,1112 @@ +# -*- 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 +from PySide2.QtCore import Slot, Qt +from PySide2.QtGui import QPalette, QColor +import sys +import platform +import datetime +import re +import os +from onionshare_cli.settings import Settings +from onionshare_cli.onion import ( + Onion, + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, +) + +from . import strings +from .widgets import Alert +from .update_checker import UpdateThread +from .tor_connection_dialog import TorConnectionDialog +from .gui_common import GuiCommon + + +class TorSettingsDialog(QtWidgets.QDialog): + """ + Settings dialog. + """ + + settings_saved = QtCore.Signal() + + def __init__(self, common): + super(TorSettingsDialog, self).__init__() + + self.common = common + + self.common.log("TorSettingsDialog", "__init__") + + self.setModal(True) + self.setWindowTitle(strings._("gui_tor_settings_window_title")) + self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))) + + self.system = platform.system() + + # If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog + self.hide_tor_settings = os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1" + + # Automatic updates options + + # Autoupdate + self.autoupdate_checkbox = QtWidgets.QCheckBox() + self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option")) + + # Last update time + self.autoupdate_timestamp = QtWidgets.QLabel() + + # Check for updates button + self.check_for_updates_button = QtWidgets.QPushButton( + strings._("gui_settings_autoupdate_check_button") + ) + self.check_for_updates_button.clicked.connect(self.check_for_updates) + # We can't check for updates if not connected to Tor + if not self.common.gui.onion.connected_to_tor: + self.check_for_updates_button.setEnabled(False) + + # Autoupdate options layout + autoupdate_group_layout = QtWidgets.QVBoxLayout() + autoupdate_group_layout.addWidget(self.autoupdate_checkbox) + autoupdate_group_layout.addWidget(self.autoupdate_timestamp) + autoupdate_group_layout.addWidget(self.check_for_updates_button) + autoupdate_group = QtWidgets.QGroupBox( + strings._("gui_settings_autoupdate_label") + ) + autoupdate_group.setLayout(autoupdate_group_layout) + + # Autoupdate is only available for Windows and Mac (Linux updates using package manager) + if self.system != "Windows" and self.system != "Darwin": + autoupdate_group.hide() + + # Language settings + language_label = QtWidgets.QLabel(strings._("gui_settings_language_label")) + self.language_combobox = QtWidgets.QComboBox() + # Populate the dropdown with all of OnionShare's available languages + language_names_to_locales = { + v: k for k, v in self.common.settings.available_locales.items() + } + language_names = list(language_names_to_locales) + language_names.sort() + for language_name in language_names: + locale = language_names_to_locales[language_name] + self.language_combobox.addItem(language_name, locale) + language_layout = QtWidgets.QHBoxLayout() + language_layout.addWidget(language_label) + language_layout.addWidget(self.language_combobox) + language_layout.addStretch() + + # Theme Settings + theme_label = QtWidgets.QLabel(strings._("gui_settings_theme_label")) + self.theme_combobox = QtWidgets.QComboBox() + theme_choices = [ + strings._("gui_settings_theme_auto"), + strings._("gui_settings_theme_light"), + strings._("gui_settings_theme_dark"), + ] + self.theme_combobox.addItems(theme_choices) + theme_layout = QtWidgets.QHBoxLayout() + theme_layout.addWidget(theme_label) + theme_layout.addWidget(self.theme_combobox) + theme_layout.addStretch() + + # Connection type: either automatic, control port, or socket file + + # Bundled Tor + self.connection_type_bundled_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_bundled_option") + ) + self.connection_type_bundled_radio.toggled.connect( + self.connection_type_bundled_toggled + ) + + # Bundled Tor doesn't work on dev mode in Windows or Mac + if (self.system == "Windows" or self.system == "Darwin") and getattr( + sys, "onionshare_dev_mode", False + ): + self.connection_type_bundled_radio.setEnabled(False) + + # Bridge options for bundled tor + + # No bridges option radio + self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_no_bridges_radio_option") + ) + self.tor_bridges_no_bridges_radio.toggled.connect( + self.tor_bridges_no_bridges_radio_toggled + ) + + ( + self.tor_path, + self.tor_geo_ip_file_path, + self.tor_geo_ipv6_file_path, + self.obfs4proxy_file_path, + self.snowflake_file_path, + ) = self.common.gui.get_tor_paths() + + # obfs4 option radio + # if the obfs4proxy binary is missing, we can't use obfs4 transports + if not self.obfs4proxy_file_path or not os.path.isfile( + self.obfs4proxy_file_path + ): + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy") + ) + self.tor_bridges_use_obfs4_radio.setEnabled(False) + else: + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_obfs4_radio_option") + ) + self.tor_bridges_use_obfs4_radio.toggled.connect( + self.tor_bridges_use_obfs4_radio_toggled + ) + + # meek_lite-azure option radio + # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports + if not self.obfs4proxy_file_path or not os.path.isfile( + self.obfs4proxy_file_path + ): + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( + strings._( + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy" + ) + ) + self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) + else: + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option") + ) + self.tor_bridges_use_meek_lite_azure_radio.toggled.connect( + self.tor_bridges_use_meek_lite_azure_radio_toggled + ) + + # Custom bridges radio and textbox + self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_custom_radio_option") + ) + self.tor_bridges_use_custom_radio.toggled.connect( + self.tor_bridges_use_custom_radio_toggled + ) + + self.tor_bridges_use_custom_label = QtWidgets.QLabel( + strings._("gui_settings_tor_bridges_custom_label") + ) + self.tor_bridges_use_custom_label.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) + self.tor_bridges_use_custom_label.setOpenExternalLinks(True) + self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() + self.tor_bridges_use_custom_textbox.setMaximumHeight(200) + self.tor_bridges_use_custom_textbox.setPlaceholderText( + "[address:port] [identifier]" + ) + + tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() + tor_bridges_use_custom_textbox_options_layout.addWidget( + self.tor_bridges_use_custom_label + ) + tor_bridges_use_custom_textbox_options_layout.addWidget( + self.tor_bridges_use_custom_textbox + ) + + self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() + self.tor_bridges_use_custom_textbox_options.setLayout( + tor_bridges_use_custom_textbox_options_layout + ) + self.tor_bridges_use_custom_textbox_options.hide() + + # Bridges layout/widget + bridges_layout = QtWidgets.QVBoxLayout() + bridges_layout.addWidget(self.tor_bridges_no_bridges_radio) + bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio) + bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio) + bridges_layout.addWidget(self.tor_bridges_use_custom_radio) + bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options) + + self.bridges = QtWidgets.QWidget() + self.bridges.setLayout(bridges_layout) + + # Automatic + self.connection_type_automatic_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_automatic_option") + ) + self.connection_type_automatic_radio.toggled.connect( + self.connection_type_automatic_toggled + ) + + # Control port + self.connection_type_control_port_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_control_port_option") + ) + self.connection_type_control_port_radio.toggled.connect( + self.connection_type_control_port_toggled + ) + + connection_type_control_port_extras_label = QtWidgets.QLabel( + strings._("gui_settings_control_port_label") + ) + self.connection_type_control_port_extras_address = QtWidgets.QLineEdit() + self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() + connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() + connection_type_control_port_extras_layout.addWidget( + connection_type_control_port_extras_label + ) + connection_type_control_port_extras_layout.addWidget( + self.connection_type_control_port_extras_address + ) + connection_type_control_port_extras_layout.addWidget( + self.connection_type_control_port_extras_port + ) + + self.connection_type_control_port_extras = QtWidgets.QWidget() + self.connection_type_control_port_extras.setLayout( + connection_type_control_port_extras_layout + ) + self.connection_type_control_port_extras.hide() + + # Socket file + self.connection_type_socket_file_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_socket_file_option") + ) + self.connection_type_socket_file_radio.toggled.connect( + self.connection_type_socket_file_toggled + ) + + connection_type_socket_file_extras_label = QtWidgets.QLabel( + strings._("gui_settings_socket_file_label") + ) + self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() + connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() + connection_type_socket_file_extras_layout.addWidget( + connection_type_socket_file_extras_label + ) + connection_type_socket_file_extras_layout.addWidget( + self.connection_type_socket_file_extras_path + ) + + self.connection_type_socket_file_extras = QtWidgets.QWidget() + self.connection_type_socket_file_extras.setLayout( + connection_type_socket_file_extras_layout + ) + self.connection_type_socket_file_extras.hide() + + # Tor SOCKS address and port + gui_settings_socks_label = QtWidgets.QLabel( + strings._("gui_settings_socks_label") + ) + self.connection_type_socks_address = QtWidgets.QLineEdit() + self.connection_type_socks_port = QtWidgets.QLineEdit() + connection_type_socks_layout = QtWidgets.QHBoxLayout() + connection_type_socks_layout.addWidget(gui_settings_socks_label) + connection_type_socks_layout.addWidget(self.connection_type_socks_address) + connection_type_socks_layout.addWidget(self.connection_type_socks_port) + + self.connection_type_socks = QtWidgets.QWidget() + self.connection_type_socks.setLayout(connection_type_socks_layout) + self.connection_type_socks.hide() + + # Authentication options + + # No authentication + self.authenticate_no_auth_radio = QtWidgets.QRadioButton( + strings._("gui_settings_authenticate_no_auth_option") + ) + self.authenticate_no_auth_radio.toggled.connect( + self.authenticate_no_auth_toggled + ) + + # Password + self.authenticate_password_radio = QtWidgets.QRadioButton( + strings._("gui_settings_authenticate_password_option") + ) + self.authenticate_password_radio.toggled.connect( + self.authenticate_password_toggled + ) + + authenticate_password_extras_label = QtWidgets.QLabel( + strings._("gui_settings_password_label") + ) + self.authenticate_password_extras_password = QtWidgets.QLineEdit("") + authenticate_password_extras_layout = QtWidgets.QHBoxLayout() + authenticate_password_extras_layout.addWidget( + authenticate_password_extras_label + ) + authenticate_password_extras_layout.addWidget( + self.authenticate_password_extras_password + ) + + self.authenticate_password_extras = QtWidgets.QWidget() + self.authenticate_password_extras.setLayout(authenticate_password_extras_layout) + self.authenticate_password_extras.hide() + + # Authentication options layout + authenticate_group_layout = QtWidgets.QVBoxLayout() + authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) + authenticate_group_layout.addWidget(self.authenticate_password_radio) + authenticate_group_layout.addWidget(self.authenticate_password_extras) + self.authenticate_group = QtWidgets.QGroupBox( + strings._("gui_settings_authenticate_label") + ) + self.authenticate_group.setLayout(authenticate_group_layout) + + # Put the radios into their own group so they are exclusive + connection_type_radio_group_layout = QtWidgets.QVBoxLayout() + connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio) + connection_type_radio_group_layout.addWidget( + self.connection_type_automatic_radio + ) + connection_type_radio_group_layout.addWidget( + self.connection_type_control_port_radio + ) + connection_type_radio_group_layout.addWidget( + self.connection_type_socket_file_radio + ) + connection_type_radio_group = QtWidgets.QGroupBox( + strings._("gui_settings_connection_type_label") + ) + connection_type_radio_group.setLayout(connection_type_radio_group_layout) + + # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) + connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() + connection_type_bridges_radio_group_layout.addWidget(self.bridges) + self.connection_type_bridges_radio_group = QtWidgets.QGroupBox( + strings._("gui_settings_tor_bridges") + ) + self.connection_type_bridges_radio_group.setLayout( + connection_type_bridges_radio_group_layout + ) + self.connection_type_bridges_radio_group.hide() + + # Test tor settings button + self.connection_type_test_button = QtWidgets.QPushButton( + strings._("gui_settings_connection_type_test_button") + ) + self.connection_type_test_button.clicked.connect(self.test_tor_clicked) + connection_type_test_button_layout = QtWidgets.QHBoxLayout() + connection_type_test_button_layout.addWidget(self.connection_type_test_button) + connection_type_test_button_layout.addStretch() + + # Connection type layout + connection_type_layout = QtWidgets.QVBoxLayout() + connection_type_layout.addWidget(self.connection_type_control_port_extras) + connection_type_layout.addWidget(self.connection_type_socket_file_extras) + connection_type_layout.addWidget(self.connection_type_socks) + connection_type_layout.addWidget(self.authenticate_group) + connection_type_layout.addWidget(self.connection_type_bridges_radio_group) + connection_type_layout.addLayout(connection_type_test_button_layout) + + # Buttons + self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) + self.save_button.clicked.connect(self.save_clicked) + self.cancel_button = QtWidgets.QPushButton( + strings._("gui_settings_button_cancel") + ) + self.cancel_button.clicked.connect(self.cancel_clicked) + version_label = QtWidgets.QLabel(f"OnionShare {self.common.version}") + version_label.setStyleSheet(self.common.gui.css["settings_version"]) + self.help_button = QtWidgets.QPushButton(strings._("gui_settings_button_help")) + self.help_button.clicked.connect(self.help_clicked) + buttons_layout = QtWidgets.QHBoxLayout() + buttons_layout.addWidget(version_label) + buttons_layout.addWidget(self.help_button) + buttons_layout.addStretch() + buttons_layout.addWidget(self.save_button) + buttons_layout.addWidget(self.cancel_button) + + # Tor network connection status + self.tor_status = QtWidgets.QLabel() + self.tor_status.setStyleSheet(self.common.gui.css["settings_tor_status"]) + self.tor_status.hide() + + # Layout + tor_layout = QtWidgets.QVBoxLayout() + tor_layout.addWidget(connection_type_radio_group) + tor_layout.addLayout(connection_type_layout) + tor_layout.addWidget(self.tor_status) + tor_layout.addStretch() + + layout = QtWidgets.QVBoxLayout() + if not self.hide_tor_settings: + layout.addLayout(tor_layout) + layout.addSpacing(20) + layout.addWidget(autoupdate_group) + if autoupdate_group.isVisible(): + layout.addSpacing(20) + layout.addLayout(language_layout) + layout.addSpacing(20) + layout.addLayout(theme_layout) + layout.addSpacing(20) + layout.addStretch() + layout.addLayout(buttons_layout) + + self.setLayout(layout) + self.cancel_button.setFocus() + + self.reload_settings() + + def reload_settings(self): + # Load settings, and fill them in + self.old_settings = Settings(self.common) + self.old_settings.load() + + use_autoupdate = self.old_settings.get("use_autoupdate") + if use_autoupdate: + self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) + else: + self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) + + autoupdate_timestamp = self.old_settings.get("autoupdate_timestamp") + self._update_autoupdate_timestamp(autoupdate_timestamp) + + locale = self.old_settings.get("locale") + locale_index = self.language_combobox.findData(locale) + self.language_combobox.setCurrentIndex(locale_index) + + theme_choice = self.old_settings.get("theme") + self.theme_combobox.setCurrentIndex(theme_choice) + + connection_type = self.old_settings.get("connection_type") + if connection_type == "bundled": + if self.connection_type_bundled_radio.isEnabled(): + self.connection_type_bundled_radio.setChecked(True) + else: + # If bundled tor is disabled, fallback to automatic + self.connection_type_automatic_radio.setChecked(True) + elif connection_type == "automatic": + self.connection_type_automatic_radio.setChecked(True) + elif connection_type == "control_port": + self.connection_type_control_port_radio.setChecked(True) + elif connection_type == "socket_file": + self.connection_type_socket_file_radio.setChecked(True) + self.connection_type_control_port_extras_address.setText( + self.old_settings.get("control_port_address") + ) + self.connection_type_control_port_extras_port.setText( + str(self.old_settings.get("control_port_port")) + ) + self.connection_type_socket_file_extras_path.setText( + self.old_settings.get("socket_file_path") + ) + self.connection_type_socks_address.setText( + self.old_settings.get("socks_address") + ) + self.connection_type_socks_port.setText( + str(self.old_settings.get("socks_port")) + ) + auth_type = self.old_settings.get("auth_type") + if auth_type == "no_auth": + self.authenticate_no_auth_radio.setChecked(True) + elif auth_type == "password": + self.authenticate_password_radio.setChecked(True) + self.authenticate_password_extras_password.setText( + self.old_settings.get("auth_password") + ) + + if self.old_settings.get("no_bridges"): + self.tor_bridges_no_bridges_radio.setChecked(True) + self.tor_bridges_use_obfs4_radio.setChecked(False) + self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) + self.tor_bridges_use_custom_radio.setChecked(False) + else: + self.tor_bridges_no_bridges_radio.setChecked(False) + self.tor_bridges_use_obfs4_radio.setChecked( + self.old_settings.get("tor_bridges_use_obfs4") + ) + self.tor_bridges_use_meek_lite_azure_radio.setChecked( + self.old_settings.get("tor_bridges_use_meek_lite_azure") + ) + + if self.old_settings.get("tor_bridges_use_custom_bridges"): + self.tor_bridges_use_custom_radio.setChecked(True) + # Remove the 'Bridge' lines at the start of each bridge. + # They are added automatically to provide compatibility with + # copying/pasting bridges provided from https://bridges.torproject.org + new_bridges = [] + bridges = self.old_settings.get("tor_bridges_use_custom_bridges").split( + "Bridge " + ) + for bridge in bridges: + new_bridges.append(bridge) + new_bridges = "".join(new_bridges) + self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) + + def connection_type_bundled_toggled(self, checked): + """ + Connection type bundled was toggled. If checked, hide authentication fields. + """ + self.common.log("TorSettingsDialog", "connection_type_bundled_toggled") + if self.hide_tor_settings: + return + if checked: + self.authenticate_group.hide() + self.connection_type_socks.hide() + self.connection_type_bridges_radio_group.show() + + def tor_bridges_no_bridges_radio_toggled(self, checked): + """ + 'No bridges' option was toggled. If checked, enable other bridge options. + """ + if self.hide_tor_settings: + return + if checked: + self.tor_bridges_use_custom_textbox_options.hide() + + def tor_bridges_use_obfs4_radio_toggled(self, checked): + """ + obfs4 bridges option was toggled. If checked, disable custom bridge options. + """ + if self.hide_tor_settings: + return + if checked: + self.tor_bridges_use_custom_textbox_options.hide() + + def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked): + """ + meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. + """ + if self.hide_tor_settings: + return + if checked: + self.tor_bridges_use_custom_textbox_options.hide() + # Alert the user about meek's costliness if it looks like they're turning it on + if not self.old_settings.get("tor_bridges_use_meek_lite_azure"): + Alert( + self.common, + strings._("gui_settings_meek_lite_expensive_warning"), + QtWidgets.QMessageBox.Warning, + ) + + def tor_bridges_use_custom_radio_toggled(self, checked): + """ + Custom bridges option was toggled. If checked, show custom bridge options. + """ + if self.hide_tor_settings: + return + if checked: + self.tor_bridges_use_custom_textbox_options.show() + + def connection_type_automatic_toggled(self, checked): + """ + Connection type automatic was toggled. If checked, hide authentication fields. + """ + self.common.log("TorSettingsDialog", "connection_type_automatic_toggled") + if self.hide_tor_settings: + return + if checked: + self.authenticate_group.hide() + self.connection_type_socks.hide() + self.connection_type_bridges_radio_group.hide() + + def connection_type_control_port_toggled(self, checked): + """ + Connection type control port was toggled. If checked, show extra fields + for Tor control address and port. If unchecked, hide those extra fields. + """ + self.common.log("TorSettingsDialog", "connection_type_control_port_toggled") + if self.hide_tor_settings: + return + if checked: + self.authenticate_group.show() + self.connection_type_control_port_extras.show() + self.connection_type_socks.show() + self.connection_type_bridges_radio_group.hide() + else: + self.connection_type_control_port_extras.hide() + + def connection_type_socket_file_toggled(self, checked): + """ + Connection type socket file was toggled. If checked, show extra fields + for socket file. If unchecked, hide those extra fields. + """ + self.common.log("TorSettingsDialog", "connection_type_socket_file_toggled") + if self.hide_tor_settings: + return + if checked: + self.authenticate_group.show() + self.connection_type_socket_file_extras.show() + self.connection_type_socks.show() + self.connection_type_bridges_radio_group.hide() + else: + self.connection_type_socket_file_extras.hide() + + def authenticate_no_auth_toggled(self, checked): + """ + Authentication option no authentication was toggled. + """ + self.common.log("TorSettingsDialog", "authenticate_no_auth_toggled") + + def authenticate_password_toggled(self, checked): + """ + Authentication option password was toggled. If checked, show extra fields + for password auth. If unchecked, hide those extra fields. + """ + self.common.log("TorSettingsDialog", "authenticate_password_toggled") + if checked: + self.authenticate_password_extras.show() + else: + self.authenticate_password_extras.hide() + + def test_tor_clicked(self): + """ + Test Tor Settings button clicked. With the given settings, see if we can + successfully connect and authenticate to Tor. + """ + self.common.log("TorSettingsDialog", "test_tor_clicked") + settings = self.settings_from_fields() + + try: + # Show Tor connection status if connection type is bundled tor + if settings.get("connection_type") == "bundled": + self.tor_status.show() + self._disable_buttons() + + def tor_status_update_func(progress, summary): + self._tor_status_update(progress, summary) + return True + + else: + tor_status_update_func = None + + onion = Onion( + self.common, + use_tmp_dir=True, + get_tor_paths=self.common.gui.get_tor_paths, + ) + onion.connect( + custom_settings=settings, + tor_status_update_func=tor_status_update_func, + ) + + # If an exception hasn't been raised yet, the Tor settings work + Alert( + self.common, + strings._("settings_test_success").format( + onion.tor_version, + onion.supports_ephemeral, + onion.supports_stealth, + onion.supports_v3_onions, + ), + ) + + # Clean up + onion.cleanup() + + except ( + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, + ) as e: + message = self.common.gui.get_translated_tor_error(e) + Alert( + self.common, + message, + QtWidgets.QMessageBox.Warning, + ) + if settings.get("connection_type") == "bundled": + self.tor_status.hide() + self._enable_buttons() + + def check_for_updates(self): + """ + Check for Updates button clicked. Manually force an update check. + """ + self.common.log("TorSettingsDialog", "check_for_updates") + # Disable buttons + self._disable_buttons() + self.common.gui.qtapp.processEvents() + + def update_timestamp(): + # Update the last checked label + settings = Settings(self.common) + settings.load() + autoupdate_timestamp = settings.get("autoupdate_timestamp") + self._update_autoupdate_timestamp(autoupdate_timestamp) + + def close_forced_update_thread(): + forced_update_thread.quit() + # Enable buttons + self._enable_buttons() + # Update timestamp + update_timestamp() + + # Check for updates + def update_available(update_url, installed_version, latest_version): + Alert( + self.common, + strings._("update_available").format( + update_url, installed_version, latest_version + ), + ) + close_forced_update_thread() + + def update_not_available(): + Alert(self.common, strings._("update_not_available")) + close_forced_update_thread() + + def update_error(): + Alert( + self.common, + strings._("update_error_check_error"), + QtWidgets.QMessageBox.Warning, + ) + close_forced_update_thread() + + def update_invalid_version(latest_version): + Alert( + self.common, + strings._("update_error_invalid_latest_version").format(latest_version), + QtWidgets.QMessageBox.Warning, + ) + close_forced_update_thread() + + forced_update_thread = UpdateThread( + self.common, self.common.gui.onion, force=True + ) + forced_update_thread.update_available.connect(update_available) + forced_update_thread.update_not_available.connect(update_not_available) + forced_update_thread.update_error.connect(update_error) + forced_update_thread.update_invalid_version.connect(update_invalid_version) + forced_update_thread.start() + + def save_clicked(self): + """ + Save button clicked. Save current settings to disk. + """ + self.common.log("TorSettingsDialog", "save_clicked") + + def changed(s1, s2, keys): + """ + Compare the Settings objects s1 and s2 and return true if any values + have changed for the given keys. + """ + for key in keys: + if s1.get(key) != s2.get(key): + return True + return False + + settings = self.settings_from_fields() + if settings: + # If language changed, inform user they need to restart OnionShare + if changed(settings, self.old_settings, ["locale"]): + # Look up error message in different locale + new_locale = settings.get("locale") + if ( + new_locale in strings.translations + and "gui_settings_language_changed_notice" + in strings.translations[new_locale] + ): + notice = strings.translations[new_locale][ + "gui_settings_language_changed_notice" + ] + else: + notice = strings._("gui_settings_language_changed_notice") + Alert(self.common, notice, QtWidgets.QMessageBox.Information) + + # If color mode changed, inform user they need to restart OnionShare + if changed(settings, self.old_settings, ["theme"]): + notice = strings._("gui_color_mode_changed_notice") + Alert(self.common, notice, QtWidgets.QMessageBox.Information) + + # Save the new settings + settings.save() + + # If Tor isn't connected, or if Tor settings have changed, Reinitialize + # the Onion object + reboot_onion = False + if not self.common.gui.local_only: + if self.common.gui.onion.is_authenticated(): + self.common.log( + "TorSettingsDialog", "save_clicked", "Connected to Tor" + ) + + if changed( + settings, + self.old_settings, + [ + "connection_type", + "control_port_address", + "control_port_port", + "socks_address", + "socks_port", + "socket_file_path", + "auth_type", + "auth_password", + "no_bridges", + "tor_bridges_use_obfs4", + "tor_bridges_use_meek_lite_azure", + "tor_bridges_use_custom_bridges", + ], + ): + + reboot_onion = True + + else: + self.common.log( + "TorSettingsDialog", "save_clicked", "Not connected to Tor" + ) + # Tor isn't connected, so try connecting + reboot_onion = True + + # Do we need to reinitialize Tor? + if reboot_onion: + # Reinitialize the Onion object + self.common.log( + "TorSettingsDialog", "save_clicked", "rebooting the Onion" + ) + self.common.gui.onion.cleanup() + + tor_con = TorConnectionDialog(self.common, settings) + tor_con.start() + + self.common.log( + "TorSettingsDialog", + "save_clicked", + f"Onion done rebooting, connected to Tor: {self.common.gui.onion.connected_to_tor}", + ) + + if ( + self.common.gui.onion.is_authenticated() + and not tor_con.wasCanceled() + ): + self.settings_saved.emit() + self.close() + + else: + self.settings_saved.emit() + self.close() + else: + self.settings_saved.emit() + self.close() + + def cancel_clicked(self): + """ + Cancel button clicked. + """ + self.common.log("TorSettingsDialog", "cancel_clicked") + if ( + not self.common.gui.local_only + and not self.common.gui.onion.is_authenticated() + ): + Alert( + self.common, + strings._("gui_tor_connection_canceled"), + QtWidgets.QMessageBox.Warning, + ) + sys.exit() + else: + self.close() + + def help_clicked(self): + """ + Help button clicked. + """ + self.common.log("TorSettingsDialog", "help_clicked") + TorSettingsDialog.open_help() + + @staticmethod + def open_help(): + help_url = "https://docs.onionshare.org/" + QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url)) + + def settings_from_fields(self): + """ + Return a Settings object that's full of values from the settings dialog. + """ + self.common.log("TorSettingsDialog", "settings_from_fields") + settings = Settings(self.common) + settings.load() # To get the last update timestamp + + # Theme + theme_index = self.theme_combobox.currentIndex() + settings.set("theme", theme_index) + + # Language + locale_index = self.language_combobox.currentIndex() + locale = self.language_combobox.itemData(locale_index) + settings.set("locale", locale) + + # Tor connection + if self.connection_type_bundled_radio.isChecked(): + settings.set("connection_type", "bundled") + if self.connection_type_automatic_radio.isChecked(): + settings.set("connection_type", "automatic") + if self.connection_type_control_port_radio.isChecked(): + settings.set("connection_type", "control_port") + if self.connection_type_socket_file_radio.isChecked(): + settings.set("connection_type", "socket_file") + + if self.autoupdate_checkbox.isChecked(): + settings.set("use_autoupdate", True) + else: + settings.set("use_autoupdate", False) + + settings.set( + "control_port_address", + self.connection_type_control_port_extras_address.text(), + ) + settings.set( + "control_port_port", self.connection_type_control_port_extras_port.text() + ) + settings.set( + "socket_file_path", self.connection_type_socket_file_extras_path.text() + ) + + settings.set("socks_address", self.connection_type_socks_address.text()) + settings.set("socks_port", self.connection_type_socks_port.text()) + + if self.authenticate_no_auth_radio.isChecked(): + settings.set("auth_type", "no_auth") + if self.authenticate_password_radio.isChecked(): + settings.set("auth_type", "password") + + settings.set("auth_password", self.authenticate_password_extras_password.text()) + + # Whether we use bridges + if self.tor_bridges_no_bridges_radio.isChecked(): + settings.set("no_bridges", True) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_custom_bridges", "") + if self.tor_bridges_use_obfs4_radio.isChecked(): + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", True) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_custom_bridges", "") + if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", True) + settings.set("tor_bridges_use_custom_bridges", "") + if self.tor_bridges_use_custom_radio.isChecked(): + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + + # Insert a 'Bridge' line at the start of each bridge. + # This makes it easier to copy/paste a set of bridges + # provided from https://bridges.torproject.org + new_bridges = [] + bridges = self.tor_bridges_use_custom_textbox.toPlainText().split("\n") + bridges_valid = False + for bridge in bridges: + if bridge != "": + # Check the syntax of the custom bridge to make sure it looks legitimate + ipv4_pattern = re.compile( + "(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$" + ) + ipv6_pattern = re.compile( + "(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$" + ) + meek_lite_pattern = re.compile( + "(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)" + ) + if ( + ipv4_pattern.match(bridge) + or ipv6_pattern.match(bridge) + or meek_lite_pattern.match(bridge) + ): + new_bridges.append("".join(["Bridge ", bridge, "\n"])) + bridges_valid = True + + if bridges_valid: + new_bridges = "".join(new_bridges) + settings.set("tor_bridges_use_custom_bridges", new_bridges) + else: + Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) + settings.set("no_bridges", True) + return False + + return settings + + def closeEvent(self, e): + self.common.log("TorSettingsDialog", "closeEvent") + + # On close, if Tor isn't connected, then quit OnionShare altogether + if not self.common.gui.local_only: + if not self.common.gui.onion.is_authenticated(): + self.common.log( + "TorSettingsDialog", + "closeEvent", + "Closing while not connected to Tor", + ) + + # Wait 1ms for the event loop to finish, then quit + QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit) + + def _update_autoupdate_timestamp(self, autoupdate_timestamp): + self.common.log("TorSettingsDialog", "_update_autoupdate_timestamp") + + if autoupdate_timestamp: + dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) + last_checked = dt.strftime("%B %d, %Y %H:%M") + else: + last_checked = strings._("gui_settings_autoupdate_timestamp_never") + self.autoupdate_timestamp.setText( + strings._("gui_settings_autoupdate_timestamp").format(last_checked) + ) + + def _tor_status_update(self, progress, summary): + self.tor_status.setText( + f"{strings._('connecting_to_tor')}
{progress}% {summary}" + ) + self.common.gui.qtapp.processEvents() + if "Done" in summary: + self.tor_status.hide() + self._enable_buttons() + + def _disable_buttons(self): + self.common.log("TorSettingsDialog", "_disable_buttons") + + self.check_for_updates_button.setEnabled(False) + self.connection_type_test_button.setEnabled(False) + self.save_button.setEnabled(False) + self.cancel_button.setEnabled(False) + + def _enable_buttons(self): + self.common.log("TorSettingsDialog", "_enable_buttons") + # We can't check for updates if we're still not connected to Tor + if not self.common.gui.onion.connected_to_tor: + self.check_for_updates_button.setEnabled(False) + else: + self.check_for_updates_button.setEnabled(True) + self.connection_type_test_button.setEnabled(True) + self.save_button.setEnabled(True) + self.cancel_button.setEnabled(True) From 4b976e538f8ab9447dc98de425e9f914c85158b2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 12 Oct 2021 21:19:48 -0700 Subject: [PATCH 05/26] Rip out non-Tor settings from TorSettingsDialog --- desktop/src/onionshare/tor_settings_dialog.py | 251 +----------------- 1 file changed, 3 insertions(+), 248 deletions(-) diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index 1ff46b3e..fbf93044 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -71,73 +71,6 @@ class TorSettingsDialog(QtWidgets.QDialog): self.system = platform.system() - # If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog - self.hide_tor_settings = os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1" - - # Automatic updates options - - # Autoupdate - self.autoupdate_checkbox = QtWidgets.QCheckBox() - self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option")) - - # Last update time - self.autoupdate_timestamp = QtWidgets.QLabel() - - # Check for updates button - self.check_for_updates_button = QtWidgets.QPushButton( - strings._("gui_settings_autoupdate_check_button") - ) - self.check_for_updates_button.clicked.connect(self.check_for_updates) - # We can't check for updates if not connected to Tor - if not self.common.gui.onion.connected_to_tor: - self.check_for_updates_button.setEnabled(False) - - # Autoupdate options layout - autoupdate_group_layout = QtWidgets.QVBoxLayout() - autoupdate_group_layout.addWidget(self.autoupdate_checkbox) - autoupdate_group_layout.addWidget(self.autoupdate_timestamp) - autoupdate_group_layout.addWidget(self.check_for_updates_button) - autoupdate_group = QtWidgets.QGroupBox( - strings._("gui_settings_autoupdate_label") - ) - autoupdate_group.setLayout(autoupdate_group_layout) - - # Autoupdate is only available for Windows and Mac (Linux updates using package manager) - if self.system != "Windows" and self.system != "Darwin": - autoupdate_group.hide() - - # Language settings - language_label = QtWidgets.QLabel(strings._("gui_settings_language_label")) - self.language_combobox = QtWidgets.QComboBox() - # Populate the dropdown with all of OnionShare's available languages - language_names_to_locales = { - v: k for k, v in self.common.settings.available_locales.items() - } - language_names = list(language_names_to_locales) - language_names.sort() - for language_name in language_names: - locale = language_names_to_locales[language_name] - self.language_combobox.addItem(language_name, locale) - language_layout = QtWidgets.QHBoxLayout() - language_layout.addWidget(language_label) - language_layout.addWidget(self.language_combobox) - language_layout.addStretch() - - # Theme Settings - theme_label = QtWidgets.QLabel(strings._("gui_settings_theme_label")) - self.theme_combobox = QtWidgets.QComboBox() - theme_choices = [ - strings._("gui_settings_theme_auto"), - strings._("gui_settings_theme_light"), - strings._("gui_settings_theme_dark"), - ] - self.theme_combobox.addItems(theme_choices) - theme_layout = QtWidgets.QHBoxLayout() - theme_layout.addWidget(theme_label) - theme_layout.addWidget(self.theme_combobox) - theme_layout.addStretch() - # Connection type: either automatic, control port, or socket file # Bundled Tor @@ -430,13 +363,7 @@ class TorSettingsDialog(QtWidgets.QDialog): strings._("gui_settings_button_cancel") ) self.cancel_button.clicked.connect(self.cancel_clicked) - version_label = QtWidgets.QLabel(f"OnionShare {self.common.version}") - version_label.setStyleSheet(self.common.gui.css["settings_version"]) - self.help_button = QtWidgets.QPushButton(strings._("gui_settings_button_help")) - self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() - buttons_layout.addWidget(version_label) - buttons_layout.addWidget(self.help_button) buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) @@ -447,23 +374,10 @@ class TorSettingsDialog(QtWidgets.QDialog): self.tor_status.hide() # Layout - tor_layout = QtWidgets.QVBoxLayout() - tor_layout.addWidget(connection_type_radio_group) - tor_layout.addLayout(connection_type_layout) - tor_layout.addWidget(self.tor_status) - tor_layout.addStretch() - layout = QtWidgets.QVBoxLayout() - if not self.hide_tor_settings: - layout.addLayout(tor_layout) - layout.addSpacing(20) - layout.addWidget(autoupdate_group) - if autoupdate_group.isVisible(): - layout.addSpacing(20) - layout.addLayout(language_layout) - layout.addSpacing(20) - layout.addLayout(theme_layout) - layout.addSpacing(20) + layout.addWidget(connection_type_radio_group) + layout.addLayout(connection_type_layout) + layout.addWidget(self.tor_status) layout.addStretch() layout.addLayout(buttons_layout) @@ -477,22 +391,6 @@ class TorSettingsDialog(QtWidgets.QDialog): self.old_settings = Settings(self.common) self.old_settings.load() - use_autoupdate = self.old_settings.get("use_autoupdate") - if use_autoupdate: - self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) - else: - self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) - - autoupdate_timestamp = self.old_settings.get("autoupdate_timestamp") - self._update_autoupdate_timestamp(autoupdate_timestamp) - - locale = self.old_settings.get("locale") - locale_index = self.language_combobox.findData(locale) - self.language_combobox.setCurrentIndex(locale_index) - - theme_choice = self.old_settings.get("theme") - self.theme_combobox.setCurrentIndex(theme_choice) - connection_type = self.old_settings.get("connection_type") if connection_type == "bundled": if self.connection_type_bundled_radio.isEnabled(): @@ -563,8 +461,6 @@ class TorSettingsDialog(QtWidgets.QDialog): Connection type bundled was toggled. If checked, hide authentication fields. """ self.common.log("TorSettingsDialog", "connection_type_bundled_toggled") - if self.hide_tor_settings: - return if checked: self.authenticate_group.hide() self.connection_type_socks.hide() @@ -574,8 +470,6 @@ class TorSettingsDialog(QtWidgets.QDialog): """ 'No bridges' option was toggled. If checked, enable other bridge options. """ - if self.hide_tor_settings: - return if checked: self.tor_bridges_use_custom_textbox_options.hide() @@ -583,8 +477,6 @@ class TorSettingsDialog(QtWidgets.QDialog): """ obfs4 bridges option was toggled. If checked, disable custom bridge options. """ - if self.hide_tor_settings: - return if checked: self.tor_bridges_use_custom_textbox_options.hide() @@ -592,8 +484,6 @@ class TorSettingsDialog(QtWidgets.QDialog): """ meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. """ - if self.hide_tor_settings: - return if checked: self.tor_bridges_use_custom_textbox_options.hide() # Alert the user about meek's costliness if it looks like they're turning it on @@ -608,8 +498,6 @@ class TorSettingsDialog(QtWidgets.QDialog): """ Custom bridges option was toggled. If checked, show custom bridge options. """ - if self.hide_tor_settings: - return if checked: self.tor_bridges_use_custom_textbox_options.show() @@ -618,8 +506,6 @@ class TorSettingsDialog(QtWidgets.QDialog): Connection type automatic was toggled. If checked, hide authentication fields. """ self.common.log("TorSettingsDialog", "connection_type_automatic_toggled") - if self.hide_tor_settings: - return if checked: self.authenticate_group.hide() self.connection_type_socks.hide() @@ -631,8 +517,6 @@ class TorSettingsDialog(QtWidgets.QDialog): for Tor control address and port. If unchecked, hide those extra fields. """ self.common.log("TorSettingsDialog", "connection_type_control_port_toggled") - if self.hide_tor_settings: - return if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() @@ -647,8 +531,6 @@ class TorSettingsDialog(QtWidgets.QDialog): for socket file. If unchecked, hide those extra fields. """ self.common.log("TorSettingsDialog", "connection_type_socket_file_toggled") - if self.hide_tor_settings: - return if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() @@ -744,68 +626,6 @@ class TorSettingsDialog(QtWidgets.QDialog): self.tor_status.hide() self._enable_buttons() - def check_for_updates(self): - """ - Check for Updates button clicked. Manually force an update check. - """ - self.common.log("TorSettingsDialog", "check_for_updates") - # Disable buttons - self._disable_buttons() - self.common.gui.qtapp.processEvents() - - def update_timestamp(): - # Update the last checked label - settings = Settings(self.common) - settings.load() - autoupdate_timestamp = settings.get("autoupdate_timestamp") - self._update_autoupdate_timestamp(autoupdate_timestamp) - - def close_forced_update_thread(): - forced_update_thread.quit() - # Enable buttons - self._enable_buttons() - # Update timestamp - update_timestamp() - - # Check for updates - def update_available(update_url, installed_version, latest_version): - Alert( - self.common, - strings._("update_available").format( - update_url, installed_version, latest_version - ), - ) - close_forced_update_thread() - - def update_not_available(): - Alert(self.common, strings._("update_not_available")) - close_forced_update_thread() - - def update_error(): - Alert( - self.common, - strings._("update_error_check_error"), - QtWidgets.QMessageBox.Warning, - ) - close_forced_update_thread() - - def update_invalid_version(latest_version): - Alert( - self.common, - strings._("update_error_invalid_latest_version").format(latest_version), - QtWidgets.QMessageBox.Warning, - ) - close_forced_update_thread() - - forced_update_thread = UpdateThread( - self.common, self.common.gui.onion, force=True - ) - forced_update_thread.update_available.connect(update_available) - forced_update_thread.update_not_available.connect(update_not_available) - forced_update_thread.update_error.connect(update_error) - forced_update_thread.update_invalid_version.connect(update_invalid_version) - forced_update_thread.start() - def save_clicked(self): """ Save button clicked. Save current settings to disk. @@ -824,27 +644,6 @@ class TorSettingsDialog(QtWidgets.QDialog): settings = self.settings_from_fields() if settings: - # If language changed, inform user they need to restart OnionShare - if changed(settings, self.old_settings, ["locale"]): - # Look up error message in different locale - new_locale = settings.get("locale") - if ( - new_locale in strings.translations - and "gui_settings_language_changed_notice" - in strings.translations[new_locale] - ): - notice = strings.translations[new_locale][ - "gui_settings_language_changed_notice" - ] - else: - notice = strings._("gui_settings_language_changed_notice") - Alert(self.common, notice, QtWidgets.QMessageBox.Information) - - # If color mode changed, inform user they need to restart OnionShare - if changed(settings, self.old_settings, ["theme"]): - notice = strings._("gui_color_mode_changed_notice") - Alert(self.common, notice, QtWidgets.QMessageBox.Information) - # Save the new settings settings.save() @@ -934,18 +733,6 @@ class TorSettingsDialog(QtWidgets.QDialog): else: self.close() - def help_clicked(self): - """ - Help button clicked. - """ - self.common.log("TorSettingsDialog", "help_clicked") - TorSettingsDialog.open_help() - - @staticmethod - def open_help(): - help_url = "https://docs.onionshare.org/" - QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url)) - def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. @@ -954,15 +741,6 @@ class TorSettingsDialog(QtWidgets.QDialog): settings = Settings(self.common) settings.load() # To get the last update timestamp - # Theme - theme_index = self.theme_combobox.currentIndex() - settings.set("theme", theme_index) - - # Language - locale_index = self.language_combobox.currentIndex() - locale = self.language_combobox.itemData(locale_index) - settings.set("locale", locale) - # Tor connection if self.connection_type_bundled_radio.isChecked(): settings.set("connection_type", "bundled") @@ -973,11 +751,6 @@ class TorSettingsDialog(QtWidgets.QDialog): if self.connection_type_socket_file_radio.isChecked(): settings.set("connection_type", "socket_file") - if self.autoupdate_checkbox.isChecked(): - settings.set("use_autoupdate", True) - else: - settings.set("use_autoupdate", False) - settings.set( "control_port_address", self.connection_type_control_port_extras_address.text(), @@ -1071,18 +844,6 @@ class TorSettingsDialog(QtWidgets.QDialog): # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit) - def _update_autoupdate_timestamp(self, autoupdate_timestamp): - self.common.log("TorSettingsDialog", "_update_autoupdate_timestamp") - - if autoupdate_timestamp: - dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) - last_checked = dt.strftime("%B %d, %Y %H:%M") - else: - last_checked = strings._("gui_settings_autoupdate_timestamp_never") - self.autoupdate_timestamp.setText( - strings._("gui_settings_autoupdate_timestamp").format(last_checked) - ) - def _tor_status_update(self, progress, summary): self.tor_status.setText( f"{strings._('connecting_to_tor')}
{progress}% {summary}" @@ -1095,18 +856,12 @@ class TorSettingsDialog(QtWidgets.QDialog): def _disable_buttons(self): self.common.log("TorSettingsDialog", "_disable_buttons") - self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) self.save_button.setEnabled(False) self.cancel_button.setEnabled(False) def _enable_buttons(self): self.common.log("TorSettingsDialog", "_enable_buttons") - # We can't check for updates if we're still not connected to Tor - if not self.common.gui.onion.connected_to_tor: - self.check_for_updates_button.setEnabled(False) - else: - self.check_for_updates_button.setEnabled(True) self.connection_type_test_button.setEnabled(True) self.save_button.setEnabled(True) self.cancel_button.setEnabled(True) From fda4f5ff2694759c051f73e0c8937a6dea264888 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 12 Oct 2021 21:24:18 -0700 Subject: [PATCH 06/26] Rip out Tor settings from SettingsDialog --- desktop/src/onionshare/settings_dialog.py | 738 +--------------------- 1 file changed, 2 insertions(+), 736 deletions(-) diff --git a/desktop/src/onionshare/settings_dialog.py b/desktop/src/onionshare/settings_dialog.py index 8ef13399..de5c6992 100644 --- a/desktop/src/onionshare/settings_dialog.py +++ b/desktop/src/onionshare/settings_dialog.py @@ -71,9 +71,6 @@ class SettingsDialog(QtWidgets.QDialog): self.system = platform.system() - # If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog - self.hide_tor_settings = os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1" - # Automatic updates options # Autoupdate @@ -138,291 +135,6 @@ class SettingsDialog(QtWidgets.QDialog): theme_layout.addWidget(self.theme_combobox) theme_layout.addStretch() - # Connection type: either automatic, control port, or socket file - - # Bundled Tor - self.connection_type_bundled_radio = QtWidgets.QRadioButton( - strings._("gui_settings_connection_type_bundled_option") - ) - self.connection_type_bundled_radio.toggled.connect( - self.connection_type_bundled_toggled - ) - - # Bundled Tor doesn't work on dev mode in Windows or Mac - if (self.system == "Windows" or self.system == "Darwin") and getattr( - sys, "onionshare_dev_mode", False - ): - self.connection_type_bundled_radio.setEnabled(False) - - # Bridge options for bundled tor - - # No bridges option radio - self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_no_bridges_radio_option") - ) - self.tor_bridges_no_bridges_radio.toggled.connect( - self.tor_bridges_no_bridges_radio_toggled - ) - - ( - self.tor_path, - self.tor_geo_ip_file_path, - self.tor_geo_ipv6_file_path, - self.obfs4proxy_file_path, - self.snowflake_file_path, - ) = self.common.gui.get_tor_paths() - - # obfs4 option radio - # if the obfs4proxy binary is missing, we can't use obfs4 transports - if not self.obfs4proxy_file_path or not os.path.isfile( - self.obfs4proxy_file_path - ): - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy") - ) - self.tor_bridges_use_obfs4_radio.setEnabled(False) - else: - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_obfs4_radio_option") - ) - self.tor_bridges_use_obfs4_radio.toggled.connect( - self.tor_bridges_use_obfs4_radio_toggled - ) - - # meek_lite-azure option radio - # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports - if not self.obfs4proxy_file_path or not os.path.isfile( - self.obfs4proxy_file_path - ): - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( - strings._( - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy" - ) - ) - self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) - else: - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option") - ) - self.tor_bridges_use_meek_lite_azure_radio.toggled.connect( - self.tor_bridges_use_meek_lite_azure_radio_toggled - ) - - # Custom bridges radio and textbox - self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_custom_radio_option") - ) - self.tor_bridges_use_custom_radio.toggled.connect( - self.tor_bridges_use_custom_radio_toggled - ) - - self.tor_bridges_use_custom_label = QtWidgets.QLabel( - strings._("gui_settings_tor_bridges_custom_label") - ) - self.tor_bridges_use_custom_label.setTextInteractionFlags( - QtCore.Qt.TextBrowserInteraction - ) - self.tor_bridges_use_custom_label.setOpenExternalLinks(True) - self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() - self.tor_bridges_use_custom_textbox.setMaximumHeight(200) - self.tor_bridges_use_custom_textbox.setPlaceholderText( - "[address:port] [identifier]" - ) - - tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() - tor_bridges_use_custom_textbox_options_layout.addWidget( - self.tor_bridges_use_custom_label - ) - tor_bridges_use_custom_textbox_options_layout.addWidget( - self.tor_bridges_use_custom_textbox - ) - - self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() - self.tor_bridges_use_custom_textbox_options.setLayout( - tor_bridges_use_custom_textbox_options_layout - ) - self.tor_bridges_use_custom_textbox_options.hide() - - # Bridges layout/widget - bridges_layout = QtWidgets.QVBoxLayout() - bridges_layout.addWidget(self.tor_bridges_no_bridges_radio) - bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio) - bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio) - bridges_layout.addWidget(self.tor_bridges_use_custom_radio) - bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options) - - self.bridges = QtWidgets.QWidget() - self.bridges.setLayout(bridges_layout) - - # Automatic - self.connection_type_automatic_radio = QtWidgets.QRadioButton( - strings._("gui_settings_connection_type_automatic_option") - ) - self.connection_type_automatic_radio.toggled.connect( - self.connection_type_automatic_toggled - ) - - # Control port - self.connection_type_control_port_radio = QtWidgets.QRadioButton( - strings._("gui_settings_connection_type_control_port_option") - ) - self.connection_type_control_port_radio.toggled.connect( - self.connection_type_control_port_toggled - ) - - connection_type_control_port_extras_label = QtWidgets.QLabel( - strings._("gui_settings_control_port_label") - ) - self.connection_type_control_port_extras_address = QtWidgets.QLineEdit() - self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() - connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() - connection_type_control_port_extras_layout.addWidget( - connection_type_control_port_extras_label - ) - connection_type_control_port_extras_layout.addWidget( - self.connection_type_control_port_extras_address - ) - connection_type_control_port_extras_layout.addWidget( - self.connection_type_control_port_extras_port - ) - - self.connection_type_control_port_extras = QtWidgets.QWidget() - self.connection_type_control_port_extras.setLayout( - connection_type_control_port_extras_layout - ) - self.connection_type_control_port_extras.hide() - - # Socket file - self.connection_type_socket_file_radio = QtWidgets.QRadioButton( - strings._("gui_settings_connection_type_socket_file_option") - ) - self.connection_type_socket_file_radio.toggled.connect( - self.connection_type_socket_file_toggled - ) - - connection_type_socket_file_extras_label = QtWidgets.QLabel( - strings._("gui_settings_socket_file_label") - ) - self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() - connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() - connection_type_socket_file_extras_layout.addWidget( - connection_type_socket_file_extras_label - ) - connection_type_socket_file_extras_layout.addWidget( - self.connection_type_socket_file_extras_path - ) - - self.connection_type_socket_file_extras = QtWidgets.QWidget() - self.connection_type_socket_file_extras.setLayout( - connection_type_socket_file_extras_layout - ) - self.connection_type_socket_file_extras.hide() - - # Tor SOCKS address and port - gui_settings_socks_label = QtWidgets.QLabel( - strings._("gui_settings_socks_label") - ) - self.connection_type_socks_address = QtWidgets.QLineEdit() - self.connection_type_socks_port = QtWidgets.QLineEdit() - connection_type_socks_layout = QtWidgets.QHBoxLayout() - connection_type_socks_layout.addWidget(gui_settings_socks_label) - connection_type_socks_layout.addWidget(self.connection_type_socks_address) - connection_type_socks_layout.addWidget(self.connection_type_socks_port) - - self.connection_type_socks = QtWidgets.QWidget() - self.connection_type_socks.setLayout(connection_type_socks_layout) - self.connection_type_socks.hide() - - # Authentication options - - # No authentication - self.authenticate_no_auth_radio = QtWidgets.QRadioButton( - strings._("gui_settings_authenticate_no_auth_option") - ) - self.authenticate_no_auth_radio.toggled.connect( - self.authenticate_no_auth_toggled - ) - - # Password - self.authenticate_password_radio = QtWidgets.QRadioButton( - strings._("gui_settings_authenticate_password_option") - ) - self.authenticate_password_radio.toggled.connect( - self.authenticate_password_toggled - ) - - authenticate_password_extras_label = QtWidgets.QLabel( - strings._("gui_settings_password_label") - ) - self.authenticate_password_extras_password = QtWidgets.QLineEdit("") - authenticate_password_extras_layout = QtWidgets.QHBoxLayout() - authenticate_password_extras_layout.addWidget( - authenticate_password_extras_label - ) - authenticate_password_extras_layout.addWidget( - self.authenticate_password_extras_password - ) - - self.authenticate_password_extras = QtWidgets.QWidget() - self.authenticate_password_extras.setLayout(authenticate_password_extras_layout) - self.authenticate_password_extras.hide() - - # Authentication options layout - authenticate_group_layout = QtWidgets.QVBoxLayout() - authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) - authenticate_group_layout.addWidget(self.authenticate_password_radio) - authenticate_group_layout.addWidget(self.authenticate_password_extras) - self.authenticate_group = QtWidgets.QGroupBox( - strings._("gui_settings_authenticate_label") - ) - self.authenticate_group.setLayout(authenticate_group_layout) - - # Put the radios into their own group so they are exclusive - connection_type_radio_group_layout = QtWidgets.QVBoxLayout() - connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio) - connection_type_radio_group_layout.addWidget( - self.connection_type_automatic_radio - ) - connection_type_radio_group_layout.addWidget( - self.connection_type_control_port_radio - ) - connection_type_radio_group_layout.addWidget( - self.connection_type_socket_file_radio - ) - connection_type_radio_group = QtWidgets.QGroupBox( - strings._("gui_settings_connection_type_label") - ) - connection_type_radio_group.setLayout(connection_type_radio_group_layout) - - # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) - connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() - connection_type_bridges_radio_group_layout.addWidget(self.bridges) - self.connection_type_bridges_radio_group = QtWidgets.QGroupBox( - strings._("gui_settings_tor_bridges") - ) - self.connection_type_bridges_radio_group.setLayout( - connection_type_bridges_radio_group_layout - ) - self.connection_type_bridges_radio_group.hide() - - # Test tor settings button - self.connection_type_test_button = QtWidgets.QPushButton( - strings._("gui_settings_connection_type_test_button") - ) - self.connection_type_test_button.clicked.connect(self.test_tor_clicked) - connection_type_test_button_layout = QtWidgets.QHBoxLayout() - connection_type_test_button_layout.addWidget(self.connection_type_test_button) - connection_type_test_button_layout.addStretch() - - # Connection type layout - connection_type_layout = QtWidgets.QVBoxLayout() - connection_type_layout.addWidget(self.connection_type_control_port_extras) - connection_type_layout.addWidget(self.connection_type_socket_file_extras) - connection_type_layout.addWidget(self.connection_type_socks) - connection_type_layout.addWidget(self.authenticate_group) - connection_type_layout.addWidget(self.connection_type_bridges_radio_group) - connection_type_layout.addLayout(connection_type_test_button_layout) - # Buttons self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) @@ -441,22 +153,8 @@ class SettingsDialog(QtWidgets.QDialog): buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) - # Tor network connection status - self.tor_status = QtWidgets.QLabel() - self.tor_status.setStyleSheet(self.common.gui.css["settings_tor_status"]) - self.tor_status.hide() - # Layout - tor_layout = QtWidgets.QVBoxLayout() - tor_layout.addWidget(connection_type_radio_group) - tor_layout.addLayout(connection_type_layout) - tor_layout.addWidget(self.tor_status) - tor_layout.addStretch() - layout = QtWidgets.QVBoxLayout() - if not self.hide_tor_settings: - layout.addLayout(tor_layout) - layout.addSpacing(20) layout.addWidget(autoupdate_group) if autoupdate_group.isVisible(): layout.addSpacing(20) @@ -493,257 +191,6 @@ class SettingsDialog(QtWidgets.QDialog): theme_choice = self.old_settings.get("theme") self.theme_combobox.setCurrentIndex(theme_choice) - connection_type = self.old_settings.get("connection_type") - if connection_type == "bundled": - if self.connection_type_bundled_radio.isEnabled(): - self.connection_type_bundled_radio.setChecked(True) - else: - # If bundled tor is disabled, fallback to automatic - self.connection_type_automatic_radio.setChecked(True) - elif connection_type == "automatic": - self.connection_type_automatic_radio.setChecked(True) - elif connection_type == "control_port": - self.connection_type_control_port_radio.setChecked(True) - elif connection_type == "socket_file": - self.connection_type_socket_file_radio.setChecked(True) - self.connection_type_control_port_extras_address.setText( - self.old_settings.get("control_port_address") - ) - self.connection_type_control_port_extras_port.setText( - str(self.old_settings.get("control_port_port")) - ) - self.connection_type_socket_file_extras_path.setText( - self.old_settings.get("socket_file_path") - ) - self.connection_type_socks_address.setText( - self.old_settings.get("socks_address") - ) - self.connection_type_socks_port.setText( - str(self.old_settings.get("socks_port")) - ) - auth_type = self.old_settings.get("auth_type") - if auth_type == "no_auth": - self.authenticate_no_auth_radio.setChecked(True) - elif auth_type == "password": - self.authenticate_password_radio.setChecked(True) - self.authenticate_password_extras_password.setText( - self.old_settings.get("auth_password") - ) - - if self.old_settings.get("no_bridges"): - self.tor_bridges_no_bridges_radio.setChecked(True) - self.tor_bridges_use_obfs4_radio.setChecked(False) - self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) - self.tor_bridges_use_custom_radio.setChecked(False) - else: - self.tor_bridges_no_bridges_radio.setChecked(False) - self.tor_bridges_use_obfs4_radio.setChecked( - self.old_settings.get("tor_bridges_use_obfs4") - ) - self.tor_bridges_use_meek_lite_azure_radio.setChecked( - self.old_settings.get("tor_bridges_use_meek_lite_azure") - ) - - if self.old_settings.get("tor_bridges_use_custom_bridges"): - self.tor_bridges_use_custom_radio.setChecked(True) - # Remove the 'Bridge' lines at the start of each bridge. - # They are added automatically to provide compatibility with - # copying/pasting bridges provided from https://bridges.torproject.org - new_bridges = [] - bridges = self.old_settings.get("tor_bridges_use_custom_bridges").split( - "Bridge " - ) - for bridge in bridges: - new_bridges.append(bridge) - new_bridges = "".join(new_bridges) - self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) - - def connection_type_bundled_toggled(self, checked): - """ - Connection type bundled was toggled. If checked, hide authentication fields. - """ - self.common.log("SettingsDialog", "connection_type_bundled_toggled") - if self.hide_tor_settings: - return - if checked: - self.authenticate_group.hide() - self.connection_type_socks.hide() - self.connection_type_bridges_radio_group.show() - - def tor_bridges_no_bridges_radio_toggled(self, checked): - """ - 'No bridges' option was toggled. If checked, enable other bridge options. - """ - if self.hide_tor_settings: - return - if checked: - self.tor_bridges_use_custom_textbox_options.hide() - - def tor_bridges_use_obfs4_radio_toggled(self, checked): - """ - obfs4 bridges option was toggled. If checked, disable custom bridge options. - """ - if self.hide_tor_settings: - return - if checked: - self.tor_bridges_use_custom_textbox_options.hide() - - def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked): - """ - meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. - """ - if self.hide_tor_settings: - return - if checked: - self.tor_bridges_use_custom_textbox_options.hide() - # Alert the user about meek's costliness if it looks like they're turning it on - if not self.old_settings.get("tor_bridges_use_meek_lite_azure"): - Alert( - self.common, - strings._("gui_settings_meek_lite_expensive_warning"), - QtWidgets.QMessageBox.Warning, - ) - - def tor_bridges_use_custom_radio_toggled(self, checked): - """ - Custom bridges option was toggled. If checked, show custom bridge options. - """ - if self.hide_tor_settings: - return - if checked: - self.tor_bridges_use_custom_textbox_options.show() - - def connection_type_automatic_toggled(self, checked): - """ - Connection type automatic was toggled. If checked, hide authentication fields. - """ - self.common.log("SettingsDialog", "connection_type_automatic_toggled") - if self.hide_tor_settings: - return - if checked: - self.authenticate_group.hide() - self.connection_type_socks.hide() - self.connection_type_bridges_radio_group.hide() - - def connection_type_control_port_toggled(self, checked): - """ - Connection type control port was toggled. If checked, show extra fields - for Tor control address and port. If unchecked, hide those extra fields. - """ - self.common.log("SettingsDialog", "connection_type_control_port_toggled") - if self.hide_tor_settings: - return - if checked: - self.authenticate_group.show() - self.connection_type_control_port_extras.show() - self.connection_type_socks.show() - self.connection_type_bridges_radio_group.hide() - else: - self.connection_type_control_port_extras.hide() - - def connection_type_socket_file_toggled(self, checked): - """ - Connection type socket file was toggled. If checked, show extra fields - for socket file. If unchecked, hide those extra fields. - """ - self.common.log("SettingsDialog", "connection_type_socket_file_toggled") - if self.hide_tor_settings: - return - if checked: - self.authenticate_group.show() - self.connection_type_socket_file_extras.show() - self.connection_type_socks.show() - self.connection_type_bridges_radio_group.hide() - else: - self.connection_type_socket_file_extras.hide() - - def authenticate_no_auth_toggled(self, checked): - """ - Authentication option no authentication was toggled. - """ - self.common.log("SettingsDialog", "authenticate_no_auth_toggled") - - def authenticate_password_toggled(self, checked): - """ - Authentication option password was toggled. If checked, show extra fields - for password auth. If unchecked, hide those extra fields. - """ - self.common.log("SettingsDialog", "authenticate_password_toggled") - if checked: - self.authenticate_password_extras.show() - else: - self.authenticate_password_extras.hide() - - def test_tor_clicked(self): - """ - Test Tor Settings button clicked. With the given settings, see if we can - successfully connect and authenticate to Tor. - """ - self.common.log("SettingsDialog", "test_tor_clicked") - settings = self.settings_from_fields() - - try: - # Show Tor connection status if connection type is bundled tor - if settings.get("connection_type") == "bundled": - self.tor_status.show() - self._disable_buttons() - - def tor_status_update_func(progress, summary): - self._tor_status_update(progress, summary) - return True - - else: - tor_status_update_func = None - - onion = Onion( - self.common, - use_tmp_dir=True, - get_tor_paths=self.common.gui.get_tor_paths, - ) - onion.connect( - custom_settings=settings, - tor_status_update_func=tor_status_update_func, - ) - - # If an exception hasn't been raised yet, the Tor settings work - Alert( - self.common, - strings._("settings_test_success").format( - onion.tor_version, - onion.supports_ephemeral, - onion.supports_stealth, - onion.supports_v3_onions, - ), - ) - - # Clean up - onion.cleanup() - - except ( - TorErrorInvalidSetting, - TorErrorAutomatic, - TorErrorSocketPort, - TorErrorSocketFile, - TorErrorMissingPassword, - TorErrorUnreadableCookieFile, - TorErrorAuthError, - TorErrorProtocolError, - BundledTorTimeout, - BundledTorBroken, - TorTooOldEphemeral, - TorTooOldStealth, - PortNotAvailable, - ) as e: - message = self.common.gui.get_translated_tor_error(e) - Alert( - self.common, - message, - QtWidgets.QMessageBox.Warning, - ) - if settings.get("connection_type") == "bundled": - self.tor_status.hide() - self._enable_buttons() - def check_for_updates(self): """ Check for Updates button clicked. Manually force an update check. @@ -847,74 +294,8 @@ class SettingsDialog(QtWidgets.QDialog): # Save the new settings settings.save() - - # If Tor isn't connected, or if Tor settings have changed, Reinitialize - # the Onion object - reboot_onion = False - if not self.common.gui.local_only: - if self.common.gui.onion.is_authenticated(): - self.common.log( - "SettingsDialog", "save_clicked", "Connected to Tor" - ) - - if changed( - settings, - self.old_settings, - [ - "connection_type", - "control_port_address", - "control_port_port", - "socks_address", - "socks_port", - "socket_file_path", - "auth_type", - "auth_password", - "no_bridges", - "tor_bridges_use_obfs4", - "tor_bridges_use_meek_lite_azure", - "tor_bridges_use_custom_bridges", - ], - ): - - reboot_onion = True - - else: - self.common.log( - "SettingsDialog", "save_clicked", "Not connected to Tor" - ) - # Tor isn't connected, so try connecting - reboot_onion = True - - # Do we need to reinitialize Tor? - if reboot_onion: - # Reinitialize the Onion object - self.common.log( - "SettingsDialog", "save_clicked", "rebooting the Onion" - ) - self.common.gui.onion.cleanup() - - tor_con = TorConnectionDialog(self.common, settings) - tor_con.start() - - self.common.log( - "SettingsDialog", - "save_clicked", - f"Onion done rebooting, connected to Tor: {self.common.gui.onion.connected_to_tor}", - ) - - if ( - self.common.gui.onion.is_authenticated() - and not tor_con.wasCanceled() - ): - self.settings_saved.emit() - self.close() - - else: - self.settings_saved.emit() - self.close() - else: - self.settings_saved.emit() - self.close() + self.settings_saved.emit() + self.close() def cancel_clicked(self): """ @@ -963,112 +344,8 @@ class SettingsDialog(QtWidgets.QDialog): locale = self.language_combobox.itemData(locale_index) settings.set("locale", locale) - # Tor connection - if self.connection_type_bundled_radio.isChecked(): - settings.set("connection_type", "bundled") - if self.connection_type_automatic_radio.isChecked(): - settings.set("connection_type", "automatic") - if self.connection_type_control_port_radio.isChecked(): - settings.set("connection_type", "control_port") - if self.connection_type_socket_file_radio.isChecked(): - settings.set("connection_type", "socket_file") - - if self.autoupdate_checkbox.isChecked(): - settings.set("use_autoupdate", True) - else: - settings.set("use_autoupdate", False) - - settings.set( - "control_port_address", - self.connection_type_control_port_extras_address.text(), - ) - settings.set( - "control_port_port", self.connection_type_control_port_extras_port.text() - ) - settings.set( - "socket_file_path", self.connection_type_socket_file_extras_path.text() - ) - - settings.set("socks_address", self.connection_type_socks_address.text()) - settings.set("socks_port", self.connection_type_socks_port.text()) - - if self.authenticate_no_auth_radio.isChecked(): - settings.set("auth_type", "no_auth") - if self.authenticate_password_radio.isChecked(): - settings.set("auth_type", "password") - - settings.set("auth_password", self.authenticate_password_extras_password.text()) - - # Whether we use bridges - if self.tor_bridges_no_bridges_radio.isChecked(): - settings.set("no_bridges", True) - settings.set("tor_bridges_use_obfs4", False) - settings.set("tor_bridges_use_meek_lite_azure", False) - settings.set("tor_bridges_use_custom_bridges", "") - if self.tor_bridges_use_obfs4_radio.isChecked(): - settings.set("no_bridges", False) - settings.set("tor_bridges_use_obfs4", True) - settings.set("tor_bridges_use_meek_lite_azure", False) - settings.set("tor_bridges_use_custom_bridges", "") - if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): - settings.set("no_bridges", False) - settings.set("tor_bridges_use_obfs4", False) - settings.set("tor_bridges_use_meek_lite_azure", True) - settings.set("tor_bridges_use_custom_bridges", "") - if self.tor_bridges_use_custom_radio.isChecked(): - settings.set("no_bridges", False) - settings.set("tor_bridges_use_obfs4", False) - settings.set("tor_bridges_use_meek_lite_azure", False) - - # Insert a 'Bridge' line at the start of each bridge. - # This makes it easier to copy/paste a set of bridges - # provided from https://bridges.torproject.org - new_bridges = [] - bridges = self.tor_bridges_use_custom_textbox.toPlainText().split("\n") - bridges_valid = False - for bridge in bridges: - if bridge != "": - # Check the syntax of the custom bridge to make sure it looks legitimate - ipv4_pattern = re.compile( - "(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$" - ) - ipv6_pattern = re.compile( - "(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$" - ) - meek_lite_pattern = re.compile( - "(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)" - ) - if ( - ipv4_pattern.match(bridge) - or ipv6_pattern.match(bridge) - or meek_lite_pattern.match(bridge) - ): - new_bridges.append("".join(["Bridge ", bridge, "\n"])) - bridges_valid = True - - if bridges_valid: - new_bridges = "".join(new_bridges) - settings.set("tor_bridges_use_custom_bridges", new_bridges) - else: - Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) - settings.set("no_bridges", True) - return False - return settings - def closeEvent(self, e): - self.common.log("SettingsDialog", "closeEvent") - - # On close, if Tor isn't connected, then quit OnionShare altogether - if not self.common.gui.local_only: - if not self.common.gui.onion.is_authenticated(): - self.common.log( - "SettingsDialog", "closeEvent", "Closing while not connected to Tor" - ) - - # Wait 1ms for the event loop to finish, then quit - QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit) - def _update_autoupdate_timestamp(self, autoupdate_timestamp): self.common.log("SettingsDialog", "_update_autoupdate_timestamp") @@ -1081,20 +358,10 @@ class SettingsDialog(QtWidgets.QDialog): strings._("gui_settings_autoupdate_timestamp").format(last_checked) ) - def _tor_status_update(self, progress, summary): - self.tor_status.setText( - f"{strings._('connecting_to_tor')}
{progress}% {summary}" - ) - self.common.gui.qtapp.processEvents() - if "Done" in summary: - self.tor_status.hide() - self._enable_buttons() - def _disable_buttons(self): self.common.log("SettingsDialog", "_disable_buttons") self.check_for_updates_button.setEnabled(False) - self.connection_type_test_button.setEnabled(False) self.save_button.setEnabled(False) self.cancel_button.setEnabled(False) @@ -1105,6 +372,5 @@ class SettingsDialog(QtWidgets.QDialog): self.check_for_updates_button.setEnabled(False) else: self.check_for_updates_button.setEnabled(True) - self.connection_type_test_button.setEnabled(True) self.save_button.setEnabled(True) self.cancel_button.setEnabled(True) From 17beca1692105681f7b1fa6b08eeffc6fd73d997 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 13 Oct 2021 19:19:53 -0700 Subject: [PATCH 07/26] Open Tor settings when canceling connecting to tor --- desktop/src/onionshare/main_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/onionshare/main_window.py b/desktop/src/onionshare/main_window.py index 0e7e3f88..b3f8b6c9 100644 --- a/desktop/src/onionshare/main_window.py +++ b/desktop/src/onionshare/main_window.py @@ -220,7 +220,7 @@ class MainWindow(QtWidgets.QMainWindow): "_tor_connection_canceled", "Settings button clicked", ) - self.open_settings() + self.open_tor_settings() if a.clickedButton() == quit_button: # Quit From c4a038720b5d893a612db79ff1a9c90fc81483a6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 13 Oct 2021 19:49:51 -0700 Subject: [PATCH 08/26] Change some Tor settings language, and combine various settings into a single "Tor settings" group box --- .../src/onionshare/resources/locale/en.json | 6 +- desktop/src/onionshare/tor_settings_dialog.py | 65 +++++++------------ 2 files changed, 27 insertions(+), 44 deletions(-) diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index ca3f8a8f..f0dd7a6a 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -50,18 +50,18 @@ "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_automatic_option": "Attempt auto-configuration with Tor Browser", + "gui_settings_controller_extras_label": "Tor settings", "gui_settings_connection_type_control_port_option": "Connect using control port", "gui_settings_connection_type_socket_file_option": "Connect using socket file", "gui_settings_connection_type_test_button": "Test Connection to Tor", "gui_settings_control_port_label": "Control port", "gui_settings_socket_file_label": "Socket file", "gui_settings_socks_label": "SOCKS port", - "gui_settings_authenticate_label": "Tor authentication settings", "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Password", "gui_settings_password_label": "Password", - "gui_settings_tor_bridges": "Tor bridge support", - "gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges", + "gui_settings_tor_bridges": "Would you like to Use a Tor bridge?", + "gui_settings_tor_bridges_no_bridges_radio_option": "Don't use a bridge", "gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Use built-in obfs4 pluggable transports (requires obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports", diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index fbf93044..817fc599 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -267,23 +267,13 @@ class TorSettingsDialog(QtWidgets.QDialog): self.connection_type_socks.hide() # Authentication options - - # No authentication - self.authenticate_no_auth_radio = QtWidgets.QRadioButton( + self.authenticate_no_auth_checkbox = QtWidgets.QCheckBox( strings._("gui_settings_authenticate_no_auth_option") ) - self.authenticate_no_auth_radio.toggled.connect( + self.authenticate_no_auth_checkbox.toggled.connect( self.authenticate_no_auth_toggled ) - # Password - self.authenticate_password_radio = QtWidgets.QRadioButton( - strings._("gui_settings_authenticate_password_option") - ) - self.authenticate_password_radio.toggled.connect( - self.authenticate_password_toggled - ) - authenticate_password_extras_label = QtWidgets.QLabel( strings._("gui_settings_password_label") ) @@ -300,15 +290,18 @@ class TorSettingsDialog(QtWidgets.QDialog): self.authenticate_password_extras.setLayout(authenticate_password_extras_layout) self.authenticate_password_extras.hide() - # Authentication options layout - authenticate_group_layout = QtWidgets.QVBoxLayout() - authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) - authenticate_group_layout.addWidget(self.authenticate_password_radio) - authenticate_group_layout.addWidget(self.authenticate_password_extras) - self.authenticate_group = QtWidgets.QGroupBox( - strings._("gui_settings_authenticate_label") + # Group for Tor settings + tor_settings_layout = QtWidgets.QVBoxLayout() + tor_settings_layout.addWidget(self.connection_type_control_port_extras) + tor_settings_layout.addWidget(self.connection_type_socket_file_extras) + tor_settings_layout.addWidget(self.connection_type_socks) + tor_settings_layout.addWidget(self.authenticate_no_auth_checkbox) + tor_settings_layout.addWidget(self.authenticate_password_extras) + self.tor_settings_group = QtWidgets.QGroupBox( + strings._("gui_settings_controller_extras_label") ) - self.authenticate_group.setLayout(authenticate_group_layout) + self.tor_settings_group.setLayout(tor_settings_layout) + self.tor_settings_group.hide() # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() @@ -349,10 +342,7 @@ class TorSettingsDialog(QtWidgets.QDialog): # Connection type layout connection_type_layout = QtWidgets.QVBoxLayout() - connection_type_layout.addWidget(self.connection_type_control_port_extras) - connection_type_layout.addWidget(self.connection_type_socket_file_extras) - connection_type_layout.addWidget(self.connection_type_socks) - connection_type_layout.addWidget(self.authenticate_group) + connection_type_layout.addWidget(self.tor_settings_group) connection_type_layout.addWidget(self.connection_type_bridges_radio_group) connection_type_layout.addLayout(connection_type_test_button_layout) @@ -421,9 +411,9 @@ class TorSettingsDialog(QtWidgets.QDialog): ) auth_type = self.old_settings.get("auth_type") if auth_type == "no_auth": - self.authenticate_no_auth_radio.setChecked(True) - elif auth_type == "password": - self.authenticate_password_radio.setChecked(True) + self.authenticate_no_auth_checkbox.setCheckState(QtCore.Qt.Checked) + else: + self.authenticate_no_auth_checkbox.setChecked(QtCore.Qt.Unchecked) self.authenticate_password_extras_password.setText( self.old_settings.get("auth_password") ) @@ -458,11 +448,11 @@ class TorSettingsDialog(QtWidgets.QDialog): def connection_type_bundled_toggled(self, checked): """ - Connection type bundled was toggled. If checked, hide authentication fields. + Connection type bundled was toggled """ self.common.log("TorSettingsDialog", "connection_type_bundled_toggled") if checked: - self.authenticate_group.hide() + self.tor_settings_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.show() @@ -507,7 +497,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ self.common.log("TorSettingsDialog", "connection_type_automatic_toggled") if checked: - self.authenticate_group.hide() + self.tor_settings_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.hide() @@ -518,7 +508,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ self.common.log("TorSettingsDialog", "connection_type_control_port_toggled") if checked: - self.authenticate_group.show() + self.tor_settings_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() @@ -532,7 +522,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ self.common.log("TorSettingsDialog", "connection_type_socket_file_toggled") if checked: - self.authenticate_group.show() + self.tor_settings_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() @@ -544,17 +534,10 @@ class TorSettingsDialog(QtWidgets.QDialog): Authentication option no authentication was toggled. """ self.common.log("TorSettingsDialog", "authenticate_no_auth_toggled") - - def authenticate_password_toggled(self, checked): - """ - Authentication option password was toggled. If checked, show extra fields - for password auth. If unchecked, hide those extra fields. - """ - self.common.log("TorSettingsDialog", "authenticate_password_toggled") if checked: - self.authenticate_password_extras.show() - else: self.authenticate_password_extras.hide() + else: + self.authenticate_password_extras.show() def test_tor_clicked(self): """ From 496adf01d2d097218e23c37a5b664ba587c96e91 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 13 Oct 2021 20:14:38 -0700 Subject: [PATCH 09/26] When you click "Test Connecting to Tor" in Tor settings, it now uses the TorConnectionDialog --- .../src/onionshare/tor_connection_dialog.py | 53 +++++++---- desktop/src/onionshare/tor_settings_dialog.py | 93 +++---------------- desktop/src/onionshare/widgets.py | 3 +- 3 files changed, 50 insertions(+), 99 deletions(-) diff --git a/desktop/src/onionshare/tor_connection_dialog.py b/desktop/src/onionshare/tor_connection_dialog.py index b5c2f61c..dd2721bd 100644 --- a/desktop/src/onionshare/tor_connection_dialog.py +++ b/desktop/src/onionshare/tor_connection_dialog.py @@ -49,11 +49,15 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): """ open_settings = QtCore.Signal() + success = QtCore.Signal() - def __init__(self, common, custom_settings=False): + def __init__( + self, common, custom_settings=False, testing_settings=False, onion=None + ): super(TorConnectionDialog, self).__init__(None) self.common = common + self.testing_settings = testing_settings if custom_settings: self.settings = custom_settings @@ -62,7 +66,15 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.common.log("TorConnectionDialog", "__init__") - self.setWindowTitle("OnionShare") + if self.testing_settings: + self.title = strings._("gui_settings_connection_type_test_button") + self.onion = onion + else: + self.title = "OnionShare" + self.onion = self.common.gui.onion + + self.setWindowTitle(self.title) + self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))) self.setModal(True) self.setFixedSize(400, 150) @@ -112,7 +124,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): def _canceled_connecting_to_tor(self): self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor") self.active = False - self.common.gui.onion.cleanup() + self.onion.cleanup() # Cancel connecting to Tor QtCore.QTimer.singleShot(1, self.cancel) @@ -121,18 +133,25 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.common.log("TorConnectionDialog", "_error_connecting_to_tor") self.active = False - def alert_and_open_settings(): - # Display the exception in an alert box - Alert( - self.common, - f"{msg}\n\n{strings._('gui_tor_connection_error_settings')}", - QtWidgets.QMessageBox.Warning, - ) + if self.testing_settings: + # If testing, just display the error but don't open settings + def alert(): + Alert(self.common, msg, QtWidgets.QMessageBox.Warning, title=self.title) - # Open settings - self.open_settings.emit() + else: + # If not testing, open settings after displaying the error + def alert(): + Alert( + self.common, + f"{msg}\n\n{strings._('gui_tor_connection_error_settings')}", + QtWidgets.QMessageBox.Warning, + title=self.title, + ) - QtCore.QTimer.singleShot(1, alert_and_open_settings) + # Open settings + self.open_settings.emit() + + QtCore.QTimer.singleShot(1, alert) # Cancel connecting to Tor QtCore.QTimer.singleShot(1, self.cancel) @@ -146,13 +165,9 @@ class TorConnectionThread(QtCore.QThread): def __init__(self, common, settings, dialog): super(TorConnectionThread, self).__init__() - self.common = common - self.common.log("TorConnectionThread", "__init__") - self.settings = settings - self.dialog = dialog def run(self): @@ -160,8 +175,8 @@ class TorConnectionThread(QtCore.QThread): # Connect to the Onion try: - self.common.gui.onion.connect(self.settings, False, self._tor_status_update) - if self.common.gui.onion.connected_to_tor: + self.dialog.onion.connect(self.settings, False, self._tor_status_update) + if self.dialog.onion.connected_to_tor: self.connected_to_tor.emit() else: self.canceled_connecting_to_tor.emit() diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index 817fc599..60b006be 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -358,16 +358,10 @@ class TorSettingsDialog(QtWidgets.QDialog): buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) - # Tor network connection status - self.tor_status = QtWidgets.QLabel() - self.tor_status.setStyleSheet(self.common.gui.css["settings_tor_status"]) - self.tor_status.hide() - # Layout layout = QtWidgets.QVBoxLayout() layout.addWidget(connection_type_radio_group) layout.addLayout(connection_type_layout) - layout.addWidget(self.tor_status) layout.addStretch() layout.addLayout(buttons_layout) @@ -547,30 +541,17 @@ class TorSettingsDialog(QtWidgets.QDialog): self.common.log("TorSettingsDialog", "test_tor_clicked") settings = self.settings_from_fields() - try: - # Show Tor connection status if connection type is bundled tor - if settings.get("connection_type") == "bundled": - self.tor_status.show() - self._disable_buttons() + onion = Onion( + self.common, + use_tmp_dir=True, + get_tor_paths=self.common.gui.get_tor_paths, + ) - def tor_status_update_func(progress, summary): - self._tor_status_update(progress, summary) - return True + tor_con = TorConnectionDialog(self.common, settings, True, onion) + tor_con.start() - else: - tor_status_update_func = None - - onion = Onion( - self.common, - use_tmp_dir=True, - get_tor_paths=self.common.gui.get_tor_paths, - ) - onion.connect( - custom_settings=settings, - tor_status_update_func=tor_status_update_func, - ) - - # If an exception hasn't been raised yet, the Tor settings work + # If Tor settings worked, show results + if onion.connected_to_tor: Alert( self.common, strings._("settings_test_success").format( @@ -579,35 +560,11 @@ class TorSettingsDialog(QtWidgets.QDialog): onion.supports_stealth, onion.supports_v3_onions, ), + title=strings._("gui_settings_connection_type_test_button"), ) - # Clean up - onion.cleanup() - - except ( - TorErrorInvalidSetting, - TorErrorAutomatic, - TorErrorSocketPort, - TorErrorSocketFile, - TorErrorMissingPassword, - TorErrorUnreadableCookieFile, - TorErrorAuthError, - TorErrorProtocolError, - BundledTorTimeout, - BundledTorBroken, - TorTooOldEphemeral, - TorTooOldStealth, - PortNotAvailable, - ) as e: - message = self.common.gui.get_translated_tor_error(e) - Alert( - self.common, - message, - QtWidgets.QMessageBox.Warning, - ) - if settings.get("connection_type") == "bundled": - self.tor_status.hide() - self._enable_buttons() + # Clean up + onion.cleanup() def save_clicked(self): """ @@ -748,9 +705,9 @@ class TorSettingsDialog(QtWidgets.QDialog): settings.set("socks_address", self.connection_type_socks_address.text()) settings.set("socks_port", self.connection_type_socks_port.text()) - if self.authenticate_no_auth_radio.isChecked(): + if self.authenticate_no_auth_checkbox.checkState() == QtCore.Qt.Checked: settings.set("auth_type", "no_auth") - if self.authenticate_password_radio.isChecked(): + else: settings.set("auth_type", "password") settings.set("auth_password", self.authenticate_password_extras_password.text()) @@ -826,25 +783,3 @@ class TorSettingsDialog(QtWidgets.QDialog): # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit) - - def _tor_status_update(self, progress, summary): - self.tor_status.setText( - f"{strings._('connecting_to_tor')}
{progress}% {summary}" - ) - self.common.gui.qtapp.processEvents() - if "Done" in summary: - self.tor_status.hide() - self._enable_buttons() - - def _disable_buttons(self): - self.common.log("TorSettingsDialog", "_disable_buttons") - - self.connection_type_test_button.setEnabled(False) - self.save_button.setEnabled(False) - self.cancel_button.setEnabled(False) - - def _enable_buttons(self): - self.common.log("TorSettingsDialog", "_enable_buttons") - self.connection_type_test_button.setEnabled(True) - self.save_button.setEnabled(True) - self.cancel_button.setEnabled(True) diff --git a/desktop/src/onionshare/widgets.py b/desktop/src/onionshare/widgets.py index b396c43f..761df212 100644 --- a/desktop/src/onionshare/widgets.py +++ b/desktop/src/onionshare/widgets.py @@ -37,6 +37,7 @@ class Alert(QtWidgets.QMessageBox): icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True, + title="OnionShare", ): super(Alert, self).__init__(None) @@ -44,7 +45,7 @@ class Alert(QtWidgets.QMessageBox): self.common.log("Alert", "__init__") - self.setWindowTitle("OnionShare") + self.setWindowTitle(title) self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))) self.setText(message) self.setIcon(icon) From 67d61d2c5374ffaf14b585f6d3ced16a99283a82 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 13 Oct 2021 20:27:17 -0700 Subject: [PATCH 10/26] Move test tor button to the bottons layout at the bottom --- desktop/src/onionshare/gui_common.py | 12 +----------- desktop/src/onionshare/tor_settings_dialog.py | 15 +++++---------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index 1dffab26..a9539714 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -392,25 +392,15 @@ class GuiCommon: QPushButton { padding: 5px 10px; }""", - # Settings dialog + # Settings dialogs "settings_version": """ QLabel { color: #666666; }""", - "settings_tor_status": """ - QLabel { - background-color: #ffffff; - color: #000000; - padding: 10px; - }""", "settings_whats_this": """ QLabel { font-size: 12px; }""", - "settings_connect_to_tor": """ - QLabel { - font-style: italic; - }""", } def get_tor_paths(self): diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index 60b006be..a1b02313 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -331,22 +331,16 @@ class TorSettingsDialog(QtWidgets.QDialog): ) self.connection_type_bridges_radio_group.hide() - # Test tor settings button - self.connection_type_test_button = QtWidgets.QPushButton( - strings._("gui_settings_connection_type_test_button") - ) - self.connection_type_test_button.clicked.connect(self.test_tor_clicked) - connection_type_test_button_layout = QtWidgets.QHBoxLayout() - connection_type_test_button_layout.addWidget(self.connection_type_test_button) - connection_type_test_button_layout.addStretch() - # Connection type layout connection_type_layout = QtWidgets.QVBoxLayout() connection_type_layout.addWidget(self.tor_settings_group) connection_type_layout.addWidget(self.connection_type_bridges_radio_group) - connection_type_layout.addLayout(connection_type_test_button_layout) # Buttons + self.test_tor_button = QtWidgets.QPushButton( + strings._("gui_settings_connection_type_test_button") + ) + self.test_tor_button.clicked.connect(self.test_tor_clicked) self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) self.cancel_button = QtWidgets.QPushButton( @@ -354,6 +348,7 @@ class TorSettingsDialog(QtWidgets.QDialog): ) self.cancel_button.clicked.connect(self.cancel_clicked) buttons_layout = QtWidgets.QHBoxLayout() + buttons_layout.addWidget(self.test_tor_button) buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) From f2dbc972857a7963c82801ebae676b4a70f41c81 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 13 Oct 2021 20:34:11 -0700 Subject: [PATCH 11/26] Improve layout of SettingsDialog --- desktop/src/onionshare/gui_common.py | 2 +- .../src/onionshare/resources/locale/en.json | 2 +- desktop/src/onionshare/settings_dialog.py | 20 ++++++++++++------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index a9539714..cb10eb3b 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -395,7 +395,7 @@ class GuiCommon: # Settings dialogs "settings_version": """ QLabel { - color: #666666; + font-size: 16px; }""", "settings_whats_this": """ QLabel { diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index f0dd7a6a..38da16fb 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -126,7 +126,7 @@ "error_cannot_create_data_dir": "Could not create OnionShare data folder: {}", "gui_receive_mode_warning": "Receive mode lets people upload files to your computer.

Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.", "gui_open_folder_error": "Failed to open folder with xdg-open. The file is here: {}", - "gui_settings_language_label": "Preferred language", + "gui_settings_language_label": "Language", "gui_settings_theme_label": "Theme", "gui_settings_theme_auto": "Auto", "gui_settings_theme_light": "Light", diff --git a/desktop/src/onionshare/settings_dialog.py b/desktop/src/onionshare/settings_dialog.py index de5c6992..5bb7be1c 100644 --- a/desktop/src/onionshare/settings_dialog.py +++ b/desktop/src/onionshare/settings_dialog.py @@ -71,6 +71,17 @@ class SettingsDialog(QtWidgets.QDialog): self.system = platform.system() + # Header + version_label = QtWidgets.QLabel(f"OnionShare {self.common.version}") + version_label.setStyleSheet(self.common.gui.css["settings_version"]) + self.help_button = QtWidgets.QPushButton(strings._("gui_settings_button_help")) + self.help_button.clicked.connect(self.help_clicked) + header_layout = QtWidgets.QHBoxLayout() + header_layout.addStretch() + header_layout.addWidget(version_label) + header_layout.addWidget(self.help_button) + header_layout.addStretch() + # Automatic updates options # Autoupdate @@ -142,24 +153,19 @@ class SettingsDialog(QtWidgets.QDialog): strings._("gui_settings_button_cancel") ) self.cancel_button.clicked.connect(self.cancel_clicked) - version_label = QtWidgets.QLabel(f"OnionShare {self.common.version}") - version_label.setStyleSheet(self.common.gui.css["settings_version"]) - self.help_button = QtWidgets.QPushButton(strings._("gui_settings_button_help")) - self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() - buttons_layout.addWidget(version_label) - buttons_layout.addWidget(self.help_button) buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) # Layout layout = QtWidgets.QVBoxLayout() + layout.addLayout(header_layout) + layout.addSpacing(20) layout.addWidget(autoupdate_group) if autoupdate_group.isVisible(): layout.addSpacing(20) layout.addLayout(language_layout) - layout.addSpacing(20) layout.addLayout(theme_layout) layout.addSpacing(20) layout.addStretch() From 39d624e923d61dbbaf8d4216a4364c5b443d8802 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 13 Oct 2021 21:11:56 -0700 Subject: [PATCH 12/26] Allow selecting a snowflake bridge, and make it try to use the snowflake bridge --- cli/onionshare_cli/common.py | 6 ++ cli/onionshare_cli/onion.py | 5 ++ .../resources/torrc_template-meek_lite_amazon | 2 - cli/onionshare_cli/settings.py | 1 + desktop/src/onionshare/gui_common.py | 16 +++- .../src/onionshare/resources/locale/en.json | 12 +-- desktop/src/onionshare/tor_settings_dialog.py | 88 ++++++++++++++----- 7 files changed, 98 insertions(+), 32 deletions(-) delete mode 100644 cli/onionshare_cli/resources/torrc_template-meek_lite_amazon diff --git a/cli/onionshare_cli/common.py b/cli/onionshare_cli/common.py index 78da8882..945a75bb 100644 --- a/cli/onionshare_cli/common.py +++ b/cli/onionshare_cli/common.py @@ -312,6 +312,9 @@ class Common: # Look in resources first base_path = self.get_resource_path("tor") if os.path.exists(base_path): + self.log( + "Common", "get_tor_paths", f"using tor binaries in {base_path}" + ) tor_path = os.path.join(base_path, "tor") tor_geo_ip_file_path = os.path.join(base_path, "geoip") tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6") @@ -319,6 +322,9 @@ class Common: snowflake_file_path = os.path.join(base_path, "snowflake-client") else: # Fallback to looking in the path + self.log( + "Common", "get_tor_paths", f"using tor binaries in system path" + ) tor_path = shutil.which("tor") if not tor_path: raise CannotFindTor() diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index a0f967b9..a4453651 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -326,6 +326,11 @@ class Onion(object): ) as o: for line in o: f.write(line) + elif self.settings.get("tor_bridges_use_snowflake"): + # Taken from: tor-browser_en-US/Browser/TorBrowser/Data/Tor/torrc-defaults + f.write( + f"ClientTransportPlugin snowflake exec {self.snowflake_file_path} -url https://snowflake-broker.torproject.net.global.prod.fastly.net/ -front cdn.sstatic.net -ice stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478\n" + ) if self.settings.get("tor_bridges_use_custom_bridges"): if "obfs4" in self.settings.get("tor_bridges_use_custom_bridges"): diff --git a/cli/onionshare_cli/resources/torrc_template-meek_lite_amazon b/cli/onionshare_cli/resources/torrc_template-meek_lite_amazon deleted file mode 100644 index 606ae889..00000000 --- a/cli/onionshare_cli/resources/torrc_template-meek_lite_amazon +++ /dev/null @@ -1,2 +0,0 @@ -Bridge meek_lite 0.0.2.0:2 B9E7141C594AF25699E0079C1F0146F409495296 url=https://d2cly7j4zqgua7.cloudfront.net/ front=a0.awsstatic.com -UseBridges 1 \ No newline at end of file diff --git a/cli/onionshare_cli/settings.py b/cli/onionshare_cli/settings.py index 4755d5b3..37c00bb6 100644 --- a/cli/onionshare_cli/settings.py +++ b/cli/onionshare_cli/settings.py @@ -108,6 +108,7 @@ class Settings(object): "no_bridges": True, "tor_bridges_use_obfs4": False, "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_snowflake": False, "tor_bridges_use_custom_bridges": "", "persistent_tabs": [], "locale": None, # this gets defined in fill_in_defaults() diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index cb10eb3b..5634d5f6 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -405,7 +405,21 @@ class GuiCommon: def get_tor_paths(self): if self.common.platform == "Linux": - return self.common.get_tor_paths() + base_path = self.get_resource_path("tor") + if os.path.exists(base_path): + tor_path = os.path.join(base_path, "tor") + tor_geo_ip_file_path = os.path.join(base_path, "geoip") + tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6") + obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy") + snowflake_file_path = os.path.join(base_path, "snowflake-client") + else: + # Fallback to looking in the path + tor_path = shutil.which("tor") + obfs4proxy_file_path = shutil.which("obfs4proxy") + snowflake_file_path = shutil.which("snowflake-client") + prefix = os.path.dirname(os.path.dirname(tor_path)) + tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") + tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6") if self.common.platform == "Windows": base_path = self.get_resource_path("tor") diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 38da16fb..3d6c8539 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -60,13 +60,13 @@ "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Password", "gui_settings_password_label": "Password", - "gui_settings_tor_bridges": "Would you like to Use a Tor bridge?", + "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_tor_bridges_no_bridges_radio_option": "Don't use a bridge", - "gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Use built-in obfs4 pluggable transports (requires obfs4proxy)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Warning: The meek_lite 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_tor_bridges_obfs4_radio_option": "Use built-in obfs4 bridge", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek-azure bridge", + "gui_settings_tor_bridges_snowflake_radio_option": "Use built-in snowflake 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.", "gui_settings_tor_bridges_custom_radio_option": "Use custom bridges", "gui_settings_tor_bridges_custom_label": "You can get bridges from https://bridges.torproject.org", "gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.", diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index a1b02313..00d221a7 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -89,6 +89,9 @@ class TorSettingsDialog(QtWidgets.QDialog): # Bridge options for bundled tor + bridges_label = QtWidgets.QLabel(strings._("gui_settings_tor_bridges_label")) + bridges_label.setWordWrap(True) + # No bridges option radio self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( strings._("gui_settings_tor_bridges_no_bridges_radio_option") @@ -107,39 +110,55 @@ class TorSettingsDialog(QtWidgets.QDialog): # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports - if not self.obfs4proxy_file_path or not os.path.isfile( - self.obfs4proxy_file_path - ): - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy") - ) - self.tor_bridges_use_obfs4_radio.setEnabled(False) - else: - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_obfs4_radio_option") - ) + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_obfs4_radio_option") + ) self.tor_bridges_use_obfs4_radio.toggled.connect( self.tor_bridges_use_obfs4_radio_toggled ) - - # meek_lite-azure option radio - # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path ): - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( - strings._( - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy" - ) - ) - self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) - else: - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option") + self.common.log( + "TorSettingsDialog", + "__init__", + f"missing binary {self.obfs4proxy_file_path}, hiding obfs4 bridge", ) + self.tor_bridges_use_obfs4_radio.hide() + + # meek-azure option radio + # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option") + ) self.tor_bridges_use_meek_lite_azure_radio.toggled.connect( self.tor_bridges_use_meek_lite_azure_radio_toggled ) + if not self.obfs4proxy_file_path or not os.path.isfile( + self.obfs4proxy_file_path + ): + self.common.log( + "TorSettingsDialog", + "__init__", + f"missing binary {self.obfs4proxy_file_path}, hiding meek-azure bridge", + ) + self.tor_bridges_use_meek_lite_azure_radio.hide() + + # snowflake option radio + # if the snowflake-client binary is missing, we can't use snowflake transports + self.tor_bridges_use_snowflake_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_snowflake_radio_option") + ) + self.tor_bridges_use_snowflake_radio.toggled.connect( + self.tor_bridges_use_snowflake_radio_toggled + ) + if not self.snowflake_file_path or not os.path.isfile(self.snowflake_file_path): + self.common.log( + "TorSettingsDialog", + "__init__", + f"missing binary {self.snowflake_file_path}, hiding snowflake bridge", + ) + self.tor_bridges_use_snowflake_radio.hide() # Custom bridges radio and textbox self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( @@ -178,9 +197,11 @@ class TorSettingsDialog(QtWidgets.QDialog): # Bridges layout/widget bridges_layout = QtWidgets.QVBoxLayout() + bridges_layout.addWidget(bridges_label) bridges_layout.addWidget(self.tor_bridges_no_bridges_radio) bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio) bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio) + bridges_layout.addWidget(self.tor_bridges_use_snowflake_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options) @@ -411,6 +432,7 @@ class TorSettingsDialog(QtWidgets.QDialog): self.tor_bridges_no_bridges_radio.setChecked(True) self.tor_bridges_use_obfs4_radio.setChecked(False) self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) + self.tor_bridges_use_snowflake_radio.setChecked(False) self.tor_bridges_use_custom_radio.setChecked(False) else: self.tor_bridges_no_bridges_radio.setChecked(False) @@ -420,6 +442,9 @@ class TorSettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_meek_lite_azure_radio.setChecked( self.old_settings.get("tor_bridges_use_meek_lite_azure") ) + self.tor_bridges_use_snowflake_radio.setChecked( + self.old_settings.get("tor_bridges_use_snowflake") + ) if self.old_settings.get("tor_bridges_use_custom_bridges"): self.tor_bridges_use_custom_radio.setChecked(True) @@ -473,6 +498,13 @@ class TorSettingsDialog(QtWidgets.QDialog): QtWidgets.QMessageBox.Warning, ) + def tor_bridges_use_snowflake_radio_toggled(self, checked): + """ + snowflake bridges option was toggled. If checked, disable custom bridge options. + """ + if checked: + self.tor_bridges_use_custom_textbox_options.hide() + def tor_bridges_use_custom_radio_toggled(self, checked): """ Custom bridges option was toggled. If checked, show custom bridge options. @@ -712,21 +744,31 @@ class TorSettingsDialog(QtWidgets.QDialog): settings.set("no_bridges", True) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", False) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_obfs4_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", True) settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", False) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", True) + settings.set("tor_bridges_use_snowflake", False) + settings.set("tor_bridges_use_custom_bridges", "") + if self.tor_bridges_use_snowflake_radio.isChecked(): + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", True) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_custom_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", False) # Insert a 'Bridge' line at the start of each bridge. # This makes it easier to copy/paste a set of bridges From 2ffd15ae82055585d963a6f59ae110b11f1b9f9a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 15 Oct 2021 09:17:03 -0700 Subject: [PATCH 13/26] Move ClientTransportPlugin into normal torrc file, and fix snowflake support --- cli/onionshare_cli/onion.py | 33 +++++++------------ cli/onionshare_cli/resources/torrc_template | 4 +++ .../resources/torrc_template-meek_lite_azure | 3 +- .../resources/torrc_template-obfs4 | 1 + .../resources/torrc_template-snowflake | 3 ++ 5 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 cli/onionshare_cli/resources/torrc_template-snowflake diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index a4453651..f8fcf68e 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -303,47 +303,38 @@ class Onion(object): torrc_template = torrc_template.replace( "{{socks_port}}", str(self.tor_socks_port) ) + torrc_template = torrc_template.replace( + "{{obfs4proxy_path}}", str(self.obfs4proxy_file_path) + ) + torrc_template = torrc_template.replace( + "{{snowflake_path}}", str(self.snowflake_file_path) + ) with open(self.tor_torrc, "w") as f: f.write(torrc_template) # Bridge support if self.settings.get("tor_bridges_use_obfs4"): - f.write( - f"ClientTransportPlugin obfs4 exec {self.obfs4proxy_file_path}\n" - ) with open( self.common.get_resource_path("torrc_template-obfs4") ) as o: for line in o: f.write(line) elif self.settings.get("tor_bridges_use_meek_lite_azure"): - f.write( - f"ClientTransportPlugin meek_lite exec {self.obfs4proxy_file_path}\n" - ) with open( self.common.get_resource_path("torrc_template-meek_lite_azure") ) as o: for line in o: f.write(line) elif self.settings.get("tor_bridges_use_snowflake"): - # Taken from: tor-browser_en-US/Browser/TorBrowser/Data/Tor/torrc-defaults - f.write( - f"ClientTransportPlugin snowflake exec {self.snowflake_file_path} -url https://snowflake-broker.torproject.net.global.prod.fastly.net/ -front cdn.sstatic.net -ice stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478\n" - ) + with open( + self.common.get_resource_path("torrc_template-snowflake") + ) as o: + for line in o: + f.write(line) if self.settings.get("tor_bridges_use_custom_bridges"): - if "obfs4" in self.settings.get("tor_bridges_use_custom_bridges"): - f.write( - f"ClientTransportPlugin obfs4 exec {self.obfs4proxy_file_path}\n" - ) - elif "meek_lite" in self.settings.get( - "tor_bridges_use_custom_bridges" - ): - f.write( - f"ClientTransportPlugin meek_lite exec {self.obfs4proxy_file_path}\n" - ) - f.write(self.settings.get("tor_bridges_use_custom_bridges")) + f.write(self.settings.get("tor_bridges_use_custom_bridges") + "\n") f.write("\nUseBridges 1") # Execute a tor subprocess diff --git a/cli/onionshare_cli/resources/torrc_template b/cli/onionshare_cli/resources/torrc_template index 8ac9e1ef..70e1cb35 100644 --- a/cli/onionshare_cli/resources/torrc_template +++ b/cli/onionshare_cli/resources/torrc_template @@ -6,3 +6,7 @@ AvoidDiskWrites 1 Log notice stdout GeoIPFile {{geo_ip_file}} GeoIPv6File {{geo_ipv6_file}} + +# Bridge configurations +ClientTransportPlugin meek_lite,obfs2,obfs3,obfs4,scramblesuit exec {{obfs4proxy_path}} +ClientTransportPlugin snowflake exec {{snowflake_path}} -url https://snowflake-broker.torproject.net.global.prod.fastly.net/ -front cdn.sstatic.net -ice stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 diff --git a/cli/onionshare_cli/resources/torrc_template-meek_lite_azure b/cli/onionshare_cli/resources/torrc_template-meek_lite_azure index a9b374ba..6f601681 100644 --- a/cli/onionshare_cli/resources/torrc_template-meek_lite_azure +++ b/cli/onionshare_cli/resources/torrc_template-meek_lite_azure @@ -1,2 +1,3 @@ +# Enable built-in meek-azure bridge Bridge meek_lite 0.0.2.0:3 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com -UseBridges 1 \ No newline at end of file +UseBridges 1 diff --git a/cli/onionshare_cli/resources/torrc_template-obfs4 b/cli/onionshare_cli/resources/torrc_template-obfs4 index 8c52a011..720cc28c 100644 --- a/cli/onionshare_cli/resources/torrc_template-obfs4 +++ b/cli/onionshare_cli/resources/torrc_template-obfs4 @@ -1,3 +1,4 @@ +# Enable built-in obfs4-bridge Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1 Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1 Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1 diff --git a/cli/onionshare_cli/resources/torrc_template-snowflake b/cli/onionshare_cli/resources/torrc_template-snowflake new file mode 100644 index 00000000..4100d3be --- /dev/null +++ b/cli/onionshare_cli/resources/torrc_template-snowflake @@ -0,0 +1,3 @@ +# Enable built-in snowflake bridge +Bridge snowflake 192.0.2.3:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72 +UseBridges 1 From 64973a00ec05e9201c4e2b7883f4c688eb92d0ab Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 15 Oct 2021 09:21:58 -0700 Subject: [PATCH 14/26] Fix CLI tests --- cli/tests/test_cli_common.py | 9 +++++++++ cli/tests/test_cli_settings.py | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cli/tests/test_cli_common.py b/cli/tests/test_cli_common.py index 9f113a84..a4798d1b 100644 --- a/cli/tests/test_cli_common.py +++ b/cli/tests/test_cli_common.py @@ -162,11 +162,15 @@ class TestGetTorPaths: tor_geo_ip_file_path = os.path.join(base_path, "Resources", "Tor", "geoip") tor_geo_ipv6_file_path = os.path.join(base_path, "Resources", "Tor", "geoip6") obfs4proxy_file_path = os.path.join(base_path, "Resources", "Tor", "obfs4proxy") + snowflake_file_path = os.path.join( + base_path, "Resources", "Tor", "snowflake-client" + ) assert common_obj.get_tor_paths() == ( tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path, + snowflake_file_path, ) @pytest.mark.skipif(sys.platform != "linux", reason="requires Linux") @@ -176,6 +180,7 @@ class TestGetTorPaths: tor_geo_ip_file_path, tor_geo_ipv6_file_path, _, # obfs4proxy is optional + _, # snowflake-client is optional ) = common_obj.get_tor_paths() assert os.path.basename(tor_path) == "tor" @@ -199,6 +204,9 @@ class TestGetTorPaths: obfs4proxy_file_path = os.path.join( os.path.join(base_path, "Tor"), "obfs4proxy.exe" ) + snowflake_file_path = os.path.join( + os.path.join(base_path, "Tor"), "snowflake-client.exe" + ) tor_geo_ip_file_path = os.path.join( os.path.join(os.path.join(base_path, "Data"), "Tor"), "geoip" ) @@ -210,6 +218,7 @@ class TestGetTorPaths: tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path, + snowflake_file_path, ) diff --git a/cli/tests/test_cli_settings.py b/cli/tests/test_cli_settings.py index ed8d5bb9..1e00f22d 100644 --- a/cli/tests/test_cli_settings.py +++ b/cli/tests/test_cli_settings.py @@ -32,9 +32,10 @@ class TestSettings: "no_bridges": True, "tor_bridges_use_obfs4": False, "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_snowflake": False, "tor_bridges_use_custom_bridges": "", "persistent_tabs": [], - "theme":0 + "theme": 0, } for key in settings_obj._settings: # Skip locale, it will not always default to the same thing From 66a744c9da3339934430828078e4f24372f67fc7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 15 Oct 2021 14:14:12 -0700 Subject: [PATCH 15/26] Improve the look of the Settings dialog, displaying the version and help link --- desktop/src/onionshare/gui_common.py | 9 ---- .../src/onionshare/resources/locale/en.json | 6 ++- desktop/src/onionshare/settings_dialog.py | 24 +++++------ desktop/src/onionshare/tor_settings_dialog.py | 43 ++++++++++++++----- 4 files changed, 47 insertions(+), 35 deletions(-) diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index 5634d5f6..3559eb92 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -392,15 +392,6 @@ class GuiCommon: QPushButton { padding: 5px 10px; }""", - # Settings dialogs - "settings_version": """ - QLabel { - font-size: 16px; - }""", - "settings_whats_this": """ - QLabel { - font-size: 12px; - }""", } def get_tor_paths(self): diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 3d6c8539..13326c4c 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -67,12 +67,14 @@ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek-azure bridge", "gui_settings_tor_bridges_snowflake_radio_option": "Use built-in snowflake 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.", - "gui_settings_tor_bridges_custom_radio_option": "Use custom bridges", - "gui_settings_tor_bridges_custom_label": "You can get bridges from https://bridges.torproject.org", + "gui_settings_tor_bridges_moat_radio_option": "Request a bridge from torproject.org", + "gui_settings_tor_bridges_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", "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", + "gui_settings_version_label": "You are using OnionShare {}", + "gui_settings_help_label": "Need help? See docs.onionshare.org", "settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports client authentication: {}.\nSupports next-gen .onion addresses: {}.", "connecting_to_tor": "Connecting to the Tor network", "update_available": "New OnionShare out. Click here to get it.

You are using {} and the latest is {}.", diff --git a/desktop/src/onionshare/settings_dialog.py b/desktop/src/onionshare/settings_dialog.py index 5bb7be1c..b1003386 100644 --- a/desktop/src/onionshare/settings_dialog.py +++ b/desktop/src/onionshare/settings_dialog.py @@ -71,17 +71,6 @@ class SettingsDialog(QtWidgets.QDialog): self.system = platform.system() - # Header - version_label = QtWidgets.QLabel(f"OnionShare {self.common.version}") - version_label.setStyleSheet(self.common.gui.css["settings_version"]) - self.help_button = QtWidgets.QPushButton(strings._("gui_settings_button_help")) - self.help_button.clicked.connect(self.help_clicked) - header_layout = QtWidgets.QHBoxLayout() - header_layout.addStretch() - header_layout.addWidget(version_label) - header_layout.addWidget(self.help_button) - header_layout.addStretch() - # Automatic updates options # Autoupdate @@ -146,6 +135,14 @@ class SettingsDialog(QtWidgets.QDialog): theme_layout.addWidget(self.theme_combobox) theme_layout.addStretch() + # Version and help + version_label = QtWidgets.QLabel( + strings._("gui_settings_version_label").format(self.common.version) + ) + help_label = QtWidgets.QLabel(strings._("gui_settings_help_label")) + help_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + help_label.setOpenExternalLinks(True) + # Buttons self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) @@ -160,8 +157,6 @@ class SettingsDialog(QtWidgets.QDialog): # Layout layout = QtWidgets.QVBoxLayout() - layout.addLayout(header_layout) - layout.addSpacing(20) layout.addWidget(autoupdate_group) if autoupdate_group.isVisible(): layout.addSpacing(20) @@ -169,6 +164,9 @@ class SettingsDialog(QtWidgets.QDialog): layout.addLayout(theme_layout) layout.addSpacing(20) layout.addStretch() + layout.addWidget(version_label) + layout.addWidget(help_label) + layout.addSpacing(20) layout.addLayout(buttons_layout) self.setLayout(layout) diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index 00d221a7..dc16e822 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -160,6 +160,38 @@ class TorSettingsDialog(QtWidgets.QDialog): ) self.tor_bridges_use_snowflake_radio.hide() + # Request a bridge from torproject.org (moat) + # self.tor_bridges_use_moat_radio = QtWidgets.QRadioButton( + # strings._("gui_settings_tor_bridges_moat_radio_option") + # ) + # self.tor_bridges_use_moat_radio.toggled.connect( + # self.tor_bridges_use_moat_radio_toggled + # ) + + # self.tor_bridges_use_moat_label = QtWidgets.QLabel( + # strings._("gui_settings_tor_bridges_moat_label") + # ) + # self.tor_bridges_use_custom_label.setOpenExternalLinks(True) + # self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() + # self.tor_bridges_use_custom_textbox.setMaximumHeight(200) + # self.tor_bridges_use_custom_textbox.setPlaceholderText( + # "[address:port] [identifier]" + # ) + + # tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() + # tor_bridges_use_custom_textbox_options_layout.addWidget( + # self.tor_bridges_use_custom_label + # ) + # tor_bridges_use_custom_textbox_options_layout.addWidget( + # self.tor_bridges_use_custom_textbox + # ) + + # self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() + # self.tor_bridges_use_custom_textbox_options.setLayout( + # tor_bridges_use_custom_textbox_options_layout + # ) + # self.tor_bridges_use_custom_textbox_options.hide() + # Custom bridges radio and textbox self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( strings._("gui_settings_tor_bridges_custom_radio_option") @@ -167,14 +199,6 @@ class TorSettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_custom_radio.toggled.connect( self.tor_bridges_use_custom_radio_toggled ) - - self.tor_bridges_use_custom_label = QtWidgets.QLabel( - strings._("gui_settings_tor_bridges_custom_label") - ) - self.tor_bridges_use_custom_label.setTextInteractionFlags( - QtCore.Qt.TextBrowserInteraction - ) - self.tor_bridges_use_custom_label.setOpenExternalLinks(True) self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() self.tor_bridges_use_custom_textbox.setMaximumHeight(200) self.tor_bridges_use_custom_textbox.setPlaceholderText( @@ -182,9 +206,6 @@ class TorSettingsDialog(QtWidgets.QDialog): ) tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() - tor_bridges_use_custom_textbox_options_layout.addWidget( - self.tor_bridges_use_custom_label - ) tor_bridges_use_custom_textbox_options_layout.addWidget( self.tor_bridges_use_custom_textbox ) From d1ae4e454ff11dff16c4598ae04a785b75c17541 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 15 Oct 2021 14:25:18 -0700 Subject: [PATCH 16/26] Simplify variable names in TorSettingsDialog, and start adding UI for moat --- .../src/onionshare/resources/locale/en.json | 12 +- desktop/src/onionshare/tor_settings_dialog.py | 208 ++++++++---------- 2 files changed, 103 insertions(+), 117 deletions(-) diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 13326c4c..51a3d53a 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -62,13 +62,13 @@ "gui_settings_password_label": "Password", "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_tor_bridges_no_bridges_radio_option": "Don't use a bridge", - "gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 bridge", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek-azure bridge", - "gui_settings_tor_bridges_snowflake_radio_option": "Use built-in snowflake bridge", + "gui_settings_bridge_none_radio_option": "Don't use a bridge", + "gui_settings_bridge_obfs4_radio_option": "Use built-in obfs4 bridge", + "gui_settings_bridge_meek_azure_radio_option": "Use built-in meek-azure bridge", + "gui_settings_bridge_snowflake_radio_option": "Use built-in snowflake 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.", - "gui_settings_tor_bridges_moat_radio_option": "Request a bridge from torproject.org", - "gui_settings_tor_bridges_custom_radio_option": "Provide a bridge you learned about from a trusted source", + "gui_settings_bridge_moat_radio_option": "Request 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", "gui_settings_button_cancel": "Cancel", diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index dc16e822..abecf949 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -93,12 +93,10 @@ class TorSettingsDialog(QtWidgets.QDialog): bridges_label.setWordWrap(True) # No bridges option radio - self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_no_bridges_radio_option") - ) - self.tor_bridges_no_bridges_radio.toggled.connect( - self.tor_bridges_no_bridges_radio_toggled + self.bridge_none_radio = QtWidgets.QRadioButton( + strings._("gui_settings_bridge_none_radio_option") ) + self.bridge_none_radio.toggled.connect(self.bridge_none_radio_toggled) ( self.tor_path, @@ -110,12 +108,10 @@ class TorSettingsDialog(QtWidgets.QDialog): # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_obfs4_radio_option") - ) - self.tor_bridges_use_obfs4_radio.toggled.connect( - self.tor_bridges_use_obfs4_radio_toggled + self.bridge_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_bridge_obfs4_radio_option") ) + self.bridge_obfs4_radio.toggled.connect(self.bridge_obfs4_radio_toggled) if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path ): @@ -124,15 +120,15 @@ class TorSettingsDialog(QtWidgets.QDialog): "__init__", f"missing binary {self.obfs4proxy_file_path}, hiding obfs4 bridge", ) - self.tor_bridges_use_obfs4_radio.hide() + self.bridge_obfs4_radio.hide() # meek-azure option radio # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option") + self.bridge_meek_azure_radio = QtWidgets.QRadioButton( + strings._("gui_settings_bridge_meek_azure_radio_option") ) - self.tor_bridges_use_meek_lite_azure_radio.toggled.connect( - self.tor_bridges_use_meek_lite_azure_radio_toggled + self.bridge_meek_azure_radio.toggled.connect( + self.bridge_meek_azure_radio_toggled ) if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path @@ -142,89 +138,65 @@ class TorSettingsDialog(QtWidgets.QDialog): "__init__", f"missing binary {self.obfs4proxy_file_path}, hiding meek-azure bridge", ) - self.tor_bridges_use_meek_lite_azure_radio.hide() + self.bridge_meek_azure_radio.hide() # snowflake option radio # if the snowflake-client binary is missing, we can't use snowflake transports - self.tor_bridges_use_snowflake_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_snowflake_radio_option") - ) - self.tor_bridges_use_snowflake_radio.toggled.connect( - self.tor_bridges_use_snowflake_radio_toggled + self.bridge_snowflake_radio = QtWidgets.QRadioButton( + strings._("gui_settings_bridge_snowflake_radio_option") ) + self.bridge_snowflake_radio.toggled.connect(self.bridge_snowflake_radio_toggled) if not self.snowflake_file_path or not os.path.isfile(self.snowflake_file_path): self.common.log( "TorSettingsDialog", "__init__", f"missing binary {self.snowflake_file_path}, hiding snowflake bridge", ) - self.tor_bridges_use_snowflake_radio.hide() + self.bridge_snowflake_radio.hide() # Request a bridge from torproject.org (moat) - # self.tor_bridges_use_moat_radio = QtWidgets.QRadioButton( - # strings._("gui_settings_tor_bridges_moat_radio_option") - # ) - # self.tor_bridges_use_moat_radio.toggled.connect( - # self.tor_bridges_use_moat_radio_toggled - # ) - - # self.tor_bridges_use_moat_label = QtWidgets.QLabel( - # strings._("gui_settings_tor_bridges_moat_label") - # ) - # self.tor_bridges_use_custom_label.setOpenExternalLinks(True) - # self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() - # self.tor_bridges_use_custom_textbox.setMaximumHeight(200) - # self.tor_bridges_use_custom_textbox.setPlaceholderText( - # "[address:port] [identifier]" - # ) - - # tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() - # tor_bridges_use_custom_textbox_options_layout.addWidget( - # self.tor_bridges_use_custom_label - # ) - # tor_bridges_use_custom_textbox_options_layout.addWidget( - # self.tor_bridges_use_custom_textbox - # ) - - # self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() - # self.tor_bridges_use_custom_textbox_options.setLayout( - # tor_bridges_use_custom_textbox_options_layout - # ) - # self.tor_bridges_use_custom_textbox_options.hide() + self.bridge_moat_radio = QtWidgets.QRadioButton( + strings._("gui_settings_bridge_moat_radio_option") + ) + self.bridge_moat_radio.toggled.connect(self.bridge_moat_radio_toggled) + self.bridge_moat_textbox = QtWidgets.QPlainTextEdit() + self.bridge_moat_textbox.setMaximumHeight(200) + self.bridge_moat_textbox.setEnabled(False) + bridge_moat_textbox_options_layout = QtWidgets.QVBoxLayout() + bridge_moat_textbox_options_layout.addWidget(self.bridge_moat_textbox) + self.bridge_moat_textbox_options = QtWidgets.QWidget() + self.bridge_moat_textbox_options.setLayout(bridge_moat_textbox_options_layout) + self.bridge_moat_textbox_options.hide() # Custom bridges radio and textbox - self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_custom_radio_option") - ) - self.tor_bridges_use_custom_radio.toggled.connect( - self.tor_bridges_use_custom_radio_toggled - ) - self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() - self.tor_bridges_use_custom_textbox.setMaximumHeight(200) - self.tor_bridges_use_custom_textbox.setPlaceholderText( - "[address:port] [identifier]" + self.bridge_custom_radio = QtWidgets.QRadioButton( + strings._("gui_settings_bridge_custom_radio_option") ) + self.bridge_custom_radio.toggled.connect(self.bridge_custom_radio_toggled) + self.bridge_custom_textbox = QtWidgets.QPlainTextEdit() + self.bridge_custom_textbox.setMaximumHeight(200) + self.bridge_custom_textbox.setPlaceholderText("[address:port] [identifier]") - tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() - tor_bridges_use_custom_textbox_options_layout.addWidget( - self.tor_bridges_use_custom_textbox - ) + bridge_custom_textbox_options_layout = QtWidgets.QVBoxLayout() + bridge_custom_textbox_options_layout.addWidget(self.bridge_custom_textbox) - self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() - self.tor_bridges_use_custom_textbox_options.setLayout( - tor_bridges_use_custom_textbox_options_layout + self.bridge_custom_textbox_options = QtWidgets.QWidget() + self.bridge_custom_textbox_options.setLayout( + bridge_custom_textbox_options_layout ) - self.tor_bridges_use_custom_textbox_options.hide() + self.bridge_custom_textbox_options.hide() # Bridges layout/widget bridges_layout = QtWidgets.QVBoxLayout() bridges_layout.addWidget(bridges_label) - bridges_layout.addWidget(self.tor_bridges_no_bridges_radio) - bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio) - bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio) - bridges_layout.addWidget(self.tor_bridges_use_snowflake_radio) - bridges_layout.addWidget(self.tor_bridges_use_custom_radio) - bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options) + bridges_layout.addWidget(self.bridge_none_radio) + bridges_layout.addWidget(self.bridge_obfs4_radio) + bridges_layout.addWidget(self.bridge_meek_azure_radio) + bridges_layout.addWidget(self.bridge_snowflake_radio) + bridges_layout.addWidget(self.bridge_moat_radio) + bridges_layout.addWidget(self.bridge_moat_textbox_options) + bridges_layout.addWidget(self.bridge_custom_radio) + bridges_layout.addWidget(self.bridge_custom_textbox_options) self.bridges = QtWidgets.QWidget() self.bridges.setLayout(bridges_layout) @@ -450,36 +422,36 @@ class TorSettingsDialog(QtWidgets.QDialog): ) if self.old_settings.get("no_bridges"): - self.tor_bridges_no_bridges_radio.setChecked(True) - self.tor_bridges_use_obfs4_radio.setChecked(False) - self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) - self.tor_bridges_use_snowflake_radio.setChecked(False) - self.tor_bridges_use_custom_radio.setChecked(False) + self.bridge_none_radio.setChecked(True) + self.bridge_obfs4_radio.setChecked(False) + self.bridge_meek_azure_radio.setChecked(False) + self.bridge_snowflake_radio.setChecked(False) + self.bridge_custom_radio.setChecked(False) else: - self.tor_bridges_no_bridges_radio.setChecked(False) - self.tor_bridges_use_obfs4_radio.setChecked( + self.bridge_none_radio.setChecked(False) + self.bridge_obfs4_radio.setChecked( self.old_settings.get("tor_bridges_use_obfs4") ) - self.tor_bridges_use_meek_lite_azure_radio.setChecked( + self.bridge_meek_azure_radio.setChecked( self.old_settings.get("tor_bridges_use_meek_lite_azure") ) - self.tor_bridges_use_snowflake_radio.setChecked( + self.bridge_snowflake_radio.setChecked( self.old_settings.get("tor_bridges_use_snowflake") ) - if self.old_settings.get("tor_bridges_use_custom_bridges"): - self.tor_bridges_use_custom_radio.setChecked(True) + if self.old_settings.get("bridge_custom_bridges"): + self.bridge_custom_radio.setChecked(True) # Remove the 'Bridge' lines at the start of each bridge. # They are added automatically to provide compatibility with # copying/pasting bridges provided from https://bridges.torproject.org new_bridges = [] - bridges = self.old_settings.get("tor_bridges_use_custom_bridges").split( + bridges = self.old_settings.get("bridge_custom_bridges").split( "Bridge " ) for bridge in bridges: new_bridges.append(bridge) new_bridges = "".join(new_bridges) - self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) + self.bridge_custom_textbox.setPlainText(new_bridges) def connection_type_bundled_toggled(self, checked): """ @@ -491,26 +463,30 @@ class TorSettingsDialog(QtWidgets.QDialog): self.connection_type_socks.hide() self.connection_type_bridges_radio_group.show() - def tor_bridges_no_bridges_radio_toggled(self, checked): + def bridge_none_radio_toggled(self, checked): """ 'No bridges' option was toggled. If checked, enable other bridge options. """ if checked: - self.tor_bridges_use_custom_textbox_options.hide() + self.bridge_custom_textbox_options.hide() + self.bridge_moat_textbox_options.hide() - def tor_bridges_use_obfs4_radio_toggled(self, checked): + def bridge_obfs4_radio_toggled(self, checked): """ obfs4 bridges option was toggled. If checked, disable custom bridge options. """ if checked: - self.tor_bridges_use_custom_textbox_options.hide() + self.bridge_custom_textbox_options.hide() + self.bridge_moat_textbox_options.hide() - def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked): + def bridge_meek_azure_radio_toggled(self, checked): """ meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. """ if checked: - self.tor_bridges_use_custom_textbox_options.hide() + self.bridge_custom_textbox_options.hide() + self.bridge_moat_textbox_options.hide() + # Alert the user about meek's costliness if it looks like they're turning it on if not self.old_settings.get("tor_bridges_use_meek_lite_azure"): Alert( @@ -519,19 +495,29 @@ class TorSettingsDialog(QtWidgets.QDialog): QtWidgets.QMessageBox.Warning, ) - def tor_bridges_use_snowflake_radio_toggled(self, checked): + def bridge_snowflake_radio_toggled(self, checked): """ snowflake bridges option was toggled. If checked, disable custom bridge options. """ if checked: - self.tor_bridges_use_custom_textbox_options.hide() + self.bridge_custom_textbox_options.hide() + self.bridge_moat_textbox_options.hide() - def tor_bridges_use_custom_radio_toggled(self, checked): + def bridge_moat_radio_toggled(self, checked): + """ + Moat (request bridge) bridges option was toggled. If checked, show moat bridge options. + """ + if checked: + self.bridge_custom_textbox_options.hide() + self.bridge_moat_textbox_options.show() + + def bridge_custom_radio_toggled(self, checked): """ Custom bridges option was toggled. If checked, show custom bridge options. """ if checked: - self.tor_bridges_use_custom_textbox_options.show() + self.bridge_custom_textbox_options.show() + self.bridge_moat_textbox_options.hide() def connection_type_automatic_toggled(self, checked): """ @@ -659,7 +645,7 @@ class TorSettingsDialog(QtWidgets.QDialog): "no_bridges", "tor_bridges_use_obfs4", "tor_bridges_use_meek_lite_azure", - "tor_bridges_use_custom_bridges", + "bridge_custom_bridges", ], ): @@ -761,31 +747,31 @@ class TorSettingsDialog(QtWidgets.QDialog): settings.set("auth_password", self.authenticate_password_extras_password.text()) # Whether we use bridges - if self.tor_bridges_no_bridges_radio.isChecked(): + if self.bridge_none_radio.isChecked(): settings.set("no_bridges", True) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_snowflake", False) - settings.set("tor_bridges_use_custom_bridges", "") - if self.tor_bridges_use_obfs4_radio.isChecked(): + settings.set("bridge_custom_bridges", "") + if self.bridge_obfs4_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", True) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_snowflake", False) - settings.set("tor_bridges_use_custom_bridges", "") - if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): + settings.set("bridge_custom_bridges", "") + if self.bridge_meek_azure_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", True) settings.set("tor_bridges_use_snowflake", False) - settings.set("tor_bridges_use_custom_bridges", "") - if self.tor_bridges_use_snowflake_radio.isChecked(): + settings.set("bridge_custom_bridges", "") + if self.bridge_snowflake_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_snowflake", True) - settings.set("tor_bridges_use_custom_bridges", "") - if self.tor_bridges_use_custom_radio.isChecked(): + settings.set("bridge_custom_bridges", "") + if self.bridge_custom_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) @@ -795,7 +781,7 @@ class TorSettingsDialog(QtWidgets.QDialog): # This makes it easier to copy/paste a set of bridges # provided from https://bridges.torproject.org new_bridges = [] - bridges = self.tor_bridges_use_custom_textbox.toPlainText().split("\n") + bridges = self.bridge_custom_textbox.toPlainText().split("\n") bridges_valid = False for bridge in bridges: if bridge != "": @@ -819,7 +805,7 @@ class TorSettingsDialog(QtWidgets.QDialog): if bridges_valid: new_bridges = "".join(new_bridges) - settings.set("tor_bridges_use_custom_bridges", new_bridges) + settings.set("bridge_custom_bridges", new_bridges) else: Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) settings.set("no_bridges", True) From 168e3057ae56926518dd01931da4180a788afc7e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 15 Oct 2021 14:44:09 -0700 Subject: [PATCH 17/26] Start implementing moat --- .../src/onionshare/resources/locale/en.json | 2 + desktop/src/onionshare/tor_settings_dialog.py | 75 ++++++++++++++----- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 51a3d53a..97ce5585 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -68,6 +68,8 @@ "gui_settings_bridge_snowflake_radio_option": "Use built-in snowflake 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.", "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", diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index abecf949..9f08d767 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -19,30 +19,14 @@ along with this program. If not, see . """ from PySide2 import QtCore, QtWidgets, QtGui -from PySide2.QtCore import Slot, Qt -from PySide2.QtGui import QPalette, QColor import sys import platform -import datetime import re import os +import requests + from onionshare_cli.settings import Settings -from onionshare_cli.onion import ( - Onion, - TorErrorInvalidSetting, - TorErrorAutomatic, - TorErrorSocketPort, - TorErrorSocketFile, - TorErrorMissingPassword, - TorErrorUnreadableCookieFile, - TorErrorAuthError, - TorErrorProtocolError, - BundledTorTimeout, - BundledTorBroken, - TorTooOldEphemeral, - TorTooOldStealth, - PortNotAvailable, -) +from onionshare_cli.onion import Onion from . import strings from .widgets import Alert @@ -159,10 +143,17 @@ class TorSettingsDialog(QtWidgets.QDialog): strings._("gui_settings_bridge_moat_radio_option") ) self.bridge_moat_radio.toggled.connect(self.bridge_moat_radio_toggled) + 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.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.setLayout(bridge_moat_textbox_options_layout) @@ -511,6 +502,52 @@ class TorSettingsDialog(QtWidgets.QDialog): self.bridge_custom_textbox_options.hide() self.bridge_moat_textbox_options.show() + def bridge_moat_button_clicked(self): + """ + Request new bridge button clicked + """ + 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() + def bridge_custom_radio_toggled(self, checked): """ Custom bridges option was toggled. If checked, show custom bridge options. From 67126a34976c4d036873323b1bf5a23c1d46340e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 15 Oct 2021 16:53:40 -0700 Subject: [PATCH 18/26] 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): """ From 9f9328fd1fa0df867eb235cb23a886873cd39624 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Oct 2021 12:15:01 -0700 Subject: [PATCH 19/26] Update linux Tor Browser URL and hash --- desktop/scripts/get-tor-linux.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop/scripts/get-tor-linux.py b/desktop/scripts/get-tor-linux.py index e47ae03d..b8f83c92 100755 --- a/desktop/scripts/get-tor-linux.py +++ b/desktop/scripts/get-tor-linux.py @@ -34,10 +34,10 @@ import requests def main(): - tarball_url = "https://dist.torproject.org/torbrowser/11.0a7/tor-browser-linux64-11.0a7_en-US.tar.xz" - tarball_filename = "tor-browser-linux64-11.0a7_en-US.tar.xz" + tarball_url = "https://dist.torproject.org/torbrowser/11.0a9/tor-browser-linux64-11.0a9_en-US.tar.xz" + tarball_filename = "tor-browser-linux64-11.0a9_en-US.tar.xz" expected_tarball_sha256 = ( - "bc9861c692f899fe0344c960dc615ff0e275cf74c61066c8735c88e3ddc2b623" + "cba4a2120b4f847d1ade637e41e69bd01b2e70b4a13e41fe8e69d0424fcf7ca7" ) # Build paths From 2161c58a4afaa78b46fbb2412dbd3cee99fb61d2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Oct 2021 12:15:25 -0700 Subject: [PATCH 20/26] If connecting to Tor fails, open the correct TorSettings dialog --- desktop/src/onionshare/main_window.py | 10 +++++----- desktop/src/onionshare/tor_connection_dialog.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/desktop/src/onionshare/main_window.py b/desktop/src/onionshare/main_window.py index b3f8b6c9..c125741c 100644 --- a/desktop/src/onionshare/main_window.py +++ b/desktop/src/onionshare/main_window.py @@ -165,7 +165,7 @@ class MainWindow(QtWidgets.QMainWindow): # Start the "Connecting to Tor" dialog, which calls onion.connect() tor_con = TorConnectionDialog(self.common) tor_con.canceled.connect(self.tor_connection_canceled) - tor_con.open_settings.connect(self.tor_connection_open_settings) + tor_con.open_tor_settings.connect(self.tor_connection_open_tor_settings) if not self.common.gui.local_only: tor_con.start() self.settings_have_changed() @@ -234,14 +234,14 @@ class MainWindow(QtWidgets.QMainWindow): # Wait 100ms before asking QtCore.QTimer.singleShot(100, ask) - def tor_connection_open_settings(self): + def tor_connection_open_tor_settings(self): """ - The TorConnectionDialog wants to open the Settings dialog + The TorConnectionDialog wants to open the Tor Settings dialog """ - self.common.log("MainWindow", "tor_connection_open_settings") + self.common.log("MainWindow", "tor_connection_open_tor_settings") # Wait 1ms for the event loop to finish closing the TorConnectionDialog - QtCore.QTimer.singleShot(1, self.open_settings) + QtCore.QTimer.singleShot(1, self.open_tor_settings) def open_tor_settings(self): """ diff --git a/desktop/src/onionshare/tor_connection_dialog.py b/desktop/src/onionshare/tor_connection_dialog.py index dd2721bd..daf49a32 100644 --- a/desktop/src/onionshare/tor_connection_dialog.py +++ b/desktop/src/onionshare/tor_connection_dialog.py @@ -48,7 +48,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): Connecting to Tor dialog. """ - open_settings = QtCore.Signal() + open_tor_settings = QtCore.Signal() success = QtCore.Signal() def __init__( @@ -149,7 +149,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): ) # Open settings - self.open_settings.emit() + self.open_tor_settings.emit() QtCore.QTimer.singleShot(1, alert) From 6bf839f8269fff99fd33827404ca12e2764f6bc6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Oct 2021 12:16:03 -0700 Subject: [PATCH 21/26] In some distros, LD_LIBRARY_PATH must be explicitly set for tor to work --- cli/onionshare_cli/onion.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index f8fcf68e..c3ee4fac 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -354,6 +354,7 @@ class Onion(object): [self.tor_path, "-f", self.tor_torrc], stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env={"LD_LIBRARY_PATH": os.path.dirname(self.tor_path)}, ) # Wait for the tor controller to start From 221258962511e58d95c73bc4d4cca7a229d791f3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Oct 2021 14:02:11 -0700 Subject: [PATCH 22/26] Actually get bridges from moat --- desktop/src/onionshare/gui_common.py | 6 + desktop/src/onionshare/moat_dialog.py | 237 ++++++++++++++---- .../src/onionshare/resources/locale/en.json | 3 +- desktop/src/onionshare/tor_settings_dialog.py | 13 +- 4 files changed, 210 insertions(+), 49 deletions(-) diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index 3559eb92..0f1dd46e 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -392,6 +392,12 @@ class GuiCommon: QPushButton { padding: 5px 10px; }""", + # Moat dialog + "moat_error": """ + QLabel { + color: #990000; + } + """, } def get_tor_paths(self): diff --git a/desktop/src/onionshare/moat_dialog.py b/desktop/src/onionshare/moat_dialog.py index ba4223eb..3cb6519b 100644 --- a/desktop/src/onionshare/moat_dialog.py +++ b/desktop/src/onionshare/moat_dialog.py @@ -20,6 +20,8 @@ along with this program. If not, see . from PySide2 import QtCore, QtWidgets, QtGui import requests +import os +import base64 from . import strings from .gui_common import GuiCommon @@ -30,6 +32,8 @@ class MoatDialog(QtWidgets.QDialog): Moat dialog: Request a bridge from torproject.org """ + got_bridges = QtCore.Signal(str) + def __init__(self, common): super(MoatDialog, self).__init__() @@ -42,7 +46,7 @@ class MoatDialog(QtWidgets.QDialog): self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))) # Label - self.label = QtWidgets.QLabel(strings._("moat_contact_label")) + self.label = QtWidgets.QLabel() # CAPTCHA image self.captcha = QtWidgets.QLabel() @@ -50,7 +54,7 @@ class MoatDialog(QtWidgets.QDialog): # Solution input 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.clicked.connect(self.reload_clicked) solution_layout = QtWidgets.QHBoxLayout() @@ -59,12 +63,12 @@ class MoatDialog(QtWidgets.QDialog): # Error label self.error_label = QtWidgets.QLabel() + self.error_label.setStyleSheet(self.common.gui.css["moat_error"]) 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") ) @@ -79,6 +83,7 @@ class MoatDialog(QtWidgets.QDialog): layout.addWidget(self.label) layout.addWidget(self.captcha) layout.addLayout(solution_layout) + layout.addStretch() layout.addWidget(self.error_label) layout.addLayout(buttons_layout) @@ -86,35 +91,95 @@ class MoatDialog(QtWidgets.QDialog): 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 + + 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): """ Submit button clicked. """ - self.common.log("MoatDialog", "submit_clicked") - pass + self.error_label.hide() + + 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): """ Cancel button 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): @@ -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__() self.common = common self.common.log("MoatThread", "__init__", f"action={action}") + self.transport = "obfs4" 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() + if self.action == "fetch": + self.common.log("MoatThread", "run", f"starting fetch") - 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() + # 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": [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"] - moat_transport = moat_res["transport"] - moat_image = moat_res["image"] - moat_challenge = moat_res["challenge"] - except: - return moat_error() + try: + moat_res = r.json() + if "errors" in moat_res: + self.common.log("MoatThread", "run", f"errors={moat_res['errors']}") + self.bridgedb_error.emit() + 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}") diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 6bc424e0..8a14f8bf 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -226,5 +226,6 @@ "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." + "moat_captcha_error": "The solution is not correct. Please try again.", + "moat_solution_empty_error": "You must enter the characters from the image" } \ 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 f5f0a302..b6495830 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -148,7 +148,8 @@ class TorSettingsDialog(QtWidgets.QDialog): self.bridge_moat_button.clicked.connect(self.bridge_moat_button_clicked) self.bridge_moat_textbox = QtWidgets.QPlainTextEdit() 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() bridge_moat_textbox_options_layout = QtWidgets.QVBoxLayout() 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") 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): """ From 01b51e94bf8916fa5b2120045068067990ab8e07 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Oct 2021 14:26:56 -0700 Subject: [PATCH 23/26] Save/load moat bridges to/from settings --- cli/onionshare_cli/settings.py | 2 + cli/tests/test_cli_settings.py | 2 + desktop/src/onionshare/tor_settings_dialog.py | 43 +++++++++++++++---- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/cli/onionshare_cli/settings.py b/cli/onionshare_cli/settings.py index 37c00bb6..29b59c80 100644 --- a/cli/onionshare_cli/settings.py +++ b/cli/onionshare_cli/settings.py @@ -109,6 +109,8 @@ class Settings(object): "tor_bridges_use_obfs4": False, "tor_bridges_use_meek_lite_azure": False, "tor_bridges_use_snowflake": False, + "tor_bridges_use_moat": False, + "tor_bridges_use_moat_bridges": "", "tor_bridges_use_custom_bridges": "", "persistent_tabs": [], "locale": None, # this gets defined in fill_in_defaults() diff --git a/cli/tests/test_cli_settings.py b/cli/tests/test_cli_settings.py index 1e00f22d..b44ddbec 100644 --- a/cli/tests/test_cli_settings.py +++ b/cli/tests/test_cli_settings.py @@ -33,6 +33,8 @@ class TestSettings: "tor_bridges_use_obfs4": False, "tor_bridges_use_meek_lite_azure": False, "tor_bridges_use_snowflake": False, + "tor_bridges_use_moat": False, + "tor_bridges_use_moat_bridges": "", "tor_bridges_use_custom_bridges": "", "persistent_tabs": [], "theme": 0, diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index b6495830..477758c2 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -150,7 +150,6 @@ class TorSettingsDialog(QtWidgets.QDialog): self.bridge_moat_textbox.setMaximumHeight(100) self.bridge_moat_textbox.setReadOnly(True) self.bridge_moat_textbox.setWordWrapMode(QtGui.QTextOption.NoWrap) - 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) @@ -417,6 +416,7 @@ class TorSettingsDialog(QtWidgets.QDialog): self.bridge_obfs4_radio.setChecked(False) self.bridge_meek_azure_radio.setChecked(False) self.bridge_snowflake_radio.setChecked(False) + self.bridge_moat_radio.setChecked(False) self.bridge_custom_radio.setChecked(False) else: self.bridge_none_radio.setChecked(False) @@ -429,14 +429,23 @@ class TorSettingsDialog(QtWidgets.QDialog): self.bridge_snowflake_radio.setChecked( self.old_settings.get("tor_bridges_use_snowflake") ) + self.bridge_moat_radio.setChecked( + self.old_settings.get("tor_bridges_use_moat") + ) + moat_bridges = self.old_settings.get("tor_bridges_use_moat_bridges") + self.bridge_moat_textbox.document().setPlainText(moat_bridges) + if len(moat_bridges.strip()) > 0: + self.bridge_moat_textbox.show() + else: + self.bridge_moat_textbox.hide() - if self.old_settings.get("bridge_custom_bridges"): + if self.old_settings.get("tor_bridges_use_custom_bridges"): self.bridge_custom_radio.setChecked(True) # Remove the 'Bridge' lines at the start of each bridge. # They are added automatically to provide compatibility with # copying/pasting bridges provided from https://bridges.torproject.org new_bridges = [] - bridges = self.old_settings.get("bridge_custom_bridges").split( + bridges = self.old_settings.get("tor_bridges_use_custom_bridges").split( "Bridge " ) for bridge in bridges: @@ -654,7 +663,7 @@ class TorSettingsDialog(QtWidgets.QDialog): "no_bridges", "tor_bridges_use_obfs4", "tor_bridges_use_meek_lite_azure", - "bridge_custom_bridges", + "tor_bridges_use_custom_bridges", ], ): @@ -761,30 +770,46 @@ class TorSettingsDialog(QtWidgets.QDialog): settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_snowflake", False) - settings.set("bridge_custom_bridges", "") + settings.set("tor_bridges_use_moat", False) + settings.set("tor_bridges_use_custom_bridges", "") if self.bridge_obfs4_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", True) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_snowflake", False) - settings.set("bridge_custom_bridges", "") + settings.set("tor_bridges_use_moat", False) + settings.set("tor_bridges_use_custom_bridges", "") if self.bridge_meek_azure_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", True) settings.set("tor_bridges_use_snowflake", False) - settings.set("bridge_custom_bridges", "") + settings.set("tor_bridges_use_moat", False) + settings.set("tor_bridges_use_custom_bridges", "") if self.bridge_snowflake_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_snowflake", True) - settings.set("bridge_custom_bridges", "") + settings.set("tor_bridges_use_moat", False) + settings.set("tor_bridges_use_custom_bridges", "") + if self.bridge_moat_radio.isChecked(): + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", False) + settings.set("tor_bridges_use_moat", True) + settings.set( + "tor_bridges_use_moat_bridges", self.bridge_moat_textbox.toPlainText() + ) + settings.set("tor_bridges_use_custom_bridges", "") if self.bridge_custom_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_snowflake", False) + settings.set("tor_bridges_use_moat", False) + settings.set("tor_bridges_use_moat_bridges", "") # Insert a 'Bridge' line at the start of each bridge. # This makes it easier to copy/paste a set of bridges @@ -814,7 +839,7 @@ class TorSettingsDialog(QtWidgets.QDialog): if bridges_valid: new_bridges = "".join(new_bridges) - settings.set("bridge_custom_bridges", new_bridges) + settings.set("tor_bridges_use_custom_bridges", new_bridges) else: Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) settings.set("no_bridges", True) From 40cb55894a53dfc5a8cb477fa606563d37d274b6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Oct 2021 15:34:42 -0700 Subject: [PATCH 24/26] Totally change the Tor Settings dialog to even more closely resemble Tor Browser --- .../src/onionshare/resources/locale/en.json | 6 +- desktop/src/onionshare/tor_settings_dialog.py | 383 ++++++++---------- 2 files changed, 182 insertions(+), 207 deletions(-) diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 8a14f8bf..63bfd48c 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -62,14 +62,14 @@ "gui_settings_password_label": "Password", "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_bridge_radio_builtin": "Select a built-in bridge", "gui_settings_bridge_none_radio_option": "Don't use a bridge", - "gui_settings_bridge_obfs4_radio_option": "Use built-in obfs4 bridge", - "gui_settings_bridge_meek_azure_radio_option": "Use built-in meek-azure bridge", - "gui_settings_bridge_snowflake_radio_option": "Use built-in snowflake 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.", "gui_settings_bridge_moat_radio_option": "Request a bridge from torproject.org", "gui_settings_bridge_moat_button": "Request a New Bridge...", "gui_settings_bridge_custom_radio_option": "Provide a bridge you learned about from a trusted source", + "gui_settings_bridge_custom_placeholder": "type address:port (one per line)", "gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.", "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index 477758c2..6fa4dda1 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -72,15 +72,6 @@ class TorSettingsDialog(QtWidgets.QDialog): # Bridge options for bundled tor - bridges_label = QtWidgets.QLabel(strings._("gui_settings_tor_bridges_label")) - bridges_label.setWordWrap(True) - - # No bridges option radio - self.bridge_none_radio = QtWidgets.QRadioButton( - strings._("gui_settings_bridge_none_radio_option") - ) - self.bridge_none_radio.toggled.connect(self.bridge_none_radio_toggled) - ( self.tor_path, self.tor_geo_ip_file_path, @@ -89,53 +80,30 @@ class TorSettingsDialog(QtWidgets.QDialog): self.snowflake_file_path, ) = self.common.gui.get_tor_paths() - # obfs4 option radio - # if the obfs4proxy binary is missing, we can't use obfs4 transports - self.bridge_obfs4_radio = QtWidgets.QRadioButton( - strings._("gui_settings_bridge_obfs4_radio_option") - ) - self.bridge_obfs4_radio.toggled.connect(self.bridge_obfs4_radio_toggled) - if not self.obfs4proxy_file_path or not os.path.isfile( - self.obfs4proxy_file_path - ): - self.common.log( - "TorSettingsDialog", - "__init__", - f"missing binary {self.obfs4proxy_file_path}, hiding obfs4 bridge", - ) - self.bridge_obfs4_radio.hide() + bridges_label = QtWidgets.QLabel(strings._("gui_settings_tor_bridges_label")) + bridges_label.setWordWrap(True) - # meek-azure option radio - # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports - self.bridge_meek_azure_radio = QtWidgets.QRadioButton( - strings._("gui_settings_bridge_meek_azure_radio_option") + self.bridge_use_checkbox = QtWidgets.QCheckBox( + strings._("gui_settings_bridge_use_checkbox") ) - self.bridge_meek_azure_radio.toggled.connect( - self.bridge_meek_azure_radio_toggled + self.bridge_use_checkbox.stateChanged.connect( + self.bridge_use_checkbox_state_changed ) - if not self.obfs4proxy_file_path or not os.path.isfile( - self.obfs4proxy_file_path - ): - self.common.log( - "TorSettingsDialog", - "__init__", - f"missing binary {self.obfs4proxy_file_path}, hiding meek-azure bridge", - ) - self.bridge_meek_azure_radio.hide() - # snowflake option radio - # if the snowflake-client binary is missing, we can't use snowflake transports - self.bridge_snowflake_radio = QtWidgets.QRadioButton( - strings._("gui_settings_bridge_snowflake_radio_option") + # Built-in bridge + self.bridge_builtin_radio = QtWidgets.QRadioButton( + strings._("gui_settings_bridge_radio_builtin") ) - self.bridge_snowflake_radio.toggled.connect(self.bridge_snowflake_radio_toggled) - if not self.snowflake_file_path or not os.path.isfile(self.snowflake_file_path): - self.common.log( - "TorSettingsDialog", - "__init__", - f"missing binary {self.snowflake_file_path}, hiding snowflake bridge", - ) - self.bridge_snowflake_radio.hide() + self.bridge_builtin_radio.toggled.connect(self.bridge_builtin_radio_toggled) + self.bridge_builtin_dropdown = QtWidgets.QComboBox() + self.bridge_builtin_dropdown.currentTextChanged.connect( + self.bridge_builtin_dropdown_changed + ) + if self.obfs4proxy_file_path and os.path.isfile(self.obfs4proxy_file_path): + self.bridge_builtin_dropdown.addItem("obfs4") + self.bridge_builtin_dropdown.addItem("meek-azure") + if self.snowflake_file_path and os.path.isfile(self.snowflake_file_path): + self.bridge_builtin_dropdown.addItem("snowflake") # Request a bridge from torproject.org (moat) self.bridge_moat_radio = QtWidgets.QRadioButton( @@ -147,6 +115,7 @@ class TorSettingsDialog(QtWidgets.QDialog): ) self.bridge_moat_button.clicked.connect(self.bridge_moat_button_clicked) self.bridge_moat_textbox = QtWidgets.QPlainTextEdit() + self.bridge_moat_textbox.setMinimumHeight(100) self.bridge_moat_textbox.setMaximumHeight(100) self.bridge_moat_textbox.setReadOnly(True) self.bridge_moat_textbox.setWordWrapMode(QtGui.QTextOption.NoWrap) @@ -154,7 +123,6 @@ class TorSettingsDialog(QtWidgets.QDialog): 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() @@ -164,8 +132,11 @@ class TorSettingsDialog(QtWidgets.QDialog): ) self.bridge_custom_radio.toggled.connect(self.bridge_custom_radio_toggled) self.bridge_custom_textbox = QtWidgets.QPlainTextEdit() - self.bridge_custom_textbox.setMaximumHeight(200) - self.bridge_custom_textbox.setPlaceholderText("[address:port] [identifier]") + self.bridge_custom_textbox.setMinimumHeight(100) + self.bridge_custom_textbox.setMaximumHeight(100) + self.bridge_custom_textbox.setPlaceholderText( + strings._("gui_settings_bridge_custom_placeholder") + ) bridge_custom_textbox_options_layout = QtWidgets.QVBoxLayout() bridge_custom_textbox_options_layout.addWidget(self.bridge_custom_textbox) @@ -176,17 +147,22 @@ class TorSettingsDialog(QtWidgets.QDialog): ) self.bridge_custom_textbox_options.hide() + # Bridge settings layout + bridge_settings_layout = QtWidgets.QVBoxLayout() + bridge_settings_layout.addWidget(self.bridge_builtin_radio) + bridge_settings_layout.addWidget(self.bridge_builtin_dropdown) + bridge_settings_layout.addWidget(self.bridge_moat_radio) + bridge_settings_layout.addWidget(self.bridge_moat_textbox_options) + bridge_settings_layout.addWidget(self.bridge_custom_radio) + bridge_settings_layout.addWidget(self.bridge_custom_textbox_options) + self.bridge_settings = QtWidgets.QWidget() + self.bridge_settings.setLayout(bridge_settings_layout) + # Bridges layout/widget bridges_layout = QtWidgets.QVBoxLayout() bridges_layout.addWidget(bridges_label) - bridges_layout.addWidget(self.bridge_none_radio) - bridges_layout.addWidget(self.bridge_obfs4_radio) - bridges_layout.addWidget(self.bridge_meek_azure_radio) - bridges_layout.addWidget(self.bridge_snowflake_radio) - bridges_layout.addWidget(self.bridge_moat_radio) - bridges_layout.addWidget(self.bridge_moat_textbox_options) - bridges_layout.addWidget(self.bridge_custom_radio) - bridges_layout.addWidget(self.bridge_custom_textbox_options) + bridges_layout.addWidget(self.bridge_use_checkbox) + bridges_layout.addWidget(self.bridge_settings) self.bridges = QtWidgets.QWidget() self.bridges.setLayout(bridges_layout) @@ -412,46 +388,56 @@ class TorSettingsDialog(QtWidgets.QDialog): ) if self.old_settings.get("no_bridges"): - self.bridge_none_radio.setChecked(True) - self.bridge_obfs4_radio.setChecked(False) - self.bridge_meek_azure_radio.setChecked(False) - self.bridge_snowflake_radio.setChecked(False) - self.bridge_moat_radio.setChecked(False) - self.bridge_custom_radio.setChecked(False) - else: - self.bridge_none_radio.setChecked(False) - self.bridge_obfs4_radio.setChecked( - self.old_settings.get("tor_bridges_use_obfs4") - ) - self.bridge_meek_azure_radio.setChecked( - self.old_settings.get("tor_bridges_use_meek_lite_azure") - ) - self.bridge_snowflake_radio.setChecked( - self.old_settings.get("tor_bridges_use_snowflake") - ) - self.bridge_moat_radio.setChecked( - self.old_settings.get("tor_bridges_use_moat") - ) - moat_bridges = self.old_settings.get("tor_bridges_use_moat_bridges") - self.bridge_moat_textbox.document().setPlainText(moat_bridges) - if len(moat_bridges.strip()) > 0: - self.bridge_moat_textbox.show() - else: - self.bridge_moat_textbox.hide() + self.bridge_use_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.bridge_settings.hide() - if self.old_settings.get("tor_bridges_use_custom_bridges"): - self.bridge_custom_radio.setChecked(True) - # Remove the 'Bridge' lines at the start of each bridge. - # They are added automatically to provide compatibility with - # copying/pasting bridges provided from https://bridges.torproject.org - new_bridges = [] - bridges = self.old_settings.get("tor_bridges_use_custom_bridges").split( - "Bridge " - ) - for bridge in bridges: - new_bridges.append(bridge) - new_bridges = "".join(new_bridges) - self.bridge_custom_textbox.setPlainText(new_bridges) + else: + self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked) + self.bridge_settings.show() + + builtin_obfs4 = self.old_settings.get("tor_bridges_use_obfs4") + builtin_meek_azure = self.old_settings.get( + "tor_bridges_use_meek_lite_azure" + ) + builtin_snowflake = self.old_settings.get("tor_bridges_use_snowflake") + + if builtin_obfs4 or builtin_meek_azure or builtin_snowflake: + self.bridge_builtin_radio.setChecked(True) + self.bridge_builtin_dropdown.show() + if builtin_obfs4: + self.bridge_builtin_dropdown.setCurrentText("obfs4") + elif builtin_meek_azure: + self.bridge_builtin_dropdown.setCurrentText("meek-azure") + elif builtin_snowflake: + self.bridge_builtin_dropdown.setCurrentText("snowflake") + + self.bridge_moat_textbox_options.hide() + self.bridge_custom_textbox_options.hide() + else: + self.bridge_builtin_radio.setChecked(False) + self.bridge_builtin_dropdown.hide() + + use_moat = self.old_settings.get("tor_bridges_use_moat") + self.bridge_moat_radio.setChecked(use_moat) + if use_moat: + self.bridge_builtin_dropdown.hide() + self.bridge_custom_textbox_options.hide() + + moat_bridges = self.old_settings.get("tor_bridges_use_moat_bridges") + self.bridge_moat_textbox.document().setPlainText(moat_bridges) + if len(moat_bridges.strip()) > 0: + self.bridge_moat_textbox_options.show() + else: + self.bridge_moat_textbox_options.hide() + + custom_bridges = self.old_settings.get("tor_bridges_use_custom_bridges") + if len(custom_bridges.strip()) != 0: + self.bridge_custom_radio.setChecked(True) + self.bridge_custom_textbox.setPlainText(custom_bridges) + + self.bridge_builtin_dropdown.hide() + self.bridge_moat_textbox_options.hide() + self.bridge_custom_textbox_options.show() def connection_type_bundled_toggled(self, checked): """ @@ -463,30 +449,31 @@ class TorSettingsDialog(QtWidgets.QDialog): self.connection_type_socks.hide() self.connection_type_bridges_radio_group.show() - def bridge_none_radio_toggled(self, checked): + def bridge_use_checkbox_state_changed(self, state): """ - 'No bridges' option was toggled. If checked, enable other bridge options. + 'Use a bridge' checkbox changed + """ + if state == QtCore.Qt.Checked: + self.bridge_settings.show() + self.bridge_builtin_radio.click() + self.bridge_builtin_dropdown.setCurrentText("obfs4") + else: + self.bridge_settings.hide() + + def bridge_builtin_radio_toggled(self, checked): + """ + 'Select a built-in bridge' radio button toggled """ if checked: + self.bridge_builtin_dropdown.show() self.bridge_custom_textbox_options.hide() self.bridge_moat_textbox_options.hide() - def bridge_obfs4_radio_toggled(self, checked): + def bridge_builtin_dropdown_changed(self, selection): """ - obfs4 bridges option was toggled. If checked, disable custom bridge options. + Build-in bridge selection changed """ - if checked: - self.bridge_custom_textbox_options.hide() - self.bridge_moat_textbox_options.hide() - - def bridge_meek_azure_radio_toggled(self, checked): - """ - meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. - """ - if checked: - self.bridge_custom_textbox_options.hide() - self.bridge_moat_textbox_options.hide() - + if selection == "meek-azure": # Alert the user about meek's costliness if it looks like they're turning it on if not self.old_settings.get("tor_bridges_use_meek_lite_azure"): Alert( @@ -495,19 +482,12 @@ class TorSettingsDialog(QtWidgets.QDialog): QtWidgets.QMessageBox.Warning, ) - def bridge_snowflake_radio_toggled(self, checked): - """ - snowflake bridges option was toggled. If checked, disable custom bridge options. - """ - if checked: - self.bridge_custom_textbox_options.hide() - self.bridge_moat_textbox_options.hide() - def bridge_moat_radio_toggled(self, checked): """ Moat (request bridge) bridges option was toggled. If checked, show moat bridge options. """ if checked: + self.bridge_builtin_dropdown.hide() self.bridge_custom_textbox_options.hide() self.bridge_moat_textbox_options.show() @@ -534,8 +514,9 @@ class TorSettingsDialog(QtWidgets.QDialog): Custom bridges option was toggled. If checked, show custom bridge options. """ if checked: - self.bridge_custom_textbox_options.show() + self.bridge_builtin_dropdown.hide() self.bridge_moat_textbox_options.hide() + self.bridge_custom_textbox_options.show() def connection_type_automatic_toggled(self, checked): """ @@ -592,6 +573,8 @@ class TorSettingsDialog(QtWidgets.QDialog): """ self.common.log("TorSettingsDialog", "test_tor_clicked") settings = self.settings_from_fields() + if not settings: + return onion = Onion( self.common, @@ -765,85 +748,77 @@ class TorSettingsDialog(QtWidgets.QDialog): settings.set("auth_password", self.authenticate_password_extras_password.text()) # Whether we use bridges - if self.bridge_none_radio.isChecked(): + if self.bridge_use_checkbox.checkState() == QtCore.Qt.Checked: + settings.set("no_bridges", False) + + if self.bridge_builtin_radio.isChecked(): + selection = self.bridge_builtin_dropdown.currentText() + if selection == "obfs4": + settings.set("tor_bridges_use_obfs4", True) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", False) + elif selection == "meek-azure": + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", True) + settings.set("tor_bridges_use_snowflake", False) + elif selection == "snowflake": + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", True) + + settings.set("tor_bridges_use_moat", False) + settings.set("tor_bridges_use_custom_bridges", "") + + if self.bridge_moat_radio.isChecked(): + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", False) + + settings.set("tor_bridges_use_moat", True) + settings.set( + "tor_bridges_use_moat_bridges", + self.bridge_moat_textbox.toPlainText(), + ) + + settings.set("tor_bridges_use_custom_bridges", "") + + if self.bridge_custom_radio.isChecked(): + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", False) + settings.set("tor_bridges_use_moat", False) + + new_bridges = [] + bridges = self.bridge_custom_textbox.toPlainText().split("\n") + bridges_valid = False + for bridge in bridges: + if bridge != "": + # Check the syntax of the custom bridge to make sure it looks legitimate + ipv4_pattern = re.compile( + "(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$" + ) + ipv6_pattern = re.compile( + "(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$" + ) + meek_lite_pattern = re.compile( + "(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)" + ) + if ( + ipv4_pattern.match(bridge) + or ipv6_pattern.match(bridge) + or meek_lite_pattern.match(bridge) + ): + new_bridges.append(bridge) + bridges_valid = True + + if bridges_valid: + new_bridges = "\n".join(new_bridges) + "\n" + settings.set("tor_bridges_use_custom_bridges", new_bridges) + else: + Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) + return False + else: settings.set("no_bridges", True) - settings.set("tor_bridges_use_obfs4", False) - settings.set("tor_bridges_use_meek_lite_azure", False) - settings.set("tor_bridges_use_snowflake", False) - settings.set("tor_bridges_use_moat", False) - settings.set("tor_bridges_use_custom_bridges", "") - if self.bridge_obfs4_radio.isChecked(): - settings.set("no_bridges", False) - settings.set("tor_bridges_use_obfs4", True) - settings.set("tor_bridges_use_meek_lite_azure", False) - settings.set("tor_bridges_use_snowflake", False) - settings.set("tor_bridges_use_moat", False) - settings.set("tor_bridges_use_custom_bridges", "") - if self.bridge_meek_azure_radio.isChecked(): - settings.set("no_bridges", False) - settings.set("tor_bridges_use_obfs4", False) - settings.set("tor_bridges_use_meek_lite_azure", True) - settings.set("tor_bridges_use_snowflake", False) - settings.set("tor_bridges_use_moat", False) - settings.set("tor_bridges_use_custom_bridges", "") - if self.bridge_snowflake_radio.isChecked(): - settings.set("no_bridges", False) - settings.set("tor_bridges_use_obfs4", False) - settings.set("tor_bridges_use_meek_lite_azure", False) - settings.set("tor_bridges_use_snowflake", True) - settings.set("tor_bridges_use_moat", False) - settings.set("tor_bridges_use_custom_bridges", "") - if self.bridge_moat_radio.isChecked(): - settings.set("no_bridges", False) - settings.set("tor_bridges_use_obfs4", False) - settings.set("tor_bridges_use_meek_lite_azure", False) - settings.set("tor_bridges_use_snowflake", False) - settings.set("tor_bridges_use_moat", True) - settings.set( - "tor_bridges_use_moat_bridges", self.bridge_moat_textbox.toPlainText() - ) - settings.set("tor_bridges_use_custom_bridges", "") - if self.bridge_custom_radio.isChecked(): - settings.set("no_bridges", False) - settings.set("tor_bridges_use_obfs4", False) - settings.set("tor_bridges_use_meek_lite_azure", False) - settings.set("tor_bridges_use_snowflake", False) - settings.set("tor_bridges_use_moat", False) - settings.set("tor_bridges_use_moat_bridges", "") - - # Insert a 'Bridge' line at the start of each bridge. - # This makes it easier to copy/paste a set of bridges - # provided from https://bridges.torproject.org - new_bridges = [] - bridges = self.bridge_custom_textbox.toPlainText().split("\n") - bridges_valid = False - for bridge in bridges: - if bridge != "": - # Check the syntax of the custom bridge to make sure it looks legitimate - ipv4_pattern = re.compile( - "(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$" - ) - ipv6_pattern = re.compile( - "(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$" - ) - meek_lite_pattern = re.compile( - "(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)" - ) - if ( - ipv4_pattern.match(bridge) - or ipv6_pattern.match(bridge) - or meek_lite_pattern.match(bridge) - ): - new_bridges.append("".join(["Bridge ", bridge, "\n"])) - bridges_valid = True - - if bridges_valid: - new_bridges = "".join(new_bridges) - settings.set("tor_bridges_use_custom_bridges", new_bridges) - else: - Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) - settings.set("no_bridges", True) - return False return settings From a89412e79d3aab8a64e5a5348bf6386544363612 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Oct 2021 15:47:11 -0700 Subject: [PATCH 25/26] Make it so when selecting moat tor actually uses those bridges, and improve tor settings dialog --- cli/onionshare_cli/onion.py | 16 +++++++++++++--- desktop/src/onionshare/moat_dialog.py | 1 + desktop/src/onionshare/resources/locale/en.json | 3 ++- desktop/src/onionshare/tor_settings_dialog.py | 8 +++++++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index c3ee4fac..d52af9f3 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -333,9 +333,19 @@ class Onion(object): for line in o: f.write(line) - if self.settings.get("tor_bridges_use_custom_bridges"): - f.write(self.settings.get("tor_bridges_use_custom_bridges") + "\n") - f.write("\nUseBridges 1") + elif self.settings.get("tor_bridges_use_moat"): + for line in self.settings.get("tor_bridges_use_moat_bridges").split( + "\n" + ): + f.write(f"Bridge {line}\n") + f.write("\nUseBridges 1\n") + + elif self.settings.get("tor_bridges_use_custom_bridges"): + for line in self.settings.get( + "tor_bridges_use_custom_bridges" + ).split("\n"): + f.write(f"Bridge {line}\n") + f.write("\nUseBridges 1\n") # Execute a tor subprocess start_ts = time.time() diff --git a/desktop/src/onionshare/moat_dialog.py b/desktop/src/onionshare/moat_dialog.py index 3cb6519b..ea58898b 100644 --- a/desktop/src/onionshare/moat_dialog.py +++ b/desktop/src/onionshare/moat_dialog.py @@ -172,6 +172,7 @@ class MoatDialog(QtWidgets.QDialog): self.label.setText(strings._("moat_captcha_label")) self.captcha.show() + self.solution_lineedit.setText("") self.solution_lineedit.show() self.reload_button.show() self.submit_button.show() diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 63bfd48c..a9fb562a 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -67,9 +67,10 @@ "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.", "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_button": "Request a New Bridge", "gui_settings_bridge_custom_radio_option": "Provide a bridge you learned about from a trusted source", "gui_settings_bridge_custom_placeholder": "type address:port (one per line)", + "gui_settings_moat_bridges_invalid": "You have not requested a bridge from torproject.org yet.", "gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.", "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index 6fa4dda1..adad6931 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -775,9 +775,15 @@ class TorSettingsDialog(QtWidgets.QDialog): settings.set("tor_bridges_use_snowflake", False) settings.set("tor_bridges_use_moat", True) + + moat_bridges = self.bridge_moat_textbox.toPlainText() + if moat_bridges.strip() == "": + Alert(self.common, strings._("gui_settings_moat_bridges_invalid")) + return False + settings.set( "tor_bridges_use_moat_bridges", - self.bridge_moat_textbox.toPlainText(), + moat_bridges, ) settings.set("tor_bridges_use_custom_bridges", "") From 3b6b74f649002f59c8fba07815b30c7340911687 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Oct 2021 15:59:07 -0700 Subject: [PATCH 26/26] Rearrange moat dialog so pressing enter submits --- desktop/src/onionshare/moat_dialog.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/desktop/src/onionshare/moat_dialog.py b/desktop/src/onionshare/moat_dialog.py index ea58898b..2651736e 100644 --- a/desktop/src/onionshare/moat_dialog.py +++ b/desktop/src/onionshare/moat_dialog.py @@ -55,11 +55,9 @@ class MoatDialog(QtWidgets.QDialog): # Solution input self.solution_lineedit = QtWidgets.QLineEdit() self.solution_lineedit.setPlaceholderText(strings._("moat_captcha_placeholder")) - 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) + self.solution_lineedit.editingFinished.connect( + self.solution_lineedit_editing_finished + ) # Error label self.error_label = QtWidgets.QLabel() @@ -69,20 +67,23 @@ class MoatDialog(QtWidgets.QDialog): # Buttons self.submit_button = QtWidgets.QPushButton(strings._("moat_captcha_submit")) self.submit_button.clicked.connect(self.submit_clicked) + self.reload_button = QtWidgets.QPushButton(strings._("moat_captcha_reload")) + self.reload_button.clicked.connect(self.reload_clicked) 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.addStretch() + buttons_layout.addWidget(self.reload_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.solution_lineedit) layout.addStretch() layout.addWidget(self.error_label) layout.addLayout(buttons_layout) @@ -117,6 +118,7 @@ class MoatDialog(QtWidgets.QDialog): Submit button clicked. """ self.error_label.hide() + self.solution_lineedit.setEnabled(False) solution = self.solution_lineedit.text().strip() if len(solution) == 0: @@ -148,6 +150,8 @@ class MoatDialog(QtWidgets.QDialog): self.error_label.setText(strings._("moat_bridgedb_error")) self.error_label.show() + self.solution_lineedit.setEnabled(True) + def captcha_error(self, msg): self.common.log("MoatDialog", "captcha_error") if msg == "": @@ -156,6 +160,8 @@ class MoatDialog(QtWidgets.QDialog): self.error_label.setText(msg) self.error_label.show() + self.solution_lineedit.setEnabled(True) + def captcha_ready(self, image, challenge): self.common.log("MoatDialog", "captcha_ready") @@ -172,11 +178,16 @@ class MoatDialog(QtWidgets.QDialog): self.label.setText(strings._("moat_captcha_label")) self.captcha.show() + self.solution_lineedit.setEnabled(True) self.solution_lineedit.setText("") self.solution_lineedit.show() + self.solution_lineedit.setFocus() self.reload_button.show() self.submit_button.show() + def solution_lineedit_editing_finished(self): + self.common.log("MoatDialog", "solution_lineedit_editing_finished") + def bridges_ready(self, bridges): self.common.log("MoatDialog", "bridges_ready", bridges) self.got_bridges.emit(bridges)