diff --git a/common/flatpak-oci-registry-private.h b/common/flatpak-oci-registry-private.h index 7b89843d..1804e43b 100644 --- a/common/flatpak-oci-registry-private.h +++ b/common/flatpak-oci-registry-private.h @@ -78,6 +78,12 @@ int flatpak_oci_registry_download_blob (FlatpakOciRegistry gpointer user_data, GCancellable *cancellable, GError **error); +char * flatpak_oci_registry_get_token (FlatpakOciRegistry *self, + const char *repository, + const char *digest, + const char *basic_auth, + GCancellable *cancellable, + GError **error); GBytes * flatpak_oci_registry_load_blob (FlatpakOciRegistry *self, const char *repository, gboolean manifest, diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c index 224cda18..b02ce7f7 100644 --- a/common/flatpak-oci-registry.c +++ b/common/flatpak-oci-registry.c @@ -879,6 +879,165 @@ flatpak_oci_registry_mirror_blob (FlatpakOciRegistry *self, return TRUE; } +static const char * +object_get_string_member_with_default (JsonNode *json, + const char *member_name, + const char *default_value) +{ + JsonNode *node; + + if (json == NULL || !JSON_NODE_HOLDS_OBJECT(json)) + return default_value; + + node = json_object_get_member (json_node_get_object (json), member_name); + + if (node == NULL || JSON_NODE_HOLDS_NULL (node) || JSON_NODE_TYPE (node) != JSON_NODE_VALUE) + return default_value; + + return json_node_get_string (node); +} + + +static char * +get_token_for_www_auth (FlatpakOciRegistry *self, + const char *www_authenticate, + const char *auth, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GInputStream) auth_stream = NULL; + g_autoptr(SoupMessage) auth_msg = NULL; + g_autoptr(GHashTable) params = NULL; + g_autoptr(GHashTable) args = NULL; + const char *realm, *service, *scope, *token; + g_autoptr(SoupURI) auth_uri = NULL; + g_autoptr(GBytes) body = NULL; + g_autoptr(JsonNode) json = NULL; + + if (g_ascii_strncasecmp (www_authenticate, "Bearer ", strlen ("Bearer ")) != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Only Bearer authentication supported"); + return NULL; + } + + params = soup_header_parse_param_list (www_authenticate + strlen ("Bearer ")); + + realm = g_hash_table_lookup (params, "realm"); + if (realm == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Only realm in authentication request"); + return NULL; + } + + auth_uri = soup_uri_new (realm); + if (auth_uri == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid realm in authentication request"); + return NULL; + } + + args = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + service = g_hash_table_lookup (params, "service"); + if (service) + g_hash_table_insert (args, "service", (char *)service); + scope = g_hash_table_lookup (params, "scope"); + if (scope) + g_hash_table_insert (args, "scope", (char *)scope); + + soup_uri_set_query_from_form (auth_uri, args); + + auth_msg = soup_message_new_from_uri ("GET", auth_uri); + + g_autofree char *basic_auth = g_strdup_printf ("Basic %s", auth); + soup_message_headers_replace (auth_msg->request_headers, "Authorization", basic_auth); + + auth_stream = soup_session_send (self->soup_session, auth_msg, NULL, error); + if (auth_stream == NULL) + return NULL; + + body = flatpak_read_stream (auth_stream, TRUE, error); + if (body == NULL) + return NULL; + + json = json_from_string ((char *)g_bytes_get_data (body, NULL), error); + if (json == NULL) + return NULL; + + token = object_get_string_member_with_default (json, "token", NULL); + if (token == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid authentication request response"); + return NULL; + } + + return g_strdup (token); +} + +char * +flatpak_oci_registry_get_token (FlatpakOciRegistry *self, + const char *repository, + const char *digest, + const char *basic_auth, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *subpath = NULL; + g_autoptr(SoupURI) uri = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autofree char *uri_s = NULL; + g_autoptr(GInputStream) stream = NULL; + g_autoptr(SoupMessage) msg = NULL; + g_autofree char *www_authenticate = NULL; + g_autofree char *token = NULL; + + g_assert (self->valid); + + subpath = get_digest_subpath (self, repository, TRUE, digest, error); + if (subpath == NULL) + return NULL; + + if (self->dfd != -1) + return g_strdup (""); // No tokens for local repos + + uri = soup_uri_new_with_base (self->base_uri, subpath); + if (uri == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Invalid relative url %s", subpath); + return NULL; + } + + msg = soup_message_new_from_uri ("HEAD", uri); + + stream = soup_session_send (self->soup_session, msg, NULL, error); + if (stream == NULL) + return NULL; + + if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) + { + return g_strdup (""); + } + else if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unexpected response status %d from repo", msg->status_code); + return NULL; + } + + /* Need www-authenticated header */ + www_authenticate = g_strdup (soup_message_headers_get_one (msg->response_headers, "WWW-Authenticate")); + if (www_authenticate == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Now WWW-Authenticate header from repo"); + return NULL; + } + + token = get_token_for_www_auth (self, www_authenticate, basic_auth, cancellable, error); + if (token == NULL) + return NULL; + + return g_steal_pointer (&token); +} + GBytes * flatpak_oci_registry_load_blob (FlatpakOciRegistry *self, const char *repository,