-
Notifications
You must be signed in to change notification settings - Fork 0
Automated Test: feature-idp-cache-implementation #327
Automated Test: feature-idp-cache-implementation #327
Conversation
Closes #32573 Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
📝 WalkthroughWalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this 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-lineif(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()); + }
| @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); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| testRealm().identityProviders().create(idpRep).close(); | ||
| getCleanup().addCleanup(testRealm().identityProviders().get("alias")::remove); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
This pull request was automatically created by
@coderabbitai/e2e-reviewer.Batch created pull request.
Summary by CodeRabbit
New Features
Bug Fixes