diff --git a/SOURCES/0003-Drop-dependency-on-WebKitGTK-139.patch b/SOURCES/0003-Drop-dependency-on-WebKitGTK-139.patch
new file mode 100644
index 0000000..c7773a1
--- /dev/null
+++ b/SOURCES/0003-Drop-dependency-on-WebKitGTK-139.patch
@@ -0,0 +1,4728 @@
+diff --git a/configure.ac b/configure.ac
+index 332a0bf..70962d6 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -113,14 +113,6 @@ if test "$enable_backend" != "no"; then
+ AC_SUBST(GTK_CFLAGS)
+ AC_SUBST(GTK_LIBS)
+
+- PKG_CHECK_MODULES(JAVASCRIPT_CORE_GTK, [javascriptcoregtk-4.0 >= 2.12.0])
+- AC_SUBST(JAVASCRIPT_CORE_GTK_CFLAGS)
+- AC_SUBST(JAVASCRIPT_CORE_GTK_LIBS)
+-
+- PKG_CHECK_MODULES(WEBKIT_GTK, [webkit2gtk-4.0 >= 2.26.0])
+- AC_SUBST(WEBKIT_GTK_CFLAGS)
+- AC_SUBST(WEBKIT_GTK_LIBS)
+-
+ PKG_CHECK_MODULES(LIBSOUP, [libsoup-2.4 >= 2.42])
+ AC_SUBST(LIBSOUP_CFLAGS)
+ AC_SUBST(LIBSOUP_LIBS)
+@@ -142,14 +134,6 @@ if test "$enable_backend" != "no"; then
+ AC_SUBST(LIBXML_LIBS)
+ fi
+
+-AC_ARG_ENABLE([inspector],
+- [AS_HELP_STRING([--enable-inspector], [Enable a WebKitWebInspector for the embedded web view])],
+- [],
+- [enable_inspector=no])
+-if test "$enable_inspector" != "no"; then
+- AC_DEFINE(GOA_INSPECTOR_ENABLED, 1, [Enable a WebKitWebInspector for the embedded web view])
+-fi
+-
+ AC_ARG_WITH(template-file,
+ [AS_HELP_STRING([--with-template-file], [Path to the template file])],
+ [],
+@@ -275,7 +259,11 @@ AC_DEFINE_UNQUOTED(GOA_GOOGLE_CLIENT_ID, ["$with_google_client_id"], [Google OAu
+ AC_DEFINE_UNQUOTED(GOA_GOOGLE_CLIENT_SECRET, ["$with_google_client_secret"], [Google OAuth 2.0 client secret])
+ if test "$enable_google" != "no"; then
+ AC_DEFINE(GOA_GOOGLE_ENABLED, 1, [Enable Google data provider])
++ if test "$with_google_client_id" != "44438659992-7kgjeitenc16ssihbtdjbgguch7ju55s.apps.googleusercontent.com"; then
++ AC_MSG_ERROR([Unexpected Google OAuth2 Client ID, correct it here and in data/Makefile.am in oauth2_schemes, to be reverse-DNS of the new Client ID])
++ fi
+ fi
++AM_CONDITIONAL(GOOGLE_ENABLED, [test x$enable_google != xno])
+
+ # IMAP/SMTP
+ AC_DEFINE(GOA_IMAP_SMTP_NAME, ["imap_smtp"], [ProviderType and extension point name])
+diff --git a/data/Makefile.am b/data/Makefile.am
+index e3608a1..c3f1009 100644
+--- a/data/Makefile.am
++++ b/data/Makefile.am
+@@ -19,17 +19,33 @@ endif
+ service_DATA = $(service_in_files:.service.in=.service)
+ %.service: %.service.in Makefile
+ @sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@
++
++endif
++
++desktopdir = $(datadir)/applications
++desktop_in_files = org.gnome.OnlineAccounts.OAuth2.desktop.in
++desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
++if GOOGLE_ENABLED
++ oauth2_schemes=x-scheme-handler/com.googleusercontent.apps.44438659992-7kgjeitenc16ssihbtdjbgguch7ju55s;
++else
++ oauth2_schemes=
+ endif
+
++%.desktop: %.desktop.in Makefile
++ @sed -e "s|\@libexecdir\@|$(libexecdir)|" -e "s|\@oauth2_schemes\@|$(oauth2_schemes)|" $< > $@
++
++
+ EXTRA_DIST = \
+ $(gsettings_schema_files) \
+ dbus-interfaces.xml \
+ org.gnome.Identity.service.in \
+ org.gnome.OnlineAccounts.service.in \
++ org.gnome.OnlineAccounts.OAuth2.desktop.in \
+ $(NULL)
+
+ CLEANFILES = \
+ org.gnome.OnlineAccounts.service \
++ org.gnome.OnlineAccounts.OAuth2.desktop \
+ org.gnome.Identity.service \
+ $(NULL)
+
+diff --git a/data/org.gnome.OnlineAccounts.OAuth2.desktop.in b/data/org.gnome.OnlineAccounts.OAuth2.desktop.in
+new file mode 100644
+index 0000000..d0478aa
+--- /dev/null
++++ b/data/org.gnome.OnlineAccounts.OAuth2.desktop.in
+@@ -0,0 +1,6 @@
++[Desktop Entry]
++Name=GNOME OAuth2 Handler
++Exec=@libexecdir@/goa-oauth2-handler %u
++Type=Application
++MimeType=x-scheme-handler/goa-oauth2;@oauth2_schemes@
++NoDisplay=true
+diff --git a/doc/goa-docs.xml b/doc/goa-docs.xml
+index 0abb53a..a9d45e1 100644
+--- a/doc/goa-docs.xml
++++ b/doc/goa-docs.xml
+@@ -171,7 +171,6 @@
+
Core
+
+
+-
+
+
+
+diff --git a/doc/goa-sections.txt b/doc/goa-sections.txt
+index 6f96d69..31a07b5 100644
+--- a/doc/goa-sections.txt
++++ b/doc/goa-sections.txt
+@@ -502,36 +502,6 @@ GoaOAuth2ProviderPrivate
+ goa_oauth2_provider_get_type
+
+
+-
+-goaoauthprovider
+-GoaOAuthProvider
+-GoaOAuthProviderClass
+-goa_oauth_provider_get_request_uri
+-goa_oauth_provider_get_request_uri_params
+-goa_oauth_provider_get_authorization_uri
+-goa_oauth_provider_get_token_uri
+-goa_oauth_provider_get_callback_uri
+-goa_oauth_provider_get_consumer_key
+-goa_oauth_provider_get_consumer_secret
+-goa_oauth_provider_build_authorization_uri
+-goa_oauth_provider_get_use_mobile_browser
+-goa_oauth_provider_is_deny_node
+-goa_oauth_provider_is_identity_node
+-goa_oauth_provider_is_password_node
+-goa_oauth_provider_add_account_key_values
+-goa_oauth_provider_get_identity_sync
+-goa_oauth_provider_get_access_token_sync
+-goa_oauth_provider_parse_request_token_error
+-
+-GOA_OAUTH_PROVIDER
+-GOA_OAUTH_PROVIDER_CLASS
+-GOA_OAUTH_PROVIDER_GET_CLASS
+-GOA_IS_OAUTH_PROVIDER
+-GOA_IS_OAUTH_PROVIDER_CLASS
+-GOA_TYPE_OAUTH_PROVIDER
+-goa_oauth_provider_get_type
+-
+-
+
+ GoaMail
+ GoaMail
+diff --git a/doc/goa.types b/doc/goa.types
+index 56ba3c4..d8d7325 100644
+--- a/doc/goa.types
++++ b/doc/goa.types
+@@ -62,5 +62,4 @@ goa_printers_proxy_get_type
+ goa_printers_skeleton_get_type
+
+ goa_provider_get_type
+-goa_oauth_provider_get_type
+ goa_oauth2_provider_get_type
+diff --git a/po/POTFILES.in b/po/POTFILES.in
+index 279fb64..e131629 100644
+--- a/po/POTFILES.in
++++ b/po/POTFILES.in
+@@ -20,7 +20,6 @@ src/goabackend/goaowncloudprovider.c
+ src/goabackend/goaprovider.c
+ src/goabackend/goasmtpauth.c
+ src/goabackend/goautils.c
+-src/goabackend/goawebview.c
+ src/goabackend/goawindowsliveprovider.c
+ src/goaidentity/goaidentityservice.c
+ src/goaidentity/goakerberosidentity.c
+diff --git a/src/goabackend/Makefile.am b/src/goabackend/Makefile.am
+index 8385237..c7e00a4 100644
+--- a/src/goabackend/Makefile.am
++++ b/src/goabackend/Makefile.am
+@@ -19,7 +19,6 @@ AM_CPPFLAGS = \
+ -DPACKAGE_LOCALSTATE_DIR=\""$(localstatedir)"\" \
+ -DPACKAGE_LOCALE_DIR=\""$(localedir)"\" \
+ -DPACKAGE_LIB_DIR=\""$(libdir)"\" \
+- -DPACKAGE_WEB_EXTENSIONS_DIR=\""$(libdir)/goa-1.0/web-extensions"\" \
+ $(WARN_CFLAGS) \
+ $(NULL)
+
+@@ -79,23 +78,18 @@ libgoa_backend_1_0_la_SOURCES = \
+ goasouplogger.h goasouplogger.c \
+ goamailclient.h goamailclient.c \
+ goaexchangeprovider.h goaexchangeprovider.c \
+- goaoauthprovider.h goaoauthprovider.c \
+ goaoauth2provider.h goaoauth2provider-priv.h \
+- goaoauth2provider-web-extension.h \
+- goaoauth2provider-web-view.h \
+ goaoauth2provider.c \
+ goagoogleprovider.h goagoogleprovider.c \
+ goafacebookprovider.h goafacebookprovider.c \
+ goaimapsmtpprovider.h goaimapsmtpprovider.c \
+ goamediaserverprovider.h goamediaserverprovider.c \
+ goaowncloudprovider.h goaowncloudprovider.c \
+- goaflickrprovider.h goaflickrprovider.c \
+ goafoursquareprovider.h goafoursquareprovider.c \
+ goawindowsliveprovider.h goawindowsliveprovider.c \
+ goalastfmprovider.h goalastfmprovider.c \
+ goaobjectskeletonutils.h goaobjectskeletonutils.c \
+ goautils.h goautils.c \
+- goawebview.h goawebview.c \
+ nautilus-floating-bar.h nautilus-floating-bar.c \
+ $(top_builddir)/src/goaidentity/org.gnome.Identity.c \
+ $(top_srcdir)/src/goaidentity/goaidentitymanagererror.c \
+@@ -112,8 +106,6 @@ libgoa_backend_1_0_la_SOURCES += \
+ endif
+
+ libgoa_backend_1_0_la_CFLAGS = \
+- $(JAVASCRIPT_CORE_GTK_CFLAGS) \
+- $(WEBKIT_GTK_CFLAGS) \
+ $(JSON_GLIB_CFLAGS) \
+ $(GCR_CFLAGS) \
+ $(GLIB_CFLAGS) \
+@@ -128,8 +120,6 @@ libgoa_backend_1_0_la_CFLAGS = \
+
+ libgoa_backend_1_0_la_LIBADD = \
+ $(top_builddir)/src/goa/libgoa-1.0.la \
+- $(JAVASCRIPT_CORE_GTK_LIBS) \
+- $(WEBKIT_GTK_LIBS) \
+ $(JSON_GLIB_LIBS) \
+ $(GCR_LIBS) \
+ $(GLIB_LIBS) \
+@@ -149,39 +139,29 @@ libgoa_backend_1_0_la_LDFLAGS = \
+
+ # ----------------------------------------------------------------------------------------------------
+
+-webextension_LTLIBRARIES = libgoawebextension.la
++libexec_PROGRAMS = goa-oauth2-handler
+
+-webextensiondir = $(libdir)/goa-1.0/web-extensions
+-
+-libgoawebextension_la_SOURCES = \
+- goawebextension.h goawebextension.c \
+- goawebextensionmain.c \
++goa_oauth2_handler_SOURCES = \
++ goaoauth2handler.c \
+ $(NULL)
+
+-libgoawebextension_la_CFLAGS = \
+- $(REST_CFLAGS) \
+- $(WEBKIT_GTK_CFLAGS) \
++goa_oauth2_handler_CFLAGS = \
++ $(GLIB_CFLAGS) \
++ $(LIBSOUP_CFLAGS) \
++ $(SECRET_CFLAGS) \
++ -DG_LOG_DOMAIN=\"goa-oauth2-handler\" \
+ $(NULL)
+
+-libgoawebextension_la_LIBADD = \
+- libgoa-backend-1.0.la \
+- $(REST_LIBS) \
+- $(WEBKIT_GTK_LIBS) \
++goa_oauth2_handler_LDADD = \
++ $(GLIB_LIBS) \
++ $(LIBSOUP_LIBS) \
++ $(SECRET_LIBS) \
+ $(NULL)
+
+-libgoawebextension_la_LDFLAGS = \
+- -avoid-version \
+- -module \
+- -no-undefined \
++goa_oauth2_handler_LDFLAGS = \
++ $(WARN_LDFLAGS) \
+ $(NULL)
+
+-# Force installation order: libgoa-backend-1.0 must be installed first, othwerwise
+-# libtool will incorrectly relink libgoawebextension.la under parallel make install.
+-# Requires ugly automake syntax - see http://debbugs.gnu.org/cgi/bugreport.cgi?bug=7328
+-
+-installwebextensionLTLIBRARIES = install-webextensionLTLIBRARIES
+-$(installwebextensionLTLIBRARIES): install-libLTLIBRARIES
+-
+ # ----------------------------------------------------------------------------------------------------
+
+ BUILT_SOURCES = \
+diff --git a/src/goabackend/goafacebookprovider.c b/src/goabackend/goafacebookprovider.c
+index c6033fb..c1d35d0 100644
+--- a/src/goabackend/goafacebookprovider.c
++++ b/src/goabackend/goafacebookprovider.c
+@@ -243,31 +243,6 @@ get_identity_sync (GoaOAuth2Provider *oauth2_provider,
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+-static gboolean
+-is_identity_node (GoaOAuth2Provider *oauth2_provider, WebKitDOMHTMLInputElement *element)
+-{
+- gboolean ret = FALSE;
+- gchar *element_type = NULL;
+- gchar *name = NULL;
+-
+- g_object_get (element, "type", &element_type, NULL);
+- if (g_strcmp0 (element_type, "text") != 0)
+- goto out;
+-
+- name = webkit_dom_html_input_element_get_name (element);
+- if (g_strcmp0 (name, "email") != 0)
+- goto out;
+-
+- ret = TRUE;
+-
+- out:
+- g_free (element_type);
+- g_free (name);
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+ static gboolean
+ build_object (GoaProvider *provider,
+ GoaObjectSkeleton *object,
+@@ -367,6 +342,5 @@ goa_facebook_provider_class_init (GoaFacebookProviderClass *klass)
+ oauth2_class->get_client_id = get_client_id;
+ oauth2_class->get_client_secret = get_client_secret;
+ oauth2_class->get_identity_sync = get_identity_sync;
+- oauth2_class->is_identity_node = is_identity_node;
+ oauth2_class->add_account_key_values = add_account_key_values;
+ }
+diff --git a/src/goabackend/goaflickrprovider.c b/src/goabackend/goaflickrprovider.c
+deleted file mode 100644
+index 702ed1e..0000000
+--- a/src/goabackend/goaflickrprovider.c
++++ /dev/null
+@@ -1,364 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+-/*
+- * Copyright (C) 2011 Willem van Engen
+- * Copyright © 2012 – 2017 Red Hat, Inc.
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-#include "config.h"
+-#include
+-
+-#include
+-#include
+-
+-#include "goaprovider.h"
+-#include "goaprovider-priv.h"
+-#include "goaflickrprovider.h"
+-#include "goaobjectskeletonutils.h"
+-#include "goasouplogger.h"
+-
+-struct _GoaFlickrProvider
+-{
+- GoaOAuthProvider parent_instance;
+-};
+-
+-G_DEFINE_TYPE_WITH_CODE (GoaFlickrProvider, goa_flickr_provider, GOA_TYPE_OAUTH_PROVIDER,
+- goa_provider_ensure_extension_points_registered ();
+- g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME,
+- g_define_type_id,
+- GOA_FLICKR_NAME,
+- 0));
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static const gchar *
+-get_provider_type (GoaProvider *provider)
+-{
+- return GOA_FLICKR_NAME;
+-}
+-
+-static gchar *
+-get_provider_name (GoaProvider *provider,
+- GoaObject *object)
+-{
+- return g_strdup (_("Flickr"));
+-}
+-
+-static GoaProviderGroup
+-get_provider_group (GoaProvider *provider)
+-{
+- return GOA_PROVIDER_GROUP_BRANDED;
+-}
+-
+-static GoaProviderFeatures
+-get_provider_features (GoaProvider *provider)
+-{
+- return GOA_PROVIDER_FEATURE_BRANDED | GOA_PROVIDER_FEATURE_PHOTOS;
+-}
+-
+-static const gchar *
+-get_consumer_key (GoaOAuthProvider *oauth_provider)
+-{
+- return GOA_FLICKR_CONSUMER_KEY;
+-}
+-
+-static const gchar *
+-get_consumer_secret (GoaOAuthProvider *oauth_provider)
+-{
+- return GOA_FLICKR_CONSUMER_SECRET;
+-}
+-
+-static const gchar *
+-get_request_uri (GoaOAuthProvider *oauth_provider)
+-{
+- return "https://www.flickr.com/services/oauth/request_token";
+-}
+-
+-static const gchar *
+-get_authorization_uri (GoaOAuthProvider *oauth_provider)
+-{
+- return "https://www.flickr.com/services/oauth/authorize";
+-}
+-
+-static const gchar *
+-get_token_uri (GoaOAuthProvider *oauth_provider)
+-{
+- return "https://www.flickr.com/services/oauth/access_token";
+-}
+-
+-static const gchar *
+-get_callback_uri (GoaOAuthProvider *oauth_provider)
+-{
+- /* Should match the URI specified in the Flickr App
+- * Garden in order to detect when the user denied access via
+- * the OAuth1 web page.
+- */
+- return "https://www.gnome.org/";
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gchar *
+-get_identity_sync (GoaOAuthProvider *oauth_provider,
+- const gchar *access_token,
+- const gchar *access_token_secret,
+- gchar **out_presentation_identity,
+- GCancellable *cancellable,
+- GError **error)
+-{
+- GError *identity_error = NULL;
+- RestProxy *proxy = NULL;
+- RestProxyCall *call = NULL;
+- JsonParser *parser = NULL;
+- JsonObject *json_object;
+- SoupLogger *logger = NULL;
+- gchar *ret = NULL;
+- gchar *id = NULL;
+- gchar *presentation_identity = NULL;
+-
+- /* TODO: cancellable */
+-
+- proxy = oauth_proxy_new_with_token (goa_oauth_provider_get_consumer_key (oauth_provider),
+- goa_oauth_provider_get_consumer_secret (oauth_provider),
+- access_token,
+- access_token_secret,
+- "https://api.flickr.com/services/rest",
+- FALSE);
+- logger = goa_soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
+- rest_proxy_add_soup_feature (proxy, SOUP_SESSION_FEATURE (logger));
+-
+- call = rest_proxy_new_call (proxy);
+- rest_proxy_call_add_param (call, "method", "flickr.test.login");
+- rest_proxy_call_add_param (call, "format", "json");
+- rest_proxy_call_add_param (call, "nojsoncallback", "1");
+- rest_proxy_call_set_method (call, "GET");
+-
+- if (!rest_proxy_call_sync (call, error))
+- goto out;
+- if (rest_proxy_call_get_status_code (call) != 200)
+- {
+- g_set_error (error,
+- GOA_ERROR,
+- GOA_ERROR_FAILED,
+- _("Expected status 200 when requesting your identity, instead got status %d (%s)"),
+- rest_proxy_call_get_status_code (call),
+- rest_proxy_call_get_status_message (call));
+- goto out;
+- }
+-
+- parser = json_parser_new ();
+- if (!json_parser_load_from_data (parser,
+- rest_proxy_call_get_payload (call),
+- rest_proxy_call_get_payload_length (call),
+- &identity_error))
+- {
+- g_warning ("json_parser_load_from_data() failed: %s (%s, %d)",
+- identity_error->message,
+- g_quark_to_string (identity_error->domain),
+- identity_error->code);
+- g_set_error (error,
+- GOA_ERROR,
+- GOA_ERROR_FAILED,
+- _("Could not parse response"));
+- goto out;
+- }
+-
+- json_object = json_node_get_object (json_parser_get_root (parser));
+- if (!json_object_has_member (json_object, "user"))
+- {
+- g_warning ("Did not find user in JSON data");
+- g_set_error (error,
+- GOA_ERROR,
+- GOA_ERROR_FAILED,
+- _("Could not parse response"));
+- goto out;
+- }
+-
+- json_object = json_object_get_object_member (json_object, "user");
+- if (!json_object_has_member (json_object, "id"))
+- {
+- g_warning ("Did not find user.id in JSON data");
+- g_set_error (error,
+- GOA_ERROR,
+- GOA_ERROR_FAILED,
+- _("Could not parse response"));
+- goto out;
+- }
+- if (!json_object_has_member (json_object, "username"))
+- {
+- g_warning ("Did not find user.username in JSON data");
+- g_set_error (error,
+- GOA_ERROR,
+- GOA_ERROR_FAILED,
+- _("Could not parse response"));
+- goto out;
+- }
+-
+- id = g_strdup (json_object_get_string_member (json_object, "id"));
+-
+- json_object = json_object_get_object_member (json_object, "username");
+- if (!json_object_has_member (json_object, "_content"))
+- {
+- g_warning ("Did not find user.username._content in JSON data");
+- g_set_error (error,
+- GOA_ERROR,
+- GOA_ERROR_FAILED,
+- _("Could not parse response"));
+- goto out;
+- }
+-
+- presentation_identity = g_strdup (json_object_get_string_member (json_object, "_content"));
+-
+- ret = id;
+- id = NULL;
+- if (out_presentation_identity != NULL)
+- {
+- *out_presentation_identity = presentation_identity;
+- presentation_identity = NULL;
+- }
+-
+- out:
+- g_clear_object (&parser);
+- g_clear_error (&identity_error);
+- g_clear_object (&call);
+- g_clear_object (&proxy);
+- g_clear_object (&logger);
+- g_free (id);
+- g_free (presentation_identity);
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gboolean
+-is_identity_node (GoaOAuthProvider *oauth_provider, WebKitDOMHTMLInputElement *element)
+-{
+- /* Flickr does not provide a way to query the string used by the
+- * user to log in via the web interface. The user id and username
+- * returned by flickr.test.login [1] are not what we are looking
+- * for.
+- *
+- * [1] http://www.flickr.com/services/api/flickr.test.login.html
+- */
+- return FALSE;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gchar *
+-parse_request_token_error (GoaOAuthProvider *oauth_provider, RestProxyCall *call)
+-{
+- const gchar *payload;
+- gchar *msg = NULL;
+- guint status;
+-
+- payload = rest_proxy_call_get_payload (call);
+- status = rest_proxy_call_get_status_code (call);
+-
+- if (status == 401 && g_strcmp0 (payload, "oauth_problem=timestamp_refused") == 0)
+- msg = g_strdup (_("Your system time is invalid. Check your date and time settings."));
+-
+- return msg;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gboolean
+-build_object (GoaProvider *provider,
+- GoaObjectSkeleton *object,
+- GKeyFile *key_file,
+- const gchar *group,
+- GDBusConnection *connection,
+- gboolean just_added,
+- GError **error)
+-{
+- GoaAccount *account = NULL;
+- gboolean photos_enabled;
+- gboolean ret = FALSE;
+-
+- /* Chain up */
+- if (!GOA_PROVIDER_CLASS (goa_flickr_provider_parent_class)->build_object (provider,
+- object,
+- key_file,
+- group,
+- connection,
+- just_added,
+- error))
+- goto out;
+-
+- account = goa_object_get_account (GOA_OBJECT (object));
+-
+- /* Photos */
+- photos_enabled = g_key_file_get_boolean (key_file, group, "PhotosEnabled", NULL);
+- goa_object_skeleton_attach_photos (object, photos_enabled);
+-
+- if (just_added)
+- {
+- goa_account_set_photos_disabled (account, !photos_enabled);
+-
+- g_signal_connect (account,
+- "notify::photos-disabled",
+- G_CALLBACK (goa_util_account_notify_property_cb),
+- (gpointer) "PhotosEnabled");
+- }
+-
+- ret = TRUE;
+-
+- out:
+- g_clear_object (&account);
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static void
+-add_account_key_values (GoaOAuthProvider *oauth_provider,
+- GVariantBuilder *builder)
+-{
+- g_variant_builder_add (builder, "{ss}", "PhotosEnabled", "true");
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static void
+-goa_flickr_provider_init (GoaFlickrProvider *self)
+-{
+-}
+-
+-static void
+-goa_flickr_provider_class_init (GoaFlickrProviderClass *klass)
+-{
+- GoaProviderClass *provider_class;
+- GoaOAuthProviderClass *oauth_class;
+-
+- provider_class = GOA_PROVIDER_CLASS (klass);
+- provider_class->get_provider_type = get_provider_type;
+- provider_class->get_provider_name = get_provider_name;
+- provider_class->get_provider_group = get_provider_group;
+- provider_class->get_provider_features = get_provider_features;
+- provider_class->build_object = build_object;
+-
+- oauth_class = GOA_OAUTH_PROVIDER_CLASS (klass);
+- oauth_class->get_identity_sync = get_identity_sync;
+- oauth_class->is_identity_node = is_identity_node;
+- oauth_class->get_consumer_key = get_consumer_key;
+- oauth_class->get_consumer_secret = get_consumer_secret;
+- oauth_class->get_request_uri = get_request_uri;
+- oauth_class->get_authorization_uri = get_authorization_uri;
+- oauth_class->get_token_uri = get_token_uri;
+- oauth_class->get_callback_uri = get_callback_uri;
+- oauth_class->parse_request_token_error = parse_request_token_error;
+- oauth_class->add_account_key_values = add_account_key_values;
+-}
+diff --git a/src/goabackend/goaflickrprovider.h b/src/goabackend/goaflickrprovider.h
+deleted file mode 100644
+index f08a8a6..0000000
+--- a/src/goabackend/goaflickrprovider.h
++++ /dev/null
+@@ -1,37 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+-/*
+- * Copyright © 2012 Willem van Engen
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+-#error "Only can be included directly."
+-#endif
+-
+-#ifndef __GOA_FLICKR_PROVIDER_H__
+-#define __GOA_FLICKR_PROVIDER_H__
+-
+-#include
+-
+-#include "goaoauthprovider.h"
+-
+-G_BEGIN_DECLS
+-
+-#define GOA_TYPE_FLICKR_PROVIDER (goa_flickr_provider_get_type ())
+-G_DECLARE_FINAL_TYPE (GoaFlickrProvider, goa_flickr_provider, GOA, FLICKR_PROVIDER, GoaOAuthProvider);
+-
+-G_END_DECLS
+-
+-#endif /* __GOA_FLICKR_PROVIDER_H__ */
+diff --git a/src/goabackend/goafoursquareprovider.c b/src/goabackend/goafoursquareprovider.c
+index c1e4146..def21cb 100644
+--- a/src/goabackend/goafoursquareprovider.c
++++ b/src/goabackend/goafoursquareprovider.c
+@@ -251,31 +251,6 @@ get_identity_sync (GoaOAuth2Provider *oauth2_provider,
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+-static gboolean
+-is_identity_node (GoaOAuth2Provider *oauth2_provider, WebKitDOMHTMLInputElement *element)
+-{
+- gboolean ret = FALSE;
+- gchar *element_type = NULL;
+- gchar *name = NULL;
+-
+- g_object_get (element, "type", &element_type, NULL);
+- if (g_strcmp0 (element_type, "email") != 0)
+- goto out;
+-
+- name = webkit_dom_html_input_element_get_name (element);
+- if (g_strcmp0 (name, "emailOrPhone") != 0)
+- goto out;
+-
+- ret = TRUE;
+-
+- out:
+- g_free (element_type);
+- g_free (name);
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+ static gboolean
+ build_object (GoaProvider *provider,
+ GoaObjectSkeleton *object,
+@@ -366,6 +341,5 @@ goa_foursquare_provider_class_init (GoaFoursquareProviderClass *klass)
+ oauth2_class->get_client_secret = get_client_secret;
+ oauth2_class->get_use_mobile_browser = get_use_mobile_browser;
+ oauth2_class->get_identity_sync = get_identity_sync;
+- oauth2_class->is_identity_node = is_identity_node;
+ oauth2_class->add_account_key_values = add_account_key_values;
+ }
+diff --git a/src/goabackend/goagoogleprovider.c b/src/goabackend/goagoogleprovider.c
+index b3c0f8f..0fb40c3 100644
+--- a/src/goabackend/goagoogleprovider.c
++++ b/src/goabackend/goagoogleprovider.c
+@@ -32,6 +32,7 @@
+ struct _GoaGoogleProvider
+ {
+ GoaOAuth2Provider parent_instance;
++ gchar *redirect_uri;
+ };
+
+ G_DEFINE_TYPE_WITH_CODE (GoaGoogleProvider, goa_google_provider, GOA_TYPE_OAUTH2_PROVIDER,
+@@ -76,19 +77,50 @@ get_provider_features (GoaProvider *provider)
+ static const gchar *
+ get_authorization_uri (GoaOAuth2Provider *oauth2_provider)
+ {
+- return "https://accounts.google.com/o/oauth2/auth";
++ return "https://accounts.google.com/o/oauth2/v2/auth";
+ }
+
+ static const gchar *
+ get_token_uri (GoaOAuth2Provider *oauth2_provider)
+ {
+- return "https://accounts.google.com/o/oauth2/token";
++ return "https://oauth2.googleapis.com/token";
+ }
+
+ static const gchar *
+ get_redirect_uri (GoaOAuth2Provider *oauth2_provider)
+ {
+- return "http://localhost";
++ G_LOCK_DEFINE_STATIC (redirect_uri);
++ GoaGoogleProvider *self = GOA_GOOGLE_PROVIDER (oauth2_provider);
++
++ G_LOCK (redirect_uri);
++
++ if (!self->redirect_uri) {
++ GPtrArray *array;
++ gchar **strv;
++ gchar *joinstr;
++ guint ii;
++
++ strv = g_strsplit (GOA_GOOGLE_CLIENT_ID, ".", -1);
++ array = g_ptr_array_new ();
++
++ for (ii = 0; strv[ii]; ii++) {
++ g_ptr_array_insert (array, 0, strv[ii]);
++ }
++
++ g_ptr_array_add (array, NULL);
++
++ joinstr = g_strjoinv (".", (gchar **) array->pdata);
++ /* Use reverse-DNS of the client ID with the below path */
++ self->redirect_uri = g_strconcat (joinstr, ":/oauth2redirect", NULL);
++
++ g_ptr_array_free (array, TRUE);
++ g_strfreev (strv);
++ g_free (joinstr);
++ }
++
++ G_UNLOCK (redirect_uri);
++
++ return self->redirect_uri;
+ }
+
+ static const gchar *
+@@ -228,37 +260,6 @@ get_identity_sync (GoaOAuth2Provider *oauth2_provider,
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+-static gboolean
+-is_identity_node (GoaOAuth2Provider *oauth2_provider, WebKitDOMHTMLInputElement *element)
+-{
+- gboolean ret = FALSE;
+- gchar *element_type = NULL;
+- gchar *id = NULL;
+- gchar *name = NULL;
+-
+- g_object_get (element, "type", &element_type, NULL);
+- if (g_strcmp0 (element_type, "email") != 0)
+- goto out;
+-
+- id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (element));
+- if (g_strcmp0 (id, "identifierId") != 0)
+- goto out;
+-
+- name = webkit_dom_html_input_element_get_name (element);
+- if (g_strcmp0 (name, "identifier") != 0)
+- goto out;
+-
+- ret = TRUE;
+-
+- out:
+- g_free (element_type);
+- g_free (id);
+- g_free (name);
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+ static gboolean
+ build_object (GoaProvider *provider,
+ GoaObjectSkeleton *object,
+@@ -400,6 +401,16 @@ add_account_key_values (GoaOAuth2Provider *oauth2_provider,
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
++static void
++goa_google_finalize (GObject *object)
++{
++ GoaGoogleProvider *self = GOA_GOOGLE_PROVIDER (object);
++
++ g_free (self->redirect_uri);
++
++ G_OBJECT_CLASS (goa_google_provider_parent_class)->finalize (object);
++}
++
+ static void
+ goa_google_provider_init (GoaGoogleProvider *self)
+ {
+@@ -410,6 +421,10 @@ goa_google_provider_class_init (GoaGoogleProviderClass *klass)
+ {
+ GoaProviderClass *provider_class;
+ GoaOAuth2ProviderClass *oauth2_class;
++ GObjectClass *object_class;
++
++ object_class = G_OBJECT_CLASS (klass);
++ object_class->finalize = goa_google_finalize;
+
+ provider_class = GOA_PROVIDER_CLASS (klass);
+ provider_class->get_provider_type = get_provider_type;
+@@ -426,7 +441,6 @@ goa_google_provider_class_init (GoaGoogleProviderClass *klass)
+ oauth2_class->get_identity_sync = get_identity_sync;
+ oauth2_class->get_redirect_uri = get_redirect_uri;
+ oauth2_class->get_scope = get_scope;
+- oauth2_class->is_identity_node = is_identity_node;
+ oauth2_class->get_token_uri = get_token_uri;
+ oauth2_class->add_account_key_values = add_account_key_values;
+ }
+diff --git a/src/goabackend/goaoauth2handler.c b/src/goabackend/goaoauth2handler.c
+new file mode 100644
+index 0000000..c5a86cb
+--- /dev/null
++++ b/src/goabackend/goaoauth2handler.c
+@@ -0,0 +1,173 @@
++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
++/*
++ * Copyright © 2023 GNOME Foundation Inc.
++ * Contributor: Andy Holmes
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, see .
++ */
++
++#include "config.h"
++
++#include
++#include
++#include
++
++
++static const SecretSchema oauth2_schema =
++{
++ .name = "org.gnome.OnlineAccounts.OAuth2",
++ .flags = SECRET_SCHEMA_NONE,
++ .attributes = {
++ {
++ .name = "goa-oauth2-client",
++ .type = SECRET_SCHEMA_ATTRIBUTE_STRING,
++ },
++ {
++ .name = "goa-oauth2-provider",
++ .type = SECRET_SCHEMA_ATTRIBUTE_STRING,
++ },
++ { "NULL", 0 }
++ }
++};
++
++static struct
++{
++ const char *client_id;
++ const char *provider;
++}
++oauth2_providers[] =
++{
++#ifdef GOA_GOOGLE_ENABLED
++ {
++ .client_id = GOA_GOOGLE_CLIENT_ID,
++ .provider = GOA_GOOGLE_NAME,
++ },
++#endif
++#ifdef GOA_WINDOWS_LIVE_ENABLED
++ {
++ .client_id = GOA_WINDOWS_LIVE_CLIENT_ID,
++ .provider = GOA_WINDOWS_LIVE_NAME,
++ },
++#endif
++ { NULL, NULL },
++};
++
++static gboolean
++get_oauth2_provider (const char *needle,
++ const char **client_out,
++ const char **provider_out)
++{
++ g_return_val_if_fail (needle != NULL, FALSE);
++
++ for (unsigned int i = 0; oauth2_providers[i].client_id != NULL; i++)
++ {
++ if (g_str_equal (needle, oauth2_providers[i].client_id))
++ {
++ if (client_out)
++ *client_out = oauth2_providers[i].client_id;
++
++ if (provider_out)
++ *provider_out = oauth2_providers[i].provider;
++
++ return TRUE;
++ }
++ }
++
++ return FALSE;
++}
++
++int
++main (int argc,
++ char **argv)
++{
++ SoupURI *uri = NULL;
++ const char *scheme = NULL;
++ const char *path = NULL;
++ const char *client_id = NULL;
++ const char *provider_type = NULL;
++ GError *error = NULL;
++
++ if (argc < 2)
++ {
++ g_printerr ("%s: Missing URI\n", argv[0]);
++ return EXIT_FAILURE;
++ }
++
++ uri = soup_uri_new (argv[1]);
++ if (uri == NULL)
++ {
++ g_printerr ("%s: Invalid URI: %s\n", argv[0], argv[1]);
++ return EXIT_FAILURE;
++ }
++
++ /* Google apps may use a reverse-DNS form of the client ID as the URI scheme
++ * See: https://developers.google.com/identity/protocols/oauth2/native-app
++ */
++ scheme = soup_uri_get_scheme (uri);
++ if (scheme != NULL)
++ {
++ g_auto (GStrv) strv = g_strsplit (scheme, ".", -1);
++ g_autoptr (GString) tmp = g_string_new ("");
++
++ for (unsigned int i = 0; strv[i] != NULL; i++)
++ {
++ if (i > 0)
++ g_string_prepend_c (tmp, '.');
++ g_string_prepend (tmp, strv[i]);
++ }
++
++ get_oauth2_provider (tmp->str, &client_id, &provider_type);
++ }
++
++ /* Windows Live uses goa-oauth2:// with the client ID as the first path segment
++ */
++ if (client_id == NULL)
++ {
++ path = soup_uri_get_path (uri);
++ if (path != NULL && *path != '\0')
++ {
++ g_auto (GStrv) strv = NULL;
++
++ strv = g_strsplit (*path == '/' ? path +1 : path, "/", 1);
++ get_oauth2_provider (strv[0], &client_id, &provider_type);
++ }
++ }
++
++ if (client_id == NULL)
++ {
++ g_printerr ("%s: Unknown provider\n", argv[0]);
++ soup_uri_free (uri);
++ return EXIT_FAILURE;
++ }
++
++ if (!secret_password_store_sync (&oauth2_schema,
++ SECRET_COLLECTION_SESSION,
++ "GNOME Online Accounts OAuth2 URI",
++ argv[1], /* Secret */
++ NULL,
++ &error,
++ "goa-oauth2-client", client_id,
++ "goa-oauth2-provider", provider_type,
++ NULL))
++ {
++ if (error != NULL)
++ g_printerr ("%s: Failed to store OAuth2 URI: %s\n", argv[0], error->message);
++
++ soup_uri_free (uri);
++ g_clear_error (&error);
++ return EXIT_FAILURE;
++ }
++
++ return EXIT_SUCCESS;
++}
+diff --git a/src/goabackend/goaoauth2provider-priv.h b/src/goabackend/goaoauth2provider-priv.h
+index de1b808..4b00a24 100644
+--- a/src/goabackend/goaoauth2provider-priv.h
++++ b/src/goabackend/goaoauth2provider-priv.h
+@@ -26,8 +26,6 @@
+ #include
+ #include
+ #include
+-#include
+-#include
+
+ G_BEGIN_DECLS
+
+@@ -51,11 +49,7 @@ G_BEGIN_DECLS
+ * @build_authorization_uri: Virtual function for goa_oauth2_provider_build_authorization_uri().
+ * @get_use_mobile_browser: Virtual function for goa_oauth2_provider_get_use_mobile_browser().
+ * @add_account_key_values: Virtual function for goa_oauth2_provider_add_account_key_values().
+- * @decide_navigation_policy: Virtual function for goa_oauth2_provider_decide_navigation_policy().
+ * @process_redirect_url: Virtual function for goa_oauth2_provider_process_redirect_url().
+- * @is_deny_node: Virtual function for goa_oauth2_provider_is_deny_node().
+- * @is_identity_node: Virtual function for goa_oauth2_provider_is_identity_node().
+- * @is_password_node: Virtual function for goa_oauth2_provider_is_password_node().
+ *
+ * Class structure for #GoaOAuth2Provider.
+ */
+@@ -86,18 +80,7 @@ struct _GoaOAuth2ProviderClass
+ void (*add_account_key_values) (GoaOAuth2Provider *provider,
+ GVariantBuilder *builder);
+
+- /* pure virtual */
+- gboolean (*is_identity_node) (GoaOAuth2Provider *provider,
+- WebKitDOMHTMLInputElement *element);
+-
+ /* virtual but with default implementation */
+- gboolean (*is_deny_node) (GoaOAuth2Provider *provider,
+- WebKitDOMNode *node);
+- gboolean (*is_password_node) (GoaOAuth2Provider *provider,
+- WebKitDOMHTMLInputElement *element);
+- gboolean (*decide_navigation_policy) (GoaOAuth2Provider *provider,
+- WebKitWebView *web_view,
+- WebKitNavigationPolicyDecision *decision);
+ gboolean (*process_redirect_url) (GoaOAuth2Provider *provider,
+ const gchar *redirect_url,
+ gchar **access_token,
+diff --git a/src/goabackend/goaoauth2provider-web-extension.h b/src/goabackend/goaoauth2provider-web-extension.h
+deleted file mode 100644
+index baac005..0000000
+--- a/src/goabackend/goaoauth2provider-web-extension.h
++++ /dev/null
+@@ -1,40 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+-/*
+- * Copyright © 2016 – 2017 Red Hat, Inc.
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+-#error "Only can be included directly."
+-#endif
+-
+-#ifndef __GOA_OAUTH2_PROVIDER_WEB_EXTENSION_H__
+-#define __GOA_OAUTH2_PROVIDER_WEB_EXTENSION_H__
+-
+-#include
+-#include
+-
+-G_BEGIN_DECLS
+-
+-gboolean goa_oauth2_provider_is_deny_node (GoaOAuth2Provider *provider,
+- WebKitDOMNode *node);
+-gboolean goa_oauth2_provider_is_identity_node (GoaOAuth2Provider *provider,
+- WebKitDOMHTMLInputElement *element);
+-gboolean goa_oauth2_provider_is_password_node (GoaOAuth2Provider *provider,
+- WebKitDOMHTMLInputElement *element);
+-
+-G_END_DECLS
+-
+-#endif /* __GOA_OAUTH2_PROVIDER_WEB_EXTENSION_H__ */
+diff --git a/src/goabackend/goaoauth2provider-web-view.h b/src/goabackend/goaoauth2provider-web-view.h
+deleted file mode 100644
+index f2dae5e..0000000
+--- a/src/goabackend/goaoauth2provider-web-view.h
++++ /dev/null
+@@ -1,37 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+-/*
+- * Copyright © 2016 – 2017 Red Hat, Inc.
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+-#error "Only can be included directly."
+-#endif
+-
+-#ifndef __GOA_OAUTH2_PROVIDER_WEB_VIEW_H__
+-#define __GOA_OAUTH2_PROVIDER_WEB_VIEW_H__
+-
+-#include
+-#include
+-
+-G_BEGIN_DECLS
+-
+-gboolean goa_oauth2_provider_decide_navigation_policy (GoaOAuth2Provider *provider,
+- WebKitWebView *web_view,
+- WebKitNavigationPolicyDecision *decision);
+-
+-G_END_DECLS
+-
+-#endif /* __GOA_OAUTH2_PROVIDER_WEB_VIEW_H__ */
+diff --git a/src/goabackend/goaoauth2provider.c b/src/goabackend/goaoauth2provider.c
+index 3715431..e7d5d2e 100644
+--- a/src/goabackend/goaoauth2provider.c
++++ b/src/goabackend/goaoauth2provider.c
+@@ -22,16 +22,13 @@
+
+ #include
+ #include
++#include
+ #include
+-#include
+
+ #include "goaprovider.h"
+ #include "goautils.h"
+-#include "goawebview.h"
+ #include "goaoauth2provider.h"
+ #include "goaoauth2provider-priv.h"
+-#include "goaoauth2provider-web-extension.h"
+-#include "goaoauth2provider-web-view.h"
+ #include "goarestproxy.h"
+
+ /**
+@@ -81,6 +78,8 @@ struct _GoaOAuth2ProviderPrivate
+ gchar *identity;
+ gchar *presentation_identity;
+ gchar *password;
++ gchar *request_uri;
++ SecretCollection *session;
+ };
+
+ G_LOCK_DEFINE_STATIC (provider_lock);
+@@ -134,70 +133,6 @@ goa_oauth2_provider_get_use_mobile_browser (GoaOAuth2Provider *self)
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+-static gboolean
+-goa_oauth2_provider_is_deny_node_default (GoaOAuth2Provider *self, WebKitDOMNode *node)
+-{
+- return FALSE;
+-}
+-
+-/**
+- * goa_oauth2_provider_is_deny_node:
+- * @self: A #GoaOAuth2Provider.
+- * @node: A WebKitDOMNode.
+- *
+- * Checks whether @node is the HTML UI element that the user can use
+- * to deny permission to access his account. Usually they are either a
+- * WebKitDOMHTMLButtonElement or a WebKitDOMHTMLInputElement.
+- *
+- * Please note that providers may have multiple such elements in their
+- * UI and this method should catch all of them.
+- *
+- * This is a virtual method where the default implementation returns
+- * %FALSE.
+- *
+- * Returns: %TRUE if the @node can be used to deny permission.
+- */
+-gboolean
+-goa_oauth2_provider_is_deny_node (GoaOAuth2Provider *self, WebKitDOMNode *node)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
+- return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->is_deny_node (self, node);
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gboolean
+-goa_oauth2_provider_is_password_node_default (GoaOAuth2Provider *self, WebKitDOMHTMLInputElement *element)
+-{
+- return FALSE;
+-}
+-
+-/**
+- * goa_oauth2_provider_is_password_node:
+- * @self: A #GoaOAuth2Provider.
+- * @element: A WebKitDOMHTMLInputElement
+- *
+- * Checks whether @element is the HTML UI element that the user can
+- * use to enter her password. This can be used to offer a
+- * #GoaPasswordBased interface by saving the user's
+- * password. Providers usually frown upon doing this, so this is not
+- * recommended.
+- *
+- * This is a virtual method where the default implementation returns
+- * %FALSE.
+- *
+- * Returns: %TRUE if @element can be used to enter the password.
+- */
+-gboolean
+-goa_oauth2_provider_is_password_node (GoaOAuth2Provider *self, WebKitDOMHTMLInputElement *element)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
+- g_return_val_if_fail (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT (element), FALSE);
+- return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->is_password_node (self, element);
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+ static void
+ goa_oauth2_provider_add_account_key_values_default (GoaOAuth2Provider *self,
+ GVariantBuilder *builder)
+@@ -287,45 +222,6 @@ goa_oauth2_provider_build_authorization_uri (GoaOAuth2Provider *self,
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+-static gboolean
+-goa_oauth2_provider_decide_navigation_policy_default (GoaOAuth2Provider *self,
+- WebKitWebView *web_view,
+- WebKitNavigationPolicyDecision *decision)
+-{
+- return FALSE;
+-}
+-
+-/*
+- * goa_oauth2_provider_decide_navigation_policy_default:
+- * @self: A #GoaOAuth2Provider.
+- * @decision: A #WebKitNavigationPolicyDecision
+- *
+- * Certain OAuth2-like, but not exactly OAuth2,
+- * providers may not send us to the redirect URI, as expected. They
+- * might need some special handling for that. This is a provider
+- * specific hook to accommodate them.
+- *
+- * This is a virtual method where the default implementation returns
+- * %FALSE.
+- *
+- * Returns: %TRUE if @provider decided what to do with @decision,
+- * %FALSE otherwise.
+- */
+-gboolean
+-goa_oauth2_provider_decide_navigation_policy (GoaOAuth2Provider *self,
+- WebKitWebView *web_view,
+- WebKitNavigationPolicyDecision *decision)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
+- g_return_val_if_fail (WEBKIT_IS_WEB_VIEW (web_view), FALSE);
+- g_return_val_if_fail (WEBKIT_IS_NAVIGATION_POLICY_DECISION (decision), FALSE);
+-
+- return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->decide_navigation_policy (self, web_view, decision);
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+ /**
+ * goa_oauth2_provider_process_redirect_url:
+ * @self: A #GoaOAuth2Provider.
+@@ -551,26 +447,6 @@ goa_oauth2_provider_get_identity_sync (GoaOAuth2Provider *self,
+ error);
+ }
+
+-/**
+- * goa_oauth2_provider_is_identity_node:
+- * @self: A #GoaOAuth2Provider.
+- * @element: A WebKitDOMHTMLInputElement.
+- *
+- * Checks whether @element is the HTML UI element that the user can
+- * use to identify herself at the provider.
+- *
+- * This is a pure virtual method - a subclass must provide an
+- * implementation.
+- *
+- * Returns: %TRUE if the @element can be used to deny permission.
+- */
+-gboolean
+-goa_oauth2_provider_is_identity_node (GoaOAuth2Provider *self, WebKitDOMHTMLInputElement *element)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
+- return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->is_identity_node (self, element);
+-}
+-
+ /* ---------------------------------------------------------------------------------------------------- */
+
+ static gchar *
+@@ -730,72 +606,42 @@ get_tokens_sync (GoaOAuth2Provider *self,
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+-static void
+-on_web_view_deny_click (GoaWebView *web_view, gpointer user_data)
+-{
+- GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (user_data);
+- GoaOAuth2ProviderPrivate *priv;
+-
+- priv = goa_oauth2_provider_get_instance_private (self);
+- gtk_dialog_response (priv->dialog, GTK_RESPONSE_CANCEL);
+-}
+-
+-static void
+-on_web_view_password_submit (GoaWebView *web_view, const gchar *password, gpointer user_data)
+-{
+- GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (user_data);
+- GoaOAuth2ProviderPrivate *priv;
+-
+- priv = goa_oauth2_provider_get_instance_private (self);
+-
+- g_free (priv->password);
+- priv->password = g_strdup (password);
+-}
+-
+ static gboolean
+-on_web_view_decide_policy (WebKitWebView *web_view,
+- WebKitPolicyDecision *decision,
+- WebKitPolicyDecisionType decision_type,
+- gpointer user_data)
++parse_requested_uri (GoaOAuth2Provider *self,
++ const char *requested_uri)
+ {
+- GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (user_data);
+- GoaOAuth2ProviderPrivate *priv;
++ GoaOAuth2ProviderPrivate *priv = goa_oauth2_provider_get_instance_private (self);
+ GHashTable *key_value_pairs;
+- WebKitNavigationAction *action;
+- WebKitURIRequest *request;
+ SoupURI *uri;
+ const gchar *fragment;
+ const gchar *oauth2_error;
+ const gchar *query;
+ const gchar *redirect_uri;
+- const gchar *requested_uri;
+- gint response_id = GTK_RESPONSE_NONE;
+-
+- priv = goa_oauth2_provider_get_instance_private (self);
+-
+- if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
+- goto default_behaviour;
+-
+- if (goa_oauth2_provider_decide_navigation_policy (self,
+- web_view,
+- WEBKIT_NAVIGATION_POLICY_DECISION (decision)))
+- {
+- response_id = 0;
+- goto ignore_request;
+- }
+
+ /* TODO: use oauth2_proxy_extract_access_token() */
++ g_assert (priv->error == NULL);
+
+- action = webkit_navigation_policy_decision_get_navigation_action (WEBKIT_NAVIGATION_POLICY_DECISION (decision));
+- request = webkit_navigation_action_get_request (action);
+- requested_uri = webkit_uri_request_get_uri (request);
+ redirect_uri = goa_oauth2_provider_get_redirect_uri (self);
+ if (!g_str_has_prefix (requested_uri, redirect_uri))
+- goto default_behaviour;
++ {
++ g_set_error (&priv->error,
++ GOA_ERROR,
++ GOA_ERROR_FAILED,
++ "Invalid URI: %s",
++ requested_uri);
++ return FALSE;
++ }
+
+ uri = soup_uri_new (requested_uri);
+- fragment = soup_uri_get_fragment (uri);
+- query = soup_uri_get_query (uri);
++ if (uri == NULL)
++ {
++ g_set_error (&priv->error,
++ GOA_ERROR,
++ GOA_ERROR_FAILED,
++ "Invalid URI: %s",
++ requested_uri);
++ return FALSE;
++ }
+
+ /* Three cases:
+ * 1) we can either have the backend handle the URI for us, or
+@@ -806,23 +652,23 @@ on_web_view_decide_policy (WebKitWebView *web_view,
+ */
+ if (GOA_OAUTH2_PROVIDER_GET_CLASS (self)->process_redirect_url)
+ {
+- gchar *url;
++ g_autofree char *url = NULL;
+
+ url = soup_uri_to_string (uri, FALSE);
++ soup_uri_free (uri);
+ if (!goa_oauth2_provider_process_redirect_url (self, url, &priv->access_token, &priv->error))
+ {
+ g_prefix_error (&priv->error, _("Authorization response: "));
+ priv->error->domain = GOA_ERROR;
+ priv->error->code = GOA_ERROR_NOT_AUTHORIZED;
+- response_id = GTK_RESPONSE_CLOSE;
++
++ return FALSE;
+ }
+- else
+- response_id = GTK_RESPONSE_OK;
+
+- g_free (url);
+- goto ignore_request;
++ return TRUE;
+ }
+
++ fragment = soup_uri_get_fragment (uri);
+ if (fragment != NULL)
+ {
+ /* fragment is encoded into a key/value pairs for the token and
+@@ -846,57 +692,173 @@ on_web_view_decide_policy (WebKitWebView *web_view,
+ priv->access_token_expires_in = atoi (expires_in_str);
+
+ priv->refresh_token = g_strdup (g_hash_table_lookup (key_value_pairs, "refresh_token"));
+-
+- response_id = GTK_RESPONSE_OK;
+ }
+ g_hash_table_unref (key_value_pairs);
+- }
+
+- if (priv->access_token != NULL)
+- goto ignore_request;
++ if (priv->access_token != NULL)
++ {
++ soup_uri_free (uri);
++ return TRUE;
++ }
++ }
+
++ query = soup_uri_get_query (uri);
+ if (query != NULL)
+ {
+ key_value_pairs = soup_form_decode (query);
+
+ priv->authorization_code = g_strdup (g_hash_table_lookup (key_value_pairs, "code"));
+- if (priv->authorization_code != NULL)
+- response_id = GTK_RESPONSE_OK;
+-
+ g_hash_table_unref (key_value_pairs);
++ if (priv->authorization_code != NULL)
++ {
++ soup_uri_free (uri);
++ return TRUE;
++ }
+ }
+
+- if (priv->authorization_code != NULL)
+- goto ignore_request;
+-
+ /* In case we don't find the access_token or auth code, then look
+ * for the error in the query part of the URI.
+ */
+ key_value_pairs = soup_form_decode (query);
+ oauth2_error = (const gchar *) g_hash_table_lookup (key_value_pairs, "error");
+ if (g_strcmp0 (oauth2_error, GOA_OAUTH2_ACCESS_DENIED) == 0)
+- response_id = GTK_RESPONSE_CANCEL;
+- else
+ {
+ g_set_error (&priv->error,
+ GOA_ERROR,
+ GOA_ERROR_NOT_AUTHORIZED,
+ _("Authorization response: %s"),
+ oauth2_error);
+- response_id = GTK_RESPONSE_CLOSE;
++ }
++ else
++ {
++ g_set_error_literal (&priv->error,
++ GOA_ERROR,
++ GOA_ERROR_FAILED,
++ _("Failed to authenticate"));
+ }
+ g_hash_table_unref (key_value_pairs);
+- goto ignore_request;
++ soup_uri_free (uri);
++ return FALSE;
++}
+
+- ignore_request:
+- g_assert (response_id != GTK_RESPONSE_NONE);
+- if (response_id < 0)
+- gtk_dialog_response (priv->dialog, response_id);
+- webkit_policy_decision_ignore (decision);
+- return TRUE;
++/* ---------------------------------------------------------------------------------------------------- */
+
+- default_behaviour:
+- return FALSE;
++static const SecretSchema oauth2_schema =
++{
++ .name = "org.gnome.OnlineAccounts.OAuth2",
++ .flags = SECRET_SCHEMA_NONE,
++ .attributes = {
++ {
++ .name = "goa-oauth2-client",
++ .type = SECRET_SCHEMA_ATTRIBUTE_STRING,
++ },
++ {
++ .name = "goa-oauth2-provider",
++ .type = SECRET_SCHEMA_ATTRIBUTE_STRING,
++ },
++ { "NULL", 0 }
++ }
++};
++
++static void
++on_secrets_changed (SecretCollection *collection,
++ GParamSpec *pspec,
++ GoaOAuth2Provider *self)
++{
++ GoaOAuth2ProviderPrivate *priv = goa_oauth2_provider_get_instance_private (self);
++ const char *client_id = NULL;
++ const char *provider_type = NULL;
++ g_autofree char *requested_uri = NULL;
++ GtkResponseType response_id = GTK_RESPONSE_NONE;
++
++ client_id = goa_oauth2_provider_get_client_id (self);
++ provider_type = goa_provider_get_provider_type (GOA_PROVIDER (self));
++ requested_uri = secret_password_lookup_sync (&oauth2_schema, NULL, NULL,
++ "goa-oauth2-client", client_id,
++ "goa-oauth2-provider", provider_type,
++ NULL);
++
++ if (requested_uri != NULL)
++ {
++ if (parse_requested_uri (self, requested_uri))
++ response_id = GTK_RESPONSE_OK;
++ else
++ response_id = GTK_RESPONSE_CANCEL;
++ }
++
++ if (response_id != GTK_RESPONSE_NONE)
++ {
++ g_signal_handlers_disconnect_by_func (collection, on_secrets_changed, self);
++ gtk_dialog_response (priv->dialog, response_id);
++ }
++}
++
++static void
++secret_service_get_cb (GObject *object,
++ GAsyncResult *result,
++ GoaOAuth2Provider *self)
++{
++ GoaOAuth2ProviderPrivate *priv = goa_oauth2_provider_get_instance_private (self);
++ g_autoptr (SecretService) service = NULL;
++ g_autolist (SecretCollection) collections = NULL;
++
++ service = secret_service_get_finish (result, &priv->error);
++ if (service == NULL)
++ goto out;
++
++ collections = secret_service_get_collections (service);
++ for (const GList *iter = collections; iter != NULL; iter = iter->next)
++ {
++ g_autofree char *label = secret_collection_get_label (iter->data);
++
++ /* The session collection is an empty string (?) */
++ if (g_strcmp0 (label, "") == 0)
++ {
++ const char *client_id = NULL;
++ const char *provider_type = NULL;
++
++ /* Ensure there's no dangling entry */
++ client_id = goa_oauth2_provider_get_client_id (self);
++ provider_type = goa_provider_get_provider_type (GOA_PROVIDER (self));
++ secret_password_clear_sync (&oauth2_schema, NULL, NULL,
++ "goa-oauth2-client", client_id,
++ "goa-oauth2-provider", provider_type,
++ NULL);
++
++ /* Watch the session collection for the requested URI */
++ priv->session = g_object_ref (iter->data);
++ g_signal_connect_object (priv->session,
++ "notify::items",
++ G_CALLBACK (on_secrets_changed),
++ self,
++ 0);
++ goto out;
++ }
++ }
++
++ if (priv->session == NULL && priv->error == NULL)
++ {
++ g_set_error (&priv->error,
++ GOA_ERROR,
++ GOA_ERROR_FAILED,
++ "Failed to connect to session keyring");
++ goto out;
++ }
++
++out:
++ g_main_loop_quit (priv->loop);
++}
++
++static void
++on_continue_in_browser (GtkButton *button,
++ GoaOAuth2Provider *self)
++{
++ GoaOAuth2ProviderPrivate *priv = goa_oauth2_provider_get_instance_private (self);
++
++ if (!g_app_info_launch_default_for_uri (priv->request_uri, NULL, &priv->error))
++ gtk_dialog_response (priv->dialog, GTK_RESPONSE_CANCEL);
++ else
++ gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE);
+ }
+
+ static gboolean
+@@ -906,12 +868,13 @@ get_tokens_and_identity (GoaOAuth2Provider *self,
+ GtkDialog *dialog,
+ GtkBox *vbox)
+ {
+- GoaOAuth2ProviderPrivate *priv;
++ GoaOAuth2ProviderPrivate *priv = goa_oauth2_provider_get_instance_private (self);
+ gboolean ret = FALSE;
+- gchar *url;
+- GtkWidget *embed;
++ int response_id = GTK_RESPONSE_NONE;
+ GtkWidget *grid;
+- GtkWidget *web_view;
++ GtkWidget *image;
++ GtkWidget *label;
++ GtkWidget *button;
+ const gchar *scope;
+ gchar *escaped_redirect_uri = NULL;
+ gchar *escaped_client_id = NULL;
+@@ -923,7 +886,6 @@ get_tokens_and_identity (GoaOAuth2Provider *self,
+ g_return_val_if_fail (GTK_IS_DIALOG (dialog), FALSE);
+ g_return_val_if_fail (GTK_IS_BOX (vbox), FALSE);
+
+- priv = goa_oauth2_provider_get_instance_private (self);
+ g_return_val_if_fail (priv->error == NULL, FALSE);
+
+ /* TODO: check with NM whether we're online, if not - return error */
+@@ -937,6 +899,8 @@ get_tokens_and_identity (GoaOAuth2Provider *self,
+ g_clear_pointer (&priv->authorization_code, g_free);
+ g_clear_pointer (&priv->access_token, g_free);
+ g_clear_pointer (&priv->refresh_token, g_free);
++ g_clear_pointer (&priv->request_uri, g_free);
++ g_clear_object (&priv->session);
+
+ /* TODO: use oauth2_proxy_build_login_url_full() */
+ escaped_redirect_uri = g_uri_escape_string (goa_oauth2_provider_get_redirect_uri (self), NULL, TRUE);
+@@ -946,40 +910,71 @@ get_tokens_and_identity (GoaOAuth2Provider *self,
+ escaped_scope = g_uri_escape_string (goa_oauth2_provider_get_scope (self), NULL, TRUE);
+ else
+ escaped_scope = NULL;
+- url = goa_oauth2_provider_build_authorization_uri (self,
+- goa_oauth2_provider_get_authorization_uri (self),
+- escaped_redirect_uri,
+- escaped_client_id,
+- escaped_scope);
++ priv->request_uri = goa_oauth2_provider_build_authorization_uri (self,
++ goa_oauth2_provider_get_authorization_uri (self),
++ escaped_redirect_uri,
++ escaped_client_id,
++ escaped_scope);
+
+ goa_utils_set_dialog_title (GOA_PROVIDER (self), dialog, add_account);
+
+- grid = gtk_grid_new ();
+- gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
+- gtk_grid_set_row_spacing (GTK_GRID (grid), 12);
++ grid = g_object_new (GTK_TYPE_BOX,
++ "orientation", GTK_ORIENTATION_VERTICAL,
++ "spacing", 18,
++ "halign", GTK_ALIGN_CENTER,
++ "hexpand", TRUE,
++ "valign", GTK_ALIGN_CENTER,
++ "vexpand", TRUE,
++ "margin", 12,
++ NULL);
+ gtk_container_add (GTK_CONTAINER (vbox), grid);
+
+- web_view = goa_web_view_new (GOA_PROVIDER (self), existing_identity);
+- gtk_widget_set_hexpand (web_view, TRUE);
+- gtk_widget_set_vexpand (web_view, TRUE);
+- embed = goa_web_view_get_view (GOA_WEB_VIEW (web_view));
+-
+- if (goa_oauth2_provider_get_use_mobile_browser (self))
+- goa_web_view_fake_mobile (GOA_WEB_VIEW (web_view));
+-
+- webkit_web_view_load_uri (WEBKIT_WEB_VIEW (embed), url);
+- g_signal_connect (embed,
+- "decide-policy",
+- G_CALLBACK (on_web_view_decide_policy),
+- self);
+- g_signal_connect (web_view, "deny-click", G_CALLBACK (on_web_view_deny_click), self);
+- g_signal_connect (web_view, "password-submit", G_CALLBACK (on_web_view_password_submit), self);
++ image = g_object_new (GTK_TYPE_IMAGE,
++ "icon-name", "web-browser-symbolic",
++ "pixel-size", 128,
++ NULL);
++ gtk_style_context_add_class (gtk_widget_get_style_context (image), "dim-label");
++ gtk_container_add (GTK_CONTAINER (grid), image);
++
++ label = gtk_label_new (_("Sign in with your browser to setup an account."));
++ gtk_style_context_add_class (gtk_widget_get_style_context (label), "heading");
++ gtk_container_add (GTK_CONTAINER (grid), label);
++
++ button = gtk_button_new_with_label (_("Continue"));
++ gtk_widget_set_halign (button, GTK_ALIGN_CENTER);
++ gtk_style_context_add_class (gtk_widget_get_style_context (button), "suggested-action");
++ g_signal_connect_object (button,
++ "clicked",
++ G_CALLBACK (on_continue_in_browser),
++ self, 0);
++ gtk_container_add (GTK_CONTAINER (grid), button);
++ gtk_dialog_add_button (priv->dialog, _("_Cancel"), GTK_RESPONSE_CANCEL);
+
+- gtk_container_add (GTK_CONTAINER (grid), web_view);
++ gtk_widget_show_all (GTK_WIDGET (vbox));
+ gtk_window_set_default_size (GTK_WINDOW (dialog), -1, -1);
+
+- gtk_widget_show_all (GTK_WIDGET (vbox));
+- gtk_dialog_run (GTK_DIALOG (dialog));
++ /* Watch the session secret collection for the OAuth2 URI */
++ secret_service_get (SECRET_SERVICE_LOAD_COLLECTIONS | SECRET_SERVICE_OPEN_SESSION,
++ NULL,
++ (GAsyncReadyCallback) secret_service_get_cb,
++ self);
++ g_main_loop_run (priv->loop);
++ if (priv->error != NULL)
++ goto out;
++
++ /* Inform the user authentication should be completed in the browser */
++ response_id = gtk_dialog_run (GTK_DIALOG (dialog));
++ if (response_id != GTK_RESPONSE_OK)
++ {
++ if (priv->error == NULL)
++ {
++ g_set_error (&priv->error,
++ GOA_ERROR,
++ GOA_ERROR_DIALOG_DISMISSED,
++ _("Dialog was dismissed"));
++ }
++ goto out;
++ }
+
+ /* We can have either the auth code, with which we'll obtain the token, or
+ * the token directly if we are using a client side flow, since we don't
+@@ -1038,12 +1033,14 @@ get_tokens_and_identity (GoaOAuth2Provider *self,
+ }
+
+ ret = TRUE;
++ gtk_dialog_response (dialog, GTK_RESPONSE_OK);
+
+ out:
+- g_free (url);
+ g_free (escaped_redirect_uri);
+ g_free (escaped_client_id);
+ g_free (escaped_scope);
++ g_clear_pointer (&priv->request_uri, g_free);
++ g_clear_object (&priv->session);
+ return ret;
+ }
+
+@@ -1107,6 +1104,7 @@ goa_oauth2_provider_add_account (GoaProvider *provider,
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ priv = goa_oauth2_provider_get_instance_private (self);
++ priv->loop = g_main_loop_new (NULL, FALSE);
+
+ if (!get_tokens_and_identity (self, TRUE, NULL, dialog, vbox))
+ goto out;
+@@ -1141,7 +1139,6 @@ goa_oauth2_provider_add_account (GoaProvider *provider,
+ NULL, /* GCancellable* */
+ (GAsyncReadyCallback) add_account_cb,
+ self);
+- priv->loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (priv->loop);
+ if (priv->error != NULL)
+ goto out;
+@@ -1191,6 +1188,7 @@ goa_oauth2_provider_refresh_account (GoaProvider *provider,
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ priv = goa_oauth2_provider_get_instance_private (self);
++ priv->loop = g_main_loop_new (NULL, FALSE);
+
+ dialog = gtk_dialog_new_with_buttons (NULL,
+ parent,
+@@ -1601,6 +1599,8 @@ goa_oauth2_provider_finalize (GObject *object)
+ g_free (priv->authorization_code);
+ g_free (priv->access_token);
+ g_free (priv->refresh_token);
++ g_clear_pointer (&priv->request_uri, g_free);
++ g_clear_object (&priv->session);
+
+ G_OBJECT_CLASS (goa_oauth2_provider_parent_class)->finalize (object);
+ }
+@@ -1626,12 +1626,9 @@ goa_oauth2_provider_class_init (GoaOAuth2ProviderClass *klass)
+ provider_class->ensure_credentials_sync = goa_oauth2_provider_ensure_credentials_sync;
+
+ klass->build_authorization_uri = goa_oauth2_provider_build_authorization_uri_default;
+- klass->decide_navigation_policy = goa_oauth2_provider_decide_navigation_policy_default;
+ klass->get_token_uri = goa_oauth2_provider_get_token_uri_default;
+ klass->get_scope = goa_oauth2_provider_get_scope_default;
+ klass->get_use_mobile_browser = goa_oauth2_provider_get_use_mobile_browser_default;
+- klass->is_deny_node = goa_oauth2_provider_is_deny_node_default;
+- klass->is_password_node = goa_oauth2_provider_is_password_node_default;
+ klass->add_account_key_values = goa_oauth2_provider_add_account_key_values_default;
+ }
+
+diff --git a/src/goabackend/goaoauthprovider.c b/src/goabackend/goaoauthprovider.c
+deleted file mode 100644
+index 0bfab6b..0000000
+--- a/src/goabackend/goaoauthprovider.c
++++ /dev/null
+@@ -1,1638 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+-/*
+- * Copyright © 2011 – 2017 Red Hat, Inc.
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-#include "config.h"
+-#include
+-#include
+-
+-#include
+-#include
+-#include
+-#include
+-
+-#include "goaprovider.h"
+-#include "goautils.h"
+-#include "goawebview.h"
+-#include "goaoauthprovider.h"
+-#include "goasouplogger.h"
+-
+-/**
+- * SECTION:goaoauthprovider
+- * @title: GoaOAuthProvider
+- * @short_description: Abstract base class for OAuth 1.0a providers
+- *
+- * #GoaOAuthProvider is an abstract base class for OAuth 1.0a
+- * compliant implementations as defined by RFC
+- * 5849. Additionally, the code works with providers
+- * implementing OAuth
+- * Session 1.0 Draft 1 for refreshing access tokens.
+- *
+- * Subclasses must implement
+- * #GoaOAuthProviderClass.get_consumer_key,
+- * #GoaOAuthProviderClass.get_consumer_secret,
+- * #GoaOAuthProviderClass.get_request_uri,
+- * #GoaOAuthProviderClass.get_authorization_uri,
+- * #GoaOAuthProviderClass.get_token_uri,
+- * #GoaOAuthProviderClass.get_callback_uri and
+- * #GoaOAuthProviderClass.get_identity_sync methods.
+- *
+- * Additionally, the
+- * #GoaProviderClass.get_provider_type,
+- * #GoaProviderClass.get_provider_name,
+- * #GoaProviderClass.build_object (this should chain up to its
+- * parent class) methods must be implemented.
+- *
+- * Note that the #GoaProviderClass.add_account,
+- * #GoaProviderClass.refresh_account and
+- * #GoaProviderClass.ensure_credentials_sync methods do not
+- * need to be implemented - this type implements these methods.
+- */
+-
+-G_LOCK_DEFINE_STATIC (provider_lock);
+-
+-G_DEFINE_ABSTRACT_TYPE (GoaOAuthProvider, goa_oauth_provider, GOA_TYPE_PROVIDER);
+-
+-static gboolean
+-is_authorization_error (GError *error)
+-{
+- gboolean ret;
+-
+- g_return_val_if_fail (error != NULL, FALSE);
+-
+- ret = FALSE;
+- if (error->domain == REST_PROXY_ERROR || error->domain == SOUP_HTTP_ERROR)
+- {
+- if (SOUP_STATUS_IS_CLIENT_ERROR (error->code))
+- ret = TRUE;
+- }
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gboolean
+-goa_oauth_provider_get_use_mobile_browser_default (GoaOAuthProvider *provider)
+-{
+- return FALSE;
+-}
+-
+-/**
+- * goa_oauth_provider_get_use_mobile_browser:
+- * @provider: A #GoaOAuthProvider.
+- *
+- * Returns whether there is a need for the embedded browser to identify
+- * itself as running on a mobile phone in order to get a more compact
+- * version of the approval page.
+- *
+- * This is a virtual method where the default implementation returns
+- * %FALSE.
+- *
+- * Returns: %TRUE if the embedded browser should identify itself as
+- * running on a mobile platform, %FALSE otherwise.
+- */
+-gboolean
+-goa_oauth_provider_get_use_mobile_browser (GoaOAuthProvider *provider)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), FALSE);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_use_mobile_browser (provider);
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gboolean
+-goa_oauth_provider_is_deny_node_default (GoaOAuthProvider *provider, WebKitDOMNode *node)
+-{
+- return FALSE;
+-}
+-
+-/**
+- * goa_oauth_provider_is_deny_node:
+- * @provider: A #GoaOAuthProvider.
+- * @node: A WebKitDOMNode.
+- *
+- * Checks whether @node is the HTML UI element that the user can use
+- * to deny permission to access his account. Usually they are either a
+- * WebKitDOMHTMLButtonElement or a WebKitDOMHTMLInputElement.
+- *
+- * Please note that providers may have multiple such elements in their
+- * UI and this method should catch all of them.
+- *
+- * This is a virtual method where the default implementation returns
+- * %FALSE.
+- *
+- * Returns: %TRUE if the @node can be used to deny permission.
+- */
+-gboolean
+-goa_oauth_provider_is_deny_node (GoaOAuthProvider *provider, WebKitDOMNode *node)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), FALSE);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->is_deny_node (provider, node);
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gboolean
+-goa_oauth_provider_is_password_node_default (GoaOAuthProvider *provider, WebKitDOMHTMLInputElement *element)
+-{
+- return FALSE;
+-}
+-
+-/**
+- * goa_oauth_provider_is_password_node:
+- * @provider: A #GoaOAuthProvider.
+- * @element: A WebKitDOMHTMLInputElement
+- *
+- * Checks whether @element is the HTML UI element that the user can
+- * use to enter her password. This can be used to offer a
+- * #GoaPasswordBased interface by saving the user's
+- * password. Providers usually frown upon doing this, so this is not
+- * recommended.
+- *
+- * This is a virtual method where the default implementation returns
+- * %FALSE.
+- *
+- * Returns: %TRUE if @element can be used to enter the password.
+- */
+-gboolean
+-goa_oauth_provider_is_password_node (GoaOAuthProvider *provider, WebKitDOMHTMLInputElement *element)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), FALSE);
+- g_return_val_if_fail (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT (element), FALSE);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->is_password_node (provider, element);
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static void
+-goa_oauth_provider_add_account_key_values_default (GoaOAuthProvider *provider,
+- GVariantBuilder *builder)
+-{
+- /* do nothing */
+-}
+-
+-/**
+- * goa_oauth_provider_add_account_key_values:
+- * @provider: A #GoaProvider.
+- * @builder: A #GVariantBuilder for a a{ss} variant.
+- *
+- * Hook for implementations to add key/value pairs to the key-file
+- * when creating an account.
+- *
+- * This is a virtual method where the default implementation does nothing.
+- */
+-void
+-goa_oauth_provider_add_account_key_values (GoaOAuthProvider *provider,
+- GVariantBuilder *builder)
+-{
+- g_return_if_fail (GOA_IS_OAUTH_PROVIDER (provider));
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->add_account_key_values (provider, builder);
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gchar *
+-goa_oauth_provider_build_authorization_uri_default (GoaOAuthProvider *provider,
+- const gchar *authorization_uri,
+- const gchar *escaped_oauth_token)
+-{
+- return g_strdup_printf ("%s"
+- "?oauth_token=%s",
+- authorization_uri,
+- escaped_oauth_token);
+-}
+-
+-/**
+- * goa_oauth_provider_build_authorization_uri:
+- * @provider: A #GoaOAuthProvider.
+- * @authorization_uri: An authorization URI.
+- * @escaped_oauth_token: An escaped oauth token.
+- *
+- * Builds the URI that can be opened in a web browser (or embedded web
+- * browser widget) to start authenticating an user.
+- *
+- * The default implementation just returns the expected URI
+- * (e.g. http://example.com/dialog/oauth?auth_token=1234567890)
+- * - override (and chain up) if you e.g. need to to pass additional
+- * parameters.
+- *
+- * The @authorization_uri parameter originate from the result of the
+- * the goa_oauth_provider_get_authorization_uri() method. The
+- * @escaped_oauth_token parameter is the temporary credentials identifier
+- * escaped using g_uri_escape_string().
+- *
+- * Returns: (transfer full): An authorization URI that must be freed with g_free().
+- */
+-gchar *
+-goa_oauth_provider_build_authorization_uri (GoaOAuthProvider *provider,
+- const gchar *authorization_uri,
+- const gchar *escaped_oauth_token)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- g_return_val_if_fail (authorization_uri != NULL, NULL);
+- g_return_val_if_fail (escaped_oauth_token != NULL, NULL);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->build_authorization_uri (provider,
+- authorization_uri,
+- escaped_oauth_token);
+-}
+-
+-/**
+- * goa_oauth_provider_get_consumer_key:
+- * @provider: A #GoaOAuthProvider.
+- *
+- * Gets the consumer key identifying the client.
+- *
+- * This is a pure virtual method - a subclass must provide an
+- * implementation.
+- *
+- * Returns: (transfer none): A string owned by @provider - do not free.
+- */
+-const gchar *
+-goa_oauth_provider_get_consumer_key (GoaOAuthProvider *provider)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_consumer_key (provider);
+-}
+-
+-/**
+- * goa_oauth_provider_get_consumer_secret:
+- * @provider: A #GoaOAuthProvider.
+- *
+- * Gets the consumer secret identifying the client.
+- *
+- * This is a pure virtual method - a subclass must provide an
+- * implementation.
+- *
+- * Returns: (transfer none): A string owned by @provider - do not free.
+- */
+-const gchar *
+-goa_oauth_provider_get_consumer_secret (GoaOAuthProvider *provider)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_consumer_secret (provider);
+-}
+-
+-/**
+- * goa_oauth_provider_get_request_uri:
+- * @provider: A #GoaOAuthProvider.
+- *
+- * Gets the request uri.
+- *
+- * http://tools.ietf.org/html/rfc5849#section-2.1
+- *
+- * This is a pure virtual method - a subclass must provide an
+- * implementation.
+- *
+- * Returns: (transfer none): A string owned by @provider - do not free.
+- */
+-const gchar *
+-goa_oauth_provider_get_request_uri (GoaOAuthProvider *provider)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_request_uri (provider);
+-}
+-
+-/**
+- * goa_oauth_provider_get_request_uri_params:
+- * @provider: A #GoaOAuthProvider.
+- *
+- * Gets additional parameters for the request URI.
+- *
+- * http://tools.ietf.org/html/rfc5849#section-2.1
+- *
+- * This is a virtual method where the default implementation returns
+- * %NULL.
+- *
+- * Returns: (transfer full): %NULL (for no parameters) or a
+- * %NULL-terminated array of (key, value) pairs that will be added to
+- * the URI. The caller will free the returned value with g_strfreev().
+- */
+-gchar **
+-goa_oauth_provider_get_request_uri_params (GoaOAuthProvider *provider)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_request_uri_params (provider);
+-}
+-
+-static gchar **
+-goa_oauth_provider_get_request_uri_params_default (GoaOAuthProvider *provider)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- return NULL;
+-}
+-
+-/**
+- * goa_oauth_provider_get_authorization_uri:
+- * @provider: A #GoaOAuthProvider.
+- *
+- * Gets the authorization uri.
+- *
+- * http://tools.ietf.org/html/rfc5849#section-2.2
+- *
+- * This is a pure virtual method - a subclass must provide an
+- * implementation.
+- *
+- * Returns: (transfer none): A string owned by @provider - do not free.
+- */
+-const gchar *
+-goa_oauth_provider_get_authorization_uri (GoaOAuthProvider *provider)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_authorization_uri (provider);
+-}
+-
+-/**
+- * goa_oauth_provider_get_token_uri:
+- * @provider: A #GoaOAuthProvider.
+- *
+- * Gets the token uri.
+- *
+- * http://tools.ietf.org/html/rfc5849#section-2.3
+- *
+- * This is a pure virtual method - a subclass must provide an
+- * implementation.
+- *
+- * Returns: (transfer none): A string owned by @provider - do not free.
+- */
+-const gchar *
+-goa_oauth_provider_get_token_uri (GoaOAuthProvider *provider)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_token_uri (provider);
+-}
+-
+-/**
+- * goa_oauth_provider_get_callback_uri:
+- * @provider: A #GoaOAuthProvider.
+- *
+- * Gets the callback uri.
+- *
+- * http://tools.ietf.org/html/rfc5849#section-2.1
+- *
+- * This is a pure virtual method - a subclass must provide an
+- * implementation.
+- *
+- * Returns: (transfer none): A string owned by @provider - do not free.
+- */
+-const gchar *
+-goa_oauth_provider_get_callback_uri (GoaOAuthProvider *provider)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_callback_uri (provider);
+-}
+-
+-/**
+- * goa_oauth_provider_get_identity_sync:
+- * @provider: A #GoaOAuthProvider.
+- * @access_token: A valid OAuth 1.0 access token.
+- * @access_token_secret: The valid secret for @access_token.
+- * @out_presentation_identity: (out): Return location for presentation identity or %NULL.
+- * @cancellable: (allow-none): A #GCancellable or %NULL.
+- * @error: Return location for error or %NULL.
+- *
+- * Method that returns the identity corresponding to @access_token and
+- * @access_token_secret.
+- *
+- * The identity is needed because all authentication happens out of
+- * band. In addition to the identity, an implementation also returns a
+- * presentation identity that is more suitable
+- * for presentation (the identity could be a GUID for example).
+- *
+- * The calling thread is blocked while the identity is obtained.
+- *
+- * This is a pure virtual method - a subclass must provide an
+- * implementation.
+- *
+- * Returns: The identity or %NULL if error is set. The returned string
+- * must be freed with g_free().
+- */
+-gchar *
+-goa_oauth_provider_get_identity_sync (GoaOAuthProvider *provider,
+- const gchar *access_token,
+- const gchar *access_token_secret,
+- gchar **out_presentation_identity,
+- GCancellable *cancellable,
+- GError **error)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- g_return_val_if_fail (access_token != NULL, NULL);
+- g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+- g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+-
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->get_identity_sync (provider,
+- access_token,
+- access_token_secret,
+- out_presentation_identity,
+- cancellable,
+- error);
+-}
+-
+-/**
+- * goa_oauth_provider_is_identity_node:
+- * @provider: A #GoaOAuthProvider.
+- * @element: A WebKitDOMHTMLInputElement.
+- *
+- * Checks whether @element is the HTML UI element that the user can
+- * use to identify herself at the provider.
+- *
+- * This is a pure virtual method - a subclass must provide an
+- * implementation.
+- *
+- * Returns: %TRUE if the @element can be used to deny permission.
+- */
+-gboolean
+-goa_oauth_provider_is_identity_node (GoaOAuthProvider *provider, WebKitDOMHTMLInputElement *element)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), FALSE);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->is_identity_node (provider, element);
+-}
+-
+-/**
+- * goa_oauth_provider_parse_request_token_error:
+- * @provider: A #GoaOAuthProvider.
+- * @call: The #RestProxyCall that was used to fetch the request token.
+- *
+- * Tries to parse the headers and payload within @call to provide a
+- * human readable error message in case the request token could not
+- * be fetched.
+- *
+- * This is a pure virtual method - a subclass must provide an
+- * implementation.
+- *
+- * Returns: A human readable error message or %NULL if the cause of the
+- * error could not be determined. The returned string must be freed with
+- * g_free().
+- */
+-gchar *
+-goa_oauth_provider_parse_request_token_error (GoaOAuthProvider *provider, RestProxyCall *call)
+-{
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- return GOA_OAUTH_PROVIDER_GET_CLASS (provider)->parse_request_token_error (provider, call);
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gchar *
+-get_tokens_sync (GoaOAuthProvider *provider,
+- const gchar *token,
+- const gchar *token_secret,
+- const gchar *session_handle, /* may be NULL */
+- const gchar *verifier, /* may be NULL */
+- gchar **out_access_token_secret,
+- gint *out_access_token_expires_in,
+- gchar **out_session_handle,
+- gint *out_session_handle_expires_in,
+- GCancellable *cancellable,
+- GError **error)
+-{
+- RestProxy *proxy;
+- RestProxyCall *call;
+- SoupLogger *logger = NULL;
+- gchar *ret = NULL;
+- guint status_code;
+- GHashTable *f;
+- const gchar *expires_in_str;
+- gchar *ret_access_token = NULL;
+- gchar *ret_access_token_secret = NULL;
+- gint ret_access_token_expires_in = 0;
+- gchar *ret_session_handle = NULL;
+- gint ret_session_handle_expires_in = 0;
+-
+- proxy = oauth_proxy_new (goa_oauth_provider_get_consumer_key (provider),
+- goa_oauth_provider_get_consumer_secret (provider),
+- goa_oauth_provider_get_token_uri (provider),
+- FALSE);
+- logger = goa_soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
+- rest_proxy_add_soup_feature (proxy, SOUP_SESSION_FEATURE (logger));
+- oauth_proxy_set_token (OAUTH_PROXY (proxy), token);
+- oauth_proxy_set_token_secret (OAUTH_PROXY (proxy), token_secret);
+- call = rest_proxy_new_call (proxy);
+- rest_proxy_call_set_method (call, "POST");
+- if (verifier != NULL)
+- rest_proxy_call_add_param (call, "oauth_verifier", verifier);
+- if (session_handle != NULL)
+- rest_proxy_call_add_param (call, "oauth_session_handle", session_handle);
+- /* TODO: cancellable support? */
+- if (!rest_proxy_call_sync (call, error))
+- goto out;
+-
+- status_code = rest_proxy_call_get_status_code (call);
+- if (status_code != 200)
+- {
+- g_set_error (error,
+- GOA_ERROR,
+- GOA_ERROR_FAILED,
+- /* Translators: the %d is a HTTP status code and the %s is a textual description of it */
+- _("Expected status 200 when requesting access token, instead got status %d (%s)"),
+- status_code,
+- rest_proxy_call_get_status_message (call));
+- goto out;
+- }
+-
+- f = soup_form_decode (rest_proxy_call_get_payload (call));
+- ret_access_token = g_strdup (g_hash_table_lookup (f, "oauth_token"));
+- ret_access_token_secret = g_strdup (g_hash_table_lookup (f, "oauth_token_secret"));
+- ret_session_handle = g_strdup (g_hash_table_lookup (f, "oauth_session_handle"));
+- expires_in_str = g_hash_table_lookup (f, "oauth_expires_in");
+- if (expires_in_str != NULL)
+- ret_access_token_expires_in = atoi (expires_in_str);
+- expires_in_str = g_hash_table_lookup (f, "oauth_authorization_expires_in");
+- if (expires_in_str != NULL)
+- ret_session_handle_expires_in = atoi (expires_in_str);
+- g_hash_table_unref (f);
+-
+- if (ret_access_token == NULL || ret_access_token_secret == NULL)
+- {
+- g_set_error (error,
+- GOA_ERROR,
+- GOA_ERROR_FAILED,
+- _("Missing access_token or access_token_secret headers in response"));
+- goto out;
+- }
+-
+- ret = ret_access_token; ret_access_token = NULL;
+- if (out_access_token_secret != NULL)
+- {
+- *out_access_token_secret = ret_access_token_secret;
+- ret_access_token_secret = NULL;
+- }
+- if (out_access_token_expires_in != NULL)
+- *out_access_token_expires_in = ret_access_token_expires_in;
+- if (out_session_handle != NULL)
+- {
+- *out_session_handle = ret_session_handle;
+- ret_session_handle = NULL;
+- }
+- if (out_session_handle_expires_in != NULL)
+- *out_session_handle_expires_in = ret_session_handle_expires_in;
+-
+- out:
+- g_free (ret_access_token);
+- g_free (ret_access_token_secret);
+- g_free (ret_session_handle);
+- g_clear_object (&call);
+- g_clear_object (&proxy);
+- g_clear_object (&logger);
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-typedef struct
+-{
+- GoaOAuthProvider *provider;
+- GtkDialog *dialog;
+- GError *error;
+- GMainLoop *loop;
+-
+- gchar *password;
+-
+- gchar *oauth_verifier;
+-
+- const gchar *existing_identity;
+-
+- gchar *identity;
+- gchar *presentation_identity;
+-
+- gchar *request_token;
+- gchar *request_token_secret;
+- gchar *access_token;
+- gchar *access_token_secret;
+- gint access_token_expires_in;
+- gchar *session_handle;
+- gint session_handle_expires_in;
+-} IdentifyData;
+-
+-static void
+-on_web_view_deny_click (GoaWebView *web_view, gpointer user_data)
+-{
+- IdentifyData *data = user_data;
+- gtk_dialog_response (data->dialog, GTK_RESPONSE_CANCEL);
+-}
+-
+-static void
+-on_web_view_password_submit (GoaWebView *web_view, const gchar *password, gpointer user_data)
+-{
+- IdentifyData *data = user_data;
+-
+- g_free (data->password);
+- data->password = g_strdup (password);
+-}
+-
+-static gboolean
+-on_web_view_decide_policy (WebKitWebView *web_view,
+- WebKitPolicyDecision *decision,
+- WebKitPolicyDecisionType decision_type,
+- gpointer user_data)
+-{
+- GHashTable *key_value_pairs;
+- IdentifyData *data = user_data;
+- SoupURI *uri;
+- WebKitNavigationAction *action;
+- WebKitURIRequest *request;
+- const gchar *query;
+- const gchar *redirect_uri;
+- const gchar *requested_uri;
+- gint response_id = GTK_RESPONSE_NONE;
+-
+- if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
+- return FALSE;
+-
+- /* TODO: use oauth_proxy_extract_access_token() */
+-
+- action = webkit_navigation_policy_decision_get_navigation_action (WEBKIT_NAVIGATION_POLICY_DECISION (decision));
+- request = webkit_navigation_action_get_request (action);
+- requested_uri = webkit_uri_request_get_uri (request);
+- redirect_uri = goa_oauth_provider_get_callback_uri (data->provider);
+-
+- if (!g_str_has_prefix (requested_uri, redirect_uri))
+- goto default_behaviour;
+-
+- uri = soup_uri_new (requested_uri);
+- query = soup_uri_get_query (uri);
+-
+- if (query != NULL)
+- {
+- key_value_pairs = soup_form_decode (query);
+-
+- data->oauth_verifier = g_strdup (g_hash_table_lookup (key_value_pairs, "oauth_verifier"));
+- if (data->oauth_verifier != NULL)
+- response_id = GTK_RESPONSE_OK;
+-
+- g_hash_table_unref (key_value_pairs);
+- }
+-
+- if (data->oauth_verifier != NULL)
+- goto ignore_request;
+-
+- /* TODO: The only OAuth1 provider is Flickr. It doesn't send any
+- * error code and only redirects to the URI specified in the Flickr
+- * App Garden. Re-evaluate when the situation changes.
+- */
+- response_id = GTK_RESPONSE_CANCEL;
+- goto ignore_request;
+-
+- ignore_request:
+- g_assert (response_id != GTK_RESPONSE_NONE);
+- gtk_dialog_response (data->dialog, response_id);
+- webkit_policy_decision_ignore (decision);
+- return TRUE;
+-
+- default_behaviour:
+- return FALSE;
+-}
+-
+-static void
+-rest_proxy_call_cb (RestProxyCall *call, const GError *error, GObject *weak_object, gpointer user_data)
+-{
+- IdentifyData *data = user_data;
+- g_main_loop_quit (data->loop);
+-}
+-
+-static gboolean
+-get_tokens_and_identity (GoaOAuthProvider *provider,
+- gboolean add_account,
+- const gchar *existing_identity,
+- GtkDialog *dialog,
+- GtkBox *vbox,
+- gchar **out_access_token,
+- gchar **out_access_token_secret,
+- gint *out_access_token_expires_in,
+- gchar **out_session_handle,
+- gint *out_session_handle_expires_in,
+- gchar **out_identity,
+- gchar **out_presentation_identity,
+- gchar **out_password,
+- GError **error)
+-{
+- gboolean ret = FALSE;
+- gchar *url = NULL;
+- IdentifyData data;
+- gchar *escaped_request_token = NULL;
+- RestProxy *proxy = NULL;
+- RestProxyCall *call = NULL;
+- SoupLogger *logger = NULL;
+- GHashTable *f;
+- GtkWidget *embed;
+- GtkWidget *grid;
+- GtkWidget *spinner;
+- GtkWidget *web_view;
+- gchar **request_params = NULL;
+- guint n;
+-
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), FALSE);
+- g_return_val_if_fail ((!add_account && existing_identity != NULL && existing_identity[0] != '\0')
+- || (add_account && existing_identity == NULL), FALSE);
+- g_return_val_if_fail (GTK_IS_DIALOG (dialog), FALSE);
+- g_return_val_if_fail (GTK_IS_BOX (vbox), FALSE);
+- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+-
+- /* TODO: check with NM whether we're online, if not - return error */
+-
+- memset (&data, '\0', sizeof (IdentifyData));
+- data.provider = provider;
+- data.dialog = dialog;
+- data.loop = g_main_loop_new (NULL, FALSE);
+- data.existing_identity = existing_identity;
+-
+- proxy = oauth_proxy_new (goa_oauth_provider_get_consumer_key (provider),
+- goa_oauth_provider_get_consumer_secret (provider),
+- goa_oauth_provider_get_request_uri (provider), FALSE);
+- logger = goa_soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
+- rest_proxy_add_soup_feature (proxy, SOUP_SESSION_FEATURE (logger));
+-
+- call = rest_proxy_new_call (proxy);
+- rest_proxy_call_set_method (call, "POST");
+- rest_proxy_call_add_param (call, "oauth_callback", goa_oauth_provider_get_callback_uri (provider));
+-
+- request_params = goa_oauth_provider_get_request_uri_params (provider);
+- if (request_params != NULL)
+- {
+- g_assert (g_strv_length (request_params) % 2 == 0);
+- for (n = 0; request_params[n] != NULL; n += 2)
+- rest_proxy_call_add_param (call, request_params[n], request_params[n+1]);
+- }
+- if (!rest_proxy_call_async (call, rest_proxy_call_cb, NULL, &data, &data.error))
+- {
+- g_prefix_error (&data.error, _("Error getting a Request Token: "));
+- goto out;
+- }
+-
+- goa_utils_set_dialog_title (GOA_PROVIDER (provider), dialog, add_account);
+-
+- grid = gtk_grid_new ();
+- gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
+- gtk_grid_set_row_spacing (GTK_GRID (grid), 12);
+- gtk_container_add (GTK_CONTAINER (vbox), grid);
+-
+- spinner = gtk_spinner_new ();
+- gtk_widget_set_hexpand (spinner, TRUE);
+- gtk_widget_set_halign (spinner, GTK_ALIGN_CENTER);
+- gtk_widget_set_vexpand (spinner, TRUE);
+- gtk_widget_set_valign (spinner, GTK_ALIGN_CENTER);
+- gtk_widget_set_size_request (GTK_WIDGET (spinner), 24, 24);
+- gtk_spinner_start (GTK_SPINNER (spinner));
+- gtk_container_add (GTK_CONTAINER (grid), spinner);
+- gtk_widget_show_all (GTK_WIDGET (vbox));
+-
+- g_main_loop_run (data.loop);
+- gtk_container_remove (GTK_CONTAINER (grid), spinner);
+-
+- if (rest_proxy_call_get_status_code (call) != 200)
+- {
+- gchar *msg;
+-
+- msg = goa_oauth_provider_parse_request_token_error (provider, call);
+- if (msg == NULL)
+- /* Translators: the %d is a HTTP status code and the %s is a textual description of it */
+- msg = g_strdup_printf (_("Expected status 200 for getting a Request Token, instead got status %d (%s)"),
+- rest_proxy_call_get_status_code (call),
+- rest_proxy_call_get_status_message (call));
+-
+- g_set_error_literal (&data.error, GOA_ERROR, GOA_ERROR_FAILED, msg);
+- g_free (msg);
+- goto out;
+- }
+- f = soup_form_decode (rest_proxy_call_get_payload (call));
+- data.request_token = g_strdup (g_hash_table_lookup (f, "oauth_token"));
+- data.request_token_secret = g_strdup (g_hash_table_lookup (f, "oauth_token_secret"));
+- g_hash_table_unref (f);
+- if (data.request_token == NULL || data.request_token_secret == NULL)
+- {
+- g_set_error (&data.error,
+- GOA_ERROR,
+- GOA_ERROR_FAILED,
+- _("Missing request_token or request_token_secret headers in response"));
+- goto out;
+- }
+-
+- escaped_request_token = g_uri_escape_string (data.request_token, NULL, TRUE);
+- url = goa_oauth_provider_build_authorization_uri (provider,
+- goa_oauth_provider_get_authorization_uri (provider),
+- escaped_request_token);
+-
+- web_view = goa_web_view_new (GOA_PROVIDER (provider), existing_identity);
+- gtk_widget_set_hexpand (web_view, TRUE);
+- gtk_widget_set_vexpand (web_view, TRUE);
+- embed = goa_web_view_get_view (GOA_WEB_VIEW (web_view));
+-
+- if (goa_oauth_provider_get_use_mobile_browser (provider))
+- goa_web_view_fake_mobile (GOA_WEB_VIEW (web_view));
+-
+- webkit_web_view_load_uri (WEBKIT_WEB_VIEW (embed), url);
+- g_signal_connect (embed,
+- "decide-policy",
+- G_CALLBACK (on_web_view_decide_policy),
+- &data);
+- g_signal_connect (web_view, "deny-click", G_CALLBACK (on_web_view_deny_click), &data);
+- g_signal_connect (web_view, "password-submit", G_CALLBACK (on_web_view_password_submit), &data);
+-
+- gtk_container_add (GTK_CONTAINER (grid), web_view);
+- gtk_window_set_default_size (GTK_WINDOW (dialog), -1, -1);
+-
+- gtk_widget_show_all (GTK_WIDGET (vbox));
+- gtk_dialog_run (GTK_DIALOG (dialog));
+-
+- if (data.oauth_verifier == NULL)
+- {
+- if (data.error == NULL)
+- {
+- g_set_error (&data.error,
+- GOA_ERROR,
+- GOA_ERROR_DIALOG_DISMISSED,
+- _("Dialog was dismissed"));
+- }
+- goto out;
+- }
+- g_assert (data.error == NULL);
+-
+- /* OK, we are done interacting with the user ... but before we can
+- * make up our mind, there are two more RPC calls to make and these
+- * call may take some time. So hide the dialog immediately.
+- */
+- gtk_widget_hide (GTK_WIDGET (dialog));
+-
+- /* OK, we now have the request token... we can exchange that for a
+- * (short-lived) access token and session_handle (used to refresh the
+- * access token)..
+- */
+-
+- /* TODO: run in worker thread */
+- data.access_token = get_tokens_sync (provider,
+- data.request_token,
+- data.request_token_secret,
+- NULL, /* session_handle */
+- data.oauth_verifier,
+- &data.access_token_secret,
+- &data.access_token_expires_in,
+- &data.session_handle,
+- &data.session_handle_expires_in,
+- NULL, /* GCancellable */
+- &data.error);
+- if (data.access_token == NULL)
+- {
+- g_prefix_error (&data.error, _("Error getting an Access Token: "));
+- goto out;
+- }
+-
+- /* TODO: run in worker thread */
+- data.identity = goa_oauth_provider_get_identity_sync (provider,
+- data.access_token,
+- data.access_token_secret,
+- &data.presentation_identity,
+- NULL, /* TODO: GCancellable */
+- &data.error);
+- if (data.identity == NULL)
+- {
+- g_prefix_error (&data.error, _("Error getting identity: "));
+- goto out;
+- }
+-
+- ret = TRUE;
+-
+- out:
+- g_clear_object (&call);
+-
+- if (ret)
+- {
+- g_warn_if_fail (data.error == NULL);
+- if (out_access_token != NULL)
+- *out_access_token = g_strdup (data.access_token);
+- if (out_access_token_secret != NULL)
+- *out_access_token_secret = g_strdup (data.access_token_secret);
+- if (out_access_token_expires_in != NULL)
+- *out_access_token_expires_in = data.access_token_expires_in;
+- if (out_session_handle != NULL)
+- *out_session_handle = g_strdup (data.session_handle);
+- if (out_session_handle_expires_in != NULL)
+- *out_session_handle_expires_in = data.session_handle_expires_in;
+- if (out_identity != NULL)
+- *out_identity = g_strdup (data.identity);
+- if (out_presentation_identity != NULL)
+- *out_presentation_identity = g_strdup (data.presentation_identity);
+- if (out_password != NULL)
+- *out_password = g_strdup (data.password);
+- }
+- else
+- {
+- g_warn_if_fail (data.error != NULL);
+- g_propagate_error (error, data.error);
+- }
+-
+- g_free (data.password);
+- g_free (data.presentation_identity);
+- g_free (data.identity);
+- g_free (url);
+-
+- g_free (data.oauth_verifier);
+- g_clear_pointer (&data.loop, g_main_loop_unref);
+- g_free (data.access_token);
+- g_free (data.access_token_secret);
+- g_free (escaped_request_token);
+-
+- g_free (data.request_token);
+- g_free (data.request_token_secret);
+-
+- g_strfreev (request_params);
+- g_clear_object (&proxy);
+- g_clear_object (&logger);
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-typedef struct
+-{
+- GError *error;
+- GMainLoop *loop;
+- gchar *account_object_path;
+-} AddData;
+-
+-static void
+-add_account_cb (GoaManager *manager,
+- GAsyncResult *res,
+- gpointer user_data)
+-{
+- AddData *data = user_data;
+- goa_manager_call_add_account_finish (manager,
+- &data->account_object_path,
+- res,
+- &data->error);
+- g_main_loop_quit (data->loop);
+-}
+-
+-static GoaObject *
+-goa_oauth_provider_add_account (GoaProvider *_provider,
+- GoaClient *client,
+- GtkDialog *dialog,
+- GtkBox *vbox,
+- GError **error)
+-{
+- GoaOAuthProvider *provider = GOA_OAUTH_PROVIDER (_provider);
+- GoaObject *ret = NULL;
+- gchar *access_token = NULL;
+- gchar *access_token_secret = NULL;
+- gint access_token_expires_in;
+- gchar *session_handle = NULL;
+- gint session_handle_expires_in;
+- gchar *identity = NULL;
+- gchar *presentation_identity = NULL;
+- gchar *password = NULL;
+- AddData data;
+- GVariantBuilder credentials;
+- GVariantBuilder details;
+-
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- g_return_val_if_fail (GOA_IS_CLIENT (client), NULL);
+- g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);
+- g_return_val_if_fail (GTK_IS_BOX (vbox), NULL);
+- g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+-
+- memset (&data, '\0', sizeof (AddData));
+- data.loop = g_main_loop_new (NULL, FALSE);
+-
+- if (!get_tokens_and_identity (provider,
+- TRUE,
+- NULL,
+- dialog,
+- vbox,
+- &access_token,
+- &access_token_secret,
+- &access_token_expires_in,
+- &session_handle,
+- &session_handle_expires_in,
+- &identity,
+- &presentation_identity,
+- &password,
+- &data.error))
+- goto out;
+-
+- /* OK, got the identity... see if there's already an account
+- * of this type with the given identity
+- */
+- if (!goa_utils_check_duplicate (client,
+- identity,
+- presentation_identity,
+- goa_provider_get_provider_type (GOA_PROVIDER (provider)),
+- (GoaPeekInterfaceFunc) goa_object_peek_oauth_based,
+- &data.error))
+- goto out;
+-
+- g_variant_builder_init (&credentials, G_VARIANT_TYPE_VARDICT);
+- g_variant_builder_add (&credentials, "{sv}", "access_token", g_variant_new_string (access_token));
+- g_variant_builder_add (&credentials, "{sv}", "access_token_secret", g_variant_new_string (access_token_secret));
+- if (access_token_expires_in > 0)
+- g_variant_builder_add (&credentials, "{sv}", "access_token_expires_at",
+- g_variant_new_int64 (goa_utils_convert_duration_sec_to_abs_usec (access_token_expires_in)));
+- if (session_handle != NULL)
+- g_variant_builder_add (&credentials, "{sv}", "session_handle", g_variant_new_string (session_handle));
+- if (session_handle_expires_in > 0)
+- g_variant_builder_add (&credentials, "{sv}", "session_handle_expires_at",
+- g_variant_new_int64 (goa_utils_convert_duration_sec_to_abs_usec (session_handle_expires_in)));
+- if (password != NULL)
+- g_variant_builder_add (&credentials, "{sv}", "password", g_variant_new_string (password));
+-
+- g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
+- goa_oauth_provider_add_account_key_values (provider, &details);
+-
+- /* we want the GoaClient to update before this method returns (so it
+- * can create a proxy for the new object) so run the mainloop while
+- * waiting for this to complete
+- */
+- goa_manager_call_add_account (goa_client_get_manager (client),
+- goa_provider_get_provider_type (GOA_PROVIDER (provider)),
+- identity,
+- presentation_identity,
+- g_variant_builder_end (&credentials),
+- g_variant_builder_end (&details),
+- NULL, /* GCancellable* */
+- (GAsyncReadyCallback) add_account_cb,
+- &data);
+- g_main_loop_run (data.loop);
+- if (data.error != NULL)
+- goto out;
+-
+- ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client),
+- data.account_object_path));
+-
+- out:
+- /* We might have an object even when data.error is set.
+- * eg., if we failed to store the credentials in the keyring.
+- */
+- if (data.error != NULL)
+- g_propagate_error (error, data.error);
+- else
+- g_assert (ret != NULL);
+-
+- g_free (identity);
+- g_free (presentation_identity);
+- g_free (password);
+- g_free (access_token);
+- g_free (access_token_secret);
+- g_free (session_handle);
+- g_free (data.account_object_path);
+- g_clear_pointer (&data.loop, g_main_loop_unref);
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gboolean
+-goa_oauth_provider_refresh_account (GoaProvider *_provider,
+- GoaClient *client,
+- GoaObject *object,
+- GtkWindow *parent,
+- GError **error)
+-{
+- GoaOAuthProvider *provider = GOA_OAUTH_PROVIDER (_provider);
+- GoaAccount *account;
+- GtkWidget *dialog;
+- gchar *access_token = NULL;
+- gchar *access_token_secret = NULL;
+- gchar *password = NULL;
+- gint access_token_expires_in;
+- gchar *session_handle = NULL;
+- gint session_handle_expires_in;
+- gchar *identity = NULL;
+- const gchar *existing_identity;
+- const gchar *existing_presentation_identity;
+- GVariantBuilder builder;
+- gboolean ret = FALSE;
+-
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), FALSE);
+- g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE);
+- g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE);
+- g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE);
+- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+-
+- dialog = gtk_dialog_new_with_buttons (NULL,
+- parent,
+- GTK_DIALOG_MODAL
+- | GTK_DIALOG_DESTROY_WITH_PARENT
+- | GTK_DIALOG_USE_HEADER_BAR,
+- NULL,
+- NULL);
+- gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
+- gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+- gtk_widget_show_all (dialog);
+-
+- account = goa_object_peek_account (object);
+-
+- /* We abuse presentation identity here because for some providers
+- * identity can be a machine readable ID, which can not be used to
+- * log in via the provider's web interface.
+- */
+- existing_presentation_identity = goa_account_get_presentation_identity (account);
+- if (!get_tokens_and_identity (provider,
+- FALSE,
+- existing_presentation_identity,
+- GTK_DIALOG (dialog),
+- GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+- &access_token,
+- &access_token_secret,
+- &access_token_expires_in,
+- &session_handle,
+- &session_handle_expires_in,
+- &identity,
+- NULL, /* out_presentation_identity */
+- &password,
+- error))
+- goto out;
+-
+- /* Changes made to the web interface by the providers can break our
+- * DOM parsing. So we should still query and check the identity
+- * afterwards.
+- */
+- existing_identity = goa_account_get_identity (account);
+- if (g_strcmp0 (identity, existing_identity) != 0)
+- {
+- g_set_error (error,
+- GOA_ERROR,
+- GOA_ERROR_FAILED,
+- _("Was asked to log in as %s, but logged in as %s"),
+- existing_identity,
+- identity);
+- goto out;
+- }
+-
+- g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+- g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token));
+- g_variant_builder_add (&builder, "{sv}", "access_token_secret", g_variant_new_string (access_token_secret));
+- if (access_token_expires_in > 0)
+- g_variant_builder_add (&builder, "{sv}", "access_token_expires_at",
+- g_variant_new_int64 (goa_utils_convert_duration_sec_to_abs_usec (access_token_expires_in)));
+- if (session_handle != NULL)
+- g_variant_builder_add (&builder, "{sv}", "session_handle", g_variant_new_string (session_handle));
+- if (session_handle_expires_in > 0)
+- g_variant_builder_add (&builder, "{sv}", "session_handle_expires_at",
+- g_variant_new_int64 (goa_utils_convert_duration_sec_to_abs_usec (session_handle_expires_in)));
+- if (password != NULL)
+- g_variant_builder_add (&builder, "{sv}", "password", g_variant_new_string (password));
+- /* TODO: run in worker thread */
+- if (!goa_utils_store_credentials_for_object_sync (GOA_PROVIDER (provider),
+- object,
+- g_variant_builder_end (&builder),
+- NULL, /* GCancellable */
+- error))
+- goto out;
+-
+- goa_account_call_ensure_credentials (goa_object_peek_account (object),
+- NULL, /* GCancellable */
+- NULL, NULL); /* callback, user_data */
+-
+- ret = TRUE;
+-
+- out:
+- gtk_widget_destroy (dialog);
+-
+- g_free (identity);
+- g_free (access_token);
+- g_free (access_token_secret);
+- g_free (password);
+- g_free (session_handle);
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static void
+-free_mutex (GMutex *mutex)
+-{
+- g_mutex_clear (mutex);
+- g_slice_free (GMutex, mutex);
+-}
+-
+-/**
+- * goa_oauth_provider_get_access_token_sync:
+- * @provider: A #GoaOAuthProvider.
+- * @object: A #GoaObject.
+- * @force_refresh: If set to %TRUE, forces a refresh of the access token, if possible.
+- * @out_access_token_secret: (out): The secret for the return access token.
+- * @out_access_token_expires_in: (out): Return location for how many seconds the returned token is valid for (0 if unknown) or %NULL.
+- * @cancellable: (allow-none): A #GCancellable or %NULL.
+- * @error: Return location for error or %NULL.
+- *
+- * Synchronously gets an access token for @object. The calling thread
+- * is blocked while the operation is pending.
+- *
+- * The resulting token is typically read from the local cache so most
+- * of the time only a local roundtrip to the storage for the token
+- * cache (e.g. gnome-keyring-daemon) is
+- * needed. However, the operation may involve refreshing the token
+- * with the service provider so a full network round-trip may be
+- * needed.
+- *
+- * Note that multiple calls are serialized to avoid multiple
+- * outstanding requests to the service provider.
+- *
+- * This operation may fail if e.g. unable to refresh the credentials
+- * or if network connectivity is not available. Note that even if a
+- * token is returned, the returned token isn't guaranteed to work -
+- * use goa_provider_ensure_credentials_sync() if you need
+- * stronger guarantees.
+- *
+- * Returns: The access token or %NULL if error is set. The returned
+- * string must be freed with g_free().
+- */
+-gchar *
+-goa_oauth_provider_get_access_token_sync (GoaOAuthProvider *provider,
+- GoaObject *object,
+- gboolean force_refresh,
+- gchar **out_access_token_secret,
+- gint *out_access_token_expires_in,
+- GCancellable *cancellable,
+- GError **error)
+-{
+- GVariant *credentials = NULL;
+- GVariantIter iter;
+- const gchar *key;
+- GVariant *value;
+- gchar *access_token = NULL;
+- gchar *access_token_secret = NULL;
+- gchar *session_handle = NULL;
+- gchar *access_token_for_refresh = NULL;
+- gchar *access_token_secret_for_refresh = NULL;
+- gchar *session_handle_for_refresh = NULL;
+- gchar *password = NULL;
+- gint access_token_expires_in = 0;
+- gint session_handle_expires_in = 0;
+- gboolean success = FALSE;
+- GVariantBuilder builder;
+- gchar *ret = NULL;
+- GMutex *lock;
+-
+- g_return_val_if_fail (GOA_IS_OAUTH_PROVIDER (provider), NULL);
+- g_return_val_if_fail (GOA_IS_OBJECT (object), NULL);
+- g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+- g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+-
+- /* provider_lock is too coarse, use a per-object lock instead */
+- G_LOCK (provider_lock);
+- lock = g_object_get_data (G_OBJECT (object), "-goa-oauth-provider-get-access-token-lock");
+- if (lock == NULL)
+- {
+- lock = g_slice_new0 (GMutex);
+- g_mutex_init (lock);
+- g_object_set_data_full (G_OBJECT (object),
+- "-goa-oauth-provider-get-access-token-lock",
+- lock,
+- (GDestroyNotify) free_mutex);
+- }
+- G_UNLOCK (provider_lock);
+-
+- g_mutex_lock (lock);
+-
+- /* First, get the credentials from the keyring */
+- credentials = goa_utils_lookup_credentials_sync (GOA_PROVIDER (provider),
+- object,
+- cancellable,
+- error);
+- if (credentials == NULL)
+- {
+- if (error != NULL)
+- {
+- (*error)->domain = GOA_ERROR;
+- (*error)->code = GOA_ERROR_NOT_AUTHORIZED;
+- }
+- goto out;
+- }
+-
+- g_variant_iter_init (&iter, credentials);
+- while (g_variant_iter_next (&iter, "{&sv}", &key, &value))
+- {
+- if (g_strcmp0 (key, "access_token") == 0)
+- access_token = g_variant_dup_string (value, NULL);
+- else if (g_strcmp0 (key, "access_token_secret") == 0)
+- access_token_secret = g_variant_dup_string (value, NULL);
+- else if (g_strcmp0 (key, "access_token_expires_at") == 0)
+- access_token_expires_in = goa_utils_convert_abs_usec_to_duration_sec (g_variant_get_int64 (value));
+- else if (g_strcmp0 (key, "session_handle") == 0)
+- session_handle = g_variant_dup_string (value, NULL);
+- else if (g_strcmp0 (key, "session_handle_expires_at") == 0)
+- session_handle_expires_in = goa_utils_convert_abs_usec_to_duration_sec (g_variant_get_int64 (value));
+- else if (g_strcmp0 (key, "password") == 0)
+- password = g_variant_dup_string (value, NULL);
+- g_variant_unref (value);
+- }
+-
+- if (access_token == NULL || access_token_secret == NULL)
+- {
+- g_set_error (error,
+- GOA_ERROR,
+- GOA_ERROR_NOT_AUTHORIZED,
+- _("Credentials do not contain access_token or access_token_secret"));
+- goto out;
+- }
+-
+- /* if we can't refresh the token, just return it no matter what */
+- if (session_handle == NULL)
+- {
+- g_debug ("Returning locally cached credentials that cannot be refreshed");
+- success = TRUE;
+- goto out;
+- }
+-
+- /* If access_token is still "fresh enough" (e.g. more than ten
+- * minutes of life left in it), just return it unless we've been
+- * asked to forcibly refresh it
+- */
+- if (!force_refresh && access_token_expires_in > 10*60)
+- {
+- g_debug ("Returning locally cached credentials (expires in %d seconds)", access_token_expires_in);
+- success = TRUE;
+- goto out;
+- }
+-
+- g_debug ("Refreshing locally cached credentials (expires in %d seconds, force_refresh=%d)", access_token_expires_in, force_refresh);
+-
+- /* Otherwise, refresh it */
+- access_token_for_refresh = access_token; access_token = NULL;
+- access_token_secret_for_refresh = access_token_secret; access_token_secret = NULL;
+- session_handle_for_refresh = session_handle; session_handle = NULL;
+- access_token = get_tokens_sync (provider,
+- access_token_for_refresh,
+- access_token_secret_for_refresh,
+- session_handle_for_refresh,
+- NULL, /* verifier */
+- &access_token_secret,
+- &access_token_expires_in,
+- &session_handle,
+- &session_handle_expires_in,
+- cancellable,
+- error);
+- if (access_token == NULL)
+- {
+- if (error != NULL)
+- {
+- g_prefix_error (error, _("Failed to refresh access token (%s, %d): "),
+- g_quark_to_string ((*error)->domain), (*error)->code);
+- (*error)->code = is_authorization_error (*error) ? GOA_ERROR_NOT_AUTHORIZED : GOA_ERROR_FAILED;
+- (*error)->domain = GOA_ERROR;
+- }
+- goto out;
+- }
+-
+- /* Good. Now update the keyring with the refreshed credentials */
+- g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+- g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token));
+- g_variant_builder_add (&builder, "{sv}", "access_token_secret", g_variant_new_string (access_token_secret));
+- if (access_token_expires_in > 0)
+- g_variant_builder_add (&builder, "{sv}", "access_token_expires_at",
+- g_variant_new_int64 (goa_utils_convert_duration_sec_to_abs_usec (access_token_expires_in)));
+- if (session_handle != NULL)
+- g_variant_builder_add (&builder, "{sv}", "session_handle", g_variant_new_string (session_handle));
+- if (session_handle_expires_in > 0)
+- g_variant_builder_add (&builder, "{sv}", "session_handle_expires_at",
+- g_variant_new_int64 (goa_utils_convert_duration_sec_to_abs_usec (session_handle_expires_in)));
+- if (password != NULL)
+- g_variant_builder_add (&builder, "{sv}", "password", g_variant_new_string (password));
+-
+- /* TODO: run in worker thread */
+- if (!goa_utils_store_credentials_for_object_sync (GOA_PROVIDER (provider),
+- object,
+- g_variant_builder_end (&builder),
+- cancellable,
+- error))
+- {
+- if (error != NULL)
+- {
+- (*error)->domain = GOA_ERROR;
+- (*error)->code = GOA_ERROR_NOT_AUTHORIZED;
+- }
+- goto out;
+- }
+-
+- success = TRUE;
+-
+- out:
+- if (success)
+- {
+- ret = access_token; access_token = NULL;
+- g_assert (ret != NULL);
+- if (out_access_token_secret != NULL)
+- {
+- *out_access_token_secret = access_token_secret; access_token_secret = NULL;
+- }
+- if (out_access_token_expires_in != NULL)
+- *out_access_token_expires_in = access_token_expires_in;
+- }
+- g_free (access_token);
+- g_free (access_token_secret);
+- g_free (session_handle);
+- g_free (access_token_for_refresh);
+- g_free (access_token_secret_for_refresh);
+- g_free (session_handle_for_refresh);
+- g_free (password);
+- g_clear_pointer (&credentials, g_variant_unref);
+-
+- g_mutex_unlock (lock);
+-
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gboolean on_handle_get_access_token (GoaOAuthBased *object,
+- GDBusMethodInvocation *invocation,
+- gpointer user_data);
+-
+-static gboolean
+-goa_oauth_provider_build_object (GoaProvider *provider,
+- GoaObjectSkeleton *object,
+- GKeyFile *key_file,
+- const gchar *group,
+- GDBusConnection *connection,
+- gboolean just_added,
+- GError **error)
+-{
+- GoaOAuthBased *oauth_based;
+- gchar *identity;
+-
+- identity = NULL;
+-
+- oauth_based = goa_object_get_oauth_based (GOA_OBJECT (object));
+- if (oauth_based != NULL)
+- goto out;
+-
+- oauth_based = goa_oauth_based_skeleton_new ();
+- goa_oauth_based_set_consumer_key (oauth_based,
+- goa_oauth_provider_get_consumer_key (GOA_OAUTH_PROVIDER (provider)));
+- goa_oauth_based_set_consumer_secret (oauth_based,
+- goa_oauth_provider_get_consumer_secret (GOA_OAUTH_PROVIDER (provider)));
+- /* Ensure D-Bus method invocations run in their own thread */
+- g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (oauth_based),
+- G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+- goa_object_skeleton_set_oauth_based (object, oauth_based);
+- g_signal_connect (oauth_based,
+- "handle-get-access-token",
+- G_CALLBACK (on_handle_get_access_token),
+- NULL);
+-
+- out:
+- g_object_unref (oauth_based);
+- g_free (identity);
+- return TRUE;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static gboolean
+-goa_oauth_provider_ensure_credentials_sync (GoaProvider *_provider,
+- GoaObject *object,
+- gint *out_expires_in,
+- GCancellable *cancellable,
+- GError **error)
+-{
+- GoaOAuthProvider *provider = GOA_OAUTH_PROVIDER (_provider);
+- gboolean ret = FALSE;
+- gchar *access_token = NULL;
+- gchar *access_token_secret = NULL;
+- gint access_token_expires_in;
+- gchar *identity = NULL;
+- gboolean force_refresh = FALSE;
+-
+- again:
+- access_token = goa_oauth_provider_get_access_token_sync (provider,
+- object,
+- force_refresh,
+- &access_token_secret,
+- &access_token_expires_in,
+- cancellable,
+- error);
+- if (access_token == NULL)
+- goto out;
+-
+- identity = goa_oauth_provider_get_identity_sync (provider,
+- access_token,
+- access_token_secret,
+- NULL, /* out_presentation_identity */
+- cancellable,
+- error);
+- if (identity == NULL)
+- {
+- /* OK, try again, with forcing the locally cached credentials to be refreshed */
+- if (!force_refresh)
+- {
+- force_refresh = TRUE;
+- g_free (access_token); access_token = NULL;
+- g_free (access_token_secret); access_token_secret = NULL;
+- g_clear_error (error);
+- goto again;
+- }
+- else
+- {
+- goto out;
+- }
+- }
+-
+- /* TODO: maybe check with the identity we have */
+- ret = TRUE;
+- if (out_expires_in != NULL)
+- *out_expires_in = access_token_expires_in;
+-
+- out:
+- g_free (identity);
+- g_free (access_token);
+- g_free (access_token_secret);
+- return ret;
+-}
+-
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-static void
+-goa_oauth_provider_init (GoaOAuthProvider *client)
+-{
+-}
+-
+-static void
+-goa_oauth_provider_class_init (GoaOAuthProviderClass *klass)
+-{
+- GoaProviderClass *provider_class;
+-
+- provider_class = GOA_PROVIDER_CLASS (klass);
+- provider_class->add_account = goa_oauth_provider_add_account;
+- provider_class->refresh_account = goa_oauth_provider_refresh_account;
+- provider_class->build_object = goa_oauth_provider_build_object;
+- provider_class->ensure_credentials_sync = goa_oauth_provider_ensure_credentials_sync;
+-
+- klass->build_authorization_uri = goa_oauth_provider_build_authorization_uri_default;
+- klass->get_use_mobile_browser = goa_oauth_provider_get_use_mobile_browser_default;
+- klass->is_deny_node = goa_oauth_provider_is_deny_node_default;
+- klass->is_password_node = goa_oauth_provider_is_password_node_default;
+- klass->get_request_uri_params = goa_oauth_provider_get_request_uri_params_default;
+- klass->add_account_key_values = goa_oauth_provider_add_account_key_values_default;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+-/* runs in a thread dedicated to handling @invocation */
+-static gboolean
+-on_handle_get_access_token (GoaOAuthBased *interface,
+- GDBusMethodInvocation *invocation,
+- gpointer user_data)
+-{
+- GoaObject *object;
+- GoaAccount *account;
+- GoaProvider *provider;
+- GError *error;
+- const gchar *id;
+- const gchar *method_name;
+- const gchar *provider_type;
+- gchar *access_token = NULL;
+- gchar *access_token_secret = NULL;
+- gint access_token_expires_in;
+-
+- /* TODO: maybe log what app is requesting access */
+-
+- object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface)));
+- account = goa_object_peek_account (object);
+-
+- id = goa_account_get_id (account);
+- provider_type = goa_account_get_provider_type (account);
+- method_name = g_dbus_method_invocation_get_method_name (invocation);
+- g_debug ("Handling %s for account (%s, %s)", method_name, provider_type, id);
+-
+- provider = goa_provider_get_for_provider_type (provider_type);
+-
+- error = NULL;
+- access_token = goa_oauth_provider_get_access_token_sync (GOA_OAUTH_PROVIDER (provider),
+- object,
+- FALSE, /* force_refresh */
+- &access_token_secret,
+- &access_token_expires_in,
+- NULL, /* GCancellable* */
+- &error);
+- if (access_token == NULL)
+- {
+- g_dbus_method_invocation_take_error (invocation, error);
+- }
+- else
+- {
+- goa_oauth_based_complete_get_access_token (interface,
+- invocation,
+- access_token,
+- access_token_secret,
+- access_token_expires_in);
+- }
+- g_free (access_token);
+- g_free (access_token_secret);
+- g_object_unref (provider);
+- return TRUE; /* invocation was handled */
+-}
+diff --git a/src/goabackend/goaoauthprovider.h b/src/goabackend/goaoauthprovider.h
+deleted file mode 100644
+index d4ffa3c..0000000
+--- a/src/goabackend/goaoauthprovider.h
++++ /dev/null
+@@ -1,143 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+-/*
+- * Copyright © 2011 – 2017 Red Hat, Inc.
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+-#error "Only can be included directly."
+-#endif
+-
+-#ifndef __GOA_OAUTH_PROVIDER_H__
+-#define __GOA_OAUTH_PROVIDER_H__
+-
+-#include
+-#include
+-#include
+-#include
+-
+-G_BEGIN_DECLS
+-
+-#define GOA_TYPE_OAUTH_PROVIDER (goa_oauth_provider_get_type ())
+-G_DECLARE_DERIVABLE_TYPE (GoaOAuthProvider, goa_oauth_provider, GOA, OAUTH_PROVIDER, GoaProvider);
+-
+-/**
+- * GoaOAuthProvider:
+- *
+- * The #GoaOAuthProvider structure contains only private data and should
+- * only be accessed using the provided API.
+- */
+-
+-/**
+- * GoaOAuthProviderClass:
+- * @parent_class: The parent class.
+- * @get_consumer_key: Virtual function for goa_oauth_provider_get_consumer_key().
+- * @get_consumer_secret: Virtual function for goa_oauth_provider_get_consumer_secret().
+- * @get_request_uri: Virtual function for goa_oauth_provider_get_request_uri().
+- * @get_authorization_uri: Virtual function for goa_oauth_provider_get_authorization_uri().
+- * @get_token_uri: Virtual function for goa_oauth_provider_get_token_uri().
+- * @get_callback_uri: Virtual function for goa_oauth_provider_get_callback_uri().
+- * @get_identity_sync: Virtual function for goa_oauth_provider_get_identity_sync().
+- * @parse_request_token_error: Virtual function for goa_oauth_provider_parse_request_token_error().
+- * @build_authorization_uri: Virtual function for goa_oauth_provider_build_authorization_uri().
+- * @get_use_mobile_browser: Virtual function for goa_oauth_provider_get_use_mobile_browser().
+- * @get_request_uri_params: Virtual function for goa_oauth_provider_get_request_uri_params().
+- * @add_account_key_values: Virtual function for goa_oauth_provider_add_account_key_values().
+- * @is_deny_node: Virtual function for goa_oauth_provider_is_deny_node().
+- * @is_identity_node: Virtual function for goa_oauth_provider_is_identity_node().
+- * @is_password_node: Virtual function for goa_oauth_provider_is_password_node().
+- *
+- * Class structure for #GoaOAuthProvider.
+- */
+-struct _GoaOAuthProviderClass
+-{
+- GoaProviderClass parent_class;
+-
+- /* pure virtual */
+- const gchar *(*get_consumer_key) (GoaOAuthProvider *provider);
+- const gchar *(*get_consumer_secret) (GoaOAuthProvider *provider);
+- const gchar *(*get_request_uri) (GoaOAuthProvider *provider);
+- const gchar *(*get_authorization_uri) (GoaOAuthProvider *provider);
+- const gchar *(*get_token_uri) (GoaOAuthProvider *provider);
+- const gchar *(*get_callback_uri) (GoaOAuthProvider *provider);
+-
+- gchar *(*get_identity_sync) (GoaOAuthProvider *provider,
+- const gchar *access_token,
+- const gchar *access_token_secret,
+- gchar **out_presentation_identity,
+- GCancellable *cancellable,
+- GError **error);
+-
+- gchar *(*parse_request_token_error) (GoaOAuthProvider *provider,
+- RestProxyCall *call);
+-
+- /* virtual but with default implementation */
+- gchar *(*build_authorization_uri) (GoaOAuthProvider *provider,
+- const gchar *authorization_uri,
+- const gchar *escaped_oauth_token);
+- gboolean (*get_use_mobile_browser) (GoaOAuthProvider *provider);
+- gchar **(*get_request_uri_params) (GoaOAuthProvider *provider);
+- void (*add_account_key_values) (GoaOAuthProvider *provider,
+- GVariantBuilder *builder);
+-
+- /* pure virtual */
+- gboolean (*is_identity_node) (GoaOAuthProvider *provider,
+- WebKitDOMHTMLInputElement *element);
+-
+- /* virtual but with default implementation */
+- gboolean (*is_deny_node) (GoaOAuthProvider *provider,
+- WebKitDOMNode *node);
+- gboolean (*is_password_node) (GoaOAuthProvider *provider,
+- WebKitDOMHTMLInputElement *element);
+-};
+-
+-const gchar *goa_oauth_provider_get_consumer_key (GoaOAuthProvider *provider);
+-const gchar *goa_oauth_provider_get_consumer_secret (GoaOAuthProvider *provider);
+-const gchar *goa_oauth_provider_get_request_uri (GoaOAuthProvider *provider);
+-gchar **goa_oauth_provider_get_request_uri_params (GoaOAuthProvider *provider);
+-const gchar *goa_oauth_provider_get_authorization_uri (GoaOAuthProvider *provider);
+-const gchar *goa_oauth_provider_get_token_uri (GoaOAuthProvider *provider);
+-const gchar *goa_oauth_provider_get_callback_uri (GoaOAuthProvider *provider);
+-gchar *goa_oauth_provider_get_identity_sync (GoaOAuthProvider *provider,
+- const gchar *access_token,
+- const gchar *access_token_secret,
+- gchar **out_presentation_identity,
+- GCancellable *cancellable,
+- GError **error);
+-gboolean goa_oauth_provider_is_deny_node (GoaOAuthProvider *provider,
+- WebKitDOMNode *node);
+-gboolean goa_oauth_provider_is_identity_node (GoaOAuthProvider *provider,
+- WebKitDOMHTMLInputElement *element);
+-gboolean goa_oauth_provider_is_password_node (GoaOAuthProvider *provider,
+- WebKitDOMHTMLInputElement *element);
+-gchar *goa_oauth_provider_parse_request_token_error (GoaOAuthProvider *provider,
+- RestProxyCall *call);
+-gchar *goa_oauth_provider_get_access_token_sync (GoaOAuthProvider *provider,
+- GoaObject *object,
+- gboolean force_refresh,
+- gchar **out_access_token_secret,
+- gint *out_access_token_expires_in,
+- GCancellable *cancellable,
+- GError **error);
+-gchar *goa_oauth_provider_build_authorization_uri (GoaOAuthProvider *provider,
+- const gchar *authorization_uri,
+- const gchar *escaped_oauth_token);
+-gboolean goa_oauth_provider_get_use_mobile_browser (GoaOAuthProvider *provider);
+-void goa_oauth_provider_add_account_key_values (GoaOAuthProvider *provider,
+- GVariantBuilder *builder);
+-
+-G_END_DECLS
+-
+-#endif /* __GOA_OAUTH_PROVIDER_H__ */
+diff --git a/src/goabackend/goaprovider.c b/src/goabackend/goaprovider.c
+index 9e5bd45..9712cd2 100644
+--- a/src/goabackend/goaprovider.c
++++ b/src/goabackend/goaprovider.c
+@@ -27,7 +27,6 @@
+ #include "goafacebookprovider.h"
+ #include "goaimapsmtpprovider.h"
+ #include "goaowncloudprovider.h"
+-#include "goaflickrprovider.h"
+ #include "goafoursquareprovider.h"
+ #include "goawindowsliveprovider.h"
+ #include "goamediaserverprovider.h"
+@@ -947,9 +946,6 @@ static struct
+ #ifdef GOA_WINDOWS_LIVE_ENABLED
+ { GOA_WINDOWS_LIVE_NAME, goa_windows_live_provider_get_type },
+ #endif
+-#ifdef GOA_FLICKR_ENABLED
+- { GOA_FLICKR_NAME, goa_flickr_provider_get_type },
+-#endif
+ #ifdef GOA_FOURSQUARE_ENABLED
+ { GOA_FOURSQUARE_NAME, goa_foursquare_provider_get_type },
+ #endif
+diff --git a/src/goabackend/goawebextension.c b/src/goabackend/goawebextension.c
+deleted file mode 100644
+index a17edf2..0000000
+--- a/src/goabackend/goawebextension.c
++++ /dev/null
+@@ -1,268 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+-/*
+- * Copyright © 2015 Damián Nohales
+- * Copyright © 2015 – 2017 Red Hat, Inc.
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-#include "config.h"
+-
+-#include
+-
+-#include "goaoauthprovider.h"
+-#include "goaoauth2provider.h"
+-#include "goaoauth2provider-web-extension.h"
+-#include "goaprovider.h"
+-#include "goawebextension.h"
+-
+-struct _GoaWebExtension
+-{
+- GObject parent;
+- GoaProvider *provider;
+- WebKitWebExtension *wk_extension;
+- gchar *existing_identity;
+- gchar *provider_type;
+-};
+-
+-enum
+-{
+- PROP_0,
+- PROP_EXISTING_IDENTITY,
+- PROP_PROVIDER_TYPE,
+- PROP_WK_EXTENSION
+-};
+-
+-G_DEFINE_TYPE (GoaWebExtension, goa_web_extension, G_TYPE_OBJECT)
+-
+-static void
+-web_extension_dom_node_deny_click_cb (WebKitDOMNode *element, WebKitDOMEvent *event, gpointer user_data)
+-{
+- WebKitDOMDOMWindow *dom_window = WEBKIT_DOM_DOM_WINDOW (user_data);
+- webkit_dom_dom_window_webkit_message_handlers_post_message (dom_window, "deny-click", "");
+-}
+-
+-static void
+-web_extension_dom_node_password_submit_cb (WebKitDOMNode *element, WebKitDOMEvent *event, gpointer user_data)
+-{
+- WebKitDOMDOMWindow *dom_window = WEBKIT_DOM_DOM_WINDOW (user_data);
+- WebKitDOMHTMLInputElement *password_node;
+- gchar *password;
+-
+- password_node = WEBKIT_DOM_HTML_INPUT_ELEMENT (g_object_get_data (G_OBJECT (dom_window), "goa-password-node"));
+- password = webkit_dom_html_input_element_get_value (password_node);
+- webkit_dom_dom_window_webkit_message_handlers_post_message (dom_window, "password-submit", password);
+- g_free (password);
+-}
+-
+-static void
+-web_extension_document_loaded_cb (WebKitWebPage *web_page, gpointer user_data)
+-{
+- GoaWebExtension *self = GOA_WEB_EXTENSION (user_data);
+- WebKitDOMDocument *document;
+- WebKitDOMDOMWindow *dom_window;
+- WebKitDOMHTMLCollection *elements = NULL;
+- gulong element_count;
+- gulong i;
+-
+- document = webkit_web_page_get_dom_document (web_page);
+- elements = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "*");
+- element_count = webkit_dom_html_collection_get_length (elements);
+-
+- dom_window = webkit_dom_document_get_default_view (document);
+-
+- for (i = 0; i < element_count; i++)
+- {
+- WebKitDOMNode *element = webkit_dom_html_collection_item (elements, i);
+-
+- if ((GOA_IS_OAUTH_PROVIDER (self->provider)
+- && goa_oauth_provider_is_deny_node (GOA_OAUTH_PROVIDER (self->provider), element))
+- || (GOA_IS_OAUTH2_PROVIDER (self->provider)
+- && goa_oauth2_provider_is_deny_node (GOA_OAUTH2_PROVIDER (self->provider), element)))
+- {
+- webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (element),
+- "click",
+- G_CALLBACK (web_extension_dom_node_deny_click_cb),
+- FALSE,
+- dom_window);
+- }
+- else if (self->existing_identity != NULL
+- && self->existing_identity[0] != '\0'
+- && WEBKIT_DOM_IS_HTML_INPUT_ELEMENT (element)
+- && ((GOA_IS_OAUTH_PROVIDER (self->provider)
+- && goa_oauth_provider_is_identity_node (GOA_OAUTH_PROVIDER (self->provider),
+- WEBKIT_DOM_HTML_INPUT_ELEMENT (element)))
+- || (GOA_IS_OAUTH2_PROVIDER (self->provider)
+- && goa_oauth2_provider_is_identity_node (GOA_OAUTH2_PROVIDER (self->provider),
+- WEBKIT_DOM_HTML_INPUT_ELEMENT (element)))))
+- {
+- webkit_dom_html_input_element_set_value (WEBKIT_DOM_HTML_INPUT_ELEMENT (element),
+- self->existing_identity);
+- webkit_dom_html_input_element_set_read_only (WEBKIT_DOM_HTML_INPUT_ELEMENT (element), TRUE);
+- }
+- else if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT (element)
+- && ((GOA_IS_OAUTH_PROVIDER (self->provider)
+- && goa_oauth_provider_is_password_node (GOA_OAUTH_PROVIDER (self->provider),
+- WEBKIT_DOM_HTML_INPUT_ELEMENT (element)))
+- || (GOA_IS_OAUTH2_PROVIDER (self->provider)
+- && goa_oauth2_provider_is_password_node (GOA_OAUTH2_PROVIDER (self->provider),
+- WEBKIT_DOM_HTML_INPUT_ELEMENT (element)))))
+- {
+- WebKitDOMHTMLFormElement *form;
+-
+- form = webkit_dom_html_input_element_get_form (WEBKIT_DOM_HTML_INPUT_ELEMENT (element));
+- if (form != NULL)
+- {
+- g_object_set_data_full (G_OBJECT (dom_window),
+- "goa-password-node",
+- g_object_ref (element),
+- g_object_unref);
+- webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (form),
+- "submit",
+- G_CALLBACK (web_extension_dom_node_password_submit_cb),
+- FALSE,
+- dom_window);
+- }
+- }
+- }
+-
+- g_clear_object (&elements);
+-}
+-
+-static void
+-web_extension_page_created_cb (GoaWebExtension *self, WebKitWebPage *web_page)
+-{
+- g_signal_connect_object (web_page, "document-loaded", G_CALLBACK (web_extension_document_loaded_cb), self, 0);
+-}
+-
+-static void
+-goa_web_extension_constructed (GObject *object)
+-{
+- GoaWebExtension *self = GOA_WEB_EXTENSION (object);
+-
+- G_OBJECT_CLASS (goa_web_extension_parent_class)->constructed (object);
+-
+- self->provider = goa_provider_get_for_provider_type (self->provider_type);
+-
+- g_signal_connect_object (self->wk_extension,
+- "page-created",
+- G_CALLBACK (web_extension_page_created_cb),
+- self,
+- G_CONNECT_SWAPPED);
+-}
+-
+-static void
+-goa_web_extension_dispose (GObject *object)
+-{
+- GoaWebExtension *self = GOA_WEB_EXTENSION (object);
+-
+- g_clear_object (&self->provider);
+- g_clear_object (&self->wk_extension);
+-
+- G_OBJECT_CLASS (goa_web_extension_parent_class)->dispose (object);
+-}
+-
+-static void
+-goa_web_extension_finalize (GObject *object)
+-{
+- GoaWebExtension *self = GOA_WEB_EXTENSION (object);
+-
+- g_free (self->existing_identity);
+- g_free (self->provider_type);
+-
+- G_OBJECT_CLASS (goa_web_extension_parent_class)->finalize (object);
+-}
+-
+-static void
+-goa_web_extension_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+-{
+- GoaWebExtension *self = GOA_WEB_EXTENSION (object);
+-
+- switch (prop_id)
+- {
+- case PROP_EXISTING_IDENTITY:
+- self->existing_identity = g_value_dup_string (value);
+- break;
+-
+- case PROP_PROVIDER_TYPE:
+- self->provider_type = g_value_dup_string (value);
+- break;
+-
+- case PROP_WK_EXTENSION:
+- self->wk_extension = WEBKIT_WEB_EXTENSION (g_value_dup_object (value));
+- break;
+-
+- default:
+- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+- break;
+- }
+-}
+-
+-static void
+-goa_web_extension_class_init (GoaWebExtensionClass *klass)
+-{
+- GObjectClass *object_class = G_OBJECT_CLASS (klass);
+-
+- object_class->constructed = goa_web_extension_constructed;
+- object_class->dispose = goa_web_extension_dispose;
+- object_class->finalize = goa_web_extension_finalize;
+- object_class->set_property = goa_web_extension_set_property;
+-
+- g_object_class_install_property (object_class,
+- PROP_EXISTING_IDENTITY,
+- g_param_spec_string ("existing-identity",
+- "A GoaAccount identity",
+- "The user name with which we want to prefill the form",
+- NULL,
+- G_PARAM_WRITABLE |
+- G_PARAM_CONSTRUCT_ONLY |
+- G_PARAM_STATIC_STRINGS));
+-
+- g_object_class_install_property (object_class,
+- PROP_PROVIDER_TYPE,
+- g_param_spec_string ("provider-type",
+- "A GoaProvider type",
+- "The provider type that is represented by this view",
+- NULL,
+- G_PARAM_WRITABLE |
+- G_PARAM_CONSTRUCT_ONLY |
+- G_PARAM_STATIC_STRINGS));
+-
+- g_object_class_install_property (object_class,
+- PROP_WK_EXTENSION,
+- g_param_spec_object ("wk-extension",
+- "A WebKitWebExtension",
+- "The associated WebKitWebExtension",
+- WEBKIT_TYPE_WEB_EXTENSION,
+- G_PARAM_WRITABLE |
+- G_PARAM_CONSTRUCT_ONLY |
+- G_PARAM_STATIC_STRINGS));
+-}
+-
+-static void
+-goa_web_extension_init (GoaWebExtension *self)
+-{
+-}
+-
+-GoaWebExtension *
+-goa_web_extension_new (WebKitWebExtension *wk_extension,
+- const gchar *provider_type,
+- const gchar *existing_identity)
+-{
+- return g_object_new (GOA_TYPE_WEB_EXTENSION,
+- "existing-identity", existing_identity,
+- "provider-type", provider_type,
+- "wk-extension", wk_extension,
+- NULL);
+-}
+diff --git a/src/goabackend/goawebextension.h b/src/goabackend/goawebextension.h
+deleted file mode 100644
+index 994f43f..0000000
+--- a/src/goabackend/goawebextension.h
++++ /dev/null
+@@ -1,37 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+-/*
+- * Copyright © 2015 Damián Nohales
+- * Copyright © 2015 – 2017 Red Hat, Inc.
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-#ifndef __GOA_WEB_EXTENSION_H__
+-#define __GOA_WEB_EXTENSION_H__
+-
+-#include
+-#include
+-
+-G_BEGIN_DECLS
+-
+-#define GOA_TYPE_WEB_EXTENSION (goa_web_extension_get_type())
+-G_DECLARE_FINAL_TYPE (GoaWebExtension, goa_web_extension, GOA, WEB_EXTENSION, GObject);
+-
+-GoaWebExtension *goa_web_extension_new (WebKitWebExtension *wk_extension,
+- const gchar *provider_type,
+- const gchar *existing_identity);
+-
+-G_END_DECLS
+-
+-#endif /* __GOA_WEB_EXTENSION_H__ */
+diff --git a/src/goabackend/goawebextensionmain.c b/src/goabackend/goawebextensionmain.c
+deleted file mode 100644
+index 061825f..0000000
+--- a/src/goabackend/goawebextensionmain.c
++++ /dev/null
+@@ -1,46 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+-/*
+- * Copyright © 2015 Damián Nohales
+- * Copyright © 2015 – 2017 Red Hat, Inc.
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-#include "config.h"
+-
+-#include
+-#include
+-
+-#include "goawebextension.h"
+-
+-static GoaWebExtension *the_extension;
+-
+-/* Silence -Wmissing-prototypes */
+-void webkit_web_extension_initialize_with_user_data (WebKitWebExtension *wk_extension, GVariant *user_data);
+-
+-G_MODULE_EXPORT void
+-webkit_web_extension_initialize_with_user_data (WebKitWebExtension *wk_extension, GVariant *user_data)
+-{
+- const gchar *existing_identity;
+- const gchar *provider_type;
+-
+- g_variant_get (user_data, "(&s&s)", &provider_type, &existing_identity);
+- the_extension = goa_web_extension_new (wk_extension, provider_type, existing_identity);
+-}
+-
+-static void __attribute__((destructor))
+-goa_web_extension_shutdown (void)
+-{
+- g_clear_object (&the_extension);
+-}
+diff --git a/src/goabackend/goawebview.c b/src/goabackend/goawebview.c
+deleted file mode 100644
+index 2438e0c..0000000
+--- a/src/goabackend/goawebview.c
++++ /dev/null
+@@ -1,515 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+- *
+- * Copyright © 2015 Damián Nohales
+- * Copyright © 2012 – 2017 Red Hat, Inc.
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-/* Based on code by the Epiphany team.
+- */
+-
+-#include "config.h"
+-
+-#include
+-#include
+-#include
+-#include
+-#include
+-
+-#include "goawebview.h"
+-#include "nautilus-floating-bar.h"
+-
+-struct _GoaWebView
+-{
+- GtkOverlay parent_instance;
+- GoaProvider *provider;
+- GtkWidget *floating_bar;
+- GtkWidget *progress_bar;
+- GtkWidget *web_view;
+- WebKitUserContentManager *user_content_manager;
+- WebKitWebContext *context;
+- gchar *existing_identity;
+- gulong clear_notify_progress_id;
+- gulong notify_load_status_id;
+- gulong notify_progress_id;
+-};
+-
+-enum
+-{
+- PROP_0,
+- PROP_EXISTING_IDENTITY,
+- PROP_PROVIDER
+-};
+-
+-enum
+-{
+- DENY_CLICK,
+- PASSWORD_SUBMIT,
+- LAST_SIGNAL
+-};
+-
+-static guint signals[LAST_SIGNAL] = { 0 };
+-
+-G_DEFINE_TYPE (GoaWebView, goa_web_view, GTK_TYPE_OVERLAY)
+-
+-static gboolean
+-web_view_clear_notify_progress_cb (gpointer user_data)
+-{
+- GoaWebView *self = GOA_WEB_VIEW (user_data);
+-
+- gtk_widget_hide (self->progress_bar);
+- self->clear_notify_progress_id = 0;
+- return FALSE;
+-}
+-
+-static char *
+-web_view_create_loading_title (const gchar *url)
+-{
+- SoupURI *uri;
+- const gchar *hostname;
+- gchar *title;
+-
+- g_return_val_if_fail (url != NULL && url[0] != '\0', NULL);
+-
+- uri = soup_uri_new (url);
+- hostname = soup_uri_get_host (uri);
+- /* translators: %s here is the address of the web page */
+- title = g_strdup_printf (_("Loading “%s”…"), hostname);
+- soup_uri_free (uri);
+-
+- return title;
+-}
+-
+-static void
+-web_view_floating_bar_update (GoaWebView *self, const gchar *text)
+-{
+- nautilus_floating_bar_set_label (NAUTILUS_FLOATING_BAR (self->floating_bar), text);
+-
+- if (text == NULL || text[0] == '\0')
+- {
+- gtk_widget_hide (self->floating_bar);
+- gtk_widget_set_halign (self->floating_bar, GTK_ALIGN_START);
+- }
+- else
+- gtk_widget_show (self->floating_bar);
+-}
+-
+-static void
+-web_view_initialize_web_extensions_cb (GoaWebView *self)
+-{
+- GVariant *data;
+- const gchar *existing_identity;
+- const gchar *provider_type;
+-
+- webkit_web_context_set_web_extensions_directory (self->context, PACKAGE_WEB_EXTENSIONS_DIR);
+-
+- if (self->provider == NULL)
+- return;
+-
+- provider_type = goa_provider_get_provider_type (self->provider);
+- existing_identity = (self->existing_identity == NULL) ? "" : self->existing_identity;
+- data = g_variant_new ("(ss)", provider_type, existing_identity);
+- webkit_web_context_set_web_extensions_initialization_user_data (self->context, data);
+-}
+-
+-#ifdef GOA_INSPECTOR_ENABLED
+-static void
+-web_view_inspector_closed_cb (WebKitWebInspector *inspector)
+-{
+- GtkWidget *window;
+- WebKitWebViewBase *inspector_web_view;
+-
+- inspector_web_view = webkit_web_inspector_get_web_view (inspector);
+- window = gtk_widget_get_toplevel (GTK_WIDGET (inspector_web_view));
+- if (gtk_widget_is_toplevel (window))
+- gtk_widget_destroy (window);
+-}
+-
+-static gboolean
+-web_view_inspector_open_window_cb (WebKitWebInspector *inspector)
+-{
+- GtkWidget *window;
+- GtkWindowGroup *group;
+- WebKitWebViewBase *inspector_web_view;
+-
+- group = gtk_window_group_new ();
+-
+- window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+- gtk_window_resize (GTK_WINDOW (window), 800, 600);
+- gtk_window_group_add_window (group, GTK_WINDOW (window));
+- g_object_unref (group);
+-
+- inspector_web_view = webkit_web_inspector_get_web_view (inspector);
+- gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (inspector_web_view));
+-
+- gtk_widget_show_all (window);
+- gtk_window_present (GTK_WINDOW (window));
+-
+- return GDK_EVENT_STOP;
+-}
+-#endif /* GOA_INSPECTOR_ENABLED */
+-
+-static void
+-web_view_load_changed_cb (WebKitWebView *web_view,
+- WebKitLoadEvent load_event,
+- gpointer user_data)
+-{
+- GoaWebView *self = GOA_WEB_VIEW (user_data);
+-
+- switch (load_event)
+- {
+- case WEBKIT_LOAD_STARTED:
+- case WEBKIT_LOAD_COMMITTED:
+- {
+- const gchar *uri;
+- gchar *title;
+-
+- uri = webkit_web_view_get_uri (web_view);
+- title = web_view_create_loading_title (uri);
+-
+- web_view_floating_bar_update (self, title);
+- g_free (title);
+- break;
+- }
+-
+- case WEBKIT_LOAD_REDIRECTED:
+- /* TODO: Update the loading uri */
+- break;
+-
+- case WEBKIT_LOAD_FINISHED:
+- web_view_floating_bar_update (self, NULL);
+- break;
+-
+- default:
+- break;
+- }
+-}
+-
+-static void
+-web_view_notify_estimated_load_progress_cb (GObject *object,
+- GParamSpec *pspec,
+- gpointer user_data)
+-{
+- GoaWebView *self = GOA_WEB_VIEW (user_data);
+- WebKitWebView *web_view = WEBKIT_WEB_VIEW (object);
+- gboolean loading;
+- const gchar *uri;
+- gdouble progress;
+-
+- if (self->clear_notify_progress_id != 0)
+- {
+- g_source_remove (self->clear_notify_progress_id);
+- self->clear_notify_progress_id = 0;
+- }
+-
+- uri = webkit_web_view_get_uri (web_view);
+- if (!uri || g_str_equal (uri, "about:blank"))
+- return;
+-
+- progress = webkit_web_view_get_estimated_load_progress (web_view);
+- loading = webkit_web_view_is_loading (web_view);
+-
+- if (progress == 1.0 || !loading)
+- self->clear_notify_progress_id = g_timeout_add (500, web_view_clear_notify_progress_cb, self);
+- else
+- gtk_widget_show (self->progress_bar);
+-
+- gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->progress_bar),
+- (loading || progress == 1.0) ? progress : 0.0);
+-}
+-
+-static void
+-web_view_script_message_received_deny_click_cb (GoaWebView *self)
+-{
+- g_signal_emit (self, signals[DENY_CLICK], 0);
+-}
+-
+-static void
+-web_view_script_message_received_password_submit_cb (GoaWebView *self, WebKitJavascriptResult *js_result)
+-{
+- JSCValue *jsc_value;
+- gchar *password = NULL;
+-
+- jsc_value = webkit_javascript_result_get_js_value (js_result);
+- password = jsc_value_to_string (jsc_value);
+- if (password != NULL && password[0] != '\0')
+- g_signal_emit (self, signals[PASSWORD_SUBMIT], 0, password);
+-
+- g_free (password);
+-}
+-
+-static void
+-goa_web_view_get_preferred_height (GtkWidget *widget, gint *minimum_size, gint *natural_size)
+-{
+- if (minimum_size != NULL)
+- *minimum_size = 200;
+-
+- if (natural_size != NULL)
+- *natural_size = 400;
+-}
+-
+-static void
+-goa_web_view_get_preferred_width (GtkWidget *widget, gint *minimum_size, gint *natural_size)
+-{
+- if (minimum_size != NULL)
+- *minimum_size = 300;
+-
+- if (natural_size != NULL)
+- *natural_size = 500;
+-}
+-
+-static GtkSizeRequestMode
+-goa_web_view_get_request_mode (GtkWidget *widget)
+-{
+- return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+-}
+-
+-static void
+-goa_web_view_constructed (GObject *object)
+-{
+- GoaWebView *self = GOA_WEB_VIEW (object);
+- const gchar *const *language_names;
+-
+- G_OBJECT_CLASS (goa_web_view_parent_class)->constructed (object);
+-
+- self->context = webkit_web_context_new ();
+- language_names = g_get_language_names ();
+- webkit_web_context_set_preferred_languages (self->context, language_names);
+- webkit_web_context_set_sandbox_enabled (self->context, TRUE);
+- g_signal_connect_swapped (self->context,
+- "initialize-web-extensions",
+- G_CALLBACK (web_view_initialize_web_extensions_cb),
+- self);
+-
+- self->user_content_manager = webkit_user_content_manager_new ();
+- g_signal_connect_swapped (self->user_content_manager,
+- "script-message-received::deny-click",
+- G_CALLBACK (web_view_script_message_received_deny_click_cb),
+- self);
+- g_signal_connect_swapped (self->user_content_manager,
+- "script-message-received::password-submit",
+- G_CALLBACK (web_view_script_message_received_password_submit_cb),
+- self);
+- webkit_user_content_manager_register_script_message_handler (self->user_content_manager, "deny-click");
+- webkit_user_content_manager_register_script_message_handler (self->user_content_manager, "password-submit");
+-
+- self->web_view = GTK_WIDGET (g_object_new (WEBKIT_TYPE_WEB_VIEW,
+- "user-content-manager", self->user_content_manager,
+- "web-context", self->context,
+- NULL));
+- gtk_container_add (GTK_CONTAINER (self), self->web_view);
+-
+-#ifdef GOA_INSPECTOR_ENABLED
+- {
+- WebKitSettings *settings;
+- WebKitWebInspector *inspector;
+-
+- /* Setup the inspector */
+- settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (self->web_view));
+- g_object_set (settings, "enable-developer-extras", TRUE, NULL);
+-
+- inspector = webkit_web_view_get_inspector (WEBKIT_WEB_VIEW (self->web_view));
+- g_signal_connect (inspector, "closed", G_CALLBACK (web_view_inspector_closed_cb), NULL);
+- g_signal_connect (inspector, "open-window", G_CALLBACK (web_view_inspector_open_window_cb), NULL);
+- }
+-#endif /* GOA_INSPECTOR_ENABLED */
+-
+- /* statusbar is hidden by default */
+- self->floating_bar = nautilus_floating_bar_new (NULL, FALSE);
+- gtk_widget_set_halign (self->floating_bar, GTK_ALIGN_START);
+- gtk_widget_set_valign (self->floating_bar, GTK_ALIGN_END);
+- gtk_widget_set_no_show_all (self->floating_bar, TRUE);
+- gtk_overlay_add_overlay (GTK_OVERLAY (self), self->floating_bar);
+-
+- self->progress_bar = gtk_progress_bar_new ();
+- gtk_style_context_add_class (gtk_widget_get_style_context (self->progress_bar),
+- GTK_STYLE_CLASS_OSD);
+- gtk_widget_set_halign (self->progress_bar, GTK_ALIGN_FILL);
+- gtk_widget_set_valign (self->progress_bar, GTK_ALIGN_START);
+- gtk_overlay_add_overlay (GTK_OVERLAY (self), self->progress_bar);
+-
+- self->notify_progress_id = g_signal_connect (self->web_view,
+- "notify::estimated-load-progress",
+- G_CALLBACK (web_view_notify_estimated_load_progress_cb),
+- self);
+- self->notify_load_status_id = g_signal_connect (self->web_view,
+- "load_changed",
+- G_CALLBACK (web_view_load_changed_cb),
+- self);
+-}
+-
+-static void
+-goa_web_view_dispose (GObject *object)
+-{
+- GoaWebView *self = GOA_WEB_VIEW (object);
+-
+- g_clear_object (&self->user_content_manager);
+- g_clear_object (&self->context);
+-
+- if (self->clear_notify_progress_id != 0)
+- {
+- g_source_remove (self->clear_notify_progress_id);
+- self->clear_notify_progress_id = 0;
+- }
+-
+- if (self->notify_load_status_id != 0)
+- {
+- g_signal_handler_disconnect (self->web_view, self->notify_load_status_id);
+- self->notify_load_status_id = 0;
+- }
+-
+- if (self->notify_progress_id != 0)
+- {
+- g_signal_handler_disconnect (self->web_view, self->notify_progress_id);
+- self->notify_progress_id = 0;
+- }
+-
+- G_OBJECT_CLASS (goa_web_view_parent_class)->dispose (object);
+-}
+-
+-static void
+-goa_web_view_finalize (GObject *object)
+-{
+- GoaWebView *self = GOA_WEB_VIEW (object);
+-
+- g_free (self->existing_identity);
+-
+- if (self->provider != NULL)
+- g_object_remove_weak_pointer (G_OBJECT (self->provider), (gpointer *) &self->provider);
+-
+- G_OBJECT_CLASS (goa_web_view_parent_class)->finalize (object);
+-}
+-
+-static void
+-goa_web_view_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+-{
+- GoaWebView *self = GOA_WEB_VIEW (object);
+-
+- switch (prop_id)
+- {
+- case PROP_EXISTING_IDENTITY:
+- self->existing_identity = g_value_dup_string (value);
+- break;
+-
+- case PROP_PROVIDER:
+- self->provider = GOA_PROVIDER (g_value_get_object (value));
+- if (self->provider != NULL)
+- g_object_add_weak_pointer (G_OBJECT (self->provider), (gpointer *) &self->provider);
+- break;
+-
+- default:
+- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+- break;
+- }
+-}
+-
+-static void
+-goa_web_view_init (GoaWebView *self)
+-{
+-}
+-
+-static void
+-goa_web_view_class_init (GoaWebViewClass *klass)
+-{
+- GObjectClass *object_class = G_OBJECT_CLASS (klass);
+- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+-
+- object_class->constructed = goa_web_view_constructed;
+- object_class->dispose = goa_web_view_dispose;
+- object_class->finalize = goa_web_view_finalize;
+- object_class->set_property = goa_web_view_set_property;
+-
+- widget_class->get_preferred_height = goa_web_view_get_preferred_height;
+- widget_class->get_preferred_width = goa_web_view_get_preferred_width;
+- widget_class->get_request_mode = goa_web_view_get_request_mode;
+-
+- g_object_class_install_property (object_class,
+- PROP_EXISTING_IDENTITY,
+- g_param_spec_string ("existing-identity",
+- "A GoaAccount identity",
+- "The user name with which we want to prefill the form",
+- NULL,
+- G_PARAM_WRITABLE |
+- G_PARAM_CONSTRUCT_ONLY |
+- G_PARAM_STATIC_STRINGS));
+-
+- g_object_class_install_property (object_class,
+- PROP_PROVIDER,
+- g_param_spec_object ("provider",
+- "A GoaProvider",
+- "The provider that is represented by this view",
+- GOA_TYPE_PROVIDER,
+- G_PARAM_WRITABLE |
+- G_PARAM_CONSTRUCT_ONLY |
+- G_PARAM_STATIC_STRINGS));
+-
+- signals[DENY_CLICK] = g_signal_new ("deny-click",
+- G_TYPE_FROM_CLASS (klass),
+- G_SIGNAL_RUN_LAST,
+- 0,
+- NULL,
+- NULL,
+- g_cclosure_marshal_VOID__VOID,
+- G_TYPE_NONE,
+- 0);
+-
+- signals[PASSWORD_SUBMIT] = g_signal_new ("password-submit",
+- G_TYPE_FROM_CLASS (klass),
+- G_SIGNAL_RUN_LAST,
+- 0,
+- NULL,
+- NULL,
+- g_cclosure_marshal_VOID__STRING,
+- G_TYPE_NONE,
+- 1,
+- G_TYPE_STRING);
+-}
+-
+-GtkWidget *
+-goa_web_view_new (GoaProvider *provider, const gchar *existing_identity)
+-{
+- return g_object_new (GOA_TYPE_WEB_VIEW, "provider", provider, "existing-identity", existing_identity, NULL);
+-}
+-
+-GtkWidget *
+-goa_web_view_get_view (GoaWebView *self)
+-{
+- return self->web_view;
+-}
+-
+-void
+-goa_web_view_fake_mobile (GoaWebView *self)
+-{
+- WebKitSettings *settings;
+-
+- settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (self->web_view));
+-
+- /* This is based on the HTC Wildfire's user agent. Some
+- * providers, like Google, refuse to provide the mobile
+- * version of their authentication pages otherwise. eg.,
+- * in Google's case, passing btmpl=mobile does not help.
+- *
+- * The actual user agent used by a HTC Wildfire is:
+- * Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; HTC Wildfire
+- * Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0
+- * Mobile Safari/533.1
+- *
+- * Also note that the user agents of some mobile browsers may
+- * not work. eg., Nokia N9.
+- */
+- webkit_settings_set_user_agent (settings,
+- "Mozilla/5.0 (GNOME; not Android) "
+- "AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile");
+-}
+diff --git a/src/goabackend/goawebview.h b/src/goabackend/goawebview.h
+deleted file mode 100644
+index 4d5d77f..0000000
+--- a/src/goabackend/goawebview.h
++++ /dev/null
+@@ -1,38 +0,0 @@
+-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+- *
+- * Copyright © 2012 – 2017 Red Hat, Inc.
+- *
+- * This library is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU Lesser General Public
+- * License as published by the Free Software Foundation; either
+- * version 2 of the License, or (at your option) any later version.
+- *
+- * This library 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
+- * Lesser General Public License for more details.
+- *
+- * You should have received a copy of the GNU Lesser General
+- * Public License along with this library; if not, see .
+- */
+-
+-#ifndef __GOA_WEB_VIEW_H__
+-#define __GOA_WEB_VIEW_H__
+-
+-#include
+-
+-#include "goaprovider.h"
+-
+-G_BEGIN_DECLS
+-
+-#define GOA_TYPE_WEB_VIEW (goa_web_view_get_type ())
+-G_DECLARE_FINAL_TYPE (GoaWebView, goa_web_view, GOA, WEB_VIEW, GtkOverlay);
+-
+-GtkWidget *goa_web_view_new (GoaProvider *provider,
+- const gchar *existing_identity);
+-GtkWidget *goa_web_view_get_view (GoaWebView *self);
+-void goa_web_view_fake_mobile (GoaWebView *self);
+-
+-G_END_DECLS
+-
+-#endif /* __GOA_WEB_VIEW_H__ */
+diff --git a/src/goabackend/goawindowsliveprovider.c b/src/goabackend/goawindowsliveprovider.c
+index be35746..0ac5efb 100644
+--- a/src/goabackend/goawindowsliveprovider.c
++++ b/src/goabackend/goawindowsliveprovider.c
+@@ -93,7 +93,8 @@ get_token_uri (GoaOAuth2Provider *oauth2_provider)
+ static const gchar *
+ get_redirect_uri (GoaOAuth2Provider *oauth2_provider)
+ {
+- return "https://login.live.com/oauth20_desktop.srf";
++ /* See: https://learn.microsoft.com/en-us/entra/identity-platform/reply-url */
++ return "goa-oauth2://localhost/"GOA_WINDOWS_LIVE_CLIENT_ID;
+ }
+
+ static const gchar *
+@@ -232,36 +233,6 @@ get_identity_sync (GoaOAuth2Provider *oauth2_provider,
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+-static gboolean
+-is_identity_node (GoaOAuth2Provider *oauth2_provider, WebKitDOMHTMLInputElement *element)
+-{
+- gboolean ret = FALSE;
+- gchar *element_type = NULL;
+- gchar *name = NULL;
+-
+- /* FIXME: This does not show up in
+- * webkit_dom_document_get_elements_by_tag_name, but can be
+- * seen in the inspector. Needs further investigation.
+- */
+-
+- g_object_get (element, "type", &element_type, NULL);
+- if (g_strcmp0 (element_type, "email") != 0)
+- goto out;
+-
+- name = webkit_dom_html_input_element_get_name (element);
+- if (g_strcmp0 (name, "login") != 0)
+- goto out;
+-
+- ret = TRUE;
+-
+- out:
+- g_free (element_type);
+- g_free (name);
+- return ret;
+-}
+-
+-/* ---------------------------------------------------------------------------------------------------- */
+-
+ static gboolean
+ build_object (GoaProvider *provider,
+ GoaObjectSkeleton *object,
+@@ -377,6 +348,5 @@ goa_windows_live_provider_class_init (GoaWindowsLiveProviderClass *klass)
+ oauth2_class->get_client_id = get_client_id;
+ oauth2_class->get_client_secret = get_client_secret;
+ oauth2_class->get_identity_sync = get_identity_sync;
+- oauth2_class->is_identity_node = is_identity_node;
+ oauth2_class->add_account_key_values = add_account_key_values;
+ }
diff --git a/SOURCES/kerberos-fixes.patch b/SOURCES/kerberos-fixes.patch
new file mode 100644
index 0000000..25c03a0
--- /dev/null
+++ b/SOURCES/kerberos-fixes.patch
@@ -0,0 +1,5242 @@
+From eb41a7c7c237797d2902d2c7b05f9d2d46fac070 Mon Sep 17 00:00:00 2001
+From: Debarshi Ray
+Date: Wed, 12 Oct 2022 22:59:34 +0200
+Subject: [PATCH 01/22] kerberos-identity-manager: Clarify an ambiguous debug
+ log about KCM
+
+Kerberos KCM credential caches do support multiple identities and a lot
+of users use KCM these days because it's the default on Fedora. Hence,
+it's better not to have a debug log that implies that the code wasn't
+expecting KCM and is making assumptions about it - KCM is definitely a
+supported Kerberos cache type.
+
+https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/merge_requests/104
+---
+ src/goaidentity/goakerberosidentitymanager.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goakerberosidentitymanager.c b/src/goaidentity/goakerberosidentitymanager.c
+index c35aa8b6..d4ff2de4 100644
+--- a/src/goaidentity/goakerberosidentitymanager.c
++++ b/src/goaidentity/goakerberosidentitymanager.c
+@@ -806,61 +806,63 @@ get_identity (GoaKerberosIdentityManager *self,
+ GoaIdentity *identity;
+
+ g_debug ("GoaKerberosIdentityManager: get identity %s", operation->identifier);
+ identity = g_hash_table_lookup (self->identities, operation->identifier);
+
+ if (identity == NULL)
+ {
+ g_task_return_new_error (operation->task,
+ GOA_IDENTITY_MANAGER_ERROR,
+ GOA_IDENTITY_MANAGER_ERROR_IDENTITY_NOT_FOUND,
+ _("Could not find identity"));
+ return;
+ }
+
+ g_task_return_pointer (operation->task, g_object_ref (identity), g_object_unref);
+ }
+
+ static krb5_error_code
+ get_new_credentials_cache (GoaKerberosIdentityManager *self,
+ krb5_ccache *credentials_cache)
+ {
+ krb5_error_code error_code;
+ gboolean supports_multiple_identities;
+
+ if (g_strcmp0 (self->credentials_cache_type, "FILE") == 0)
+ {
+ g_debug ("GoaKerberosIdentityManager: credential cache type %s doesn't supports cache collections",
+ self->credentials_cache_type);
+ supports_multiple_identities = FALSE;
+ }
+- else if (g_strcmp0 (self->credentials_cache_type, "DIR") == 0 || g_strcmp0 (self->credentials_cache_type, "KEYRING") == 0)
++ else if (g_strcmp0 (self->credentials_cache_type, "DIR") == 0
++ || g_strcmp0 (self->credentials_cache_type, "KCM") == 0
++ || g_strcmp0 (self->credentials_cache_type, "KEYRING") == 0)
+ {
+ g_debug ("GoaKerberosIdentityManager: credential cache type %s supports cache collections", self->credentials_cache_type);
+ supports_multiple_identities = TRUE;
+ }
+ else
+ {
+ g_debug ("GoaKerberosIdentityManager: don't know if credential cache type %s supports cache collections, "
+ "assuming yes",
+ self->credentials_cache_type);
+ supports_multiple_identities = TRUE;
+ }
+
+ /* If we're configured for FILE based credentials, then we only
+ * have one ccache, and we need to use it always.
+ *
+ * If we're configured for DIR or KEYRING based credentials, then we
+ * can have multiple ccache's so we should use the default one first
+ * (so it gets selected automatically) and then fallback to unique
+ * ccache names for subsequent tickets.
+ *
+ */
+ if (!supports_multiple_identities || g_hash_table_size (self->identities) == 0)
+ error_code = krb5_cc_default (self->kerberos_context, credentials_cache);
+ else
+ error_code = krb5_cc_new_unique (self->kerberos_context, self->credentials_cache_type, NULL, credentials_cache);
+
+ return error_code;
+ }
+
+ static void
+--
+2.39.3
+
+
+From 3bd1d5df6a25899651d2af72a5c226c3696e9f7c Mon Sep 17 00:00:00 2001
+From: Debarshi Ray
+Date: Thu, 13 Oct 2022 18:46:46 +0200
+Subject: [PATCH 02/22] kerberos-identity: Clarify and remove redundancy from
+ the renewal errors
+
+The "Could not renew identity" segment in the error strings from
+goa_kerberos_identity_renew is redundant because the caller already
+knows that this function is about renewing an identity and any failure
+means just that. In fact, it's callers in GoaIdentityService and
+GoaKerberosIdentityManager already prepend a similar segment when using
+the error strings.
+
+Debug logs are already verbose enough. It's better not to make it even
+more difficult to follow them through needless redundancy.
+
+When reporting errors from krb5_cc_get_principal, it's good to make the
+error string match the documentation [1] of that function because it
+makes the code self-documenting.
+
+[1] https://web.mit.edu/kerberos/krb5-devel/doc/appdev/refs/api/krb5_cc_get_principal.html
+
+https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/160
+---
+ src/goaidentity/goakerberosidentity.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 45d54f4d..3d2fe25c 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1403,71 +1403,72 @@ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+ }
+ else
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+ }
+ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+ }
+ }
+
+ gboolean
+ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+ {
+ krb5_error_code error_code = 0;
+ krb5_principal principal;
+ krb5_creds new_credentials;
+ gboolean renewed = FALSE;
+ char *name = NULL;
+
+ if (self->credentials_cache == NULL)
+ {
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+- _("Could not renew identity: Not signed in"));
++ _("Not signed in"));
+ goto out;
+ }
+
+ error_code = krb5_cc_get_principal (self->kerberos_context, self->credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+- error_code, _("Could not renew identity: "));
++ error_code,
++ _("Could not get the default principal: "));
+ goto out;
+ }
+
+ name = goa_kerberos_identity_get_principal_name (self);
+
+ error_code = krb5_get_renewed_creds (self->kerberos_context, &new_credentials, principal, self->credentials_cache, NULL);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_RENEWING,
+ error_code,
+ _("Could not get new credentials to renew identity %s: "),
+ name);
+ goto free_principal;
+ }
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ goto free_principal;
+ }
+
+ g_debug ("GoaKerberosIdentity: identity %s renewed", name);
+ renewed = TRUE;
+
+ free_principal:
+ krb5_free_principal (self->kerberos_context, principal);
+--
+2.39.3
+
+
+From a2e8e45b8e44201b9704ee31b9d762f71379be95 Mon Sep 17 00:00:00 2001
+From: Debarshi Ray
+Date: Thu, 13 Oct 2022 19:02:59 +0200
+Subject: [PATCH 03/22] kerberos-identity: Clarify the error when talking to
+ the KDC failed
+
+When krb5_get_renewed_creds fails to talk to the Kerberos Key
+Distribution Centre (or KDC) because of network problems,
+krb5_get_error_message translates the krb5_error_code as:
+ Resource temporarily unavailable
+
+This ends up in the debug logs as:
+ GoaKerberosIdentityManager: could not renew identity: Could not get
+ new credentials to renew identity FOO@BAR.ORG: Resource temporarily
+ unavailable
+ GoaIdentityService: could not renew identity: Could not get new
+ credentials to renew identity FOO@BAR.ORG: Resource temporarily
+ unavailable
+
+It's not immediately clear that the 'resource' that's 'temporarily
+unavailable' is actually the KDC, which would make it easier to
+understand that there is a network problem.
+
+Therefore, mention KDC in the error string from krb5_get_renewed_creds
+and make it match the documentation [1] of that function because it
+makes the code self-documenting.
+
+[1] https://web.mit.edu/kerberos/krb5-devel/doc/appdev/refs/api/krb5_get_renewed_creds.html
+
+https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/160
+---
+ src/goaidentity/goakerberosidentity.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 3d2fe25c..57ab616f 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1427,61 +1427,61 @@ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+
+ if (self->credentials_cache == NULL)
+ {
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ _("Not signed in"));
+ goto out;
+ }
+
+ error_code = krb5_cc_get_principal (self->kerberos_context, self->credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ error_code,
+ _("Could not get the default principal: "));
+ goto out;
+ }
+
+ name = goa_kerberos_identity_get_principal_name (self);
+
+ error_code = krb5_get_renewed_creds (self->kerberos_context, &new_credentials, principal, self->credentials_cache, NULL);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_RENEWING,
+ error_code,
+- _("Could not get new credentials to renew identity %s: "),
++ _("Could not get renewed credentials from the KDC for identity %s: "),
+ name);
+ goto free_principal;
+ }
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ goto free_principal;
+ }
+
+ g_debug ("GoaKerberosIdentity: identity %s renewed", name);
+ renewed = TRUE;
+
+ free_principal:
+ krb5_free_principal (self->kerberos_context, principal);
+
+ out:
+ g_free (name);
+
+ return renewed;
+ }
+
+ gboolean
+ goa_kerberos_identity_erase (GoaKerberosIdentity *self, GError **error)
+ {
+ krb5_error_code error_code = 0;
+
+ if (self->credentials_cache != NULL)
+--
+2.39.3
+
+
+From 4b4719ea24e8a8503ccd015659eb82e0fbdbb7de Mon Sep 17 00:00:00 2001
+From: Debarshi Ray
+Date: Thu, 13 Oct 2022 22:14:07 +0200
+Subject: [PATCH 04/22] kerberos-identity: Fail initialization if an identifier
+ can't be found
+
+The inability to get an identifier already leads to an error.
+Continuing beyond that point can lead to the verification_error trying
+to clobber it.
+
+https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/merge_requests/107
+---
+ src/goaidentity/goakerberosidentity.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 57ab616f..b72ce6ab 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -958,63 +958,64 @@ reset_alarms (GoaKerberosIdentity *self)
+ g_clear_pointer (&expiration_time, g_date_time_unref);
+ g_clear_pointer (&latest_possible_renewal_time, g_date_time_unref);
+ g_clear_pointer (&start_time, g_date_time_unref);
+
+ connect_alarm_signals (self);
+ }
+
+ static void
+ clear_alarms (GoaKerberosIdentity *self)
+ {
+ disconnect_alarm_signals (self);
+ clear_alarm_and_unref_on_idle (self, &self->renewal_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiring_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiration_alarm);
+ }
+
+ static gboolean
+ goa_kerberos_identity_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (initable);
+ GError *verification_error;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (self->identifier == NULL)
+ {
+ self->identifier = get_identifier (self, error);
++ if (self->identifier == NULL)
++ return FALSE;
+
+- if (self->identifier != NULL)
+- queue_notify (self, &self->identifier_idle_id, "identifier");
++ queue_notify (self, &self->identifier_idle_id, "identifier");
+ }
+
+ verification_error = NULL;
+ self->cached_verification_level = verify_identity (self, &self->preauth_identity_source, &verification_error);
+
+ switch (self->cached_verification_level)
+ {
+ case VERIFICATION_LEVEL_EXISTS:
+ case VERIFICATION_LEVEL_SIGNED_IN:
+ reset_alarms (self);
+
+ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+ return TRUE;
+
+ case VERIFICATION_LEVEL_UNVERIFIED:
+ return TRUE;
+
+ case VERIFICATION_LEVEL_ERROR:
+ default:
+ if (verification_error != NULL)
+ {
+ g_propagate_error (error, verification_error);
+ return FALSE;
+ }
+
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_VERIFYING,
+ _("No associated identification found"));
+ return FALSE;
+--
+2.39.3
+
+
+From 10da18e2f5ab562ce7c0ea52701956adb80a118f Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Thu, 27 Oct 2022 09:18:53 -0400
+Subject: [PATCH 05/22] kerberos-identity: Ensure idles queued to main thread
+ are property synchronized
+
+Kerberos identities are refreshed on a helper thread, and the state of
+those identities are exported over the user bus on the main thread.
+
+Since the main consumer of an identity's properties is the bus service
+running on the main thread, to simplify things, property notifications
+are dispatched from the main thread as well (even though the underlying
+state is changed on a worker thread).
+
+The mechanism to dispatch property notifies to the main thread is an
+idle handler. The logic for doing the dispatch has a concurrency
+bug however. In order to coaelsce multiple notifies that happen in
+quick succession, the dispatch code checks for a preexisting idle id
+associated with the given property. That idle id is set from the worker
+thread when the idle is queued, and it's cleared from the main thread
+when the idle is dispatched. The bug is that the main thread could in
+theory clear the idle id immediately after the worker thread decided
+there was already a notify queued, leading to a notify getting
+completely dropped.
+
+This commit addresses the bug by adding appropriate locking.
+
+Closes https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/160
+---
+ src/goaidentity/goakerberosidentity.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index b72ce6ab..695396bf 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -493,61 +493,64 @@ snoop_preauth_identity_from_credentials (GoaKerberosIdentity *self,
+ static krb5_timestamp
+ get_current_time (GoaKerberosIdentity *self)
+ {
+ krb5_timestamp current_time;
+ krb5_error_code error_code;
+
+ error_code = krb5_timeofday (self->kerberos_context, ¤t_time);
+ if (error_code != 0)
+ {
+ const char *error_message;
+
+ error_message = krb5_get_error_message (self->kerberos_context, error_code);
+ g_debug ("GoaKerberosIdentity: Error getting current time: %s", error_message);
+ krb5_free_error_message (self->kerberos_context, error_message);
+ return 0;
+ }
+
+ return current_time;
+ }
+
+ typedef struct
+ {
+ GoaKerberosIdentity *self;
+ guint *idle_id;
+ const char *property_name;
+ } NotifyRequest;
+
+ static void
+ clear_idle_id (NotifyRequest *request)
+ {
++ G_LOCK (identity_lock);
+ *request->idle_id = 0;
++ G_UNLOCK (identity_lock);
++
+ g_object_unref (request->self);
+ g_slice_free (NotifyRequest, request);
+ }
+
+ static gboolean
+ on_notify_queued (NotifyRequest *request)
+ {
+ g_object_notify (G_OBJECT (request->self), request->property_name);
+
+ return FALSE;
+ }
+
+ static void
+ queue_notify (GoaKerberosIdentity *self,
+ guint *idle_id,
+ const char *property_name)
+ {
+ NotifyRequest *request;
+
+ if (*idle_id != 0)
+ {
+ return;
+ }
+
+ request = g_slice_new0 (NotifyRequest);
+ request->self = g_object_ref (self);
+ request->idle_id = idle_id;
+ request->property_name = property_name;
+
+ *idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+--
+2.39.3
+
+
+From b8cb0f6df2742e0a79f764edf8f99ffa40a4347d Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Thu, 13 Oct 2022 16:11:54 -0400
+Subject: [PATCH 06/22] identity: Don't add temporary accounts for expired
+ credentials
+
+The identity service creates a "temporary" kerberos account when
+a user manually does kinit, to handle automatic renewal, etc.
+
+Unfortunately, it also picks up expired cruft that builds up in
+KCM based credential caches, and creates temporary accounts for
+that as well.
+
+This commit tries to avoid that.
+
+Closes https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/32
+---
+ src/goaidentity/goaidentityservice.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goaidentityservice.c b/src/goaidentity/goaidentityservice.c
+index 3dd27060..a25de416 100644
+--- a/src/goaidentity/goaidentityservice.c
++++ b/src/goaidentity/goaidentityservice.c
+@@ -1070,61 +1070,61 @@ add_temporary_account (GoaIdentityService *self,
+ g_object_ref (identity));
+ }
+ else
+ {
+ add_temporary_account_as_kerberos (self,
+ identity,
+ NULL,
+ on_temporary_account_added_as_kerberos,
+ g_object_ref (identity));
+ }
+
+ g_free (realm);
+ }
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+ static void
+ on_identity_added (GoaIdentityManager *identity_manager,
+ GoaIdentity *identity,
+ GoaIdentityService *self)
+ {
+ GoaObject *object;
+ const char *identifier;
+
+ export_identity (self, identity);
+
+ identifier = goa_identity_get_identifier (identity);
+
+ object = find_object_with_principal (self, identifier, FALSE);
+
+- if (object == NULL)
++ if (object == NULL && goa_identity_is_signed_in (identity))
+ add_temporary_account (self, identity);
+
+ g_clear_object (&object);
+ }
+
+ static void
+ on_identity_removed (GoaIdentityManager *identity_manager,
+ GoaIdentity *identity,
+ GoaIdentityService *self)
+ {
+ GoaObject *object;
+ const char *identifier;
+
+ identifier = goa_identity_get_identifier (identity);
+ object = find_object_with_principal (self, identifier, FALSE);
+
+ if (object != NULL)
+ ensure_account_credentials (self, object);
+
+ unexport_identity (self, identity);
+ g_clear_object (&object);
+ }
+
+ static void
+ on_identity_refreshed (GoaIdentityManager *identity_manager,
+ GoaIdentity *identity,
+ GoaIdentityService *self)
+ {
+ GoaObject *object;
+ const char *identifier;
+--
+2.39.3
+
+
+From bf61b7ed9842be060d50c7ef0fa6f5987d05d67b Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Mon, 28 Nov 2022 14:16:09 -0500
+Subject: [PATCH 07/22] kerberos-identity: Attempt to cope with multiple
+ credential caches per identity
+
+At the moment the identity service assumes there will just be one
+credential cache collection for any given prinicipal.
+
+This isn't necessarily true though, and the service gets quite
+confused when that assumption doesn't hold up.
+
+This commit attempts to make it cope with the situation better, by
+maintaining a hash table of collections per identity. It deems
+one of the collections the "active" one and relegates the rest to
+be backup if the active one expires and can't be renewed.
+
+Closes: https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/79
+---
+ src/goaidentity/goakerberosidentity.c | 340 ++++++++++++++++++++++----
+ 1 file changed, 287 insertions(+), 53 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 695396bf..dbb5991d 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -6,73 +6,76 @@
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see .
+ */
+
+ #include "config.h"
+
+ #include "goaidentity.h"
+ #include "goakerberosidentity.h"
+ #include "goakerberosidentityinquiry.h"
+ #include "goaalarm.h"
+
+ #include
+ #include
+ #include
+
+ #include
+ #include
+ #include
+
+ typedef enum
+ {
++ VERIFICATION_LEVEL_ERROR = -1,
+ VERIFICATION_LEVEL_UNVERIFIED,
+- VERIFICATION_LEVEL_ERROR,
+ VERIFICATION_LEVEL_EXISTS,
+ VERIFICATION_LEVEL_SIGNED_IN
+ } VerificationLevel;
+
+ struct _GoaKerberosIdentity
+ {
+ GObject parent;
+
+ krb5_context kerberos_context;
+ krb5_ccache credentials_cache;
+
++ GHashTable *credentials_caches;
++ char *active_credentials_cache_name;
++
+ char *identifier;
+ guint identifier_idle_id;
+
+ char *preauth_identity_source;
+
+ krb5_timestamp start_time;
+ guint start_time_idle_id;
+ krb5_timestamp renewal_time;
+ guint renewal_time_idle_id;
+ krb5_timestamp expiration_time;
+ guint expiration_time_idle_id;
+
+ GoaAlarm *expiration_alarm;
+ GoaAlarm *expiring_alarm;
+ GoaAlarm *renewal_alarm;
+
+ VerificationLevel cached_verification_level;
+ guint is_signed_in_idle_id;
+ };
+
+ enum
+ {
+ EXPIRING,
+ EXPIRED,
+ UNEXPIRED,
+ NEEDS_RENEWAL,
+ NEEDS_REFRESH,
+ NUMBER_OF_SIGNALS,
+ };
+
+@@ -82,84 +85,99 @@ enum
+ PROP_IDENTIFIER,
+ PROP_IS_SIGNED_IN,
+ PROP_START_TIMESTAMP,
+ PROP_RENEWAL_TIMESTAMP,
+ PROP_EXPIRATION_TIMESTAMP
+ };
+
+ static guint signals[NUMBER_OF_SIGNALS] = { 0 };
+
+ static void identity_interface_init (GoaIdentityInterface *interface);
+ static void initable_interface_init (GInitableIface *interface);
+ static void reset_alarms (GoaKerberosIdentity *self);
+ static void clear_alarms (GoaKerberosIdentity *self);
+ static gboolean goa_kerberos_identity_is_signed_in (GoaIdentity *identity);
+ static void set_and_prefix_error_from_krb5_error_code (GoaKerberosIdentity *self,
+ GError **error,
+ gint code,
+ krb5_error_code error_code,
+ const char *format,
+ ...) G_GNUC_PRINTF (5, 6);
+
+ G_LOCK_DEFINE_STATIC (identity_lock);
+
+ G_DEFINE_TYPE_WITH_CODE (GoaKerberosIdentity,
+ goa_kerberos_identity,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ initable_interface_init)
+ G_IMPLEMENT_INTERFACE (GOA_TYPE_IDENTITY,
+ identity_interface_init));
++
++static void
++close_credentials_caches (GoaKerberosIdentity *self)
++{
++ GHashTableIter iter;
++ const char *name;
++ krb5_ccache credentials_cache;
++
++ g_hash_table_iter_init (&iter, self->credentials_caches);
++ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
++ {
++ krb5_cc_close (self->kerberos_context, credentials_cache);
++ }
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++}
++
+ static void
+ goa_kerberos_identity_dispose (GObject *object)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (object);
+
+ G_LOCK (identity_lock);
+ clear_alarms (self);
+ g_clear_pointer (&self->preauth_identity_source, g_free);
++ close_credentials_caches (self);
++ g_clear_pointer (&self->credentials_caches, g_hash_table_unref);
+ G_UNLOCK (identity_lock);
+
+ G_OBJECT_CLASS (goa_kerberos_identity_parent_class)->dispose (object);
+
+ }
+
+ static void
+ goa_kerberos_identity_finalize (GObject *object)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (object);
+
+ g_free (self->identifier);
+
+- if (self->credentials_cache != NULL)
+- krb5_cc_close (self->kerberos_context, self->credentials_cache);
+-
+ G_OBJECT_CLASS (goa_kerberos_identity_parent_class)->finalize (object);
+ }
+
+ static void
+ goa_kerberos_identity_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *param_spec)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (object);
+
+ switch (property_id)
+ {
+ case PROP_IDENTIFIER:
+ G_LOCK (identity_lock);
+ g_value_set_string (value, self->identifier);
+ G_UNLOCK (identity_lock);
+ break;
+ case PROP_IS_SIGNED_IN:
+ g_value_set_boolean (value,
+ goa_kerberos_identity_is_signed_in (GOA_IDENTITY (self)));
+ break;
+ case PROP_START_TIMESTAMP:
+ G_LOCK (identity_lock);
+ g_value_set_int64 (value, (gint64) self->start_time);
+ G_UNLOCK (identity_lock);
+ break;
+ case PROP_RENEWAL_TIMESTAMP:
+ G_LOCK (identity_lock);
+ g_value_set_int64 (value, (gint64) self->renewal_time);
+@@ -228,109 +246,114 @@ goa_kerberos_identity_class_init (GoaKerberosIdentityClass *klass)
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+
+ g_object_class_override_property (object_class, PROP_IDENTIFIER, "identifier");
+ g_object_class_override_property (object_class, PROP_IS_SIGNED_IN, "is-signed-in");
+ g_object_class_override_property (object_class,
+ PROP_START_TIMESTAMP,
+ "start-timestamp");
+ g_object_class_override_property (object_class,
+ PROP_RENEWAL_TIMESTAMP,
+ "renewal-timestamp");
+ g_object_class_override_property (object_class,
+ PROP_EXPIRATION_TIMESTAMP,
+ "expiration-timestamp");
+
+ }
+
+ static char *
+ get_identifier (GoaKerberosIdentity *self,
+ GError **error)
+ {
+ krb5_principal principal;
+ krb5_error_code error_code;
+ char *unparsed_name;
+ char *identifier = NULL;
++ krb5_ccache credentials_cache;
+
+- if (self->credentials_cache == NULL)
++ if (self->active_credentials_cache_name == NULL)
+ return NULL;
+
+- error_code = krb5_cc_get_principal (self->kerberos_context, self->credentials_cache, &principal);
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++
++ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ if (error_code == KRB5_CC_END)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ error_code,
+ _("Could not find identity in credential cache: "));
+ }
+ else
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ENUMERATING_CREDENTIALS,
+ error_code,
+ _("Could not find identity in credential cache: "));
+ }
+ return NULL;
+ }
+
+ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &unparsed_name);
+ if (error_code != 0)
+ {
+ const char *error_message;
+
+ error_message = krb5_get_error_message (self->kerberos_context, error_code);
+ g_debug ("GoaKerberosIdentity: Error parsing principal identity name: %s",
+ error_message);
+ krb5_free_error_message (self->kerberos_context, error_message);
+ goto out;
+ }
+
+ identifier = g_strdup (unparsed_name);
+ krb5_free_unparsed_name (self->kerberos_context, unparsed_name);
+
+ out:
+ krb5_free_principal (self->kerberos_context, principal);
+ return identifier;
+ }
+
+ static void
+ goa_kerberos_identity_init (GoaKerberosIdentity *self)
+ {
++ self->credentials_caches = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ }
+
+ static void
+ set_and_prefix_error_from_krb5_error_code (GoaKerberosIdentity *self,
+ GError **error,
+ gint code,
+ krb5_error_code error_code,
+ const char *format,
+ ...)
+ {
+ const char *error_message;
+ char *literal_prefix;
+ va_list args;
+
+ error_message = krb5_get_error_message (self->kerberos_context, error_code);
+ g_set_error_literal (error, GOA_IDENTITY_ERROR, code, error_message);
+
+ va_start (args, format);
+ literal_prefix = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_prefix_error (error, "%s", literal_prefix);
+
+ g_free (literal_prefix);
+ krb5_free_error_message (self->kerberos_context, error_message);
+ }
+
+ char *
+ goa_kerberos_identity_get_principal_name (GoaKerberosIdentity *self)
+ {
+@@ -613,169 +636,280 @@ examine_credentials (GoaKerberosIdentity *self,
+ krb5_timestamp current_time;
+
+ G_LOCK (identity_lock);
+
+ if (credentials->times.starttime != 0)
+ credentials_start_time = credentials->times.starttime;
+ else
+ credentials_start_time = credentials->times.authtime;
+
+ *renewal_time = credentials->times.renew_till;
+
+ credentials_end_time = credentials->times.endtime;
+
+ if (self->start_time == 0)
+ *start_time = credentials_start_time;
+ else
+ *start_time = MIN (self->start_time, credentials_start_time);
+ *expiration_time = MAX (credentials->times.endtime, self->expiration_time);
+ G_UNLOCK (identity_lock);
+
+ current_time = get_current_time (self);
+
+ if (current_time < credentials_start_time ||
+ credentials_end_time <= current_time)
+ *are_expired = TRUE;
+ else
+ *are_expired = FALSE;
+ }
+
+ static VerificationLevel
+-verify_identity (GoaKerberosIdentity *self,
+- char **preauth_identity_source,
+- GError **error)
++verify_identity_in_credentials_cache (GoaKerberosIdentity *self,
++ char **preauth_identity_source,
++ krb5_ccache credentials_cache,
++ krb5_timestamp *start_time,
++ krb5_timestamp *renewal_time,
++ krb5_timestamp *expiration_time,
++ GError **error)
+ {
+ krb5_principal principal = NULL;
+ krb5_cc_cursor cursor;
+ krb5_creds credentials;
+ krb5_error_code error_code;
+- krb5_timestamp start_time = 0;
+- krb5_timestamp renewal_time = 0;
+- krb5_timestamp expiration_time = 0;
+ VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+
+- if (self->credentials_cache == NULL)
+- goto out;
++ g_debug ("GoaKerberosIdentity: Verifying identity in credentials cache '%s'",
++ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+
+- error_code = krb5_cc_get_principal (self->kerberos_context, self->credentials_cache, &principal);
++ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+ if (error_code != 0)
+ {
++ if (error_code == KRB5_CC_END)
++ g_debug ("GoaKerberosIdentity: Credentials cache empty");
++ else if (error_code == KRB5_FCC_NOFILE)
++ g_debug ("GoaKerberosIdentity: Credentials cache missing");
++
+ if (error_code == KRB5_CC_END || error_code == KRB5_FCC_NOFILE)
+ goto out;
+
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_NOT_FOUND,
+ error_code,
+ _("Could not find identity in credential cache: "));
+ verification_level = VERIFICATION_LEVEL_ERROR;
+ goto out;
+ }
+
+- error_code = krb5_cc_start_seq_get (self->kerberos_context, self->credentials_cache, &cursor);
++ error_code = krb5_cc_start_seq_get (self->kerberos_context, credentials_cache, &cursor);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ error_code,
+ _("Could not find identity credentials in cache: "));
+
+ verification_level = VERIFICATION_LEVEL_ERROR;
+ goto out;
+ }
+
+ verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+
+- error_code = krb5_cc_next_cred (self->kerberos_context, self->credentials_cache, &cursor, &credentials);
++ error_code = krb5_cc_next_cred (self->kerberos_context, credentials_cache, &cursor, &credentials);
+ while (error_code == 0)
+ {
+ if (credentials_validate_existence (self, principal, &credentials))
+ {
+ gboolean credentials_are_expired = TRUE;
+
+- examine_credentials (self, &credentials,
+- &start_time,
+- &renewal_time,
+- &expiration_time,
++ examine_credentials (self,
++ &credentials,
++ start_time,
++ renewal_time,
++ expiration_time,
+ &credentials_are_expired);
+
+ if (!credentials_are_expired)
+ verification_level = VERIFICATION_LEVEL_SIGNED_IN;
+ else
+ verification_level = VERIFICATION_LEVEL_EXISTS;
+ }
+ else
+ {
+ snoop_preauth_identity_from_credentials (self, &credentials, preauth_identity_source);
+ }
+
+ krb5_free_cred_contents (self->kerberos_context, &credentials);
+- error_code = krb5_cc_next_cred (self->kerberos_context, self->credentials_cache, &cursor, &credentials);
++ error_code = krb5_cc_next_cred (self->kerberos_context, credentials_cache, &cursor, &credentials);
+ }
+
+ if (error_code != KRB5_CC_END)
+ {
+ verification_level = VERIFICATION_LEVEL_ERROR;
+
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ENUMERATING_CREDENTIALS,
+ error_code,
+ _("Could not sift through identity credentials in cache: "));
+- goto end_sequence;
+ }
+
+- end_sequence:
+- error_code = krb5_cc_end_seq_get (self->kerberos_context, self->credentials_cache, &cursor);
++ error_code = krb5_cc_end_seq_get (self->kerberos_context, credentials_cache, &cursor);
+ if (error_code != 0)
+ {
+ verification_level = VERIFICATION_LEVEL_ERROR;
+
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ENUMERATING_CREDENTIALS,
+ error_code,
+ _("Could not finish up sifting through "
+ "identity credentials in cache: "));
+ goto out;
+ }
++
+ out:
++ switch (verification_level)
++ {
++ case VERIFICATION_LEVEL_EXISTS:
++ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are out of date",
++ krb5_cc_get_name (self->kerberos_context, credentials_cache));
++ break;
++
++ case VERIFICATION_LEVEL_SIGNED_IN:
++ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are valid",
++ krb5_cc_get_name (self->kerberos_context, credentials_cache));
++ break;
++
++ case VERIFICATION_LEVEL_UNVERIFIED:
++ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are missing",
++ krb5_cc_get_name (self->kerberos_context, credentials_cache));
++ break;
++
++ case VERIFICATION_LEVEL_ERROR:
++ default:
++ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' could not be validated",
++ krb5_cc_get_name (self->kerberos_context, credentials_cache));
++ break;
++ }
++
++ if (principal != NULL)
++ krb5_free_principal (self->kerberos_context, principal);
++ return verification_level;
++}
++
++static VerificationLevel
++verify_identity (GoaKerberosIdentity *self,
++ char **preauth_identity_source,
++ GError **error)
++{
++ krb5_ccache credentials_cache;
++ const char *name;
++ krb5_timestamp start_time = 0;
++ krb5_timestamp renewal_time = 0;
++ krb5_timestamp expiration_time = 0;
++ VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
++ GHashTableIter iter;
++
++ if (self->active_credentials_cache_name != NULL)
++ {
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++
++ verification_level = verify_identity_in_credentials_cache (self,
++ preauth_identity_source,
++ credentials_cache,
++ &start_time,
++ &renewal_time,
++ &expiration_time,
++ error);
++ if (verification_level == VERIFICATION_LEVEL_SIGNED_IN)
++ goto out;
++
++ if (verification_level == VERIFICATION_LEVEL_UNVERIFIED)
++ {
++ krb5_cc_close (self->kerberos_context, credentials_cache);
++ g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ }
++ }
++
++ self->start_time = 0;
++ self->renewal_time = 0;
++ self->expiration_time = 0;
++
++ g_hash_table_iter_init (&iter, self->credentials_caches);
++ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
++ {
++ krb5_timestamp new_start_time = 0;
++ krb5_timestamp new_renewal_time = 0;
++ krb5_timestamp new_expiration_time = 0;
++
++ if (g_strcmp0 (name, self->active_credentials_cache_name) == 0)
++ continue;
++
++ g_clear_pointer (preauth_identity_source, g_free);
++ verification_level = verify_identity_in_credentials_cache (self,
++ preauth_identity_source,
++ credentials_cache,
++ &new_start_time,
++ &new_renewal_time,
++ &new_expiration_time,
++ error);
++
++ if (verification_level == VERIFICATION_LEVEL_SIGNED_IN ||
++ self->active_credentials_cache_name == NULL)
++ {
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ self->active_credentials_cache_name = g_strdup (name);
++ start_time = new_start_time;
++ renewal_time = new_renewal_time;
++ expiration_time = new_expiration_time;
++
++ if (verification_level == VERIFICATION_LEVEL_SIGNED_IN)
++ break;
++ }
++ else if (verification_level == VERIFICATION_LEVEL_UNVERIFIED)
++ {
++ krb5_cc_close (self->kerberos_context, credentials_cache);
++ g_hash_table_iter_remove (&iter);
++ }
++ }
+
++out:
+ G_LOCK (identity_lock);
+ set_start_time (self, start_time);
+ set_renewal_time (self, renewal_time);
+ set_expiration_time (self, expiration_time);
+ G_UNLOCK (identity_lock);
+
+- if (principal != NULL)
+- krb5_free_principal (self->kerberos_context, principal);
+ return verification_level;
+ }
+
+ static gboolean
+ goa_kerberos_identity_is_signed_in (GoaIdentity *identity)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (identity);
+ gboolean is_signed_in = FALSE;
+
+ G_LOCK (identity_lock);
+ if (self->cached_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ is_signed_in = TRUE;
+ G_UNLOCK (identity_lock);
+
+ return is_signed_in;
+ }
+
+ static void
+ identity_interface_init (GoaIdentityInterface *interface)
+ {
+ interface->get_identifier = goa_kerberos_identity_get_identifier;
+ interface->is_signed_in = goa_kerberos_identity_is_signed_in;
+ }
+
+ static void
+ on_expiration_alarm_fired (GoaAlarm *alarm,
+ GoaKerberosIdentity *self)
+ {
+ g_return_if_fail (GOA_IS_ALARM (alarm));
+ g_return_if_fail (GOA_IS_KERBEROS_IDENTITY (self));
+@@ -1052,121 +1186,154 @@ on_kerberos_inquiry (krb5_context kerberos_context,
+ GoaIdentityInquiry *inquiry;
+ krb5_error_code error_code = 0;
+
+ if (number_of_prompts == 0)
+ goto out;
+
+ inquiry = goa_kerberos_identity_inquiry_new (operation->identity,
+ name,
+ banner,
+ prompts,
+ number_of_prompts);
+
+ operation->inquiry_func (inquiry,
+ operation->cancellable,
+ operation->inquiry_data);
+
+ if (goa_identity_inquiry_is_failed (inquiry))
+ error_code = KRB5_LIBOS_CANTREADPWD;
+ else if (!goa_identity_inquiry_is_complete (inquiry))
+ g_cancellable_cancel (operation->cancellable);
+
+ if (g_cancellable_is_cancelled (operation->cancellable))
+ error_code = KRB5_LIBOS_PWDINTR;
+
+ g_object_unref (inquiry);
+
+ out:
+ return error_code;
+ }
+
++static void
++goa_kerberos_identity_add_credentials_cache (GoaKerberosIdentity *self,
++ krb5_ccache credentials_cache)
++{
++ const char *cache_name;
++
++ cache_name = krb5_cc_get_name (self->kerberos_context, credentials_cache);
++
++ if (g_hash_table_contains (self->credentials_caches, cache_name))
++ {
++ krb5_ccache old_credentials_cache;
++
++ old_credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches, cache_name);
++
++ krb5_cc_close (self->kerberos_context, old_credentials_cache);
++ }
++
++ g_hash_table_replace (self->credentials_caches, g_strdup (cache_name), credentials_cache);
++
++ if (self->active_credentials_cache_name == NULL)
++ {
++ self->active_credentials_cache_name = g_strdup (cache_name);
++ }
++}
++
+ static gboolean
+-create_credential_cache (GoaKerberosIdentity *self,
+- GError **error)
++create_credentials_cache (GoaKerberosIdentity *self,
++ GError **error)
+ {
+ krb5_ccache default_cache;
++ krb5_ccache new_cache;
+ const char *cache_type;
+ krb5_error_code error_code;
+
+ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
+
+ if (error_code == 0)
+ {
+ cache_type = krb5_cc_get_type (self->kerberos_context, default_cache);
+- error_code = krb5_cc_new_unique (self->kerberos_context, cache_type, NULL, &self->credentials_cache);
++ error_code = krb5_cc_new_unique (self->kerberos_context, cache_type, NULL, &new_cache);
+ }
+
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ALLOCATING_CREDENTIALS,
+ error_code,
+ _("Could not create credential cache: "));
+
+ return FALSE;
+ }
+
++ goa_kerberos_identity_add_credentials_cache (self, new_cache);
++
+ return TRUE;
+ }
+
+ static gboolean
+ goa_kerberos_identity_update_credentials (GoaKerberosIdentity *self,
+ krb5_principal principal,
+ krb5_creds *new_credentials,
+ GError **error)
+ {
+ krb5_error_code error_code;
++ krb5_ccache credentials_cache;
+
+- if (self->credentials_cache == NULL)
++
++ if (self->active_credentials_cache_name == NULL)
+ {
+- if (!create_credential_cache (self, error))
++ if (!create_credentials_cache (self, error))
+ {
+ krb5_free_cred_contents (self->kerberos_context, new_credentials);
+ goto out;
+ }
+ }
+
+- error_code = krb5_cc_initialize (self->kerberos_context, self->credentials_cache, principal);
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++
++ error_code = krb5_cc_initialize (self->kerberos_context, credentials_cache, principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ALLOCATING_CREDENTIALS,
+ error_code,
+ _("Could not initialize credentials cache: "));
+
+ krb5_free_cred_contents (self->kerberos_context, new_credentials);
+ goto out;
+ }
+
+- error_code = krb5_cc_store_cred (self->kerberos_context, self->credentials_cache, new_credentials);
++ error_code = krb5_cc_store_cred (self->kerberos_context, credentials_cache, new_credentials);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_SAVING_CREDENTIALS,
+ error_code,
+ _("Could not store new credentials in credentials cache: "));
+
+ krb5_free_cred_contents (self->kerberos_context, new_credentials);
+ goto out;
+ }
+ krb5_free_cred_contents (self->kerberos_context, new_credentials);
+
+ return TRUE;
+ out:
+ return FALSE;
+ }
+
+ static SignInOperation *
+ sign_in_operation_new (GoaKerberosIdentity *identity,
+ GoaIdentityInquiryFunc inquiry_func,
+ gpointer inquiry_data,
+ GDestroyNotify destroy_notify,
+ GCancellable *cancellable)
+ {
+ SignInOperation *operation;
+
+ operation = g_slice_new0 (SignInOperation);
+ operation->identity = g_object_ref (identity);
+ operation->inquiry_func = inquiry_func;
+@@ -1327,201 +1494,268 @@ goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+ krb5_free_principal (self->kerberos_context, principal);
+
+ g_debug ("GoaKerberosIdentity: identity signed in");
+ signed_in = TRUE;
+ done:
+
+ return signed_in;
+ }
+
+ static void
+ update_identifier (GoaKerberosIdentity *self, GoaKerberosIdentity *new_identity)
+ {
+ char *new_identifier;
+
+ new_identifier = get_identifier (self, NULL);
+ if (g_strcmp0 (self->identifier, new_identifier) != 0 && new_identifier != NULL)
+ {
+ g_free (self->identifier);
+ self->identifier = new_identifier;
+ queue_notify (self, &self->identifier_idle_id, "identifier");
+ }
+ else
+ {
+ g_free (new_identifier);
+ }
+ }
+
++static int
++goa_kerberos_identity_compare (GoaKerberosIdentity *self,
++ GoaKerberosIdentity *new_identity)
++{
++ if (self->cached_verification_level < new_identity->cached_verification_level)
++ return -100;
++
++ if (self->cached_verification_level > new_identity->cached_verification_level)
++ return 100;
++
++ if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
++ return 50;
++
++ if (self->expiration_time < new_identity->expiration_time)
++ return -10;
++
++ if (self->expiration_time > new_identity->expiration_time)
++ return 10;
++
++ if (self->start_time > new_identity->start_time)
++ return -5;
++
++ if (self->start_time < new_identity->start_time)
++ return 5;
++
++ if (self->renewal_time < new_identity->renewal_time)
++ return -1;
++
++ if (self->renewal_time > new_identity->renewal_time)
++ return 1;
++
++ return 0;
++}
++
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
++ int comparison;
++
++ comparison = goa_kerberos_identity_compare (self, new_identity);
++
++ if (new_identity->active_credentials_cache_name != NULL)
++ {
++ krb5_ccache credentials_cache;
++ krb5_ccache copied_cache;
+
+- if (self->credentials_cache != NULL)
+- krb5_cc_close (self->kerberos_context, self->credentials_cache);
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
++ new_identity->active_credentials_cache_name);
++ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
+- krb5_cc_dup (new_identity->kerberos_context, new_identity->credentials_cache, &self->credentials_cache);
++ if (comparison < 0)
++ g_clear_pointer (&self->active_credentials_cache_name, &g_free);
++
++ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
++ }
++
++ if (comparison >= 0)
++ return;
+
+ G_LOCK (identity_lock);
+ update_identifier (self, new_identity);
+-
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+ old_verification_level = self->cached_verification_level;
+ new_verification_level = new_identity->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ reset_alarms (self);
+ else
+ clear_alarms (self);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
+ new_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+ }
+ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+ new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+ }
+ else
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+ }
+ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+ }
+ }
+
+ gboolean
+ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+ {
+ krb5_error_code error_code = 0;
+ krb5_principal principal;
+ krb5_creds new_credentials;
++ krb5_ccache credentials_cache;
+ gboolean renewed = FALSE;
+ char *name = NULL;
+
+- if (self->credentials_cache == NULL)
++ if (self->active_credentials_cache_name == NULL)
+ {
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ _("Not signed in"));
+ goto out;
+ }
+
+- error_code = krb5_cc_get_principal (self->kerberos_context, self->credentials_cache, &principal);
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ error_code,
+ _("Could not get the default principal: "));
+ goto out;
+ }
+
+ name = goa_kerberos_identity_get_principal_name (self);
+
+- error_code = krb5_get_renewed_creds (self->kerberos_context, &new_credentials, principal, self->credentials_cache, NULL);
++ error_code = krb5_get_renewed_creds (self->kerberos_context, &new_credentials, principal, credentials_cache, NULL);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_RENEWING,
+ error_code,
+ _("Could not get renewed credentials from the KDC for identity %s: "),
+ name);
+ goto free_principal;
+ }
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ goto free_principal;
+ }
+
+ g_debug ("GoaKerberosIdentity: identity %s renewed", name);
+ renewed = TRUE;
+
+ free_principal:
+ krb5_free_principal (self->kerberos_context, principal);
+
+ out:
+ g_free (name);
+
+ return renewed;
+ }
+
+ gboolean
+ goa_kerberos_identity_erase (GoaKerberosIdentity *self, GError **error)
+ {
++ GHashTableIter iter;
++ const char *name;
++ krb5_ccache credentials_cache;
+ krb5_error_code error_code = 0;
+
+- if (self->credentials_cache != NULL)
++ if (self->active_credentials_cache_name != NULL)
+ {
+- error_code = krb5_cc_destroy (self->kerberos_context, self->credentials_cache);
+- self->credentials_cache = NULL;
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++ g_debug ("GoaKerberosIdentity: Destroying active credentials cache %s", self->active_credentials_cache_name);
++ error_code = krb5_cc_destroy (self->kerberos_context, credentials_cache);
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++
++ if (error_code != 0)
++ {
++ set_and_prefix_error_from_krb5_error_code (self,
++ error,
++ GOA_IDENTITY_ERROR_REMOVING_CREDENTIALS,
++ error_code, _("Could not erase identity: "));
++ }
+ }
+
+- if (error_code != 0)
++ g_hash_table_iter_init (&iter, self->credentials_caches);
++ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
+ {
+- set_and_prefix_error_from_krb5_error_code (self,
+- error,
+- GOA_IDENTITY_ERROR_REMOVING_CREDENTIALS,
+- error_code, _("Could not erase identity: "));
+- return FALSE;
++ g_debug ("GoaKerberosIdentity: Destroying inactive credentials cache %s", name);
++ krb5_cc_destroy (self->kerberos_context, credentials_cache);
+ }
++ g_hash_table_remove_all (self->credentials_caches);
+
+- return TRUE;
++ return error_code == 0;
+ }
+
+ GoaIdentity *
+ goa_kerberos_identity_new (krb5_context context, krb5_ccache cache, GError **error)
+ {
+ GoaKerberosIdentity *self;
++ krb5_ccache copied_cache;
+
+ self = GOA_KERBEROS_IDENTITY (g_object_new (GOA_TYPE_KERBEROS_IDENTITY, NULL));
+-
+- krb5_cc_dup (context, cache, &self->credentials_cache);
+ self->kerberos_context = context;
+
++ krb5_cc_dup (self->kerberos_context, cache, &copied_cache);
++ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
++
+ error = NULL;
+ if (!g_initable_init (G_INITABLE (self), NULL, error))
+ {
+ g_object_unref (self);
+ return NULL;
+ }
+
+ return GOA_IDENTITY (self);
+ }
+--
+2.39.3
+
+
+From 02b8df618e1e26ed70b586b3214d1c8f255f9909 Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Mon, 28 Nov 2022 15:58:09 -0500
+Subject: [PATCH 08/22] kerberos-identity: Clear alarms on temporary identity
+
+When the identity service does a refresh, it creates a new temporary
+identity object to check the credentials, then it merges that
+temporary identity into the preexisting identity object (so the
+pointers don't change).
+
+This has the unfortunate side-effect of arming expiration alarms in
+the temporary object, that can then fire immediately before the object
+is thrown out.
+
+This commit disarms those alarms so they don't fire needlessly.
+---
+ src/goaidentity/goakerberosidentity.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index dbb5991d..6006385b 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1554,60 +1554,62 @@ goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+
+ return 0;
+ }
+
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
+ int comparison;
+
+ comparison = goa_kerberos_identity_compare (self, new_identity);
+
+ if (new_identity->active_credentials_cache_name != NULL)
+ {
+ krb5_ccache credentials_cache;
+ krb5_ccache copied_cache;
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+ new_identity->active_credentials_cache_name);
+ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
+ if (comparison < 0)
+ g_clear_pointer (&self->active_credentials_cache_name, &g_free);
+
+ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
+ }
+
++ clear_alarms (new_identity);
++
+ if (comparison >= 0)
+ return;
+
+ G_LOCK (identity_lock);
+ update_identifier (self, new_identity);
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+ old_verification_level = self->cached_verification_level;
+ new_verification_level = new_identity->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ reset_alarms (self);
+ else
+ clear_alarms (self);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
+ new_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+--
+2.39.3
+
+
+From c0f70c84a8fafbcaf3245765be51a7a027b312e3 Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Tue, 29 Nov 2022 12:21:09 -0500
+Subject: [PATCH 09/22] kerberos-identity: Add missing locking
+
+commit c492cbfd861bc773cf8b4c15bc722380355fc4b3 introduced some
+code to goa_kerberos_identity_update that's not protected by the
+identity lock.
+
+It really should be.
+
+This commit fixes that.
+---
+ src/goaidentity/goakerberosidentity.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 6006385b..b51c9920 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1537,76 +1537,78 @@ goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+ if (self->expiration_time < new_identity->expiration_time)
+ return -10;
+
+ if (self->expiration_time > new_identity->expiration_time)
+ return 10;
+
+ if (self->start_time > new_identity->start_time)
+ return -5;
+
+ if (self->start_time < new_identity->start_time)
+ return 5;
+
+ if (self->renewal_time < new_identity->renewal_time)
+ return -1;
+
+ if (self->renewal_time > new_identity->renewal_time)
+ return 1;
+
+ return 0;
+ }
+
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
+ int comparison;
+
++ G_LOCK (identity_lock);
+ comparison = goa_kerberos_identity_compare (self, new_identity);
+
+ if (new_identity->active_credentials_cache_name != NULL)
+ {
+ krb5_ccache credentials_cache;
+ krb5_ccache copied_cache;
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+ new_identity->active_credentials_cache_name);
+ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
+ if (comparison < 0)
+ g_clear_pointer (&self->active_credentials_cache_name, &g_free);
+
+ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
+ }
++ G_UNLOCK (identity_lock);
+
+ clear_alarms (new_identity);
+
+ if (comparison >= 0)
+ return;
+
+ G_LOCK (identity_lock);
+ update_identifier (self, new_identity);
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+ old_verification_level = self->cached_verification_level;
+ new_verification_level = new_identity->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ reset_alarms (self);
+ else
+ clear_alarms (self);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+--
+2.39.3
+
+
+From 4d8bba0ec4df1c5fd8533c4b77b62002d5c99ece Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Wed, 30 Nov 2022 13:53:41 -0500
+Subject: [PATCH 10/22] kerberos-identity: Drop the weird ampersand
+
+This commit drops an unnecessary and non-idiomatic ampersand.
+---
+ src/goaidentity/goakerberosidentity.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index b51c9920..55288d24 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1550,61 +1550,61 @@ goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+ return -1;
+
+ if (self->renewal_time > new_identity->renewal_time)
+ return 1;
+
+ return 0;
+ }
+
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
+ int comparison;
+
+ G_LOCK (identity_lock);
+ comparison = goa_kerberos_identity_compare (self, new_identity);
+
+ if (new_identity->active_credentials_cache_name != NULL)
+ {
+ krb5_ccache credentials_cache;
+ krb5_ccache copied_cache;
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+ new_identity->active_credentials_cache_name);
+ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
+ if (comparison < 0)
+- g_clear_pointer (&self->active_credentials_cache_name, &g_free);
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
+
+ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
+ }
+ G_UNLOCK (identity_lock);
+
+ clear_alarms (new_identity);
+
+ if (comparison >= 0)
+ return;
+
+ G_LOCK (identity_lock);
+ update_identifier (self, new_identity);
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+ old_verification_level = self->cached_verification_level;
+ new_verification_level = new_identity->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ reset_alarms (self);
+ else
+ clear_alarms (self);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+--
+2.39.3
+
+
+From 1d947c23ae037ea9063064338250251cc52f0b0c Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Thu, 15 Dec 2022 14:46:01 -0500
+Subject: [PATCH 11/22] kerberos-identity: Unbreak handling of fresh caches
+
+commit 4acfcc323e986526975ede981673dd173be4e267 attempted to
+avoid an error variable getting stomped all over by returning
+FALSE when encountering the error.
+
+Unfortunately, it's actual legitimate for an error to happen
+in that path and we should proceed anyway.
+
+That can happen when a credential cache is new and not yet
+initialized, so it won't have a principal associated with it
+yet.
+
+This commit changes the problematic code to just pass NULL
+for the error variable, since we don't need it.
+---
+ src/goaidentity/goakerberosidentity.c | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 55288d24..a20c0438 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1094,65 +1094,64 @@ reset_alarms (GoaKerberosIdentity *self)
+ g_clear_pointer (&renewal_time, g_date_time_unref);
+ g_clear_pointer (&expiration_time, g_date_time_unref);
+ g_clear_pointer (&latest_possible_renewal_time, g_date_time_unref);
+ g_clear_pointer (&start_time, g_date_time_unref);
+
+ connect_alarm_signals (self);
+ }
+
+ static void
+ clear_alarms (GoaKerberosIdentity *self)
+ {
+ disconnect_alarm_signals (self);
+ clear_alarm_and_unref_on_idle (self, &self->renewal_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiring_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiration_alarm);
+ }
+
+ static gboolean
+ goa_kerberos_identity_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (initable);
+ GError *verification_error;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (self->identifier == NULL)
+ {
+- self->identifier = get_identifier (self, error);
+- if (self->identifier == NULL)
+- return FALSE;
++ self->identifier = get_identifier (self, NULL);
+
+- queue_notify (self, &self->identifier_idle_id, "identifier");
++ if (self->identifier != NULL)
++ queue_notify (self, &self->identifier_idle_id, "identifier");
+ }
+
+ verification_error = NULL;
+ self->cached_verification_level = verify_identity (self, &self->preauth_identity_source, &verification_error);
+
+ switch (self->cached_verification_level)
+ {
+ case VERIFICATION_LEVEL_EXISTS:
+ case VERIFICATION_LEVEL_SIGNED_IN:
+ reset_alarms (self);
+
+ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+ return TRUE;
+
+ case VERIFICATION_LEVEL_UNVERIFIED:
+ return TRUE;
+
+ case VERIFICATION_LEVEL_ERROR:
+ default:
+ if (verification_error != NULL)
+ {
+ g_propagate_error (error, verification_error);
+ return FALSE;
+ }
+
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_VERIFYING,
+ _("No associated identification found"));
+ return FALSE;
+--
+2.39.3
+
+
+From 712feaacacf435f807a9f11fc5fa6066d6a052af Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Thu, 15 Dec 2022 14:46:01 -0500
+Subject: [PATCH 12/22] kerberos-identity: Fix buglet in update_identity
+
+The update_identity function is supposed to transfer the identity
+form one object to another.
+
+In practice, this is currently always a noop because only objects
+with the same identities get copied to each other.
+
+Nevertheless, there is a bug in the function. It grabs the identity
+from the target object instead of from the source object.
+
+This commit fixes that.
+---
+ src/goaidentity/goakerberosidentity.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index a20c0438..bc607966 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1480,61 +1480,61 @@ goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+
+ if (destroy_notify)
+ destroy_notify (inquiry_data);
+ sign_in_operation_free (operation);
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+ krb5_free_principal (self->kerberos_context, principal);
+
+ g_debug ("GoaKerberosIdentity: identity signed in");
+ signed_in = TRUE;
+ done:
+
+ return signed_in;
+ }
+
+ static void
+ update_identifier (GoaKerberosIdentity *self, GoaKerberosIdentity *new_identity)
+ {
+ char *new_identifier;
+
+- new_identifier = get_identifier (self, NULL);
++ new_identifier = get_identifier (new_identity, NULL);
+ if (g_strcmp0 (self->identifier, new_identifier) != 0 && new_identifier != NULL)
+ {
+ g_free (self->identifier);
+ self->identifier = new_identifier;
+ queue_notify (self, &self->identifier_idle_id, "identifier");
+ }
+ else
+ {
+ g_free (new_identifier);
+ }
+ }
+
+ static int
+ goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ if (self->cached_verification_level < new_identity->cached_verification_level)
+ return -100;
+
+ if (self->cached_verification_level > new_identity->cached_verification_level)
+ return 100;
+
+ if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+ return 50;
+
+ if (self->expiration_time < new_identity->expiration_time)
+ return -10;
+
+ if (self->expiration_time > new_identity->expiration_time)
+ return 10;
+--
+2.39.3
+
+
+From 4f3d89260a4b600e26a67a671039ea03692fb684 Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Thu, 15 Dec 2022 16:27:27 -0500
+Subject: [PATCH 13/22] goakerberosidentity: Fix crash when erasing credentials
+
+Right now when erasing an identity we erase the
+active credentials first and then the inactive
+ones.
+
+We neglect to take the active one out of the hash
+table, though, so it gets destroyed twice.
+
+This commit fixes that.
+---
+ src/goaidentity/goakerberosidentity.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index bc607966..46a6fb22 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1692,60 +1692,62 @@ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+ {
+ goto free_principal;
+ }
+
+ g_debug ("GoaKerberosIdentity: identity %s renewed", name);
+ renewed = TRUE;
+
+ free_principal:
+ krb5_free_principal (self->kerberos_context, principal);
+
+ out:
+ g_free (name);
+
+ return renewed;
+ }
+
+ gboolean
+ goa_kerberos_identity_erase (GoaKerberosIdentity *self, GError **error)
+ {
+ GHashTableIter iter;
+ const char *name;
+ krb5_ccache credentials_cache;
+ krb5_error_code error_code = 0;
+
+ if (self->active_credentials_cache_name != NULL)
+ {
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
+ g_debug ("GoaKerberosIdentity: Destroying active credentials cache %s", self->active_credentials_cache_name);
+ error_code = krb5_cc_destroy (self->kerberos_context, credentials_cache);
++ g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
++
+ g_clear_pointer (&self->active_credentials_cache_name, g_free);
+
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_REMOVING_CREDENTIALS,
+ error_code, _("Could not erase identity: "));
+ }
+ }
+
+ g_hash_table_iter_init (&iter, self->credentials_caches);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
+ {
+ g_debug ("GoaKerberosIdentity: Destroying inactive credentials cache %s", name);
+ krb5_cc_destroy (self->kerberos_context, credentials_cache);
+ }
+ g_hash_table_remove_all (self->credentials_caches);
+
+ return error_code == 0;
+ }
+
+ GoaIdentity *
+ goa_kerberos_identity_new (krb5_context context, krb5_ccache cache, GError **error)
+ {
+ GoaKerberosIdentity *self;
+ krb5_ccache copied_cache;
+
+ self = GOA_KERBEROS_IDENTITY (g_object_new (GOA_TYPE_KERBEROS_IDENTITY, NULL));
+ self->kerberos_context = context;
+--
+2.39.3
+
+
+From d36f9eb09ee157a01ad66f3950076a46cda94094 Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Thu, 15 Dec 2022 15:35:49 -0500
+Subject: [PATCH 14/22] goakerberosidentity: Explicitly switch to credentials
+ cache when needed
+
+If we're updating a credentials cache and decide
+it should be the new default for an identity, and
+the old credentials cache was the default cache
+for the cache collection then we should make the
+new credential cache the default cache for the
+collection, too.
+
+This commit adds that. It also makes the new
+credentials cache the default if there wasn't a
+valid default set already. This brings consistency
+to differences in behavior from different kerberos
+ccache types.
+---
+ src/goaidentity/goakerberosidentity.c | 63 +++++++++++++++++++++++++--
+ 1 file changed, 60 insertions(+), 3 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 46a6fb22..4fe4b70b 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1527,100 +1527,157 @@ goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+ if (self->cached_verification_level < new_identity->cached_verification_level)
+ return -100;
+
+ if (self->cached_verification_level > new_identity->cached_verification_level)
+ return 100;
+
+ if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+ return 50;
+
+ if (self->expiration_time < new_identity->expiration_time)
+ return -10;
+
+ if (self->expiration_time > new_identity->expiration_time)
+ return 10;
+
+ if (self->start_time > new_identity->start_time)
+ return -5;
+
+ if (self->start_time < new_identity->start_time)
+ return 5;
+
+ if (self->renewal_time < new_identity->renewal_time)
+ return -1;
+
+ if (self->renewal_time > new_identity->renewal_time)
+ return 1;
+
+ return 0;
+ }
+
++static char *
++get_default_cache_name (GoaKerberosIdentity *self)
++{
++ int error_code;
++ krb5_ccache default_cache;
++ krb5_principal principal;
++ char *default_cache_name;
++ char *principal_name;
++
++ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
++
++ if (error_code != 0)
++ return NULL;
++
++ /* Return NULL if the default cache doesn't pass basic sanity checks
++ */
++ error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &principal_name);
++ krb5_free_principal (self->kerberos_context, principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ krb5_free_unparsed_name (self->kerberos_context, principal_name);
++
++ default_cache_name = g_strdup (krb5_cc_get_name (self->kerberos_context, default_cache));
++ krb5_cc_close (self->kerberos_context, default_cache);
++
++ return default_cache_name;
++}
++
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
+ int comparison;
+
+ G_LOCK (identity_lock);
++
++ old_verification_level = self->cached_verification_level;
++ new_verification_level = new_identity->cached_verification_level;
++
+ comparison = goa_kerberos_identity_compare (self, new_identity);
+
+ if (new_identity->active_credentials_cache_name != NULL)
+ {
++ g_autofree char *default_cache_name = NULL;
+ krb5_ccache credentials_cache;
+ krb5_ccache copied_cache;
++ gboolean should_switch_to_new_credentials_cache = FALSE;
++
++ default_cache_name = get_default_cache_name (self);
++
++ if (default_cache_name == NULL)
++ should_switch_to_new_credentials_cache = TRUE;
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+ new_identity->active_credentials_cache_name);
+ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
++ if (g_strcmp0 (default_cache_name, self->active_credentials_cache_name) == 0)
++ {
++ if ((comparison < 0) ||
++ (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN))
++ should_switch_to_new_credentials_cache = TRUE;
++ }
++
+ if (comparison < 0)
+- g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ {
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ self->active_credentials_cache_name = g_strdup (new_identity->active_credentials_cache_name);
++ }
+
+ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
++
++ if (should_switch_to_new_credentials_cache)
++ krb5_cc_switch (self->kerberos_context, copied_cache);
+ }
+ G_UNLOCK (identity_lock);
+
+ clear_alarms (new_identity);
+
+ if (comparison >= 0)
+ return;
+
+ G_LOCK (identity_lock);
+ update_identifier (self, new_identity);
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+- old_verification_level = self->cached_verification_level;
+- new_verification_level = new_identity->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ reset_alarms (self);
+ else
+ clear_alarms (self);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
+ new_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+ }
+ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+ new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ G_LOCK (identity_lock);
+--
+2.39.3
+
+
+From bef93518e9f23d69c01c714ec9c8a6f2c012a08c Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Mon, 16 Jan 2023 15:00:36 -0500
+Subject: [PATCH 15/22] goakerberosidentity: Fix automatic reinitialization
+
+The identity service has the ability to automatically fetch a new ticket
+granting ticket from the KDC when the existing one expires, provided the
+user keeps their kerberos password in GNOME keyring.
+
+Unfortunately, commit aca400799c225a84e5d0fc90efb206c8f1d48bc3
+inadvertently broke this feature in some cases.
+
+When deciding whether or not to make a new credentials cache for a
+principal the active one it looks at various characteristics of the
+competing credentials to decide which cache is better.
+
+For instance, if one credentials cache has a ticket that's valid and
+signed in, but the other credentials cache only has an expired ticket,
+then obviously the one that's valid and signed in gets picked to be
+active.
+
+Likewise, if one is expiring in 10 minutes and one is expiring in
+24 hours, the one that expires in 24 hours will be treated as better.
+
+This comparison, only makes sense, though when looking at two different
+credentials caches. If we're updating a preexisting credentials cache,
+then we're actually just comparing up to date data with out of date
+data. In that case, we need to proceed even if new newer view of the
+credentials look worse than the older view of those credentials.
+Unfortunately, the buggy commit neglected to account for that.
+
+This commit fixes that problem and related problems, by more
+thoroughly and systematically checking all the permutations of
+credentials in the old credentials cache for the identity, the
+new credentials cache for the identity, and the default credentials
+cache. It also adds a lot more logging for clarity.
+---
+ src/goaidentity/goakerberosidentity.c | 198 ++++++++++++++++++++++++--
+ 1 file changed, 183 insertions(+), 15 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 4fe4b70b..d046a8a4 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1562,130 +1562,298 @@ get_default_cache_name (GoaKerberosIdentity *self)
+ krb5_principal principal;
+ char *default_cache_name;
+ char *principal_name;
+
+ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
+
+ if (error_code != 0)
+ return NULL;
+
+ /* Return NULL if the default cache doesn't pass basic sanity checks
+ */
+ error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
+
+ if (error_code != 0)
+ return NULL;
+
+ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &principal_name);
+ krb5_free_principal (self->kerberos_context, principal);
+
+ if (error_code != 0)
+ return NULL;
+
+ krb5_free_unparsed_name (self->kerberos_context, principal_name);
+
+ default_cache_name = g_strdup (krb5_cc_get_name (self->kerberos_context, default_cache));
+ krb5_cc_close (self->kerberos_context, default_cache);
+
+ return default_cache_name;
+ }
+
++static char *
++get_default_principal (GoaKerberosIdentity *self)
++{
++ int error_code;
++ krb5_ccache default_cache;
++ krb5_principal principal;
++ char *unparsed_principal, *principal_name;
++
++ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
++
++ if (error_code != 0)
++ return NULL;
++
++ /* Return NULL if the default cache doesn't pass basic sanity checks
++ */
++ error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &unparsed_principal);
++ krb5_free_principal (self->kerberos_context, principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ principal_name = g_strdup (unparsed_principal);
++ krb5_free_unparsed_name (self->kerberos_context, unparsed_principal);
++
++ krb5_cc_close (self->kerberos_context, default_cache);
++
++ return principal_name;
++}
++
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
++ gboolean should_set_cache_active = FALSE;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
++ g_autofree char *default_principal = NULL;
+ int comparison;
+
+ G_LOCK (identity_lock);
+
++ g_debug ("GoaKerberosIdentity: Evaluating updated credentials for identity %s "
++ "(old credentials cache name: %s, new credentials cache name: %s)",
++ self->identifier,
++ self->active_credentials_cache_name,
++ new_identity->active_credentials_cache_name);
+ old_verification_level = self->cached_verification_level;
+ new_verification_level = new_identity->cached_verification_level;
+
++ default_principal = get_default_principal (self);
+ comparison = goa_kerberos_identity_compare (self, new_identity);
+
+ if (new_identity->active_credentials_cache_name != NULL)
+ {
+ g_autofree char *default_cache_name = NULL;
+ krb5_ccache credentials_cache;
+ krb5_ccache copied_cache;
+- gboolean should_switch_to_new_credentials_cache = FALSE;
++ gboolean should_set_cache_as_default = FALSE;
++ gboolean is_default_principal = FALSE, is_default_cache = FALSE;
++ gboolean cache_already_active = FALSE;
++
++ is_default_principal = g_strcmp0 (default_principal, self->identifier) == 0;
+
+ default_cache_name = get_default_cache_name (self);
++ is_default_cache = g_strcmp0 (default_cache_name, self->active_credentials_cache_name) == 0;
++ cache_already_active = g_strcmp0 (self->active_credentials_cache_name, new_identity->active_credentials_cache_name) == 0;
+
+- if (default_cache_name == NULL)
+- should_switch_to_new_credentials_cache = TRUE;
++ g_debug ("GoaKerberosIdentity: Default credentials cache is '%s' (is %sus, is %sactive)", default_cache_name, is_default_cache? "" : "not ", cache_already_active? "" : "not ");
+
+- credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+- new_identity->active_credentials_cache_name);
+- krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
++ if (default_principal == NULL)
++ {
++ should_set_cache_as_default = TRUE;
++ should_set_cache_active = TRUE;
+
+- if (g_strcmp0 (default_cache_name, self->active_credentials_cache_name) == 0)
++ g_debug ("GoaKerberosIdentity: Setting default credentials cache to '%s' (principal %s) "
++ "because there is no active default",
++ new_identity->active_credentials_cache_name,
++ self->identifier);
++ }
++ else if (!is_default_principal)
++ {
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache from '%s' to '%s' (principal %s) "
++ "because identity is currently not default (credentials already active? %s)",
++ default_cache_name,
++ new_identity->active_credentials_cache_name,
++ self->identifier,
++ cache_already_active? "yes" : "no");
++ should_set_cache_as_default = FALSE;
++
++ if (comparison < 0)
++ {
++ should_set_cache_active = TRUE;
++
++ g_debug ("GoaKerberosIdentity: Switching identity %s from credentials cache '%s' to credentials cache '%s' "
++ "because it has better credentials",
++ self->identifier,
++ self->active_credentials_cache_name,
++ new_identity->active_credentials_cache_name);
++ }
++ else if (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
++ {
++ should_set_cache_active = TRUE;
++
++ g_debug ("GoaKerberosIdentity: Switching identity %s from credentials cache '%s' to "
++ "'%s' because it is newer and is otherwise just as good",
++ self->identifier,
++ self->active_credentials_cache_name,
++ new_identity->active_credentials_cache_name);
++ }
++ else
++ {
++ should_set_cache_active = FALSE;
++
++ g_debug ("GoaKerberosIdentity: Not switching identity %s from credentials cache '%s' to '%s' "
++ "because it has less good credentials",
++ self->identifier,
++ default_cache_name,
++ new_identity->active_credentials_cache_name);
++ }
++ }
++ else if (cache_already_active)
++ {
++ if (is_default_cache)
++ {
++ should_set_cache_as_default = FALSE;
++ should_set_cache_active = FALSE;
++
++ g_debug ("GoaKerberosIdentity: Not setting default credentials cache to '%s' "
++ "because cache is already active for identity %s and identity is default",
++ new_identity->active_credentials_cache_name,
++ self->identifier);
++ }
++ else
++ {
++ should_set_cache_as_default = TRUE;
++ should_set_cache_active = TRUE;
++
++ g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
++ "because identity %s is default and cache is supposed to be active already but isn't",
++ default_cache_name,
++ new_identity->active_credentials_cache_name,
++ self->identifier);
++ }
++ }
++ else
+ {
+- if ((comparison < 0) ||
+- (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN))
+- should_switch_to_new_credentials_cache = TRUE;
++ if (comparison < 0)
++ {
++ should_set_cache_as_default = TRUE;
++ should_set_cache_active = TRUE;
++
++ g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
++ "because identity %s is default and the cache has better credentials than those "
++ "in '%s'",
++ default_cache_name,
++ new_identity->active_credentials_cache_name,
++ self->identifier,
++ self->active_credentials_cache_name);
++ }
++ else if (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
++ {
++ should_set_cache_as_default = TRUE;
++ should_set_cache_active = TRUE;
++
++ g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
++ "because identity %s is default, and the cache has newer, and otherwise "
++ "just as good credentials as those in '%s'",
++ default_cache_name,
++ new_identity->active_credentials_cache_name,
++ self->identifier,
++ self->active_credentials_cache_name);
++ }
++ else
++ {
++ should_set_cache_as_default = FALSE;
++ should_set_cache_active = FALSE;
++
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache from '%s' to '%s' "
++ "because identity %s is default but newer credentials aren't as good as those in '%s'",
++ default_cache_name,
++ new_identity->active_credentials_cache_name,
++ self->identifier,
++ self->active_credentials_cache_name);
++ }
+ }
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
++ new_identity->active_credentials_cache_name);
++ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
+- if (comparison < 0)
++ if (should_set_cache_active)
+ {
+ g_clear_pointer (&self->active_credentials_cache_name, g_free);
+ self->active_credentials_cache_name = g_strdup (new_identity->active_credentials_cache_name);
+ }
+
+ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
+
+- if (should_switch_to_new_credentials_cache)
++ if (should_set_cache_as_default)
+ krb5_cc_switch (self->kerberos_context, copied_cache);
+ }
+ G_UNLOCK (identity_lock);
+
+ clear_alarms (new_identity);
+
+- if (comparison >= 0)
++ if (!should_set_cache_active)
+ return;
+
+ G_LOCK (identity_lock);
++ g_debug ("GoaKerberosIdentity: Setting identity %s to use updated credentials in credentials cache '%s'",
++ self->identifier, self->active_credentials_cache_name);
+ update_identifier (self, new_identity);
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+- reset_alarms (self);
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials have updated times, resetting alarms", self->identifier);
++ reset_alarms (self);
++ }
+ else
+- clear_alarms (self);
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials are now expired, clearing alarms", self->identifier);
++ clear_alarms (self);
++ }
++ }
++ else
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials do not have updated times, so not adjusting alarms", self->identifier);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
+ new_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+ }
+ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+ new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+ }
+ else
+ {
+ G_LOCK (identity_lock);
+--
+2.39.3
+
+
+From 829c24d0ecbac452c38d5217b8465457ea0cfb43 Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Thu, 19 Jan 2023 11:31:14 -0500
+Subject: [PATCH 16/22] goakerberosidentity: Fall back to stale credentials if
+ active credentials get destroyed
+
+At the moment, the identity service doesn't recognize when a credentials
+cache gets kdestroy'd explicitly by the user. It knows when a principal
+is purged from all credential caches, but it doesn't know when a
+specific cache is removed.
+
+This means it doesn't fall back properly to an older credential crash if
+the active cache gets destroyed.
+
+This commit addresses that problem by reachitecting things a bit.
+
+Previously, cache updates were processed by creating a new transient
+GoaKerberosIdentity and then merging it into an existing identity using
+goa_kerberos_identity_update. Using a full blown GoaKerberosIdentity
+object as a wrapper around a lone credentials cache is kind of a weird
+pattern and doesn't facillate processing cache removal, since we can't
+create a transient identity for what's not there.
+
+This commit exports goa_kerberos_identity_add_credentials_cache as
+public api as an alternative to the merging transient identity flow.
+
+It also also adds a goa_kerberos_identity_refresh function to be called
+after goa_kerberos_identity_add_credentials_cache is preformed for all
+new caches. It handles merging in the new credentials from the updated
+credentials caches, and also handles cache removal.
+
+A benefit of this new flow is much of the guts of
+goa_kerberos_identity_update have now been moved to verify_identity
+allowing for some code deduplication. Previously verify_identity was
+only called at object construction time, though, so this commit adds
+more locking to accomodate it get called while the identity is in use by
+other threads.
+---
+ src/goaidentity/goakerberosidentity.c | 706 +++++++++----------
+ src/goaidentity/goakerberosidentity.h | 8 +-
+ src/goaidentity/goakerberosidentitymanager.c | 132 ++--
+ 3 files changed, 419 insertions(+), 427 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index d046a8a4..b5cbcecd 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -619,65 +619,62 @@ set_expiration_time (GoaKerberosIdentity *self,
+ self->expiration_time = expiration_time;
+ queue_notify (self, &self->expiration_time_idle_id, "expiration-timestamp");
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ static void
+ examine_credentials (GoaKerberosIdentity *self,
+ krb5_creds *credentials,
+ krb5_timestamp *start_time,
+ krb5_timestamp *renewal_time,
+ krb5_timestamp *expiration_time,
+ gboolean *are_expired)
+ {
+ krb5_timestamp credentials_start_time;
+ krb5_timestamp credentials_end_time;
+ krb5_timestamp current_time;
+
+ G_LOCK (identity_lock);
+
+ if (credentials->times.starttime != 0)
+ credentials_start_time = credentials->times.starttime;
+ else
+ credentials_start_time = credentials->times.authtime;
+
+ *renewal_time = credentials->times.renew_till;
+
+ credentials_end_time = credentials->times.endtime;
+
+- if (self->start_time == 0)
+- *start_time = credentials_start_time;
+- else
+- *start_time = MIN (self->start_time, credentials_start_time);
+- *expiration_time = MAX (credentials->times.endtime, self->expiration_time);
++ *start_time = credentials_start_time;
++ *expiration_time = credentials->times.endtime;
+ G_UNLOCK (identity_lock);
+
+ current_time = get_current_time (self);
+
+ if (current_time < credentials_start_time ||
+ credentials_end_time <= current_time)
+ *are_expired = TRUE;
+ else
+ *are_expired = FALSE;
+ }
+
+ static VerificationLevel
+ verify_identity_in_credentials_cache (GoaKerberosIdentity *self,
+ char **preauth_identity_source,
+ krb5_ccache credentials_cache,
+ krb5_timestamp *start_time,
+ krb5_timestamp *renewal_time,
+ krb5_timestamp *expiration_time,
+ GError **error)
+ {
+ krb5_principal principal = NULL;
+ krb5_cc_cursor cursor;
+ krb5_creds credentials;
+ krb5_error_code error_code;
+ VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+
+ g_debug ("GoaKerberosIdentity: Verifying identity in credentials cache '%s'",
+ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+
+ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+@@ -771,146 +768,395 @@ verify_identity_in_credentials_cache (GoaKerberosIdentity *self,
+ out:
+ switch (verification_level)
+ {
+ case VERIFICATION_LEVEL_EXISTS:
+ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are out of date",
+ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+ break;
+
+ case VERIFICATION_LEVEL_SIGNED_IN:
+ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are valid",
+ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+ break;
+
+ case VERIFICATION_LEVEL_UNVERIFIED:
+ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are missing",
+ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+ break;
+
+ case VERIFICATION_LEVEL_ERROR:
+ default:
+ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' could not be validated",
+ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+ break;
+ }
+
+ if (principal != NULL)
+ krb5_free_principal (self->kerberos_context, principal);
+ return verification_level;
+ }
+
++static char *
++get_default_principal (GoaKerberosIdentity *self)
++{
++ int error_code;
++ krb5_ccache default_cache;
++ krb5_principal principal;
++ char *unparsed_principal, *principal_name;
++
++ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
++
++ if (error_code != 0)
++ return NULL;
++
++ /* Return NULL if the default cache doesn't pass basic sanity checks
++ */
++ error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &unparsed_principal);
++ krb5_free_principal (self->kerberos_context, principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ principal_name = g_strdup (unparsed_principal);
++ krb5_free_unparsed_name (self->kerberos_context, unparsed_principal);
++
++ krb5_cc_close (self->kerberos_context, default_cache);
++
++ return principal_name;
++}
++
++static char *
++get_default_cache_name (GoaKerberosIdentity *self)
++{
++ int error_code;
++ krb5_ccache default_cache;
++ krb5_principal principal;
++ char *default_cache_name;
++ char *principal_name;
++
++ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
++
++ if (error_code != 0)
++ return NULL;
++
++ /* Return NULL if the default cache doesn't pass basic sanity checks
++ */
++ error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &principal_name);
++ krb5_free_principal (self->kerberos_context, principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ krb5_free_unparsed_name (self->kerberos_context, principal_name);
++
++ default_cache_name = g_strdup (krb5_cc_get_name (self->kerberos_context, default_cache));
++ krb5_cc_close (self->kerberos_context, default_cache);
++
++ return default_cache_name;
++}
++
+ static VerificationLevel
+ verify_identity (GoaKerberosIdentity *self,
+ char **preauth_identity_source,
+ GError **error)
+ {
+ krb5_ccache credentials_cache;
++ g_autofree char *default_principal = NULL;
++ g_autofree char *default_credentials_cache_name = NULL;
++ gboolean is_default_principal;
++ gboolean is_default_credentials_cache;
++ gboolean should_switch_default_credentials_cache = FALSE;
++ gboolean time_changed = FALSE;
+ const char *name;
+- krb5_timestamp start_time = 0;
+- krb5_timestamp renewal_time = 0;
+- krb5_timestamp expiration_time = 0;
+- VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
++ krb5_timestamp best_start_time = 0;
++ krb5_timestamp best_renewal_time = 0;
++ krb5_timestamp best_expiration_time = 0;
++ g_autofree char *best_preauth_identity_source = NULL;
++ g_autofree char *best_credentials_cache_name = NULL;
++ VerificationLevel old_verification_level = VERIFICATION_LEVEL_UNVERIFIED;
++ VerificationLevel best_verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+ GHashTableIter iter;
+
+ if (self->active_credentials_cache_name != NULL)
+ {
++ G_LOCK (identity_lock);
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
++ G_UNLOCK (identity_lock);
+
+- verification_level = verify_identity_in_credentials_cache (self,
+- preauth_identity_source,
+- credentials_cache,
+- &start_time,
+- &renewal_time,
+- &expiration_time,
+- error);
+- if (verification_level == VERIFICATION_LEVEL_SIGNED_IN)
++ best_verification_level = verify_identity_in_credentials_cache (self,
++ &best_preauth_identity_source,
++ credentials_cache,
++ &best_start_time,
++ &best_renewal_time,
++ &best_expiration_time,
++ error);
++ G_LOCK (identity_lock);
++ best_credentials_cache_name = g_strdup (self->active_credentials_cache_name);
++ G_UNLOCK (identity_lock);
++
++ if (best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ goto out;
+
+- if (verification_level == VERIFICATION_LEVEL_UNVERIFIED)
++ if (best_verification_level == VERIFICATION_LEVEL_UNVERIFIED ||
++ best_verification_level == VERIFICATION_LEVEL_ERROR)
+ {
+- krb5_cc_close (self->kerberos_context, credentials_cache);
+- g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
+- g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ g_clear_pointer (&best_credentials_cache_name, g_free);
++
++ G_LOCK (identity_lock);
++ if (self->identifier != NULL)
++ {
++ krb5_cc_close (self->kerberos_context, credentials_cache);
++ g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ }
++ G_UNLOCK (identity_lock);
+ }
+ }
+
+- self->start_time = 0;
+- self->renewal_time = 0;
+- self->expiration_time = 0;
++ G_LOCK (identity_lock);
++ old_verification_level = self->cached_verification_level;
++ G_UNLOCK (identity_lock);
+
++ G_LOCK (identity_lock);
+ g_hash_table_iter_init (&iter, self->credentials_caches);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
+ {
+ krb5_timestamp new_start_time = 0;
+ krb5_timestamp new_renewal_time = 0;
+ krb5_timestamp new_expiration_time = 0;
++ g_autofree char *new_preauth_identity_source = NULL;
++ VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
++ gboolean has_better_credentials = FALSE;
+
+ if (g_strcmp0 (name, self->active_credentials_cache_name) == 0)
+ continue;
+
+- g_clear_pointer (preauth_identity_source, g_free);
++ G_UNLOCK (identity_lock);
++
++ if (preauth_identity_source != NULL)
++ g_clear_pointer (preauth_identity_source, g_free);
++
+ verification_level = verify_identity_in_credentials_cache (self,
+- preauth_identity_source,
++ &new_preauth_identity_source,
+ credentials_cache,
+ &new_start_time,
+ &new_renewal_time,
+ &new_expiration_time,
+ error);
+
+- if (verification_level == VERIFICATION_LEVEL_SIGNED_IN ||
+- self->active_credentials_cache_name == NULL)
++ if (verification_level == VERIFICATION_LEVEL_UNVERIFIED ||
++ verification_level == VERIFICATION_LEVEL_ERROR)
+ {
+- g_clear_pointer (&self->active_credentials_cache_name, g_free);
+- self->active_credentials_cache_name = g_strdup (name);
+- start_time = new_start_time;
+- renewal_time = new_renewal_time;
+- expiration_time = new_expiration_time;
+-
+- if (verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+- break;
++ G_LOCK (identity_lock);
++ if (self->identifier != NULL)
++ {
++ krb5_cc_close (self->kerberos_context, credentials_cache);
++ g_hash_table_iter_remove (&iter);
++ }
++
++ /* Note: The lock is held while iterating */
++ continue;
+ }
+- else if (verification_level == VERIFICATION_LEVEL_UNVERIFIED)
++
++ if (best_verification_level < verification_level)
++ has_better_credentials = TRUE;
++ else if (best_verification_level > verification_level)
++ has_better_credentials = FALSE;
++ else if (best_expiration_time < new_expiration_time)
++ has_better_credentials = TRUE;
++ else if (best_expiration_time > new_expiration_time)
++ has_better_credentials = FALSE;
++ else if (best_start_time > new_start_time)
++ has_better_credentials = TRUE;
++ else if (best_start_time > new_start_time)
++ has_better_credentials = FALSE;
++ else if (best_renewal_time < new_renewal_time)
++ has_better_credentials = TRUE;
++ else if (best_renewal_time > new_renewal_time)
++ has_better_credentials = FALSE;
++ else
++ has_better_credentials = FALSE;
++
++ if (has_better_credentials)
+ {
+- krb5_cc_close (self->kerberos_context, credentials_cache);
+- g_hash_table_iter_remove (&iter);
++ best_verification_level = verification_level;
++ best_start_time = new_start_time;
++ best_renewal_time = new_renewal_time;
++ best_expiration_time = new_expiration_time;
++
++ g_clear_pointer (&best_preauth_identity_source, g_free);
++ best_preauth_identity_source = g_steal_pointer (&new_preauth_identity_source);
++
++ g_clear_pointer (&best_credentials_cache_name, g_free);
++ best_credentials_cache_name = g_strdup (name);
+ }
++
++ G_LOCK (identity_lock);
++ }
++ G_UNLOCK (identity_lock);
++
++ if (best_credentials_cache_name == NULL)
++ {
++ g_hash_table_iter_init (&iter, self->credentials_caches);
++ if (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
++ best_credentials_cache_name = g_strdup (name);
+ }
+
+ out:
++
+ G_LOCK (identity_lock);
+- set_start_time (self, start_time);
+- set_renewal_time (self, renewal_time);
+- set_expiration_time (self, expiration_time);
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ self->active_credentials_cache_name = g_steal_pointer (&best_credentials_cache_name);
+ G_UNLOCK (identity_lock);
+
+- return verification_level;
++ *preauth_identity_source = g_steal_pointer (&best_preauth_identity_source);
++
++ if (best_verification_level > VERIFICATION_LEVEL_UNVERIFIED)
++ {
++ G_LOCK (identity_lock);
++ time_changed |= set_start_time (self, best_start_time);
++ time_changed |= set_renewal_time (self, best_renewal_time);
++ time_changed |= set_expiration_time (self, best_expiration_time);
++ G_UNLOCK (identity_lock);
++
++ if (time_changed)
++ {
++ if (best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials have updated times, resetting alarms", self->identifier);
++ reset_alarms (self);
++ }
++ else
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials are now expired, clearing alarms", self->identifier);
++ clear_alarms (self);
++ }
++ }
++ else
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials do not have updated times, so not adjusting alarms", self->identifier);
++ }
++ }
++ else
++ {
++ g_debug ("GoaKerberosIdentity: identity is unverified, clearing alarms");
++ clear_alarms (self);
++ }
++
++ if (best_verification_level != old_verification_level)
++ {
++ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
++ best_verification_level == VERIFICATION_LEVEL_EXISTS)
++ {
++ G_LOCK (identity_lock);
++ self->cached_verification_level = best_verification_level;
++ G_UNLOCK (identity_lock);
++
++ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
++ }
++ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
++ best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
++ {
++ G_LOCK (identity_lock);
++ self->cached_verification_level = best_verification_level;
++ G_UNLOCK (identity_lock);
++
++ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
++ }
++ else
++ {
++ G_LOCK (identity_lock);
++ self->cached_verification_level = best_verification_level;
++ G_UNLOCK (identity_lock);
++ }
++ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
++ }
++
++ default_principal = get_default_principal (self);
++ is_default_principal = g_strcmp0 (default_principal, self->identifier) == 0;
++
++ default_credentials_cache_name = get_default_cache_name (self);
++ is_default_credentials_cache = g_strcmp0 (default_credentials_cache_name, self->active_credentials_cache_name) == 0;
++
++ if (self->active_credentials_cache_name == NULL)
++ {
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache because identity %s has no active credentials cache to switch to", self->identifier);
++ should_switch_default_credentials_cache = FALSE;
++ }
++ else if (self->identifier == NULL)
++ {
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache to '%s' because it is not yet initialized", self->active_credentials_cache_name);
++ should_switch_default_credentials_cache = FALSE;
++ }
++ else if (default_principal == NULL)
++ {
++ g_debug ("GoaKerberosIdentity: Switching default credentials cache to '%s' (identity %s) because there is currently no default", self->active_credentials_cache_name, self->identifier);
++ should_switch_default_credentials_cache = TRUE;
++ }
++ else if (!is_default_principal)
++ {
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache because identity %s is not the default identity", self->identifier);
++ should_switch_default_credentials_cache = FALSE;
++ }
++ else if (!is_default_credentials_cache)
++ {
++ g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' because identity %s is the default, and that credentials cache is supposed to be the active cache for that identity",
++ default_credentials_cache_name, self->active_credentials_cache_name, self->identifier);
++ should_switch_default_credentials_cache = TRUE;
++ }
++ else
++ {
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache to '%s' for identity %s because it's already the default", self->active_credentials_cache_name, self->identifier);
++ should_switch_default_credentials_cache = FALSE;
++ }
++
++ if (should_switch_default_credentials_cache)
++ {
++ G_LOCK (identity_lock);
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++ krb5_cc_switch (self->kerberos_context, credentials_cache);
++ G_UNLOCK (identity_lock);
++ }
++
++ return best_verification_level;
+ }
+
+ static gboolean
+ goa_kerberos_identity_is_signed_in (GoaIdentity *identity)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (identity);
+ gboolean is_signed_in = FALSE;
+
+ G_LOCK (identity_lock);
+ if (self->cached_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ is_signed_in = TRUE;
+ G_UNLOCK (identity_lock);
+
+ return is_signed_in;
+ }
+
+ static void
+ identity_interface_init (GoaIdentityInterface *interface)
+ {
+ interface->get_identifier = goa_kerberos_identity_get_identifier;
+ interface->is_signed_in = goa_kerberos_identity_is_signed_in;
+ }
+
+ static void
+ on_expiration_alarm_fired (GoaAlarm *alarm,
+ GoaKerberosIdentity *self)
+ {
+ g_return_if_fail (GOA_IS_ALARM (alarm));
+ g_return_if_fail (GOA_IS_KERBEROS_IDENTITY (self));
+
+@@ -1059,60 +1305,62 @@ reset_alarms (GoaKerberosIdentity *self)
+ GDateTime *expiring_time = NULL;
+ GDateTime *latest_possible_renewal_time = NULL;
+ GDateTime *renewal_time = NULL;
+
+ G_LOCK (identity_lock);
+ start_time = g_date_time_new_from_unix_local (self->start_time);
+ if (self->renewal_time != 0)
+ latest_possible_renewal_time = g_date_time_new_from_unix_local (self->renewal_time);
+ expiration_time = g_date_time_new_from_unix_local (self->expiration_time);
+ G_UNLOCK (identity_lock);
+
+ /* Let the user reauthenticate 10 min before expiration */
+ expiring_time = g_date_time_add_minutes (expiration_time, -10);
+
+ if (latest_possible_renewal_time != NULL)
+ {
+ GTimeSpan lifespan;
+
+ lifespan = g_date_time_difference (expiration_time, start_time);
+
+ /* Try to quietly auto-renew halfway through so in ideal configurations
+ * the ticket is never more than halfway to unrenewable
+ */
+ renewal_time = g_date_time_add (start_time, lifespan / 2);
+ }
+
+ disconnect_alarm_signals (self);
+
+ if (renewal_time != NULL)
+ reset_alarm (self, &self->renewal_alarm, renewal_time);
++ else if (self->renewal_alarm != NULL)
++ clear_alarm_and_unref_on_idle (self, &self->renewal_alarm);
+
+ reset_alarm (self, &self->expiring_alarm, expiring_time);
+ reset_alarm (self, &self->expiration_alarm, expiration_time);
+
+ g_clear_pointer (&expiring_time, g_date_time_unref);
+ g_clear_pointer (&renewal_time, g_date_time_unref);
+ g_clear_pointer (&expiration_time, g_date_time_unref);
+ g_clear_pointer (&latest_possible_renewal_time, g_date_time_unref);
+ g_clear_pointer (&start_time, g_date_time_unref);
+
+ connect_alarm_signals (self);
+ }
+
+ static void
+ clear_alarms (GoaKerberosIdentity *self)
+ {
+ disconnect_alarm_signals (self);
+ clear_alarm_and_unref_on_idle (self, &self->renewal_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiring_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiration_alarm);
+ }
+
+ static gboolean
+ goa_kerberos_identity_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (initable);
+ GError *verification_error;
+
+@@ -1185,114 +1433,137 @@ on_kerberos_inquiry (krb5_context kerberos_context,
+ GoaIdentityInquiry *inquiry;
+ krb5_error_code error_code = 0;
+
+ if (number_of_prompts == 0)
+ goto out;
+
+ inquiry = goa_kerberos_identity_inquiry_new (operation->identity,
+ name,
+ banner,
+ prompts,
+ number_of_prompts);
+
+ operation->inquiry_func (inquiry,
+ operation->cancellable,
+ operation->inquiry_data);
+
+ if (goa_identity_inquiry_is_failed (inquiry))
+ error_code = KRB5_LIBOS_CANTREADPWD;
+ else if (!goa_identity_inquiry_is_complete (inquiry))
+ g_cancellable_cancel (operation->cancellable);
+
+ if (g_cancellable_is_cancelled (operation->cancellable))
+ error_code = KRB5_LIBOS_PWDINTR;
+
+ g_object_unref (inquiry);
+
+ out:
+ return error_code;
+ }
+
+-static void
++gboolean
++goa_kerberos_identity_has_credentials_cache (GoaKerberosIdentity *self,
++ krb5_ccache credentials_cache)
++{
++ const char *cache_name;
++
++ cache_name = krb5_cc_get_name (self->kerberos_context, credentials_cache);
++
++ return g_hash_table_contains (self->credentials_caches, cache_name);
++}
++
++void
+ goa_kerberos_identity_add_credentials_cache (GoaKerberosIdentity *self,
+ krb5_ccache credentials_cache)
+ {
+ const char *cache_name;
++ krb5_ccache copied_cache;
+
+ cache_name = krb5_cc_get_name (self->kerberos_context, credentials_cache);
+
+ if (g_hash_table_contains (self->credentials_caches, cache_name))
+ {
+ krb5_ccache old_credentials_cache;
+
++ g_debug ("GoaKerberosIdentity: Updating credentials in credentials cache '%s' for identity %s ", cache_name, self->identifier);
++
+ old_credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches, cache_name);
+
+ krb5_cc_close (self->kerberos_context, old_credentials_cache);
+ }
++ else
++ {
++ if (self->identifier != NULL)
++ g_debug ("GoaKerberosIdentity: Associating identity %s with new credentials cache '%s'", self->identifier, cache_name);
++ else
++ g_debug ("GoaKerberosIdentity: Associating new identity with new credentials cache '%s'", cache_name);
++ }
+
+- g_hash_table_replace (self->credentials_caches, g_strdup (cache_name), credentials_cache);
++ krb5_cc_dup (self->kerberos_context, credentials_cache, &copied_cache);
++ g_hash_table_replace (self->credentials_caches, g_strdup (cache_name), copied_cache);
+
+ if (self->active_credentials_cache_name == NULL)
+ {
+ self->active_credentials_cache_name = g_strdup (cache_name);
+ }
+ }
+
+ static gboolean
+ create_credentials_cache (GoaKerberosIdentity *self,
+ GError **error)
+ {
+ krb5_ccache default_cache;
+ krb5_ccache new_cache;
+ const char *cache_type;
+ krb5_error_code error_code;
+
+ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
+
+ if (error_code == 0)
+ {
+ cache_type = krb5_cc_get_type (self->kerberos_context, default_cache);
+ error_code = krb5_cc_new_unique (self->kerberos_context, cache_type, NULL, &new_cache);
+ }
+
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ALLOCATING_CREDENTIALS,
+ error_code,
+ _("Could not create credential cache: "));
+
+ return FALSE;
+ }
+
+ goa_kerberos_identity_add_credentials_cache (self, new_cache);
++ krb5_cc_close (self->kerberos_context, new_cache);
+
+ return TRUE;
+ }
+
+ static gboolean
+ goa_kerberos_identity_update_credentials (GoaKerberosIdentity *self,
+ krb5_principal principal,
+ krb5_creds *new_credentials,
+ GError **error)
+ {
+ krb5_error_code error_code;
+ krb5_ccache credentials_cache;
+
+
+ if (self->active_credentials_cache_name == NULL)
+ {
+ if (!create_credentials_cache (self, error))
+ {
+ krb5_free_cred_contents (self->kerberos_context, new_credentials);
+ goto out;
+ }
+ }
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
+
+ error_code = krb5_cc_initialize (self->kerberos_context, credentials_cache, principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+@@ -1348,81 +1619,78 @@ sign_in_operation_new (GoaKerberosIdentity *identity,
+ }
+
+ static void
+ sign_in_operation_free (SignInOperation *operation)
+ {
+ g_object_unref (operation->identity);
+ g_object_unref (operation->cancellable);
+
+ g_slice_free (SignInOperation, operation);
+ }
+
+ gboolean
+ goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ const char *principal_name,
+ gconstpointer initial_password,
+ const char *preauth_source,
+ GoaIdentitySignInFlags flags,
+ GoaIdentityInquiryFunc inquiry_func,
+ gpointer inquiry_data,
+ GDestroyNotify destroy_notify,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ SignInOperation *operation;
+ krb5_principal principal;
+ krb5_error_code error_code;
+ krb5_creds new_credentials;
+ krb5_get_init_creds_opt *options;
+ krb5_deltat start_time;
+ char *service_name;
+- gboolean signed_in;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ error_code = krb5_get_init_creds_opt_alloc (self->kerberos_context, &options);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ALLOCATING_CREDENTIALS,
+ error_code,
+ "%s",
+ ""); /* Silence -Wformat-zero-length */
+ if (destroy_notify)
+ destroy_notify (inquiry_data);
+ return FALSE;
+ }
+
+- signed_in = FALSE;
+-
+ operation = sign_in_operation_new (self,
+ inquiry_func,
+ inquiry_data,
+ destroy_notify,
+ cancellable);
+
+ if (g_strcmp0 (self->identifier, principal_name) != 0)
+ {
+ g_free (self->identifier);
+ self->identifier = g_strdup (principal_name);
+ }
+
+ error_code = krb5_parse_name (self->kerberos_context, principal_name, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_PARSING_IDENTIFIER,
+ error_code,
+ "%s",
+ ""); /* Silence -Wformat-zero-length */
+ if (destroy_notify)
+ destroy_notify (inquiry_data);
+ return FALSE;
+ }
+
+ if ((flags & GOA_IDENTITY_SIGN_IN_FLAGS_DISALLOW_FORWARDING) == 0)
+ krb5_get_init_creds_opt_set_forwardable (options, TRUE);
+
+ if ((flags & GOA_IDENTITY_SIGN_IN_FLAGS_DISALLOW_PROXYING) == 0)
+@@ -1468,426 +1736,128 @@ goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_AUTHENTICATION_FAILED,
+ error_code,
+ "%s",
+ ""); /* Silence -Wformat-zero-length */
+ if (destroy_notify)
+ destroy_notify (inquiry_data);
+ sign_in_operation_free (operation);
+
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+
+ if (destroy_notify)
+ destroy_notify (inquiry_data);
+ sign_in_operation_free (operation);
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+ krb5_free_principal (self->kerberos_context, principal);
+
+- g_debug ("GoaKerberosIdentity: identity signed in");
+- signed_in = TRUE;
+ done:
+
+- return signed_in;
+-}
+-
+-static void
+-update_identifier (GoaKerberosIdentity *self, GoaKerberosIdentity *new_identity)
+-{
+- char *new_identifier;
++ goa_kerberos_identity_refresh (self);
+
+- new_identifier = get_identifier (new_identity, NULL);
+- if (g_strcmp0 (self->identifier, new_identifier) != 0 && new_identifier != NULL)
+- {
+- g_free (self->identifier);
+- self->identifier = new_identifier;
+- queue_notify (self, &self->identifier_idle_id, "identifier");
+- }
+- else
++ if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+ {
+- g_free (new_identifier);
++ g_debug ("GoaKerberosIdentity: Identity '%s' could not be signed in", principal_name);
++ return FALSE;
+ }
+-}
+-
+-static int
+-goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+- GoaKerberosIdentity *new_identity)
+-{
+- if (self->cached_verification_level < new_identity->cached_verification_level)
+- return -100;
+-
+- if (self->cached_verification_level > new_identity->cached_verification_level)
+- return 100;
+-
+- if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+- return 50;
+-
+- if (self->expiration_time < new_identity->expiration_time)
+- return -10;
+-
+- if (self->expiration_time > new_identity->expiration_time)
+- return 10;
+-
+- if (self->start_time > new_identity->start_time)
+- return -5;
+-
+- if (self->start_time < new_identity->start_time)
+- return 5;
+-
+- if (self->renewal_time < new_identity->renewal_time)
+- return -1;
+-
+- if (self->renewal_time > new_identity->renewal_time)
+- return 1;
+-
+- return 0;
+-}
+-
+-static char *
+-get_default_cache_name (GoaKerberosIdentity *self)
+-{
+- int error_code;
+- krb5_ccache default_cache;
+- krb5_principal principal;
+- char *default_cache_name;
+- char *principal_name;
+-
+- error_code = krb5_cc_default (self->kerberos_context, &default_cache);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- /* Return NULL if the default cache doesn't pass basic sanity checks
+- */
+- error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &principal_name);
+- krb5_free_principal (self->kerberos_context, principal);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- krb5_free_unparsed_name (self->kerberos_context, principal_name);
+-
+- default_cache_name = g_strdup (krb5_cc_get_name (self->kerberos_context, default_cache));
+- krb5_cc_close (self->kerberos_context, default_cache);
+-
+- return default_cache_name;
+-}
+-
+-static char *
+-get_default_principal (GoaKerberosIdentity *self)
+-{
+- int error_code;
+- krb5_ccache default_cache;
+- krb5_principal principal;
+- char *unparsed_principal, *principal_name;
+-
+- error_code = krb5_cc_default (self->kerberos_context, &default_cache);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- /* Return NULL if the default cache doesn't pass basic sanity checks
+- */
+- error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &unparsed_principal);
+- krb5_free_principal (self->kerberos_context, principal);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- principal_name = g_strdup (unparsed_principal);
+- krb5_free_unparsed_name (self->kerberos_context, unparsed_principal);
+-
+- krb5_cc_close (self->kerberos_context, default_cache);
+
+- return principal_name;
++ g_debug ("GoaKerberosIdentity: Identity '%s' signed in", principal_name);
++ return TRUE;
+ }
+
+ void
+-goa_kerberos_identity_update (GoaKerberosIdentity *self,
+- GoaKerberosIdentity *new_identity)
++goa_kerberos_identity_refresh (GoaKerberosIdentity *self)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+- gboolean should_set_cache_active = FALSE;
+- gboolean time_changed = FALSE;
+- char *preauth_identity_source = NULL;
+- g_autofree char *default_principal = NULL;
+- int comparison;
++ g_autofree char *preauth_identity_source = NULL;
++ g_autoptr (GError) error = NULL;
+
+- G_LOCK (identity_lock);
+-
+- g_debug ("GoaKerberosIdentity: Evaluating updated credentials for identity %s "
+- "(old credentials cache name: %s, new credentials cache name: %s)",
++ g_debug ("GoaKerberosIdentity: Refreshing identity %s (active credentials cache: %s)",
+ self->identifier,
+- self->active_credentials_cache_name,
+- new_identity->active_credentials_cache_name);
+- old_verification_level = self->cached_verification_level;
+- new_verification_level = new_identity->cached_verification_level;
+-
+- default_principal = get_default_principal (self);
+- comparison = goa_kerberos_identity_compare (self, new_identity);
+-
+- if (new_identity->active_credentials_cache_name != NULL)
+- {
+- g_autofree char *default_cache_name = NULL;
+- krb5_ccache credentials_cache;
+- krb5_ccache copied_cache;
+- gboolean should_set_cache_as_default = FALSE;
+- gboolean is_default_principal = FALSE, is_default_cache = FALSE;
+- gboolean cache_already_active = FALSE;
+-
+- is_default_principal = g_strcmp0 (default_principal, self->identifier) == 0;
+-
+- default_cache_name = get_default_cache_name (self);
+- is_default_cache = g_strcmp0 (default_cache_name, self->active_credentials_cache_name) == 0;
+- cache_already_active = g_strcmp0 (self->active_credentials_cache_name, new_identity->active_credentials_cache_name) == 0;
+-
+- g_debug ("GoaKerberosIdentity: Default credentials cache is '%s' (is %sus, is %sactive)", default_cache_name, is_default_cache? "" : "not ", cache_already_active? "" : "not ");
+-
+- if (default_principal == NULL)
+- {
+- should_set_cache_as_default = TRUE;
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Setting default credentials cache to '%s' (principal %s) "
+- "because there is no active default",
+- new_identity->active_credentials_cache_name,
+- self->identifier);
+- }
+- else if (!is_default_principal)
+- {
+- g_debug ("GoaKerberosIdentity: Not switching default credentials cache from '%s' to '%s' (principal %s) "
+- "because identity is currently not default (credentials already active? %s)",
+- default_cache_name,
+- new_identity->active_credentials_cache_name,
+- self->identifier,
+- cache_already_active? "yes" : "no");
+- should_set_cache_as_default = FALSE;
+-
+- if (comparison < 0)
+- {
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Switching identity %s from credentials cache '%s' to credentials cache '%s' "
+- "because it has better credentials",
+- self->identifier,
+- self->active_credentials_cache_name,
+- new_identity->active_credentials_cache_name);
+- }
+- else if (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+- {
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Switching identity %s from credentials cache '%s' to "
+- "'%s' because it is newer and is otherwise just as good",
+- self->identifier,
+- self->active_credentials_cache_name,
+- new_identity->active_credentials_cache_name);
+- }
+- else
+- {
+- should_set_cache_active = FALSE;
+-
+- g_debug ("GoaKerberosIdentity: Not switching identity %s from credentials cache '%s' to '%s' "
+- "because it has less good credentials",
+- self->identifier,
+- default_cache_name,
+- new_identity->active_credentials_cache_name);
+- }
+- }
+- else if (cache_already_active)
+- {
+- if (is_default_cache)
+- {
+- should_set_cache_as_default = FALSE;
+- should_set_cache_active = FALSE;
+-
+- g_debug ("GoaKerberosIdentity: Not setting default credentials cache to '%s' "
+- "because cache is already active for identity %s and identity is default",
+- new_identity->active_credentials_cache_name,
+- self->identifier);
+- }
+- else
+- {
+- should_set_cache_as_default = TRUE;
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
+- "because identity %s is default and cache is supposed to be active already but isn't",
+- default_cache_name,
+- new_identity->active_credentials_cache_name,
+- self->identifier);
+- }
+- }
+- else
+- {
+- if (comparison < 0)
+- {
+- should_set_cache_as_default = TRUE;
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
+- "because identity %s is default and the cache has better credentials than those "
+- "in '%s'",
+- default_cache_name,
+- new_identity->active_credentials_cache_name,
+- self->identifier,
+- self->active_credentials_cache_name);
+- }
+- else if (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+- {
+- should_set_cache_as_default = TRUE;
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
+- "because identity %s is default, and the cache has newer, and otherwise "
+- "just as good credentials as those in '%s'",
+- default_cache_name,
+- new_identity->active_credentials_cache_name,
+- self->identifier,
+- self->active_credentials_cache_name);
+- }
+- else
+- {
+- should_set_cache_as_default = FALSE;
+- should_set_cache_active = FALSE;
+-
+- g_debug ("GoaKerberosIdentity: Not switching default credentials cache from '%s' to '%s' "
+- "because identity %s is default but newer credentials aren't as good as those in '%s'",
+- default_cache_name,
+- new_identity->active_credentials_cache_name,
+- self->identifier,
+- self->active_credentials_cache_name);
+- }
+- }
+- credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+- new_identity->active_credentials_cache_name);
+- krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+-
+- if (should_set_cache_active)
+- {
+- g_clear_pointer (&self->active_credentials_cache_name, g_free);
+- self->active_credentials_cache_name = g_strdup (new_identity->active_credentials_cache_name);
+- }
++ self->active_credentials_cache_name);
+
+- goa_kerberos_identity_add_credentials_cache (self, copied_cache);
+-
+- if (should_set_cache_as_default)
+- krb5_cc_switch (self->kerberos_context, copied_cache);
+- }
++ G_LOCK (identity_lock);
++ old_verification_level = self->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+- clear_alarms (new_identity);
+-
+- if (!should_set_cache_active)
+- return;
++ new_verification_level = verify_identity (self, &preauth_identity_source, &error);
+
+ G_LOCK (identity_lock);
+- g_debug ("GoaKerberosIdentity: Setting identity %s to use updated credentials in credentials cache '%s'",
+- self->identifier, self->active_credentials_cache_name);
+- update_identifier (self, new_identity);
+- time_changed |= set_start_time (self, new_identity->start_time);
+- time_changed |= set_renewal_time (self, new_identity->renewal_time);
+- time_changed |= set_expiration_time (self, new_identity->expiration_time);
+- G_UNLOCK (identity_lock);
+-
+- if (time_changed)
+- {
+- if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+- {
+- g_debug ("GoaKerberosIdentity: identity %s credentials have updated times, resetting alarms", self->identifier);
+- reset_alarms (self);
+- }
+- else
+- {
+- g_debug ("GoaKerberosIdentity: identity %s credentials are now expired, clearing alarms", self->identifier);
+- clear_alarms (self);
+- }
+- }
+- else
++ if (g_strcmp0 (self->preauth_identity_source, preauth_identity_source) != 0)
+ {
+- g_debug ("GoaKerberosIdentity: identity %s credentials do not have updated times, so not adjusting alarms", self->identifier);
++ g_free (self->preauth_identity_source);
++ self->preauth_identity_source = g_steal_pointer (&preauth_identity_source);
+ }
+-
+- G_LOCK (identity_lock);
+- g_free (self->preauth_identity_source);
+- self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+- if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
++ if ((old_verification_level == VERIFICATION_LEVEL_SIGNED_IN) &&
+ new_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+ }
+ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+ new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+ }
+ else
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+ }
++ G_LOCK (identity_lock);
+ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
++ G_UNLOCK (identity_lock);
+ }
+ }
+
+ gboolean
+ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+ {
+ krb5_error_code error_code = 0;
+ krb5_principal principal;
+ krb5_creds new_credentials;
+ krb5_ccache credentials_cache;
+ gboolean renewed = FALSE;
+ char *name = NULL;
+
+ if (self->active_credentials_cache_name == NULL)
+ {
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ _("Not signed in"));
+ goto out;
+ }
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
+ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+@@ -1945,47 +1915,45 @@ goa_kerberos_identity_erase (GoaKerberosIdentity *self, GError **error)
+ g_debug ("GoaKerberosIdentity: Destroying active credentials cache %s", self->active_credentials_cache_name);
+ error_code = krb5_cc_destroy (self->kerberos_context, credentials_cache);
+ g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
+
+ g_clear_pointer (&self->active_credentials_cache_name, g_free);
+
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_REMOVING_CREDENTIALS,
+ error_code, _("Could not erase identity: "));
+ }
+ }
+
+ g_hash_table_iter_init (&iter, self->credentials_caches);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
+ {
+ g_debug ("GoaKerberosIdentity: Destroying inactive credentials cache %s", name);
+ krb5_cc_destroy (self->kerberos_context, credentials_cache);
+ }
+ g_hash_table_remove_all (self->credentials_caches);
+
+ return error_code == 0;
+ }
+
+ GoaIdentity *
+ goa_kerberos_identity_new (krb5_context context, krb5_ccache cache, GError **error)
+ {
+ GoaKerberosIdentity *self;
+- krb5_ccache copied_cache;
+
+ self = GOA_KERBEROS_IDENTITY (g_object_new (GOA_TYPE_KERBEROS_IDENTITY, NULL));
+ self->kerberos_context = context;
+
+- krb5_cc_dup (self->kerberos_context, cache, &copied_cache);
+- goa_kerberos_identity_add_credentials_cache (self, copied_cache);
++ goa_kerberos_identity_add_credentials_cache (self, cache);
+
+ error = NULL;
+ if (!g_initable_init (G_INITABLE (self), NULL, error))
+ {
+ g_object_unref (self);
+ return NULL;
+ }
+
+ return GOA_IDENTITY (self);
+ }
+diff --git a/src/goaidentity/goakerberosidentity.h b/src/goaidentity/goakerberosidentity.h
+index de0752cd..70cd4e3f 100644
+--- a/src/goaidentity/goakerberosidentity.h
++++ b/src/goaidentity/goakerberosidentity.h
+@@ -14,54 +14,58 @@
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see .
+ */
+
+ #ifndef __GOA_KERBEROS_IDENTITY_H__
+ #define __GOA_KERBEROS_IDENTITY_H__
+
+ #include
+ #include
+
+ #include
+ #include "goaidentityinquiry.h"
+
+ G_BEGIN_DECLS
+
+ #define GOA_TYPE_KERBEROS_IDENTITY (goa_kerberos_identity_get_type ())
+ G_DECLARE_FINAL_TYPE (GoaKerberosIdentity, goa_kerberos_identity, GOA, KERBEROS_IDENTITY, GObject);
+
+ typedef enum
+ {
+ GOA_KERBEROS_IDENTITY_DESCRIPTION_REALM,
+ GOA_KERBEROS_IDENTITY_DESCRIPTION_USERNAME_AND_REALM,
+ GOA_KERBEROS_IDENTITY_DESCRIPTION_USERNAME_ROLE_AND_REALM
+ } GoaKerberosIdentityDescriptionLevel;
+
+ GoaIdentity *goa_kerberos_identity_new (krb5_context kerberos_context,
+ krb5_ccache cache,
+ GError **error);
+
++gboolean goa_kerberos_identity_has_credentials_cache (GoaKerberosIdentity *self,
++ krb5_ccache credentials_cache);
++void goa_kerberos_identity_add_credentials_cache (GoaKerberosIdentity *self,
++ krb5_ccache cache);
++
+ gboolean goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ const char *principal_name,
+ gconstpointer initial_password,
+ const char *preauth_source,
+ GoaIdentitySignInFlags flags,
+ GoaIdentityInquiryFunc inquiry_func,
+ gpointer inquiry_data,
+ GDestroyNotify destroy_notify,
+ GCancellable *cancellable,
+ GError **error);
+-void goa_kerberos_identity_update (GoaKerberosIdentity *identity,
+- GoaKerberosIdentity *new_identity);
++void goa_kerberos_identity_refresh (GoaKerberosIdentity *identity);
+ gboolean goa_kerberos_identity_renew (GoaKerberosIdentity *self,
+ GError **error);
+ gboolean goa_kerberos_identity_erase (GoaKerberosIdentity *self,
+ GError **error);
+
+ char *goa_kerberos_identity_get_principal_name (GoaKerberosIdentity *self);
+ char *goa_kerberos_identity_get_realm_name (GoaKerberosIdentity *self);
+ char *goa_kerberos_identity_get_preauthentication_source (GoaKerberosIdentity *self);
+
+ G_END_DECLS
+
+ #endif /* __GOA_KERBEROS_IDENTITY_H__ */
+diff --git a/src/goaidentity/goakerberosidentitymanager.c b/src/goaidentity/goakerberosidentitymanager.c
+index d4ff2de4..7785b891 100644
+--- a/src/goaidentity/goakerberosidentitymanager.c
++++ b/src/goaidentity/goakerberosidentitymanager.c
+@@ -471,207 +471,227 @@ static void
+ drop_stale_identities (GoaKerberosIdentityManager *self,
+ Operation *operation,
+ GHashTable *known_identities)
+ {
+ GList *stale_identity_ids;
+ GList *node;
+
+ stale_identity_ids = g_hash_table_get_keys (self->identities);
+
+ node = stale_identity_ids;
+ while (node != NULL)
+ {
+ GoaIdentity *identity;
+ const char *identifier = node->data;
+
+ identity = g_hash_table_lookup (known_identities, identifier);
+ if (identity == NULL)
+ {
+ identity = g_hash_table_lookup (self->identities, identifier);
+
+ if (identity != NULL)
+ {
+ remove_identity (self, operation, identity);
+ }
+ }
+ node = node->next;
+ }
+ g_list_free (stale_identity_ids);
+ }
+
+-static void
+-update_identity (GoaKerberosIdentityManager *self,
+- Operation *operation,
+- GoaIdentity *identity,
+- GoaIdentity *new_identity)
+-{
+-
+- goa_kerberos_identity_update (GOA_KERBEROS_IDENTITY (identity),
+- GOA_KERBEROS_IDENTITY (new_identity));
+-
+- if (goa_identity_is_signed_in (identity))
+- {
+- IdentitySignalWork *work;
+-
+- /* if it's not expired, send out a refresh signal */
+- g_debug ("GoaKerberosIdentityManager: identity '%s' refreshed",
+- goa_identity_get_identifier (identity));
+-
+- work = identity_signal_work_new (self, identity);
+- goa_kerberos_identify_manager_send_to_context (operation->context,
+- (GSourceFunc)
+- do_identity_signal_refreshed_work,
+- work,
+- (GDestroyNotify)
+- identity_signal_work_free);
+- }
+-}
+-
+ static void
+ add_identity (GoaKerberosIdentityManager *self,
+ Operation *operation,
+ GoaIdentity *identity,
+ const char *identifier)
+ {
+ IdentitySignalWork *work;
+
+ g_hash_table_replace (self->identities, g_strdup (identifier), g_object_ref (identity));
+
+ if (!goa_identity_is_signed_in (identity))
+ g_hash_table_replace (self->expired_identities, g_strdup (identifier), identity);
+
+ work = identity_signal_work_new (self, identity);
+ goa_kerberos_identify_manager_send_to_context (operation->context,
+ (GSourceFunc)
+ do_identity_signal_added_work,
+ work,
+ (GDestroyNotify) identity_signal_work_free);
+ }
+
++static char *
++get_principal_from_cache (GoaKerberosIdentityManager *self,
++ krb5_ccache cache)
++{
++ int error_code;
++ krb5_principal principal;
++ char *unparsed_name;
++ char *principal_name;
++
++ error_code = krb5_cc_get_principal (self->kerberos_context, cache, &principal);
++
++ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &unparsed_name);
++ krb5_free_principal (self->kerberos_context, principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ principal_name = g_strdup (unparsed_name);
++
++ krb5_free_unparsed_name (self->kerberos_context, unparsed_name);
++
++ return principal_name;
++}
++
+ static void
+-refresh_identity (GoaKerberosIdentityManager *self,
+- Operation *operation,
+- GHashTable *refreshed_identities,
+- GoaIdentity *identity)
++import_credentials_cache (GoaKerberosIdentityManager *self,
++ Operation *operation,
++ GHashTable *refreshed_identities,
++ krb5_ccache cache)
+ {
+- const char *identifier;
+- GoaIdentity *old_identity;
++ g_autofree char *identifier = NULL;
++ GoaIdentity *identity = NULL;
+
+- identifier = goa_identity_get_identifier (identity);
++ identifier = get_principal_from_cache (self, cache);
+
+ if (identifier == NULL)
+ return;
+
+- old_identity = g_hash_table_lookup (self->identities, identifier);
++ identity = g_hash_table_lookup (self->identities, identifier);
+
+- if (old_identity != NULL)
++ if (identity == NULL)
+ {
+- g_debug ("GoaKerberosIdentityManager: refreshing identity '%s'", identifier);
+- update_identity (self, operation, old_identity, identity);
++ g_autoptr(GError) error = NULL;
+
+- /* Reuse the old identity, so any object data set up on it doesn't
+- * disappear spurriously
+- */
+- identifier = goa_identity_get_identifier (old_identity);
+- identity = old_identity;
++ g_debug ("GoaKerberosIdentityManager: Adding new identity '%s'", identifier);
++ identity = goa_kerberos_identity_new (self->kerberos_context, cache, &error);
++
++ if (error != NULL)
++ {
++ g_debug ("GoaKerberosIdentityManager: Could not track identity %s: %s",
++ identifier, error->message);
++ return;
++ }
++
++ add_identity (self, operation, identity, identifier);
+ }
+ else
+ {
+- g_debug ("GoaKerberosIdentityManager: adding new identity '%s'", identifier);
+- add_identity (self, operation, identity, identifier);
++ if (!goa_kerberos_identity_has_credentials_cache (GOA_KERBEROS_IDENTITY (identity), cache))
++ goa_kerberos_identity_add_credentials_cache (GOA_KERBEROS_IDENTITY (identity), cache);
+ }
+
+- /* Track refreshed identities so we can emit removals when we're done fully
++ /* Track refreshed identities so we can emit refreshes and removals when we're done fully
+ * enumerating the collection of credential caches
+ */
+ g_hash_table_replace (refreshed_identities,
+ g_strdup (identifier),
+ g_object_ref (identity));
+ }
+
+ static gboolean
+ refresh_identities (GoaKerberosIdentityManager *self,
+ Operation *operation)
+ {
+ krb5_error_code error_code;
+ krb5_ccache cache;
+ krb5_cccol_cursor cursor;
+ const char *error_message;
+ GHashTable *refreshed_identities;
++ GHashTableIter iter;
++ const char *name;
++ GoaIdentity *identity;
+
+ /* If we have more refreshes queued up, don't bother doing this one
+ */
+ if (!g_atomic_int_dec_and_test (&self->pending_refresh_count))
+ {
+ return FALSE;
+ }
+
+ g_debug ("GoaKerberosIdentityManager: Refreshing identities");
+ refreshed_identities = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ error_code = krb5_cccol_cursor_new (self->kerberos_context, &cursor);
+
+ if (error_code != 0)
+ {
+ error_message = krb5_get_error_message (self->kerberos_context, error_code);
+ g_debug ("GoaKerberosIdentityManager: Error looking up available credential caches: %s",
+ error_message);
+ krb5_free_error_message (self->kerberos_context, error_message);
+ goto done;
+ }
+
+ error_code = krb5_cccol_cursor_next (self->kerberos_context, cursor, &cache);
+
+ while (error_code == 0 && cache != NULL)
+ {
+- GoaIdentity *identity;
+-
+- identity = goa_kerberos_identity_new (self->kerberos_context, cache, NULL);
+-
+- if (identity != NULL)
+- {
+- refresh_identity (self, operation, refreshed_identities, identity);
+- g_object_unref (identity);
+- }
++ import_credentials_cache (self, operation, refreshed_identities, cache);
+
+ krb5_cc_close (self->kerberos_context, cache);
+ error_code = krb5_cccol_cursor_next (self->kerberos_context, cursor, &cache);
+ }
+
+ if (error_code != 0)
+ {
+ error_message = krb5_get_error_message (self->kerberos_context, error_code);
+ g_debug ("GoaKerberosIdentityManager: Error iterating over available credential caches: %s",
+ error_message);
+ krb5_free_error_message (self->kerberos_context, error_message);
+ }
+
+ krb5_cccol_cursor_free (self->kerberos_context, &cursor);
++
++ g_hash_table_iter_init (&iter, self->identities);
++ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &identity))
++ {
++ goa_kerberos_identity_refresh (GOA_KERBEROS_IDENTITY (identity));
++
++ if (goa_identity_is_signed_in (identity))
++ {
++ IdentitySignalWork *work;
++
++ /* if it's not expired, send out a refresh signal */
++ g_debug ("GoaKerberosIdentityManager: identity '%s' refreshed",
++ goa_identity_get_identifier (identity));
++
++ work = identity_signal_work_new (self, identity);
++ goa_kerberos_identify_manager_send_to_context (operation->context,
++ (GSourceFunc)
++ do_identity_signal_refreshed_work,
++ work,
++ (GDestroyNotify)
++ identity_signal_work_free);
++ }
++ }
++
+ done:
+ drop_stale_identities (self, operation, refreshed_identities);
+ g_hash_table_unref (refreshed_identities);
+
+ return TRUE;
+ }
+
+ static int
+ identity_sort_func (GoaIdentity *a,
+ GoaIdentity *b)
+ {
+ return g_strcmp0 (goa_identity_get_identifier (a),
+ goa_identity_get_identifier (b));
+ }
+
+ static void
+ free_identity_list (GList *list)
+ {
+ g_list_free_full (list, g_object_unref);
+ }
+
+ static void
+ list_identities (GoaKerberosIdentityManager *self,
+ Operation *operation)
+ {
+ GList *identities;
+
+ g_debug ("GoaKerberosIdentityManager: Listing identities");
+ identities = g_hash_table_get_values (self->identities);
+
+--
+2.39.3
+
+
+From 7b78149d1f402412ea73b587c141e3f0ec8e87a3 Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Thu, 9 Feb 2023 12:05:16 -0500
+Subject: [PATCH 17/22] goakerberosidentity: Ensure credentials of expired
+ identities at startup
+
+If the identity service is started later than goa-daemon then it won't
+currently notify goa-daemon about tickets that expired before it
+was running.
+
+This commit fixes the problem by manually calling EnsureCredentials on
+all signed out identities after their initial enumeration.
+---
+ src/goaidentity/goaidentityservice.c | 20 ++++++++++++++++----
+ 1 file changed, 16 insertions(+), 4 deletions(-)
+
+diff --git a/src/goaidentity/goaidentityservice.c b/src/goaidentity/goaidentityservice.c
+index a25de416..7c2e389b 100644
+--- a/src/goaidentity/goaidentityservice.c
++++ b/src/goaidentity/goaidentityservice.c
+@@ -1442,82 +1442,88 @@ sign_in (GoaIdentityService *self,
+ on_identity_inquiry,
+ self,
+ cancellable,
+ (GAsyncReadyCallback)
+ on_identity_signed_in,
+ g_object_ref (operation_result));
+
+ g_object_unref (operation_result);
+ }
+
+ static void
+ on_identity_expiring (GoaIdentityManager *identity_manager,
+ GoaIdentity *identity,
+ GoaIdentityService *self)
+ {
+ const char *principal;
+ GoaObject *object;
+
+ principal = goa_identity_get_identifier (identity);
+
+ g_debug ("GoaIdentityService: identity %s expiring", principal);
+
+ object = find_object_with_principal (self, principal, TRUE);
+
+ if (object == NULL)
+ return;
+
+ ensure_account_credentials (self, object);
+ g_clear_object (&object);
+ }
+-
+ static void
+-on_identity_expired (GoaIdentityManager *identity_manager,
+- GoaIdentity *identity,
+- GoaIdentityService *self)
++handle_identity_expired (GoaIdentityService *self,
++ GoaIdentity *identity)
+ {
+ const char *principal;
+ GoaObject *object;
+
+ principal = goa_identity_get_identifier (identity);
+
+ g_debug ("GoaIdentityService: identity %s expired", principal);
+
+ object = find_object_with_principal (self, principal, TRUE);
+
+ if (object == NULL)
+ return;
+
+ ensure_account_credentials (self, object);
+ g_clear_object (&object);
+ }
+
++static void
++on_identity_expired (GoaIdentityManager *identity_manager,
++ GoaIdentity *identity,
++ GoaIdentityService *self)
++{
++ handle_identity_expired (self, identity);
++}
++
+ static void
+ on_sign_out_for_account_change_done (GoaIdentityService *self,
+ GAsyncResult *result)
+ {
+ GError *error = NULL;
+ gboolean had_error;
+
+ /* Workaround for bgo#764163 */
+ had_error = g_task_had_error (G_TASK (result));
+ g_task_propagate_boolean (G_TASK (result), &error);
+ if (had_error)
+ {
+ g_debug ("Log out failed: %s", error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ g_debug ("Log out complete");
+ }
+ }
+
+ static void
+ on_ticketing_done (GoaIdentityService *self,
+ GAsyncResult *result)
+ {
+ GoaObject *object;
+
+ object = g_task_get_task_data (G_TASK (result));
+ ensure_account_credentials (self, object);
+ }
+@@ -1678,60 +1684,66 @@ on_identities_listed (GoaIdentityManager *manager,
+
+ if (identities == NULL)
+ {
+ if (error != NULL)
+ {
+ g_warning ("Could not list identities: %s", error->message);
+ g_error_free (error);
+ }
+ goto out;
+ }
+
+ for (node = identities; node != NULL; node = node->next)
+ {
+ GoaIdentity *identity = node->data;
+ const char *principal;
+ GoaObject *object;
+ char *object_path;
+
+ object_path = export_identity (self, identity);
+
+ principal = goa_identity_get_identifier (identity);
+
+ object = find_object_with_principal (self, principal, TRUE);
+
+ if (object == NULL)
+ add_temporary_account (self, identity);
+ else
+ g_object_unref (object);
+
+ g_free (object_path);
++
++ /* Treat identities that started out expired as if they just expired, in case
++ * the identity service is started long after goa-daemon
++ */
++ if (!goa_identity_is_signed_in (identity))
++ handle_identity_expired (self, identity);
+ }
+
+ out:
+ g_object_unref (self);
+ }
+
+ static void
+ ensure_credentials_for_accounts (GoaIdentityService *self)
+ {
+ GDBusObjectManager *object_manager;
+ GList *accounts;
+ GList *node;
+
+ object_manager = goa_client_get_object_manager (self->client);
+
+ g_signal_connect (object_manager, "interface-added", G_CALLBACK (on_account_interface_added), self);
+ g_signal_connect (object_manager, "interface-removed", G_CALLBACK (on_account_interface_removed), self);
+
+ accounts = goa_client_get_accounts (self->client);
+
+ for (node = accounts; node != NULL; node = node->next)
+ {
+ GoaObject *object = GOA_OBJECT (node->data);
+ GoaAccount *account;
+ GoaTicketing *ticketing;
+ const char *provider_type;
+
+ account = goa_object_peek_account (object);
+
+ if (account == NULL)
+--
+2.39.3
+
+
+From 4003fdbed262f2da2e0affb39bda68c7ae1ccf18 Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Tue, 21 Feb 2023 12:10:46 -0500
+Subject: [PATCH 18/22] goakerberosidentity: Don't send "expired" and
+ "unexpired" signals from two parts of the code
+
+Since commit e869642bd079aec2098542a3c8f1b296694499ab verify_identity
+handles sending "expired" and "unexpired" signals on its own.
+
+Unfortunately that commit neglected to make
+goa_kerberos_identity_refresh stop sending the signals.
+
+This commit removes the duplicated logic.
+---
+ src/goaidentity/goakerberosidentity.c | 42 ++++-----------------------
+ 1 file changed, 6 insertions(+), 36 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index b5cbcecd..20add727 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1753,112 +1753,82 @@ goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ destroy_notify (inquiry_data);
+ sign_in_operation_free (operation);
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+ krb5_free_principal (self->kerberos_context, principal);
+
+ done:
+
+ goa_kerberos_identity_refresh (self);
+
+ if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ g_debug ("GoaKerberosIdentity: Identity '%s' could not be signed in", principal_name);
+ return FALSE;
+ }
+
+ g_debug ("GoaKerberosIdentity: Identity '%s' signed in", principal_name);
+ return TRUE;
+ }
+
+ void
+ goa_kerberos_identity_refresh (GoaKerberosIdentity *self)
+ {
+- VerificationLevel old_verification_level, new_verification_level;
+ g_autofree char *preauth_identity_source = NULL;
+ g_autoptr (GError) error = NULL;
+
+ g_debug ("GoaKerberosIdentity: Refreshing identity %s (active credentials cache: %s)",
+ self->identifier,
+ self->active_credentials_cache_name);
+
+- G_LOCK (identity_lock);
+- old_verification_level = self->cached_verification_level;
+- G_UNLOCK (identity_lock);
++ verify_identity (self, &preauth_identity_source, &error);
+
+- new_verification_level = verify_identity (self, &preauth_identity_source, &error);
++ if (error != NULL)
++ {
++ g_debug ("GoaKerberosIdentity: Could not verify identity %s: %s", self->identifier, error->message);
++ return;
++ }
+
+ G_LOCK (identity_lock);
+ if (g_strcmp0 (self->preauth_identity_source, preauth_identity_source) != 0)
+ {
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = g_steal_pointer (&preauth_identity_source);
+ }
+ G_UNLOCK (identity_lock);
+-
+- if (new_verification_level != old_verification_level)
+- {
+- if ((old_verification_level == VERIFICATION_LEVEL_SIGNED_IN) &&
+- new_verification_level == VERIFICATION_LEVEL_EXISTS)
+- {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = new_verification_level;
+- G_UNLOCK (identity_lock);
+-
+- g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+- }
+- else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+- new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+- {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = new_verification_level;
+- G_UNLOCK (identity_lock);
+-
+- g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+- }
+- else
+- {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = new_verification_level;
+- G_UNLOCK (identity_lock);
+- }
+- G_LOCK (identity_lock);
+- queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+- G_UNLOCK (identity_lock);
+- }
+ }
+
+ gboolean
+ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+ {
+ krb5_error_code error_code = 0;
+ krb5_principal principal;
+ krb5_creds new_credentials;
+ krb5_ccache credentials_cache;
+ gboolean renewed = FALSE;
+ char *name = NULL;
+
+ if (self->active_credentials_cache_name == NULL)
+ {
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ _("Not signed in"));
+ goto out;
+ }
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
+ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ error_code,
+--
+2.39.3
+
+
+From 967384abe9044cfeacaf99f5df2c2985d3b2e357 Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Mon, 20 Feb 2023 14:30:24 -0500
+Subject: [PATCH 19/22] goakerberosidentity: Ensure properties are updated in a
+ timely fashion
+
+At the moment property notifications of identity objects are deferred
+to a lower priority idle handler.
+
+This is suboptimal because it means there can be a bit of a delay
+updating the status of, e.g., IsSignedIn, over the bus.
+
+This commit changes the notification to queue at normal priority.
+---
+ src/goaidentity/goakerberosidentity.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 20add727..de646d5f 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -549,61 +549,61 @@ clear_idle_id (NotifyRequest *request)
+
+ g_object_unref (request->self);
+ g_slice_free (NotifyRequest, request);
+ }
+
+ static gboolean
+ on_notify_queued (NotifyRequest *request)
+ {
+ g_object_notify (G_OBJECT (request->self), request->property_name);
+
+ return FALSE;
+ }
+
+ static void
+ queue_notify (GoaKerberosIdentity *self,
+ guint *idle_id,
+ const char *property_name)
+ {
+ NotifyRequest *request;
+
+ if (*idle_id != 0)
+ {
+ return;
+ }
+
+ request = g_slice_new0 (NotifyRequest);
+ request->self = g_object_ref (self);
+ request->idle_id = idle_id;
+ request->property_name = property_name;
+
+- *idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
++ *idle_id = g_idle_add_full (G_PRIORITY_DEFAULT,
+ (GSourceFunc)
+ on_notify_queued,
+ request,
+ (GDestroyNotify)
+ clear_idle_id);
+ }
+
+ static gboolean
+ set_start_time (GoaKerberosIdentity *self,
+ krb5_timestamp start_time)
+ {
+ if (self->start_time != start_time)
+ {
+ self->start_time = start_time;
+ queue_notify (self, &self->start_time_idle_id, "start-timestamp");
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ static gboolean
+ set_renewal_time (GoaKerberosIdentity *self,
+ krb5_timestamp renewal_time)
+ {
+ if (self->renewal_time != renewal_time)
+ {
+ self->renewal_time = renewal_time;
+ queue_notify (self, &self->renewal_time_idle_id, "renewal-timestamp");
+ return TRUE;
+ }
+--
+2.39.3
+
+
+From 345403d4842005b6666fb059a32da8701964f95d Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Mon, 20 Feb 2023 14:32:52 -0500
+Subject: [PATCH 20/22] goakerberosidentity: Queue is-signed-in notify before
+ emitting expired signal
+
+Right now the "expired" signal is emitted before the "is-signed-in"
+property notification is queued. This notification is used to update the
+"IsSignedIn" property of the object on the bus. The "expired" signal
+emission leads to a corresponding "identity-expired" signal on the manager
+object. A handler for that signal calls into gnome-online-accounts to check
+identity status. In response, gnome-online-accounts will check the "IsSignedIn"
+property.
+
+This commit makes sure the "is-signed-in" notification is queued first,
+before the "expired" signal is emitted.
+
+Of course queuing the notify before "expired" is emitted isn't enough to
+ensure the notify happens in time. A future commit will make sure the
+"identity-expired" signal itself is deferred to the main thread, which
+should resolve that problem.
+---
+ src/goaidentity/goakerberosidentity.c | 20 +++++---------------
+ 1 file changed, 5 insertions(+), 15 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index de646d5f..0eb0a9ca 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1028,85 +1028,75 @@ out:
+ time_changed |= set_renewal_time (self, best_renewal_time);
+ time_changed |= set_expiration_time (self, best_expiration_time);
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ g_debug ("GoaKerberosIdentity: identity %s credentials have updated times, resetting alarms", self->identifier);
+ reset_alarms (self);
+ }
+ else
+ {
+ g_debug ("GoaKerberosIdentity: identity %s credentials are now expired, clearing alarms", self->identifier);
+ clear_alarms (self);
+ }
+ }
+ else
+ {
+ g_debug ("GoaKerberosIdentity: identity %s credentials do not have updated times, so not adjusting alarms", self->identifier);
+ }
+ }
+ else
+ {
+ g_debug ("GoaKerberosIdentity: identity is unverified, clearing alarms");
+ clear_alarms (self);
+ }
+
+ if (best_verification_level != old_verification_level)
+ {
++ G_LOCK (identity_lock);
++ self->cached_verification_level = best_verification_level;
++ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
++ G_UNLOCK (identity_lock);
++
+ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
+ best_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = best_verification_level;
+- G_UNLOCK (identity_lock);
+-
+ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+ }
+ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+ best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = best_verification_level;
+- G_UNLOCK (identity_lock);
+-
+ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+ }
+- else
+- {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = best_verification_level;
+- G_UNLOCK (identity_lock);
+- }
+- queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+ }
+
+ default_principal = get_default_principal (self);
+ is_default_principal = g_strcmp0 (default_principal, self->identifier) == 0;
+
+ default_credentials_cache_name = get_default_cache_name (self);
+ is_default_credentials_cache = g_strcmp0 (default_credentials_cache_name, self->active_credentials_cache_name) == 0;
+
+ if (self->active_credentials_cache_name == NULL)
+ {
+ g_debug ("GoaKerberosIdentity: Not switching default credentials cache because identity %s has no active credentials cache to switch to", self->identifier);
+ should_switch_default_credentials_cache = FALSE;
+ }
+ else if (self->identifier == NULL)
+ {
+ g_debug ("GoaKerberosIdentity: Not switching default credentials cache to '%s' because it is not yet initialized", self->active_credentials_cache_name);
+ should_switch_default_credentials_cache = FALSE;
+ }
+ else if (default_principal == NULL)
+ {
+ g_debug ("GoaKerberosIdentity: Switching default credentials cache to '%s' (identity %s) because there is currently no default", self->active_credentials_cache_name, self->identifier);
+ should_switch_default_credentials_cache = TRUE;
+ }
+ else if (!is_default_principal)
+ {
+ g_debug ("GoaKerberosIdentity: Not switching default credentials cache because identity %s is not the default identity", self->identifier);
+ should_switch_default_credentials_cache = FALSE;
+ }
+ else if (!is_default_credentials_cache)
+ {
+--
+2.39.3
+
+
+From 3a3a9fbbb84adc7c8797027a2c8c9b3fada03e0b Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Mon, 20 Feb 2023 14:57:57 -0500
+Subject: [PATCH 21/22] goakerberosidentitymanager: Ensure identity-expired
+ signal is emitted from main loop thread
+
+Right now most of the the identity manager signals get emitted from the
+main thread. This makes sense, and is what a caller would typically
+expect (given they connect their callbacks in the main thread as well).
+
+The one exception is "identity-expired" which gets emitted from the
+worker thread. This commit fixes that.
+---
+ src/goaidentity/goakerberosidentitymanager.c | 21 ++++++++++++++++++--
+ 1 file changed, 19 insertions(+), 2 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentitymanager.c b/src/goaidentity/goakerberosidentitymanager.c
+index 7785b891..9a89917e 100644
+--- a/src/goaidentity/goakerberosidentitymanager.c
++++ b/src/goaidentity/goakerberosidentitymanager.c
+@@ -254,66 +254,83 @@ static void
+ schedule_refresh (GoaKerberosIdentityManager *self)
+ {
+ Operation *operation;
+
+ g_atomic_int_inc (&self->pending_refresh_count);
+
+ operation = operation_new (self, NULL, OPERATION_TYPE_REFRESH, NULL);
+ g_thread_pool_push (self->thread_pool, operation, NULL);
+ }
+
+ static IdentitySignalWork *
+ identity_signal_work_new (GoaKerberosIdentityManager *self,
+ GoaIdentity *identity)
+ {
+ IdentitySignalWork *work;
+
+ work = g_slice_new (IdentitySignalWork);
+ work->manager = self;
+ work->identity = g_object_ref (identity);
+
+ return work;
+ }
+
+ static void
+ identity_signal_work_free (IdentitySignalWork *work)
+ {
+ g_object_unref (work->identity);
+ g_slice_free (IdentitySignalWork, work);
+ }
+
++static void
++do_identity_signal_expired_work (IdentitySignalWork *work)
++{
++ GoaKerberosIdentityManager *self = work->manager;
++ GoaIdentity *identity = work->identity;
++
++ g_debug ("GoaKerberosIdentityManager: identity expired");
++ _goa_identity_manager_emit_identity_expired (GOA_IDENTITY_MANAGER (self), identity);
++}
++
+ static void
+ on_identity_expired (GoaIdentity *identity,
+ GoaKerberosIdentityManager *self)
+ {
+- _goa_identity_manager_emit_identity_expired (GOA_IDENTITY_MANAGER (self),
+- identity);
++ IdentitySignalWork *work;
++
++ work = identity_signal_work_new (self, identity);
++ goa_kerberos_identify_manager_send_to_context (g_main_context_default (),
++ (GSourceFunc)
++ do_identity_signal_expired_work,
++ work,
++ (GDestroyNotify)
++ identity_signal_work_free);
+ }
+
+ static void
+ on_identity_unexpired (GoaIdentity *identity,
+ GoaKerberosIdentityManager *self)
+ {
+ g_debug ("GoaKerberosIdentityManager: identity unexpired");
+ /* If an identity is now unexpired, that means some sort of weird
+ * clock skew happened and we should just do a full refresh, since it's
+ * probably affected more than one identity
+ */
+ schedule_refresh (self);
+ }
+
+ static void
+ on_identity_expiring (GoaIdentity *identity,
+ GoaKerberosIdentityManager *self)
+ {
+ g_debug ("GoaKerberosIdentityManager: identity about to expire");
+ _goa_identity_manager_emit_identity_expiring (GOA_IDENTITY_MANAGER (self),
+ identity);
+ }
+
+ static void
+ on_identity_needs_renewal (GoaIdentity *identity,
+ GoaKerberosIdentityManager *self)
+ {
+ g_debug ("GoaKerberosIdentityManager: identity needs renewal");
+ _goa_identity_manager_emit_identity_needs_renewal (GOA_IDENTITY_MANAGER (self),
+ identity);
+--
+2.39.3
+
+
+From 792600480e19f9e973fd7261fa5973be836f8470 Mon Sep 17 00:00:00 2001
+From: Ray Strode
+Date: Tue, 21 Feb 2023 12:07:04 -0500
+Subject: [PATCH 22/22] goakerberosidentity: Ensure old_verification_level is
+ initialized
+
+verify_identity has a short-circuit at the top of the file if the
+active credentials cache is already known. In that case it bypasses
+the search for a credentials cache. Unfortunately it also inadvertently
+bypasses initialization of old_verification_level, leading to the
+code thinking the identity is always going unexpired.
+
+This commit fixes that.
+---
+ src/goaidentity/goakerberosidentity.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 0eb0a9ca..e4f09e14 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -859,101 +859,101 @@ get_default_cache_name (GoaKerberosIdentity *self)
+ krb5_free_unparsed_name (self->kerberos_context, principal_name);
+
+ default_cache_name = g_strdup (krb5_cc_get_name (self->kerberos_context, default_cache));
+ krb5_cc_close (self->kerberos_context, default_cache);
+
+ return default_cache_name;
+ }
+
+ static VerificationLevel
+ verify_identity (GoaKerberosIdentity *self,
+ char **preauth_identity_source,
+ GError **error)
+ {
+ krb5_ccache credentials_cache;
+ g_autofree char *default_principal = NULL;
+ g_autofree char *default_credentials_cache_name = NULL;
+ gboolean is_default_principal;
+ gboolean is_default_credentials_cache;
+ gboolean should_switch_default_credentials_cache = FALSE;
+ gboolean time_changed = FALSE;
+ const char *name;
+ krb5_timestamp best_start_time = 0;
+ krb5_timestamp best_renewal_time = 0;
+ krb5_timestamp best_expiration_time = 0;
+ g_autofree char *best_preauth_identity_source = NULL;
+ g_autofree char *best_credentials_cache_name = NULL;
+ VerificationLevel old_verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+ VerificationLevel best_verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+ GHashTableIter iter;
+
++ G_LOCK (identity_lock);
++ old_verification_level = self->cached_verification_level;
++ G_UNLOCK (identity_lock);
++
+ if (self->active_credentials_cache_name != NULL)
+ {
+ G_LOCK (identity_lock);
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
+ G_UNLOCK (identity_lock);
+
+ best_verification_level = verify_identity_in_credentials_cache (self,
+ &best_preauth_identity_source,
+ credentials_cache,
+ &best_start_time,
+ &best_renewal_time,
+ &best_expiration_time,
+ error);
+ G_LOCK (identity_lock);
+ best_credentials_cache_name = g_strdup (self->active_credentials_cache_name);
+ G_UNLOCK (identity_lock);
+
+ if (best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ goto out;
+
+ if (best_verification_level == VERIFICATION_LEVEL_UNVERIFIED ||
+ best_verification_level == VERIFICATION_LEVEL_ERROR)
+ {
+ g_clear_pointer (&best_credentials_cache_name, g_free);
+
+ G_LOCK (identity_lock);
+ if (self->identifier != NULL)
+ {
+ krb5_cc_close (self->kerberos_context, credentials_cache);
+ g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
+ g_clear_pointer (&self->active_credentials_cache_name, g_free);
+ }
+ G_UNLOCK (identity_lock);
+ }
+ }
+
+- G_LOCK (identity_lock);
+- old_verification_level = self->cached_verification_level;
+- G_UNLOCK (identity_lock);
+-
+ G_LOCK (identity_lock);
+ g_hash_table_iter_init (&iter, self->credentials_caches);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
+ {
+ krb5_timestamp new_start_time = 0;
+ krb5_timestamp new_renewal_time = 0;
+ krb5_timestamp new_expiration_time = 0;
+ g_autofree char *new_preauth_identity_source = NULL;
+ VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+ gboolean has_better_credentials = FALSE;
+
+ if (g_strcmp0 (name, self->active_credentials_cache_name) == 0)
+ continue;
+
+ G_UNLOCK (identity_lock);
+
+ if (preauth_identity_source != NULL)
+ g_clear_pointer (preauth_identity_source, g_free);
+
+ verification_level = verify_identity_in_credentials_cache (self,
+ &new_preauth_identity_source,
+ credentials_cache,
+ &new_start_time,
+ &new_renewal_time,
+ &new_expiration_time,
+ error);
+
+ if (verification_level == VERIFICATION_LEVEL_UNVERIFIED ||
+ verification_level == VERIFICATION_LEVEL_ERROR)
+ {
+--
+2.39.3
+
diff --git a/SPECS/gnome-online-accounts.spec b/SPECS/gnome-online-accounts.spec
index d621054..03ea01e 100644
--- a/SPECS/gnome-online-accounts.spec
+++ b/SPECS/gnome-online-accounts.spec
@@ -2,11 +2,10 @@
%global glib2_version 2.52
%global gtk3_version 3.19.12
%global libsoup_version 2.42
-%global webkit2gtk3_version 2.26.0
Name: gnome-online-accounts
Version: 3.40.0
-Release: 2%{?dist}
+Release: 6%{?dist}
Summary: Single sign-on framework for GNOME
License: LGPLv2+
@@ -14,11 +13,16 @@ URL: https://wiki.gnome.org/Projects/GnomeOnlineAccounts
Source0: https://download.gnome.org/sources/gnome-online-accounts/3.40/%{name}-%{version}.tar.xz
# https://pagure.io/fedora-workstation/issue/83
-Patch0: 0001-Remove-Documents-support.patch
+Patch: 0001-Remove-Documents-support.patch
# https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/63
# https://bugzilla.redhat.com/show_bug.cgi?id=1913641
-Patch1: 0001-google-Remove-Photos-support.patch
+Patch: 0001-google-Remove-Photos-support.patch
+
+Patch: kerberos-fixes.patch
+Patch: 0003-Drop-dependency-on-WebKitGTK-139.patch
+
+Obsoletes: gnome-online-accounts-oauth2 < 3.40.0-5
BuildRequires: pkgconfig(gcr-3)
BuildRequires: pkgconfig(gio-2.0) >= %{glib2_version}
@@ -29,7 +33,6 @@ BuildRequires: pkgconfig(gobject-introspection-1.0)
BuildRequires: gettext >= %{gettext_version}
BuildRequires: gtk-doc
BuildRequires: krb5-devel
-BuildRequires: pkgconfig(webkit2gtk-4.0) >= %{webkit2gtk3_version}
BuildRequires: pkgconfig(json-glib-1.0)
BuildRequires: pkgconfig(libsecret-1) >= 0.7
BuildRequires: pkgconfig(libsoup-2.4) >= %{libsoup_version}
@@ -37,11 +40,13 @@ BuildRequires: pkgconfig(rest-0.7)
BuildRequires: pkgconfig(libxml-2.0)
BuildRequires: vala
BuildRequires: make
+BuildRequires: git
+BuildRequires: autoconf
+BuildRequires: automake
Requires: glib2%{?_isa} >= %{glib2_version}
Requires: gtk3%{?_isa} >= %{gtk3_version}
Requires: libsoup%{?_isa} >= %{libsoup_version}
-Requires: webkit2gtk3%{?_isa} >= %{webkit2gtk3_version}
%description
GNOME Online Accounts provides interfaces so that applications and libraries
@@ -58,11 +63,15 @@ The %{name}-devel package contains libraries and header files for
developing applications that use %{name}.
%prep
-%setup -q
-%patch0 -p1
-%patch1 -p1
+%autosetup -S git
%build
+aclocal -I m4
+autoheader
+automake
+libtoolize
+autoconf
+
%configure \
--disable-facebook \
--disable-flickr \
@@ -71,6 +80,7 @@ developing applications that use %{name}.
--disable-media-server \
--disable-silent-rules \
--disable-static \
+ --enable-compile-warnings=yes \
--enable-documentation \
--enable-fedora \
--enable-exchange \
@@ -100,10 +110,10 @@ find $RPM_BUILD_ROOT -name '*.la' -delete
%{_libdir}/libgoa-backend-1.0.so.1
%{_libdir}/libgoa-backend-1.0.so.1.0.0
%dir %{_libdir}/goa-1.0
-%dir %{_libdir}/goa-1.0/web-extensions
-%{_libdir}/goa-1.0/web-extensions/libgoawebextension.so
%{_prefix}/libexec/goa-daemon
%{_prefix}/libexec/goa-identity-service
+%{_prefix}/libexec/goa-oauth2-handler
+%{_datadir}/applications/org.gnome.OnlineAccounts.OAuth2.desktop
%{_datadir}/dbus-1/services/org.gnome.OnlineAccounts.service
%{_datadir}/dbus-1/services/org.gnome.Identity.service
%{_datadir}/icons/hicolor/*/apps/goa-*.svg
@@ -125,6 +135,20 @@ find $RPM_BUILD_ROOT -name '*.la' -delete
%{_datadir}/vala/
%changelog
+* Wed Nov 15 2023 Milan Crha - 3.40.0-6
+- Related: RHEL-10492 (Add margin around OAuth2 prompt content)
+
+* Wed Nov 08 2023 Milan Crha - 3.40.0-5
+- Resolves: RHEL-10492 (Move account types that depend on WebKitGTK into separate optional subpackage)
+- backport upstream fix to use external browser for OAuth2
+
+* Wed Oct 11 2023 Milan Crha - 3.40.0-4
+- Resolves: RHEL-10492 (Move account types that depend on WebKitGTK into separate optional subpackage)
+
+* Tue Jun 06 2023 Ray Strode - 3.40.0-3
+- Backport various kerberos fixes from upstream
+ Resolves: #2177765
+
* Mon Aug 09 2021 Mohan Boddu - 3.40.0-2
- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
Related: rhbz#1991688