2 * Copyright (c) 2016, JANET(UK)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of JANET(UK) nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 // Defined here as workaround for emacs vala-mode indentation failure.
39 static const string CANCEL = Stock.CANCEL;
41 static const string CANCEL = STOCK_CANCEL;
45 class IdentityDialog : Dialog
47 private static Gdk.Color white = make_color(65535, 65535, 65535);
48 private static Gdk.Color selected_color = make_color(0xd9 << 8, 0xf7 << 8, 65535);
50 private static MoonshotLogger logger = get_logger("IdentityDialog");
52 static const string displayname_labeltext = _("Display Name");
53 static const string realm_labeltext = _("Realm");
54 static const string username_labeltext = _("Username");
55 static const string password_labeltext = _("Password");
57 private Entry displayname_entry;
58 private Label displayname_label;
59 private Entry realm_entry;
60 private Label realm_label;
61 private Entry username_entry;
62 private Label username_label;
63 private Entry password_entry;
64 private Label password_label;
65 private CheckButton remember_checkbutton;
66 private Label message_label;
70 private Label selected_item = null;
72 // Whether to clear the card's TrustAnchor after the user selects OK
73 internal bool clear_trust_anchor = false;
75 public string display_name {
76 get { return displayname_entry.get_text(); }
79 public string issuer {
80 get { return realm_entry.get_text(); }
83 public string username {
84 get { return username_entry.get_text(); }
87 public string password {
88 get { return password_entry.get_text(); }
91 public bool store_password {
92 get { return remember_checkbutton.active; }
95 internal ArrayList<string> get_services()
100 public IdentityDialog(IdentityManagerView parent)
102 this.with_idcard(null, _("Add ID Card"), parent);
105 public IdentityDialog.with_idcard(IdCard? a_card, string title, IdentityManagerView parent)
107 bool is_new_card = false;
113 card = a_card ?? new IdCard();
114 this.set_title(title);
115 this.set_modal(true);
116 this.set_transient_for(parent);
118 this.add_buttons(_("OK"), ResponseType.OK, CANCEL, ResponseType.CANCEL);
119 Box content_area = (Box) this.get_content_area();
121 displayname_label = new Label(@"$displayname_labeltext:");
122 displayname_label.set_alignment(0, (float) 0.5);
123 displayname_entry = new Entry();
124 displayname_entry.set_text(card.display_name);
125 displayname_entry.set_width_chars(40);
127 realm_label = new Label(@"$realm_labeltext:");
128 realm_label.set_alignment(0, (float) 0.5);
129 realm_entry = new Entry();
130 realm_entry.set_text(card.issuer);
131 realm_entry.set_width_chars(60);
133 username_label = new Label(@"$username_labeltext:");
134 username_label.set_alignment(0, (float) 0.5);
135 username_entry = new Entry();
136 username_entry.set_text(card.username);
137 username_entry.set_width_chars(40);
139 password_label = new Label(@"$password_labeltext:");
140 password_label.set_alignment(0, (float) 0.5);
142 remember_checkbutton = new CheckButton.with_label(_("Remember password"));
143 remember_checkbutton.active = card.store_password;
145 password_entry = new Entry();
146 password_entry.set_invisible_char('*');
147 password_entry.set_visibility(false);
148 password_entry.set_width_chars(40);
149 password_entry.set_text(card.password);
151 message_label = new Label("");
152 message_label.set_visible(false);
154 set_atk_relation(displayname_label, displayname_entry, Atk.RelationType.LABEL_FOR);
155 set_atk_relation(realm_label, realm_entry, Atk.RelationType.LABEL_FOR);
156 set_atk_relation(username_label, username_entry, Atk.RelationType.LABEL_FOR);
157 set_atk_relation(password_label, password_entry, Atk.RelationType.LABEL_FOR);
159 content_area.pack_start(message_label, false, false, 6);
160 add_as_vbox(content_area, displayname_label, displayname_entry);
161 add_as_vbox(content_area, username_label, username_entry);
162 add_as_vbox(content_area, realm_label, realm_entry);
163 add_as_vbox(content_area, password_label, password_entry);
165 // var entries = new VBox(false, 6);
166 // add_as_vbox(entries, displayname_label, displayname_entry);
167 // add_as_vbox(entries, realm_label, realm_entry);
168 // add_as_vbox(entries, username_label, username_entry);
169 // add_as_vbox(entries, password_label, password_entry);
170 // content_area.pack_start(entries, false, false, 0);
172 var remember_hbox = new HBox(false, 40);
173 remember_hbox.pack_start(new HBox(false, 0), false, false, 0);
174 remember_hbox.pack_start(remember_checkbutton, false, false, 0);
175 content_area.pack_start(remember_hbox, false, false, 2);
176 // content_area.pack_start(remember_checkbutton, false, false, 2);
178 this.response.connect(on_response);
179 content_area.set_border_width(6);
183 Widget trust_anchor_box = make_trust_anchor_box(card);
184 content_area.pack_start(trust_anchor_box, false, false, 15);
186 var services_vbox = make_services_vbox();
187 content_area.pack_start(services_vbox);
190 if (card.is_no_identity())
192 displayname_entry.set_sensitive(false);
193 realm_entry.set_sensitive(false);
194 username_entry.set_sensitive(false);
195 password_entry.set_sensitive(false);
196 remember_checkbutton.set_sensitive(false);
199 this.set_border_width(6);
200 this.set_resizable(false);
201 this.modify_bg(StateType.NORMAL, white);
205 private Widget make_trust_anchor_box(IdCard id)
208 Label ta_label = new Label(_("Trust anchor: ")
209 + (id.trust_anchor.is_empty() ? _("None") : _("Enterprise provisioned")));
210 ta_label.set_alignment(0, 0.5f);
212 if (id.trust_anchor.is_empty()) {
217 AttachOptions opts = AttachOptions.EXPAND | AttachOptions.FILL;
218 AttachOptions fill = AttachOptions.FILL;
220 Table ta_table = new Table(6, 2, false);
223 var ta_clear_button = new Button.with_label(_("Clear Trust Anchor"));
224 ta_clear_button.clicked.connect((w) => {
225 clear_trust_anchor = true;
226 ta_table.set_sensitive(false);
230 ta_table.attach(ta_label, 0, 1, row, row + 1, opts, opts, 0, 0);
231 ta_table.attach(ta_clear_button, 1, 2, row, row + 1, fill, fill, 0, 0);
235 Label added_label = new Label(_("Added on: N/A"));
236 added_label.set_alignment(0, 0.5f);
237 ta_table.attach(added_label, 0, 1, row, row + 1, opts, opts, 20, 5);
240 if (id.trust_anchor.get_anchor_type() == TrustAnchor.TrustAnchorType.SERVER_CERT) {
241 Widget fingerprint = make_ta_fingerprint_widget(id.trust_anchor);
242 ta_table.attach(fingerprint, 0, 2, row, row + 2, opts, opts, 20, 5);
245 Label ca_cert_label = new Label(_("CA Certificate:"));
246 ca_cert_label.set_alignment(0, 0.5f);
247 var export_button = new Button.with_label(_("Export Certificate"));
249 export_button.clicked.connect((w) => {export_certificate(id);});
251 ta_table.attach(ca_cert_label, 0, 1, row, row + 1, opts, opts, 20, 0);
252 ta_table.attach(export_button, 1, 2, row, row + 1, fill, fill, 0, 0);
255 //!!TODO: When to show Subject, and when (if ever) show Subject-Altname here?
256 Label subject_label = new Label(_("Subject: ") + id.trust_anchor.subject);
257 subject_label.set_alignment(0, 0.5f);
258 ta_table.attach(subject_label, 0, 1, row, row + 1, opts, opts, 40, 5);
261 Label expiration_label = new Label(_("Expiration date: ") + id.trust_anchor.get_expiration_date());
262 expiration_label.set_alignment(0, 0.5f);
263 ta_table.attach(expiration_label, 0, 1, row, row + 1, opts, opts, 40, 5);
266 //!!TODO: What *is* this?
267 Label constraint_label = new Label(_("Constraint: "));
268 constraint_label.set_alignment(0, 0.5f);
269 ta_table.attach(constraint_label, 0, 1, row, row + 1, opts, opts, 20, 0);
277 private static void add_as_vbox(Box content_area, Label label, Entry entry)
279 VBox vbox = new VBox(false, 2);
281 vbox.pack_start(label, false, false, 0);
282 vbox.pack_start(entry, false, false, 0);
284 // Hack to prevent the text entries from stretching horizontally
285 HBox hbox = new HBox(false, 0);
286 hbox.pack_start(vbox, false, false, 0);
287 content_area.pack_start(hbox, false, false, 6);
290 private static string update_preamble(string preamble)
293 return _("Missing required field: ");
294 return _("Missing required fields: ");
297 private static string update_message(string old_message, string new_item)
300 if (old_message == "")
303 message = old_message + ", " + new_item;
307 private static void check_field(string field, Label label, string fieldname, ref string preamble, ref string message)
310 label.set_markup(@"$fieldname:");
313 label.set_markup(@"<span foreground=\"red\">$fieldname:</span>");
314 preamble = update_preamble(preamble);
315 message = update_message(message, fieldname);
318 private bool check_fields()
320 string preamble = "";
322 string password_test = store_password ? password : "not required";
323 if (!card.is_no_identity())
325 check_field(display_name, displayname_label, displayname_labeltext, ref preamble, ref message);
326 check_field(username, username_label, username_labeltext, ref preamble, ref message);
327 check_field(issuer, realm_label, realm_labeltext, ref preamble, ref message);
328 check_field(password_test, password_label, password_labeltext, ref preamble, ref message);
331 message_label.set_visible(true);
332 message_label.set_markup(@"<span foreground=\"red\">$preamble$message</span>");
338 private void on_response(Dialog source, int response_id)
340 switch (response_id) {
341 case ResponseType.OK:
342 complete = check_fields();
344 case ResponseType.CANCEL:
350 private static void label_make_bold(Label label)
352 var font_desc = new Pango.FontDescription();
354 font_desc.set_weight(Pango.Weight.BOLD);
356 /* This will only affect the weight of the font. The rest is
357 * from the current state of the widget, which comes from the
358 * theme or user prefs, since the font desc only has the
359 * weight flag turned on.
361 label.modify_font(font_desc);
364 private VBox make_services_vbox()
366 logger.trace("make_services_vbox");
368 var services_vbox_alignment = new Alignment(0, 0, 1, 0);
369 var services_vscroll = new ScrolledWindow(null, null);
370 services_vscroll.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
371 services_vscroll.set_shadow_type(ShadowType.IN);
372 services_vscroll.set_size_request(0, 60);
373 services_vscroll.add_with_viewport(services_vbox_alignment);
376 var remove_button = new Button.from_stock(Stock.REMOVE);
378 var remove_button = new Button.from_stock(STOCK_REMOVE);
380 remove_button.set_sensitive(false);
383 var services_table = new Table(card.services.size, 1, false);
384 services_table.set_row_spacings(1);
385 services_table.set_col_spacings(0);
386 services_table.modify_bg(StateType.NORMAL, white);
388 var table_button_hbox = new HBox(false, 6);
389 table_button_hbox.pack_start(services_vscroll, true, true, 6);
391 // Hack to prevent the button from growing vertically
392 VBox fixed_height = new VBox(false, 0);
393 fixed_height.pack_start(remove_button, false, false, 0);
394 table_button_hbox.pack_start(fixed_height, false, false, 0);
396 // A table doesn't have a background color, so put it in an EventBox, and
397 // set the EventBox's background color instead.
398 EventBox table_bg = new EventBox();
399 table_bg.modify_bg(StateType.NORMAL, white);
400 table_bg.add(services_table);
401 services_vbox_alignment.add(table_bg);
403 var services_vbox_title = new Label(_("Services:"));
404 label_make_bold(services_vbox_title);
405 services_vbox_title.set_alignment(0, 0.5f);
407 var services_vbox = new VBox(false, 6);
408 services_vbox.pack_start(services_vbox_title, false, false, 0);
409 services_vbox.pack_start(table_button_hbox, true, true, 0);
412 foreach (string service in card.services)
414 var label = new Label(service);
415 label.set_alignment((float) 0, (float) 0);
418 EventBox event_box = new EventBox();
419 event_box.modify_bg(StateType.NORMAL, white);
420 event_box.add(label);
421 event_box.button_press_event.connect(() =>
423 var state = label.get_state();
424 logger.trace("button_press_callback: Label state=" + state.to_string() + " setting bg to " + white.to_string());
426 if (selected_item == label)
429 selected_item.parent.modify_bg(state, white);
430 selected_item = null;
431 remove_button.set_sensitive(false);
435 if (selected_item != null)
438 selected_item.parent.modify_bg(state, white);
439 selected_item = null;
443 selected_item = label;
444 selected_item.parent.modify_bg(state, selected_color);
445 remove_button.set_sensitive(true);
450 services_table.attach_defaults(event_box, 0, 1, i, i+1);
454 remove_button.clicked.connect((remove_button) =>
456 var result = WarningDialog.confirm(this,
457 Markup.printf_escaped(
458 "<span font-weight='heavy'>You are about to remove the service '%s'.</span>",
460 + "\n\nAre you sure you want to do this?",
466 card.services.remove(selected_item.label);
467 services_table.remove(selected_item.parent);
468 selected_item = null;
469 remove_button.set_sensitive(false);
475 return services_vbox;
478 private void export_certificate(IdCard id)
480 var dialog = new FileChooserDialog("Save File",
482 FileChooserAction.SAVE,
483 _("Cancel"),ResponseType.CANCEL,
484 _("Save"), ResponseType.ACCEPT,
486 dialog.set_do_overwrite_confirmation(true);
487 // dialog.set_current_folder(default_folder_for_saving);
488 //dialog.set_current_name("Untitled document");
489 if (dialog.run() == ResponseType.ACCEPT)
491 const string CERT_HEADER = "-----BEGIN CERTIFICATE-----\n";
492 const string CERT_FOOTER = "\n-----END CERTIFICATE-----\n";
494 // Normalize the certificate to PEM format:
495 // 1) Strip any embedded newlines in the certificate...
496 string cert = id.trust_anchor.ca_cert.replace("\n", "");
498 // 2), re-embed newlines every 64 chars.
499 string newcert = CERT_HEADER;
500 while (cert.length > 63) {
501 newcert += cert[0:63] + "\n";
502 cert = cert[63:cert.length];
504 if (cert.length > 0) {
506 newcert += CERT_FOOTER;
509 string filename = dialog.get_filename();
510 var file = File.new_for_path(filename);
511 var stream = file.replace(null, false, FileCreateFlags.PRIVATE);
512 stream.write(newcert.data);