Blame SOURCES/0001-Add-basic-auth-support-to-flatpak-plugin.patch

a7f877
From b6a41a1b9e9020a23dbc418183ebe4746b6ec027 Mon Sep 17 00:00:00 2001
a7f877
From: Kalev Lember <klember@redhat.com>
a7f877
Date: Mon, 18 May 2020 14:45:35 +0200
a7f877
Subject: [PATCH 1/2] Add basic auth support to flatpak plugin
a7f877
a7f877
This is useful for e.g. OCI remotes that can use basic auth.
a7f877
a7f877
All user visible strings in the basic auth dialog are taken from the
a7f877
flatpak CLI client.
a7f877
---
a7f877
 lib/gs-plugin-loader.c              |  29 +++-
a7f877
 lib/gs-plugin-loader.h              |   7 +-
a7f877
 lib/gs-plugin.c                     |  68 +++++++++-
a7f877
 lib/gs-plugin.h                     |  13 +-
a7f877
 plugins/flatpak/gs-plugin-flatpak.c |  55 +++++++-
a7f877
 po/POTFILES.in                      |   2 +
a7f877
 src/gnome-software.gresource.xml    |   1 +
a7f877
 src/gs-basic-auth-dialog.c          | 130 ++++++++++++++++++
a7f877
 src/gs-basic-auth-dialog.h          |  28 ++++
a7f877
 src/gs-basic-auth-dialog.ui         | 203 ++++++++++++++++++++++++++++
a7f877
 src/gs-shell.c                      |  25 +++-
a7f877
 src/meson.build                     |   1 +
a7f877
 12 files changed, 556 insertions(+), 6 deletions(-)
a7f877
 create mode 100644 src/gs-basic-auth-dialog.c
a7f877
 create mode 100644 src/gs-basic-auth-dialog.h
a7f877
 create mode 100644 src/gs-basic-auth-dialog.ui
a7f877
a7f877
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
a7f877
index 979f3d5d..35382e3f 100644
a7f877
--- a/lib/gs-plugin-loader.c
a7f877
+++ b/lib/gs-plugin-loader.c
a7f877
@@ -1,7 +1,7 @@
a7f877
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
a7f877
  *
a7f877
  * Copyright (C) 2007-2018 Richard Hughes <richard@hughsie.com>
a7f877
- * Copyright (C) 2014-2018 Kalev Lember <klember@redhat.com>
a7f877
+ * Copyright (C) 2014-2020 Kalev Lember <klember@redhat.com>
a7f877
  *
a7f877
  * SPDX-License-Identifier: GPL-2.0+
a7f877
  */
a7f877
@@ -74,6 +74,7 @@ enum {
a7f877
 	SIGNAL_PENDING_APPS_CHANGED,
a7f877
 	SIGNAL_UPDATES_CHANGED,
a7f877
 	SIGNAL_RELOAD,
a7f877
+	SIGNAL_BASIC_AUTH_START,
a7f877
 	SIGNAL_LAST
a7f877
 };
a7f877
 
a7f877
@@ -2016,6 +2017,23 @@ gs_plugin_loader_status_changed_cb (GsPlugin *plugin,
a7f877
 		       0, app, status);
a7f877
 }
a7f877
 
a7f877
+static void
a7f877
+gs_plugin_loader_basic_auth_start_cb (GsPlugin *plugin,
a7f877
+                                      const gchar *remote,
a7f877
+                                      const gchar *realm,
a7f877
+                                      GCallback callback,
a7f877
+                                      gpointer user_data,
a7f877
+                                      GsPluginLoader *plugin_loader)
a7f877
+{
a7f877
+	g_debug ("emitting basic-auth-start %s", realm);
a7f877
+	g_signal_emit (plugin_loader,
a7f877
+		       signals[SIGNAL_BASIC_AUTH_START], 0,
a7f877
+		       remote,
a7f877
+		       realm,
a7f877
+		       callback,
a7f877
+		       user_data);
a7f877
+}
a7f877
+
a7f877
 static gboolean
a7f877
 gs_plugin_loader_job_actions_changed_delay_cb (gpointer user_data)
a7f877
 {
a7f877
@@ -2102,6 +2120,9 @@ gs_plugin_loader_open_plugin (GsPluginLoader *plugin_loader,
a7f877
 	g_signal_connect (plugin, "status-changed",
a7f877
 			  G_CALLBACK (gs_plugin_loader_status_changed_cb),
a7f877
 			  plugin_loader);
a7f877
+	g_signal_connect (plugin, "basic-auth-start",
a7f877
+			  G_CALLBACK (gs_plugin_loader_basic_auth_start_cb),
a7f877
+			  plugin_loader);
a7f877
 	g_signal_connect (plugin, "report-event",
a7f877
 			  G_CALLBACK (gs_plugin_loader_report_event_cb),
a7f877
 			  plugin_loader);
a7f877
@@ -2712,6 +2733,12 @@ gs_plugin_loader_class_init (GsPluginLoaderClass *klass)
a7f877
 			      G_STRUCT_OFFSET (GsPluginLoaderClass, reload),
a7f877
 			      NULL, NULL, g_cclosure_marshal_VOID__VOID,
a7f877
 			      G_TYPE_NONE, 0);
a7f877
+	signals [SIGNAL_BASIC_AUTH_START] =
a7f877
+		g_signal_new ("basic-auth-start",
a7f877
+			      G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
a7f877
+			      G_STRUCT_OFFSET (GsPluginLoaderClass, basic_auth_start),
a7f877
+			      NULL, NULL, g_cclosure_marshal_generic,
a7f877
+			      G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER);
a7f877
 }
a7f877
 
a7f877
 static void
a7f877
diff --git a/lib/gs-plugin-loader.h b/lib/gs-plugin-loader.h
a7f877
index 74cbfa53..e88ea2d1 100644
a7f877
--- a/lib/gs-plugin-loader.h
a7f877
+++ b/lib/gs-plugin-loader.h
a7f877
@@ -1,7 +1,7 @@
a7f877
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
a7f877
  *
a7f877
  * Copyright (C) 2007-2017 Richard Hughes <richard@hughsie.com>
a7f877
- * Copyright (C) 2015 Kalev Lember <klember@redhat.com>
a7f877
+ * Copyright (C) 2015-2020 Kalev Lember <klember@redhat.com>
a7f877
  *
a7f877
  * SPDX-License-Identifier: GPL-2.0+
a7f877
  */
a7f877
@@ -31,6 +31,11 @@ struct _GsPluginLoaderClass
a7f877
 	void			(*pending_apps_changed)	(GsPluginLoader	*plugin_loader);
a7f877
 	void			(*updates_changed)	(GsPluginLoader	*plugin_loader);
a7f877
 	void			(*reload)		(GsPluginLoader	*plugin_loader);
a7f877
+	void			(*basic_auth_start)	(GsPluginLoader	*plugin_loader,
a7f877
+							 const gchar	*remote,
a7f877
+							 const gchar	*realm,
a7f877
+							 GCallback	 callback,
a7f877
+							 gpointer	 user_data);
a7f877
 };
a7f877
 
a7f877
 GsPluginLoader	*gs_plugin_loader_new			(void);
a7f877
diff --git a/lib/gs-plugin.c b/lib/gs-plugin.c
a7f877
index 5aed1058..3f63fa97 100644
a7f877
--- a/lib/gs-plugin.c
a7f877
+++ b/lib/gs-plugin.c
a7f877
@@ -1,7 +1,7 @@
a7f877
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
a7f877
  *
a7f877
  * Copyright (C) 2013-2016 Richard Hughes <richard@hughsie.com>
a7f877
- * Copyright (C) 2014-2018 Kalev Lember <klember@redhat.com>
a7f877
+ * Copyright (C) 2014-2020 Kalev Lember <klember@redhat.com>
a7f877
  *
a7f877
  * SPDX-License-Identifier: GPL-2.0+
a7f877
  */
a7f877
@@ -87,6 +87,7 @@ enum {
a7f877
 	SIGNAL_RELOAD,
a7f877
 	SIGNAL_REPORT_EVENT,
a7f877
 	SIGNAL_ALLOW_UPDATES,
a7f877
+	SIGNAL_BASIC_AUTH_START,
a7f877
 	SIGNAL_LAST
a7f877
 };
a7f877
 
a7f877
@@ -851,6 +852,64 @@ gs_plugin_status_update (GsPlugin *plugin, GsApp *app, GsPluginStatus status)
a7f877
 	g_source_attach (idle_source, NULL);
a7f877
 }
a7f877
 
a7f877
+typedef struct {
a7f877
+	GsPlugin	*plugin;
a7f877
+	gchar		*remote;
a7f877
+	gchar		*realm;
a7f877
+	GCallback	 callback;
a7f877
+	gpointer	 user_data;
a7f877
+} GsPluginBasicAuthHelper;
a7f877
+
a7f877
+static gboolean
a7f877
+gs_plugin_basic_auth_start_cb (gpointer user_data)
a7f877
+{
a7f877
+	GsPluginBasicAuthHelper *helper = user_data;
a7f877
+	g_signal_emit (helper->plugin,
a7f877
+		       signals[SIGNAL_BASIC_AUTH_START], 0,
a7f877
+		       helper->remote,
a7f877
+		       helper->realm,
a7f877
+		       helper->callback,
a7f877
+		       helper->user_data);
a7f877
+	g_free (helper->remote);
a7f877
+	g_free (helper->realm);
a7f877
+	g_slice_free (GsPluginBasicAuthHelper, helper);
a7f877
+	return FALSE;
a7f877
+}
a7f877
+
a7f877
+/**
a7f877
+ * gs_plugin_basic_auth_start:
a7f877
+ * @plugin: a #GsPlugin
a7f877
+ * @remote: a string
a7f877
+ * @realm: a string
a7f877
+ * @callback: callback to invoke to submit the user/password
a7f877
+ * @user_data: callback data to pass to the callback
a7f877
+ *
a7f877
+ * Emit the basic-auth-start signal in the main thread.
a7f877
+ *
a7f877
+ * Since: 3.38
a7f877
+ **/
a7f877
+void
a7f877
+gs_plugin_basic_auth_start (GsPlugin *plugin,
a7f877
+                            const gchar *remote,
a7f877
+                            const gchar *realm,
a7f877
+                            GCallback callback,
a7f877
+                            gpointer user_data)
a7f877
+{
a7f877
+	GsPluginBasicAuthHelper *helper;
a7f877
+	g_autoptr(GSource) idle_source = NULL;
a7f877
+
a7f877
+	helper = g_slice_new0 (GsPluginBasicAuthHelper);
a7f877
+	helper->plugin = plugin;
a7f877
+	helper->remote = g_strdup (remote);
a7f877
+	helper->realm = g_strdup (realm);
a7f877
+	helper->callback = callback;
a7f877
+	helper->user_data = user_data;
a7f877
+
a7f877
+	idle_source = g_idle_source_new ();
a7f877
+	g_source_set_callback (idle_source, gs_plugin_basic_auth_start_cb, helper, NULL);
a7f877
+	g_source_attach (idle_source, NULL);
a7f877
+}
a7f877
+
a7f877
 static gboolean
a7f877
 gs_plugin_app_launch_cb (gpointer user_data)
a7f877
 {
a7f877
@@ -1959,6 +2018,13 @@ gs_plugin_class_init (GsPluginClass *klass)
a7f877
 			      G_STRUCT_OFFSET (GsPluginClass, allow_updates),
a7f877
 			      NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN,
a7f877
 			      G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
a7f877
+
a7f877
+	signals [SIGNAL_BASIC_AUTH_START] =
a7f877
+		g_signal_new ("basic-auth-start",
a7f877
+			      G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
a7f877
+			      G_STRUCT_OFFSET (GsPluginClass, basic_auth_start),
a7f877
+			      NULL, NULL, g_cclosure_marshal_generic,
a7f877
+			      G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER);
a7f877
 }
a7f877
 
a7f877
 static void
a7f877
diff --git a/lib/gs-plugin.h b/lib/gs-plugin.h
a7f877
index 7dd2d864..d07afd3b 100644
a7f877
--- a/lib/gs-plugin.h
a7f877
+++ b/lib/gs-plugin.h
a7f877
@@ -1,6 +1,7 @@
a7f877
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
a7f877
  *
a7f877
  * Copyright (C) 2012-2016 Richard Hughes <richard@hughsie.com>
a7f877
+ * Copyright (C) 2020 Kalev Lember <klember@redhat.com>
a7f877
  *
a7f877
  * SPDX-License-Identifier: GPL-2.0+
a7f877
  */
a7f877
@@ -37,7 +38,12 @@ struct _GsPluginClass
a7f877
 							 GsPluginEvent	*event);
a7f877
 	void			(*allow_updates)	(GsPlugin	*plugin,
a7f877
 							 gboolean	 allow_updates);
a7f877
-	gpointer		 padding[26];
a7f877
+	void			(*basic_auth_start)	(GsPlugin	*plugin,
a7f877
+							 const gchar	*remote,
a7f877
+							 const gchar	*realm,
a7f877
+							 GCallback	 callback,
a7f877
+							 gpointer	 user_data);
a7f877
+	gpointer		 padding[25];
a7f877
 };
a7f877
 
a7f877
 typedef struct	GsPluginData	GsPluginData;
a7f877
@@ -116,5 +122,10 @@ void		 gs_plugin_report_event			(GsPlugin	*plugin,
a7f877
 void		 gs_plugin_set_allow_updates		(GsPlugin	*plugin,
a7f877
 							 gboolean	 allow_updates);
a7f877
 gboolean	 gs_plugin_get_network_available	(GsPlugin	*plugin);
a7f877
+void		 gs_plugin_basic_auth_start		(GsPlugin	*plugin,
a7f877
+							 const gchar	*remote,
a7f877
+							 const gchar	*realm,
a7f877
+							 GCallback	 callback,
a7f877
+							 gpointer	 user_data);
a7f877
 
a7f877
 G_END_DECLS
a7f877
diff --git a/plugins/flatpak/gs-plugin-flatpak.c b/plugins/flatpak/gs-plugin-flatpak.c
a7f877
index 4d6a81ba..2518025d 100644
a7f877
--- a/plugins/flatpak/gs-plugin-flatpak.c
a7f877
+++ b/plugins/flatpak/gs-plugin-flatpak.c
a7f877
@@ -2,7 +2,7 @@
a7f877
  *
a7f877
  * Copyright (C) 2016 Joaquim Rocha <jrocha@endlessm.com>
a7f877
  * Copyright (C) 2016-2018 Richard Hughes <richard@hughsie.com>
a7f877
- * Copyright (C) 2017-2018 Kalev Lember <klember@redhat.com>
a7f877
+ * Copyright (C) 2017-2020 Kalev Lember <klember@redhat.com>
a7f877
  *
a7f877
  * SPDX-License-Identifier: GPL-2.0+
a7f877
  */
a7f877
@@ -456,6 +456,55 @@ _group_apps_by_installation (GsPlugin *plugin,
a7f877
 	return g_steal_pointer (&applist_by_flatpaks);
a7f877
 }
a7f877
 
a7f877
+#if FLATPAK_CHECK_VERSION(1,6,0)
a7f877
+typedef struct {
a7f877
+	FlatpakTransaction *transaction;
a7f877
+	guint id;
a7f877
+} BasicAuthData;
a7f877
+
a7f877
+static void
a7f877
+basic_auth_data_free (BasicAuthData *data)
a7f877
+{
a7f877
+	g_object_unref (data->transaction);
a7f877
+	g_slice_free (BasicAuthData, data);
a7f877
+}
a7f877
+
a7f877
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(BasicAuthData, basic_auth_data_free)
a7f877
+
a7f877
+static void
a7f877
+_basic_auth_cb (const gchar *user, const gchar *password, gpointer user_data)
a7f877
+{
a7f877
+	g_autoptr(BasicAuthData) data = user_data;
a7f877
+
a7f877
+	g_debug ("Submitting basic auth data");
a7f877
+
a7f877
+	/* NULL user aborts the basic auth request */
a7f877
+	flatpak_transaction_complete_basic_auth (data->transaction, data->id, user, password, NULL /* options */);
a7f877
+}
a7f877
+
a7f877
+static gboolean
a7f877
+_basic_auth_start (FlatpakTransaction *transaction,
a7f877
+                   const char *remote,
a7f877
+                   const char *realm,
a7f877
+                   GVariant *options,
a7f877
+                   guint id,
a7f877
+                   GsPlugin *plugin)
a7f877
+{
a7f877
+	BasicAuthData *data;
a7f877
+
a7f877
+	if (!gs_plugin_has_flags (plugin, GS_PLUGIN_FLAGS_INTERACTIVE))
a7f877
+		return FALSE;
a7f877
+
a7f877
+	data = g_slice_new0 (BasicAuthData);
a7f877
+	data->transaction = g_object_ref (transaction);
a7f877
+	data->id = id;
a7f877
+
a7f877
+	g_debug ("Login required remote %s (realm %s)\n", remote, realm);
a7f877
+	gs_plugin_basic_auth_start (plugin, remote, realm, G_CALLBACK (_basic_auth_cb), data);
a7f877
+	return TRUE;
a7f877
+}
a7f877
+#endif
a7f877
+
a7f877
 static FlatpakTransaction *
a7f877
 _build_transaction (GsPlugin *plugin, GsFlatpak *flatpak,
a7f877
 		    GCancellable *cancellable, GError **error)
a7f877
@@ -491,6 +540,10 @@ _build_transaction (GsPlugin *plugin, GsFlatpak *flatpak,
a7f877
 	/* connect up signals */
a7f877
 	g_signal_connect (transaction, "ref-to-app",
a7f877
 			  G_CALLBACK (_ref_to_app), plugin);
a7f877
+#if FLATPAK_CHECK_VERSION(1,6,0)
a7f877
+	g_signal_connect (transaction, "basic-auth-start",
a7f877
+			  G_CALLBACK (_basic_auth_start), plugin);
a7f877
+#endif
a7f877
 
a7f877
 	/* use system installations as dependency sources for user installations */
a7f877
 	flatpak_transaction_add_default_dependency_sources (transaction);
a7f877
diff --git a/po/POTFILES.in b/po/POTFILES.in
a7f877
index 20721c4a..a44a6ad3 100644
a7f877
--- a/po/POTFILES.in
a7f877
+++ b/po/POTFILES.in
a7f877
@@ -10,6 +10,8 @@ src/gs-app-row.c
a7f877
 src/gs-app-row.ui
a7f877
 src/gs-app-tile.c
a7f877
 src/gs-app-tile.ui
a7f877
+src/gs-basic-auth-dialog.c
a7f877
+src/gs-basic-auth-dialog.ui
a7f877
 lib/gs-category.c
a7f877
 src/gs-category-page.c
a7f877
 src/gs-category-page.ui
a7f877
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
a7f877
index 3eaabca2..459ecf82 100644
a7f877
--- a/src/gnome-software.gresource.xml
a7f877
+++ b/src/gnome-software.gresource.xml
a7f877
@@ -4,6 +4,7 @@
a7f877
   <file preprocess="xml-stripblanks">gnome-software.ui</file>
a7f877
   <file preprocess="xml-stripblanks">gs-app-addon-row.ui</file>
a7f877
   <file preprocess="xml-stripblanks">gs-app-row.ui</file>
a7f877
+  <file preprocess="xml-stripblanks">gs-basic-auth-dialog.ui</file>
a7f877
   <file preprocess="xml-stripblanks">gs-category-page.ui</file>
a7f877
   <file preprocess="xml-stripblanks">gs-category-tile.ui</file>
a7f877
   <file preprocess="xml-stripblanks">gs-details-page.ui</file>
a7f877
diff --git a/src/gs-basic-auth-dialog.c b/src/gs-basic-auth-dialog.c
a7f877
new file mode 100644
a7f877
index 00000000..c690a327
a7f877
--- /dev/null
a7f877
+++ b/src/gs-basic-auth-dialog.c
a7f877
@@ -0,0 +1,130 @@
a7f877
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
a7f877
+ *
a7f877
+ * Copyright (C) 2020 Kalev Lember <klember@redhat.com>
a7f877
+ *
a7f877
+ * SPDX-License-Identifier: GPL-2.0+
a7f877
+ */
a7f877
+
a7f877
+#include "config.h"
a7f877
+
a7f877
+#include "gs-basic-auth-dialog.h"
a7f877
+
a7f877
+#include <glib.h>
a7f877
+#include <glib/gi18n.h>
a7f877
+#include <gtk/gtk.h>
a7f877
+
a7f877
+struct _GsBasicAuthDialog
a7f877
+{
a7f877
+	GtkDialog		 parent_instance;
a7f877
+
a7f877
+	GsBasicAuthCallback	 callback;
a7f877
+	gpointer		 callback_data;
a7f877
+
a7f877
+	/* template widgets */
a7f877
+	GtkButton		*login_button;
a7f877
+	GtkLabel		*description_label;
a7f877
+	GtkEntry		*user_entry;
a7f877
+	GtkEntry		*password_entry;
a7f877
+};
a7f877
+
a7f877
+G_DEFINE_TYPE (GsBasicAuthDialog, gs_basic_auth_dialog, GTK_TYPE_DIALOG)
a7f877
+
a7f877
+static void
a7f877
+cancel_button_clicked_cb (GsBasicAuthDialog *dialog)
a7f877
+{
a7f877
+	/* abort the basic auth request */
a7f877
+	dialog->callback (NULL, NULL, dialog->callback_data);
a7f877
+
a7f877
+	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
a7f877
+}
a7f877
+
a7f877
+static void
a7f877
+login_button_clicked_cb (GsBasicAuthDialog *dialog)
a7f877
+{
a7f877
+	const gchar *user;
a7f877
+	const gchar *password;
a7f877
+
a7f877
+	user = gtk_entry_get_text (dialog->user_entry);
a7f877
+	password = gtk_entry_get_text (dialog->password_entry);
a7f877
+
a7f877
+	/* submit the user/password to basic auth */
a7f877
+	dialog->callback (user, password, dialog->callback_data);
a7f877
+
a7f877
+	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
a7f877
+}
a7f877
+
a7f877
+static void
a7f877
+dialog_validate (GsBasicAuthDialog *dialog)
a7f877
+{
a7f877
+	const gchar *user;
a7f877
+	const gchar *password;
a7f877
+	gboolean valid_user;
a7f877
+	gboolean valid_password;
a7f877
+
a7f877
+	/* require user */
a7f877
+	user = gtk_entry_get_text (dialog->user_entry);
a7f877
+	valid_user = user != NULL && strlen (user) != 0;
a7f877
+
a7f877
+	/* require password */
a7f877
+	password = gtk_entry_get_text (dialog->password_entry);
a7f877
+	valid_password = password != NULL && strlen (password) != 0;
a7f877
+
a7f877
+	gtk_widget_set_sensitive (GTK_WIDGET (dialog->login_button), valid_user && valid_password);
a7f877
+}
a7f877
+
a7f877
+static void
a7f877
+update_description (GsBasicAuthDialog *dialog, const gchar *remote, const gchar *realm)
a7f877
+{
a7f877
+	g_autofree gchar *description = NULL;
a7f877
+
a7f877
+	/* TRANSLATORS: This is a description for entering user/password */
a7f877
+	description = g_strdup_printf (_("Login required remote %s (realm %s)"),
a7f877
+				       remote, realm);
a7f877
+	gtk_label_set_text (dialog->description_label, description);
a7f877
+}
a7f877
+
a7f877
+static void
a7f877
+gs_basic_auth_dialog_init (GsBasicAuthDialog *dialog)
a7f877
+{
a7f877
+	gtk_widget_init_template (GTK_WIDGET (dialog));
a7f877
+}
a7f877
+
a7f877
+static void
a7f877
+gs_basic_auth_dialog_class_init (GsBasicAuthDialogClass *klass)
a7f877
+{
a7f877
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
a7f877
+
a7f877
+	gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-basic-auth-dialog.ui");
a7f877
+
a7f877
+	gtk_widget_class_bind_template_child (widget_class, GsBasicAuthDialog, login_button);
a7f877
+	gtk_widget_class_bind_template_child (widget_class, GsBasicAuthDialog, description_label);
a7f877
+	gtk_widget_class_bind_template_child (widget_class, GsBasicAuthDialog, user_entry);
a7f877
+	gtk_widget_class_bind_template_child (widget_class, GsBasicAuthDialog, password_entry);
a7f877
+
a7f877
+	gtk_widget_class_bind_template_callback (widget_class, dialog_validate);
a7f877
+	gtk_widget_class_bind_template_callback (widget_class, cancel_button_clicked_cb);
a7f877
+	gtk_widget_class_bind_template_callback (widget_class, login_button_clicked_cb);
a7f877
+}
a7f877
+
a7f877
+GtkWidget *
a7f877
+gs_basic_auth_dialog_new (GtkWindow *parent,
a7f877
+                          const gchar *remote,
a7f877
+                          const gchar *realm,
a7f877
+                          GsBasicAuthCallback callback,
a7f877
+                          gpointer callback_data)
a7f877
+{
a7f877
+	GsBasicAuthDialog *dialog;
a7f877
+
a7f877
+	dialog = g_object_new (GS_TYPE_BASIC_AUTH_DIALOG,
a7f877
+	                       "use-header-bar", TRUE,
a7f877
+	                       "transient-for", parent,
a7f877
+	                       "modal", TRUE,
a7f877
+	                       NULL);
a7f877
+	dialog->callback = callback;
a7f877
+	dialog->callback_data = callback_data;
a7f877
+
a7f877
+	update_description (dialog, remote, realm);
a7f877
+	dialog_validate (dialog);
a7f877
+
a7f877
+	return GTK_WIDGET (dialog);
a7f877
+}
a7f877
diff --git a/src/gs-basic-auth-dialog.h b/src/gs-basic-auth-dialog.h
a7f877
new file mode 100644
a7f877
index 00000000..ec5f1d03
a7f877
--- /dev/null
a7f877
+++ b/src/gs-basic-auth-dialog.h
a7f877
@@ -0,0 +1,28 @@
a7f877
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
a7f877
+ *
a7f877
+ * Copyright (C) 2020 Kalev Lember <klember@redhat.com>
a7f877
+ *
a7f877
+ * SPDX-License-Identifier: GPL-2.0+
a7f877
+ */
a7f877
+
a7f877
+#pragma once
a7f877
+
a7f877
+#include <gtk/gtk.h>
a7f877
+
a7f877
+#include "gnome-software-private.h"
a7f877
+
a7f877
+G_BEGIN_DECLS
a7f877
+
a7f877
+typedef void (*GsBasicAuthCallback) (const gchar *user, const gchar *password, gpointer callback_data);
a7f877
+
a7f877
+#define GS_TYPE_BASIC_AUTH_DIALOG (gs_basic_auth_dialog_get_type ())
a7f877
+
a7f877
+G_DECLARE_FINAL_TYPE (GsBasicAuthDialog, gs_basic_auth_dialog, GS, BASIC_AUTH_DIALOG, GtkDialog)
a7f877
+
a7f877
+GtkWidget	*gs_basic_auth_dialog_new		(GtkWindow		*parent,
a7f877
+							 const gchar		*remote,
a7f877
+							 const gchar		*realm,
a7f877
+							 GsBasicAuthCallback	 callback,
a7f877
+							 gpointer		 callback_data);
a7f877
+
a7f877
+G_END_DECLS
a7f877
diff --git a/src/gs-basic-auth-dialog.ui b/src/gs-basic-auth-dialog.ui
a7f877
new file mode 100644
a7f877
index 00000000..339e831d
a7f877
--- /dev/null
a7f877
+++ b/src/gs-basic-auth-dialog.ui
a7f877
@@ -0,0 +1,203 @@
a7f877
+
a7f877
+<interface>
a7f877
+  <template class="GsBasicAuthDialog" parent="GtkDialog">
a7f877
+    <property name="can_focus">False</property>
a7f877
+    <property name="border_width">5</property>
a7f877
+    <property name="resizable">False</property>
a7f877
+    <property name="modal">True</property>
a7f877
+    <property name="destroy_with_parent">True</property>
a7f877
+    <property name="type_hint">dialog</property>
a7f877
+    <property name="title" translatable="yes">Login Required</property>
a7f877
+    <property name="use_header_bar">1</property>
a7f877
+    <child internal-child="headerbar">
a7f877
+      <object class="GtkHeaderBar">
a7f877
+        <property name="visible">True</property>
a7f877
+        <property name="can_focus">False</property>
a7f877
+        <property name="show_close_button">False</property>
a7f877
+        <child>
a7f877
+          <object class="GtkButton" id="cancel_button">
a7f877
+            <property name="label" translatable="yes">_Cancel</property>
a7f877
+            <property name="visible">True</property>
a7f877
+            <property name="visible">True</property>
a7f877
+            <property name="can_focus">True</property>
a7f877
+            <property name="can_default">True</property>
a7f877
+            <property name="receives_default">True</property>
a7f877
+            <property name="use_action_appearance">False</property>
a7f877
+            <property name="use_underline">True</property>
a7f877
+            <property name="valign">center</property>
a7f877
+            <signal name="clicked" handler="cancel_button_clicked_cb" object="GsBasicAuthDialog" swapped="yes"/>
a7f877
+            <style>
a7f877
+              <class name="text-button"/>
a7f877
+            </style>
a7f877
+          </object>
a7f877
+          <packing>
a7f877
+            <property name="pack_type">start</property>
a7f877
+          </packing>
a7f877
+        </child>
a7f877
+        <child>
a7f877
+          <object class="GtkButton" id="login_button">
a7f877
+            <property name="label" translatable="yes">_Login</property>
a7f877
+            <property name="visible">True</property>
a7f877
+            <property name="can_focus">True</property>
a7f877
+            <property name="can_default">True</property>
a7f877
+            <property name="has_default">True</property>
a7f877
+            <property name="receives_default">True</property>
a7f877
+            <property name="use_action_appearance">False</property>
a7f877
+            <property name="use_underline">True</property>
a7f877
+            <property name="valign">center</property>
a7f877
+            <signal name="clicked" handler="login_button_clicked_cb" object="GsBasicAuthDialog" swapped="yes"/>
a7f877
+            <style>
a7f877
+              <class name="text-button"/>
a7f877
+              <class name="suggested-action"/>
a7f877
+            </style>
a7f877
+          </object>
a7f877
+          <packing>
a7f877
+            <property name="pack_type">end</property>
a7f877
+          </packing>
a7f877
+        </child>
a7f877
+      </object>
a7f877
+    </child>
a7f877
+    <child internal-child="vbox">
a7f877
+      <object class="GtkBox">
a7f877
+        <property name="visible">True</property>
a7f877
+        <property name="can_focus">False</property>
a7f877
+        <property name="orientation">vertical</property>
a7f877
+        <child>
a7f877
+          <object class="GtkGrid">
a7f877
+            <property name="visible">True</property>
a7f877
+            <property name="can_focus">False</property>
a7f877
+            <property name="hexpand">True</property>
a7f877
+            <property name="row_spacing">8</property>
a7f877
+            <property name="column_spacing">6</property>
a7f877
+            <property name="border_width">20</property>
a7f877
+            <property name="margin_end">20</property>
a7f877
+            <child>
a7f877
+              <object class="GtkLabel" id="description_label">
a7f877
+                <property name="visible">True</property>
a7f877
+                <property name="can_focus">False</property>
a7f877
+                <property name="wrap">True</property>
a7f877
+                <property name="wrap_mode">word-char</property>
a7f877
+                <property name="margin_bottom">20</property>
a7f877
+                <property name="max_width_chars">55</property>
a7f877
+                <property name="xalign">0</property>
a7f877
+                <style>
a7f877
+                  <class name="dim-label"/>
a7f877
+                </style>
a7f877
+              </object>
a7f877
+              <packing>
a7f877
+                <property name="left_attach">0</property>
a7f877
+                <property name="top_attach">0</property>
a7f877
+                <property name="width">2</property>
a7f877
+                <property name="height">1</property>
a7f877
+              </packing>
a7f877
+            </child>
a7f877
+            <child>
a7f877
+              <object class="GtkLabel" id="user_label">
a7f877
+                <property name="visible">True</property>
a7f877
+                <property name="can_focus">False</property>
a7f877
+                <property name="xalign">1</property>
a7f877
+                <property name="label" translatable="yes">_User</property>
a7f877
+                <property name="use_underline">True</property>
a7f877
+                <property name="mnemonic_widget">user_entry</property>
a7f877
+                <property name="margin_start">20</property>
a7f877
+                <style>
a7f877
+                  <class name="dim-label"/>
a7f877
+                </style>
a7f877
+              </object>
a7f877
+              <packing>
a7f877
+                <property name="left_attach">0</property>
a7f877
+                <property name="top_attach">3</property>
a7f877
+                <property name="width">1</property>
a7f877
+                <property name="height">1</property>
a7f877
+              </packing>
a7f877
+            </child>
a7f877
+            <child>
a7f877
+              <object class="GtkLabel" id="password_label">
a7f877
+                <property name="visible">True</property>
a7f877
+                <property name="can_focus">False</property>
a7f877
+                <property name="xalign">1</property>
a7f877
+                <property name="label" translatable="yes">_Password</property>
a7f877
+                <property name="use_underline">True</property>
a7f877
+                <property name="mnemonic_widget">password_entry</property>
a7f877
+                <property name="margin_start">20</property>
a7f877
+                <style>
a7f877
+                  <class name="dim-label"/>
a7f877
+                </style>
a7f877
+              </object>
a7f877
+              <packing>
a7f877
+                <property name="left_attach">0</property>
a7f877
+                <property name="top_attach">4</property>
a7f877
+                <property name="width">1</property>
a7f877
+                <property name="height">1</property>
a7f877
+              </packing>
a7f877
+            </child>
a7f877
+            <child>
a7f877
+              <object class="GtkEntry" id="user_entry">
a7f877
+                <property name="visible">True</property>
a7f877
+                <property name="can_focus">True</property>
a7f877
+                <property name="has_focus">True</property>
a7f877
+                <property name="hexpand">True</property>
a7f877
+                <property name="invisible_char">●</property>
a7f877
+                <property name="activates_default">True</property>
a7f877
+                <property name="invisible_char_set">True</property>
a7f877
+                <property name="input_purpose">password</property>
a7f877
+                <signal name="changed" handler="dialog_validate" object="GsBasicAuthDialog" swapped="yes"/>
a7f877
+                <signal name="activate" handler="dialog_validate" object="GsBasicAuthDialog" swapped="yes"/>
a7f877
+              </object>
a7f877
+              <packing>
a7f877
+                <property name="left_attach">1</property>
a7f877
+                <property name="top_attach">3</property>
a7f877
+                <property name="width">1</property>
a7f877
+                <property name="height">1</property>
a7f877
+              </packing>
a7f877
+            </child>
a7f877
+            <child>
a7f877
+              <object class="GtkEntry" id="password_entry">
a7f877
+                <property name="visible">True</property>
a7f877
+                <property name="can_focus">True</property>
a7f877
+                <property name="hexpand">True</property>
a7f877
+                <property name="visibility">False</property>
a7f877
+                <property name="invisible_char">●</property>
a7f877
+                <property name="activates_default">True</property>
a7f877
+                <property name="invisible_char_set">True</property>
a7f877
+                <property name="input_purpose">password</property>
a7f877
+                <signal name="changed" handler="dialog_validate" object="GsBasicAuthDialog" swapped="yes"/>
a7f877
+                <signal name="activate" handler="dialog_validate" object="GsBasicAuthDialog" swapped="yes"/>
a7f877
+              </object>
a7f877
+              <packing>
a7f877
+                <property name="left_attach">1</property>
a7f877
+                <property name="top_attach">4</property>
a7f877
+                <property name="width">1</property>
a7f877
+                <property name="height">1</property>
a7f877
+              </packing>
a7f877
+            </child>
a7f877
+          </object>
a7f877
+          <packing>
a7f877
+            <property name="expand">False</property>
a7f877
+            <property name="fill">True</property>
a7f877
+            <property name="position">0</property>
a7f877
+          </packing>
a7f877
+        </child>
a7f877
+      </object>
a7f877
+    </child>
a7f877
+  </template>
a7f877
+  <object class="GtkSizeGroup">
a7f877
+    <widgets>
a7f877
+      <widget name="user_label"/>
a7f877
+      <widget name="password_label"/>
a7f877
+    </widgets>
a7f877
+  </object>
a7f877
+  <object class="GtkSizeGroup">
a7f877
+    <widgets>
a7f877
+      <widget name="user_entry"/>
a7f877
+      <widget name="password_entry"/>
a7f877
+    </widgets>
a7f877
+  </object>
a7f877
+  <object class="GtkSizeGroup">
a7f877
+    <property name="mode">horizontal</property>
a7f877
+    <widgets>
a7f877
+      <widget name="login_button"/>
a7f877
+      <widget name="cancel_button"/>
a7f877
+    </widgets>
a7f877
+  </object>
a7f877
+</interface>
a7f877
diff --git a/src/gs-shell.c b/src/gs-shell.c
a7f877
index 009776ad..41503cf8 100644
a7f877
--- a/src/gs-shell.c
a7f877
+++ b/src/gs-shell.c
a7f877
@@ -2,7 +2,7 @@
a7f877
  *
a7f877
  * Copyright (C) 2013-2017 Richard Hughes <richard@hughsie.com>
a7f877
  * Copyright (C) 2013 Matthias Clasen <mclasen@redhat.com>
a7f877
- * Copyright (C) 2014-2018 Kalev Lember <klember@redhat.com>
a7f877
+ * Copyright (C) 2014-2020 Kalev Lember <klember@redhat.com>
a7f877
  *
a7f877
  * SPDX-License-Identifier: GPL-2.0+
a7f877
  */
a7f877
@@ -18,6 +18,7 @@
a7f877
 
a7f877
 #include "gs-common.h"
a7f877
 #include "gs-shell.h"
a7f877
+#include "gs-basic-auth-dialog.h"
a7f877
 #include "gs-details-page.h"
a7f877
 #include "gs-installed-page.h"
a7f877
 #include "gs-metered-data-dialog.h"
a7f877
@@ -362,6 +363,25 @@ scheduler_ready_cb (GObject *source_object,
a7f877
 }
a7f877
 #endif  /* HAVE_MOGWAI */
a7f877
 
a7f877
+static void
a7f877
+gs_shell_basic_auth_start_cb (GsPluginLoader *plugin_loader,
a7f877
+                              const gchar *remote,
a7f877
+                              const gchar *realm,
a7f877
+                              GsBasicAuthCallback callback,
a7f877
+                              gpointer callback_data,
a7f877
+                              GsShell *shell)
a7f877
+{
a7f877
+	GsShellPrivate *priv = gs_shell_get_instance_private (shell);
a7f877
+	GtkWidget *dialog;
a7f877
+
a7f877
+	dialog = gs_basic_auth_dialog_new (priv->main_window, remote, realm, callback, callback_data);
a7f877
+	gs_shell_modal_dialog_present (shell, GTK_DIALOG (dialog));
a7f877
+
a7f877
+	/* just destroy */
a7f877
+	g_signal_connect_swapped (dialog, "response",
a7f877
+				  G_CALLBACK (gtk_widget_destroy), dialog);
a7f877
+}
a7f877
+
a7f877
 static void
a7f877
 free_back_entry (BackEntry *entry)
a7f877
 {
a7f877
@@ -2126,6 +2146,9 @@ gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *can
a7f877
 	g_signal_connect_object (priv->plugin_loader, "notify::network-metered",
a7f877
 				 G_CALLBACK (gs_shell_network_metered_notify_cb),
a7f877
 				 shell, 0);
a7f877
+	g_signal_connect_object (priv->plugin_loader, "basic-auth-start",
a7f877
+				 G_CALLBACK (gs_shell_basic_auth_start_cb),
a7f877
+				 shell, 0);
a7f877
 	priv->cancellable = g_object_ref (cancellable);
a7f877
 
a7f877
 	priv->settings = g_settings_new ("org.gnome.software");
a7f877
diff --git a/src/meson.build b/src/meson.build
a7f877
index cbd0a511..6581e77c 100644
a7f877
--- a/src/meson.build
a7f877
+++ b/src/meson.build
a7f877
@@ -20,6 +20,7 @@ gnome_software_sources = [
a7f877
   'gs-application.c',
a7f877
   'gs-app-row.c',
a7f877
   'gs-app-tile.c',
a7f877
+  'gs-basic-auth-dialog.c',
a7f877
   'gs-category-page.c',
a7f877
   'gs-category-tile.c',
a7f877
   'gs-common.c',
a7f877
-- 
a7f877
2.26.2
a7f877