Skip to content

Add caching support for IdentityProviderStorageProvider.getForLogin operations#2

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

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

Conversation

@everettbu

@everettbu everettbu commented Jul 26, 2025

Copy link
Copy Markdown

Test 2

Summary by CodeRabbit

  • New Features
    • Improved caching and invalidation for identity providers available for login, enhancing performance and cache consistency for login-related queries.
  • Bug Fixes
    • Ensured that only enabled identity providers are considered in organization-aware login flows.
  • Tests
    • Added comprehensive tests to verify correct caching and invalidation behavior for login-eligible identity providers.

Closes #32573

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

coderabbitai Bot commented Jul 26, 2025

Copy link
Copy Markdown

Walkthrough

Caching and invalidation for identity providers (IDPs) filtered by login availability and fetch mode are introduced. New methods and cache keys support efficient retrieval and coherence of login-related IDP queries. Predicate logic for login eligibility is refined, explicit enabled checks are added in filtering, and a comprehensive test validates cache population and invalidation scenarios.

Changes

Cohort / File(s) Change Summary
Infinispan IDP Storage Provider Caching & Invalidation
model/infinispan/.../InfinispanIdentityProviderStorageProvider.java
Adds cache key and logic for login-filtered IDPs, implements getForLogin, and introduces targeted cache invalidation on create/update/remove based on login eligibility and organization association.
Login Predicate Logic Update
server-spi/src/main/java/org/keycloak/models/IdentityProviderStorageProvider.java
Refines the login predicate to include checks for organization association and public broker status in the provider's configuration.
Organization-aware IDP Filtering
services/src/main/java/org/keycloak/organization/forms/login/freemarker/model/OrganizationAwareIdentityProviderBean.java
Adds explicit isEnabled() checks in IDP filtering to ensure only enabled providers are included, with comments explaining the necessity due to possible wrapping.
Comprehensive Cache Test
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java
Introduces a new test method that creates various IDPs, exercises login cache queries, and validates cache population and invalidation under multiple scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant InfinispanIDPStorageProvider
    participant Cache
    participant DelegateProvider

    Client->>InfinispanIDPStorageProvider: getForLogin(fetchMode, organizationId)
    InfinispanIDPStorageProvider->>Cache: Check login cache (cacheKeyForLogin)
    alt Cache hit and valid
        Cache-->>InfinispanIDPStorageProvider: Return cached IDPs
        InfinispanIDPStorageProvider-->>Client: Return IDPs
    else Cache miss or invalid
        InfinispanIDPStorageProvider->>DelegateProvider: Load IDPs for login
        DelegateProvider-->>InfinispanIDPStorageProvider: Return IDPs
        InfinispanIDPStorageProvider->>Cache: Store IDPs in cache
        InfinispanIDPStorageProvider-->>Client: Return IDPs
    end
Loading
sequenceDiagram
    participant Admin
    participant InfinispanIDPStorageProvider
    participant Cache

    Admin->>InfinispanIDPStorageProvider: create/update/remove IDP
    InfinispanIDPStorageProvider->>Cache: Register invalidation (if login-eligible or org changes)
    Cache-->>InfinispanIDPStorageProvider: Invalidate affected login caches
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

In the warren of code where IDPs hop,
Caches now guard each login stop.
With predicates checked and logic tight,
Only enabled bunnies join the login night.
Tests burrow deep, ensuring all is right—
A cachey delight for every login byte!
🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature-idp-cache-implementation

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@everettbu

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2025

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java (1)

369-535: Comprehensive test coverage for login IDP caching!

The test thoroughly validates caching behavior across multiple scenarios:

  • Initial empty cache state
  • Cache population with different fetch modes
  • Four distinct invalidation scenarios

The test design effectively verifies that caches are selectively invalidated only when necessary, which is crucial for performance.

Consider these improvements for better maintainability:

  1. Extract magic numbers as constants:
+    private static final int TOTAL_IDPS = 20;
+    private static final int ORG_IDPS_START_INDEX = 10;
+    
     @Test
     public void testCacheIDPForLogin() {
         // create 20 providers, and associate 10 of them with an organization.
-        for (int i = 0; i < 20; i++) {
+        for (int i = 0; i < TOTAL_IDPS; i++) {
  1. Consider breaking this 166-line test into smaller, focused test methods:
    • testCacheIDPForLogin_InitialState()
    • testCacheIDPForLogin_NoInvalidationOnNonLoginChanges()
    • testCacheIDPForLogin_InvalidationOnLoginStatusChange()
    • testCacheIDPForLogin_InvalidationOnOrgAssociation()
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java (1)

382-389: Consider adding debug logging for cache invalidations.

Cache invalidation can be a source of performance issues if it happens too frequently. Adding debug logging would help with troubleshooting.

 private void registerIDPLoginInvalidation(IdentityProviderModel idp) {
     // only invalidate login caches if the IDP qualifies as a login IDP.
     if (getLoginPredicate().test(idp)) {
+        if (logger.isDebugEnabled()) {
+            logger.debugf("Invalidating login caches for IDP: %s", idp.getAlias());
+        }
         for (FetchMode mode : FetchMode.values()) {
             realmCache.registerInvalidation(cacheKeyForLogin(getRealm(), mode));
         }
     }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ff9e91 and 3e59772.

📒 Files selected for processing (4)
  • model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java (6 hunks)
  • server-spi/src/main/java/org/keycloak/models/IdentityProviderStorageProvider.java (1 hunks)
  • services/src/main/java/org/keycloak/organization/forms/login/freemarker/model/OrganizationAwareIdentityProviderBean.java (1 hunks)
  • testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java (3 hunks)
🔇 Additional comments (5)
services/src/main/java/org/keycloak/organization/forms/login/freemarker/model/OrganizationAwareIdentityProviderBean.java (1)

75-75: LGTM! Defensive re-check for wrapped IDPs.

The explicit isEnabled() re-checks are necessary and correctly placed. Since IDPs may be wrapped with organization-aware implementations that override the isEnabled() method, these checks ensure that only truly enabled IDPs are included in the results.

Also applies to: 80-80

server-spi/src/main/java/org/keycloak/models/IdentityProviderStorageProvider.java (1)

254-254: Well-structured login predicate enhancement.

The predicate correctly restricts login-eligible IDPs to those either not associated with any organization or explicitly marked as public brokers. I’ve verified that OrganizationModel.BROKER_PUBLIC is properly imported and defined in OrganizationModel.java. LGTM.

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java (3)

77-79: Clean cache key generation for login-specific caches.

The cache key structure appropriately includes the realm ID and fetch mode, enabling efficient cache management per fetch mode.


213-255: Well-implemented caching for login IDPs.

The getForLogin method correctly follows the established caching pattern used in getByOrganization. It properly handles:

  • Cache miss scenarios
  • Partial cache entries
  • Cache invalidation when IDPs are not found

391-418: Excellent selective cache invalidation logic!

The registerIDPLoginInvalidationOnUpdate method implements smart invalidation that only clears caches when necessary. This optimization prevents unnecessary cache invalidations when:

  • Non-login IDPs remain non-login after update
  • Login IDPs remain login-eligible with unchanged organization association

This is a performance-conscious implementation that minimizes cache churn.

Comment on lines 99 to +111
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential NPE when removing non-existent IDP.

The storedIdp retrieved at line 100 could be null if the alias doesn't exist, which would cause issues when passed to registerIDPInvalidation and registerIDPLoginInvalidation.

Add null check:

 @Override
 public boolean remove(String alias) {
     String cacheKey = cacheKeyIdpAlias(getRealm(), alias);
     IdentityProviderModel storedIdp = idpDelegate.getByAlias(alias);
+    if (storedIdp == null) {
+        return false;
+    }
     if (isInvalid(cacheKey)) {
📝 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
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);
@Override
public boolean remove(String alias) {
String cacheKey = cacheKeyIdpAlias(getRealm(), alias);
IdentityProviderModel storedIdp = idpDelegate.getByAlias(alias);
if (storedIdp == null) {
return false;
}
if (isInvalid(cacheKey)) {
//lookup idp by alias in cache to be able to invalidate its internalId
registerIDPInvalidation(storedIdp);
} else {
CachedIdentityProvider cached = realmCache.getCache().get(cacheKey, CachedIdentityProvider.class);
if (cached != null) {
registerIDPInvalidation(cached.getIdentityProvider());
}
}
registerCountInvalidation();
registerIDPLoginInvalidation(storedIdp);
}
🤖 Prompt for AI Agents
In
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java
around lines 99 to 111, the variable storedIdp can be null if the alias does not
exist, which leads to potential NullPointerExceptions when passed to
registerIDPInvalidation and registerIDPLoginInvalidation. Add null checks before
calling these methods to ensure storedIdp is not null before invoking them.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants