From 86b925a294aaaf9f5f3936e060294273cafbb40e Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Mon, 21 Feb 2011 12:17:34 -0500 Subject: [PATCH] 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 --- 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(-) create mode 100644 js/ui/polkitAuthenticationAgent.js create mode 100644 src/shell-polkit-authentication-agent.c create mode 100644 src/shell-polkit-authentication-agent.h diff --git a/configure.ac b/configure.ac index 92976cc23..3258f460f 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 1da66198d..747639053 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 e4a414525..55bb11131 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 265dbf9fd..ec8a68e52 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 000000000..294c58df9 --- /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 + */ + +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 33d97e5c1..ea12a2b90 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 34d107884..9bc1cb0b0 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 000000000..2ac120dfe --- /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 + */ + +#include "config.h" + +#include + +#include "shell-marshal.h" + +#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE +#include +#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 000000000..9dbd215d8 --- /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 + */ + +#ifndef __SHELL_POLKIT_AUTHENTICATION_AGENT_H__ +#define __SHELL_POLKIT_AUTHENTICATION_AGENT_H__ + +#include + +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__ */