OCI: Add flatpak_oci_registry_get_token() method

This tries to do a HEAD request to the repository and if that fails
with 401 uses the OCI mechanism to (given a basic auth token) generate
a token for the download.
This commit is contained in:
Alexander Larsson
2019-12-06 19:18:44 +01:00
committed by Alexander Larsson
parent b1a8852895
commit 461a592b8e
2 changed files with 165 additions and 0 deletions

View File

@@ -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,

View File

@@ -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,