Blob Blame History Raw
From 8888746f18ad5662a07a48e7e8bf0a4b2d75273d Mon Sep 17 00:00:00 2001
From: Debarshi Ray <debarshir@gnome.org>
Date: Wed, 12 Oct 2022 22:59:34 +0200
Subject: [PATCH 01/16] 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 caed5ae2..4fcb132c 100644
--- a/src/goaidentity/goakerberosidentitymanager.c
+++ b/src/goaidentity/goakerberosidentitymanager.c
@@ -807,61 +807,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.0


From a25eeeabda86b281665941842d4904b47112f85e Mon Sep 17 00:00:00 2001
From: Debarshi Ray <debarshir@gnome.org>
Date: Thu, 13 Oct 2022 18:46:46 +0200
Subject: [PATCH 02/16] 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.0


From 0cd016658de5f30ddf9a8fb749899067d57a3570 Mon Sep 17 00:00:00 2001
From: Debarshi Ray <debarshir@gnome.org>
Date: Thu, 13 Oct 2022 19:02:59 +0200
Subject: [PATCH 03/16] 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.0


From 2e777d41cc6545da48f9e10083f1468887e2f880 Mon Sep 17 00:00:00 2001
From: Debarshi Ray <debarshir@gnome.org>
Date: Thu, 13 Oct 2022 22:14:07 +0200
Subject: [PATCH 04/16] 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.0


From 9f66a5c92a0183a94c28fe24fad615631bc85b1b Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Thu, 27 Oct 2022 09:18:53 -0400
Subject: [PATCH 05/16] 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, &current_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.0


From 6c2b4c40af149f92e446c06af8cb1091370c0504 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Thu, 13 Oct 2022 16:11:54 -0400
Subject: [PATCH 06/16] 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.0


From aca400799c225a84e5d0fc90efb206c8f1d48bc3 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Mon, 28 Nov 2022 14:16:09 -0500
Subject: [PATCH 07/16] 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 <http://www.gnu.org/licenses/>.
  */
 
 #include "config.h"
 
 #include "goaidentity.h"
 #include "goakerberosidentity.h"
 #include "goakerberosidentityinquiry.h"
 #include "goaalarm.h"
 
 #include <netinet/in.h>
 #include <arpa/nameser.h>
 #include <resolv.h>
 
 #include <string.h>
 #include <glib/gi18n.h>
 #include <gio/gio.h>
 
 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.0


From 5f61f503de118c716809a18484e60a6d5a55b6e3 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Mon, 28 Nov 2022 15:58:09 -0500
Subject: [PATCH 08/16] 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.0


From e3821a2746b3a1f01dedece09f33f8b4d7573713 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Tue, 29 Nov 2022 12:21:09 -0500
Subject: [PATCH 09/16] 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.0


From 9eaf73c81528aae5c89ac9b3366f48d30bca4794 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Wed, 30 Nov 2022 13:53:41 -0500
Subject: [PATCH 10/16] 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.0


From 54f40b8c5696cd15e371f428eccc9090c37ebe65 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Thu, 15 Dec 2022 14:46:01 -0500
Subject: [PATCH 11/16] 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.0


From 09856fdebf7c6dc70a6d07fd76197eb42fdb9262 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Thu, 15 Dec 2022 14:46:01 -0500
Subject: [PATCH 12/16] 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.0


From 2f80c78f7b607fe45ac0c9cd5a9aa4dabecc9538 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Thu, 15 Dec 2022 16:27:27 -0500
Subject: [PATCH 13/16] 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.0


From 592aecd7bf439a7b5b276354d5262a1ce807d8e1 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Thu, 15 Dec 2022 15:35:49 -0500
Subject: [PATCH 14/16] 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.0


From 5cedc418bb4eabd94085660f224ebe78532d3ed4 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Mon, 16 Jan 2023 15:00:36 -0500
Subject: [PATCH 15/16] 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.0


From 8910cc096115f51bc1b13839d3909557276e7145 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Thu, 19 Jan 2023 11:31:14 -0500
Subject: [PATCH 16/16] 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 <http://www.gnu.org/licenses/>.
  */
 
 #ifndef __GOA_KERBEROS_IDENTITY_H__
 #define __GOA_KERBEROS_IDENTITY_H__
 
 #include <glib.h>
 #include <glib-object.h>
 
 #include <krb5.h>
 #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 4fcb132c..f2d01571 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.0