Hi there 👋
| 🌥️ Tech |
| 2 high, 8 medium, 3 low |
This MR introduces caching for getForLogin in the Infinispan IdentityProviderStorageProvider, along with organization-aware filtering. The overall design is sound, but several issues were identified: unnecessary database round-trips, null-pointer risks, missing invalidation for removeAll(), missed @Override annotations, race windows in cache-miss handling, and inconsistent isEnabled() re-checking in the organization-aware bean. Additionally, the tests suffer from cleanup bugs and lack coverage for critical scenarios such as org-disabled states, removal of login-qualifying IDPs, and org-to-org transitions. These issues should be addressed to ensure correctness, performance, and test reliability.
These might need a close look
- 🔴 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java (L97)
Potential NullPointerException: If storedIdp is null (alias does not exist), registerIDPInvalidation(storedIdp) will throw NPE. Add a null guard before calling registerIDPInvalidation.
- 🔴 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java (L399)
Test cleanup uses literal string "alias" instead of actual IDP alias. Cleanup will silently fail to remove created IDPs, causing test pollution. Use dynamic alias variable.
Worth checking
- 🟡 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java (L94)
Unnecessary database round-trip: idpDelegate.getByAlias(alias) is called even when storedIdp from cache is non-null. Cache hit should skip the DB call entirely.
- 🟡 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java (L113)
Missing cache invalidation for login caches in removeAll(). After removing all IDPs, registerIDPInvalidation is not called, leaving stale entries in login caches.
- 🟡 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java (L385)
registerIDPLoginInvalidation uses getLoginPredicate() which includes organization-aware isEnabled() re-checking. This means any cache invalidation re-evaluates the predicate against the current (potentially changed) state, which can cause excessive or incorrect invalidations. Consider separating the cache key construction from the predicate evaluation.
- 🟡 services/src/main/java/org/keycloak/models/utils/IdentityProviderStorageProvider.java (L258)
The default getForLogin implementation does not apply the organization-aware getLoginPredicate() logic, creating an inconsistency between the cached and uncached paths.
- 🟡 services/src/main/java/org/keycloak/models/jpa/OrganizationAwareIdentityProviderBean.java (L79)
The REALM_ONLY path does not filter with idp.isEnabled(), unlike the ORGANIZATION and ANY paths. This inconsistency means disabled IDPs may be returned in the REALM_ONLY case.
- 🟡 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java (L430)
Test only validates the count of IDPs returned from cache, not their identity. A cache returning stale/wrong IDPs would still pass the count-based assertion.
- 🟡 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java (L520)
No test coverage for the scenario where an IDP is disabled for an organization while cached. The cache may return stale enabled state.
- 🟡 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java (L416)
No test for the scenario where an IDP that was previously qualifying for login is removed, ensuring the cache reflects the removal.
Small things (take or leave)
- 🔵 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java (L221)
Missing @Override annotation on getForLogin method. Add it for consistency and compile-time checking.
- 🔵 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/idp/InfinispanIdentityProviderStorageProvider.java (L246)
Potential race window: cache.invalidateObject(cacheKey) is called before the new query result is fully constructed. If a concurrent read arrives between invalidation and population, it triggers a redundant DB query.
- 🔵 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java (L534)
No test coverage for the scenario where an IDP is transitioned from one organization to another while cached.
FriendlyReviewer detected 1 of 2 golden bugs. The cleanup alias bug (#2) was found and upgraded from Medium to HIGH. The recursive caching bug (#1) was missed — it is a subtle issue where
session.identityProviders().getById()goes through the Infinispan wrapper instead of the JPA delegate. FriendlyReviewer found related problems (race condition in cache-miss, missing invalidation) but not the recursion itself.The 12 supplementary findings are where FriendlyReviewer compensated. The NPE in
remove()(HIGH) is a legitimate crash risk. The unnecessary DB round-trip, missing invalidation inremoveAll(), and inconsistent org-aware filtering are all real performance or correctness concerns.A pattern worth noting: three findings flag missing test coverage for specific scenarios (org-disabled, login-qualified IDP removal, org-to-org transition). FriendlyReviewer does not just review the implementation — it evaluates whether the tests are adequate for the code being changed.
The missing
@Overrideannotation is minor but illustrates how FriendlyReviewer catches style issues that escape human review.