[gnome-shell] Add a PolicyKit authentication agent
- From: David Zeuthen <davidz src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] Add a PolicyKit authentication agent
- Date: Tue, 22 Feb 2011 20:14:08 +0000 (UTC)
commit 86b925a294aaaf9f5f3936e060294273cafbb40e
Author: David Zeuthen <davidz redhat com>
Date: Mon Feb 21 12:17:34 2011 -0500
Add a PolicyKit authentication agent
A PolicyKit Authentication Agent is a construct used to authenticate
one or more identities. See the PolicyKit documentation for more
details on authentication agents and how PolicyKit works:
http://hal.freedesktop.org/docs/polkit/
Since gjs does not support subclassing a GObject class from Javascript
code, we bring in a native class to bridge the VFuncs to GObject
signals. Additionally, this native class also queues up authentication
requests so the user of the native class only has to deal with a
single outstanding request at any one time.
The file js/ui/polkitAuthenticationAgent.js introduces a singleton
that listens for authentication requests via the native class. This
singleton uses the PolkitAgent machinery to do the actual heavy-weight
lifting required for authentication (essentially a PAM conversation).
We currently don't allow the user to pick the identity to be
authenticated.
https://bugzilla.gnome.org/show_bug.cgi?id=642886
Signed-off-by: David Zeuthen <davidz redhat com>
configure.ac | 5 +-
data/theme/gnome-shell.css | 72 ++++++
js/Makefile.am | 1 +
js/ui/main.js | 4 +
js/ui/polkitAuthenticationAgent.js | 350 +++++++++++++++++++++++++
src/Makefile.am | 2 +
src/shell-marshal.list | 1 +
src/shell-polkit-authentication-agent.c | 424 +++++++++++++++++++++++++++++++
src/shell-polkit-authentication-agent.h | 32 +++
9 files changed, 889 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 92976cc..3258f46 100644
--- a/configure.ac
+++ b/configure.ac
@@ -71,7 +71,7 @@ LIBEDATASERVER_REQUIRED=1.2.0
LIBEDATASERVERUI2_REQUIRED=1.2.0
LIBEDATASERVERUI3_REQUIRED=2.91.6
TELEPATHY_GLIB_MIN_VERSION=0.13.12
-
+POLKIT_MIN_VERSION=0.100
# Collect more than 20 libraries for a prize!
PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION
@@ -86,7 +86,8 @@ PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION
libstartup-notification-1.0
gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
libcanberra
- telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION)
+ telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION
+ polkit-agent-1 >= $POLKIT_MIN_VERSION)
GJS_VERSION=`$PKG_CONFIG --modversion gjs-internals-1.0`
AC_DEFINE_UNQUOTED([GJS_VERSION], ["$GJS_VERSION"], [The version of GJS we're linking to])
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 1da6619..7476390 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -1347,6 +1347,78 @@ StTooltip StLabel {
color: #444444;
}
+/* PolicyKit Authentication Dialog */
+.polkit-dialog {
+ /* this is the width of the entire modal popup */
+ width: 500px;
+}
+
+.polkit-dialog-main-layout {
+ spacing: 10px;
+ padding: 10px;
+}
+
+.polkit-dialog-message-layout {
+ spacing: 10px;
+}
+
+.polkit-dialog-headline {
+ font-size: 12pt;
+ font-weight: bold;
+ color: #666666;
+}
+
+.polkit-dialog-description {
+ font-size: 10pt;
+ color: white;
+}
+
+.polkit-dialog-user-layout {
+ padding-left: 10px;
+ spacing: 10px;
+}
+
+.polkit-dialog-password-label {
+ padding-right: 0.5em;
+}
+
+.polkit-dialog-password-entry {
+ background-color: white;
+ color: black;
+ border-radius: 5px;
+}
+
+.polkit-dialog-error-label {
+ font-size: 12px;
+ color: white;
+}
+
+.polkit-dialog-error-box {
+ padding-top: 15px;
+ spacing: 5px;
+}
+
+.polkit-dialog-checking-label {
+ font-size: 12px;
+ color: white;
+}
+
+.polkit-dialog-checking-box {
+ padding-top: 15px;
+ spacing: 5px;
+}
+
+.polkit-dialog-info-label {
+ font-size: 12px;
+ color: white;
+}
+
+.polkit-dialog-info-box {
+ padding-top: 15px;
+ spacing: 5px;
+}
+
+
/* Magnifier */
.magnifier-zoom-region {
diff --git a/js/Makefile.am b/js/Makefile.am
index e4a4145..55bb111 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -39,6 +39,7 @@ nobase_dist_js_DATA = \
ui/panel.js \
ui/panelMenu.js \
ui/placeDisplay.js \
+ ui/polkitAuthenticationAgent.js \
ui/popupMenu.js \
ui/runDialog.js \
ui/scripting.js \
diff --git a/js/ui/main.js b/js/ui/main.js
index 265dbf9..ec8a68e 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -23,6 +23,7 @@ const _ = Gettext.gettext;
const Chrome = imports.ui.chrome;
const CtrlAltTab = imports.ui.ctrlAltTab;
const EndSessionDialog = imports.ui.endSessionDialog;
+const PolkitAuthenticationAgent = imports.ui.polkitAuthenticationAgent;
const Environment = imports.ui.environment;
const ExtensionSystem = imports.ui.extensionSystem;
const MessageTray = imports.ui.messageTray;
@@ -183,6 +184,9 @@ function start() {
// initiate logouts.
EndSessionDialog.init();
+ // Attempt to become a PolicyKit authentication agent
+ PolkitAuthenticationAgent.init()
+
global.gdk_screen.connect('monitors-changed', _relayout);
ExtensionSystem.init();
diff --git a/js/ui/polkitAuthenticationAgent.js b/js/ui/polkitAuthenticationAgent.js
new file mode 100644
index 0000000..294c58d
--- /dev/null
+++ b/js/ui/polkitAuthenticationAgent.js
@@ -0,0 +1,350 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*-
+ *
+ * Copyright 2010 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+*
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+const Lang = imports.lang;
+const Signals = imports.signals;
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+const Shell = imports.gi.Shell;
+const Clutter = imports.gi.Clutter;
+const St = imports.gi.St;
+const Pango = imports.gi.Pango;
+const Gdm = imports.gi.Gdm;
+const Gio = imports.gi.Gio;
+const Mainloop = imports.mainloop;
+const Polkit = imports.gi.Polkit;
+const PolkitAgent = imports.gi.PolkitAgent;
+
+const ModalDialog = imports.ui.modalDialog;
+
+function AuthenticationDialog(message, cookie, userNames) {
+ this._init(message, cookie, userNames);
+}
+
+AuthenticationDialog.prototype = {
+ __proto__: ModalDialog.ModalDialog.prototype,
+
+ _init: function(message, cookie, userNames) {
+ ModalDialog.ModalDialog.prototype._init.call(this, { styleClass: 'polkit-dialog' });
+
+ this.message = message;
+ this.userNames = userNames;
+
+ let mainContentBox = new St.BoxLayout({ style_class: 'polkit-dialog-main-layout',
+ vertical: false });
+ this.contentLayout.add(mainContentBox,
+ { x_fill: true,
+ y_fill: true });
+
+ let icon = new St.Icon({ icon_name: 'dialog-password-symbolic' });
+ mainContentBox.add(icon,
+ { x_fill: true,
+ y_fill: false,
+ x_align: St.Align.END,
+ y_align: St.Align.START });
+
+ let messageBox = new St.BoxLayout({ style_class: 'polkit-dialog-message-layout',
+ vertical: true });
+ mainContentBox.add(messageBox,
+ { y_align: St.Align.START });
+
+ this._subjectLabel = new St.Label({ style_class: 'polkit-dialog-headline',
+ text: _('Authentication Required') });
+
+ messageBox.add(this._subjectLabel,
+ { y_fill: false,
+ y_align: St.Align.START });
+
+ this._descriptionLabel = new St.Label({ style_class: 'polkit-dialog-description',
+ text: message });
+ this._descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
+ this._descriptionLabel.clutter_text.line_wrap = true;
+
+ messageBox.add(this._descriptionLabel,
+ { y_fill: true,
+ y_align: St.Align.START });
+
+ if (userNames.length > 1) {
+ log('polkitAuthenticationAgent: Received ' + userNames.length +
+ ' identities that can be used for authentication. Only ' +
+ 'considering the first one.');
+ }
+
+ let userName = userNames[0];
+
+ this._user = Gdm.UserManager.ref_default().get_user(userName);
+ let userRealName = this._user.get_real_name()
+ this._userLoadedId = this._user.connect('notify::is_loaded',
+ Lang.bind(this, this._onUserChanged));
+ this._userChangedId = this._user.connect('changed',
+ Lang.bind(this, this._onUserChanged));
+
+ // Special case 'root'
+ if (userName == 'root')
+ userRealName = _('Administrator');
+
+ // Work around Gdm.UserManager returning an empty string for the real name
+ if (userRealName.length == 0)
+ userRealName = userName;
+
+ let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout',
+ vertical: false });
+ messageBox.add(userBox);
+
+ this._userIcon = new St.Icon();
+ this._userIcon.hide();
+ userBox.add(this._userIcon,
+ { x_fill: true,
+ y_fill: false,
+ x_align: St.Align.END,
+ y_align: St.Align.START });
+
+ let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-label',
+ text: userRealName }));
+ userBox.add(userLabel,
+ { x_fill: true,
+ y_fill: false,
+ x_align: St.Align.END,
+ y_align: St.Align.MIDDLE });
+
+ this._onUserChanged();
+
+ this._passwordBox = new St.BoxLayout({ vertical: false });
+ messageBox.add(this._passwordBox);
+ this._passwordLabel = new St.Label(({ style_class: 'polkit-dialog-password-label' }));
+ this._passwordBox.add(this._passwordLabel);
+ this._passwordEntry = new St.Entry({ style_class: 'polkit-dialog-password-entry',
+ text: _(''),
+ can_focus: true});
+ this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivate));
+ this._passwordBox.add(this._passwordEntry,
+ {expand: true });
+ this._passwordBox.hide();
+
+ this._errorBox = new St.BoxLayout({ style_class: 'polkit-dialog-error-box' });
+ messageBox.add(this._errorBox);
+ let errorIcon = new St.Icon({ icon_name: 'dialog-error',
+ icon_size: 24,
+ style_class: 'polkit-dialog-error-icon' });
+ this._errorBox.add(errorIcon, { y_align: St.Align.MIDDLE });
+ this._errorMessage = new St.Label({ style_class: 'polkit-dialog-error-label' });
+ this._errorMessage.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
+ this._errorMessage.clutter_text.line_wrap = true;
+ this._errorBox.add(this._errorMessage, { expand: true,
+ y_align: St.Align.MIDDLE,
+ y_fill: true });
+ this._errorBox.hide();
+
+ this._infoBox = new St.BoxLayout({ style_class: 'polkit-dialog-info-box' });
+ messageBox.add(this._infoBox);
+ let infoIcon = new St.Icon({ icon_name: 'dialog-information',
+ icon_size: 24,
+ style_class: 'polkit-dialog-info-icon' });
+ this._infoBox.add(infoIcon, { y_align: St.Align.MIDDLE });
+ this._infoMessage = new St.Label({ style_class: 'polkit-dialog-info-label'});
+ this._infoMessage.clutter_text.line_wrap = true;
+ this._infoBox.add(this._infoMessage, { expand: true,
+ y_align: St.Align.MIDDLE,
+ y_fill: true });
+ this._infoBox.hide();
+
+ this.setButtons([{ label: _('Cancel'),
+ action: Lang.bind(this, this.cancel),
+ key: Clutter.Escape
+ },
+ { label: _('Authenticate'),
+ action: Lang.bind(this, this._onAuthenticateButtonPressed)
+ }]);
+
+ this._doneEmitted = false;
+
+ this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
+ this._cookie = cookie;
+
+ this._session = new PolkitAgent.Session({ identity: this._identityToAuth,
+ cookie: this._cookie });
+ this._session.connect('completed', Lang.bind(this, this._onSessionCompleted));
+ this._session.connect('request', Lang.bind(this, this._onSessionRequest));
+ this._session.connect('show-error', Lang.bind(this, this._onSessionShowError));
+ this._session.connect('show-info', Lang.bind(this, this._onSessionShowInfo));
+ this.connect('opened',
+ Lang.bind(this, function() {
+ this._session.initiate();
+ }));
+ },
+
+ _emitDone: function(keepVisible) {
+ if (!this._doneEmitted) {
+ this._doneEmitted = true;
+ this.emit('done', keepVisible);
+ }
+ },
+
+ _onEntryActivate: function() {
+ let response = this._passwordEntry.get_text();
+ this._session.response(response);
+ // When the user responds, dismiss already shown info and
+ // error texts (if any)
+ this._errorBox.hide();
+ this._infoBox.hide();
+ },
+
+ _onAuthenticateButtonPressed: function() {
+ this._onEntryActivate();
+ },
+
+ _onSessionCompleted: function(session, gainedAuthorization) {
+ this._passwordBox.hide();
+ this._emitDone(!gainedAuthorization);
+ },
+
+ _onSessionRequest: function(session, request, echo_on) {
+ // Cheap localization trick
+ if (request == 'Password:')
+ this._passwordLabel.set_text(_('Password:'));
+ else
+ this._passwordLabel.set_text(request);
+
+ if (echo_on)
+ this._passwordEntry.clutter_text.set_password_char('');
+ else
+ this._passwordEntry.clutter_text.set_password_char('\u25cf'); // â?? U+25CF BLACK CIRCLE
+
+ this._passwordBox.show();
+ this._passwordEntry.set_text('');
+ this._passwordEntry.grab_key_focus();
+ },
+
+ _onSessionShowError: function(session, text) {
+ this._passwordEntry.set_text('');
+ this._errorMessage.set_text(text);
+ this._errorBox.show();
+ },
+
+ _onSessionShowInfo: function(session, text) {
+ this._passwordEntry.set_text('');
+ this._infoMessage.set_text(text);
+ this._infoBox.show();
+ },
+
+ destroySession: function() {
+ if (this._session) {
+ this._session.cancel();
+ this._session = null;
+ }
+ },
+
+ _onUserChanged: function() {
+ if (this._user.is_loaded) {
+ let iconFileName = this._user.get_icon_file();
+ let iconFile = Gio.file_new_for_path(iconFileName);
+ let icon;
+ if (iconFile.query_exists(null)) {
+ icon = new Gio.FileIcon({file: iconFile});
+ } else {
+ icon = new Gio.ThemedIcon({name: 'avatar-default'});
+ }
+ this._userIcon.set_gicon (icon);
+ this._userIcon.show();
+ }
+ },
+
+ cancel: function() {
+ this.close(global.get_current_time());
+ this._emitDone(false);
+ },
+
+};
+Signals.addSignalMethods(AuthenticationDialog.prototype);
+
+function AuthenticationAgent() {
+ this._init();
+}
+
+AuthenticationAgent.prototype = {
+ _init: function() {
+ this._native = new Shell.PolkitAuthenticationAgent();
+ this._native.connect('initiate', Lang.bind(this, this._onInitiate));
+ this._native.connect('cancel', Lang.bind(this, this._onCancel));
+ this._currentDialog = null;
+ this._isCompleting = false;
+ },
+
+ _onInitiate: function(nativeAgent, actionId, message, iconName, cookie, userNames) {
+ this._currentDialog = new AuthenticationDialog(message, cookie, userNames);
+ if (!this._currentDialog.open(global.get_current_time())) {
+ // This can fail if e.g. unable to get input grab
+ //
+ // In an ideal world this wouldn't happen (because the
+ // Shell is in complete control of the session) but that's
+ // just not how things work right now.
+ //
+ // We could add retrying if this turns out to be a problem
+ log('polkitAuthenticationAgent: Failed to show modal dialog');
+ this._currentDialog.destroySession();
+ this._currentDialog = null;
+ this._native.complete()
+ } else {
+ this._currentDialog.connect('done', Lang.bind(this, this._onDialogDone));
+ }
+ },
+
+ _onCancel: function(nativeAgent) {
+ this._completeRequest(false);
+ },
+
+ _onDialogDone: function(dialog, keepVisible) {
+ this._completeRequest(keepVisible);
+ },
+
+ _reallyCompleteRequest: function() {
+ this._currentDialog.close();
+ this._currentDialog.destroySession();
+ this._currentDialog = null;
+ this._isCompleting = false;
+
+ this._native.complete()
+ },
+
+ _completeRequest: function(keepVisible) {
+ if (this._isCompleting)
+ return;
+
+ this._isCompleting = true;
+
+ if (keepVisible) {
+ // Give the user 2 seconds to read 'Authentication Failure' before
+ // dismissing the dialog
+ Mainloop.timeout_add(2000,
+ Lang.bind(this,
+ function() {
+ this._reallyCompleteRequest();
+ }));
+ } else {
+ this._reallyCompleteRequest();
+ }
+ }
+}
+
+function init() {
+ let agent = new AuthenticationAgent();
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index 33d97e5..ea12a2b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -94,6 +94,8 @@ libgnome_shell_la_SOURCES = \
shell-gtk-embed.c \
shell-global.c \
shell-perf-log.c \
+ shell-polkit-authentication-agent.h \
+ shell-polkit-authentication-agent.c \
shell-slicer.c \
shell-stack.c \
shell-tray-icon.c \
diff --git a/src/shell-marshal.list b/src/shell-marshal.list
index 34d1078..9bc1cb0 100644
--- a/src/shell-marshal.list
+++ b/src/shell-marshal.list
@@ -5,3 +5,4 @@ VOID:BOXED,OBJECT
VOID:OBJECT,OBJECT
VOID:STRING,OBJECT,BOOLEAN
VOID:INT,INT
+VOID:STRING,STRING,STRING,STRING,BOXED
diff --git a/src/shell-polkit-authentication-agent.c b/src/shell-polkit-authentication-agent.c
new file mode 100644
index 0000000..2ac120d
--- /dev/null
+++ b/src/shell-polkit-authentication-agent.c
@@ -0,0 +1,424 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 20011 Red Hat, Inc.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <pwd.h>
+
+#include "shell-marshal.h"
+
+#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE
+#include <polkitagent/polkitagent.h>
+#include "shell-polkit-authentication-agent.h"
+
+/* uncomment for useful debug output */
+/* #define SHOW_DEBUG */
+
+#ifdef SHOW_DEBUG
+static void
+print_debug (const gchar *format, ...)
+{
+ gchar *s;
+ va_list ap;
+ gchar timebuf[64];
+ GTimeVal now;
+ time_t now_t;
+ struct tm broken_down;
+
+ g_get_current_time (&now);
+ now_t = now.tv_sec;
+ localtime_r (&now_t, &broken_down);
+ strftime (timebuf, sizeof timebuf, "%H:%M:%S", &broken_down);
+
+ va_start (ap, format);
+ s = g_strdup_vprintf (format, ap);
+ va_end (ap);
+
+ g_print ("ShellPolkitAuthenticationAgent: %s.%03d: %s\n", timebuf, (gint) (now.tv_usec / 1000), s);
+ g_free (s);
+}
+#else
+static void
+print_debug (const gchar *str, ...)
+{
+}
+#endif
+
+
+struct _ShellPolkitAuthenticationAgentClass
+{
+ PolkitAgentListenerClass parent_class;
+};
+
+struct _AuthRequest;
+typedef struct _AuthRequest AuthRequest;
+
+struct _ShellPolkitAuthenticationAgent {
+ PolkitAgentListener parent_instance;
+
+ GList *scheduled_requests;
+
+ AuthRequest *current_request;
+};
+
+/* Signals */
+enum
+{
+ INITIATE_SIGNAL,
+ CANCEL_SIGNAL,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (ShellPolkitAuthenticationAgent, shell_polkit_authentication_agent, POLKIT_AGENT_TYPE_LISTENER);
+
+static void initiate_authentication (PolkitAgentListener *listener,
+ const gchar *action_id,
+ const gchar *message,
+ const gchar *icon_name,
+ PolkitDetails *details,
+ const gchar *cookie,
+ GList *identities,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+static gboolean initiate_authentication_finish (PolkitAgentListener *listener,
+ GAsyncResult *res,
+ GError **error);
+
+static void
+shell_polkit_authentication_agent_init (ShellPolkitAuthenticationAgent *agent)
+{
+ gpointer handle;
+ PolkitSubject *subject;
+ GError *error;
+
+ subject = NULL;
+
+ error = NULL;
+ subject = polkit_unix_session_new_for_process_sync (getpid (),
+ NULL, /* GCancellable* */
+ &error);
+ if (subject == NULL)
+ {
+ g_warning ("Error getting session for the process we are in: %s (%s %d)",
+ error->message,
+ g_quark_to_string (error->domain),
+ error->code);
+ g_error_free (error);
+ goto out;
+ }
+
+ handle = polkit_agent_listener_register (POLKIT_AGENT_LISTENER (agent),
+ POLKIT_AGENT_REGISTER_FLAGS_NONE,
+ subject,
+ NULL, /* use default object path */
+ NULL, /* GCancellable */
+ &error);
+ if (handle == NULL)
+ {
+ g_warning ("Error registering polkit authentication agent: %s (%s %d)",
+ error->message,
+ g_quark_to_string (error->domain),
+ error->code);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* We don't need to register so skip saving handle */
+
+ out:
+ if (subject != NULL)
+ g_object_unref (subject);
+}
+
+static void
+shell_polkit_authentication_agent_finalize (GObject *object)
+{
+ /* ShellPolkitAuthenticationAgent *agent = SHELL_POLKIT_AUTHENTICATION_AGENT (object); */
+
+ /* Specifically left empty since the object stays alive forever - if code
+ * is reused it would need to free outstanding requests etc.
+ */
+
+ G_OBJECT_CLASS (shell_polkit_authentication_agent_parent_class)->finalize (object);
+}
+
+static void
+shell_polkit_authentication_agent_class_init (ShellPolkitAuthenticationAgentClass *klass)
+{
+ GObjectClass *gobject_class;
+ PolkitAgentListenerClass *listener_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = shell_polkit_authentication_agent_finalize;
+
+ listener_class = POLKIT_AGENT_LISTENER_CLASS (klass);
+ listener_class->initiate_authentication = initiate_authentication;
+ listener_class->initiate_authentication_finish = initiate_authentication_finish;
+
+ signals[INITIATE_SIGNAL] =
+ g_signal_new ("initiate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* class_offset */
+ NULL, /* accumulator */
+ NULL, /* accumulator data */
+ _shell_marshal_VOID__STRING_STRING_STRING_STRING_BOXED,
+ G_TYPE_NONE,
+ 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRV);
+
+ signals[CANCEL_SIGNAL] =
+ g_signal_new ("cancel",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* class_offset */
+ NULL, /* accumulator */
+ NULL, /* accumulator data */
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+ShellPolkitAuthenticationAgent *
+shell_polkit_authentication_agent_new (void)
+{
+ return SHELL_POLKIT_AUTHENTICATION_AGENT (g_object_new (SHELL_TYPE_POLKIT_AUTHENTICATION_AGENT, NULL));
+}
+
+struct _AuthRequest {
+ /* not holding ref */
+ ShellPolkitAuthenticationAgent *agent;
+ GCancellable *cancellable;
+ gulong handler_id;
+
+ /* copies */
+ gchar *action_id;
+ gchar *message;
+ gchar *icon_name;
+ PolkitDetails *details;
+ gchar *cookie;
+ GList *identities;
+
+ GSimpleAsyncResult *simple;
+};
+
+static void
+auth_request_free (AuthRequest *request)
+{
+ g_cancellable_disconnect (request->cancellable, request->handler_id);
+ g_free (request->action_id);
+ g_free (request->message);
+ g_free (request->icon_name);
+ g_object_unref (request->details);
+ g_list_foreach (request->identities, (GFunc) g_object_unref, NULL);
+ g_list_free (request->identities);
+ g_object_unref (request->simple);
+ g_free (request);
+}
+
+static void
+auth_request_initiate (AuthRequest *request)
+{
+ gchar **user_names;
+ GPtrArray *p;
+ GList *l;
+
+ p = g_ptr_array_new ();
+ for (l = request->identities; l != NULL; l = l->next)
+ {
+ if (POLKIT_IS_UNIX_USER (l->data))
+ {
+ PolkitUnixUser *user = POLKIT_UNIX_USER (l->data);
+ gint uid;
+ gchar buf[4096];
+ struct passwd pwd;
+ struct passwd *ppwd;
+
+ uid = polkit_unix_user_get_uid (user);
+ if (getpwuid_r (uid, &pwd, buf, sizeof (buf), &ppwd) == 0)
+ {
+ if (!g_utf8_validate (pwd.pw_name, -1, NULL))
+ {
+ g_warning ("Invalid UTF-8 in username for uid %d. Skipping", uid);
+ }
+ else
+ {
+ g_ptr_array_add (p, g_strdup (pwd.pw_name));
+ }
+ }
+ else
+ {
+ g_warning ("Error looking up user name for uid %d", uid);
+ }
+ }
+ else
+ {
+ g_warning ("Unsupporting identity of GType %s", g_type_name (G_TYPE_FROM_INSTANCE (l->data)));
+ }
+ }
+ g_ptr_array_add (p, NULL);
+ user_names = (gchar **) g_ptr_array_free (p, FALSE);
+ g_signal_emit (request->agent,
+ signals[INITIATE_SIGNAL],
+ 0, /* detail */
+ request->action_id,
+ request->message,
+ request->icon_name,
+ request->cookie,
+ user_names);
+ g_strfreev (user_names);
+}
+
+static void auth_request_complete (AuthRequest *request);
+
+static gboolean
+handle_cancelled_in_idle (gpointer user_data)
+{
+ AuthRequest *request = user_data;
+
+ print_debug ("CANCELLED %s cookie %s", request->action_id, request->cookie);
+ if (request == request->agent->current_request)
+ {
+ g_signal_emit (request->agent,
+ signals[CANCEL_SIGNAL],
+ 0); /* detail */
+ }
+ else
+ {
+ auth_request_complete (request);
+ }
+
+ return FALSE;
+}
+
+static void
+on_request_cancelled (GCancellable *cancellable,
+ gpointer user_data)
+{
+ AuthRequest *request = user_data;
+ /* post-pone to idle to handle GCancellable deadlock in
+ *
+ * https://bugzilla.gnome.org/show_bug.cgi?id=642968
+ */
+ g_idle_add (handle_cancelled_in_idle, request);
+}
+
+static void maybe_process_next_request (ShellPolkitAuthenticationAgent *agent);
+
+static void
+auth_request_complete (AuthRequest *request)
+{
+ ShellPolkitAuthenticationAgent *agent = request->agent;
+
+ if (agent->current_request == request)
+ {
+ print_debug ("COMPLETING CURRENT %s cookie %s", request->action_id, request->cookie);
+
+ g_simple_async_result_complete_in_idle (request->simple);
+ auth_request_free (request);
+
+ agent->current_request = NULL;
+
+ maybe_process_next_request (agent);
+ }
+ else
+ {
+ print_debug ("COMPLETING SCHEDULED %s cookie %s", request->action_id, request->cookie);
+ agent->scheduled_requests = g_list_remove (agent->scheduled_requests, request);
+ g_simple_async_result_complete_in_idle (request->simple);
+ auth_request_free (request);
+ }
+}
+
+static void
+maybe_process_next_request (ShellPolkitAuthenticationAgent *agent)
+{
+ print_debug ("MAYBE_PROCESS cur=%p len(scheduled)=%d", agent->current_request, g_list_length (agent->scheduled_requests));
+
+ if (agent->current_request == NULL && agent->scheduled_requests != NULL)
+ {
+ AuthRequest *request;
+
+ request = agent->scheduled_requests->data;
+
+ agent->current_request = request;
+ agent->scheduled_requests = g_list_remove (agent->scheduled_requests, request);
+
+ print_debug ("INITIATING %s cookie %s", request->action_id, request->cookie);
+ auth_request_initiate (request);
+ }
+}
+
+static void
+initiate_authentication (PolkitAgentListener *listener,
+ const gchar *action_id,
+ const gchar *message,
+ const gchar *icon_name,
+ PolkitDetails *details,
+ const gchar *cookie,
+ GList *identities,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellPolkitAuthenticationAgent *agent = SHELL_POLKIT_AUTHENTICATION_AGENT (listener);
+ AuthRequest *request;
+
+ request = g_new0 (AuthRequest, 1);
+ request->agent = agent;
+ request->action_id = g_strdup (action_id);
+ request->message = g_strdup (message);
+ request->icon_name = g_strdup (icon_name);
+ request->details = g_object_ref (details);
+ request->cookie = g_strdup (cookie);
+ request->identities = g_list_copy (identities);
+ g_list_foreach (request->identities, (GFunc) g_object_ref, NULL);
+ request->simple = g_simple_async_result_new (G_OBJECT (listener),
+ callback,
+ user_data,
+ initiate_authentication);
+ request->cancellable = cancellable;
+ request->handler_id = g_cancellable_connect (request->cancellable,
+ G_CALLBACK (on_request_cancelled),
+ request,
+ NULL); /* GDestroyNotify for request */
+
+ print_debug ("SCHEDULING %s cookie %s", request->action_id, request->cookie);
+ agent->scheduled_requests = g_list_append (agent->scheduled_requests, request);
+
+ maybe_process_next_request (agent);
+}
+
+static gboolean
+initiate_authentication_finish (PolkitAgentListener *listener,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+ else
+ return TRUE;
+}
+
+void
+shell_polkit_authentication_agent_complete (ShellPolkitAuthenticationAgent *agent)
+{
+ g_return_if_fail (SHELL_IS_POLKIT_AUTHENTICATION_AGENT (agent));
+ g_return_if_fail (agent->current_request != NULL);
+
+ auth_request_complete (agent->current_request);
+}
diff --git a/src/shell-polkit-authentication-agent.h b/src/shell-polkit-authentication-agent.h
new file mode 100644
index 0000000..9dbd215
--- /dev/null
+++ b/src/shell-polkit-authentication-agent.h
@@ -0,0 +1,32 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 20011 Red Hat, Inc.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __SHELL_POLKIT_AUTHENTICATION_AGENT_H__
+#define __SHELL_POLKIT_AUTHENTICATION_AGENT_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ShellPolkitAuthenticationAgent ShellPolkitAuthenticationAgent;
+typedef struct _ShellPolkitAuthenticationAgentClass ShellPolkitAuthenticationAgentClass;
+
+#define SHELL_TYPE_POLKIT_AUTHENTICATION_AGENT (shell_polkit_authentication_agent_get_type ())
+#define SHELL_POLKIT_AUTHENTICATION_AGENT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_POLKIT_AUTHENTICATION_AGENT, ShellPolkitAuthenticationAgent))
+#define SHELL_POLKIT_AUTHENTICATION_AGENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_POLKIT_AUTHENTICATION_AGENT, ShellPolkitAuthenticationAgentClass))
+#define SHELL_IS_POLKIT_AUTHENTICATION_AGENT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_POLKIT_AUTHENTICATION_AGENT))
+#define SHELL_IS_POLKIT_AUTHENTICATION_AGENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_POLKIT_AUTHENTICATION_AGENT))
+#define SHELL_POLKIT_AUTHENTICATION_AGENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_POLKIT_AUTHENTICATION_AGENT, ShellPolkitAuthenticationAgentClass))
+
+GType shell_polkit_authentication_agent_get_type (void) G_GNUC_CONST;
+ShellPolkitAuthenticationAgent *shell_polkit_authentication_agent_new (void);
+void shell_polkit_authentication_agent_complete (ShellPolkitAuthenticationAgent *agent);
+
+G_END_DECLS
+
+#endif /* __SHELL_POLKIT_AUTHENTICATION_AGENT_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]