Add caching support for IdentityProviderStorageProvider.getForLogin operations#9
Conversation
Closes #32573 Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
|
@CodeAnt-AI: review |
|
CodeAnt AI is running the review. |
Sequence DiagramShows 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)
Generated by CodeAnt AI |
| registerIDPInvalidation(storedIdp); | ||
| } else { | ||
| CachedIdentityProvider cached = realmCache.getCache().get(cacheKey, CachedIdentityProvider.class); | ||
| if (cached != null) { | ||
| registerIDPInvalidation(cached.getIdentityProvider()); | ||
| } | ||
| } | ||
| registerCountInvalidation(); |
There was a problem hiding this comment.
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.| 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); |
There was a problem hiding this comment.
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.| 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 finished running the review. |
User description
Test 2
Summary by CodeRabbit
New Features
Bug Fixes
Tests
✏️ 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
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:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
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:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
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.