aboutsummaryrefslogtreecommitdiff
path: root/plugins/omemo/src/ui/contact_details_dialog.vala
blob: ad43f00e6c7d637a498f224ec38fddd338992a01 (plain) (blame)
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
using Gtk;
using Xmpp;
using Gee;
using Qlite;
using Dino.Entities;
using Qrencode;
using Gdk;

namespace Dino.Plugins.Omemo {

[GtkTemplate (ui = "/im/dino/Dino/omemo/contact_details_dialog.ui")]
public class ContactDetailsDialog : Gtk.Dialog {

    private Plugin plugin;
    private Account account;
    private Jid jid;
    private bool own = false;
    private int own_id = 0;

    [GtkChild] private Label automatically_accept_new_label;
    [GtkChild] private Label automatically_accept_new_descr;
    [GtkChild] private Label own_key_label;
    [GtkChild] private Label new_keys_label;
    [GtkChild] private Label associated_keys_label;
    [GtkChild] private Box own_fingerprint_container;
    [GtkChild] private Label own_fingerprint_label;
    [GtkChild] private Box new_keys_container;
    [GtkChild] private ListBox new_keys_listbox;
    [GtkChild] private Box keys_container;
    [GtkChild] private ListBox keys_listbox;
    [GtkChild] private ListBox unused_keys_listbox;
    [GtkChild] private Switch auto_accept_switch;
    [GtkChild] private Button copy_button;
    [GtkChild] private Button show_qrcode_button;
    [GtkChild] private Image qrcode_image;
    [GtkChild] private Popover qrcode_popover;

    construct {
        // If we set the strings in the .ui file, they don't get translated
        title = _("OMEMO Key Management");
        automatically_accept_new_label.label = _("Automatically accept new keys");
        automatically_accept_new_descr.label = _("When this contact adds new encryption keys to their account, automatically accept them.");
        own_key_label.label = _("Own key");
        new_keys_label.label = _("New keys");
        associated_keys_label.label = _("Associated keys");
    }

    public ContactDetailsDialog(Plugin plugin, Account account, Jid jid) {
        Object(use_header_bar : Environment.get_variable("GTK_CSD") != "0" ? 1 : 0);
        this.plugin = plugin;
        this.account = account;
        this.jid = jid;

        if (Environment.get_variable("GTK_CSD") != "0") {
            (get_header_bar() as HeaderBar).set_subtitle(jid.bare_jid.to_string());
        }

        keys_listbox.row_activated.connect(on_key_entry_clicked);
        unused_keys_listbox.row_activated.connect(on_key_entry_clicked);
        auto_accept_switch.state_set.connect(on_auto_accept_toggled);

        int identity_id = plugin.db.identity.get_id(account.id);
        if (identity_id < 0) return;

        auto_accept_switch.set_active(plugin.db.trust.get_blind_trust(identity_id, jid.bare_jid.to_string()));

        // Dialog opened from the account settings menu
        // Show the fingerprint for this device separately with buttons for a qrcode and to copy
        if(jid.equals(account.bare_jid)) {
            own = true;
            own_id = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id];

            automatically_accept_new_descr.label = _("When you add new encryption keys to your account, automatically accept them.");

            own_fingerprint_container.visible = true;

            string own_b64 = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.identity_key_public_base64];
            string fingerprint = fingerprint_from_base64(own_b64);
            own_fingerprint_label.set_markup(fingerprint_markup(fingerprint));

            copy_button.clicked.connect(() => {Clipboard.get_default(get_display()).set_text(fingerprint, fingerprint.length);});

            int sid = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id];
            Pixbuf qr_pixbuf = new QRcode(@"xmpp:$(account.bare_jid)?omemo-sid-$(sid)=$(fingerprint)", 2).to_pixbuf();
            qr_pixbuf = qr_pixbuf.scale_simple(150, 150, InterpType.NEAREST);

            Pixbuf pixbuf = new Pixbuf(
                qr_pixbuf.colorspace,
                qr_pixbuf.has_alpha,
                qr_pixbuf.bits_per_sample,
                170,
                170
            );
            pixbuf.fill(uint32.MAX);
            qr_pixbuf.copy_area(0, 0, 150, 150, pixbuf, 10, 10);

            qrcode_image.set_from_pixbuf(pixbuf);
            show_qrcode_button.clicked.connect(qrcode_popover.popup);
        }

        new_keys_listbox.set_header_func(header_function);

        keys_listbox.set_header_func(header_function);

        //Show any new devices for which the user must decide whether to accept or reject
        foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) {
            add_new_fingerprint(device);
        }

        //Show the normal devicelist
        foreach (Row device in plugin.db.identity_meta.get_known_devices(identity_id, jid.to_string())) {
            if(own && device[plugin.db.identity_meta.device_id] == own_id) {
                continue;
            }
            add_fingerprint(device, (TrustLevel) device[plugin.db.identity_meta.trust_level]);
        }
    }

    private void header_function(ListBoxRow row, ListBoxRow? before) {
        if (row.get_header() == null && before != null) {
            row.set_header(new Separator(Orientation.HORIZONTAL));
        }
    }

    private void add_fingerprint(Row device, TrustLevel trust) {
        string key_base64 = device[plugin.db.identity_meta.identity_key_public_base64];
        bool key_active = device[plugin.db.identity_meta.now_active];
        FingerprintRow fingerprint_row = new FingerprintRow(device, key_base64, trust, key_active) { visible = true, activatable = true, hexpand = true };

        if (device[plugin.db.identity_meta.now_active]) {
            keys_container.visible = true;
            keys_listbox.add(fingerprint_row);
        } else {
            unused_keys_listbox.add(fingerprint_row);
        }
    }

    private void on_key_entry_clicked(ListBoxRow widget) {
        FingerprintRow? fingerprint_row = widget as FingerprintRow;
        if (fingerprint_row == null) return;

        Row updated_device = plugin.db.identity_meta.get_device(fingerprint_row.row[plugin.db.identity_meta.identity_id], fingerprint_row.row[plugin.db.identity_meta.address_name], fingerprint_row.row[plugin.db.identity_meta.device_id]);
        ManageKeyDialog manage_dialog = new ManageKeyDialog(updated_device, plugin.db);
        manage_dialog.set_transient_for((Gtk.Window) get_toplevel());
        manage_dialog.present();
        manage_dialog.response.connect((response) => {
            fingerprint_row.update_trust_state(response, fingerprint_row.row[plugin.db.identity_meta.now_active]);
            update_stored_trust(response, fingerprint_row.row);
        });
    }

    private bool on_auto_accept_toggled(bool active) {
        plugin.trust_manager.set_blind_trust(account, jid, active);

        if (active) {
            int identity_id = plugin.db.identity.get_id(account.id);
            if (identity_id < 0) return false;

            new_keys_container.visible = false;
            foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) {
                plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED);
                add_fingerprint(device, TrustLevel.TRUSTED);
            }
        }
        return false;
    }

    private void update_stored_trust(int response, Row device) {
        switch (response) {
            case TrustLevel.TRUSTED:
                plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED);
                break;
            case TrustLevel.UNTRUSTED:
                plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED);
                break;
            case TrustLevel.VERIFIED:
                plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.VERIFIED);
                plugin.trust_manager.set_blind_trust(account, jid, false);
                auto_accept_switch.set_active(false);
                break;
        }
    }

    private void add_new_fingerprint(Row device) {
        new_keys_container.visible = true;

        ListBoxRow lbr = new ListBoxRow() { visible = true, activatable = false, hexpand = true };
        Box box = new Box(Gtk.Orientation.HORIZONTAL, 40) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14, hexpand = true };

        Box control_box = new Box(Gtk.Orientation.HORIZONTAL, 0) { visible = true, hexpand = true };

        Button yes_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true };
        yes_button.image = new Image.from_icon_name("emblem-ok-symbolic", IconSize.BUTTON);
        yes_button.get_style_context().add_class("suggested-action");

        Button no_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true };
        no_button.image = new Image.from_icon_name("action-unavailable-symbolic", IconSize.BUTTON);
        no_button.get_style_context().add_class("destructive-action");

        yes_button.clicked.connect(() => {
            plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED);
            add_fingerprint(device, TrustLevel.TRUSTED);
            new_keys_listbox.remove(lbr);
            if (new_keys_listbox.get_children().length() < 1) new_keys_container.visible = false;
        });

        no_button.clicked.connect(() => {
            plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED);
            add_fingerprint(device, TrustLevel.UNTRUSTED);
            new_keys_listbox.remove(lbr);
            if (new_keys_listbox.get_children().length() < 1) new_keys_container.visible = false;
        });

        string res = fingerprint_markup(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64]));
        Label lbl = new Label(res)
            { use_markup=true, justify=Justification.RIGHT, visible=true, halign = Align.START, valign = Align.CENTER, hexpand = false };


        box.add(lbl);

        control_box.add(yes_button);
        control_box.add(no_button);
        control_box.get_style_context().add_class("linked");

        box.add(control_box);

        lbr.add(box);
        new_keys_listbox.add(lbr);
    }
}

public class FingerprintRow : ListBoxRow {

    private Image trust_image = new Image() { visible = true, halign = Align.END, icon_size = IconSize.BUTTON };
    private Label fingerprint_label = new Label("") { use_markup=true, justify=Justification.RIGHT, visible=true, halign = Align.START, valign = Align.CENTER, hexpand = false };
    private Label trust_label = new Label(null) { visible = true, hexpand = true, xalign = 0 };

    public Row row;

    construct {
        Box box = new Box(Gtk.Orientation.HORIZONTAL, 40) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14, hexpand = true };
        Box status_box = new Box(Gtk.Orientation.HORIZONTAL, 5) { visible = true, hexpand = true };

        box.add(fingerprint_label);
        box.add(status_box);

        status_box.add(trust_label);
        status_box.add(trust_image);

        this.add(box);
    }

    public FingerprintRow(Row row, string key_base64, int trust, bool now_active) {
        this.row = row;
        fingerprint_label.label = fingerprint_markup(fingerprint_from_base64(key_base64));
        update_trust_state(trust, now_active);
    }

    public void update_trust_state(int trust, bool now_active) {
        switch(trust) {
            case TrustLevel.TRUSTED:
                trust_image.icon_name = "emblem-ok-symbolic";
                trust_label.set_markup("<span color='#1A63D9'>%s</span>".printf(_("Accepted")));
                fingerprint_label.get_style_context().remove_class("dim-label");
                break;
            case TrustLevel.UNTRUSTED:
                trust_image.icon_name = "action-unavailable-symbolic";
                trust_label.set_markup("<span color='#D91900'>%s</span>".printf(_("Rejected")));
                fingerprint_label.get_style_context().add_class("dim-label");
                break;
            case TrustLevel.VERIFIED:
                trust_image.icon_name = "security-high-symbolic";
                trust_label.set_markup("<span color='#1A63D9'>%s</span>".printf(_("Verified")));
                fingerprint_label.get_style_context().remove_class("dim-label");
                break;
        }

        if (!now_active) {
            trust_image.icon_name = "appointment-missed-symbolic";
            trust_label.set_markup("<span color='#8b8e8f'>%s</span>".printf(_("Unused")));
        }
    }
}

}