Skip to content

Add caching support for IdentityProviderStorageProvider.getForLogin operations#9

Open
ShashankFC wants to merge 1 commit into
feature-idp-cache-baselinefrom
feature-idp-cache-implementation
Open

Add caching support for IdentityProviderStorageProvider.getForLogin operations#9
ShashankFC wants to merge 1 commit into
feature-idp-cache-baselinefrom
feature-idp-cache-implementation

Conversation

@ShashankFC

@ShashankFC ShashankFC commented Feb 13, 2026

Copy link
Copy Markdown

User description

Test 2

Summary by CodeRabbit

  • New Features

    • Implemented caching mechanism for identity provider login retrieval to improve performance.
  • Bug Fixes

    • Enhanced identity provider filtering to enforce enabled status and organization visibility requirements.
    • Improved handling of organization linkage and broker public accessibility in login scenarios.
  • Tests

    • Added comprehensive test coverage for identity provider login caching behavior.

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/keycloak-coderabbit#2


CodeAnt-AI Description

Add caching for identity provider login lookups and enforce enabled/public checks in login UI

What Changed

  • getForLogin results are now cached per realm and fetch mode so repeated login lookups return cached IDP lists until invalidated
  • Cache invalidation for login lists occurs only when an IDP's login-availability changes or when its organization linkage changes; updates that don't affect login eligibility no longer clear these caches
  • Login UI now re-checks that an IDP is enabled before showing it (prevents wrapped/disabled IDPs from appearing)
  • Comprehensive tests added to verify caching behavior, invalidation scenarios, and cached counts for different fetch modes and organization links

Impact

✅ Faster login IDP listing
✅ Fewer unnecessary cache invalidations when editing IDPs
✅ Clearer login choices by hiding disabled IDPs

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Closes #32573

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
@ShashankFC

Copy link
Copy Markdown
Author

@CodeAnt-AI: review

@codeant-ai

codeant-ai Bot commented Feb 13, 2026

Copy link
Copy Markdown

CodeAnt AI is running the review.

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Feb 13, 2026
@codeant-ai

codeant-ai Bot commented Feb 13, 2026

Copy link
Copy Markdown

Sequence Diagram

Shows the new caching for IdentityProviderStorageProvider.getForLogin and how IDP create/update/remove triggers cache invalidation so subsequent login lookups refresh from the delegate.

sequenceDiagram
    participant LoginUI as Client
    participant IDPCacheLayer as InfinispanIdentityProviderStorageProvider
    participant Delegate as IdentityProviderStorageProvider (delegate)
    participant Cache as RealmCache

    LoginUI->>IDPCacheLayer: getForLogin(fetchMode, orgId)
    IDPCacheLayer->>Cache: lookup cacheKeyForLogin(realm, fetchMode)
    alt cache miss / search not present
        IDPCacheLayer->>Delegate: getForLogin(fetchMode, orgId)
        Delegate-->>IDPCacheLayer: list of IDP ids
        IDPCacheLayer->>Cache: add IdentityProviderListQuery (cache result)
        IDPCacheLayer-->>LoginUI: resolved IDP models (from delegate via ids)
    else cache hit
        Cache-->>IDPCacheLayer: cached IDP ids
        IDPCacheLayer-->>LoginUI: resolved IDP models (from session by id)
    end

    Note over IDPCacheLayer,Cache: When an IDP is created/updated/removed:
    Admin->>IDPCacheLayer: create/update/remove IdentityProviderModel
    IDPCacheLayer->>Cache: registerInvalidation(cacheKeyForLogin...) (only when login-availability/org-link changed)
    Cache-->>IDPCacheLayer: caches invalidated
    IDPCacheLayer->>Delegate: forward create/update/remove (persist changes)
Loading

Generated by CodeAnt AI

Comment on lines +103 to 110
registerIDPInvalidation(storedIdp);
} else {
CachedIdentityProvider cached = realmCache.getCache().get(cacheKey, CachedIdentityProvider.class);
if (cached != null) {
registerIDPInvalidation(cached.getIdentityProvider());
}
}
registerCountInvalidation();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: When removing an identity provider by alias that does not exist, the cached storedIdp will be null and the subsequent calls to registerIDPInvalidation(storedIdp) and registerIDPLoginInvalidation(storedIdp) will dereference it, causing a NullPointerException during removal instead of cleanly returning false; guard these calls with a null check so cache invalidation only runs when an IDP was actually found. [null pointer]

Severity Level: Major ⚠️
- [FAIL] Partial IDP import crashes removing non-existent provider alias.
- [WARN] Admin tasks using remove(alias) see unexpected server errors.
Suggested change
registerIDPInvalidation(storedIdp);
} else {
CachedIdentityProvider cached = realmCache.getCache().get(cacheKey, CachedIdentityProvider.class);
if (cached != null) {
registerIDPInvalidation(cached.getIdentityProvider());
}
}
registerCountInvalidation();
if (storedIdp != null) {
registerIDPInvalidation(storedIdp);
}
} else {
CachedIdentityProvider cached = realmCache.getCache().get(cacheKey, CachedIdentityProvider.class);
if (cached != null) {
registerIDPInvalidation(cached.getIdentityProvider());
}
}
registerCountInvalidation();
if (storedIdp != null) {
registerIDPLoginInvalidation(storedIdp);
}
Steps of Reproduction ✅
1. Trigger an IDP removal by alias using `IdentityProviderStorageProvider.remove(String)`
as wired through the realm model, for example via partial import code at
`services/src/main/java/org/keycloak/partialimport/IdentityProvidersPartialImport.java:69`,
which calls `session.identityProviders().remove(getName(idpRep))` for each identity
provider representation.

2. Ensure the alias passed to `remove(...)` does not exist in the realm (e.g., the partial
import JSON contains an IDP marked for removal whose alias is not present in the target
realm). According to the SPI contract at
`server-spi/src/main/java/org/keycloak/models/IdentityProviderStorageProvider.java:76-82`,
`getByAlias(String alias)` must return `null` when no provider is found.

3. With the Infinispan cache provider active, the call is handled by
`InfinispanIdentityProviderStorageProvider.remove(String alias)` at
`model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java:97-113`,
where `storedIdp` is assigned from `idpDelegate.getByAlias(alias)` and is therefore `null`
for the non-existent alias.

4. The method then calls `registerIDPInvalidation(storedIdp)` (line 103) and
`registerIDPLoginInvalidation(storedIdp)` (line 111). Both helper methods dereference the
argument (`registerIDPInvalidation` uses `idp.getInternalId()` and `idp.getAlias()` at
lines 365-368), causing a `NullPointerException` and returning an HTTP 500 error for the
admin operation instead of the intended `false` return from `idpDelegate.remove(alias)`
for a missing provider.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java
**Line:** 103:110
**Comment:**
	*Null Pointer: When removing an identity provider by alias that does not exist, the cached `storedIdp` will be null and the subsequent calls to `registerIDPInvalidation(storedIdp)` and `registerIDPLoginInvalidation(storedIdp)` will dereference it, causing a NullPointerException during removal instead of cleanly returning `false`; guard these calls with a null check so cache invalidation only runs when an IDP was actually found.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

if (i >= 10)
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
testRealm().identityProviders().create(idpRep).close();
getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: In the login IDP cache test, the cleanup registration always targets an IDP with literal alias "alias" instead of the dynamically created "idp-alias-i", so the created providers are never cleaned up and may leak across tests or cause unexpected side effects; use the actual alias used when creating each provider for the cleanup. [possible bug]

Severity Level: Major ⚠️
- ⚠️ Extra IDPs remain in test realm after cache test.
- ⚠️ Subsequent organization/IDP tests may see unexpected additional providers.
- ⚠️ Test suite isolation degraded; order-dependent flakiness possible.
- ⚠️ Cleanup pattern inconsistent with other IDP tests using dynamic aliases.
Suggested change
getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);
getCleanup().addCleanup(testRealm().identityProviders().get("idp-alias-" + i)::remove);
Steps of Reproduction ✅
1. Run the test class `OrganizationCacheTest` (file
`testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java`)
so that method `testCacheIDPForLogin()` (starts at line 370 in the final file) is
executed.

2. Inside `testCacheIDPForLogin()`, the loop at lines 372–381 creates 20 identity
providers with aliases `"idp-alias-0"``"idp-alias-19"` via
`testRealm().identityProviders().create(idpRep).close();` (line 380).

3. For each created provider, the test registers a cleanup callback using
`getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove);` at line
381, which always targets the literal alias `"alias"` instead of the actual `"idp-alias-"
+ i`.

4. When the test teardown runs, `AbstractOrganizationTest#getCleanup()` (lines 89–92 in
`AbstractOrganizationTest.java`) returns a `TestCleanup` whose `executeCleanup()` (lines
126–252 in `TestCleanup.java`) calls the registered `AutoCloseable.close()`, effectively
invoking `identityProviders().get("alias").remove()`; this fails (no such alias) and the
resulting exception is swallowed by `TestCleanup.addCleanup` (lines 65–72), leaving all
`"idp-alias-*"` providers in the realm and potentially affecting subsequent tests that
reuse the same test realm.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java
**Line:** 381:381
**Comment:**
	*Possible Bug: In the login IDP cache test, the cleanup registration always targets an IDP with literal alias `"alias"` instead of the dynamically created `"idp-alias-i"`, so the created providers are never cleaned up and may leak across tests or cause unexpected side effects; use the actual alias used when creating each provider for the cleanup.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

@codeant-ai

codeant-ai Bot commented Feb 13, 2026

Copy link
Copy Markdown

CodeAnt AI finished running the review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants