summaryrefslogtreecommitdiff
path: root/src/passwords-dialog.c
diff options
context:
space:
mode:
authorWilliam Jon McCann <william.jon.mccann@gmail.com>2013-12-10 15:32:12 +0100
committerWilliam Jon McCann <william.jon.mccann@gmail.com>2013-12-11 15:24:26 +0100
commit114f45faef774f32c7090d0e54c168389bd4f0ea (patch)
tree3e3542c3cd8d91952e760186bdd951c9effd99c3 /src/passwords-dialog.c
parent0c14b9d1ae62b5de912c5b8831abc0ff4de7a900 (diff)
downloadepiphany-114f45faef774f32c7090d0e54c168389bd4f0ea.tar.gz
Add a new passwords dialog
https://bugzilla.gnome.org/show_bug.cgi?id=720239
Diffstat (limited to 'src/passwords-dialog.c')
-rw-r--r--src/passwords-dialog.c454
1 files changed, 454 insertions, 0 deletions
diff --git a/src/passwords-dialog.c b/src/passwords-dialog.c
new file mode 100644
index 000000000..e144502f1
--- /dev/null
+++ b/src/passwords-dialog.c
@@ -0,0 +1,454 @@
+/* -*- 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+#define SECRET_API_SUBJECT_TO_CHANGE
+#include <libsecret/secret.h>
+
+#include "ephy-form-auth-data.h"
+#include "ephy-string.h"
+#include "passwords-dialog.h"
+
+enum
+{
+ COL_PASSWORDS_HOST,
+ COL_PASSWORDS_USER,
+ COL_PASSWORDS_PASSWORD,
+ COL_PASSWORDS_INVISIBLE,
+ COL_PASSWORDS_DATA,
+};
+
+#define URI_KEY "uri"
+#define FORM_USERNAME_KEY "form_username"
+#define FORM_PASSWORD_KEY "form_password"
+#define USERNAME_KEY "username"
+
+struct PasswordsDialogPrivate
+{
+ GtkWidget *passwords_treeview;
+ GtkWidget *liststore;
+ GtkWidget *treemodelfilter;
+ GtkWidget *treemodelsort;
+ GtkWidget *remove_toolbutton;
+ GtkWidget *show_passwords_toolbutton;
+ GtkWidget *clear_button;
+ GtkWidget *password_column;
+ GtkWidget *password_renderer;
+
+ SecretService *ss;
+ GCancellable *ss_cancellable;
+ gboolean filled;
+
+ char *search_text;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (PasswordsDialog, passwords_dialog, GTK_TYPE_DIALOG)
+
+static void populate_model (PasswordsDialog *dialog);
+
+static void
+reload_model (PasswordsDialog *dialog)
+{
+ gtk_list_store_clear (GTK_LIST_STORE (dialog->priv->liststore));
+ dialog->priv->filled = FALSE;
+ populate_model (dialog);
+}
+
+static void
+passwords_dialog_dispose (GObject *object)
+{
+ PasswordsDialogPrivate *priv;
+
+ priv = EPHY_PASSWORDS_DIALOG (object)->priv;
+
+ if (priv->ss_cancellable != NULL) {
+ g_cancellable_cancel (priv->ss_cancellable);
+ g_clear_object (&priv->ss_cancellable);
+ }
+
+ g_clear_object (&priv->ss);
+ g_free (priv->search_text);
+ priv->search_text = NULL;
+
+ G_OBJECT_CLASS (passwords_dialog_parent_class)->dispose (object);
+}
+
+static void
+secret_remove_ready_cb (GObject *source,
+ GAsyncResult *res,
+ PasswordsDialog *dialog)
+{
+ secret_item_delete_finish (SECRET_ITEM (source), res, NULL);
+}
+
+static void
+secret_remove (PasswordsDialog *dialog,
+ SecretItem *item)
+{
+ secret_item_delete (item, NULL, (GAsyncReadyCallback)secret_remove_ready_cb, dialog);
+}
+
+static void
+delete_selection (PasswordsDialog *dialog)
+{
+ GList *llist, *rlist = NULL, *l, *r;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreePath *path;
+ GtkTreeIter iter, iter2;
+ GtkTreeRowReference *row_ref = NULL;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->passwords_treeview));
+ llist = gtk_tree_selection_get_selected_rows (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->priv->treemodelsort),
+ &filter_iter,
+ &iter);
+
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (dialog->priv->treemodelfilter),
+ &child_iter,
+ &filter_iter);
+
+ gtk_list_store_remove (GTK_LIST_STORE (dialog->priv->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->priv->passwords_treeview), path, NULL, FALSE);
+ gtk_tree_path_free (path);
+ }
+
+ gtk_tree_row_reference_free (row_ref);
+ }
+}
+
+static gboolean
+on_passwords_treeview_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ PasswordsDialog *dialog)
+{
+ if (event->keyval == GDK_KEY_Delete || event->keyval == GDK_KEY_KP_Delete)
+ {
+ delete_selection (dialog);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+on_remove_toolbutton_clicked (GtkToolButton *toolbutton,
+ PasswordsDialog *dialog)
+{
+ delete_selection (dialog);
+
+ /* Restore the focus to the button */
+ gtk_widget_grab_focus (GTK_WIDGET (toolbutton));
+}
+
+static void
+on_show_passwords_toolbutton_toggled (GtkToggleToolButton *toolbutton,
+ PasswordsDialog *dialog)
+{
+ gboolean active;
+
+ active = gtk_toggle_tool_button_get_active (toolbutton);
+
+ gtk_tree_view_column_set_attributes (GTK_TREE_VIEW_COLUMN (dialog->priv->password_column),
+ GTK_CELL_RENDERER (dialog->priv->password_renderer),
+ "text", (active ? COL_PASSWORDS_PASSWORD : COL_PASSWORDS_INVISIBLE),
+ NULL);
+ gtk_widget_queue_draw (dialog->priv->passwords_treeview);
+}
+
+static void
+on_treeview_selection_changed (GtkTreeSelection *selection,
+ PasswordsDialog *dialog)
+{
+ gboolean has_selection;
+
+ has_selection = gtk_tree_selection_count_selected_rows (selection) > 0;
+
+ gtk_widget_set_sensitive (dialog->priv->remove_toolbutton, has_selection);
+}
+
+static void
+on_search_entry_changed (GtkSearchEntry *entry,
+ PasswordsDialog *dialog)
+{
+ const char *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ g_free (dialog->priv->search_text);
+ dialog->priv->search_text = g_strdup (text);
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (dialog->priv->treemodelfilter));
+}
+
+static void
+passwords_dialog_class_init (PasswordsDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = passwords_dialog_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/epiphany/passwords-dialog.ui");
+
+ gtk_widget_class_bind_template_child_private (widget_class, PasswordsDialog, liststore);
+ gtk_widget_class_bind_template_child_private (widget_class, PasswordsDialog, treemodelfilter);
+ gtk_widget_class_bind_template_child_private (widget_class, PasswordsDialog, treemodelsort);
+ gtk_widget_class_bind_template_child_private (widget_class, PasswordsDialog, passwords_treeview);
+ gtk_widget_class_bind_template_child_private (widget_class, PasswordsDialog, clear_button);
+ gtk_widget_class_bind_template_child_private (widget_class, PasswordsDialog, remove_toolbutton);
+ gtk_widget_class_bind_template_child_private (widget_class, PasswordsDialog, show_passwords_toolbutton);
+ gtk_widget_class_bind_template_child_private (widget_class, PasswordsDialog, password_column);
+ gtk_widget_class_bind_template_child_private (widget_class, PasswordsDialog, password_renderer);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_passwords_treeview_key_press_event);
+ gtk_widget_class_bind_template_callback (widget_class, on_treeview_selection_changed);
+ gtk_widget_class_bind_template_callback (widget_class, on_remove_toolbutton_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, on_show_passwords_toolbutton_toggled);
+ gtk_widget_class_bind_template_callback (widget_class, on_search_entry_changed);
+}
+
+static void
+delete_all_passwords_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ PasswordsDialog *dialog)
+{
+ secret_service_clear_finish (dialog->priv->ss, res, NULL);
+ reload_model (dialog);
+}
+
+static void
+delete_all_passwords (PasswordsDialog *dialog)
+{
+ GHashTable *attributes;
+
+ attributes = secret_attributes_build (EPHY_FORM_PASSWORD_SCHEMA, NULL);
+ secret_service_clear (dialog->priv->ss,
+ EPHY_FORM_PASSWORD_SCHEMA,
+ attributes,
+ dialog->priv->ss_cancellable,
+ (GAsyncReadyCallback)delete_all_passwords_ready_cb,
+ dialog);
+ g_hash_table_unref (attributes);
+}
+
+static void
+passwords_dialog_response_cb (GtkDialog *widget,
+ int response,
+ PasswordsDialog *dialog)
+{
+ if (response == GTK_RESPONSE_REJECT) {
+ delete_all_passwords (dialog);
+ return;
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+secrets_search_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ PasswordsDialog *dialog)
+{
+ GList *matches;
+ GList *l;
+
+ matches = secret_service_search_finish (dialog->priv->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 *host = NULL;
+ GtkTreeIter iter;
+
+ attributes = secret_item_get_attributes (item);
+ username = g_hash_table_lookup (attributes, USERNAME_KEY);
+ host = ephy_string_get_host_name (g_hash_table_lookup (attributes, URI_KEY));
+ value = secret_item_get_secret (item);
+ password = secret_value_get (value, NULL);
+
+ gtk_list_store_insert_with_values (GTK_LIST_STORE (dialog->priv->liststore),
+ &iter,
+ -1,
+ COL_PASSWORDS_HOST, host,
+ COL_PASSWORDS_USER, username,
+ COL_PASSWORDS_PASSWORD, password,
+ COL_PASSWORDS_INVISIBLE, "●●●●●●●●",
+ COL_PASSWORDS_DATA, item,
+ -1);
+
+ g_free (host);
+ g_hash_table_unref (attributes);
+ }
+
+ g_list_free_full (matches, g_object_unref);
+}
+
+static void
+populate_model (PasswordsDialog *dialog)
+{
+ GHashTable *attributes;
+
+ g_assert (dialog->priv->filled == FALSE);
+
+ attributes = secret_attributes_build (EPHY_FORM_PASSWORD_SCHEMA, NULL);
+
+ secret_service_search (dialog->priv->ss,
+ EPHY_FORM_PASSWORD_SCHEMA,
+ attributes,
+ SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
+ dialog->priv->ss_cancellable,
+ (GAsyncReadyCallback)secrets_search_ready_cb,
+ dialog);
+
+ g_hash_table_unref (attributes);
+}
+
+static void
+secrets_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ PasswordsDialog *dialog)
+{
+ dialog->priv->ss = secret_service_get_finish (res, NULL);
+ populate_model (dialog);
+}
+
+static gboolean
+row_visible_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ PasswordsDialog *dialog)
+{
+ char *username;
+ char *host;
+ gboolean visible = FALSE;
+
+ if (dialog->priv->search_text == NULL)
+ return TRUE;
+
+ gtk_tree_model_get (model, iter,
+ COL_PASSWORDS_HOST, &host,
+ COL_PASSWORDS_USER, &username,
+ -1);
+
+ if (host != NULL && g_strrstr (host, dialog->priv->search_text) != NULL)
+ visible = TRUE;
+ else if (username != NULL && g_strrstr (username, dialog->priv->search_text) != NULL)
+ visible = TRUE;
+
+ g_free (host);
+ g_free (username);
+
+ return visible;
+}
+
+static void
+passwords_dialog_init (PasswordsDialog *dialog)
+{
+ dialog->priv = passwords_dialog_get_instance_private (dialog);
+ gtk_widget_init_template (GTK_WIDGET (dialog));
+
+ gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (dialog->priv->treemodelfilter),
+ (GtkTreeModelFilterVisibleFunc)row_visible_func,
+ dialog,
+ NULL);
+
+ dialog->priv->ss_cancellable = g_cancellable_new ();
+ secret_service_get (SECRET_SERVICE_OPEN_SESSION | SECRET_SERVICE_LOAD_COLLECTIONS,
+ dialog->priv->ss_cancellable,
+ (GAsyncReadyCallback)secrets_ready_cb,
+ dialog);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (passwords_dialog_response_cb), dialog);
+}