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 "webkitmarshal.h" 29 #include "webkitsoupauthdialog.h" 30 31 /** 32 * SECTION:webkitsoupauthdialog 33 * @short_description: A #SoupSessionFeature to provide a simple 34 * authentication dialog for HTTP basic auth support. 35 * 36 * #WebKitSoupAuthDialog is a #SoupSessionFeature that you can attach to your 37 * #SoupSession to provide a simple authentication dialog while 38 * handling HTTP basic auth. It is built as a simple C-only module 39 * to ease reuse. 40 */ 41 42 static void webkit_soup_auth_dialog_session_feature_init(SoupSessionFeatureInterface* feature_interface, gpointer interface_data); 43 static void attach(SoupSessionFeature* manager, SoupSession* session); 44 static void detach(SoupSessionFeature* manager, SoupSession* session); 45 46 enum { 47 CURRENT_TOPLEVEL, 48 LAST_SIGNAL 49 }; 50 51 static guint signals[LAST_SIGNAL] = { 0 }; 52 53 G_DEFINE_TYPE_WITH_CODE(WebKitSoupAuthDialog, webkit_soup_auth_dialog, G_TYPE_OBJECT, 54 G_IMPLEMENT_INTERFACE(SOUP_TYPE_SESSION_FEATURE, 55 webkit_soup_auth_dialog_session_feature_init)) 56 57 static void webkit_soup_auth_dialog_class_init(WebKitSoupAuthDialogClass* klass) 58 { 59 GObjectClass* object_class = G_OBJECT_CLASS(klass); 60 61 signals[CURRENT_TOPLEVEL] = 62 g_signal_new("current-toplevel", 63 G_OBJECT_CLASS_TYPE(object_class), 64 G_SIGNAL_RUN_LAST, 65 G_STRUCT_OFFSET(WebKitSoupAuthDialogClass, current_toplevel), 66 NULL, NULL, 67 webkit_marshal_OBJECT__OBJECT, 68 GTK_TYPE_WIDGET, 1, 69 SOUP_TYPE_MESSAGE); 70 } 71 72 static void webkit_soup_auth_dialog_init(WebKitSoupAuthDialog* instance) 73 { 74 } 75 76 static void webkit_soup_auth_dialog_session_feature_init(SoupSessionFeatureInterface *feature_interface, 77 gpointer interface_data) 78 { 79 feature_interface->attach = attach; 80 feature_interface->detach = detach; 81 } 82 83 typedef struct _WebKitAuthData { 84 SoupMessage* msg; 85 SoupAuth* auth; 86 SoupSession* session; 87 SoupSessionFeature* manager; 88 GtkWidget* loginEntry; 89 GtkWidget* passwordEntry; 90 GtkWidget* checkButton; 91 char *username; 92 char *password; 93 } WebKitAuthData; 94 95 static void free_authData(WebKitAuthData* authData) 96 { 97 g_object_unref(authData->msg); 98 g_free(authData->username); 99 g_free(authData->password); 100 g_slice_free(WebKitAuthData, authData); 101 } 102 103 #ifdef SOUP_TYPE_PASSWORD_MANAGER 104 static void save_password_callback(SoupMessage* msg, WebKitAuthData* authData) 105 { 106 /* Anything but 401 and 5xx means the password was accepted */ 107 if (msg->status_code != 401 && msg->status_code < 500) 108 soup_auth_save_password(authData->auth, authData->username, authData->password); 109 110 /* Disconnect the callback. If the authentication succeeded we are 111 * done, and if it failed we'll create a new authData and we'll 112 * connect to 'got-headers' again in response_callback */ 113 g_signal_handlers_disconnect_by_func(msg, save_password_callback, authData); 114 115 free_authData(authData); 116 } 117 #endif 118 119 static void response_callback(GtkDialog* dialog, gint response_id, WebKitAuthData* authData) 120 { 121 gboolean freeAuthData = TRUE; 122 123 if (response_id == GTK_RESPONSE_OK) { 124 authData->username = g_strdup(gtk_entry_get_text(GTK_ENTRY(authData->loginEntry))); 125 authData->password = g_strdup(gtk_entry_get_text(GTK_ENTRY(authData->passwordEntry))); 126 127 soup_auth_authenticate(authData->auth, authData->username, authData->password); 128 129 #ifdef SOUP_TYPE_PASSWORD_MANAGER 130 if (authData->checkButton && 131 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(authData->checkButton))) { 132 g_signal_connect(authData->msg, "got-headers", G_CALLBACK(save_password_callback), authData); 133 freeAuthData = FALSE; 134 } 135 #endif 136 } 137 138 soup_session_unpause_message(authData->session, authData->msg); 139 if (freeAuthData) 140 free_authData(authData); 141 gtk_widget_destroy(GTK_WIDGET(dialog)); 142 } 143 144 static GtkWidget * 145 table_add_entry(GtkWidget* table, 146 int row, 147 const char* label_text, 148 const char* value, 149 gpointer user_data) 150 { 151 GtkWidget* entry; 152 GtkWidget* label; 153 154 label = gtk_label_new(label_text); 155 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); 156 157 entry = gtk_entry_new(); 158 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); 159 160 if (value) 161 gtk_entry_set_text(GTK_ENTRY(entry), value); 162 163 gtk_table_attach(GTK_TABLE(table), label, 164 0, 1, row, row + 1, 165 GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); 166 gtk_table_attach_defaults(GTK_TABLE(table), entry, 167 1, 2, row, row + 1); 168 169 return entry; 170 } 171 172 static gboolean session_can_save_passwords(SoupSession* session) 173 { 174 #ifdef SOUP_TYPE_PASSWORD_MANAGER 175 return soup_session_get_feature(session, SOUP_TYPE_PASSWORD_MANAGER) != NULL; 176 #else 177 return FALSE; 178 #endif 179 } 180 181 static void show_auth_dialog(WebKitAuthData* authData, const char* login, const char* password) 182 { 183 GtkWidget* toplevel; 184 GtkWidget* widget; 185 GtkDialog* dialog; 186 GtkWindow* window; 187 GtkWidget* entryContainer; 188 GtkWidget* hbox; 189 GtkWidget* mainVBox; 190 GtkWidget* vbox; 191 GtkWidget* icon; 192 GtkWidget* table; 193 GtkWidget* messageLabel; 194 char* message; 195 SoupURI* uri; 196 GtkWidget* rememberBox; 197 GtkWidget* checkButton; 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 (session_can_save_passwords(authData->session)) { 277 rememberBox = gtk_vbox_new(FALSE, 6); 278 gtk_box_pack_start(GTK_BOX(vbox), rememberBox, 279 FALSE, FALSE, 0); 280 checkButton = gtk_check_button_new_with_mnemonic(_("_Remember password")); 281 if (login && password) 282 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkButton), TRUE); 283 gtk_label_set_line_wrap(GTK_LABEL(gtk_bin_get_child(GTK_BIN(checkButton))), TRUE); 284 gtk_box_pack_start(GTK_BOX(rememberBox), checkButton, FALSE, FALSE, 0); 285 authData->checkButton = checkButton; 286 } 287 288 g_signal_connect(dialog, "response", G_CALLBACK(response_callback), authData); 289 gtk_widget_show_all(widget); 290 } 291 292 static void session_authenticate(SoupSession* session, SoupMessage* msg, SoupAuth* auth, gboolean retrying, gpointer user_data) 293 { 294 SoupURI* uri; 295 WebKitAuthData* authData; 296 SoupSessionFeature* manager = (SoupSessionFeature*)user_data; 297 #ifdef SOUP_TYPE_PASSWORD_MANAGER 298 GSList* users; 299 #endif 300 const char *login, *password; 301 302 soup_session_pause_message(session, msg); 303 /* We need to make sure the message sticks around when pausing it */ 304 g_object_ref(msg); 305 306 uri = soup_message_get_uri(msg); 307 authData = g_slice_new0(WebKitAuthData); 308 authData->msg = msg; 309 authData->auth = auth; 310 authData->session = session; 311 authData->manager = manager; 312 313 login = password = NULL; 314 315 #ifdef SOUP_TYPE_PASSWORD_MANAGER 316 users = soup_auth_get_saved_users(auth); 317 if (users) { 318 login = users->data; 319 password = soup_auth_get_saved_password(auth, login); 320 g_slist_free(users); 321 } 322 #endif 323 324 show_auth_dialog(authData, login, password); 325 } 326 327 static void attach(SoupSessionFeature* manager, SoupSession* session) 328 { 329 g_signal_connect(session, "authenticate", G_CALLBACK(session_authenticate), manager); 330 } 331 332 static void detach(SoupSessionFeature* manager, SoupSession* session) 333 { 334 g_signal_handlers_disconnect_by_func(session, session_authenticate, manager); 335 } 336 337 338