[gnome-software/wip/rancell/snapauth] snapd: Use snap authentication



commit 0a1e279532bab11ec7b27300bb32fd41cb2b5b86
Author: Robert Ancell <robert ancell canonical com>
Date:   Fri Jul 1 15:33:07 2016 +1200

    snapd: Use snap authentication

 src/plugins/gs-plugin-snap.c |  143 +++++++++++++++++++++++++++++++++++++++---
 src/plugins/gs-snapd.c       |   94 ++++++++++++++++-----------
 src/plugins/gs-snapd.h       |    9 ++-
 3 files changed, 197 insertions(+), 49 deletions(-)
---
diff --git a/src/plugins/gs-plugin-snap.c b/src/plugins/gs-plugin-snap.c
index 3e15380..8b2b8c0 100644
--- a/src/plugins/gs-plugin-snap.c
+++ b/src/plugins/gs-plugin-snap.c
@@ -26,17 +26,27 @@
 
 #include "gs-snapd.h"
 
+struct GsPluginData {
+       GsAuth          *auth;
+};
+
 typedef gboolean (*AppFilterFunc)(const gchar *id, JsonObject *object, gpointer data);
 
 void
 gs_plugin_initialize (GsPlugin *plugin)
 {
+       GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
+
        if (!gs_snapd_exists ()) {
                g_debug ("disabling '%s' as snapd not running",
                         gs_plugin_get_name (plugin));
                gs_plugin_set_enabled (plugin, FALSE);
        }
 
+       priv->auth = gs_auth_new ("snapd");
+       gs_auth_set_provider_name (priv->auth, "Snap Store");
+       gs_plugin_add_auth (plugin, priv->auth);
+
        gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "desktop-categories");
        gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "ubuntu-reviews");
        gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_BETTER_THAN, "packagekit");
@@ -124,7 +134,7 @@ refine_app (GsPlugin *plugin, GsApp *app, JsonObject *package)
                g_autofree gchar *icon_response = NULL;
                gsize icon_response_length;
 
-               if (gs_snapd_request ("GET", icon_url, NULL, NULL, TRUE, NULL,
+               if (gs_snapd_request (SOUP_METHOD_GET, icon_url, NULL, NULL,
                                      NULL, NULL, NULL,
                                      &icon_response, &icon_response_length,
                                      NULL)) {
@@ -207,7 +217,7 @@ get_apps (GsPlugin *plugin,
                g_string_append (path, fields);
        }
        g_ptr_array_free (query_fields, TRUE);
-       if (!gs_snapd_request ("GET", path->str, NULL, NULL, TRUE, NULL,
+       if (!gs_snapd_request (SOUP_METHOD_GET, path->str, NULL, NULL,
                               &status_code, &reason_phrase,
                               &response_type, &response,
                               NULL, error))
@@ -271,7 +281,7 @@ get_app (GsPlugin *plugin, GsApp *app, GError **error)
        guint i;
 
        path = g_strdup_printf ("/v2/snaps/%s", gs_app_get_id (app));
-       if (!gs_snapd_request ("GET", path, NULL, NULL, TRUE, NULL,
+       if (!gs_snapd_request (SOUP_METHOD_GET, path, NULL, NULL,
                               &status_code, &reason_phrase,
                               &response_type, &response,
                               NULL, error))
@@ -284,7 +294,7 @@ get_app (GsPlugin *plugin, GsApp *app, GError **error)
                g_clear_pointer (&response, g_free);
 
                path = g_strdup_printf ("/v2/find?q=%s", gs_app_get_id (app));
-               if (!gs_snapd_request ("GET", path, NULL, NULL, TRUE, NULL,
+               if (!gs_snapd_request (SOUP_METHOD_GET, path, NULL, NULL,
                                       &status_code, &reason_phrase,
                                       &response_type, &response,
                                       NULL, error))
@@ -337,6 +347,8 @@ get_app (GsPlugin *plugin, GsApp *app, GError **error)
 void
 gs_plugin_destroy (GsPlugin *plugin)
 {
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_clear_object (&priv->auth);
 }
 
 static gboolean
@@ -401,16 +413,22 @@ send_package_action (GsPlugin *plugin,
         const gchar *resource_path;
        const gchar *type;
        const gchar *change_id;
-       g_autoptr(GVariant) macaroon = NULL;
 
        content = g_strdup_printf ("{\"action\": \"%s\"}", action);
        path = g_strdup_printf ("/v2/snaps/%s", id);
-       if (!gs_snapd_request ("POST", path, content, NULL, TRUE,
-                              &macaroon, &status_code,
-                              &reason_phrase, &response_type,
+       if (!gs_snapd_request (SOUP_METHOD_POST, path, content, NULL,
+                              &status_code, &reason_phrase, &response_type,
                               &response, NULL, 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,
@@ -440,7 +458,7 @@ send_package_action (GsPlugin *plugin,
                        /* Wait for a little bit before polling */
                        g_usleep (100 * 1000);
 
-                       if (!gs_snapd_request ("GET", resource_path, NULL, macaroon, TRUE, NULL,
+                       if (!gs_snapd_request (SOUP_METHOD_GET, resource_path, NULL, NULL,
                                               &status_code, &status_reason_phrase,
                                               &status_response_type, &status_response,
                                               NULL, error)) {
@@ -545,3 +563,110 @@ 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)
+{
+       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;
+       guint status_code;
+       g_autofree gchar *reason_phrase = NULL;
+       g_autofree gchar *response_type = NULL;
+       g_autofree gchar *response = NULL;
+
+       if (auth != priv->auth)
+               return TRUE;
+
+       builder = json_builder_new ();
+       json_builder_begin_object (builder);
+       json_builder_set_member_name (builder, "username");
+       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;
+       }
+
+       if (!gs_snapd_request (SOUP_METHOD_POST, "/v2/login", data, NULL,
+                              &status_code, &reason_phrase,
+                              &response_type, &response, NULL,
+                              error))
+               return FALSE;
+
+       if (status_code != SOUP_STATUS_OK) {
+               g_autofree gchar *error_message = NULL;
+               g_autofree gchar *error_kind = NULL;
+
+               if (!gs_snapd_parse_error (response_type, response, &error_message, &error_kind, error))
+                       return FALSE;
+
+               if (g_strcmp0 (error_kind, "two-factor-required") == 0) {
+                       g_set_error_literal (error,
+                                            GS_PLUGIN_ERROR,
+                                            GS_PLUGIN_ERROR_PIN_REQUIRED,
+                                            error_message);
+               }
+               else {
+                       g_set_error_literal (error,
+                                            GS_PLUGIN_ERROR,
+                                            GS_PLUGIN_ERROR_FAILED,
+                                            error_message);
+               }
+               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;
+}
diff --git a/src/plugins/gs-snapd.c b/src/plugins/gs-snapd.c
index c11b1ce..23cee4b 100644
--- a/src/plugins/gs-snapd.c
+++ b/src/plugins/gs-snapd.c
@@ -22,6 +22,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <gs-plugin.h>
+#include <json-glib/json-glib.h>
 #include <libsoup/soup.h>
 #include <gio/gunixsocketaddress.h>
 
@@ -95,8 +96,6 @@ gs_snapd_request (const gchar  *method,
                  const gchar  *path,
                  const gchar  *content,
                  GVariant     *macaroon,
-                 gboolean      retry_after_login,
-                 GVariant    **out_macaroon,
                  guint        *status_code,
                  gchar       **reason_phrase,
                  gchar       **response_type,
@@ -116,7 +115,6 @@ gs_snapd_request (const gchar  *method,
        const gchar *discharge;
        GVariantIter *iter;
        guint code;
-       gboolean ret;
 
        // NOTE: Would love to use libsoup but it doesn't support unix sockets
        // https://bugzilla.gnome.org/show_bug.cgi?id=727563
@@ -187,39 +185,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);
-
-               if (macaroon == NULL) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_AUTH_REQUIRED,
-                                            "failed to authenticate");
-                       return FALSE;
-               }
-
-               ret = gs_snapd_request (method,
-                                       path,
-                                       content,
-                                       macaroon,
-                                       FALSE,
-                                       NULL,
-                                       status_code,
-                                       reason_phrase,
-                                       response_type,
-                                       response,
-                                       response_length,
-                                       error);
-
-               if (ret && out_macaroon != NULL) {
-                       *out_macaroon = macaroon;
-               } else {
-                       g_variant_unref (macaroon);
-               }
-
-               return ret;
-       }
-
        /* work out how much data to follow */
        if (g_strcmp0 (soup_message_headers_get_one (headers, "Transfer-Encoding"),
                       "chunked") == 0) {
@@ -281,8 +246,6 @@ gs_snapd_request (const gchar  *method,
                                      error))
                        return FALSE;
 
-       if (out_macaroon != NULL)
-               *out_macaroon = g_variant_ref (macaroon);
        if (response_type)
                *response_type = g_strdup (soup_message_headers_get_one (headers, "Content-Type"));
        if (response) {
@@ -296,3 +259,58 @@ gs_snapd_request (const gchar  *method,
 
        return TRUE;
 }
+
+gboolean
+gs_snapd_parse_error (const gchar      *response_type,
+                     const gchar       *response,
+                     gchar             **error_message,
+                     gchar             **error_kind,
+                     GError            **error)
+{
+       g_autoptr(JsonParser) parser = NULL;
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(JsonObject) root = NULL;
+       g_autoptr(JsonObject) result_object = NULL;
+
+       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));
+       result_object = json_object_get_object_member (root, "result");
+       if (error_message)
+               *error_message = json_object_get_string_member (result_object, "message");
+       if (error_kind)
+               *error_kind = json_object_get_string_member (result_object, "kind");
+
+       return TRUE;
+}
diff --git a/src/plugins/gs-snapd.h b/src/plugins/gs-snapd.h
index 58ac6fe..631f0e7 100644
--- a/src/plugins/gs-snapd.h
+++ b/src/plugins/gs-snapd.h
@@ -30,8 +30,6 @@ gboolean gs_snapd_request     (const gchar    *method,
                                 const gchar    *path,
                                 const gchar    *content,
                                 GVariant       *macaroon,
-                                gboolean        retry_after_login,
-                                GVariant       **out_macaroon,
                                 guint          *status_code,
                                 gchar          **reason_phrase,
                                 gchar          **response_type,
@@ -39,4 +37,11 @@ gboolean gs_snapd_request    (const gchar    *method,
                                 gsize          *response_length,
                                 GError         **error);
 
+gboolean gs_snapd_parse_error  (const gchar    *response_type,
+                                const gchar    *response,
+                                gchar          **error_message,
+                                gchar          **error_kind,
+                                GError         **error);
+
+
 #endif /* __GS_SNAPD_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]