Skip to content

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

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

  • New Features

    • Introduced specialized caching for identity providers during login to improve performance.
    • Enhanced organization-aware filtering of available identity providers during authentication flows.
  • Bug Fixes

    • Disabled identity providers are now correctly filtered from broker and login provider selection.

Closes #32573

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

coderabbit-eval bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

This change introduces login-specific identity provider caching with organization awareness to the Infinispan cache layer, adding filter logic to ensure only enabled, organization-relevant IDPs appear during login flows. New cache keys, invalidation paths, and predicate logic enable separate caching from general IDP lookups.

Changes

Cohort / File(s) Summary
Infinispan Cache Implementation
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java
Added getForLogin(FetchMode mode, String organizationId) method for login-specific IDP retrieval and cacheKeyForLogin(RealmModel realm, FetchMode fetchMode) static helper. Integrated login cache invalidation in create, update, and remove operations based on login eligibility predicates.
SPI Layer
server-spi/src/main/java/org/keycloak/models/IdentityProviderStorageProvider.java
Enhanced login predicate to filter IDPs by organization ID and broker-public configuration, allowing IDPs with null organization OR public broker flag set.
Form Bean Filtering
services/src/main/java/org/keycloak/organization/forms/login/freemarker/model/OrganizationAwareIdentityProviderBean.java
Added isEnabled() filter check to searchForIdentityProviders method for both ORG_ONLY and ALL fetch paths to exclude disabled IDPs during login.
Test Coverage
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java
Added comprehensive testCacheIDPForLogin() validating login cache semantics across REALM_ONLY, ORG_ONLY, and ALL modes, cache invalidation on IDP eligibility changes, and organization reassignments.

Sequence Diagram

sequenceDiagram
    actor User as User (Login)
    participant LoginForm as Login Form<br/>(Bean)
    participant Cache as Infinispan<br/>Cache
    participant Storage as IDP Storage<br/>Provider
    participant Predicate as Login<br/>Predicate

    User->>LoginForm: Search available IDPs
    LoginForm->>Cache: getForLogin(mode, orgId)
    alt Cache Hit
        Cache-->>LoginForm: Cached IDP Stream
    else Cache Miss
        Cache->>Storage: getForLogin(mode, orgId)
        Storage->>Predicate: Apply login predicate<br/>(org-aware filtering)
        Predicate-->>Storage: Filtered IDP Stream
        Storage-->>Cache: Store in login cache<br/>with cacheKey
        Cache-->>LoginForm: IDP Stream
    end
    LoginForm->>LoginForm: Filter enabled IDPs<br/>+ exclude current
    LoginForm-->>User: Display available IDPs
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A warren of caches, organized with care,
Login paths branching through the predicate's air,
Brokers and organizations now dance in the flow,
Enabled IDPs gleam with a cache-warming glow,
The rabbit hops faster, login's never too slow! ✨

🚥 Pre-merge checks | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description lacks substantive detail about the changes; it only states the PR was auto-created without explaining the code modifications or objectives. Add a comprehensive description detailing the new login-specific IDP caching mechanism, organization-aware mapping, cache invalidation logic, and test coverage.
Title check ❓ Inconclusive The title 'Automated Test: feature-idp-cache-implementation' does not clearly describe the main change; it appears to be a generic label for an automated test rather than a meaningful summary of the code changes. Provide a descriptive title that summarizes the primary change, such as 'Add login-specific IDP caching with organization awareness and cache 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: 2

🤖 Fix all issues with AI agents
In
`@model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java`:
- Around line 97-113: The remove(...) method can NPE when
idpDelegate.getByAlias(alias) returns null and isInvalid(cacheKey) is true; fix
by null-checking the storedIdp before calling registerIDPInvalidation(storedIdp)
(i.e., only call registerIDPInvalidation when storedIdp != null), leaving the
cached-branch behavior for CachedIdentityProvider and still calling
registerIDPLoginInvalidation(storedIdp) which is already null-safe; update
remove(), referencing idpDelegate.getByAlias, isInvalid,
registerIDPInvalidation, registerIDPLoginInvalidation, and
CachedIdentityProvider.

In
`@testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java`:
- Around line 380-382: The cleanup currently registers the literal "alias"
instead of the actual IDP alias; change the code that creates the IDP to capture
its alias (e.g., String alias = idpRep.getAlias() or retrieve the created
provider's alias from testRealm().identityProviders().create(idpRep) result) and
pass that variable into getCleanup().addCleanup(...): replace
testRealm().identityProviders().get("alias")::remove with
testRealm().identityProviders().get(alias)::remove for both occurrences (the
block after testRealm().identityProviders().create(idpRep).close() and the
similar spot at line ~425) so the cleanup removes the specific created identity
provider rather than a hardcoded name.
🧹 Nitpick comments (1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java (1)

372-382: Missing braces on single-line if (line 378–379).

The if (i >= 10) block has no braces. While functionally correct, this is prone to subtle bugs if a second statement is later added under the conditional.

♻️ Suggested improvement
-            if (i >= 10)
-                idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
+            if (i >= 10) {
+                idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
+            }

Comment on lines 97 to 113
@Override
public boolean remove(String alias) {
String cacheKey = cacheKeyIdpAlias(getRealm(), alias);
IdentityProviderModel storedIdp = idpDelegate.getByAlias(alias);
if (isInvalid(cacheKey)) {
//lookup idp by alias in cache to be able to invalidate its internalId
registerIDPInvalidation(idpDelegate.getByAlias(alias));
registerIDPInvalidation(storedIdp);
} else {
CachedIdentityProvider cached = realmCache.getCache().get(cacheKey, CachedIdentityProvider.class);
if (cached != null) {
registerIDPInvalidation(cached.getIdentityProvider());
}
}
registerCountInvalidation();
registerIDPLoginInvalidation(storedIdp);
return idpDelegate.remove(alias);
}

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential NPE if storedIdp is null in remove().

Line 100 fetches storedIdp from the delegate, but if no IDP exists for the given alias, storedIdp will be null. When isInvalid(cacheKey) is true, line 103 passes storedIdp to registerIDPInvalidation, which unconditionally calls idp.getInternalId() and idp.getAlias() — resulting in an NPE.

Line 111's registerIDPLoginInvalidation(storedIdp) is safe since getLoginPredicate() starts with a Objects::nonNull check, but line 103 is not guarded.

🛡️ Proposed null guard
         IdentityProviderModel storedIdp = idpDelegate.getByAlias(alias);
         if (isInvalid(cacheKey)) {
             //lookup idp by alias in cache to be able to invalidate its internalId
-            registerIDPInvalidation(storedIdp);
+            if (storedIdp != null) {
+                registerIDPInvalidation(storedIdp);
+            }
         } else {
🤖 Prompt for AI Agents
In
`@model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java`
around lines 97 - 113, The remove(...) method can NPE when
idpDelegate.getByAlias(alias) returns null and isInvalid(cacheKey) is true; fix
by null-checking the storedIdp before calling registerIDPInvalidation(storedIdp)
(i.e., only call registerIDPInvalidation when storedIdp != null), leaving the
cached-branch behavior for CachedIdentityProvider and still calling
registerIDPLoginInvalidation(storedIdp) which is already null-safe; update
remove(), referencing idpDelegate.getByAlias, isInvalid,
registerIDPInvalidation, registerIDPLoginInvalidation, and
CachedIdentityProvider.

Comment on lines +380 to +382
testRealm().identityProviders().create(idpRep).close();
getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);
}

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Cleanup registers literal "alias" instead of the actual IDP alias.

getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove) will try to remove an IDP with alias "alias", not the one just created (e.g., "idp-alias-0"). Same issue at line 425. This appears to be copied from the pre-existing pattern at line 303.

While this likely doesn't cause test failures (realm teardown or other cleanup may handle it), it means these individual cleanup hooks are no-ops.

🐛 Proposed fix
-            getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);
+            getCleanup().addCleanup(testRealm().identityProviders().get(idpRep.getAlias())::remove);

And similarly at line 425:

-        getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);
+        getCleanup().addCleanup(testRealm().identityProviders().get(idpRep.getAlias())::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
testRealm().identityProviders().create(idpRep).close();
getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);
}
testRealm().identityProviders().create(idpRep).close();
getCleanup().addCleanup(testRealm().identityProviders().get(idpRep.getAlias())::remove);
}
🤖 Prompt for AI Agents
In
`@testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java`
around lines 380 - 382, The cleanup currently registers the literal "alias"
instead of the actual IDP alias; change the code that creates the IDP to capture
its alias (e.g., String alias = idpRep.getAlias() or retrieve the created
provider's alias from testRealm().identityProviders().create(idpRep) result) and
pass that variable into getCleanup().addCleanup(...): replace
testRealm().identityProviders().get("alias")::remove with
testRealm().identityProviders().get(alias)::remove for both occurrences (the
block after testRealm().identityProviders().create(idpRep).close() and the
similar spot at line ~425) so the cleanup removes the specific created identity
provider rather than a hardcoded name.

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