summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZeeshan Ali (Khattak) <zeeshanak@gnome.org>2016-02-18 16:43:23 +0000
committerZeeshan Ali (Khattak) <zeeshanak@gnome.org>2016-03-03 16:07:58 +0000
commite7509321a58bf7bca07b668808d691aa3527c835 (patch)
tree50bda7028d252996ae81258dd053929873530459
parentd77ec8e8212434dac188ae43c16627363c215435 (diff)
downloadgnome-control-center-e7509321a58bf7bca07b668808d691aa3527c835.tar.gz
privacy: Per-app location access control
Latest gnome-shell (3.19.91) now asks user if they'd want to allow the application to gain access to their location information when an application tries to access this information. The user's choice is saved in xdg-app's permission store and user can no longer can change their mind about this later on. Hence the need to provide these per-application controls in control-center. https://bugzilla.gnome.org/show_bug.cgi?id=761245
-rw-r--r--panels/privacy/cc-privacy-panel.c325
-rw-r--r--panels/privacy/privacy.ui54
2 files changed, 372 insertions, 7 deletions
diff --git a/panels/privacy/cc-privacy-panel.c b/panels/privacy/cc-privacy-panel.c
index da8516d17..0df5ccc60 100644
--- a/panels/privacy/cc-privacy-panel.c
+++ b/panels/privacy/cc-privacy-panel.c
@@ -21,6 +21,7 @@
#include "shell/list-box-helper.h"
#include "cc-privacy-panel.h"
#include "cc-privacy-resources.h"
+#include "cc-util.h"
#include <gio/gdesktopappinfo.h>
#include <glib/gi18n.h>
@@ -38,6 +39,9 @@ CC_PANEL_REGISTER (CcPrivacyPanel, cc_privacy_panel)
#define REPORT_TECHNICAL_PROBLEMS "report-technical-problems"
#define LOCATION_ENABLED "enabled"
+#define APP_PERMISSIONS_TABLE "gnome"
+#define APP_PERMISSIONS_ID "geolocation"
+
struct _CcPrivacyPanelPrivate
{
GtkBuilder *builder;
@@ -48,6 +52,9 @@ struct _CcPrivacyPanelPrivate
GtkWidget *trash_dialog;
GtkWidget *software_dialog;
GtkWidget *list_box;
+ GtkWidget *location_apps_list_box;
+ GtkWidget *location_apps_label;
+ GtkWidget *location_apps_frame;
GSettings *lockdown_settings;
GSettings *lock_settings;
@@ -62,6 +69,10 @@ struct _CcPrivacyPanelPrivate
GCancellable *cancellable;
GDBusProxy *gclue_manager;
+ GDBusProxy *perm_store;
+ GVariant *location_apps_perms;
+
+ GtkSizeGroup *location_icon_size_group;
};
static char *
@@ -417,11 +428,24 @@ update_location_label (CcPrivacyPanel *self)
}
static void
+update_location_apps_sensitivity (CcPrivacyPanel *self)
+{
+ gboolean enabled;
+
+ enabled = g_settings_get_boolean (self->priv->location_settings,
+ LOCATION_ENABLED);
+ gtk_widget_set_sensitive (self->priv->location_apps_frame,
+ enabled);
+ gtk_widget_set_sensitive (self->priv->location_apps_label,
+ enabled);
+}
+
+static void
on_location_setting_changed (GSettings *settings,
gchar *key,
gpointer user_data)
{
- update_location_label (user_data);
+ update_location_apps_sensitivity (user_data);
}
static void
@@ -462,6 +486,285 @@ on_gclue_manager_ready (GObject *source_object,
update_location_label (self);
}
+typedef struct
+{
+ CcPrivacyPanel *self;
+ GtkWidget *widget;
+ gchar *app_id;
+ gboolean changing_state;
+ gboolean pending_state;
+} LocationAppStateData;
+
+static void
+location_app_state_data_free (LocationAppStateData *data)
+{
+ g_free (data->app_id);
+ g_slice_free (LocationAppStateData, data);
+}
+
+static void
+on_perm_store_set_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ LocationAppStateData *data;
+ GVariant *results;
+ GError *error = NULL;
+
+ results = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+ res,
+ &error);
+ if (results == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to store permissions: %s", error->message);
+ g_error_free (error);
+
+ return;
+ }
+ g_variant_unref (results);
+
+ data = (LocationAppStateData *) user_data;
+ data->changing_state = FALSE;
+ gtk_switch_set_state (GTK_SWITCH (data->widget), data->pending_state);
+}
+
+static gboolean
+on_location_app_state_set (GtkSwitch *widget,
+ gboolean state,
+ gpointer user_data)
+{
+ LocationAppStateData *data = (LocationAppStateData *) user_data;
+ CcPrivacyPanel *self = data->self;
+ GVariant *params, *in_dict, *out_data;
+ GVariantIter iter;
+ gchar *key;
+ gchar **value;
+ GVariantBuilder builder;
+
+ if (data->changing_state)
+ return TRUE;
+
+ data->changing_state = TRUE;
+ data->pending_state = state;
+
+ in_dict = g_variant_get_child_value (self->priv->location_apps_perms, 0);
+ g_variant_iter_init (&iter, in_dict);
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+ while (g_variant_iter_loop (&iter, "{s^as}", &key, &value))
+ {
+ gchar *tmp = NULL;
+
+ if (g_strv_length (value) < 2)
+ /* It's OK to drop the entry if it's not in expected format */
+ continue;
+
+ if (g_strcmp0 (data->app_id, key) == 0)
+ {
+ tmp = value[0];
+ value[0] = state ? "EXACT" : "NONE";
+ }
+
+ g_variant_builder_add (&builder, "{s^as}", key, value);
+
+ if (tmp != NULL)
+ value[0] = tmp;
+ }
+ g_variant_unref (in_dict);
+
+ out_data = g_variant_get_child_value (self->priv->location_apps_perms, 1);
+ params = g_variant_new ("(sbsa{sas}v)",
+ APP_PERMISSIONS_TABLE,
+ TRUE,
+ APP_PERMISSIONS_ID,
+ &builder,
+ out_data);
+ g_variant_unref (out_data);
+
+ g_dbus_proxy_call (self->priv->perm_store,
+ "Set",
+ params,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ self->priv->cancellable,
+ on_perm_store_set_done,
+ data);
+
+ return TRUE;
+}
+
+static void
+add_location_app (CcPrivacyPanel *self,
+ const gchar *app_id,
+ gboolean enabled,
+ gint64 last_used)
+{
+ GDesktopAppInfo *app_info;
+ char *desktop_id;
+ GtkWidget *box, *row, *w;
+ GIcon *icon;
+ GDateTime *t;
+ char *last_used_str;
+ LocationAppStateData *data;
+
+ desktop_id = g_strdup_printf ("%s.desktop", app_id);
+ app_info = g_desktop_app_info_new (desktop_id);
+ g_free (desktop_id);
+ if (app_info == NULL)
+ return;
+
+ row = gtk_list_box_row_new ();
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_set_margin_top (box, 6);
+ gtk_widget_set_margin_bottom (box, 6);
+ gtk_container_add (GTK_CONTAINER (row), box);
+ gtk_widget_set_hexpand (box, TRUE);
+ gtk_container_add (GTK_CONTAINER (self->priv->location_apps_list_box), row);
+
+ icon = g_app_info_get_icon (G_APP_INFO (app_info));
+ w = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_LARGE_TOOLBAR);
+ gtk_widget_set_margin_start (w, 12);
+ gtk_widget_set_halign (w, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
+ gtk_size_group_add_widget (self->priv->location_icon_size_group, w);
+ gtk_box_pack_start (GTK_BOX (box), w, FALSE, FALSE, 0);
+
+ w = gtk_label_new (g_app_info_get_name (G_APP_INFO (app_info)));
+ gtk_widget_set_margin_start (w, 12);
+ gtk_widget_set_margin_end (w, 12);
+ gtk_widget_set_halign (w, GTK_ALIGN_START);
+ gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
+ gtk_label_set_xalign (GTK_LABEL (w), 0);
+ gtk_box_pack_start (GTK_BOX (box), w, FALSE, FALSE, 0);
+
+ t = g_date_time_new_from_unix_utc (last_used);
+ last_used_str = cc_util_get_smart_date (t);
+ w = gtk_label_new (last_used_str);
+ g_free (last_used_str);
+ gtk_style_context_add_class (gtk_widget_get_style_context (w), "dim-label");
+ gtk_widget_set_margin_start (w, 12);
+ gtk_widget_set_margin_end (w, 12);
+ gtk_widget_set_halign (w, GTK_ALIGN_END);
+ gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
+ gtk_box_pack_start (GTK_BOX (box), w, TRUE, TRUE, 0);
+
+ w = gtk_switch_new ();
+ gtk_switch_set_active (GTK_SWITCH (w), enabled);
+ gtk_widget_set_margin_end (w, 12);
+ gtk_widget_set_halign (w, GTK_ALIGN_END);
+ gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
+ gtk_box_pack_start (GTK_BOX (box), w, FALSE, FALSE, 0);
+
+ data = g_slice_new (LocationAppStateData);
+ data->self = self;
+ data->app_id = g_strdup (app_id);
+ data->widget = w;
+ data->changing_state = FALSE;
+ g_signal_connect_data (G_OBJECT (w),
+ "state-set",
+ G_CALLBACK (on_location_app_state_set),
+ data,
+ (GClosureNotify) location_app_state_data_free,
+ 0);
+
+ gtk_widget_show_all (row);
+}
+
+static void
+on_perm_store_lookup_done(GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CcPrivacyPanel *self;
+ CcPrivacyPanelPrivate *priv;
+ GVariant *permissions, *dict;
+ GVariantIter iter;
+ gchar *key;
+ gchar **value;
+ GList *children;
+ GError *error = NULL;
+
+ permissions = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+ res,
+ &error);
+ if (permissions == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed fetch permissions from xdg-app permission store: %s",
+ error->message);
+ g_error_free (error);
+
+ return;
+ }
+ self = user_data;
+ priv = self->priv;
+
+ priv->location_apps_perms = permissions;
+ dict = g_variant_get_child_value (permissions, 0);
+ g_variant_iter_init (&iter, dict);
+ while (g_variant_iter_loop (&iter, "{s^as}", &key, &value))
+ {
+ gboolean enabled;
+ gint64 last_used;
+
+ if (g_strv_length (value) < 2)
+ {
+ g_debug ("Permissions for %s in incorrect format, ignoring..", key);
+ continue;
+ }
+
+ enabled = (g_strcmp0 (value[0], "NONE") != 0);
+ last_used = g_ascii_strtoll (value[1], NULL, 10);
+
+ add_location_app (self, key, enabled, last_used);
+ }
+ g_variant_unref (dict);
+
+ children = gtk_container_get_children (GTK_CONTAINER (priv->location_apps_list_box));
+ if (g_list_length (children) > 0)
+ {
+ gtk_widget_set_visible (priv->location_apps_label, TRUE);
+ gtk_widget_set_visible (priv->location_apps_frame, TRUE);
+ }
+ g_list_free (children);
+}
+
+static void
+on_perm_store_ready (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CcPrivacyPanel *self;
+ GDBusProxy *proxy;
+ GVariant *params;
+ GError *error = NULL;
+
+ proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+ if (proxy == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to connect to xdg-app permission store: %s",
+ error->message);
+ g_error_free (error);
+
+ return;
+ }
+ self = user_data;
+ self->priv->perm_store = proxy;
+
+ params = g_variant_new ("(ss)",
+ APP_PERMISSIONS_TABLE,
+ APP_PERMISSIONS_ID);
+ g_dbus_proxy_call (self->priv->perm_store,
+ "Lookup",
+ params,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ self->priv->cancellable,
+ on_perm_store_lookup_done,
+ self);
+}
+
static void
add_location (CcPrivacyPanel *self)
{
@@ -499,6 +802,16 @@ add_location (CcPrivacyPanel *self)
priv->cancellable,
on_gclue_manager_ready,
self);
+
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.XdgApp",
+ "/org/freedesktop/XdgApp/PermissionStore",
+ "org.freedesktop.XdgApp.PermissionStore",
+ priv->cancellable,
+ on_perm_store_ready,
+ self);
}
static void
@@ -902,6 +1215,9 @@ cc_privacy_panel_finalize (GObject *object)
g_clear_object (&priv->location_settings);
g_clear_object (&priv->gclue_manager);
g_clear_object (&priv->cancellable);
+ g_clear_object (&priv->perm_store);
+ g_clear_object (&priv->location_icon_size_group);
+ g_clear_pointer (&priv->location_apps_perms, g_variant_unref);
G_OBJECT_CLASS (cc_privacy_panel_parent_class)->finalize (object);
}
@@ -974,6 +1290,13 @@ cc_privacy_panel_init (CcPrivacyPanel *self)
gtk_container_add (GTK_CONTAINER (frame), widget);
self->priv->list_box = widget;
gtk_widget_show (widget);
+ self->priv->location_apps_list_box = WID ("location_apps_list_box");
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->priv->location_apps_list_box),
+ cc_list_box_update_header_func,
+ NULL, NULL);
+ self->priv->location_apps_frame = WID ("location_apps_frame");
+ self->priv->location_apps_label = WID ("location_apps_label");
+ self->priv->location_icon_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH);
g_signal_connect_swapped (widget, "row-activated",
G_CALLBACK (activate_row), self);
diff --git a/panels/privacy/privacy.ui b/panels/privacy/privacy.ui
index 804157cc3..9bc358027 100644
--- a/panels/privacy/privacy.ui
+++ b/panels/privacy/privacy.ui
@@ -725,23 +725,24 @@ All the information we collect is made anonymous, and we will never share your d
<object class="GtkBox" id="location-vbox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
+ <property name="margin_start">12</property>
+ <property name="margin_end">12</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="location_description_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_start">12</property>
- <property name="margin_end">12</property>
- <property name="margin_top">6</property>
+ <property name="margin_top">12</property>
<property name="margin_bottom">12</property>
<property name="label" translatable="yes">Location services allow applications to determine your geographical position. Accuracy is increased by enabling WiFi and GPS.</property>
<property name="wrap">True</property>
<property name="max_width_chars">50</property>
+ <property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">1</property>
+ <property name="position">0</property>
</packing>
</child>
@@ -750,8 +751,6 @@ All the information we collect is made anonymous, and we will never share your d
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="shadow_type">in</property>
- <property name="margin_top">12</property>
- <property name="margin_bottom">12</property>
<child>
<object class="GtkListBox" id="location_listbox">
@@ -815,10 +814,53 @@ All the information we collect is made anonymous, and we will never share your d
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="location_apps_label">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">24</property>
+ <property name="margin_bottom">12</property>
+ <property name="halign">start</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Applications</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
+ <child>
+ <object class="GtkFrame" id="location_apps_frame">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="shadow_type">in</property>
+ <property name="margin_bottom">12</property>
+
+ <child>
+ <object class="GtkListBox" id="location_apps_list_box">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="selection_mode">none</property>
+ </object>
+ </child>
+
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+
</object>
</child>
</object>