Home | History | Annotate | Download | only in webkit
      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