1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
#!/usr/bin/env python3
"""
Syncthing-GTK - IDDialog
Dialog with Device ID and generated QR code
"""
from gi.repository import Gio, GLib
from .tools import IS_WINDOWS
from syncthing_gtk.uibuilder import UIBuilder
import urllib.request, http.client, ssl
import os, tempfile, logging
log = logging.getLogger("IDDialog")
class IDDialog(object):
""" Dialog with Device ID and generated QR code """
def __init__(self, app, device_id):
self.app = app
self.device_id = device_id
self.setup_widgets()
self.ssl_ctx = create_ssl_context()
self.load_data()
def __getitem__(self, name):
""" Convince method that allows widgets to be accessed via self["widget"] """
return self.builder.get_object(name)
def show(self, parent=None):
if not parent is None:
self["dialog"].set_transient_for(parent)
self["dialog"].show_all()
def close(self):
self["dialog"].hide()
self["dialog"].destroy()
def setup_widgets(self):
# Load glade file
self.builder = UIBuilder()
self.builder.add_from_file(os.path.join(self.app.gladepath, "device-id.glade"))
self.builder.connect_signals(self)
self["vID"].set_text(self.device_id)
def load_data(self):
""" Loads QR code from Syncthing daemon """
if IS_WINDOWS:
return self.load_data_urllib()
uri = "%s/qr/?text=%s" % (self.app.daemon.get_webui_url(), self.device_id)
io = Gio.file_new_for_uri(uri)
io.load_contents_async(None, self.cb_syncthing_qr, ())
def load_data_urllib(self):
""" Loads QR code from Syncthing daemon """
uri = "%s/qr/?text=%s" % (self.app.daemon.get_webui_url(), self.device_id)
api_key = self.app.daemon.get_api_key()
opener = urllib.request.build_opener(DummyHTTPSHandler(self.ssl_ctx))
if not api_key is None:
opener.addheaders = [("X-API-Key", api_key)]
a = opener.open(uri)
data = a.read()
tf = tempfile.NamedTemporaryFile("wb", suffix=".png", delete=False)
tf.write(data)
tf.close()
self["vQR"].set_from_file(tf.name)
os.unlink(tf.name)
def cb_btClose_clicked(self, *a):
self.close()
def cb_syncthing_qr(self, io, results, *a):
"""
Called when QR code is loaded or operation fails. Image is then
displayed in dialog, failure is silently ignored.
"""
try:
ok, contents, etag = io.load_contents_finish(results)
if ok:
# QR is loaded, save it to temp file and let GTK to handle
# rest
tf = tempfile.NamedTemporaryFile("wb", suffix=".png", delete=False)
tf.write(contents)
tf.close()
self["vQR"].set_from_file(tf.name)
os.unlink(tf.name)
except GLib.Error as e:
if e.code in [14, 15]:
# Unauthorized. Grab CSRF token from daemon and try again
log.warning("Failed to load image using glib. Retrying with urllib2.")
self.load_data_urllib()
except Exception as e:
log.exception(e)
return
finally:
del io
def create_ssl_context():
""" May return NULL if ssl is not available """
if hasattr(ssl, "create_default_context"):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
else:
log.warning("SSL is not available, cannot verify server certificate.")
class DummyHTTPSHandler(urllib.request.HTTPSHandler):
"""
Dummy HTTPS handler that ignores certificate errors. This in unsafe,
but used ONLY for QR code images.
"""
def __init__(self, ctx):
urllib.request.HTTPSHandler.__init__(self)
self.ctx = ctx
def https_open(self, req):
return self.do_open(self.getConnection, req)
def getConnection(self, host, timeout=300):
if not self.ctx is None:
return http.client.HTTPSConnection(host, context=self.ctx)
return True
|