• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #define LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY
23 
24 #include <glib/gi18n-lib.h>
25 #include <gtk/gtk.h>
26 #include <libsoup/soup.h>
27 
28 #include "GtkVersioning.h"
29 #include "webkitmarshal.h"
30 #include "webkitsoupauthdialog.h"
31 
32 /**
33  * SECTION:webkitsoupauthdialog
34  * @short_description: A #SoupSessionFeature to provide a simple
35  * authentication dialog for HTTP basic auth support.
36  *
37  * #WebKitSoupAuthDialog is a #SoupSessionFeature that you can attach to your
38  * #SoupSession to provide a simple authentication dialog while
39  * handling HTTP basic auth. It is built as a simple C-only module
40  * 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 
G_DEFINE_TYPE_WITH_CODE(WebKitSoupAuthDialog,webkit_soup_auth_dialog,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,webkit_soup_auth_dialog_session_feature_init))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     /**
63      * WebKitSoupAuthDialog::current-toplevel:
64      * @authDialog: the object on which the signal is emitted
65      * @message: the #SoupMessage being used in the authentication process
66      *
67      * This signal is emitted by the @authDialog when it needs to know
68      * the current toplevel widget in order to correctly set the
69      * transiency for the authentication dialog.
70      *
71      * Return value: (transfer none): the current toplevel #GtkWidget or %NULL if there's none
72      *
73      * Since: 1.1.1
74      */
75     signals[CURRENT_TOPLEVEL] =
76       g_signal_new("current-toplevel",
77                    G_OBJECT_CLASS_TYPE(object_class),
78                    G_SIGNAL_RUN_LAST,
79                    G_STRUCT_OFFSET(WebKitSoupAuthDialogClass, current_toplevel),
80                    NULL, NULL,
81                    webkit_marshal_OBJECT__OBJECT,
82                    GTK_TYPE_WIDGET, 1,
83                    SOUP_TYPE_MESSAGE);
84 }
85 
webkit_soup_auth_dialog_init(WebKitSoupAuthDialog * instance)86 static void webkit_soup_auth_dialog_init(WebKitSoupAuthDialog* instance)
87 {
88 }
89 
webkit_soup_auth_dialog_session_feature_init(SoupSessionFeatureInterface * feature_interface,gpointer interface_data)90 static void webkit_soup_auth_dialog_session_feature_init(SoupSessionFeatureInterface *feature_interface,
91                                                          gpointer interface_data)
92 {
93     feature_interface->attach = attach;
94     feature_interface->detach = detach;
95 }
96 
97 typedef struct _WebKitAuthData {
98     SoupMessage* msg;
99     SoupAuth* auth;
100     SoupSession* session;
101     SoupSessionFeature* manager;
102     GtkWidget* loginEntry;
103     GtkWidget* passwordEntry;
104     GtkWidget* checkButton;
105     char *username;
106     char *password;
107 } WebKitAuthData;
108 
free_authData(WebKitAuthData * authData)109 static void free_authData(WebKitAuthData* authData)
110 {
111     g_object_unref(authData->msg);
112     g_free(authData->username);
113     g_free(authData->password);
114     g_slice_free(WebKitAuthData, authData);
115 }
116 
117 #ifdef SOUP_TYPE_PASSWORD_MANAGER
save_password_callback(SoupMessage * msg,WebKitAuthData * authData)118 static void save_password_callback(SoupMessage* msg, WebKitAuthData* authData)
119 {
120     /* Anything but 401 and 5xx means the password was accepted */
121     if (msg->status_code != 401 && msg->status_code < 500)
122         soup_auth_save_password(authData->auth, authData->username, authData->password);
123 
124     /* Disconnect the callback. If the authentication succeeded we are
125      * done, and if it failed we'll create a new authData and we'll
126      * connect to 'got-headers' again in response_callback */
127     g_signal_handlers_disconnect_by_func(msg, save_password_callback, authData);
128 
129     free_authData(authData);
130 }
131 #endif
132 
response_callback(GtkDialog * dialog,gint response_id,WebKitAuthData * authData)133 static void response_callback(GtkDialog* dialog, gint response_id, WebKitAuthData* authData)
134 {
135     gboolean freeAuthData = TRUE;
136 
137     if (response_id == GTK_RESPONSE_OK) {
138         authData->username = g_strdup(gtk_entry_get_text(GTK_ENTRY(authData->loginEntry)));
139         authData->password = g_strdup(gtk_entry_get_text(GTK_ENTRY(authData->passwordEntry)));
140 
141         soup_auth_authenticate(authData->auth, authData->username, authData->password);
142 
143 #ifdef SOUP_TYPE_PASSWORD_MANAGER
144         if (authData->checkButton &&
145             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(authData->checkButton))) {
146             g_signal_connect(authData->msg, "got-headers", G_CALLBACK(save_password_callback), authData);
147             freeAuthData = FALSE;
148         }
149 #endif
150     }
151 
152     soup_session_unpause_message(authData->session, authData->msg);
153     if (freeAuthData)
154         free_authData(authData);
155     gtk_widget_destroy(GTK_WIDGET(dialog));
156 }
157 
158 static GtkWidget *
table_add_entry(GtkWidget * table,int row,const char * label_text,const char * value,gpointer user_data)159 table_add_entry(GtkWidget*  table,
160                 int         row,
161                 const char* label_text,
162                 const char* value,
163                 gpointer    user_data)
164 {
165     GtkWidget* entry;
166     GtkWidget* label;
167 
168     label = gtk_label_new(label_text);
169     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
170 
171     entry = gtk_entry_new();
172     gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
173 
174     if (value)
175         gtk_entry_set_text(GTK_ENTRY(entry), value);
176 
177     gtk_table_attach(GTK_TABLE(table), label,
178                      0, 1, row, row + 1,
179                      GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
180     gtk_table_attach_defaults(GTK_TABLE(table), entry,
181                               1, 2, row, row + 1);
182 
183     return entry;
184 }
185 
session_can_save_passwords(SoupSession * session)186 static gboolean session_can_save_passwords(SoupSession* session)
187 {
188 #ifdef SOUP_TYPE_PASSWORD_MANAGER
189     return soup_session_get_feature(session, SOUP_TYPE_PASSWORD_MANAGER) != NULL;
190 #else
191     return FALSE;
192 #endif
193 }
194 
show_auth_dialog(WebKitAuthData * authData,const char * login,const char * password)195 static void show_auth_dialog(WebKitAuthData* authData, const char* login, const char* password)
196 {
197     GtkWidget* toplevel;
198     GtkWidget* widget;
199     GtkDialog* dialog;
200     GtkWindow* window;
201     GtkWidget* entryContainer;
202     GtkWidget* hbox;
203     GtkWidget* mainVBox;
204     GtkWidget* vbox;
205     GtkWidget* icon;
206     GtkWidget* table;
207     GtkWidget* serverMessageDescriptionLabel;
208     GtkWidget* serverMessageLabel;
209     GtkWidget* descriptionLabel;
210     char* description;
211     const char* realm;
212     gboolean hasRealm;
213     SoupURI* uri;
214     GtkWidget* rememberBox;
215     GtkWidget* checkButton;
216 
217     /* From GTK+ gtkmountoperation.c, modified and simplified. LGPL 2 license */
218 
219     widget = gtk_dialog_new();
220     window = GTK_WINDOW(widget);
221     dialog = GTK_DIALOG(widget);
222 
223     gtk_dialog_add_buttons(dialog,
224                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
225                            GTK_STOCK_OK, GTK_RESPONSE_OK,
226                            NULL);
227 
228     /* Set the dialog up with HIG properties */
229     gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
230     gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_content_area(dialog)), 2); /* 2 * 5 + 2 = 12 */
231     gtk_container_set_border_width(GTK_CONTAINER(gtk_dialog_get_action_area(dialog)), 5);
232     gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_action_area(dialog)), 6);
233 
234     gtk_window_set_resizable(window, FALSE);
235     gtk_window_set_title(window, "");
236     gtk_window_set_icon_name(window, GTK_STOCK_DIALOG_AUTHENTICATION);
237 
238     gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK);
239 
240     /* Get the current toplevel */
241     g_signal_emit(authData->manager, signals[CURRENT_TOPLEVEL], 0, authData->msg, &toplevel);
242 
243     if (toplevel)
244         gtk_window_set_transient_for(window, GTK_WINDOW(toplevel));
245 
246     /* Build contents */
247     hbox = gtk_hbox_new(FALSE, 12);
248     gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
249     gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(dialog)), hbox, TRUE, TRUE, 0);
250 
251     icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_AUTHENTICATION,
252                                     GTK_ICON_SIZE_DIALOG);
253 
254     gtk_misc_set_alignment(GTK_MISC(icon), 0.5, 0.0);
255     gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 0);
256 
257     mainVBox = gtk_vbox_new(FALSE, 18);
258     gtk_box_pack_start(GTK_BOX(hbox), mainVBox, TRUE, TRUE, 0);
259 
260     uri = soup_message_get_uri(authData->msg);
261     description = g_strdup_printf(_("A username and password are being requested by the site %s"), uri->host);
262     descriptionLabel = gtk_label_new(description);
263     g_free(description);
264     gtk_misc_set_alignment(GTK_MISC(descriptionLabel), 0.0, 0.5);
265     gtk_label_set_line_wrap(GTK_LABEL(descriptionLabel), TRUE);
266     gtk_box_pack_start(GTK_BOX(mainVBox), GTK_WIDGET(descriptionLabel),
267                        FALSE, FALSE, 0);
268 
269     vbox = gtk_vbox_new(FALSE, 6);
270     gtk_box_pack_start(GTK_BOX(mainVBox), vbox, FALSE, FALSE, 0);
271 
272     /* The table that holds the entries */
273     entryContainer = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
274 
275     gtk_alignment_set_padding(GTK_ALIGNMENT(entryContainer),
276                               0, 0, 0, 0);
277 
278     gtk_box_pack_start(GTK_BOX(vbox), entryContainer,
279                        FALSE, FALSE, 0);
280 
281     realm = soup_auth_get_realm(authData->auth);
282     // Checking that realm is not an empty string
283     hasRealm = (realm && (strlen(realm) > 0));
284 
285     table = gtk_table_new(hasRealm ? 3 : 2, 2, FALSE);
286     gtk_table_set_col_spacings(GTK_TABLE(table), 12);
287     gtk_table_set_row_spacings(GTK_TABLE(table), 6);
288     gtk_container_add(GTK_CONTAINER(entryContainer), table);
289 
290     if (hasRealm) {
291         serverMessageDescriptionLabel = gtk_label_new(_("Server message:"));
292         serverMessageLabel = gtk_label_new(realm);
293         gtk_misc_set_alignment(GTK_MISC(serverMessageDescriptionLabel), 0.0, 0.5);
294         gtk_label_set_line_wrap(GTK_LABEL(serverMessageDescriptionLabel), TRUE);
295         gtk_misc_set_alignment(GTK_MISC(serverMessageLabel), 0.0, 0.5);
296         gtk_label_set_line_wrap(GTK_LABEL(serverMessageLabel), TRUE);
297 
298         gtk_table_attach_defaults(GTK_TABLE(table), serverMessageDescriptionLabel,
299                                   0, 1, 0, 1);
300         gtk_table_attach_defaults(GTK_TABLE(table), serverMessageLabel,
301                                   1, 2, 0, 1);
302     }
303 
304     authData->loginEntry = table_add_entry(table, hasRealm ? 1 : 0, _("Username:"),
305                                            login, NULL);
306     authData->passwordEntry = table_add_entry(table, hasRealm ? 2 : 1, _("Password:"),
307                                               password, NULL);
308 
309     gtk_entry_set_visibility(GTK_ENTRY(authData->passwordEntry), FALSE);
310 
311     if (session_can_save_passwords(authData->session)) {
312         rememberBox = gtk_vbox_new(FALSE, 6);
313         gtk_box_pack_start(GTK_BOX(vbox), rememberBox,
314                            FALSE, FALSE, 0);
315         checkButton = gtk_check_button_new_with_mnemonic(_("_Remember password"));
316         if (login && password)
317             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkButton), TRUE);
318         gtk_label_set_line_wrap(GTK_LABEL(gtk_bin_get_child(GTK_BIN(checkButton))), TRUE);
319         gtk_box_pack_start(GTK_BOX(rememberBox), checkButton, FALSE, FALSE, 0);
320         authData->checkButton = checkButton;
321     }
322 
323     g_signal_connect(dialog, "response", G_CALLBACK(response_callback), authData);
324     gtk_widget_show_all(widget);
325 }
326 
session_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer user_data)327 static void session_authenticate(SoupSession* session, SoupMessage* msg, SoupAuth* auth, gboolean retrying, gpointer user_data)
328 {
329     SoupURI* uri;
330     WebKitAuthData* authData;
331     SoupSessionFeature* manager = (SoupSessionFeature*)user_data;
332 #ifdef SOUP_TYPE_PASSWORD_MANAGER
333     GSList* users;
334 #endif
335     const char *login, *password;
336 
337     soup_session_pause_message(session, msg);
338     /* We need to make sure the message sticks around when pausing it */
339     g_object_ref(msg);
340 
341     uri = soup_message_get_uri(msg);
342     authData = g_slice_new0(WebKitAuthData);
343     authData->msg = msg;
344     authData->auth = auth;
345     authData->session = session;
346     authData->manager = manager;
347 
348     login = password = NULL;
349 
350 #ifdef SOUP_TYPE_PASSWORD_MANAGER
351     users = soup_auth_get_saved_users(auth);
352     if (users) {
353         login = users->data;
354         password = soup_auth_get_saved_password(auth, login);
355         g_slist_free(users);
356     }
357 #endif
358 
359     show_auth_dialog(authData, login, password);
360 }
361 
attach(SoupSessionFeature * manager,SoupSession * session)362 static void attach(SoupSessionFeature* manager, SoupSession* session)
363 {
364     g_signal_connect(session, "authenticate", G_CALLBACK(session_authenticate), manager);
365 }
366 
detach(SoupSessionFeature * manager,SoupSession * session)367 static void detach(SoupSessionFeature* manager, SoupSession* session)
368 {
369     g_signal_handlers_disconnect_by_func(session, session_authenticate, manager);
370 }
371 
372 
373