/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright © 2013 Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #define SECRET_API_SUBJECT_TO_CHANGE #include #include "ephy-form-auth-data.h" #include "ephy-uri-helpers.h" #include "passwords-dialog.h" typedef enum { COL_PASSWORDS_ORIGIN, COL_PASSWORDS_USER, COL_PASSWORDS_PASSWORD, COL_PASSWORDS_INVISIBLE, COL_PASSWORDS_DATA, } PasswordsDialogColumn; #define URI_KEY "uri" #define FORM_USERNAME_KEY "form_username" #define FORM_PASSWORD_KEY "form_password" #define USERNAME_KEY "username" struct _EphyPasswordsDialog { GtkDialog parent_instance; GtkWidget *passwords_treeview; GtkTreeSelection *tree_selection; GtkWidget *liststore; GtkWidget *treemodelfilter; GtkWidget *treemodelsort; GtkWidget *show_passwords_button; GtkWidget *password_column; GtkWidget *password_renderer; GMenuModel *treeview_popup_menu_model; GActionGroup *action_group; SecretService *ss; GCancellable *ss_cancellable; gboolean filled; char *search_text; }; G_DEFINE_TYPE (EphyPasswordsDialog, ephy_passwords_dialog, GTK_TYPE_DIALOG) static void populate_model (EphyPasswordsDialog *dialog); static void reload_model (EphyPasswordsDialog *dialog) { gtk_list_store_clear (GTK_LIST_STORE (dialog->liststore)); dialog->filled = FALSE; populate_model (dialog); } static void ephy_passwords_dialog_dispose (GObject *object) { EphyPasswordsDialog *dialog = EPHY_PASSWORDS_DIALOG (object); if (dialog->ss_cancellable != NULL) { g_cancellable_cancel (dialog->ss_cancellable); g_clear_object (&(dialog->ss_cancellable)); } g_clear_object (&(dialog->ss)); g_free (dialog->search_text); dialog->search_text = NULL; G_OBJECT_CLASS (ephy_passwords_dialog_parent_class)->dispose (object); } static void secret_remove_ready_cb (GObject *source, GAsyncResult *res, EphyPasswordsDialog *dialog) { secret_item_delete_finish (SECRET_ITEM (source), res, NULL); } static void secret_remove (EphyPasswordsDialog *dialog, SecretItem *item) { secret_item_delete (item, NULL, (GAsyncReadyCallback)secret_remove_ready_cb, dialog); } static void forget (GSimpleAction *action, GVariant *parameter, gpointer user_data) { EphyPasswordsDialog *dialog = EPHY_PASSWORDS_DIALOG (user_data); GList *llist, *rlist = NULL, *l, *r; GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter, iter2; GtkTreeRowReference *row_ref = NULL; llist = gtk_tree_selection_get_selected_rows (dialog->tree_selection, &model); if (llist == NULL) { /* nothing to delete, return early */ return; } for (l = llist; l != NULL; l = l->next) { rlist = g_list_prepend (rlist, gtk_tree_row_reference_new (model, (GtkTreePath *)l->data)); } /* Intelligent selection logic, no actual selection yet */ path = gtk_tree_row_reference_get_path ((GtkTreeRowReference *)g_list_first (rlist)->data); gtk_tree_model_get_iter (model, &iter, path); gtk_tree_path_free (path); iter2 = iter; if (gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter)) { path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); row_ref = gtk_tree_row_reference_new (model, path); } else { path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter2); if (gtk_tree_path_prev (path)) { row_ref = gtk_tree_row_reference_new (model, path); } } gtk_tree_path_free (path); /* Removal */ for (r = rlist; r != NULL; r = r->next) { GValue val = { 0, }; SecretItem *item; GtkTreeIter filter_iter; GtkTreeIter child_iter; path = gtk_tree_row_reference_get_path ((GtkTreeRowReference *)r->data); gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get_value (model, &iter, COL_PASSWORDS_DATA, &val); item = g_value_get_object (&val); secret_remove (dialog, item); g_value_unset (&val); gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (dialog->treemodelsort), &filter_iter, &iter); gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (dialog->treemodelfilter), &child_iter, &filter_iter); gtk_list_store_remove (GTK_LIST_STORE (dialog->liststore), &child_iter); gtk_tree_row_reference_free ((GtkTreeRowReference *)r->data); gtk_tree_path_free (path); } g_list_foreach (llist, (GFunc)gtk_tree_path_free, NULL); g_list_free (llist); g_list_free (rlist); /* Selection */ if (row_ref != NULL) { path = gtk_tree_row_reference_get_path (row_ref); if (path != NULL) { gtk_tree_view_set_cursor (GTK_TREE_VIEW (dialog->passwords_treeview), path, NULL, FALSE); gtk_tree_path_free (path); } gtk_tree_row_reference_free (row_ref); } } static void show_passwords (GSimpleAction *action, GVariant *parameter, gpointer user_data) { EphyPasswordsDialog *dialog = EPHY_PASSWORDS_DIALOG (user_data); gboolean active; active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->show_passwords_button)); gtk_tree_view_column_set_attributes (GTK_TREE_VIEW_COLUMN (dialog->password_column), GTK_CELL_RENDERER (dialog->password_renderer), "text", (active ? COL_PASSWORDS_PASSWORD : COL_PASSWORDS_INVISIBLE), NULL); gtk_widget_queue_draw (dialog->passwords_treeview); } static void update_selection_actions (GActionMap *action_map, gboolean has_selection) { GAction *forget_action; forget_action = g_action_map_lookup_action (action_map, "forget"); g_simple_action_set_enabled (G_SIMPLE_ACTION (forget_action), has_selection); } static void on_treeview_selection_changed (GtkTreeSelection *selection, EphyPasswordsDialog *dialog) { update_selection_actions (G_ACTION_MAP (dialog->action_group), gtk_tree_selection_count_selected_rows (selection) > 0); } static void on_search_entry_changed (GtkSearchEntry *entry, EphyPasswordsDialog *dialog) { const char *text; text = gtk_entry_get_text (GTK_ENTRY (entry)); g_free (dialog->search_text); dialog->search_text = g_strdup (text); gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (dialog->treemodelfilter)); } static char * get_selected_item (EphyPasswordsDialog *dialog, PasswordsDialogColumn column) { GtkTreeModel *model; GList *selected; GtkTreeIter iter; char *value; selected = gtk_tree_selection_get_selected_rows (dialog->tree_selection, &model); gtk_tree_model_get_iter (model, &iter, selected->data); gtk_tree_model_get (model, &iter, column, &value, -1); g_list_free_full (selected, (GDestroyNotify)gtk_tree_path_free); return value; } static void copy_password (GSimpleAction *action, GVariant *parameter, gpointer user_data) { EphyPasswordsDialog *dialog = EPHY_PASSWORDS_DIALOG (user_data); char *password; password = get_selected_item (dialog, COL_PASSWORDS_PASSWORD); if (password != NULL) { gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (dialog), GDK_SELECTION_CLIPBOARD), password, -1); } g_free (password); } static void copy_username (GSimpleAction *action, GVariant *parameter, gpointer user_data) { EphyPasswordsDialog *dialog = EPHY_PASSWORDS_DIALOG (user_data); char *username; username = get_selected_item (dialog, COL_PASSWORDS_USER); if (username != NULL) { gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (dialog), GDK_SELECTION_CLIPBOARD), username, -1); } g_free (username); } static void update_popup_menu_actions (GActionGroup *action_group, gboolean only_one_selected_item) { GAction *copy_password_action; GAction *copy_username_action; copy_password_action = g_action_map_lookup_action (G_ACTION_MAP (action_group), "copy-password"); copy_username_action = g_action_map_lookup_action (G_ACTION_MAP (action_group), "copy-username"); g_simple_action_set_enabled (G_SIMPLE_ACTION (copy_password_action), only_one_selected_item); g_simple_action_set_enabled (G_SIMPLE_ACTION (copy_username_action), only_one_selected_item); } static gboolean on_passwords_treeview_button_press_event (GtkWidget *widget, GdkEventButton *event, EphyPasswordsDialog *dialog) { if (event->button == 3) { int n; GtkWidget *menu; n = gtk_tree_selection_count_selected_rows (dialog->tree_selection); if (n == 0) return FALSE; update_popup_menu_actions (dialog->action_group, (n == 1)); menu = gtk_menu_new_from_model (dialog->treeview_popup_menu_model); gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (dialog), NULL); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } return FALSE; } static void ephy_passwords_dialog_class_init (EphyPasswordsDialogClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = ephy_passwords_dialog_dispose; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/epiphany/passwords-dialog.ui"); gtk_widget_class_bind_template_child (widget_class, EphyPasswordsDialog, liststore); gtk_widget_class_bind_template_child (widget_class, EphyPasswordsDialog, treemodelfilter); gtk_widget_class_bind_template_child (widget_class, EphyPasswordsDialog, treemodelsort); gtk_widget_class_bind_template_child (widget_class, EphyPasswordsDialog, passwords_treeview); gtk_widget_class_bind_template_child (widget_class, EphyPasswordsDialog, tree_selection); gtk_widget_class_bind_template_child (widget_class, EphyPasswordsDialog, show_passwords_button); gtk_widget_class_bind_template_child (widget_class, EphyPasswordsDialog, password_column); gtk_widget_class_bind_template_child (widget_class, EphyPasswordsDialog, password_renderer); gtk_widget_class_bind_template_child (widget_class, EphyPasswordsDialog, treeview_popup_menu_model); gtk_widget_class_bind_template_callback (widget_class, on_passwords_treeview_button_press_event); gtk_widget_class_bind_template_callback (widget_class, on_treeview_selection_changed); gtk_widget_class_bind_template_callback (widget_class, on_search_entry_changed); } static void delete_all_passwords_ready_cb (GObject *source_object, GAsyncResult *res, EphyPasswordsDialog *dialog) { secret_service_clear_finish (dialog->ss, res, NULL); reload_model (dialog); } static void forget_all (GSimpleAction *action, GVariant *parameter, gpointer user_data) { EphyPasswordsDialog *dialog = EPHY_PASSWORDS_DIALOG (user_data); GHashTable *attributes; attributes = secret_attributes_build (EPHY_FORM_PASSWORD_SCHEMA, NULL); secret_service_clear (dialog->ss, EPHY_FORM_PASSWORD_SCHEMA, attributes, dialog->ss_cancellable, (GAsyncReadyCallback)delete_all_passwords_ready_cb, dialog); g_hash_table_unref (attributes); } static void secrets_search_ready_cb (GObject *source_object, GAsyncResult *res, EphyPasswordsDialog *dialog) { GList *matches; GList *l; matches = secret_service_search_finish (dialog->ss, res, NULL); for (l = matches; l != NULL; l = l->next) { SecretItem *item = l->data; SecretValue *value = NULL; GHashTable *attributes = NULL; const char *username = NULL; const char *password = NULL; char *origin = NULL; GtkTreeIter iter; attributes = secret_item_get_attributes (item); username = g_hash_table_lookup (attributes, USERNAME_KEY); value = secret_item_get_secret (item); password = secret_value_get (value, NULL); origin = ephy_uri_to_security_origin (g_hash_table_lookup (attributes, URI_KEY)); if (origin == NULL) { g_hash_table_unref (attributes); continue; } gtk_list_store_insert_with_values (GTK_LIST_STORE (dialog->liststore), &iter, -1, COL_PASSWORDS_ORIGIN, origin, COL_PASSWORDS_USER, username, COL_PASSWORDS_PASSWORD, password, COL_PASSWORDS_INVISIBLE, "●●●●●●●●", COL_PASSWORDS_DATA, item, -1); g_free (origin); g_hash_table_unref (attributes); } g_list_free_full (matches, g_object_unref); } static void populate_model (EphyPasswordsDialog *dialog) { GHashTable *attributes; g_assert (dialog->filled == FALSE); attributes = secret_attributes_build (EPHY_FORM_PASSWORD_SCHEMA, NULL); secret_service_search (dialog->ss, EPHY_FORM_PASSWORD_SCHEMA, attributes, SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS, dialog->ss_cancellable, (GAsyncReadyCallback)secrets_search_ready_cb, dialog); g_hash_table_unref (attributes); } static void secrets_ready_cb (GObject *source_object, GAsyncResult *res, EphyPasswordsDialog *dialog) { dialog->ss = secret_service_get_finish (res, NULL); populate_model (dialog); } static gboolean row_visible_func (GtkTreeModel *model, GtkTreeIter *iter, EphyPasswordsDialog *dialog) { char *username; char *origin; gboolean visible = FALSE; if (dialog->search_text == NULL) return TRUE; gtk_tree_model_get (model, iter, COL_PASSWORDS_ORIGIN, &origin, COL_PASSWORDS_USER, &username, -1); if (origin != NULL && g_strrstr (origin, dialog->search_text) != NULL) visible = TRUE; else if (username != NULL && g_strrstr (username, dialog->search_text) != NULL) visible = TRUE; g_free (origin); g_free (username); return visible; } static GActionGroup * create_action_group (EphyPasswordsDialog *dialog) { const GActionEntry entries[] = { { "copy-password", copy_password }, { "copy-username", copy_username }, { "forget", forget }, { "forget-all", forget_all }, { "show-passwords", show_passwords } }; GSimpleActionGroup *group; group = g_simple_action_group_new (); g_action_map_add_action_entries (G_ACTION_MAP (group), entries, G_N_ELEMENTS (entries), dialog); return G_ACTION_GROUP (group); } static void ephy_passwords_dialog_init (EphyPasswordsDialog *dialog) { gtk_widget_init_template (GTK_WIDGET (dialog)); gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (dialog->treemodelfilter), (GtkTreeModelFilterVisibleFunc)row_visible_func, dialog, NULL); dialog->ss_cancellable = g_cancellable_new (); secret_service_get (SECRET_SERVICE_OPEN_SESSION | SECRET_SERVICE_LOAD_COLLECTIONS, dialog->ss_cancellable, (GAsyncReadyCallback)secrets_ready_cb, dialog); dialog->action_group = create_action_group (dialog); gtk_widget_insert_action_group (GTK_WIDGET (dialog), "passwords", dialog->action_group); update_selection_actions (G_ACTION_MAP (dialog->action_group), FALSE); } EphyPasswordsDialog * ephy_passwords_dialog_new (void) { return g_object_new (EPHY_TYPE_PASSWORDS_DIALOG, "use-header-bar", TRUE, NULL); }