[gnome-software/wip/ubuntu-xenial] Switch Snap and Ubuntu review plugins to use GsAuth
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/wip/ubuntu-xenial] Switch Snap and Ubuntu review plugins to use GsAuth
- Date: Fri, 9 Sep 2016 04:45:51 +0000 (UTC)
commit 5e6d85d7cd1e8f0344ba6025ba0b0f1196986541
Author: Robert Ancell <robert ancell canonical com>
Date: Fri Sep 9 13:12:40 2016 +1200
Switch Snap and Ubuntu review plugins to use GsAuth
po/POTFILES.in | 2 -
src/gnome-software.gresource.xml | 1 -
src/plugins/Makefile.am | 25 +-
src/plugins/gs-plugin-snap.c | 176 ++++++++++-
src/plugins/gs-plugin-ubuntu-reviews.c | 112 +++----
src/plugins/gs-plugin-ubuntuone.c | 271 +++++++++++++++
src/plugins/gs-snapd.c | 144 +++++----
src/plugins/gs-snapd.h | 18 +-
src/plugins/gs-ubuntuone-dialog.c | 563 --------------------------------
src/plugins/gs-ubuntuone-dialog.h | 45 ---
src/plugins/gs-ubuntuone-dialog.ui | 386 ----------------------
src/plugins/gs-ubuntuone.c | 410 -----------------------
src/plugins/gs-ubuntuone.h | 49 ---
13 files changed, 597 insertions(+), 1605 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 91c990a..313957f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -59,8 +59,6 @@ src/gs-upgrade-banner.c
src/gs-utils.c
[type: gettext/glade]src/gs-menus.ui
src/org.gnome.Software.desktop.in
-src/plugins/gs-ubuntuone-dialog.c
-[type: gettext/glade]src/plugins/gs-ubuntuone-dialog.ui
src/plugins/gs-plugin-snap.c
src/plugins/menu-spec-common.c
[type: gettext/glade]src/gs-popular-tile.ui
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index dd289a2..400f87a 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -34,6 +34,5 @@
<file>gtk-style.css</file>
<file>gtk-style-hc.css</file>
<file>plugins/ubuntu-one.png</file>
- <file preprocess="xml-stripblanks">plugins/gs-ubuntuone-dialog.ui</file>
</gresource>
</gresources>
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index c5b0666..fd65be2 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -14,7 +14,6 @@ AM_CPPFLAGS = \
$(LIMBA_CFLAGS) \
$(XDG_APP_CFLAGS) \
$(OAUTH_CFLAGS) \
- $(LIBSECRET_CFLAGS) \
$(SNAP_CFLAGS) \
-DBINDIR=\"$(bindir)\" \
-DDATADIR=\"$(datadir)\" \
@@ -42,7 +41,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_APT
plugin_LTLIBRARIES += \
@@ -209,10 +209,6 @@ libgs_plugin_hardcoded_blacklist_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
if HAVE_UBUNTU_REVIEWS
libgs_plugin_ubuntu_reviews_la_SOURCES = \
gs-plugin-ubuntu-reviews.c \
- gs-ubuntuone.h \
- gs-ubuntuone.c \
- gs-ubuntuone-dialog.h \
- gs-ubuntuone-dialog.c \
gs-snapd.h \
gs-snapd.c
libgs_plugin_ubuntu_reviews_la_LIBADD = \
@@ -297,10 +293,6 @@ libgs_plugin_packagekit_proxy_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
libgs_plugin_snap_la_SOURCES = \
gs-plugin-snap.c \
- gs-ubuntuone.h \
- gs-ubuntuone.c \
- gs-ubuntuone-dialog.h \
- gs-ubuntuone-dialog.c \
gs-snapd.h \
gs-snapd.c
libgs_plugin_snap_la_LIBADD = \
@@ -310,7 +302,16 @@ libgs_plugin_snap_la_LIBADD = \
$(JSON_GLIB_LIBS) \
$(LIBSECRET_LIBS)
libgs_plugin_snap_la_LDFLAGS = -module -avoid-version
-libgs_plugin_snap_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS) -DUSE_SNAPD
+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)
+libgs_plugin_ubuntuone_la_LDFLAGS = -module -avoid-version
+libgs_plugin_ubuntuone_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
check_PROGRAMS = \
gs-self-test
@@ -328,6 +329,6 @@ gs_self_test_CFLAGS = $(WARN_CFLAGS)
TESTS = gs-self-test
-EXTRA_DIST = moduleset-test.xml gs-ubuntuone-dialog.h gs-ubuntuone-dialog.ui ubuntu-one.png
com.canonical.Unity.Launcher.xml
+EXTRA_DIST = moduleset-test.xml ubuntu-one.png com.canonical.Unity.Launcher.xml
-include $(top_srcdir)/git.mk
diff --git a/src/plugins/gs-plugin-snap.c b/src/plugins/gs-plugin-snap.c
index 90f1aa0..0b37484 100644
--- a/src/plugins/gs-plugin-snap.c
+++ b/src/plugins/gs-plugin-snap.c
@@ -22,10 +22,11 @@
#include <gs-plugin.h>
#include <glib/gi18n.h>
#include <json-glib/json-glib.h>
+#include <snapd-glib/snapd-glib.h>
#include "gs-snapd.h"
-#include "gs-ubuntuone.h"
struct GsPluginPrivate {
+ GsAuth *auth;
};
typedef gboolean (*AppFilterFunc)(const gchar *id, JsonObject *object, gpointer data);
@@ -47,6 +48,26 @@ gs_plugin_initialize (GsPlugin *plugin)
gs_plugin_get_name ());
gs_plugin_set_enabled (plugin, FALSE);
}
+
+ plugin->priv->auth = gs_auth_new ("snapd");
+ gs_auth_set_provider_name (plugin->priv->auth, "Snap Store");
+ gs_auth_set_provider_schema (plugin->priv->auth, "com.ubuntu.UbuntuOne.GnomeSoftware");
+ gs_plugin_add_auth (plugin, plugin->priv->auth);
+}
+
+gboolean
+gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
+{
+ /* load from disk */
+ gs_auth_add_metadata (plugin->priv->auth, "macaroon", NULL);
+ if (!gs_auth_store_load (plugin->priv->auth,
+ GS_AUTH_STORE_FLAG_USERNAME |
+ GS_AUTH_STORE_FLAG_METADATA,
+ cancellable, error))
+ return FALSE;
+
+ /* success */
+ return TRUE;
}
static JsonParser *
@@ -91,12 +112,43 @@ parse_result (const gchar *response, const gchar *response_type, GError **error)
}
static void
+get_macaroon (GsPlugin *plugin, gchar **macaroon, gchar ***discharges)
+{
+ GsAuth *auth;
+ const gchar *serialized_macaroon;
+ g_autoptr(GVariant) macaroon_variant = NULL;
+ g_autoptr (GError) error_local = NULL;
+
+ *macaroon = NULL;
+ *discharges = NULL;
+
+ auth = gs_plugin_get_auth_by_id (plugin, "snapd");
+ if (auth == NULL)
+ return;
+ serialized_macaroon = gs_auth_get_metadata_item (auth, "macaroon");
+ if (serialized_macaroon == NULL)
+ return;
+ macaroon_variant = g_variant_parse (G_VARIANT_TYPE ("(sas)"),
+ serialized_macaroon,
+ NULL,
+ NULL,
+ NULL);
+ if (macaroon_variant == NULL)
+ return;
+ g_variant_get (macaroon_variant, "(s^as)", macaroon, discharges);
+}
+
+static void
refine_app (GsPlugin *plugin, GsApp *app, JsonObject *package, gboolean from_search, GCancellable
*cancellable)
{
+ g_autofree gchar *macaroon = NULL;
+ g_auto(GStrv) discharges = NULL;
const gchar *status, *icon_url, *launch_name = NULL;
g_autoptr(GdkPixbuf) icon_pixbuf = NULL;
gint64 size = -1;
+ get_macaroon (plugin, &macaroon, &discharges);
+
status = json_object_get_string_member (package, "status");
if (g_strcmp0 (status, "installed") == 0 || g_strcmp0 (status, "active") == 0) {
const gchar *update_available;
@@ -136,9 +188,9 @@ refine_app (GsPlugin *plugin, GsApp *app, JsonObject *package, gboolean from_sea
gsize icon_response_length;
if (gs_snapd_request ("GET", icon_url, NULL,
- TRUE, NULL, TRUE, NULL,
- NULL, NULL, NULL,
- &icon_response, &icon_response_length,
+ macaroon, discharges,
+ NULL, NULL,
+ NULL, &icon_response, &icon_response_length,
cancellable, NULL)) {
g_autoptr(GdkPixbufLoader) loader = NULL;
@@ -205,6 +257,8 @@ get_apps (GsPlugin *plugin,
GCancellable *cancellable,
GError **error)
{
+ g_autofree gchar *macaroon = NULL;
+ g_auto(GStrv) discharges = NULL;
guint status_code;
GPtrArray *query_fields;
g_autoptr (GString) path = NULL;
@@ -215,6 +269,8 @@ get_apps (GsPlugin *plugin,
GList *snaps;
GList *l;
+ get_macaroon (plugin, &macaroon, &discharges);
+
/* Get all the apps */
query_fields = g_ptr_array_new_with_free_func (g_free);
if (sources != NULL) {
@@ -248,7 +304,7 @@ get_apps (GsPlugin *plugin,
}
g_ptr_array_free (query_fields, TRUE);
if (!gs_snapd_request ("GET", path->str, NULL,
- TRUE, NULL, TRUE, NULL,
+ macaroon, discharges,
&status_code, &reason_phrase,
&response_type, &response, NULL,
cancellable, error))
@@ -298,6 +354,8 @@ get_apps (GsPlugin *plugin,
static gboolean
get_app (GsPlugin *plugin, GsApp *app, GCancellable *cancellable, GError **error)
{
+ g_autofree gchar *macaroon = NULL;
+ g_auto(GStrv) discharges = NULL;
guint status_code;
g_autofree gchar *path = NULL;
g_autofree gchar *reason_phrase = NULL;
@@ -306,9 +364,11 @@ get_app (GsPlugin *plugin, GsApp *app, GCancellable *cancellable, GError **error
g_autoptr(JsonParser) parser = NULL;
JsonObject *root, *result;
+ get_macaroon (plugin, &macaroon, &discharges);
+
path = g_strdup_printf ("/v2/snaps/%s", gs_app_get_id (app));
if (!gs_snapd_request ("GET", path, NULL,
- TRUE, NULL, TRUE, NULL,
+ macaroon, discharges,
&status_code, &reason_phrase,
&response_type, &response, NULL,
cancellable, error))
@@ -344,6 +404,7 @@ get_app (GsPlugin *plugin, GsApp *app, GCancellable *cancellable, GError **error
void
gs_plugin_destroy (GsPlugin *plugin)
{
+ g_clear_object (&plugin->priv->auth);
}
static gboolean
@@ -403,6 +464,8 @@ send_package_action (GsPlugin *plugin,
GCancellable *cancellable,
GError **error)
{
+ g_autofree gchar *macaroon = NULL;
+ g_auto(GStrv) discharges = NULL;
g_autofree gchar *content = NULL, *path = NULL;
guint status_code;
g_autofree gchar *reason_phrase = NULL;
@@ -414,20 +477,29 @@ send_package_action (GsPlugin *plugin,
JsonArray *tasks;
GList *task_list, *l;
gint64 done, total, task_done, task_total;
- const gchar *resource_path;
+ const gchar *resource_path;
const gchar *type;
const gchar *change_id;
- g_autoptr(GVariant) macaroon = NULL;
+
+ get_macaroon (plugin, &macaroon, &discharges);
content = g_strdup_printf ("{\"action\": \"%s\"}", action);
path = g_strdup_printf ("/v2/snaps/%s", id);
if (!gs_snapd_request ("POST", path, content,
- TRUE, NULL, TRUE, &macaroon,
+ macaroon, discharges,
&status_code, &reason_phrase,
&response_type, &response, NULL,
cancellable, error))
return FALSE;
+ if (status_code == SOUP_STATUS_UNAUTHORIZED) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_REQUIRED,
+ "Requires authentication with @snapd");
+ return FALSE;
+ }
+
if (status_code != SOUP_STATUS_ACCEPTED) {
g_set_error (error,
GS_PLUGIN_ERROR,
@@ -458,7 +530,7 @@ send_package_action (GsPlugin *plugin,
g_usleep (100 * 1000);
if (!gs_snapd_request ("GET", resource_path, NULL,
- TRUE, macaroon, TRUE, NULL,
+ macaroon, discharges,
&status_code, &status_reason_phrase,
&status_response_type, &status_response, NULL,
cancellable, error)) {
@@ -595,3 +667,87 @@ gs_plugin_app_remove (GsPlugin *plugin,
gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
return TRUE;
}
+
+gboolean
+gs_plugin_auth_login (GsPlugin *plugin, GsAuth *auth,
+ GCancellable *cancellable, GError **error)
+{
+ g_autoptr(SnapdAuthData) auth_data = NULL;
+ g_autoptr(GVariant) macaroon_variant = NULL;
+ g_autofree gchar *serialized_macaroon = NULL;
+ g_autoptr(GError) local_error = NULL;
+
+ if (auth != plugin->priv->auth)
+ return TRUE;
+
+ auth_data = snapd_login_sync (gs_auth_get_username (auth), gs_auth_get_password (auth),
gs_auth_get_pin (auth), NULL, &local_error);
+ if (auth_data == NULL) {
+ if (g_error_matches (local_error, SNAPD_ERROR, SNAPD_ERROR_TWO_FACTOR_REQUIRED)) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_PIN_REQUIRED,
+ local_error->message);
+ } else if (g_error_matches (local_error, SNAPD_ERROR, SNAPD_ERROR_AUTH_DATA_INVALID) ||
+ g_error_matches (local_error, SNAPD_ERROR, SNAPD_ERROR_TWO_FACTOR_INVALID)) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_INVALID,
+ local_error->message);
+ } else {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ local_error->message);
+ }
+ return FALSE;
+ }
+
+ macaroon_variant = g_variant_new ("(s^as)",
+ snapd_auth_data_get_macaroon (auth_data),
+ snapd_auth_data_get_discharges (auth_data));
+ serialized_macaroon = g_variant_print (macaroon_variant, FALSE);
+ gs_auth_add_metadata (auth, "macaroon", serialized_macaroon);
+
+ /* store */
+ if (!gs_auth_store_save (auth,
+ GS_AUTH_STORE_FLAG_USERNAME |
+ GS_AUTH_STORE_FLAG_METADATA,
+ cancellable, error))
+ return FALSE;
+
+ gs_auth_add_flags (plugin->priv->auth, GS_AUTH_FLAG_VALID);
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_auth_lost_password (GsPlugin *plugin, GsAuth *auth,
+ GCancellable *cancellable, GError **error)
+{
+ if (auth != plugin->priv->auth)
+ return TRUE;
+
+ // FIXME: snapd might not be using Ubuntu One accounts
+ // https://bugs.launchpad.net/bugs/1598667
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_INVALID,
+ "do online using @https://login.ubuntu.com/+forgot_password");
+ return FALSE;
+}
+
+gboolean
+gs_plugin_auth_register (GsPlugin *plugin, GsAuth *auth,
+ GCancellable *cancellable, GError **error)
+{
+ if (auth != plugin->priv->auth)
+ return TRUE;
+
+ // FIXME: snapd might not be using Ubuntu One accounts
+ // https://bugs.launchpad.net/bugs/1598667
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_INVALID,
+ "do online using @https://login.ubuntu.com/+login");
+ return FALSE;
+}
diff --git a/src/plugins/gs-plugin-ubuntu-reviews.c b/src/plugins/gs-plugin-ubuntu-reviews.c
index 11cd1af..e17fada 100644
--- a/src/plugins/gs-plugin-ubuntu-reviews.c
+++ b/src/plugins/gs-plugin-ubuntu-reviews.c
@@ -31,17 +31,12 @@
#include <gs-plugin.h>
#include <gs-utils.h>
-#include "gs-ubuntuone.h"
#include "gs-os-release.h"
struct GsPluginPrivate {
gchar *db_path;
sqlite3 *db;
gsize db_loaded;
- gchar *consumer_key;
- gchar *consumer_secret;
- gchar *token_key;
- gchar *token_secret;
};
typedef struct {
@@ -113,10 +108,6 @@ gs_plugin_destroy (GsPlugin *plugin)
{
GsPluginPrivate *priv = plugin->priv;
- g_clear_pointer (&priv->token_secret, g_free);
- g_clear_pointer (&priv->token_key, g_free);
- g_clear_pointer (&priv->consumer_secret, g_free);
- g_clear_pointer (&priv->consumer_key, g_free);
g_clear_pointer (&priv->db, sqlite3_close);
g_free (priv->db_path);
}
@@ -370,6 +361,27 @@ parse_review_entries (GsPlugin *plugin, JsonParser *parser, GError **error)
return TRUE;
}
+static gboolean
+get_ubuntuone_token (GsPlugin *plugin,
+ gchar **consumer_key, gchar **consumer_secret,
+ gchar **token_key, gchar **token_secret,
+ GCancellable *cancellable, GError **error)
+{
+ GsAuth *auth = gs_plugin_get_auth_by_id (plugin, "ubuntuone");
+ if (auth == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "No UbuntuOne authentication provider");
+ return FALSE;
+ }
+ *consumer_key = g_strdup (gs_auth_get_metadata_item (auth, "consumer-key"));
+ *consumer_secret = g_strdup (gs_auth_get_metadata_item (auth, "consumer-secret"));
+ *token_key = g_strdup (gs_auth_get_metadata_item (auth, "token-key"));
+ *token_secret = g_strdup (gs_auth_get_metadata_item (auth, "token-secret"));
+ return *consumer_key != NULL && *consumer_secret != NULL && *token_key != NULL && *token_secret !=
NULL;
+}
+
static void
sign_message (SoupMessage *message, OAuthMethod method,
const gchar *consumer_key, const gchar *consumer_secret,
@@ -404,10 +416,24 @@ send_review_request (GsPlugin *plugin,
JsonParser **result,
GCancellable *cancellable, GError **error)
{
- GsPluginPrivate *priv = plugin->priv;
+ 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;
+ 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);
@@ -426,10 +452,8 @@ send_review_request (GsPlugin *plugin,
if (do_sign)
sign_message (msg,
OA_PLAINTEXT,
- priv->consumer_key,
- priv->consumer_secret,
- priv->token_key,
- priv->token_secret);
+ consumer_key, consumer_secret,
+ token_key, token_secret);
*status_code = soup_session_send_message (plugin->soup_session, msg);
@@ -653,10 +677,15 @@ parse_review (GsReview *review, const gchar *our_username, JsonNode *node)
static gboolean
parse_reviews (GsPlugin *plugin, JsonParser *parser, GsApp *app, GError **error)
{
- GsPluginPrivate *priv = plugin->priv;
+ GsAuth *auth;
JsonArray *array;
+ const gchar *consumer_key = NULL;
guint i;
+ auth = gs_plugin_get_auth_by_id (plugin, "ubuntuone");
+ if (auth != NULL)
+ consumer_key = gs_auth_get_metadata_item (auth, "consumer-key");
+
if (!JSON_NODE_HOLDS_ARRAY (json_parser_get_root (parser)))
return FALSE;
array = json_node_get_array (json_parser_get_root (parser));
@@ -665,7 +694,7 @@ parse_reviews (GsPlugin *plugin, JsonParser *parser, GsApp *app, GError **error)
/* Read in from JSON... (skip bad entries) */
review = gs_review_new ();
- if (parse_review (review, priv->consumer_key, json_array_get_element (array, i)))
+ if (parse_review (review, consumer_key, json_array_get_element (array, i)))
gs_app_add_review (app, review);
}
@@ -769,45 +798,11 @@ refine_rating (GsPlugin *plugin, GsApp *app, GCancellable *cancellable, GError *
}
static gboolean
-get_ubuntuone_credentials (GsPlugin *plugin,
- gboolean required,
- GError **error)
-{
- GsPluginPrivate *priv = plugin->priv;
-
- /* Use current credentials if already available */
- if (priv->consumer_key != NULL &&
- priv->consumer_secret != NULL &&
- priv->token_key != NULL &&
- priv->token_secret != NULL)
- return TRUE;
-
- /* Otherwise start with a clean slate */
- g_clear_pointer (&priv->token_secret, g_free);
- g_clear_pointer (&priv->token_key, g_free);
- g_clear_pointer (&priv->consumer_secret, g_free);
- g_clear_pointer (&priv->consumer_key, g_free);
-
- /* Use credentials if we have them */
- if (gs_ubuntuone_get_credentials (&priv->consumer_key, &priv->consumer_secret, &priv->token_key,
&priv->token_secret))
- return TRUE;
-
- /* Otherwise log in to get them */
- if (required)
- return gs_ubuntuone_sign_in (&priv->consumer_key, &priv->consumer_secret, &priv->token_key,
&priv->token_secret, error);
- else
- return TRUE;
-}
-
-static gboolean
refine_reviews (GsPlugin *plugin, GsApp *app, GCancellable *cancellable, GError **error)
{
GPtrArray *sources;
guint i, j;
- if (!get_ubuntuone_credentials (plugin, FALSE, error))
- return FALSE;
-
/* Skip if already has reviews */
if (gs_app_get_reviews (app)->len > 0)
return TRUE;
@@ -869,7 +864,8 @@ gs_plugin_review_submit (GsPlugin *plugin,
GCancellable *cancellable,
GError **error)
{
- GsPluginPrivate *priv = plugin->priv;
+ GsAuth *auth;
+ const gchar *consumer_key = NULL;
gint rating;
gint n_stars;
g_autofree gchar *os_id = NULL, *os_ubuntu_codename = NULL, *language = NULL, *architecture = NULL;
@@ -885,9 +881,6 @@ gs_plugin_review_submit (GsPlugin *plugin,
return FALSE;
}
- if (!get_ubuntuone_credentials (plugin, TRUE, error))
- return FALSE;
-
/* Ubuntu reviews require a summary and description - just make one up for now */
rating = gs_review_get_rating (review);
if (rating > 80)
@@ -942,7 +935,10 @@ gs_plugin_review_submit (GsPlugin *plugin,
}
// Extract new fields from posted review
- parse_review (review, priv->consumer_key, json_parser_get_root (result));
+ auth = gs_plugin_get_auth_by_id (plugin, "ubuntuone");
+ if (auth != NULL)
+ consumer_key = gs_auth_get_metadata_item (auth, "consumer-key");
+ parse_review (review, consumer_key, json_parser_get_root (result));
return TRUE;
}
@@ -965,9 +961,6 @@ gs_plugin_review_report (GsPlugin *plugin,
if (review_id == NULL)
return TRUE;
- if (!get_ubuntuone_credentials (plugin, TRUE, error))
- return FALSE;
-
/* Create message for reviews.ubuntu.com */
reason = soup_uri_encode ("FIXME: gnome-software", NULL);
text = soup_uri_encode ("FIXME: gnome-software", NULL);
@@ -1001,9 +994,6 @@ set_review_usefulness (GsPlugin *plugin,
g_autofree gchar *path = NULL;
guint status_code;
- if (!get_ubuntuone_credentials (plugin, TRUE, error))
- return FALSE;
-
/* Create message for reviews.ubuntu.com */
path = g_strdup_printf ("/api/1.0/reviews/%s/recommendations/?useful=%s",
review_id, is_useful ? "True" : "False");
diff --git a/src/plugins/gs-plugin-ubuntuone.c b/src/plugins/gs-plugin-ubuntuone.c
new file mode 100644
index 0000000..98d0b0c
--- /dev/null
+++ b/src/plugins/gs-plugin-ubuntuone.c
@@ -0,0 +1,271 @@
+/* -*- 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 <gs-plugin.h>
+#include <string.h>
+#include <json-glib/json-glib.h>
+
+// Documented in http://canonical-identity-provider.readthedocs.io
+#define UBUNTU_LOGIN_HOST "https://login.ubuntu.com"
+
+struct GsPluginPrivate {
+ GsAuth *auth;
+};
+
+const gchar *
+gs_plugin_get_name (void)
+{
+ return "ubuntuone";
+}
+
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+ /* create private area */
+ plugin->priv = GS_PLUGIN_GET_PRIVATE (GsPluginPrivate);
+
+ /* check that we are running on Ubuntu */
+ if (!gs_plugin_check_distro_id (plugin, "ubuntu")) {
+ gs_plugin_set_enabled (plugin, FALSE);
+ g_debug ("disabling '%s' as we're not Ubuntu", plugin->name);
+ return;
+ }
+
+ plugin->priv->auth = gs_auth_new (plugin->name);
+ gs_auth_set_provider_name (plugin->priv->auth, "Ubuntu One");
+ gs_auth_set_provider_schema (plugin->priv->auth, "com.ubuntu.UbuntuOne.GnomeSoftware");
+ //gs_auth_set_provider_logo (plugin->priv->auth, "...");
+ gs_plugin_add_auth (plugin, plugin->priv->auth);
+}
+
+gboolean
+gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
+{
+ GsPluginPrivate *priv = plugin->priv;
+
+ /* load from disk */
+ gs_auth_add_metadata (priv->auth, "consumer-key", NULL);
+ gs_auth_add_metadata (priv->auth, "consumer-secret", NULL);
+ gs_auth_add_metadata (priv->auth, "token-key", NULL);
+ gs_auth_add_metadata (priv->auth, "token-secret", NULL);
+ if (!gs_auth_store_load (priv->auth,
+ GS_AUTH_STORE_FLAG_USERNAME |
+ GS_AUTH_STORE_FLAG_METADATA,
+ cancellable, error))
+ return FALSE;
+
+ /* success */
+ return TRUE;
+}
+
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+ GsPluginPrivate *priv = plugin->priv;
+ g_clear_object (&priv->auth);
+}
+
+gboolean
+gs_plugin_auth_login (GsPlugin *plugin, GsAuth *auth,
+ GCancellable *cancellable, GError **error)
+{
+ GsPluginPrivate *priv = plugin->priv;
+ 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 *tmp;
+
+ 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 (plugin->soup_session, 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_AUTH_INVALID,//ACCOUNT_SUSPENDED,
+ message);
+ } else if (g_strcmp0 (code, "ACCOUNT_DEACTIVATED") == 0) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_INVALID,//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_AUTH_INVALID,
+ message);
+ }
+ return FALSE;
+ }
+
+ /* consumer-key */
+ tmp = json_object_get_string_member (json_node_get_object (response_root), "consumer_key");
+ if (tmp == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Response from %s missing required field consumer_key",
+ UBUNTU_LOGIN_HOST);
+ return FALSE;
+ }
+ gs_auth_add_metadata (auth, "consumer-key", tmp);
+
+ /* consumer-secret */
+ tmp = json_object_get_string_member (json_node_get_object (response_root), "consumer_secret");
+ if (tmp == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Response from %s missing required field consumer_secret",
+ UBUNTU_LOGIN_HOST);
+ return FALSE;
+ }
+ gs_auth_add_metadata (auth, "consumer-secret", tmp);
+
+ /* token-key */
+ tmp = json_object_get_string_member (json_node_get_object (response_root), "token_key");
+ if (tmp == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Response from %s missing required field token_key",
+ UBUNTU_LOGIN_HOST);
+ return FALSE;
+ }
+ gs_auth_add_metadata (auth, "token-key", tmp);
+
+ /* token-secret */
+ tmp = json_object_get_string_member (json_node_get_object (response_root), "token_secret");
+ if (tmp == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Response from %s missing required field token_secret",
+ UBUNTU_LOGIN_HOST);
+ return FALSE;
+ }
+ gs_auth_add_metadata (auth, "token-secret", tmp);
+
+ /* store */
+ if (!gs_auth_store_save (auth,
+ GS_AUTH_STORE_FLAG_USERNAME |
+ GS_AUTH_STORE_FLAG_METADATA,
+ cancellable, error))
+ return FALSE;
+
+ gs_auth_add_flags (priv->auth, GS_AUTH_FLAG_VALID);
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_auth_lost_password (GsPlugin *plugin, GsAuth *auth,
+ GCancellable *cancellable, GError **error)
+{
+ GsPluginPrivate *priv = plugin->priv;
+
+ 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)
+{
+ GsPluginPrivate *priv = plugin->priv;
+
+ 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;
+}
diff --git a/src/plugins/gs-snapd.c b/src/plugins/gs-snapd.c
index 4f1d39a..e158fc9 100644
--- a/src/plugins/gs-snapd.c
+++ b/src/plugins/gs-snapd.c
@@ -26,7 +26,6 @@
#include <gio/gunixsocketaddress.h>
#include "gs-snapd.h"
-#include "gs-ubuntuone.h"
// snapd API documentation is at https://github.com/snapcore/snapd/blob/master/docs/rest.md
@@ -96,10 +95,8 @@ gboolean
gs_snapd_request (const gchar *method,
const gchar *path,
const gchar *content,
- gboolean authenticate,
- GVariant *macaroon,
- gboolean retry_after_login,
- GVariant **out_macaroon,
+ const gchar *macaroon,
+ gchar **discharges,
guint *status_code,
gchar **reason_phrase,
gchar **response_type,
@@ -114,19 +111,9 @@ gs_snapd_request (const gchar *method,
gsize max_data_length = 65535, data_length = 0, header_length;
gchar data[max_data_length + 1], *body = NULL;
g_autoptr (SoupMessageHeaders) headers = NULL;
- g_autoptr(GVariant) auto_macaroon = NULL;
gsize chunk_length = 0, n_required;
gchar *chunk_start = NULL;
- const gchar *root;
- const gchar *discharge;
- GVariantIter *iter;
guint code;
- gboolean ret;
-
- if (macaroon == NULL && authenticate) {
- auto_macaroon = gs_ubuntuone_get_macaroon (TRUE, FALSE, NULL);
- macaroon = auto_macaroon;
- }
// NOTE: Would love to use libsoup but it doesn't support unix sockets
// https://bugzilla.gnome.org/show_bug.cgi?id=727563
@@ -139,13 +126,11 @@ gs_snapd_request (const gchar *method,
g_string_append_printf (request, "%s %s HTTP/1.1\r\n", method, path);
g_string_append (request, "Host:\r\n");
if (macaroon != NULL) {
- g_variant_get (macaroon, "(&sas)", &root, &iter);
- g_string_append_printf (request, "Authorization: Macaroon root=\"%s\"", root);
+ gint i;
- while (g_variant_iter_next (iter, "&s", &discharge))
- g_string_append_printf (request, ",discharge=\"%s\"", discharge);
-
- g_variant_iter_free (iter);
+ g_string_append_printf (request, "Authorization: Macaroon root=\"%s\"", macaroon);
+ for (i = 0; discharges[i] != NULL; i++)
+ g_string_append_printf (request, ",discharge=\"%s\"", discharges[i]);
g_string_append (request, "\r\n");
}
if (content)
@@ -199,45 +184,6 @@ gs_snapd_request (const gchar *method,
if (status_code != NULL)
*status_code = code;
- if ((code == 401 || code == 403) && retry_after_login) {
- g_socket_close (socket, NULL);
-
- gs_ubuntuone_clear_macaroon ();
-
- macaroon = gs_ubuntuone_get_macaroon (FALSE, TRUE, NULL);
-
- if (macaroon == NULL) {
- g_set_error_literal (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_FAILED,
- "failed to authenticate");
- return FALSE;
- }
-
- ret = gs_snapd_request (method,
- path,
- content,
- TRUE,
- macaroon,
- FALSE,
- NULL,
- status_code,
- reason_phrase,
- response_type,
- response,
- response_length,
- cancellable,
- error);
-
- if (ret && out_macaroon != NULL) {
- *out_macaroon = macaroon;
- } else {
- g_variant_unref (macaroon);
- }
-
- return ret;
- }
-
/* read content */
switch (soup_message_headers_get_encoding (headers)) {
case SOUP_ENCODING_EOF:
@@ -340,8 +286,6 @@ gs_snapd_request (const gchar *method,
return FALSE;
}
- if (out_macaroon != NULL)
- *out_macaroon = g_variant_ref (macaroon);
if (response_type)
*response_type = g_strdup (soup_message_headers_get_content_type (headers, NULL));
if (response) {
@@ -357,3 +301,79 @@ gs_snapd_request (const gchar *method,
return TRUE;
}
+
+gboolean
+gs_snapd_parse_result (const gchar *response_type,
+ const gchar *response,
+ JsonObject **result,
+ GError **error)
+{
+ g_autoptr(JsonParser) parser = NULL;
+ g_autoptr(GError) error_local = NULL;
+ JsonObject *root;
+
+ if (response_type == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned no content type");
+ return FALSE;
+ }
+ if (g_strcmp0 (response_type, "application/json") != 0) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned unexpected content type %s", response_type);
+ return FALSE;
+ }
+
+ parser = json_parser_new ();
+ if (!json_parser_load_from_data (parser, response, -1, &error_local)) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Unable to parse snapd response: %s",
+ error_local->message);
+ return FALSE;
+ }
+
+ if (!JSON_NODE_HOLDS_OBJECT (json_parser_get_root (parser))) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd response does is not a valid JSON object");
+ return FALSE;
+ }
+ root = json_node_get_object (json_parser_get_root (parser));
+ if (!json_object_has_member (root, "result")) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd response does not contain a \"result\" field");
+ return FALSE;
+ }
+ if (result != NULL)
+ *result = json_object_ref (json_object_get_object_member (root, "result"));
+
+ return TRUE;
+}
+
+gboolean
+gs_snapd_parse_error (const gchar *response_type,
+ const gchar *response,
+ gchar **message,
+ gchar **kind,
+ GError **error)
+{
+ g_autoptr(JsonObject) result = NULL;
+
+ if (!gs_snapd_parse_result (response_type, response, &result, error))
+ return FALSE;
+
+ if (message != NULL)
+ *message = g_strdup (json_object_get_string_member (result, "message"));
+ if (kind != NULL)
+ *kind = json_object_has_member (result, "kind") ? g_strdup (json_object_get_string_member
(result, "kind")) : NULL;
+
+ return TRUE;
+}
diff --git a/src/plugins/gs-snapd.h b/src/plugins/gs-snapd.h
index 27540d0..419253f 100644
--- a/src/plugins/gs-snapd.h
+++ b/src/plugins/gs-snapd.h
@@ -23,16 +23,15 @@
#define __GS_SNAPD_H__
#include <gio/gio.h>
+#include <json-glib/json-glib.h>
gboolean gs_snapd_exists (void);
gboolean gs_snapd_request (const gchar *method,
const gchar *path,
const gchar *content,
- gboolean authenticate,
- GVariant *macaroon,
- gboolean retry_after_login,
- GVariant **out_macaroon,
+ const gchar *macaroon,
+ gchar **discharges,
guint *status_code,
gchar **reason_phrase,
gchar **response_type,
@@ -41,4 +40,15 @@ gboolean gs_snapd_request (const gchar *method,
GCancellable *cancellable,
GError **error);
+gboolean gs_snapd_parse_result (const gchar *response_type,
+ const gchar *response,
+ JsonObject **result,
+ GError **error);
+
+gboolean gs_snapd_parse_error (const gchar *response_type,
+ const gchar *response,
+ gchar **message,
+ gchar **kind,
+ GError **error);
+
#endif /* __GS_SNAPD_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]