Commit 382d68ff authored by Andre Heinecke's avatar Andre Heinecke Committed by Marek Kašík

Use secrets service for cups auth_info

    When a printer requires auth_info (e.g. a printer connected
    over the samba protocol) it is now possible to save the
    credentials necessary for printing if a secrets service
    is available over dbus.
    The auth_info is then stored / loaded from the default
    collection of that secrets service.
    If no such service is available the user is not shown
    the option to remember the password and the behavior
    remains the same as before.

    https://bugzilla.gnome.org/show_bug.cgi?id=674264
parent d5dae5b5
......@@ -124,7 +124,6 @@ VOID:UINT,STRING,UINT
VOID:UINT,UINT
VOID:VOID
OBJECT:OBJECT,INT,INT
VOID:POINTER,POINTER,POINTER,POINTER,STRING
VOID:OBJECT,STRING,POINTER,POINTER
INT:INT
VOID:POINTER,STRING,INT
......
......@@ -47,6 +47,7 @@ struct _GtkPrintBackendPrivate
GtkPrintBackendStatus status;
char **auth_info_required;
char **auth_info;
gboolean store_auth_info;
};
enum {
......@@ -360,7 +361,8 @@ static void request_password (GtkPrintBack
gpointer auth_info_default,
gpointer auth_info_display,
gpointer auth_info_visible,
const gchar *prompt);
const gchar *prompt,
gboolean can_store_auth_info);
static void
gtk_print_backend_class_init (GtkPrintBackendClass *class)
......@@ -437,9 +439,9 @@ gtk_print_backend_class_init (GtkPrintBackendClass *class)
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkPrintBackendClass, request_password),
NULL, NULL,
_gtk_marshal_VOID__POINTER_POINTER_POINTER_POINTER_STRING,
G_TYPE_NONE, 5, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_STRING);
NULL, NULL, NULL,
G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER,
G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
}
static void
......@@ -666,12 +668,24 @@ gtk_print_backend_print_stream (GtkPrintBackend *backend,
void
gtk_print_backend_set_password (GtkPrintBackend *backend,
gchar **auth_info_required,
gchar **auth_info)
gchar **auth_info,
gboolean store_auth_info)
{
g_return_if_fail (GTK_IS_PRINT_BACKEND (backend));
if (GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password)
GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password (backend, auth_info_required, auth_info);
GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password (backend,
auth_info_required,
auth_info,
store_auth_info);
}
static void
store_auth_info_toggled (GtkCheckButton *chkbtn,
gpointer user_data)
{
gboolean *data = (gboolean *) user_data;
*data = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chkbtn));
}
static void
......@@ -698,9 +712,9 @@ password_dialog_response (GtkWidget *dialog,
gint i;
if (response_id == GTK_RESPONSE_OK)
gtk_print_backend_set_password (backend, priv->auth_info_required, priv->auth_info);
gtk_print_backend_set_password (backend, priv->auth_info_required, priv->auth_info, priv->store_auth_info);
else
gtk_print_backend_set_password (backend, priv->auth_info_required, NULL);
gtk_print_backend_set_password (backend, priv->auth_info_required, NULL, FALSE);
for (i = 0; i < g_strv_length (priv->auth_info_required); i++)
if (priv->auth_info[i] != NULL)
......@@ -725,10 +739,11 @@ request_password (GtkPrintBackend *backend,
gpointer auth_info_default,
gpointer auth_info_display,
gpointer auth_info_visible,
const gchar *prompt)
const gchar *prompt,
gboolean can_store_auth_info)
{
GtkPrintBackendPrivate *priv = backend->priv;
GtkWidget *dialog, *box, *main_box, *label, *icon, *vbox, *entry;
GtkWidget *dialog, *box, *main_box, *label, *icon, *vbox, *entry, *chkbtn;
GtkWidget *focus = NULL;
GtkWidget *content_area;
gchar *markup;
......@@ -742,6 +757,7 @@ request_password (GtkPrintBackend *backend,
priv->auth_info_required = g_strdupv (ai_required);
length = g_strv_length (ai_required);
priv->auth_info = g_new0 (gchar *, length);
priv->store_auth_info = FALSE;
dialog = gtk_dialog_new_with_buttons ( _("Authentication"), NULL, GTK_DIALOG_MODAL,
_("_Cancel"), GTK_RESPONSE_CANCEL,
......@@ -812,6 +828,16 @@ request_password (GtkPrintBackend *backend,
}
}
if (can_store_auth_info)
{
chkbtn = gtk_check_button_new_with_mnemonic (_("_Remember password"));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chkbtn), FALSE);
gtk_box_pack_start (GTK_BOX (vbox), chkbtn, FALSE, FALSE, 6);
g_signal_connect (chkbtn, "toggled",
G_CALLBACK (store_auth_info_toggled),
&(priv->store_auth_info));
}
if (focus != NULL)
{
gtk_widget_grab_focus (focus);
......
......@@ -124,12 +124,14 @@ struct _GtkPrintBackendClass
gpointer auth_info_default,
gpointer auth_info_display,
gpointer auth_info_visible,
const gchar *prompt);
const gchar *prompt,
gboolean can_store_auth_info);
/* not a signal */
void (*set_password) (GtkPrintBackend *backend,
gchar **auth_info_required,
gchar **auth_info);
gchar **auth_info,
gboolean store_auth_info);
/* Padding for future expansion */
void (*_gtk_reserved1) (void);
......@@ -162,7 +164,8 @@ void gtk_print_backend_destroy (GtkPrintBackend *pri
GDK_AVAILABLE_IN_ALL
void gtk_print_backend_set_password (GtkPrintBackend *backend,
gchar **auth_info_required,
gchar **auth_info);
gchar **auth_info,
gboolean can_store_auth_info);
/* Backend-only functions for GtkPrintBackend */
......
......@@ -29,12 +29,14 @@ backend_LTLIBRARIES = libprintbackend-cups.la
libprintbackend_cups_la_SOURCES = \
gtkprintbackendcups.c \
gtkprintercups.c \
gtkcupsutils.c
gtkcupsutils.c \
gtkcupssecretsutils.c
noinst_HEADERS = \
gtkprintbackendcups.h \
gtkprintercups.h \
gtkcupsutils.h
gtkcupsutils.h \
gtkcupssecretsutils.h
libprintbackend_cups_la_LDFLAGS = -avoid-version -module $(no_undefined)
libprintbackend_cups_la_LIBADD = $(LDADDS) $(CUPS_LIBS)
......
/* gtkcupssecretsutils.h: Helper to use a secrets service for printer passwords
* Copyright (C) 2014, Intevation GmbH
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <glib.h>
#include <gio/gio.h>
#include <string.h>
#include <gtk/gtk.h>
#include "gtkcupssecretsutils.h"
#define SECRETS_BUS "org.freedesktop.secrets"
#define SECRETS_IFACE(interface) "org.freedesktop.Secret."interface
#define SECRETS_PATH "/org/freedesktop/secrets"
#define SECRETS_TIMEOUT 5000
typedef enum
{
SECRETS_SERVICE_ACTION_QUERY,
SECRETS_SERVICE_ACTION_STORE
} SecretsServiceAction;
typedef struct
{
GDBusConnection *dbus_connection;
SecretsServiceAction action;
gchar **auth_info,
**auth_info_labels,
**auth_info_required,
*printer_uri,
*session_path,
*collection_path;
GDBusProxy *item_proxy;
guint prompt_subscription;
} SecretsServiceData;
/**
* create_attributes:
* @printer_uri: URI for the printer
* @additional_labels: Optional labels for additional attributes
* @additional_attrs: Optional additional attributes
*
* Creates a GVariant dictionary with key / value pairs that
* can be used to identify a secret item.
*
* Returns: A GVariant dictionary of string pairs or NULL on error.
*/
static GVariant *
create_attributes (const gchar *printer_uri,
gchar **additional_attrs,
gchar **additional_labels)
{
GVariantBuilder *attr_builder = NULL;
GVariant *ret = NULL;
if (printer_uri == NULL)
{
GTK_NOTE (PRINTING,
g_print ("create_attributes called with invalid parameters.\n"));
return NULL;
}
attr_builder = g_variant_builder_new (G_VARIANT_TYPE_DICTIONARY);
/* The printer uri is the main identifying part */
g_variant_builder_add (attr_builder, "{ss}", "uri", printer_uri);
if (additional_labels != NULL)
{
int i;
for (i = 0; additional_labels[i] != NULL; i++)
{
g_variant_builder_add (attr_builder, "{ss}",
additional_labels[i],
additional_attrs[i]);
}
}
ret = g_variant_builder_end (attr_builder);
g_variant_builder_unref (attr_builder);
return ret;
}
static void
get_secret_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task;
SecretsServiceData *task_data;
GError *error = NULL;
GVariant *output,
*attributes;
gchar **auth_info = NULL,
*key = NULL,
*value = NULL;
GVariantIter *iter = NULL;
guint i;
gint pw_field = -1;
task = user_data;
task_data = g_task_get_task_data (task);
output = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
res,
&error);
if (output == NULL)
{
g_task_return_error (task, error);
return;
}
attributes = g_dbus_proxy_get_cached_property (task_data->item_proxy,
"Attributes");
if (attributes == NULL)
{
GTK_NOTE (PRINTING, g_print ("Failed to lookup attributes.\n"));
g_variant_unref (output);
g_task_return_pointer (task, NULL, NULL);
return;
}
/* Iterate over the attributes to fill the auth info */
g_variant_get (attributes, "a{ss}", &iter);
auth_info = g_new0 (gchar *,
g_strv_length (task_data->auth_info_required) + 1);
while (g_variant_iter_loop (iter, "{ss}", &key, &value))
{
/* Match attributes with required auth info */
for (i = 0; task_data->auth_info_required[i] != NULL; i++)
{
if ((strcmp (key, "user") == 0 ||
strcmp (key, "username") == 0) &&
strcmp (task_data->auth_info_required[i],
"username") == 0)
{
auth_info[i] = g_strdup (value);
}
else if (strcmp (key, "domain") == 0 &&
strcmp (task_data->auth_info_required[i], "domain") == 0)
{
auth_info[i] = g_strdup (value);
}
else if ((strcmp (key, "hostname") == 0 ||
strcmp (key, "server") == 0 ) &&
strcmp (task_data->auth_info_required[i], "hostname") == 0)
{
auth_info[i] = g_strdup (value);
}
else if (strcmp (task_data->auth_info_required[i], "password") == 0)
{
pw_field = i;
}
}
}
if (pw_field == -1)
{
/* should not happen... */
GTK_NOTE (PRINTING, g_print ("No password required?.\n"));
g_variant_unref (output);
goto fail;
}
else
{
GVariant *secret,
*s_value;
gconstpointer ba_passwd = NULL;
gsize len = 0;
secret = g_variant_get_child_value (output, 0);
g_variant_unref (output);
if (secret == NULL || g_variant_n_children (secret) != 4)
{
GTK_NOTE (PRINTING, g_print ("Get secret response invalid.\n"));
if (secret != NULL)
g_variant_unref (secret);
goto fail;
}
s_value = g_variant_get_child_value (secret, 2);
ba_passwd = g_variant_get_fixed_array (s_value,
&len,
sizeof (guchar));
g_variant_unref (secret);
if (ba_passwd == NULL || strlen (ba_passwd) > len + 1)
{
/* No secret or the secret is not a zero terminated value */
GTK_NOTE (PRINTING, g_print ("Invalid secret.\n"));
g_variant_unref (s_value);
goto fail;
}
auth_info[pw_field] = g_strndup (ba_passwd, len);
g_variant_unref (s_value);
}
for (i = 0; task_data->auth_info_required[i] != NULL; i++)
{
if (auth_info[i] == NULL)
{
/* Error out if we did not find everything */
GTK_NOTE (PRINTING, g_print ("Failed to lookup required attribute: %s.\n",
task_data->auth_info_required[i]));
goto fail;
}
}
g_task_return_pointer (task, auth_info, NULL);
return;
fail:
/* Error out */
GTK_NOTE (PRINTING, g_print ("Failed to lookup secret.\n"));
for (i = 0; i < g_strv_length (task_data->auth_info_required); i++)
{
/* Not all fields of auth_info are neccessarily written so we can not
use strfreev here */
g_free (auth_info[i]);
}
g_free (auth_info);
g_task_return_pointer (task, NULL, NULL);
}
static void
create_item_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task;
GError *error = NULL;
GVariant *output;
gchar *item = NULL;
task = user_data;
output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
res,
&error);
if (output == NULL)
{
g_task_return_error (task, error);
return;
}
g_variant_get (output, "(&o&o)", &item, NULL);
if (item != NULL && strlen (item) > 1)
{
GTK_NOTE (PRINTING, g_print ("Successfully stored auth info.\n"));
g_task_return_pointer (task, NULL, NULL);
return;
}
g_variant_unref (output);
}
static void
do_store_auth_info (GTask *task)
{
GVariant *attributes = NULL,
*properties = NULL,
*secret = NULL;
gchar **additional_attrs = NULL,
**additional_labels = NULL,
*password = NULL;
SecretsServiceData *task_data = g_task_get_task_data (task);
guint i,
length,
additional_count = 0;
GVariantBuilder *prop_builder = NULL;
length = g_strv_length (task_data->auth_info_labels);
additional_attrs = g_new0 (gchar *, length + 1);
additional_labels = g_new0 (gchar *, length + 1);
/* The labels user and server are chosen to be compatible with
the attributes used by system-config-printer */
for (i = 0; task_data->auth_info_labels[i] != NULL; i++)
{
if (g_strcmp0 (task_data->auth_info_labels[i], "username") == 0)
{
additional_attrs[additional_count] = task_data->auth_info[i];
additional_labels[additional_count++] = "user";
}
else if (g_strcmp0 (task_data->auth_info_labels[i], "hostname") == 0)
{
additional_attrs[additional_count] = task_data->auth_info[i];
additional_labels[additional_count++] = "server";
}
else if (g_strcmp0 (task_data->auth_info_labels[i], "password") == 0)
{
password = task_data->auth_info[i];
}
}
attributes = create_attributes (task_data->printer_uri,
additional_attrs,
additional_labels);
g_free (additional_labels);
g_free (additional_attrs);
if (attributes == NULL)
{
GTK_NOTE (PRINTING, g_print ("Failed to create attributes.\n"));
g_task_return_pointer (task, NULL, NULL);
return;
}
if (password == NULL)
{
GTK_NOTE (PRINTING, g_print ("No secret to store.\n"));
g_task_return_pointer (task, NULL, NULL);
return;
}
prop_builder = g_variant_builder_new (G_VARIANT_TYPE_DICTIONARY);
g_variant_builder_add (prop_builder, "{sv}", SECRETS_IFACE ("Item.Label"),
g_variant_new_string (task_data->printer_uri));
g_variant_builder_add (prop_builder, "{sv}", SECRETS_IFACE ("Item.Attributes"),
attributes);
properties = g_variant_builder_end (prop_builder);
g_variant_builder_unref (prop_builder);
secret = g_variant_new ("(oay@ays)",
task_data->session_path,
NULL,
g_variant_new_bytestring (password),
"text/plain");
g_dbus_connection_call (task_data->dbus_connection,
SECRETS_BUS,
task_data->collection_path,
SECRETS_IFACE ("Collection"),
"CreateItem",
g_variant_new ("(@a{sv}@(oayays)b)",
properties,
secret,
TRUE),
G_VARIANT_TYPE ("(oo)"),
G_DBUS_CALL_FLAGS_NONE,
SECRETS_TIMEOUT,
g_task_get_cancellable (task),
create_item_cb,
task);
}
static void
prompt_completed_cb (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
GTask *task;
SecretsServiceData *task_data;
GVariant *dismissed;
gboolean is_dismissed = TRUE;
task = user_data;
task_data = g_task_get_task_data (task);
g_dbus_connection_signal_unsubscribe (task_data->dbus_connection,
task_data->prompt_subscription);
task_data->prompt_subscription = 0;
dismissed = g_variant_get_child_value (parameters, 0);
if (dismissed == NULL)
{
GTK_NOTE (PRINTING, g_print ("Invalid prompt signal.\n"));
g_task_return_pointer (task, NULL, NULL);
return;
}
g_variant_get (dismissed, "b", &is_dismissed);
g_variant_unref (dismissed);
if (is_dismissed)
{
GTK_NOTE (PRINTING, g_print ("Collection unlock dismissed.\n"));
g_task_return_pointer (task, NULL, NULL);
return;
}
/* Prompt successfull, proceed to get or store secret */
switch (task_data->action)
{
case SECRETS_SERVICE_ACTION_STORE:
do_store_auth_info (task);
break;
case SECRETS_SERVICE_ACTION_QUERY:
g_dbus_proxy_call (task_data->item_proxy,
"GetSecret",
g_variant_new ("(o)",
task_data->session_path),
G_DBUS_CALL_FLAGS_NONE,
SECRETS_TIMEOUT,
g_task_get_cancellable (task),
get_secret_cb,
task);
break;
}
}
static void
prompt_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task;
SecretsServiceData *task_data;
GError *error = NULL;
GVariant *output;
task = user_data;
task_data = g_task_get_task_data (task);
output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
res,
&error);
if (output == NULL)
{
g_task_return_error (task, error);
return;
}
g_variant_unref (output);
/* Connect to the prompt's completed signal */
task_data->prompt_subscription =
g_dbus_connection_signal_subscribe (task_data->dbus_connection,
NULL,
SECRETS_IFACE ("Prompt"),
"Completed",
NULL,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
prompt_completed_cb,
task,
NULL);
}
static void
unlock_collection_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task;
SecretsServiceData *task_data;
GError *error = NULL;
GVariant *output;
const gchar *prompt_path;
task = user_data;
task_data = g_task_get_task_data (task);
output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
res,
&error);
if (output == NULL)
{
g_task_return_error (task, error);
return;
}
g_variant_get (output, "(@ao&o)", NULL, &prompt_path);
if (prompt_path != NULL && strlen (prompt_path) > 1)
{
g_dbus_connection_call (task_data->dbus_connection,
SECRETS_BUS,
prompt_path,
SECRETS_IFACE ("Prompt"),
"Prompt",
g_variant_new ("(s)", "0"),
G_VARIANT_TYPE ("()"),
G_DBUS_CALL_FLAGS_NONE,
SECRETS_TIMEOUT,
g_task_get_cancellable (task),
prompt_cb,
task);
}
else
{
switch (task_data->action)
{
case SECRETS_SERVICE_ACTION_STORE:
do_store_auth_info (task);
break;
case SECRETS_SERVICE_ACTION_QUERY:
/* Prompt successfull proceed to get secret */
g_dbus_proxy_call (task_data->item_proxy,
"GetSecret",
g_variant_new ("(o)",
task_data->session_path),
G_DBUS_CALL_FLAGS_NONE,
SECRETS_TIMEOUT,
g_task_get_cancellable (task),
get_secret_cb,
task);
break;
}
}
g_variant_unref (output);
}
static void
unlock_read_alias_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task;
SecretsServiceData *task_data;
GError *error = NULL;
GVariant *output,
*subresult;
gsize path_len = 0;
const gchar *collection_path;
task = user_data;
task_data = g_task_get_task_data (task);
output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
res,
&error);
if (output == NULL)
{
g_task_return_error (task, error);
return;
}
subresult = g_variant_get_child_value (output, 0);
g_variant_unref (output);
if (subresult == NULL)
{
GTK_NOTE (PRINTING, g_print ("Invalid ReadAlias response.\n"));
g_task_return_pointer (task, NULL, NULL);
return;