d5ca79cd93066fdb4ba847d15c24f4277a556bfa
[WebKit-https.git] / WebKit / gtk / webkit / webkitsoupauthdialog.c
1 /*
2  * Copyright (C) 2009 Igalia S.L.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21
22 #include <glib/gi18n-lib.h>
23 #include <gtk/gtk.h>
24 #include <libsoup/soup.h>
25 #if USE(GNOMEKEYRING)
26 #include <gnome-keyring.h>
27 #endif
28
29 #include "webkitmarshal.h"
30 #include "webkitsoupauthdialog.h"
31
32 /**
33  * SECTION:webkitsoupauthdialog
34  * @short_description: A #SoupFeature to provide a simple
35  * authentication dialog for HTTP basic auth support.
36  *
37  * #WebKitSoupAuthDialog is a #SoupFeature that you can attach to your
38  * #SoupSession to provide a simple authentication dialog, with
39  * optional GNOME Keyring support, while handling HTTP basic auth. It
40  * is built as a simple C-only module to ease reuse.
41  */
42
43 static void webkit_soup_auth_dialog_session_feature_init(SoupSessionFeatureInterface* feature_interface, gpointer interface_data);
44 static void attach(SoupSessionFeature* manager, SoupSession* session);
45 static void detach(SoupSessionFeature* manager, SoupSession* session);
46
47 enum {
48     CURRENT_TOPLEVEL,
49     LAST_SIGNAL
50 };
51
52 static guint signals[LAST_SIGNAL] = { 0 };
53
54 G_DEFINE_TYPE_WITH_CODE(WebKitSoupAuthDialog, webkit_soup_auth_dialog, G_TYPE_OBJECT,
55                         G_IMPLEMENT_INTERFACE(SOUP_TYPE_SESSION_FEATURE,
56                                               webkit_soup_auth_dialog_session_feature_init))
57
58 static void webkit_soup_auth_dialog_class_init(WebKitSoupAuthDialogClass* klass)
59 {
60     GObjectClass* object_class = G_OBJECT_CLASS(klass);
61
62     signals[CURRENT_TOPLEVEL] =
63       g_signal_new("current-toplevel",
64                    G_OBJECT_CLASS_TYPE(object_class),
65                    G_SIGNAL_RUN_LAST,
66                    G_STRUCT_OFFSET(WebKitSoupAuthDialogClass, current_toplevel),
67                    NULL, NULL,
68                    webkit_marshal_OBJECT__OBJECT,
69                    GTK_TYPE_WIDGET, 1,
70                    SOUP_TYPE_MESSAGE);
71 }
72
73 static void webkit_soup_auth_dialog_init(WebKitSoupAuthDialog* instance)
74 {
75 }
76
77 static void webkit_soup_auth_dialog_session_feature_init(SoupSessionFeatureInterface *feature_interface,
78                                                          gpointer interface_data)
79 {
80     feature_interface->attach = attach;
81     feature_interface->detach = detach;
82 }
83
84 typedef struct _WebKitAuthData {
85     SoupMessage* msg;
86     SoupAuth* auth;
87     SoupSession* session;
88     SoupSessionFeature* manager;
89     GtkWidget* loginEntry;
90     GtkWidget* passwordEntry;
91 #if USE(GNOMEKEYRING)
92     GtkWidget* checkButton;
93 #endif
94 } WebKitAuthData;
95
96 static void free_authData(WebKitAuthData* authData)
97 {
98     g_object_unref(authData->msg);
99     g_slice_free(WebKitAuthData, authData);
100 }
101
102 #if USE(GNOMEKEYRING)
103 static void set_password_callback(GnomeKeyringResult result, guint32 val, gpointer user_data)
104 {
105     /* Dummy callback, gnome_keyring_set_network_password does not accept a NULL one */
106 }
107 #endif
108
109 static void response_callback(GtkDialog* dialog, gint response_id, WebKitAuthData* authData)
110 {
111     const char* login;
112     const char* password;
113 #if USE(GNOMEKEYRING)
114     SoupURI* uri;
115     gboolean storePassword;
116 #endif
117
118     switch(response_id) {
119     case GTK_RESPONSE_OK:
120         login = gtk_entry_get_text(GTK_ENTRY(authData->loginEntry));
121         password = gtk_entry_get_text(GTK_ENTRY(authData->passwordEntry));
122         soup_auth_authenticate(authData->auth, login, password);
123
124 #if USE(GNOMEKEYRING)
125         storePassword = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(authData->checkButton));
126         if (storePassword) {
127             uri = soup_message_get_uri(authData->msg);
128             gnome_keyring_set_network_password(NULL,
129                                                login,
130                                                soup_auth_get_realm(authData->auth),
131                                                uri->host,
132                                                NULL,
133                                                uri->scheme,
134                                                soup_auth_get_scheme_name(authData->auth),
135                                                uri->port,
136                                                password,
137                                                (GnomeKeyringOperationGetIntCallback)set_password_callback,
138                                                NULL,
139                                                NULL);
140         }
141 #endif
142     default:
143         break;
144     }
145
146     soup_session_unpause_message(authData->session, authData->msg);
147     free_authData(authData);
148     gtk_widget_destroy(GTK_WIDGET(dialog));
149 }
150
151 static GtkWidget *
152 table_add_entry (GtkWidget*  table,
153                  int         row,
154                  const char* label_text,
155                  const char* value,
156                  gpointer    user_data)
157 {
158     GtkWidget* entry;
159     GtkWidget* label;
160
161     label = gtk_label_new(label_text);
162     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
163
164     entry = gtk_entry_new();
165     gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
166
167     if (value)
168         gtk_entry_set_text(GTK_ENTRY(entry), value);
169
170     gtk_table_attach(GTK_TABLE(table), label,
171                      0, 1, row, row + 1,
172                      GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
173     gtk_table_attach_defaults(GTK_TABLE(table), entry,
174                               1, 2, row, row + 1);
175
176     return entry;
177 }
178
179 static void show_auth_dialog(WebKitAuthData* authData, const char* login, const char* password)
180 {
181     GtkWidget* toplevel;
182     GtkWidget* widget;
183     GtkDialog* dialog;
184     GtkWindow* window;
185     GtkWidget* entryContainer;
186     GtkWidget* hbox;
187     GtkWidget* mainVBox;
188     GtkWidget* vbox;
189     GtkWidget* icon;
190     GtkWidget* table;
191     GtkWidget* messageLabel;
192     char* message;
193     SoupURI* uri;
194 #if USE(GNOMEKEYRING)
195     GtkWidget* rememberBox;
196     GtkWidget* checkButton;
197 #endif
198
199     /* From GTK+ gtkmountoperation.c, modified and simplified. LGPL 2 license */
200
201     widget = gtk_dialog_new();
202     window = GTK_WINDOW(widget);
203     dialog = GTK_DIALOG(widget);
204
205     gtk_dialog_add_buttons(dialog,
206                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
207                            GTK_STOCK_OK, GTK_RESPONSE_OK,
208                            NULL);
209
210     /* Set the dialog up with HIG properties */
211     gtk_dialog_set_has_separator(dialog, FALSE);
212     gtk_container_set_border_width(GTK_CONTAINER (dialog), 5);
213     gtk_box_set_spacing(GTK_BOX(dialog->vbox), 2); /* 2 * 5 + 2 = 12 */
214     gtk_container_set_border_width(GTK_CONTAINER (dialog->action_area), 5);
215     gtk_box_set_spacing(GTK_BOX(dialog->action_area), 6);
216
217     gtk_window_set_resizable(window, FALSE);
218     gtk_window_set_title(window, "");
219     gtk_window_set_icon_name(window, GTK_STOCK_DIALOG_AUTHENTICATION);
220
221     gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK);
222
223     /* Get the current toplevel */
224     g_signal_emit(authData->manager, signals[CURRENT_TOPLEVEL], 0, authData->msg, &toplevel);
225
226     if (toplevel)
227         gtk_window_set_transient_for(window, GTK_WINDOW(toplevel));
228
229     /* Build contents */
230     hbox = gtk_hbox_new(FALSE, 12);
231     gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
232     gtk_box_pack_start(GTK_BOX(dialog->vbox), hbox, TRUE, TRUE, 0);
233
234     icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_AUTHENTICATION,
235                                     GTK_ICON_SIZE_DIALOG);
236
237     gtk_misc_set_alignment(GTK_MISC(icon), 0.5, 0.0);
238     gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 0);
239
240     mainVBox = gtk_vbox_new(FALSE, 18);
241     gtk_box_pack_start(GTK_BOX(hbox), mainVBox, TRUE, TRUE, 0);
242
243     uri = soup_message_get_uri(authData->msg);
244     message = g_strdup_printf(_("A username and password are being requested by the site %s"), uri->host);
245     messageLabel = gtk_label_new(message);
246     g_free(message);
247     gtk_misc_set_alignment(GTK_MISC(messageLabel), 0.0, 0.5);
248     gtk_label_set_line_wrap(GTK_LABEL(messageLabel), TRUE);
249     gtk_box_pack_start(GTK_BOX(mainVBox), GTK_WIDGET(messageLabel),
250                        FALSE, FALSE, 0);
251
252     vbox = gtk_vbox_new(FALSE, 6);
253     gtk_box_pack_start(GTK_BOX (mainVBox), vbox, FALSE, FALSE, 0);
254
255     /* The table that holds the entries */
256     entryContainer = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
257
258     gtk_alignment_set_padding(GTK_ALIGNMENT(entryContainer),
259                               0, 0, 0, 0);
260
261     gtk_box_pack_start(GTK_BOX(vbox), entryContainer,
262                        FALSE, FALSE, 0);
263
264     table = gtk_table_new(2, 2, FALSE);
265     gtk_table_set_col_spacings(GTK_TABLE (table), 12);
266     gtk_table_set_row_spacings(GTK_TABLE (table), 6);
267     gtk_container_add(GTK_CONTAINER(entryContainer), table);
268
269     authData->loginEntry = table_add_entry(table, 0, _("Username:"),
270                                            login, NULL);
271     authData->passwordEntry = table_add_entry(table, 1, _("Password:"),
272                                               password, NULL);
273
274     gtk_entry_set_visibility(GTK_ENTRY(authData->passwordEntry), FALSE);
275
276 #if USE(GNOMEKEYRING)
277     rememberBox = gtk_vbox_new (FALSE, 6);
278     gtk_box_pack_start (GTK_BOX (vbox), rememberBox,
279                         FALSE, FALSE, 0);
280
281     checkButton = gtk_check_button_new_with_label(_("_Remember password"));
282     if (login && password)
283         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkButton), TRUE);
284     gtk_label_set_line_wrap(GTK_LABEL(gtk_bin_get_child(GTK_BIN(checkButton))), TRUE);
285     gtk_box_pack_start (GTK_BOX (rememberBox), checkButton, FALSE, FALSE, 0);
286     authData->checkButton = checkButton;
287 #endif
288
289     g_signal_connect(dialog, "response", G_CALLBACK(response_callback), authData);
290     gtk_widget_show_all(widget);
291 }
292
293 #if USE(GNOMEKEYRING)
294 static void find_password_callback(GnomeKeyringResult result, GList* list, WebKitAuthData* authData)
295 {
296     GList* p;
297     const char* login = NULL;
298     const char* password = NULL;
299
300     for (p = list; p; p = p->next) {
301         /* FIXME: support multiple logins/passwords ? */
302         GnomeKeyringNetworkPasswordData* data = (GnomeKeyringNetworkPasswordData*)p->data;
303         login = data->user;
304         password = data->password;
305         break;
306     }
307
308     show_auth_dialog(authData, login, password);
309 }
310 #endif
311
312 static void session_authenticate(SoupSession* session, SoupMessage* msg, SoupAuth* auth, gboolean retrying, gpointer user_data)
313 {
314     SoupURI* uri;
315     WebKitAuthData* authData;
316     SoupSessionFeature* manager = (SoupSessionFeature*)user_data;
317
318     soup_session_pause_message(session, msg);
319     /* We need to make sure the message sticks around when pausing it */
320     g_object_ref(msg);
321
322     uri = soup_message_get_uri(msg);
323     authData = g_slice_new(WebKitAuthData);
324     authData->msg = msg;
325     authData->auth = auth;
326     authData->session = session;
327     authData->manager = manager;
328
329     /*
330      * If we have gnome-keyring let's try to find the password first in the ring.
331      * Otherwise just show the dialog straight away
332      */
333 #if USE(GNOMEKEYRING)
334     gnome_keyring_find_network_password(NULL,
335                                         soup_auth_get_realm(auth),
336                                         uri->host,
337                                         NULL,
338                                         uri->scheme,
339                                         soup_auth_get_scheme_name(auth),
340                                         uri->port,
341                                         (GnomeKeyringOperationGetListCallback)find_password_callback,
342                                         authData,
343                                         NULL);
344 #else
345     show_auth_dialog(authData, NULL, NULL);
346 #endif
347 }
348
349 static void attach(SoupSessionFeature* manager, SoupSession* session)
350 {
351     g_signal_connect(session, "authenticate", G_CALLBACK(session_authenticate), manager);
352 }
353
354 static void detach(SoupSessionFeature* manager, SoupSession* session)
355 {
356     g_signal_handlers_disconnect_by_func(session, session_authenticate, manager);
357 }
358
359