[gnome-shell] Add a PolicyKit authentication agent



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]