/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright © 2011 – 2017 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see . */ #include "config.h" #include #include "goaprovider.h" #include "goaprovider-priv.h" #include "goaproviderfactory.h" #include "goaexchangeprovider.h" #include "goagoogleprovider.h" #include "goafacebookprovider.h" #include "goaimapsmtpprovider.h" #include "goaowncloudprovider.h" #include "goaflickrprovider.h" #include "goafoursquareprovider.h" #include "goawindowsliveprovider.h" #include "goatelepathyfactory.h" #include "goapocketprovider.h" #include "goamediaserverprovider.h" #include "goalastfmprovider.h" #include "goatodoistprovider.h" #ifdef GOA_KERBEROS_ENABLED #include "goakerberosprovider.h" #endif #include "goautils.h" /** * SECTION:goaprovider * @title: GoaProvider * @short_description: Abstract base class for providers * * #GoaProvider is the base type for all providers. */ enum { PROP_0, PROP_PRESEED_DATA, NUM_PROPERTIES }; static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; static gboolean goa_provider_ensure_credentials_sync_real (GoaProvider *self, GoaObject *object, gint *out_expires_in, GCancellable *cancellable, GError **error); static gboolean goa_provider_build_object_real (GoaProvider *self, GoaObjectSkeleton *object, GKeyFile *key_file, const gchar *group, GDBusConnection *connection, gboolean just_added, GError **error); static guint goa_provider_get_credentials_generation_real (GoaProvider *self); static GIcon *goa_provider_get_provider_icon_real (GoaProvider *self, GoaObject *object); static void goa_provider_initialize_real (GoaProvider *self); static void goa_provider_remove_account_real (GoaProvider *self, GoaObject *object, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); static gboolean goa_provider_remove_account_finish_real (GoaProvider *self, GAsyncResult *res, GError **error); static void goa_provider_show_account_real (GoaProvider *provider, GoaClient *client, GoaObject *object, GtkBox *vbox, GtkGrid *dummy1, GtkGrid *dummy2); G_DEFINE_ABSTRACT_TYPE (GoaProvider, goa_provider, G_TYPE_OBJECT); static struct { GoaProviderFeatures feature; const gchar *property; const gchar *blurb; } provider_features_info[] = { /* The order in which the features are listed is * important because it affects the order in which they are * displayed in the show_account() UI */ { .feature = GOA_PROVIDER_FEATURE_MAIL, .property = "mail-disabled", .blurb = N_("_Mail"), }, { .feature = GOA_PROVIDER_FEATURE_CALENDAR, .property = "calendar-disabled", .blurb = N_("Cale_ndar"), }, { .feature = GOA_PROVIDER_FEATURE_CONTACTS, .property = "contacts-disabled", .blurb = N_("_Contacts"), }, { .feature = GOA_PROVIDER_FEATURE_CHAT, .property = "chat-disabled", .blurb = N_("C_hat"), }, { .feature = GOA_PROVIDER_FEATURE_DOCUMENTS, .property = "documents-disabled", .blurb = N_("_Documents"), }, { .feature = GOA_PROVIDER_FEATURE_MUSIC, .property = "music-disabled", .blurb = N_("M_usic"), }, { .feature = GOA_PROVIDER_FEATURE_PHOTOS, .property = "photos-disabled", .blurb = N_("_Photos"), }, { .feature = GOA_PROVIDER_FEATURE_FILES, .property = "files-disabled", .blurb = N_("_Files"), }, { .feature = GOA_PROVIDER_FEATURE_TICKETING, .property = "ticketing-disabled", .blurb = N_("Network _Resources"), }, { .feature = GOA_PROVIDER_FEATURE_READ_LATER, .property = "read-later-disabled", .blurb = N_("_Read Later"), }, { .feature = GOA_PROVIDER_FEATURE_PRINTERS, .property = "printers-disabled", .blurb = N_("Prin_ters"), }, { .feature = GOA_PROVIDER_FEATURE_MAPS, .property = "maps-disabled", .blurb = N_("_Maps"), }, { .feature = GOA_PROVIDER_FEATURE_TODO, .property = "todo-disabled", .blurb = N_("T_o Do"), }, { .feature = GOA_PROVIDER_FEATURE_INVALID, .property = NULL, .blurb = NULL, } }; static void goa_provider_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_PRESEED_DATA: g_value_set_variant (value, NULL); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void goa_provider_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_PRESEED_DATA: break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void goa_provider_init (GoaProvider *self) { } static void goa_provider_class_init (GoaProviderClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = goa_provider_set_property; object_class->get_property = goa_provider_get_property; klass->build_object = goa_provider_build_object_real; klass->ensure_credentials_sync = goa_provider_ensure_credentials_sync_real; klass->get_credentials_generation = goa_provider_get_credentials_generation_real; klass->get_provider_icon = goa_provider_get_provider_icon_real; klass->initialize = goa_provider_initialize_real; klass->remove_account = goa_provider_remove_account_real; klass->remove_account_finish = goa_provider_remove_account_finish_real; klass->show_account = goa_provider_show_account_real; /** * GoaProvider:preseed-data * * An #GVariant of type a{sv} storing any information already collected that * can be useful when creating a new account. For instance, this can be useful * to reuse the HTTP cookies from an existing browser session to skip the * prompt for username and password in the OAuth2-based providers by passing * a #GVariant with the following contents: * * * * { * "cookies": [ * { * "domain": "example.com", * "name": "LSID", * "value": "asdfasdfasdf" * }, * { * "domain": "accounts.example.com", * "name": "SSID", * "value": "asdfasdfasdf" * } * ] * } * * * * Unknown or unsupported keys will be ignored by providers. * * Deprecated: 3.28: This property does nothing. */ properties[PROP_PRESEED_DATA] = g_param_spec_variant ("preseed-data", "Collected data to pre-seed account creation", "A a{sv} #GVariant containing a provider-type specific set of data that" "can be useful during account creation (eg. http cookies from an existing" "browser session or the entrypoint url for self-hosted services).", G_VARIANT_TYPE_VARDICT, NULL, G_PARAM_DEPRECATED | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); } /** * goa_provider_get_provider_type: * @self: A #GoaProvider. * * Gets the type of @self. * * This is a pure virtual method - a subclass must provide an * implementation. * * Returns: (transfer none): A string owned by @self, do not free. */ const gchar * goa_provider_get_provider_type (GoaProvider *self) { g_return_val_if_fail (GOA_IS_PROVIDER (self), NULL); return GOA_PROVIDER_GET_CLASS (self)->get_provider_type (self); } /** * goa_provider_get_provider_name: * @self: A #GoaProvider. * @object: (allow-none): A #GoaObject for an account. * * Gets a name for @self and @object that is suitable for display * in an user interface. The returned value may depend on @object (if * it's not %NULL) - for example, hosted accounts might return a * different name. * * This is a pure virtual method - a subclass must provide an * implementation. * * Returns: (transfer full): A string that should be freed with g_free(). */ gchar * goa_provider_get_provider_name (GoaProvider *self, GoaObject *object) { g_return_val_if_fail (GOA_IS_PROVIDER (self), NULL); return GOA_PROVIDER_GET_CLASS (self)->get_provider_name (self, object); } /** * goa_provider_get_provider_icon: * @self: A #GoaProvider. * @object: A #GoaObject for an account. * * Gets an icon for @self and @object that is suitable for display * in an user interface. The returned value may depend on @object - * for example, hosted accounts might return a different icon. * * This is a virtual method with a default implementation that returns * a #GThemedIcon with fallbacks constructed from the name * goa-account-TYPE where TYPE * is the return value of goa_provider_get_provider_type(). * * Returns: (transfer full): An icon that should be freed with g_object_unref(). */ GIcon * goa_provider_get_provider_icon (GoaProvider *self, GoaObject *object) { g_return_val_if_fail (GOA_IS_PROVIDER (self), NULL); return GOA_PROVIDER_GET_CLASS (self)->get_provider_icon (self, object); } static GIcon * goa_provider_get_provider_icon_real (GoaProvider *self, GoaObject *object) { GIcon *ret; gchar *s; s = g_strdup_printf ("goa-account-%s", goa_provider_get_provider_type (self)); ret = g_themed_icon_new_with_default_fallbacks (s); g_free (s); return ret; } /** * goa_provider_get_provider_group: * @self: A #GoaProvider. * * Gets the group to which @self belongs that is suitable for * organizing the providers while displaying them in an user * interface. * * This is a pure virtual method - a subclass must provide an * implementation. * * Returns: A #GoaProviderGroup. * * Since: 3.8 * * Deprecated: 3.10: Use goa_provider_get_provider_features() instead. */ GoaProviderGroup goa_provider_get_provider_group (GoaProvider *self) { g_return_val_if_fail (GOA_IS_PROVIDER (self), GOA_PROVIDER_GROUP_INVALID); return GOA_PROVIDER_GET_CLASS (self)->get_provider_group (self); } /** * goa_provider_get_provider_features: * @self: A #GoaProvider. * * Get the features bitmask (eg. %GOA_PROVIDER_FEATURE_CHAT|%GOA_PROVIDER_FEATURE_CONTACTS) * supported by the provider. * * Returns: The #GoaProviderFeatures bitmask with the provided features. * * Since: 3.10 */ GoaProviderFeatures goa_provider_get_provider_features (GoaProvider *self) { g_return_val_if_fail (GOA_IS_PROVIDER (self), GOA_PROVIDER_FEATURE_INVALID); g_return_val_if_fail (GOA_PROVIDER_GET_CLASS (self)->get_provider_features != NULL, GOA_PROVIDER_FEATURE_INVALID); return GOA_PROVIDER_GET_CLASS (self)->get_provider_features (self); } /* ---------------------------------------------------------------------------------------------------- */ void goa_provider_initialize (GoaProvider *self) { g_return_if_fail (GOA_IS_PROVIDER (self)); GOA_PROVIDER_GET_CLASS (self)->initialize (self); } static void goa_provider_initialize_real (GoaProvider *self) { } /* ---------------------------------------------------------------------------------------------------- */ /** * goa_provider_add_account: * @self: A #GoaProvider. * @client: A #GoaClient. * @dialog: A #GtkDialog. * @vbox: A vertically oriented #GtkBox to put content in. * @error: Return location for error or %NULL. * * This method brings up the user interface necessary to create a new * account on @client of the type for @self, interacts with the * user to get all information needed and creates the account. * * The passed in @dialog widget is guaranteed to be visible with @vbox * being empty and the only visible widget in @dialog's content * area. The dialog has exactly one action widget, a cancel button * with response id GTK_RESPONSE_CANCEL. Implementations are free to * add additional action widgets, as needed. * * If an account was successfully created, a #GoaObject for the * created account is returned. If @dialog is dismissed, %NULL is * returned and @error is set to %GOA_ERROR_DIALOG_DISMISSED. If an * account couldn't be created then @error is set. In some cases, * for example, when the credentials could not be stored in the * keyring, a #GoaObject can be returned even if @error is set. * * The caller will always show an error dialog if @error is set unless * the error is %GOA_ERROR_DIALOG_DISMISSED. * * Implementations should run the default main loop while * interacting with the user and may do so using e.g. gtk_dialog_run() * on @dialog. * * This is a pure virtual method - a subclass must provide an * implementation. * * Returns: The #GoaObject for the created account (must be relased * with g_object_unref()) or %NULL if @error is set. */ GoaObject * goa_provider_add_account (GoaProvider *self, GoaClient *client, GtkDialog *dialog, GtkBox *vbox, GError **error) { GoaObject *ret; g_return_val_if_fail (GOA_IS_PROVIDER (self), NULL); g_return_val_if_fail (GOA_IS_CLIENT (client), NULL); g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); ret = GOA_PROVIDER_GET_CLASS (self)->add_account (self, client, dialog, vbox, error); g_warn_if_fail ((ret == NULL && (error == NULL || *error != NULL)) || GOA_IS_OBJECT (ret)); return ret; } /* ---------------------------------------------------------------------------------------------------- */ /** * goa_provider_refresh_account: * @self: A #GoaProvider. * @client: A #GoaClient. * @object: A #GoaObject with a #GoaAccount interface. * @parent: (allow-none): Transient parent of dialogs or %NULL. * @error: Return location for error or %NULL. * * This method brings up the user interface necessary for refreshing * the credentials for the account specified by @object. This * typically involves having the user log in to the account again. * * Implementations should use @parent (unless %NULL) as the transient * parent of any created windows/dialogs. * * Implementations should run the default main loop while * interacting with the user. * * This is a pure virtual method - a subclass must provide an * implementation. * * Returns: %TRUE if the account has been refreshed, %FALSE if @error * is set. */ gboolean goa_provider_refresh_account (GoaProvider *self, GoaClient *client, GoaObject *object, GtkWindow *parent, GError **error) { g_return_val_if_fail (GOA_IS_PROVIDER (self), FALSE); g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE); g_return_val_if_fail (GOA_IS_OBJECT (object) && goa_object_peek_account (object) != NULL, FALSE); g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); return GOA_PROVIDER_GET_CLASS (self)->refresh_account (self, client, object, parent, error); } /* ---------------------------------------------------------------------------------------------------- */ /** * goa_provider_show_account: * @self: A #GoaProvider. * @client: A #GoaClient. * @object: A #GoaObject with a #GoaAccount interface. * @vbox: A vertically oriented #GtkBox to put content in. * @grid: A #GtkGrid to put content in. * @dummy: Unused. * * Method used to add widgets in the control panel for the account * represented by @object. * * This is a virtual method where the default implementation adds * one GtkSwitch per service supported by the provider (as reported * by goa_provider_get_provider_features()). */ void goa_provider_show_account (GoaProvider *self, GoaClient *client, GoaObject *object, GtkBox *vbox, GtkGrid *dummy1, GtkGrid *dummy2) { g_return_if_fail (GOA_IS_PROVIDER (self)); g_return_if_fail (GOA_IS_CLIENT (client)); g_return_if_fail (GOA_IS_OBJECT (object) && goa_object_peek_account (object) != NULL); g_return_if_fail (GTK_IS_BOX (vbox)); GOA_PROVIDER_GET_CLASS (self)->show_account (self, client, object, vbox, dummy1, dummy2); } static void goa_provider_show_account_real (GoaProvider *provider, GoaClient *client, GoaObject *object, GtkBox *vbox, GtkGrid *dummy1, GtkGrid *dummy2) { GoaProviderFeatures features; GtkWidget *grid; gint row; guint i; const char *label; row = 0; goa_utils_account_add_attention_needed (client, object, provider, vbox); grid = gtk_grid_new (); gtk_widget_set_halign (grid, GTK_ALIGN_CENTER); gtk_widget_set_hexpand (grid, TRUE); gtk_widget_set_margin_end (grid, 72); gtk_widget_set_margin_start (grid, 72); gtk_widget_set_margin_top (grid, 24); gtk_grid_set_column_spacing (GTK_GRID (grid), 12); gtk_grid_set_row_spacing (GTK_GRID (grid), 6); gtk_container_add (GTK_CONTAINER (vbox), grid); goa_utils_account_add_header (object, GTK_GRID (grid), row++); features = goa_provider_get_provider_features (provider); /* Translators: This is a label for a series of * options switches. For example: “Use for Mail”. */ label = _("Use for"); for (i = 0; provider_features_info[i].property != NULL; i++) { if ((features & provider_features_info[i].feature) != 0) { goa_util_add_row_switch_from_keyfile_with_blurb (GTK_GRID (grid), row++, object, label, provider_features_info[i].property, _(provider_features_info[i].blurb)); label = NULL; } } } /* ---------------------------------------------------------------------------------------------------- */ /** * goa_provider_build_object: * @self: A #GoaProvider. * @object: The #GoaObjectSkeleton that is being built. * @key_file: The #GKeyFile with configuation data. * @group: The group in @key_file to get data from. * @connection: The #GDBusConnection used by the daemon to connect to the message bus. * @just_added: Whether the account was newly created or being updated. * @error: Return location for error or %NULL. * * This method is called when construction account #GoaObject * from configuration data - it basically provides a way to add * provider-specific information. * * The passed in @object will have a #GoaAccount interface * set. Implementations should validate and use data from @key_file to * add more interfaces to @object. * * Note that this may be called on already exported objects - for * example on configuration files reload. * * This is a pure virtual method - a subclass must provide an * implementation. * * Returns: %TRUE if data was valid, %FALSE if @error is set. */ gboolean goa_provider_build_object (GoaProvider *self, GoaObjectSkeleton *object, GKeyFile *key_file, const gchar *group, GDBusConnection *connection, gboolean just_added, GError **error) { g_return_val_if_fail (GOA_IS_PROVIDER (self), FALSE); g_return_val_if_fail (GOA_IS_OBJECT_SKELETON (object) && goa_object_peek_account (GOA_OBJECT (object)) != NULL, FALSE); g_return_val_if_fail (key_file != NULL, FALSE); g_return_val_if_fail (group != NULL, FALSE); g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); return GOA_PROVIDER_GET_CLASS (self)->build_object (self, object, key_file, group, connection, just_added, error); } /* ---------------------------------------------------------------------------------------------------- */ typedef struct { GoaObject *object; gint expires_in; } EnsureCredentialsData; static EnsureCredentialsData * ensure_credentials_data_new (GoaObject *object) { EnsureCredentialsData *data; data = g_new0 (EnsureCredentialsData, 1); data->object = g_object_ref (object); return data; } static void ensure_credentials_data_free (EnsureCredentialsData *data) { g_object_unref (data->object); g_free (data); } static void ensure_credentials_in_thread_func (GTask *task, gpointer object, gpointer task_data, GCancellable *cancellable) { EnsureCredentialsData *data; GError *error; data = task_data; error = NULL; if (!goa_provider_ensure_credentials_sync (GOA_PROVIDER (object), data->object, &data->expires_in, cancellable, &error)) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); } /** * goa_provider_ensure_credentials: * @self: A #GoaProvider. * @object: A #GoaObject with a #GoaAccount interface. * @cancellable: (allow-none): A #GCancellable or %NULL. * @callback: The function to call when the request is satisfied. * @user_data: Pointer to pass to @callback. * * Ensures that credentials for @object are still valid. * * When the result is ready, @callback will be called in the the thread-default main * loop this function was called from. You can then call * goa_provider_ensure_credentials_finish() to get the result * of the operation. * * This is a virtual method where the default implementation simply * throws the %GOA_ERROR_NOT_SUPPORTED error. A subclass may provide * another implementation. */ void goa_provider_ensure_credentials (GoaProvider *self, GoaObject *object, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; g_return_if_fail (GOA_IS_PROVIDER (self)); g_return_if_fail (GOA_IS_OBJECT (object)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); task = g_task_new (self, cancellable, callback, user_data); g_task_set_task_data (task, ensure_credentials_data_new (object), (GDestroyNotify) ensure_credentials_data_free); g_task_set_source_tag (task, goa_provider_ensure_credentials); g_task_run_in_thread (task, ensure_credentials_in_thread_func); g_object_unref (task); } /** * goa_provider_ensure_credentials_finish: * @self: A #GoaProvider. * @out_expires_in: (out): Return location for how long the expired credentials are good for (0 if unknown) or %NULL. * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to goa_provider_ensure_credentials(). * @error: Return location for error or %NULL. * * Finishes an operation started with goa_provider_ensure_credentials(). * * Returns: %TRUE if the credentials for the passed #GoaObject are valid, %FALSE if @error is set. */ gboolean goa_provider_ensure_credentials_finish (GoaProvider *self, gint *out_expires_in, GAsyncResult *res, GError **error) { GTask *task; gboolean had_error; gboolean ret; EnsureCredentialsData *data; ret = FALSE; g_return_val_if_fail (GOA_IS_PROVIDER (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (g_task_is_valid (res, self), FALSE); task = G_TASK (res); g_return_val_if_fail (g_task_get_source_tag (task) == goa_provider_ensure_credentials, FALSE); /* Workaround for bgo#764163 */ had_error = g_task_had_error (task); ret = g_task_propagate_boolean (task, error); if (had_error) goto out; data = g_task_get_task_data (task); if (out_expires_in != NULL) *out_expires_in = data->expires_in; out: return ret; } /* ---------------------------------------------------------------------------------------------------- */ /** * goa_provider_ensure_credentials_sync: * @self: A #GoaProvider. * @object: A #GoaObject with a #GoaAccount interface. * @out_expires_in: (out): Return location for how long the expired credentials are good for (0 if unknown) or %NULL. * @cancellable: (allow-none): A #GCancellable or %NULL. * @error: Return location for error or %NULL. * * Like goa_provider_ensure_credentials() but blocks the * calling thread until an answer is received. * * Returns: %TRUE if the credentials for the passed #GoaObject are valid, %FALSE if @error is set. */ gboolean goa_provider_ensure_credentials_sync (GoaProvider *self, GoaObject *object, gint *out_expires_in, GCancellable *cancellable, GError **error) { GoaAccount *account = NULL; GoaProviderFeatures features; gboolean disabled = TRUE; gboolean ret = FALSE; guint i; g_return_val_if_fail (GOA_IS_PROVIDER (self), FALSE); g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); account = goa_object_get_account (object); g_return_val_if_fail (GOA_IS_ACCOUNT (account), FALSE); features = goa_provider_get_provider_features (self); for (i = 0; provider_features_info[i].property != NULL; i++) { if ((features & provider_features_info[i].feature) != 0) { gboolean feature_disabled; g_object_get (account, provider_features_info[i].property, &feature_disabled, NULL); disabled = disabled && feature_disabled; if (!disabled) break; } } if (disabled) { g_set_error_literal (error, GOA_ERROR, GOA_ERROR_NOT_SUPPORTED, _("Account is disabled")); goto out; } ret = GOA_PROVIDER_GET_CLASS (self)->ensure_credentials_sync (self, object, out_expires_in, cancellable, error); out: if (!ret && error != NULL && *error == NULL) { const gchar *provider_type; provider_type = goa_provider_get_provider_type (self); g_critical ("GoaProvider (%s) failed to set error correctly", provider_type); g_set_error_literal (error, GOA_ERROR, GOA_ERROR_NOT_AUTHORIZED, _("Unknown error")); } g_clear_object (&account); return ret; } /* ---------------------------------------------------------------------------------------------------- */ static gboolean goa_provider_ensure_credentials_sync_real (GoaProvider *self, GoaObject *object, gint *out_expires_in, GCancellable *cancellable, GError **error) { g_set_error (error, GOA_ERROR, GOA_ERROR_NOT_SUPPORTED, _("ensure_credentials_sync is not implemented on type %s"), g_type_name (G_TYPE_FROM_INSTANCE (self))); return FALSE; } static gboolean goa_provider_build_object_real (GoaProvider *self, GoaObjectSkeleton *object, GKeyFile *key_file, const gchar *group, GDBusConnection *connection, gboolean just_added, GError **error) { /* does nothing */ return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /** * goa_provider_get_credentials_generation: * @self: A #GoaProvider. * * Gets the generation of credentials being used for the provider. * * Implementations should bump this number when changes are introduced * that may render existing credentials unusable. * * For example, if an additional scope is requested (e.g. access to * contacts data) while obtaining credentials, then this number needs * to be bumped since existing credentials are not good for the added * scope. * * This is a virtual method where the default implementation returns * 0. * * Returns: The current generation of credentials. */ guint goa_provider_get_credentials_generation (GoaProvider *self) { g_return_val_if_fail (GOA_IS_PROVIDER (self), 0); return GOA_PROVIDER_GET_CLASS (self)->get_credentials_generation (self); } static guint goa_provider_get_credentials_generation_real (GoaProvider *self) { return 0; } /* ---------------------------------------------------------------------------------------------------- */ void goa_provider_ensure_extension_points_registered (void) { static gsize once_init_value = 0; if (g_once_init_enter (&once_init_value)) { GIOExtensionPoint *extension_point; extension_point = g_io_extension_point_register (GOA_PROVIDER_EXTENSION_POINT_NAME); g_io_extension_point_set_required_type (extension_point, GOA_TYPE_PROVIDER); extension_point = g_io_extension_point_register (GOA_PROVIDER_FACTORY_EXTENSION_POINT_NAME); g_io_extension_point_set_required_type (extension_point, GOA_TYPE_PROVIDER_FACTORY); g_once_init_leave (&once_init_value, 1); } } /* ---------------------------------------------------------------------------------------------------- */ static struct { const gchar *name; GType (*get_type) (void); } ordered_builtins_map[] = { /* The order in which the providers' types are created is * important because it affects the order in which they are * returned by goa_provider_get_all. */ #ifdef GOA_GOOGLE_ENABLED { GOA_GOOGLE_NAME, goa_google_provider_get_type }, #endif #ifdef GOA_OWNCLOUD_ENABLED { GOA_OWNCLOUD_NAME, goa_owncloud_provider_get_type }, #endif #ifdef GOA_FACEBOOK_ENABLED { GOA_FACEBOOK_NAME, goa_facebook_provider_get_type }, #endif #ifdef GOA_WINDOWS_LIVE_ENABLED { GOA_WINDOWS_LIVE_NAME, goa_windows_live_provider_get_type }, #endif #ifdef GOA_FLICKR_ENABLED { GOA_FLICKR_NAME, goa_flickr_provider_get_type }, #endif #ifdef GOA_POCKET_ENABLED { GOA_POCKET_NAME, goa_pocket_provider_get_type }, #endif #ifdef GOA_FOURSQUARE_ENABLED { GOA_FOURSQUARE_NAME, goa_foursquare_provider_get_type }, #endif #ifdef GOA_EXCHANGE_ENABLED { GOA_EXCHANGE_NAME, goa_exchange_provider_get_type }, #endif #ifdef GOA_LASTFM_ENABLED { GOA_LASTFM_NAME, goa_lastfm_provider_get_type }, #endif #ifdef GOA_TODOIST_ENABLED { GOA_TODOIST_NAME, goa_todoist_provider_get_type }, #endif #ifdef GOA_IMAP_SMTP_ENABLED { GOA_IMAP_SMTP_NAME, goa_imap_smtp_provider_get_type }, #endif #ifdef GOA_KERBEROS_ENABLED { GOA_KERBEROS_NAME, goa_kerberos_provider_get_type }, #endif #ifdef GOA_MEDIA_SERVER_ENABLED { GOA_MEDIA_SERVER_NAME, goa_media_server_provider_get_type }, #endif #ifdef GOA_TELEPATHY_ENABLED { GOA_TELEPATHY_NAME, goa_telepathy_factory_get_type }, #endif { NULL, NULL } }; void goa_provider_ensure_builtins_loaded (void) { static gsize once_init_value = 0; goa_provider_ensure_extension_points_registered (); if (g_once_init_enter (&once_init_value)) { GSettings *settings; gchar **whitelisted_providers; guint i; guint j; gboolean all = FALSE; settings = g_settings_new (GOA_SETTINGS_SCHEMA); whitelisted_providers = g_settings_get_strv (settings, GOA_SETTINGS_WHITELISTED_PROVIDERS); /* Enable everything if there is 'all'. */ for (i = 0; whitelisted_providers[i] != NULL; i++) { if (g_strcmp0 (whitelisted_providers[i], "all") == 0) { g_debug ("Loading all providers: "); for (j = 0; ordered_builtins_map[j].name != NULL; j++) { g_debug (" - %s", ordered_builtins_map[j].name); g_type_ensure ((*ordered_builtins_map[j].get_type) ()); } all = TRUE; break; } } if (all) goto cleanup; /* Otherwise try them one by one. */ g_debug ("Loading whitelisted providers: "); for (i = 0; ordered_builtins_map[i].name != NULL; i++) { for (j = 0; whitelisted_providers[j] != NULL; j++) { if (g_strcmp0 (whitelisted_providers[j], ordered_builtins_map[i].name) == 0) { g_debug (" - %s", ordered_builtins_map[j].name); g_type_ensure ((*ordered_builtins_map[i].get_type) ()); break; } } } cleanup: g_strfreev (whitelisted_providers); g_object_unref (settings); g_once_init_leave (&once_init_value, 1); } } /* ---------------------------------------------------------------------------------------------------- */ /** * goa_provider_get_for_provider_type: * @provider_type: A provider type. * * Returns a #GoaProvider for @provider_type (if available). * * If @provider_type doesn't contain any "/", a * %GOA_PROVIDER_EXTENSION_POINT_NAME extension for @provider_type is looked up * and the newly created #GoaProvider, if any, is returned. * * If @provider_type contains a "/", a * %GOA_PROVIDER_FACTORY_EXTENSION_POINT_NAME extension for the first part of * @provider_type is looked up. If found, the #GoaProviderFactory is used * to create a dynamic #GoaProvider matching the second part of @provider_type. * * Returns: (transfer full): A #GoaProvider (that must be freed * with g_object_unref()) or %NULL if not found. */ GoaProvider * goa_provider_get_for_provider_type (const gchar *provider_type) { GIOExtension *extension; GIOExtensionPoint *extension_point; gchar **split_provider_type; GoaProvider *ret; g_return_val_if_fail (provider_type != NULL, NULL); goa_provider_ensure_builtins_loaded (); ret = NULL; split_provider_type = g_strsplit (provider_type, "/", 2); if (g_strv_length (split_provider_type) == 1) { /* Normal provider */ extension_point = g_io_extension_point_lookup (GOA_PROVIDER_EXTENSION_POINT_NAME); extension = g_io_extension_point_get_extension_by_name (extension_point, provider_type); if (extension != NULL) ret = GOA_PROVIDER (g_object_new (g_io_extension_get_type (extension), NULL)); } else { /* Dynamic provider created through a factory */ extension_point = g_io_extension_point_lookup (GOA_PROVIDER_FACTORY_EXTENSION_POINT_NAME); extension = g_io_extension_point_get_extension_by_name (extension_point, split_provider_type[0]); if (extension != NULL) { GoaProviderFactory *factory = g_object_new (g_io_extension_get_type (extension), NULL); ret = goa_provider_factory_get_provider (factory, split_provider_type[1]); g_object_unref (factory); } } g_strfreev (split_provider_type); return ret; } /* ---------------------------------------------------------------------------------------------------- */ typedef struct { GQueue ret; gint pending_calls; GTask *task; } GetAllData; static void free_list_and_unref (gpointer data) { g_list_free_full (data, g_object_unref); } static gint compare_providers (GoaProvider *a, GoaProvider *b) { gboolean a_branded; gboolean b_branded; if (goa_provider_get_provider_features (a) & GOA_PROVIDER_FEATURE_BRANDED) a_branded = TRUE; else a_branded = FALSE; if (goa_provider_get_provider_features (b) & GOA_PROVIDER_FEATURE_BRANDED) b_branded = TRUE; else b_branded = FALSE; /* g_queue_sort() uses a stable sort, so, if we return 0, the order * is not changed. */ if (a_branded == b_branded) return 0; else if (a_branded) return -1; else return 1; } static void get_all_check_done (GetAllData *data) { if (data->pending_calls > 0) return; /* Make sure that branded providers come first, but don't change the * order otherwise. */ g_queue_sort (&data->ret, (GCompareDataFunc) compare_providers, NULL); /* Steal the list out of the GQueue. */ g_task_return_pointer (data->task, data->ret.head, free_list_and_unref); g_object_unref (data->task); g_slice_free (GetAllData, data); } static void get_providers_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GoaProviderFactory *factory = GOA_PROVIDER_FACTORY (source); GetAllData *data = user_data; GList *providers = NULL; GList *l; GError *error = NULL; if (!goa_provider_factory_get_providers_finish (factory, &providers, res, &error)) { g_critical ("Error getting providers from a factory: %s (%s, %d)", error->message, g_quark_to_string (error->domain), error->code); g_clear_error (&error); goto out; } for (l = providers; l != NULL; l = l->next) { /* Steal the value */ g_queue_push_tail (&data->ret, l->data); } g_list_free (providers); out: data->pending_calls--; get_all_check_done (data); } /** * goa_provider_get_all: * @callback: The function to call when the request is satisfied. * @user_data: Pointer to pass to @callback. * * Creates a list of all the available #GoaProvider instances. * * When the result is ready, @callback will be called in the the thread-default main * loop this function was called from. You can then call * goa_provider_get_all_finish() to get the result of the operation. * * See goa_provider_get_for_provider_type() for details on how the providers * are found. */ void goa_provider_get_all (GAsyncReadyCallback callback, gpointer user_data) { GList *extensions; GList *l; GIOExtensionPoint *extension_point; GetAllData *data; gint i; goa_provider_ensure_builtins_loaded (); data = g_slice_new0 (GetAllData); data->task = g_task_new (NULL, NULL, callback, user_data); g_task_set_source_tag (data->task, goa_provider_get_all); g_queue_init (&data->ret); /* Load the normal providers. */ extension_point = g_io_extension_point_lookup (GOA_PROVIDER_EXTENSION_POINT_NAME); extensions = g_io_extension_point_get_extensions (extension_point); /* TODO: what if there are two extensions with the same name? */ for (l = extensions, i = 0; l != NULL; l = l->next, i++) { GIOExtension *extension = l->data; /* The extensions are loaded in the reverse order we used in * goa_provider_ensure_builtins_loaded, so we need to push * extension if front of the already loaded ones. */ g_queue_push_head (&data->ret, g_object_new (g_io_extension_get_type (extension), NULL)); } /* Load the provider factories and get the dynamic providers out of them. */ extension_point = g_io_extension_point_lookup (GOA_PROVIDER_FACTORY_EXTENSION_POINT_NAME); extensions = g_io_extension_point_get_extensions (extension_point); for (l = extensions, i = 0; l != NULL; l = l->next, i++) { GIOExtension *extension = l->data; GoaProviderFactory *factory; factory = GOA_PROVIDER_FACTORY (g_object_new (g_io_extension_get_type (extension), NULL)); goa_provider_factory_get_providers (factory, get_providers_cb, data); g_object_unref (factory); data->pending_calls++; } get_all_check_done (data); } /** * goa_provider_get_all_finish: * @out_providers: (out) (transfer full) (element-type GoaProvider): * Return location for a list of #GoaProvider instances. * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to goa_provider_get_all(). * @error: Return location for error or %NULL. * * Finishes an operation started with goa_provider_get_all(). * * Returns: %TRUE if the list was successfully retrieved, %FALSE if @error is set. */ gboolean goa_provider_get_all_finish (GList **out_providers, GAsyncResult *result, GError **error) { GTask *task; GList *providers; gboolean had_error; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE); task = G_TASK (result); g_return_val_if_fail (g_task_get_source_tag (task) == goa_provider_get_all, FALSE); /* Workaround for bgo#764163 */ had_error = g_task_had_error (task); providers = g_task_propagate_pointer (task, error); if (had_error) return FALSE; if (out_providers != NULL) { *out_providers = providers; providers = NULL; } g_list_free_full (providers, g_object_unref); return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ void goa_provider_remove_account (GoaProvider *self, GoaObject *object, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail (GOA_IS_PROVIDER (self)); g_return_if_fail (GOA_IS_OBJECT (object)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); GOA_PROVIDER_GET_CLASS (self)->remove_account (self, object, cancellable, callback, user_data); } gboolean goa_provider_remove_account_finish (GoaProvider *self, GAsyncResult *res, GError **error) { g_return_val_if_fail (GOA_IS_PROVIDER (self), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); return GOA_PROVIDER_GET_CLASS (self)->remove_account_finish (self, res, error); } /* ---------------------------------------------------------------------------------------------------- */ static void goa_provider_remove_account_real (GoaProvider *self, GoaObject *object, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, goa_provider_remove_account_real); g_task_return_boolean (task, TRUE); g_object_unref (task); } static gboolean goa_provider_remove_account_finish_real (GoaProvider *self, GAsyncResult *res, GError **error) { GTask *task; g_return_val_if_fail (g_task_is_valid (res, self), FALSE); task = G_TASK (res); g_return_val_if_fail (g_task_get_source_tag (task) == goa_provider_remove_account_real, FALSE); return g_task_propagate_boolean (task, error); } /* ---------------------------------------------------------------------------------------------------- */ /** * goa_provider_set_preseed_data: * @self: The #GoaProvider * @preseed_data: A #GVariant of type a{sv} * * Sets the #GoaProvider:preseed-data property to feed any information already * collected that can be useful when creating a new account. * * If the @preseed_data #GVariant is floating, it is consumed to allow * 'inline' use of the g_variant_new() family of functions. * * Deprecated: 3.28: This function does nothing. */ void goa_provider_set_preseed_data (GoaProvider *self, GVariant *preseed_data) { } /** * goa_provider_get_preseed_data: * @self: The #GoaProvider * * Gets the #GVariant set through the #GoaProvider:preseed-data property. * * Returns: (transfer none): A #GVariant that is known to be valid until * the property is overridden or the provider freed. * * Deprecated: 3.28: This function does nothing. */ GVariant * goa_provider_get_preseed_data (GoaProvider *self) { return NULL; } /* ---------------------------------------------------------------------------------------------------- */ GtkWidget * goa_util_add_row_widget (GtkGrid *grid, gint row, const gchar *label_text, GtkWidget *widget) { GtkWidget *label; g_return_val_if_fail (GTK_IS_GRID (grid), NULL); g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); if (label_text != NULL) { GtkStyleContext *context; label = gtk_label_new (label_text); context = gtk_widget_get_style_context (label); gtk_style_context_add_class (context, GTK_STYLE_CLASS_DIM_LABEL); gtk_widget_set_halign (label, GTK_ALIGN_END); gtk_widget_set_hexpand (label, TRUE); gtk_grid_attach (grid, label, 0, row, 1, 1); } gtk_grid_attach (grid, widget, 1, row, 3, 1); return widget; } /* ---------------------------------------------------------------------------------------------------- */ gchar * goa_util_lookup_keyfile_string (GoaObject *object, const gchar *key) { GoaAccount *account; GError *error; GKeyFile *key_file; gchar *path; gchar *group; gchar *ret; ret = NULL; account = goa_object_peek_account (object); path = g_strdup_printf ("%s/goa-1.0/accounts.conf", g_get_user_config_dir ()); group = g_strdup_printf ("Account %s", goa_account_get_id (account)); key_file = g_key_file_new (); error = NULL; if (!g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, &error)) { g_warning ("Error loading keyfile %s: %s (%s, %d)", path, error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); goto out; } ret = g_key_file_get_string (key_file, group, key, &error); if (ret == NULL) { /* this is not fatal (think upgrade-path) */ g_debug ("Error getting value for key %s in group `%s' from keyfile %s: %s (%s, %d)", key, group, path, error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); goto out; } out: g_key_file_unref (key_file); g_free (group); g_free (path); return ret; } gboolean goa_util_lookup_keyfile_boolean (GoaObject *object, const gchar *key) { GoaAccount *account; GError *error; GKeyFile *key_file; gchar *path; gchar *group; gboolean ret; ret = FALSE; account = goa_object_peek_account (object); path = g_strdup_printf ("%s/goa-1.0/accounts.conf", g_get_user_config_dir ()); group = g_strdup_printf ("Account %s", goa_account_get_id (account)); key_file = g_key_file_new (); error = NULL; if (!g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, &error)) { g_warning ("Error loading keyfile %s: %s (%s, %d)", path, error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); goto out; } ret = g_key_file_get_boolean (key_file, group, key, &error); if (error != NULL) { /* this is not fatal (think upgrade-path) */ g_debug ("Error getting boolean value for key %s in group `%s' from keyfile %s: %s (%s, %d)", key, group, path, error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); goto out; } out: g_key_file_unref (key_file); g_free (group); g_free (path); return ret; } /* ---------------------------------------------------------------------------------------------------- */ void goa_util_account_notify_property_cb (GObject *object, GParamSpec *pspec, gpointer user_data) { GoaAccount *account; gboolean value; const gchar *key; const gchar *name; g_return_if_fail (GOA_IS_ACCOUNT (object)); account = GOA_ACCOUNT (object); key = user_data; name = g_param_spec_get_name (pspec); g_object_get (account, name, &value, NULL); goa_utils_keyfile_set_boolean (account, key, !value); } /* ---------------------------------------------------------------------------------------------------- */ GtkWidget * goa_util_add_row_switch_from_keyfile_with_blurb (GtkGrid *grid, gint row, GoaObject *object, const gchar *label_text, const gchar *property, const gchar *blurb) { GoaAccount *account; GtkWidget *hbox; GtkWidget *switch_; gboolean value; account = goa_object_peek_account (object); g_object_get (account, property, &value, NULL); switch_ = gtk_switch_new (); if (goa_account_get_attention_needed (account)) { gtk_widget_set_sensitive (switch_, FALSE); gtk_switch_set_active (GTK_SWITCH (switch_), FALSE); } else { gtk_switch_set_active (GTK_SWITCH (switch_), !value); g_object_bind_property (switch_, "active", account, property, G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN); } hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE); if (blurb != NULL) { GtkWidget *label; label = gtk_label_new_with_mnemonic (blurb); gtk_label_set_mnemonic_widget (GTK_LABEL (label), switch_); gtk_label_set_width_chars (GTK_LABEL (label), 18); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_container_add (GTK_CONTAINER (hbox), label); } gtk_container_add (GTK_CONTAINER (hbox), switch_); goa_util_add_row_widget (grid, row, label_text, hbox); return switch_; }