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 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 86 static void webkit_soup_auth_dialog_init(WebKitSoupAuthDialog* instance) 87 { 88 } 89 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 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 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 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 * 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 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 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 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 362 static void attach(SoupSessionFeature* manager, SoupSession* session) 363 { 364 g_signal_connect(session, "authenticate", G_CALLBACK(session_authenticate), manager); 365 } 366 367 static void detach(SoupSessionFeature* manager, SoupSession* session) 368 { 369 g_signal_handlers_disconnect_by_func(session, session_authenticate, manager); 370 } 371 372 373