Skip to content

Automated Test: feature-idp-cache-implementation #317

Conversation

admin-coderabbit
Copy link
Owner

@admin-coderabbit admin-coderabbit commented Feb 4, 2026

This pull request was automatically created by @coderabbitai/e2e-reviewer.

Batch created pull request.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an issue where disabled identity providers could appear in login flows when using organization-aware identity provider retrieval.
  • Performance

    • Improved login performance through optimized caching of identity providers available for authentication, with intelligent cache invalidation on identity provider changes.

Closes #32573

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
@coderabbit-eval
Copy link

coderabbit-eval bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

The changes introduce a login-aware caching layer for Identity Providers with new methods to generate login cache keys, retrieve login-eligible IDPs with caching and fallback to delegate, and invalidate caches on create, update, and remove operations. Supporting changes enhance login predicates with organization and enabled-status filtering.

Changes

Cohort / File(s) Summary
Login-aware IDP Caching
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java
Added cacheKeyForLogin() for login cache key generation, getForLogin() to fetch login-eligible IDPs with caching and organization awareness, registerIDPLoginInvalidation() and registerIDPLoginInvalidationOnUpdate() for cache invalidation logic. Expanded invalidation flow on create, update, and remove operations. Added IDP_LOGIN_SUFFIX constant and supporting imports.
Login Filtering Logic
server-spi/src/main/java/org/keycloak/models/IdentityProviderStorageProvider.java, services/src/main/java/org/keycloak/organization/forms/login/freemarker/model/OrganizationAwareIdentityProviderBean.java
Enhanced login predicate with organization and broker public flag filtering. Added isEnabled() guard on retrieved identity providers to exclude disabled providers.
Login Cache Tests
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java
New testCacheIDPForLogin() method exercising login-caching behavior across FetchMode values (REALM_ONLY, ORG_ONLY, ALL), validating cache population, invalidation on IDP modifications, and organization-aware retrieval scenarios.

Sequence Diagram

sequenceDiagram
    actor Client as Login Flow
    participant Storage as IDP Storage<br/>Provider
    participant Cache as Infinispan<br/>Cache
    participant Delegate as Delegate<br/>Storage

    Client->>Storage: getForLogin(mode, orgId)
    activate Storage
    
    Storage->>Storage: cacheKeyForLogin(realm, mode)
    activate Storage
    deactivate Storage
    
    Storage->>Cache: get(loginCacheKey)
    activate Cache
    alt Cache Hit
        Cache-->>Storage: IDPs (Stream)
    else Cache Miss
        Cache-->>Storage: null
    end
    deactivate Cache
    
    alt Cached Result Available
        Storage-->>Client: return cached Stream
    else Cache Miss
        Storage->>Delegate: get(mode, orgId)
        activate Delegate
        Delegate-->>Storage: IDPs (Stream)
        deactivate Delegate
        
        Storage->>Storage: filter: enabled & org-aware<br/>& login-eligible
        activate Storage
        deactivate Storage
        
        Storage->>Cache: put(loginCacheKey, IDPs)
        activate Cache
        Cache-->>Storage: acknowledged
        deactivate Cache
        
        Storage-->>Client: return filtered Stream
    end
    
    deactivate Storage
    
    Note over Storage,Cache: On IDP create/update/remove:<br/>invalidate login caches via<br/>registerIDPLoginInvalidation(idp)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hop, cache, and leap with glee,
Login IDPs now caching spree!
Organization-aware, enabled, and bright,
Invalidations dance through day and night. 🌙✨

🚥 Pre-merge checks | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is generic and does not explain the actual changes, objectives, or reasoning for the implementation. It only mentions that the PR was auto-created without providing substantive details. Add a detailed description explaining the new login-aware caching layer, the changes to invalidation logic, test coverage, and the business rationale for these changes.
Title check ❓ Inconclusive The title describes an automated test addition but does not clearly convey the primary change: implementing login-aware caching for Identity Providers with expanded invalidation logic. Use a more descriptive title that reflects the main implementation: e.g., 'Add login-aware caching layer for Identity Providers' or 'Implement IDP login cache with organization-aware invalidation'.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature-idp-cache-implementation

Comment @coderabbit eval help to get the list of available commands and usage tips.

@coderabbit-eval coderabbit-eval bot closed this Feb 4, 2026
Copy link

@coderabbit-eval coderabbit-eval bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java`:
- Around line 369-535: The cleanup calls currently use the literal "alias" so
created IDPs aren’t removed; update the getCleanup().addCleanup(...) calls to
reference the actual aliases you create: in the creation loop use
getCleanup().addCleanup(testRealm().identityProviders().get("idp-alias-" +
i)::remove) (matching the idpRep alias), and also add explicit cleanup calls for
the later single creates/updates (e.g., "idp-alias-20"), ensuring every
test-created IdentityProvider (all "idp-alias-0" .. "idp-alias-20") is scheduled
for removal via testRealm().identityProviders().get(<alias>)::remove.
🧹 Nitpick comments (1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java (1)

213-255: Normalize REALM_ONLY searchKey to avoid per-org cache entries.

If callers pass an orgId with REALM_ONLY, results are identical but cached under multiple keys. Normalizing the key avoids extra entries.

♻️ Suggested refactor
-        String searchKey = organizationId != null ? organizationId : "";
+        String searchKey = (mode == FetchMode.REALM_ONLY)
+                ? ""
+                : (organizationId != null ? organizationId : "");

Comment on lines +369 to +535
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
}
});

// 2- update a couple of idps (one not available for login, one available), but don't change their login-availability status
// none of these operations should invalidate the login caches.
idpRep = testRealm().identityProviders().get("idp-alias-20").toRepresentation();
idpRep.getConfig().put("somekey", "somevalue");
idpRep.setTrustEmail(true);
testRealm().identityProviders().get("idp-alias-20").update(idpRep); // should still be unavailable for login

idpRep = testRealm().identityProviders().get("idp-alias-0").toRepresentation();
idpRep.getConfig().put("somekey", "somevalue");
idpRep.setTrustEmail(true);
testRealm().identityProviders().get("idp-alias-0").update(idpRep); // should still be available for login

getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);

// check all caches for login are still there.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
}
});

// 3- update an IDP, changing the availability for login - this should invalidate the caches.
idpRep = testRealm().identityProviders().get("idp-alias-20").toRepresentation();
idpRep.setHideOnLogin(false);
testRealm().identityProviders().get("idp-alias-20").update(idpRep);

getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);

// check all caches have been cleared.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNull(identityProviderListQuery);
}

// re-do searches to populate the caches again and check the updated results.
session.identityProviders().getForLogin(FetchMode.REALM_ONLY, null);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.REALM_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(6, identityProviderListQuery.getIDPs("").size());

session.identityProviders().getForLogin(FetchMode.ORG_ONLY, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ORG_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(5, identityProviderListQuery.getIDPs(orgaId).size());

session.identityProviders().getForLogin(FetchMode.ALL, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ALL), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(11, identityProviderListQuery.getIDPs(orgaId).size());

});

// 4- finally, change one of the realm-level login IDPs, linking it to an org - although it still qualifies for login, it is now
// linked to an org, which should invalidate all login caches.
idpRep = testRealm().identityProviders().get("idp-alias-20").toRepresentation();
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
testRealm().identityProviders().get("idp-alias-20").update(idpRep);
testRealm().organizations().get(orgaId).identityProviders().addIdentityProvider("idp-alias-20");

getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);

// check all caches have been cleared.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNull(identityProviderListQuery);
}

// re-do searches to populate the caches again and check the updated results.
session.identityProviders().getForLogin(FetchMode.REALM_ONLY, null);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.REALM_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(5, identityProviderListQuery.getIDPs("").size());

session.identityProviders().getForLogin(FetchMode.ORG_ONLY, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ORG_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(6, identityProviderListQuery.getIDPs(orgaId).size());

session.identityProviders().getForLogin(FetchMode.ALL, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ALL), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(11, identityProviderListQuery.getIDPs(orgaId).size());

});
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix cleanup to remove the IDPs created by this test.

Using the literal "alias" leaves the test-created IDPs behind and can affect later tests.

🧹 Proposed fix
-            idpRep.setAlias("idp-alias-" + i);
+            final String alias = "idp-alias-" + i;
+            idpRep.setAlias(alias);
             idpRep.setEnabled((i % 2) == 0); // half of the IDPs will be disabled and won't qualify for login.
             idpRep.setDisplayName("Broker " + i);
             idpRep.setProviderId("keycloak-oidc");
             if (i >= 10)
                 idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
             testRealm().identityProviders().create(idpRep).close();
-            getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);
+            getCleanup().addCleanup(testRealm().identityProviders().get(alias)::remove);
         }
@@
-        idpRep.setAlias("idp-alias-" + 20);
+        String alias = "idp-alias-20";
+        idpRep.setAlias(alias);
         idpRep.setEnabled(true);
         idpRep.setHideOnLogin(true); // this will make the new IDP not available for login.
         idpRep.setDisplayName("Broker " + 20);
         idpRep.setProviderId("keycloak-oidc");
         testRealm().identityProviders().create(idpRep).close();
-        getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);
+        getCleanup().addCleanup(testRealm().identityProviders().get(alias)::remove);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Test
public void testCacheIDPForLogin() {
// create 20 providers, and associate 10 of them with an organization.
for (int i = 0; i < 20; i++) {
IdentityProviderRepresentation idpRep = new IdentityProviderRepresentation();
idpRep.setAlias("idp-alias-" + i);
idpRep.setEnabled((i % 2) == 0); // half of the IDPs will be disabled and won't qualify for login.
idpRep.setDisplayName("Broker " + i);
idpRep.setProviderId("keycloak-oidc");
if (i >= 10)
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
testRealm().identityProviders().create(idpRep).close();
getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);
}
String orgaId = testRealm().organizations().getAll().get(0).getId();
for (int i = 10; i < 20; i++) {
testRealm().organizations().get(orgaId).identityProviders().addIdentityProvider("idp-alias-" + i);
}
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
// check all caches for login don't exist yet
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNull(identityProviderListQuery);
}
// perform some login IDP searches and ensure they are cached.
session.identityProviders().getForLogin(FetchMode.REALM_ONLY, null);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.REALM_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(5, identityProviderListQuery.getIDPs("").size());
session.identityProviders().getForLogin(FetchMode.ORG_ONLY, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ORG_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(5, identityProviderListQuery.getIDPs(orgaId).size());
session.identityProviders().getForLogin(FetchMode.ALL, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ALL), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(10, identityProviderListQuery.getIDPs(orgaId).size());
});
// 1- add/remove IDPs that are not available for login - none of these operations should invalidate the login caches.
IdentityProviderRepresentation idpRep = new IdentityProviderRepresentation();
idpRep.setAlias("idp-alias-" + 20);
idpRep.setEnabled(true);
idpRep.setHideOnLogin(true); // this will make the new IDP not available for login.
idpRep.setDisplayName("Broker " + 20);
idpRep.setProviderId("keycloak-oidc");
testRealm().identityProviders().create(idpRep).close();
getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);
// remove one IDP that was not available for login.
testRealm().identityProviders().get("idp-alias-1").remove();
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
// check all caches for login are still there.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
}
});
// 2- update a couple of idps (one not available for login, one available), but don't change their login-availability status
// none of these operations should invalidate the login caches.
idpRep = testRealm().identityProviders().get("idp-alias-20").toRepresentation();
idpRep.getConfig().put("somekey", "somevalue");
idpRep.setTrustEmail(true);
testRealm().identityProviders().get("idp-alias-20").update(idpRep); // should still be unavailable for login
idpRep = testRealm().identityProviders().get("idp-alias-0").toRepresentation();
idpRep.getConfig().put("somekey", "somevalue");
idpRep.setTrustEmail(true);
testRealm().identityProviders().get("idp-alias-0").update(idpRep); // should still be available for login
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
// check all caches for login are still there.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
}
});
// 3- update an IDP, changing the availability for login - this should invalidate the caches.
idpRep = testRealm().identityProviders().get("idp-alias-20").toRepresentation();
idpRep.setHideOnLogin(false);
testRealm().identityProviders().get("idp-alias-20").update(idpRep);
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
// check all caches have been cleared.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNull(identityProviderListQuery);
}
// re-do searches to populate the caches again and check the updated results.
session.identityProviders().getForLogin(FetchMode.REALM_ONLY, null);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.REALM_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(6, identityProviderListQuery.getIDPs("").size());
session.identityProviders().getForLogin(FetchMode.ORG_ONLY, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ORG_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(5, identityProviderListQuery.getIDPs(orgaId).size());
session.identityProviders().getForLogin(FetchMode.ALL, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ALL), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(11, identityProviderListQuery.getIDPs(orgaId).size());
});
// 4- finally, change one of the realm-level login IDPs, linking it to an org - although it still qualifies for login, it is now
// linked to an org, which should invalidate all login caches.
idpRep = testRealm().identityProviders().get("idp-alias-20").toRepresentation();
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
testRealm().identityProviders().get("idp-alias-20").update(idpRep);
testRealm().organizations().get(orgaId).identityProviders().addIdentityProvider("idp-alias-20");
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
// check all caches have been cleared.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNull(identityProviderListQuery);
}
// re-do searches to populate the caches again and check the updated results.
session.identityProviders().getForLogin(FetchMode.REALM_ONLY, null);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.REALM_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(5, identityProviderListQuery.getIDPs("").size());
session.identityProviders().getForLogin(FetchMode.ORG_ONLY, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ORG_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(6, identityProviderListQuery.getIDPs(orgaId).size());
session.identityProviders().getForLogin(FetchMode.ALL, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ALL), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(11, identityProviderListQuery.getIDPs(orgaId).size());
});
}
`@Test`
public void testCacheIDPForLogin() {
// create 20 providers, and associate 10 of them with an organization.
for (int i = 0; i < 20; i++) {
IdentityProviderRepresentation idpRep = new IdentityProviderRepresentation();
final String alias = "idp-alias-" + i;
idpRep.setAlias(alias);
idpRep.setEnabled((i % 2) == 0); // half of the IDPs will be disabled and won't qualify for login.
idpRep.setDisplayName("Broker " + i);
idpRep.setProviderId("keycloak-oidc");
if (i >= 10)
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
testRealm().identityProviders().create(idpRep).close();
getCleanup().addCleanup(testRealm().identityProviders().get(alias)::remove);
}
String orgaId = testRealm().organizations().getAll().get(0).getId();
for (int i = 10; i < 20; i++) {
testRealm().organizations().get(orgaId).identityProviders().addIdentityProvider("idp-alias-" + i);
}
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
// check all caches for login don't exist yet
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNull(identityProviderListQuery);
}
// perform some login IDP searches and ensure they are cached.
session.identityProviders().getForLogin(FetchMode.REALM_ONLY, null);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.REALM_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(5, identityProviderListQuery.getIDPs("").size());
session.identityProviders().getForLogin(FetchMode.ORG_ONLY, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ORG_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(5, identityProviderListQuery.getIDPs(orgaId).size());
session.identityProviders().getForLogin(FetchMode.ALL, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ALL), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(10, identityProviderListQuery.getIDPs(orgaId).size());
});
// 1- add/remove IDPs that are not available for login - none of these operations should invalidate the login caches.
IdentityProviderRepresentation idpRep = new IdentityProviderRepresentation();
String alias = "idp-alias-20";
idpRep.setAlias(alias);
idpRep.setEnabled(true);
idpRep.setHideOnLogin(true); // this will make the new IDP not available for login.
idpRep.setDisplayName("Broker " + 20);
idpRep.setProviderId("keycloak-oidc");
testRealm().identityProviders().create(idpRep).close();
getCleanup().addCleanup(testRealm().identityProviders().get(alias)::remove);
// remove one IDP that was not available for login.
testRealm().identityProviders().get("idp-alias-1").remove();
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
// check all caches for login are still there.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
}
});
// 2- update a couple of idps (one not available for login, one available), but don't change their login-availability status
// none of these operations should invalidate the login caches.
idpRep = testRealm().identityProviders().get("idp-alias-20").toRepresentation();
idpRep.getConfig().put("somekey", "somevalue");
idpRep.setTrustEmail(true);
testRealm().identityProviders().get("idp-alias-20").update(idpRep); // should still be unavailable for login
idpRep = testRealm().identityProviders().get("idp-alias-0").toRepresentation();
idpRep.getConfig().put("somekey", "somevalue");
idpRep.setTrustEmail(true);
testRealm().identityProviders().get("idp-alias-0").update(idpRep); // should still be available for login
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
// check all caches for login are still there.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
}
});
// 3- update an IDP, changing the availability for login - this should invalidate the caches.
idpRep = testRealm().identityProviders().get("idp-alias-20").toRepresentation();
idpRep.setHideOnLogin(false);
testRealm().identityProviders().get("idp-alias-20").update(idpRep);
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
// check all caches have been cleared.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNull(identityProviderListQuery);
}
// re-do searches to populate the caches again and check the updated results.
session.identityProviders().getForLogin(FetchMode.REALM_ONLY, null);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.REALM_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(6, identityProviderListQuery.getIDPs("").size());
session.identityProviders().getForLogin(FetchMode.ORG_ONLY, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ORG_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(5, identityProviderListQuery.getIDPs(orgaId).size());
session.identityProviders().getForLogin(FetchMode.ALL, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ALL), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(11, identityProviderListQuery.getIDPs(orgaId).size());
});
// 4- finally, change one of the realm-level login IDPs, linking it to an org - although it still qualifies for login, it is now
// linked to an org, which should invalidate all login caches.
idpRep = testRealm().identityProviders().get("idp-alias-20").toRepresentation();
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
testRealm().identityProviders().get("idp-alias-20").update(idpRep);
testRealm().organizations().get(orgaId).identityProviders().addIdentityProvider("idp-alias-20");
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
RealmCacheSession realmCache = (RealmCacheSession) session.getProvider(CacheRealmProvider.class);
// check all caches have been cleared.
for (FetchMode fetchMode : IdentityProviderStorageProvider.FetchMode.values()) {
String cachedKey = cacheKeyForLogin(realm, fetchMode);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cachedKey, IdentityProviderListQuery.class);
assertNull(identityProviderListQuery);
}
// re-do searches to populate the caches again and check the updated results.
session.identityProviders().getForLogin(FetchMode.REALM_ONLY, null);
IdentityProviderListQuery identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.REALM_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(5, identityProviderListQuery.getIDPs("").size());
session.identityProviders().getForLogin(FetchMode.ORG_ONLY, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ORG_ONLY), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(6, identityProviderListQuery.getIDPs(orgaId).size());
session.identityProviders().getForLogin(FetchMode.ALL, orgaId);
identityProviderListQuery = realmCache.getCache().get(cacheKeyForLogin(realm, FetchMode.ALL), IdentityProviderListQuery.class);
assertNotNull(identityProviderListQuery);
assertEquals(11, identityProviderListQuery.getIDPs(orgaId).size());
});
}
🤖 Prompt for AI Agents
In
`@testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java`
around lines 369 - 535, The cleanup calls currently use the literal "alias" so
created IDPs aren’t removed; update the getCleanup().addCleanup(...) calls to
reference the actual aliases you create: in the creation loop use
getCleanup().addCleanup(testRealm().identityProviders().get("idp-alias-" +
i)::remove) (matching the idpRep alias), and also add explicit cleanup calls for
the later single creates/updates (e.g., "idp-alias-20"), ensuring every
test-created IdentityProvider (all "idp-alias-0" .. "idp-alias-20") is scheduled
for removal via testRealm().identityProviders().get(<alias>)::remove.

Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant