diff options
author | Zeeshan Ali (Khattak) <zeeshanak@gnome.org> | 2016-02-18 16:43:23 +0000 |
---|---|---|
committer | Zeeshan Ali (Khattak) <zeeshanak@gnome.org> | 2016-03-03 16:07:58 +0000 |
commit | e7509321a58bf7bca07b668808d691aa3527c835 (patch) | |
tree | 50bda7028d252996ae81258dd053929873530459 | |
parent | d77ec8e8212434dac188ae43c16627363c215435 (diff) | |
download | gnome-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.c | 325 | ||||
-rw-r--r-- | panels/privacy/privacy.ui | 54 |
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> |