You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by la...@apache.org on 2026/10/12 19:21:13 UTC

(airavata-custos) branch master updated: clear identity cache and allocation memberships when deleting user (#622)

This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git


The following commit(s) were added to refs/heads/master by this push:
     new 92bc9765a clear identity cache and allocation memberships when deleting user (#622)
92bc9765a is described below

commit 92bc9765ac77e0ba9fb65cb84c473bc3777ae1b1
Author: Yasith Jayawardana <ya...@icloud.com>
AuthorDate: Mon Oct 12 15:21:08 2026 -0400

    clear identity cache and allocation memberships when deleting user (#622)
    
    * clear identity cache and allocation memberships when deleting user
    
    * reduce it to on delete cascade
---
 internal/db/migrations/000004_allocations.up.sql  |  3 +-
 pkg/service/identity_resolver_integration_test.go | 36 +++++++++++++++++++++++
 pkg/service/user.go                               |  1 +
 pkg/service/user_identity.go                      |  2 ++
 pkg/service/user_merge.go                         |  1 +
 pkg/service/user_merge_integration_test.go        | 16 ++++++++++
 6 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/internal/db/migrations/000004_allocations.up.sql b/internal/db/migrations/000004_allocations.up.sql
index 47a178612..b87e0db84 100644
--- a/internal/db/migrations/000004_allocations.up.sql
+++ b/internal/db/migrations/000004_allocations.up.sql
@@ -149,7 +149,8 @@ CREATE TABLE IF NOT EXISTS compute_allocation_memberships
     PRIMARY KEY (id),
     CONSTRAINT uq_compute_allocation_memberships_allocation_user UNIQUE (compute_allocation_id, user_id),
     CONSTRAINT fk_compute_allocation_memberships_allocation FOREIGN KEY (compute_allocation_id)
-        REFERENCES compute_allocations (id) ON DELETE CASCADE
+        REFERENCES compute_allocations (id) ON DELETE CASCADE,
+    CONSTRAINT fk_compute_allocation_memberships_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
 );
 CREATE INDEX IF NOT EXISTS idx_compute_allocation_memberships_user ON compute_allocation_memberships (user_id);
 CREATE INDEX IF NOT EXISTS idx_compute_allocation_memberships_status ON compute_allocation_memberships (membership_status);
diff --git a/pkg/service/identity_resolver_integration_test.go b/pkg/service/identity_resolver_integration_test.go
index 087ad16f9..2e630d60e 100644
--- a/pkg/service/identity_resolver_integration_test.go
+++ b/pkg/service/identity_resolver_integration_test.go
@@ -295,6 +295,42 @@ func TestResolveCaller_EmailFallback_PublishesIdentityCreated(t *testing.T) {
 	}
 }
 
+func TestResolveCaller_DropsTheCachedCallerOnIdentityChange(t *testing.T) {
+	cases := map[string]func(svc *Service, user, other, identityID string) error{
+		"identity deleted": func(svc *Service, _, _, id string) error { return svc.DeleteUserIdentity(ctx(), id) },
+		"identity rebound": func(svc *Service, _, _, id string) error {
+			return svc.UpdateUserIdentity(ctx(), &models.UserIdentity{ID: id, OIDCSub: "sub-other"})
+		},
+		"user deleted": func(svc *Service, user, _, _ string) error { return svc.DeleteUser(ctx(), user) },
+		"user merged": func(svc *Service, user, other, _ string) error {
+			_, err := svc.MergeUsers(ctx(), other, user)
+			return err
+		},
+	}
+	for name, change := range cases {
+		t.Run(name, func(t *testing.T) {
+			database := setupTestDB(t)
+			svc := newTestService(database)
+			user := seedUser(t, database, "cached@example.edu")
+			seedOIDCIdentity(t, database, user, "sub-cached", "cached@example.edu")
+			var identityID string
+			if err := database.Get(&identityID, "SELECT id FROM user_identities WHERE user_id = $1", user); err != nil {
+				t.Fatalf("read identity: %v", err)
+			}
+			claims := &identity.Claims{Sub: "sub-cached", Email: "cached@example.edu", EmailVerified: true}
+			if _, _, err := svc.ResolveCaller(ctx(), claims); err != nil {
+				t.Fatalf("resolve: %v", err)
+			}
+			if err := change(svc, user, seedUser(t, database, "survivor@example.edu"), identityID); err != nil {
+				t.Fatalf("change: %v", err)
+			}
+			if caller, _, err := svc.ResolveCaller(ctx(), claims); err == nil && caller.UserID == user {
+				t.Fatal("cached caller survived the change")
+			}
+		})
+	}
+}
+
 func TestResolveCaller_RefusesDisabledUser(t *testing.T) {
 	database := setupTestDB(t)
 	svc := newTestService(database)
diff --git a/pkg/service/user.go b/pkg/service/user.go
index 8e76fb40c..b9e6b88a9 100644
--- a/pkg/service/user.go
+++ b/pkg/service/user.go
@@ -245,5 +245,6 @@ func (s *Service) DeleteUser(ctx context.Context, id string) error {
 	}); err != nil {
 		return fmt.Errorf("delete user: %w", err)
 	}
+	s.InvalidateAllIdentities()
 	return nil
 }
diff --git a/pkg/service/user_identity.go b/pkg/service/user_identity.go
index dbffc363f..a5e01e7d7 100644
--- a/pkg/service/user_identity.go
+++ b/pkg/service/user_identity.go
@@ -166,6 +166,7 @@ func (s *Service) UpdateUserIdentity(ctx context.Context, e *models.UserIdentity
 	}); err != nil {
 		return fmt.Errorf("update user identity: %w", err)
 	}
+	s.InvalidateAllIdentities()
 	return nil
 }
 
@@ -189,5 +190,6 @@ func (s *Service) DeleteUserIdentity(ctx context.Context, id string) error {
 	}); err != nil {
 		return fmt.Errorf("delete user identity: %w", err)
 	}
+	s.InvalidateAllIdentities()
 	return nil
 }
diff --git a/pkg/service/user_merge.go b/pkg/service/user_merge.go
index 913acbe70..faea905c7 100644
--- a/pkg/service/user_merge.go
+++ b/pkg/service/user_merge.go
@@ -131,6 +131,7 @@ func (s *Service) MergeUsers(ctx context.Context, survivingID, retiringID string
 	}); err != nil {
 		return nil, fmt.Errorf("merge users: %w", err)
 	}
+	s.InvalidateAllIdentities()
 	return survivor, nil
 }
 
diff --git a/pkg/service/user_merge_integration_test.go b/pkg/service/user_merge_integration_test.go
index 92c6200e4..a5438f583 100644
--- a/pkg/service/user_merge_integration_test.go
+++ b/pkg/service/user_merge_integration_test.go
@@ -135,3 +135,19 @@ func TestMergeUsers_KeepsHigherTagAndPIHoldsNoTag(t *testing.T) {
 		}
 	}
 }
+
+func TestDeleteUser_RemovesTheirAllocationMemberships(t *testing.T) {
+	database := setupTestDB(t)
+	svc := newTestService(database)
+	_, allocID := newClusterAllocation(t, svc, seedUser(t, database, "pi@example.edu"))
+	member := seedUser(t, database, "member@example.edu")
+	if _, err := svc.CreateComputeAllocationMembership(ctx(), &models.ComputeAllocationMembership{ComputeAllocationID: allocID, UserID: member, MembershipStatus: models.ACTIVE}); err != nil {
+		t.Fatalf("seed membership: %v", err)
+	}
+	if err := svc.DeleteUser(ctx(), member); err != nil {
+		t.Fatalf("delete user: %v", err)
+	}
+	if left, _ := svc.memberships.FindByUser(ctx(), member); len(left) != 0 {
+		t.Fatalf("%d memberships left", len(left))
+	}
+}