[gnome-software/wip/ubuntu-xenial] Switch Snap and Ubuntu review plugins to use GsAuth



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]