Commit c301be1e authored by Guido Gunther's avatar Guido Gunther
Browse files

Merge branch 'polkit-agent' into 'master'

Allow the shell to act as polkit agent

Closes #22

See merge request Librem5/phosh!194
parents 9d249d77 c5f16ad5
......@@ -10,6 +10,7 @@ Build-Depends:
libhandy-0.0-dev (>= 0.0.1),
libnm-dev,
libpam0g-dev,
libpolkit-agent-1-dev,
libpulse-dev,
libupower-glib-dev,
libwayland-dev,
......
......@@ -112,6 +112,7 @@ libgvc = subproject('gvc',
])
libgvc_dep = libgvc.get_variable('libgvc_dep')
libnm_dep = dependency('libnm', version: '>= 1.14')
libpolkit_agent_dep = dependency('polkit-agent-1', version: '>= 0.105')
subdir('data')
subdir('po')
......
......@@ -59,7 +59,7 @@ static void layer_surface_configure(void *data,
PhoshLayerSurface *self = data;
gtk_window_resize (GTK_WINDOW (self), width, height);
zwlr_layer_surface_v1_ack_configure(surface, serial);
gtk_widget_show_all (GTK_WIDGET (self));
gtk_widget_show (GTK_WIDGET (self));
g_signal_emit (self, signals[CONFIGURED], 0);
}
......
......@@ -21,6 +21,10 @@ phosh_sources = [
'app.h',
'auth.c',
'auth.h',
'polkit-auth-agent.c',
'polkit-auth-agent.h',
'polkit-auth-prompt.c',
'polkit-auth-prompt.h',
'background.c',
'background.h',
'batteryinfo.c',
......@@ -89,6 +93,7 @@ phosh_deps = [
dependency('upower-glib', version: '>=0.99.1'),
libgvc_dep,
libnm_dep,
libpolkit_agent_dep,
cc.find_library('pam', required: true),
cc.find_library('m', required: false),
cc.find_library('rt', required: false),
......
......@@ -4,6 +4,7 @@
<file preprocess="xml-stripblanks">ui/favorites.ui</file>
<file preprocess="xml-stripblanks">ui/home.ui</file>
<file preprocess="xml-stripblanks">ui/lockscreen.ui</file>
<file preprocess="xml-stripblanks">ui/polkit-auth-prompt.ui</file>
<file preprocess="xml-stripblanks">ui/settings-menu.ui</file>
<file preprocess="xml-stripblanks">ui/system-prompt.ui</file>
<file preprocess="xml-stripblanks">ui/top-panel.ui</file>
......
/*
* Copyright (C) 2019 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
* Author: Guido Günther <agx@sigxcpu.org>
*/
#define G_LOG_DOMAIN "phosh-polkit-auth-agent"
#include "polkit-auth-agent.h"
#include "polkit-auth-prompt.h"
#include "shell.h"
#include "phosh-wayland.h"
#include <sys/types.h>
#include <pwd.h>
#define PHOSH_POLKIT_AUTH_DEBUG
#ifdef PHOSH_POLKIT_AUTH_DEBUG
#define auth_debug(...) g_debug(__VA_ARGS__)
#else
static void auth_debug(const gchar *str, ...) {}
#endif
/**
* SECTION:phosh-pokit-auth-agent
* @short_description: PolicyKit Authentication Agent
* @Title: PhoshPolkitAuthAgent
*
* The #PhoshPolkitAuthAgent is responsible for handling policy kit
* interaction so the shell can work as a authentication agent.
*/
typedef struct {
/* not holding ref */
PhoshPolkitAuthAgent *agent;
GCancellable *cancellable;
gulong handler_id;
/* copies */
gchar *action_id;
gchar *message;
gchar *icon_name;
PolkitDetails *details;
gchar *cookie;
GList *identities;
GTask *simple;
} AuthRequest;
struct _PhoshPolkitAuthAgent
{
PolkitAgentListener parent;
GList *scheduled_requests;
AuthRequest *current_request;
PhoshPolkitAuthPrompt *current_prompt;
gpointer handle;
};
G_DEFINE_TYPE (PhoshPolkitAuthAgent, phosh_polkit_auth_agent, POLKIT_AGENT_TYPE_LISTENER);
static void auth_request_complete (AuthRequest *request, gboolean dismissed);
static gboolean
agent_register (PhoshPolkitAuthAgent *self)
{
GError *err = NULL;
g_autoptr(PolkitSubject) subject;
subject = polkit_unix_session_new_for_process_sync (getpid (),
NULL, /* GCancellable* */
&err);
if (subject == NULL) {
g_warning("PolKit failed to properly get our session");
return FALSE;
}
/* FIXME: this blocks so we should do it async */
self->handle = polkit_agent_listener_register (POLKIT_AGENT_LISTENER (self),
POLKIT_AGENT_REGISTER_FLAGS_NONE,
subject,
NULL, /* use default object path */
NULL, /* GCancellable */
&err);
if (!self->handle) {
g_warning("Auth agent failed to register: %s", err->message);
return FALSE;
}
g_debug ("Polkit auth agent registered");
return TRUE;
}
static void
auth_request_free (AuthRequest *request)
{
g_free (request->action_id);
g_free (request->message);
g_free (request->icon_name);
g_object_unref (request->details);
g_free (request->cookie);
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
close_prompt (PhoshPolkitAuthAgent *self)
{
if (self->current_prompt) {
gtk_widget_destroy (GTK_WIDGET (self->current_prompt));
self->current_prompt = NULL;
}
}
static void
on_prompt_done (PhoshPolkitAuthPrompt *prompt, gboolean cancelled, AuthRequest *request)
{
g_return_if_fail (PHOSH_IS_POLKIT_AUTH_PROMPT (prompt));
close_prompt (request->agent);
auth_request_complete (request, cancelled);
}
static void
auth_request_initiate (AuthRequest *request)
{
PhoshWayland *wl = phosh_wayland_get_default ();
PhoshShell *shell = phosh_shell_get_default ();
PhoshMonitor *primary_monitor;
g_auto(GStrv) 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;
int ret;
uid = polkit_unix_user_get_uid (user);
ret = getpwuid_r (uid, &pwd, buf, sizeof (buf), &ppwd);
if (!ret) {
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: %d", uid, ret);
}
} 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_debug("New prompt for %s", request->message);
primary_monitor = phosh_shell_get_primary_monitor (shell);
/* We must not issue a new prompt when there's one alread */
g_return_if_fail (!request->agent->current_prompt);
request->agent->current_prompt = PHOSH_POLKIT_AUTH_PROMPT (
phosh_polkit_auth_prompt_new (
request->action_id,
request->message,
request->icon_name,
request->cookie,
user_names,
phosh_wayland_get_zwlr_layer_shell_v1(wl),
primary_monitor->wl_output));
g_signal_connect (request->agent->current_prompt,
"done",
G_CALLBACK (on_prompt_done),
request);
}
static void
maybe_process_next_request (PhoshPolkitAuthAgent *self)
{
auth_debug ("cur=%p len(scheduled)=%d",
self->current_request,
g_list_length (self->scheduled_requests));
if (self->current_request == NULL && self->scheduled_requests != NULL) {
AuthRequest *request;
request = self->scheduled_requests->data;
self->current_request = request;
self->scheduled_requests = g_list_remove (self->scheduled_requests, request);
auth_debug ("initiating %s cookie %s", request->action_id, request->cookie);
auth_request_initiate (request);
}
}
static void
auth_request_complete (AuthRequest *request, gboolean dismissed)
{
PhoshPolkitAuthAgent *self = request->agent;
gboolean is_current = self->current_request == request;
auth_debug ("completing %s %s cookie %s", is_current ? "current" : "scheduled",
request->action_id, request->cookie);
if (!is_current)
self->scheduled_requests = g_list_remove (self->scheduled_requests, request);
g_cancellable_disconnect (request->cancellable, request->handler_id);
if (dismissed) {
g_task_return_new_error (request->simple,
POLKIT_ERROR,
POLKIT_ERROR_CANCELLED,
_("Authentication dialog was dismissed by the user"));
} else {
g_task_return_boolean (request->simple, TRUE);
}
auth_request_free (request);
if (is_current) {
self->current_request = NULL;
maybe_process_next_request (self);
}
}
static gboolean
handle_cancelled_in_idle (gpointer user_data)
{
AuthRequest *request = user_data;
auth_debug ("cancelled %s cookie %s", request->action_id, request->cookie);
if (request == request->agent->current_request)
close_prompt (request->agent);
auth_request_complete (request, FALSE);
return FALSE;
}
/*
* on_request_canelled:
*
* This happens when the application requesting authentication
* is closed.
*/
static void
on_request_cancelled (GCancellable *cancellable,
gpointer user_data)
{
AuthRequest *request = user_data;
guint id;
/*
* post-pone to idle to handle GCancellable deadlock in
* https://bugzilla.gnome.org/show_bug.cgi?id=642968
* https://gitlab.gnome.org/GNOME/glib/issues/740
*/
id = g_idle_add (handle_cancelled_in_idle, request);
g_source_set_name_by_id (id, "[phosh] handle_cancelled_in_idle");
}
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)
{
PhoshPolkitAuthAgent *self = PHOSH_POLKIT_AUTH_AGENT (listener);
AuthRequest *request;
request = g_new0 (AuthRequest, 1);
request->agent = self;
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_task_new (listener, NULL, callback, user_data);
request->cancellable = cancellable;
request->handler_id = g_cancellable_connect (request->cancellable,
G_CALLBACK (on_request_cancelled),
request,
NULL); /* GDestroyNotify for request */
auth_debug ("scheduling %s cookie %s", request->action_id, request->cookie);
self->scheduled_requests = g_list_append (self->scheduled_requests, request);
maybe_process_next_request (self);
}
static gboolean
initiate_authentication_finish (PolkitAgentListener *listener,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
phosh_polkit_auth_agent_constructed (GObject *object)
{
PhoshPolkitAuthAgent *self = PHOSH_POLKIT_AUTH_AGENT (object);
G_OBJECT_CLASS (phosh_polkit_auth_agent_parent_class)->constructed (object);
if (agent_register (self))
g_return_if_fail (self->handle);
}
static void
auth_request_dismiss (AuthRequest *request)
{
auth_request_complete (request, TRUE);
}
static void
phosh_polkit_auth_agent_unregister (PhoshPolkitAuthAgent *self)
{
if (self->scheduled_requests != NULL) {
g_list_foreach (self->scheduled_requests, (GFunc)auth_request_dismiss, NULL);
self->scheduled_requests = NULL;
}
if (self->current_request != NULL)
auth_request_dismiss (self->current_request);
polkit_agent_listener_unregister (self->handle);
self->handle = NULL;
}
static void
phosh_polkit_auth_agent_dispose (GObject *object)
{
PhoshPolkitAuthAgent *self = PHOSH_POLKIT_AUTH_AGENT (object);
if (self->handle)
phosh_polkit_auth_agent_unregister (self);
G_OBJECT_CLASS (phosh_polkit_auth_agent_parent_class)->dispose (object);
}
static void
phosh_polkit_auth_agent_class_init (PhoshPolkitAuthAgentClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PolkitAgentListenerClass *listener_class;
object_class->constructed = phosh_polkit_auth_agent_constructed;
object_class->dispose = phosh_polkit_auth_agent_dispose;
listener_class = POLKIT_AGENT_LISTENER_CLASS (klass);
listener_class->initiate_authentication = initiate_authentication;
listener_class->initiate_authentication_finish = initiate_authentication_finish;
}
static void
phosh_polkit_auth_agent_init (PhoshPolkitAuthAgent *self)
{
}
PhoshPolkitAuthAgent *
phosh_polkit_auth_agent_new (void)
{
return g_object_new (PHOSH_TYPE_POLKIT_AUTH_AGENT, NULL);
}
/*
* Copyright (C) 2019 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
* Author: Guido Günther <agx@sigxcpu.org>
*/
#pragma once
#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE
#include <polkitagent/polkitagent.h>
#include <glib-object.h>
G_BEGIN_DECLS
/* libpolkit lacks these */
G_DEFINE_AUTOPTR_CLEANUP_FUNC(PolkitSubject, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(PolkitAgentListener, g_object_unref)
#define PHOSH_TYPE_POLKIT_AUTH_AGENT (phosh_polkit_auth_agent_get_type())
G_DECLARE_FINAL_TYPE (PhoshPolkitAuthAgent, phosh_polkit_auth_agent, PHOSH, POLKIT_AUTH_AGENT, PolkitAgentListener)
PhoshPolkitAuthAgent * phosh_polkit_auth_agent_new (void);
void phosh_polkit_authentication_agent_register (PhoshPolkitAuthAgent *agent,
GError **error);
G_END_DECLS
/*
* Copyright (C) 2019 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
* Author: Guido Günther <agx@sigxcpu.org>
*
* Based on gnome-shell's shell-keyring-prompt.c
* Author: Stef Walter <stefw@gnome.org>
*/
#define G_LOG_DOMAIN "phosh-polkit-auth-prompt"
#include "config.h"
#include "polkit-auth-prompt.h"
#define GCR_API_SUBJECT_TO_CHANGE
#include <gcr/gcr.h>
#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE
#include <polkitagent/polkitagent.h>
#include <glib/gi18n.h>
/**
* SECTION:phosh-polkit-auth-prompt
* @short_description: A modal prompt for policy kit authentication
* @Title: PhoshPolkitAuthPrompt
*
* The #PhoshPolkitAuthPrompt is used to ask policy kit authentication.
* This handles the interaction with #PolkitAgentSession.
*/
enum {
PROP_0,
PROP_ACTION_ID,
PROP_COOKIE,
PROP_MESSAGE,
PROP_ICON_NAME,
PROP_USER_NAMES,
PROP_LAST_PROP,
};
static GParamSpec *props[PROP_LAST_PROP];
enum {
DONE,
N_SIGNALS
};
static guint signals[N_SIGNALS] = { 0 };
struct _PhoshPolkitAuthPrompt
{
PhoshLayerSurface parent;
GtkWidget *lbl_message;
GtkWidget *lbl_user_name;
GtkWidget *lbl_password;
GtkWidget *lbl_info;
GtkWidget *img_icon;
GtkWidget *btn_authenticate;
GtkWidget *spinner_authenticate;
GtkWidget *btn_cancel;
GtkWidget *entry_password;
GtkEntryBuffer *password_buffer;
gchar *action_id;
gchar *message;
gchar *icon_name;
gchar *cookie;
GStrv user_names;
PolkitIdentity *identity;
PolkitAgentSession *session;
gboolean done_emitted;
};
G_DEFINE_TYPE(PhoshPolkitAuthPrompt, phosh_polkit_auth_prompt, PHOSH_TYPE_LAYER_SURFACE);
static void phosh_polkit_auth_prompt_initiate (PhoshPolkitAuthPrompt *self);
static void
set_action_id (PhoshPolkitAuthPrompt *self, const gchar *action_id)
{
g_return_if_fail (PHOSH_IS_POLKIT_AUTH_PROMPT (self));
if (!g_strcmp0 (self->action_id, action_id))
return;
g_clear_pointer (&self->action_id, g_free);
self->action_id = g_strdup (action_id);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTION_ID]);
}
static void
set_cookie (PhoshPolkitAuthPrompt *self, const gchar *cookie)
{
g_return_if_fail (PHOSH_IS_POLKIT_AUTH_PROMPT (self));
if (!g_strcmp0 (self->cookie, cookie))
return;
g_clear_pointer (&self->cookie, g_free);
self->cookie = g_strdup (cookie);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_COOKIE]);
}
static void
set_message (PhoshPolkitAuthPrompt *self, const gchar *message)
{
g_return_if_fail (PHOSH_IS_POLKIT_AUTH_PROMPT (self));
if (!g_strcmp0 (self->message, message))
return;
g_clear_pointer (&self->message, g_free);
self->message = g_strdup (message);
gtk_label_set_label (GTK_LABEL (self->lbl_message), message ?: "");
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_MESSAGE]);
}
static void
set_icon_name (PhoshPolkitAuthPrompt *self, const gchar *icon_name)
{
g_return_if_fail (PHOSH_IS_POLKIT_AUTH_PROMPT (self));
if (!g_strcmp0 (self->icon_name, icon_name))
return;
g_clear_pointer (&self->icon_name, g_free);
self->icon_name = g_strdup (icon_name);
gtk_image_set_from_icon_name (GTK_IMAGE (self->img_icon),
(icon_name && strlen(icon_name)) ? icon_name : "dialog-password",
GTK_ICON_SIZE_DIALOG);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]);
}
static void
set_user_names (PhoshPolkitAuthPrompt *self, const GStrv user_names)
{