File: iddialog.py

package info (click to toggle)
syncthing-gtk 0.9.4.4%2Bds%2Bgit20221205%2B12a9702d29ab-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 2,888 kB
  • sloc: python: 8,077; sh: 259; xml: 134; makefile: 6
file content (140 lines) | stat: -rw-r--r-- 4,317 bytes parent folder | download | duplicates (2)
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env python3
"""
Syncthing-GTK - IDDialog

Dialog with Device ID and generated QR code
"""


import http.client
import logging
import os
import ssl
import tempfile
import urllib.request

from gi.repository import Gio, GLib

from syncthing_gtk.uibuilder import UIBuilder

from .tools import IS_WINDOWS


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 parent is not 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 ui file
        self.builder = UIBuilder()
        self.builder.add_from_file(os.path.join(
            self.app.uipath, "device-id.ui"))
        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 api_key is not 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 self.ctx is not None:
            return http.client.HTTPSConnection(host, context=self.ctx)
        return True