[gnome-software/wip/rancell/ubuntuone: 5/5] Add UbuntuOne plugin to authorize Ubuntu reviews
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/wip/rancell/ubuntuone: 5/5] Add UbuntuOne plugin to authorize Ubuntu reviews
- Date: Thu, 30 Jun 2016 05:15:35 +0000 (UTC)
commit 29e100d1073b0d3df0afa9de8b977b3e410f4e6e
Author: Robert Ancell <robert ancell canonical com>
Date: Wed Jun 29 16:37:38 2016 +1200
Add UbuntuOne plugin to authorize Ubuntu reviews
configure.ac | 2 +
src/gs-os-release.c | 21 +++
src/gs-os-release.h | 13 +-
src/gs-plugin.h | 22 ++-
src/plugins/Makefile.am | 17 ++-
src/plugins/gs-plugin-ubuntu-reviews.c | 270 +++++++++++++++++++++++++++++++-
src/plugins/gs-plugin-ubuntuone.c | 212 +++++++++++++++++++++++++
7 files changed, 538 insertions(+), 19 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 7f2133e..1417c1d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -68,6 +68,8 @@ PKG_CHECK_MODULES(SQLITE, sqlite3)
PKG_CHECK_MODULES(SOUP, libsoup-2.4 >= 2.51.92)
PKG_CHECK_MODULES(GSETTINGS_DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.11.5)
PKG_CHECK_MODULES(GNOME_DESKTOP, gnome-desktop-3.0 >= 3.17.92)
+PKG_CHECK_MODULES(OAUTH, oauth)
+PKG_CHECK_MODULES(LIBSECRET, libsecret-1)
AC_PATH_PROG(APPSTREAM_UTIL, [appstream-util], [unfound])
AC_ARG_ENABLE(man,
[AS_HELP_STRING([--enable-man],
diff --git a/src/gs-os-release.c b/src/gs-os-release.c
index c92d364..3559e5a 100644
--- a/src/gs-os-release.c
+++ b/src/gs-os-release.c
@@ -45,6 +45,7 @@ struct _GsOsRelease
gchar *id;
gchar *version_id;
gchar *pretty_name;
+ gchar *ubuntu_codename;
};
static void gs_os_release_initable_iface_init (GInitableIface *iface);
@@ -116,6 +117,10 @@ gs_os_release_initable_init (GInitable *initable,
os_release->pretty_name = g_strdup (tmp);
continue;
}
+ if (g_strcmp0 (lines[i], "UBUNTU_CODENAME") == 0) {
+ os_release->ubuntu_codename = g_strdup (tmp);
+ continue;
+ }
}
return TRUE;
}
@@ -195,6 +200,21 @@ gs_os_release_get_pretty_name (GsOsRelease *os_release)
return os_release->pretty_name;
}
+/**
+ * gs_os_release_get_ubuntu_codename:
+ * @os_release: A #GsOsRelease
+ *
+ * Gets the Ubuntu codename from the os-release parser.
+ *
+ * Returns: a string, or %NULL
+ **/
+const gchar *
+gs_os_release_get_ubuntu_codename (GsOsRelease *os_release)
+{
+ g_return_val_if_fail (GS_IS_OS_RELEASE (os_release), NULL);
+ return os_release->ubuntu_codename;
+}
+
static void
gs_os_release_finalize (GObject *object)
{
@@ -204,6 +224,7 @@ gs_os_release_finalize (GObject *object)
g_free (os_release->id);
g_free (os_release->version_id);
g_free (os_release->pretty_name);
+ g_free (os_release->ubuntu_codename);
G_OBJECT_CLASS (gs_os_release_parent_class)->finalize (object);
}
diff --git a/src/gs-os-release.h b/src/gs-os-release.h
index ffd338e..d7e951b 100644
--- a/src/gs-os-release.h
+++ b/src/gs-os-release.h
@@ -33,12 +33,13 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (GsOsRelease, gs_os_release, GS, OS_RELEASE, GObject)
-GsOsRelease *gs_os_release_new (GError **error);
-const gchar *gs_os_release_get_name (GsOsRelease *os_release);
-const gchar *gs_os_release_get_version (GsOsRelease *os_release);
-const gchar *gs_os_release_get_id (GsOsRelease *os_release);
-const gchar *gs_os_release_get_version_id (GsOsRelease *os_release);
-const gchar *gs_os_release_get_pretty_name (GsOsRelease *os_release);
+GsOsRelease *gs_os_release_new (GError **error);
+const gchar *gs_os_release_get_name (GsOsRelease *os_release);
+const gchar *gs_os_release_get_version (GsOsRelease *os_release);
+const gchar *gs_os_release_get_id (GsOsRelease *os_release);
+const gchar *gs_os_release_get_version_id (GsOsRelease *os_release);
+const gchar *gs_os_release_get_pretty_name (GsOsRelease *os_release);
+const gchar *gs_os_release_get_ubuntu_codename (GsOsRelease *os_release);
G_END_DECLS
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index 646157f..28347b9 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -100,15 +100,17 @@ typedef enum {
/**
* GsPluginError:
- * @GS_PLUGIN_ERROR_FAILED: Generic failure
- * @GS_PLUGIN_ERROR_NOT_SUPPORTED: Action not supported
- * @GS_PLUGIN_ERROR_CANCELLED: Action was cancelled
- * @GS_PLUGIN_ERROR_NO_NETWORK: No network connection available
- * @GS_PLUGIN_ERROR_NO_SECURITY: Security policy forbid action
- * @GS_PLUGIN_ERROR_NO_SPACE: No disk space to allow action
- * @GS_PLUGIN_ERROR_AUTH_REQUIRED: Authentication was required
- * @GS_PLUGIN_ERROR_AUTH_INVALID: Provided authentication was invalid
- * @GS_PLUGIN_ERROR_PIN_REQUIRED: PIN required for authentication
+ * @GS_PLUGIN_ERROR_FAILED: Generic failure
+ * @GS_PLUGIN_ERROR_NOT_SUPPORTED: Action not supported
+ * @GS_PLUGIN_ERROR_CANCELLED: Action was cancelled
+ * @GS_PLUGIN_ERROR_NO_NETWORK: No network connection available
+ * @GS_PLUGIN_ERROR_NO_SECURITY: Security policy forbid action
+ * @GS_PLUGIN_ERROR_NO_SPACE: No disk space to allow action
+ * @GS_PLUGIN_ERROR_AUTH_REQUIRED: Authentication was required
+ * @GS_PLUGIN_ERROR_AUTH_INVALID: Provided authentication was invalid
+ * @GS_PLUGIN_ERROR_PIN_REQUIRED: PIN required for authentication
+ * @GS_PLUGIN_ERROR_ACCOUNT_SUSPENDED: User account has been suspended
+ * @GS_PLUGIN_ERROR_ACCOUNT_DEACTIVATED: User account has been deactivated
*
* The failure error types.
**/
@@ -122,6 +124,8 @@ typedef enum {
GS_PLUGIN_ERROR_AUTH_REQUIRED,
GS_PLUGIN_ERROR_AUTH_INVALID,
GS_PLUGIN_ERROR_PIN_REQUIRED,
+ GS_PLUGIN_ERROR_ACCOUNT_SUSPENDED,
+ GS_PLUGIN_ERROR_ACCOUNT_DEACTIVATED,
/*< private >*/
GS_PLUGIN_ERROR_LAST
} GsPluginError;
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index c57b7d4..108a831 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -15,6 +15,8 @@ AM_CPPFLAGS = \
$(OSTREE_CFLAGS) \
$(FLATPAK_CFLAGS) \
$(RPM_CFLAGS) \
+ $(LIBSECRET_CFLAGS) \
+ $(OAUTH_CFLAGS) \
-DI_KNOW_THE_GNOME_SOFTWARE_API_IS_SUBJECT_TO_CHANGE \
-DBINDIR=\"$(bindir)\" \
-DDATADIR=\"$(datadir)\" \
@@ -43,7 +45,8 @@ plugin_LTLIBRARIES = \
libgs_plugin_fedora-tagger-usage.la \
libgs_plugin_epiphany.la \
libgs_plugin_icons.la \
- libgs_plugin_snap.la
+ libgs_plugin_snap.la \
+ libgs_plugin_ubuntuone.la
if HAVE_PACKAGEKIT
plugin_LTLIBRARIES += \
@@ -251,7 +254,7 @@ libgs_plugin_hardcoded_featured_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
if HAVE_UBUNTU_REVIEWS
libgs_plugin_ubuntu_reviews_la_SOURCES = \
gs-plugin-ubuntu-reviews.c
-libgs_plugin_ubuntu_reviews_la_LIBADD = $(GS_PLUGIN_LIBS) $(SOUP_LIBS) $(JSON_GLIB_LIBS) $(SQLITE_LIBS)
+libgs_plugin_ubuntu_reviews_la_LIBADD = $(GS_PLUGIN_LIBS) $(SOUP_LIBS) $(JSON_GLIB_LIBS) $(SQLITE_LIBS)
$(LIBSECRET_LIBS) $(OAUTH_LIBS)
libgs_plugin_ubuntu_reviews_la_LDFLAGS = -module -avoid-version
libgs_plugin_ubuntu_reviews_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
endif
@@ -344,6 +347,16 @@ libgs_plugin_snap_la_LIBADD = \
libgs_plugin_snap_la_LDFLAGS = -module -avoid-version
libgs_plugin_snap_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+libgs_plugin_ubuntuone_la_SOURCES = \
+ gs-plugin-ubuntuone.c
+libgs_plugin_ubuntuone_la_LIBADD = \
+ $(GS_PLUGIN_LIBS) \
+ $(SOUP_LIBS) \
+ $(JSON_GLIB_LIBS) \
+ $(LIBSECRET_LIBS)
+libgs_plugin_ubuntuone_la_LDFLAGS = -module -avoid-version
+libgs_plugin_ubuntuone_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+
if ENABLE_TESTS
check_PROGRAMS = \
gs-self-test
diff --git a/src/plugins/gs-plugin-ubuntu-reviews.c b/src/plugins/gs-plugin-ubuntu-reviews.c
index ba664e0..e0aee59 100644
--- a/src/plugins/gs-plugin-ubuntu-reviews.c
+++ b/src/plugins/gs-plugin-ubuntu-reviews.c
@@ -25,12 +25,16 @@
#include <math.h>
#include <json-glib/json-glib.h>
#include <sqlite3.h>
+#include <libsecret/secret.h>
+#include <oauth.h>
#include <gnome-software.h>
struct GsPluginData {
gchar *db_path;
sqlite3 *db;
gsize db_loaded;
+ gchar *origin;
+ gchar *distroseries;
};
typedef struct {
@@ -50,10 +54,14 @@ typedef struct {
/* Number of pages of reviews to download */
#define N_PAGES 3
+#define SECRET_SCHEMA_NAME "com.ubuntu.UbuntuOne.GnomeSoftware"
+
void
gs_plugin_initialize (GsPlugin *plugin)
{
GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
+ g_autoptr(GsOsRelease) os_release = NULL;
+ g_autoptr(GError) error = NULL;
/* check that we are running on Ubuntu */
if (!gs_plugin_check_distro_id (plugin, "ubuntu")) {
@@ -67,6 +75,21 @@ gs_plugin_initialize (GsPlugin *plugin)
"ubuntu-reviews.db",
NULL);
+ os_release = gs_os_release_new (&error);
+ if (os_release == NULL) {
+ g_warning ("Failed to determine OS information: %s", error->message);
+ priv->origin = g_strdup ("unknown");
+ priv->distroseries = g_strdup ("unknown");
+ } else {
+ priv->origin = g_strdup (gs_os_release_get_id (os_release));
+ if (priv->origin == NULL)
+ priv->origin = g_strdup ("unknown");
+ if (strcmp (priv->origin, "ubuntu") == 0)
+ priv->distroseries = g_strdup (gs_os_release_get_ubuntu_codename (os_release));
+ if (priv->distroseries == NULL)
+ priv->distroseries = g_strdup ("unknown");
+ }
+
/* we have more reviews than ORDS */
gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "odrs");
@@ -80,6 +103,8 @@ gs_plugin_destroy (GsPlugin *plugin)
GsPluginData *priv = gs_plugin_get_data (plugin);
g_clear_pointer (&priv->db, sqlite3_close);
g_free (priv->db_path);
+ g_free (priv->origin);
+ g_free (priv->distroseries);
}
static gint
@@ -336,12 +361,81 @@ parse_review_entries (GsPlugin *plugin, JsonParser *parser, GError **error)
}
static gboolean
-send_review_request (GsPlugin *plugin, const gchar *method, const gchar *path, JsonBuilder *request,
JsonParser **result, GCancellable *cancellable, GError **error)
+lookup_secret (const gchar *key, gchar **value, GCancellable *cancellable, GError **error)
+{
+ static SecretSchema schema = {
+ SECRET_SCHEMA_NAME,
+ SECRET_SCHEMA_NONE,
+ { { "key", SECRET_SCHEMA_ATTRIBUTE_STRING } }
+ };
+
+ *value = secret_password_lookup_sync (&schema, cancellable, error, "key", key, NULL);
+ return *value != NULL;
+}
+
+static gboolean
+get_ubuntuone_token (GsPlugin *plugin,
+ gchar **consumer_key, gchar **consumer_secret,
+ gchar **token_key, gchar **token_secret,
+ GCancellable *cancellable, GError **error)
+{
+ if (!lookup_secret ("consumer-key", consumer_key, cancellable, error) ||
+ !lookup_secret ("consumer-secret", consumer_secret, cancellable, error) ||
+ !lookup_secret ("token-key", token_key, cancellable, error) ||
+ !lookup_secret ("token-secret", token_secret, cancellable, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+sign_message (SoupMessage *message, OAuthMethod method,
+ const gchar *consumer_key, const gchar *consumer_secret,
+ const gchar *token_key, const gchar *token_secret)
+{
+ g_autofree gchar *url = NULL, *oauth_authorization_parameters = NULL, *authorization_text = NULL;
+ gchar **url_parameters = NULL;
+ int url_parameters_length;
+
+ url = soup_uri_to_string (soup_message_get_uri (message), FALSE);
+
+ url_parameters_length = oauth_split_url_parameters(url, &url_parameters);
+ oauth_sign_array2_process (&url_parameters_length, &url_parameters,
+ NULL,
+ method,
+ message->method,
+ consumer_key, consumer_secret,
+ token_key, token_secret);
+ oauth_authorization_parameters = oauth_serialize_url_sep (url_parameters_length, 1, url_parameters,
", ", 6);
+ oauth_free_array (&url_parameters_length, &url_parameters);
+ authorization_text = g_strdup_printf ("OAuth realm=\"Ratings and Reviews\", %s",
oauth_authorization_parameters);
+ soup_message_headers_append (message->request_headers, "Authorization", authorization_text);
+}
+
+static gboolean
+send_review_request (GsPlugin *plugin,
+ const gchar *method, const gchar *path,
+ JsonBuilder *request,
+ gboolean do_sign,
+ JsonParser **result,
+ GCancellable *cancellable, GError **error)
{
+ g_autofree gchar *consumer_key = NULL;
+ g_autofree gchar *consumer_secret = NULL;
+ g_autofree gchar *token_key = NULL;
+ g_autofree gchar *token_secret = NULL;
g_autofree gchar *uri = NULL;
g_autoptr(SoupMessage) msg = NULL;
guint status_code;
+ if (do_sign && !get_ubuntuone_token (plugin, &consumer_key, &consumer_secret, &token_key,
&token_secret, cancellable, NULL)) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_REQUIRED,
+ "Requires authentication with @ubuntuone");
+ return FALSE;
+ }
+
uri = g_strdup_printf ("%s%s",
UBUNTU_REVIEWS_SERVER, path);
msg = soup_message_new (method, uri);
@@ -357,6 +451,12 @@ send_review_request (GsPlugin *plugin, const gchar *method, const gchar *path, J
soup_message_set_request (msg, "application/json", SOUP_MEMORY_TAKE, data, length);
}
+ if (do_sign)
+ sign_message (msg,
+ OA_PLAINTEXT,
+ consumer_key, consumer_secret,
+ token_key, token_secret);
+
status_code = soup_session_send_message (gs_plugin_get_soup_session (plugin), msg);
if (status_code != SOUP_STATUS_OK) {
g_set_error (error,
@@ -398,7 +498,7 @@ download_review_stats (GsPlugin *plugin, GCancellable *cancellable, GError **err
g_autoptr(SoupMessage) msg = NULL;
g_autoptr(JsonParser) result = NULL;
- if (!send_review_request (plugin, SOUP_METHOD_GET, "/api/1.0/review-stats/any/any/", NULL, &result,
cancellable, error))
+ if (!send_review_request (plugin, SOUP_METHOD_GET, "/api/1.0/review-stats/any/any/", NULL, FALSE,
&result, cancellable, error))
return FALSE;
/* Extract the stats from the data */
@@ -724,3 +824,169 @@ gs_plugin_refine_app (GsPlugin *plugin,
return TRUE;
}
+static void
+add_string_member (JsonBuilder *builder, const gchar *name, const gchar *value)
+{
+ json_builder_set_member_name (builder, name);
+ json_builder_add_string_value (builder, value);
+}
+
+static void
+add_int_member (JsonBuilder *builder, const gchar *name, gint64 value)
+{
+ json_builder_set_member_name (builder, name);
+ json_builder_add_int_value (builder, value);
+}
+
+gboolean
+gs_plugin_review_submit (GsPlugin *plugin,
+ GsApp *app,
+ GsReview *review,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ gint rating;
+ gint n_stars;
+ g_autofree gchar *language = NULL, *architecture = NULL;
+ g_autoptr(JsonBuilder) request = NULL;
+
+ /* Ubuntu reviews require a summary and description - just make one up for now */
+ rating = gs_review_get_rating (review);
+ if (rating > 80)
+ n_stars = 5;
+ else if (rating > 60)
+ n_stars = 4;
+ else if (rating > 40)
+ n_stars = 3;
+ else if (rating > 20)
+ n_stars = 2;
+ else
+ n_stars = 1;
+
+ language = get_language (plugin);
+
+ // FIXME: Need to get Apt::Architecture configuration value from APT
+ architecture = g_strdup ("amd64");
+
+ /* Create message for reviews.ubuntu.com */
+ request = json_builder_new ();
+ json_builder_begin_object (request);
+ add_string_member (request, "package_name", gs_app_get_source_default (app));
+ add_string_member (request, "summary", gs_review_get_summary (review));
+ add_string_member (request, "review_text", gs_review_get_text (review));
+ add_string_member (request, "language", language);
+ add_string_member (request, "origin", priv->origin);
+ add_string_member (request, "distroseries", priv->distroseries);
+ add_string_member (request, "version", gs_review_get_version (review));
+ add_int_member (request, "rating", n_stars);
+ add_string_member (request, "arch_tag", architecture);
+ json_builder_end_object (request);
+
+ return send_review_request (plugin, SOUP_METHOD_POST, "/api/1.0/reviews/", request, TRUE, NULL,
cancellable, error);
+}
+
+gboolean
+gs_plugin_review_report (GsPlugin *plugin,
+ GsApp *app,
+ GsReview *review,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *review_id;
+ g_autofree gchar *reason = NULL;
+ g_autofree gchar *text = NULL;
+ g_autofree gchar *path = NULL;
+
+ /* Can only modify Ubuntu reviews */
+ review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+ if (review_id == NULL)
+ return TRUE;
+
+ /* Create message for reviews.ubuntu.com */
+ reason = g_strdup ("FIXME: gnome-software");
+ text = g_strdup ("FIXME: gnome-software");
+ // FIXME: escape reason / text properly
+ path = g_strdup_printf ("/api/1.0/reviews/%s/recommendations/?reason=%s&text=%s", review_id, reason,
text);
+ if (!send_review_request (plugin, SOUP_METHOD_POST, path, NULL, TRUE, NULL, cancellable, error))
+ return FALSE;
+
+ gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
+ return TRUE;
+}
+
+static gboolean
+set_review_usefulness (GsPlugin *plugin,
+ const gchar *review_id,
+ gboolean is_useful,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *path = NULL;
+
+ /* Create message for reviews.ubuntu.com */
+ path = g_strdup_printf ("/api/1.0/reviews/%s/recommendations/?useful=%s", review_id, is_useful ?
"True" : "False");
+ return send_review_request (plugin, SOUP_METHOD_POST, path, NULL, TRUE, NULL, cancellable, error);
+}
+
+gboolean
+gs_plugin_review_upvote (GsPlugin *plugin,
+ GsApp *app,
+ GsReview *review,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *review_id;
+
+ /* Can only modify Ubuntu reviews */
+ review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+ if (review_id == NULL)
+ return TRUE;
+
+ if (!set_review_usefulness (plugin, review_id, TRUE, cancellable, error))
+ return FALSE;
+
+ gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
+ return TRUE;
+}
+
+gboolean
+gs_plugin_review_downvote (GsPlugin *plugin,
+ GsApp *app,
+ GsReview *review,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *review_id;
+
+ /* Can only modify Ubuntu reviews */
+ review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+ if (review_id == NULL)
+ return TRUE;
+
+ if (!set_review_usefulness (plugin, review_id, FALSE, cancellable, error))
+ return FALSE;
+
+ gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
+ return TRUE;
+}
+
+gboolean
+gs_plugin_review_remove (GsPlugin *plugin,
+ GsApp *app,
+ GsReview *review,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *review_id;
+ g_autofree gchar *path = NULL;
+
+ /* Can only modify Ubuntu reviews */
+ review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+ if (review_id == NULL)
+ return TRUE;
+
+ /* Create message for reviews.ubuntu.com */
+ path = g_strdup_printf ("/api/1.0/reviews/delete/%s/", review_id);
+ return send_review_request (plugin, SOUP_METHOD_POST, path, NULL, TRUE, NULL, cancellable, error);
+}
diff --git a/src/plugins/gs-plugin-ubuntuone.c b/src/plugins/gs-plugin-ubuntuone.c
new file mode 100644
index 0000000..8131529
--- /dev/null
+++ b/src/plugins/gs-plugin-ubuntuone.c
@@ -0,0 +1,212 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Canonical Ltd
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <libsoup/soup.h>
+#include <json-glib/json-glib.h>
+#include <libsecret/secret.h>
+#include <gnome-software.h>
+
+// Documented in http://canonical-identity-provider.readthedocs.io
+#define UBUNTU_LOGIN_HOST "https://login.ubuntu.com"
+
+#define SECRET_SCHEMA_NAME "com.ubuntu.UbuntuOne.GnomeSoftware"
+
+struct GsPluginData {
+ GsAuth *auth;
+};
+
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
+
+ /* set up a dummy authentication provider */
+ priv->auth = gs_auth_new (gs_plugin_get_name (plugin));
+ gs_auth_set_provider_name (priv->auth, "Ubuntu One");
+ //gs_auth_set_provider_logo (priv->auth, "...");
+ gs_plugin_add_auth (plugin, priv->auth);
+}
+
+static gboolean
+store_secret (const gchar *key, const gchar *value, GCancellable *cancellable, GError **error)
+{
+ static SecretSchema schema = {
+ SECRET_SCHEMA_NAME,
+ SECRET_SCHEMA_NONE,
+ { { "key", SECRET_SCHEMA_ATTRIBUTE_STRING } }
+ };
+
+ return secret_password_store_sync (&schema, NULL, SECRET_SCHEMA_NAME, value, cancellable, error,
"key", key, NULL);
+}
+
+gboolean
+gs_plugin_auth_login (GsPlugin *plugin, GsAuth *auth,
+ GCancellable *cancellable, GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(JsonBuilder) builder = NULL;
+ g_autoptr(JsonNode) json_root = NULL;
+ g_autoptr(JsonGenerator) json_generator = NULL;
+ g_autofree gchar *data = NULL;
+ g_autofree gchar *uri = NULL;
+ g_autoptr(SoupMessage) msg = NULL;
+ guint status_code;
+ g_autoptr(JsonParser) parser = NULL;
+ JsonNode *response_root;
+ const gchar *consumer_key, *consumer_secret, *token_key, *token_secret;
+
+ if (auth != priv->auth)
+ return TRUE;
+
+ builder = json_builder_new ();
+ json_builder_begin_object (builder);
+ json_builder_set_member_name (builder, "token_name");
+ json_builder_add_string_value (builder, "GNOME Software");
+ json_builder_set_member_name (builder, "email");
+ json_builder_add_string_value (builder, gs_auth_get_username (auth));
+ json_builder_set_member_name (builder, "password");
+ json_builder_add_string_value (builder, gs_auth_get_password (auth));
+ if (gs_auth_get_pin (auth)) {
+ json_builder_set_member_name (builder, "otp");
+ json_builder_add_string_value (builder, gs_auth_get_pin (auth));
+ }
+ json_builder_end_object (builder);
+
+ json_root = json_builder_get_root (builder);
+ json_generator = json_generator_new ();
+ json_generator_set_pretty (json_generator, TRUE);
+ json_generator_set_root (json_generator, json_root);
+ data = json_generator_to_data (json_generator, NULL);
+ if (data == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Failed to generate JSON request");
+ return FALSE;
+ }
+
+ uri = g_strdup_printf ("%s/api/v2/tokens/oauth", UBUNTU_LOGIN_HOST);
+ msg = soup_message_new (SOUP_METHOD_POST, uri);
+ soup_message_set_request (msg, "application/json", SOUP_MEMORY_COPY, data, strlen (data));
+ status_code = soup_session_send_message (gs_plugin_get_soup_session (plugin), msg);
+
+ parser = json_parser_new ();
+ if (!json_parser_load_from_data (parser, msg->response_body->data, -1, error))
+ return FALSE;
+ response_root = json_parser_get_root (parser);
+
+ if (status_code != SOUP_STATUS_OK) {
+ const gchar *message, *code;
+
+ message = json_object_get_string_member (json_node_get_object (response_root), "message");
+ code = json_object_get_string_member (json_node_get_object (response_root), "code");
+
+ if (g_strcmp0 (code, "INVALID_CREDENTIALS") == 0 || g_strcmp0 (code, "EMAIL_INVALIDATED") ==
0 || g_strcmp0 (code, "TWOFACTOR_FAILURE") == 0) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_INVALID,
+ message);
+ }
+ else if (g_strcmp0 (code, "ACCOUNT_SUSPENDED") == 0) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_ACCOUNT_SUSPENDED,
+ message);
+ }
+ else if (g_strcmp0 (code, "ACCOUNT_DEACTIVATED") == 0) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_ACCOUNT_DEACTIVATED,
+ message);
+ }
+ else if (g_strcmp0 (code, "TWOFACTOR_REQUIRED") == 0) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_PIN_REQUIRED,
+ message);
+ }
+ else {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ message);
+ }
+ return FALSE;
+ }
+
+ consumer_key = json_object_get_string_member (json_node_get_object (response_root), "consumer_key");
+ consumer_secret = json_object_get_string_member (json_node_get_object (response_root),
"consumer_secret");
+ token_key = json_object_get_string_member (json_node_get_object (response_root), "token_key");
+ token_secret = json_object_get_string_member (json_node_get_object (response_root), "token_secret");
+ if (consumer_key == NULL || consumer_secret == NULL || token_key == NULL || token_secret == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Response from %s missing required fields", UBUNTU_LOGIN_HOST);
+ return FALSE;
+ }
+
+ if (!store_secret ("consumer-key", consumer_key, cancellable, error) ||
+ !store_secret ("consumer-secret", consumer_secret, cancellable, error) ||
+ !store_secret ("token-key", token_key, cancellable, error) ||
+ !store_secret ("token-secret", token_secret, cancellable, error)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_auth_lost_password (GsPlugin *plugin, GsAuth *auth,
+ GCancellable *cancellable, GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+
+ if (auth != priv->auth)
+ return TRUE;
+
+ /* return with data */
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_INVALID,
+ "do online using @%s/+forgot_password", UBUNTU_LOGIN_HOST);
+ return FALSE;
+}
+
+gboolean
+gs_plugin_auth_register (GsPlugin *plugin, GsAuth *auth,
+ GCancellable *cancellable, GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+
+ if (auth != priv->auth)
+ return TRUE;
+
+ /* return with data */
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_INVALID,
+ "do online using @%s/+login", UBUNTU_LOGIN_HOST);
+ return FALSE;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]