Skip to content

fix: correctly handle duplicate values in setAttribute()#48384

Merged
ahus1 merged 2 commits intokeycloak:mainfrom
dasniko:pr/38526-consistent-multi-value-attributes
Apr 23, 2026
Merged

fix: correctly handle duplicate values in setAttribute()#48384
ahus1 merged 2 commits intokeycloak:mainfrom
dasniko:pr/38526-consistent-multi-value-attributes

Conversation

@dasniko
Copy link
Copy Markdown
Contributor

@dasniko dasniko commented Apr 22, 2026

When an user attribute holds duplicate values (e.g. ["bar", "bar"]) and the caller reduces the count (e.g. to ["bar"]), the change was silently dropped. Both the infinispan cache adapter and the JPA adapter compared old and new values as Sets, so ["bar", "bar"] and ["bar"] both collapsed to {"bar"} and the equality check returned true, causing an early return without persisting any change.

The infinispan adapter's check is the primary failure point in real deployments: it guards the call to getDelegateForUpdate(), so the JPA layer was never reached at all.

Fix: replace Set-based comparison with List-based comparison in both adapters. CollectionUtil.collectionEquals already counts duplicates correctly when given Lists, so ["bar", "bar"] vs. ["bar"] correctly evaluates to not-equal and the update proceeds.

Includes a regression test that verifies duplicate-value reduction is persisted correctly across transaction boundaries.

closes #38526

🤖 AI disclosure: The analysis of the issue and the proposed changes were done with the help of Claude Code. The implementations, especially the new test method, was done myself. Claude also wrote the commit message.

Copy link
Copy Markdown

@keycloak-github-bot keycloak-github-bot Bot left a comment

Choose a reason for hiding this comment

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

Unreported flaky test detected, please review

@keycloak-github-bot
Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.forms.MultiFactorAuthenticationTest#testAlternativeMechanismsInDifferentSubflows

Keycloak CI - Forms IT (chrome)

org.opentest4j.AssertionFailedError: Expected PasswordPage but was localhost (https://localhost:8543/auth/realms/test/login-actions/authenticate?session_code=ujD4IoVg9WVsYugYJ6VQKgnGQeRQfPPba22uuPfUVt4&execution=d226bd1b-317b-43da-83e2-bf00c3bcf124&client_id=test-app&tab_id=jY96cU7Ttcs&client_data=eyJydSI6Imh0dHBzOi8vbG9jYWxob3N0Ojg1NDMvYXV0aC9yZWFsbXMvbWFzdGVyL2FwcC9hdXRoIiwicnQiOiJjb2RlIn0) ==> expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

@ryanemerson
Copy link
Copy Markdown
Contributor

I see there's #41432, which you have contributed to, but it appears to be abandoned. Let's proceed here and we can close that PR once this has been merged.

@dasniko
Copy link
Copy Markdown
Contributor Author

dasniko commented Apr 23, 2026

Yes, #41432 is much more complex and does also not work. So I started a completely new approach. And it turns out that it is quite easy to fix.

)

When an attribute holds duplicate values (e.g. ["bar", "bar"]) and the
caller reduces the count (e.g. to ["bar"]), the change was silently
dropped. Both the infinispan cache adapter and the JPA adapter compared
old and new values as Sets, so ["bar", "bar"] and ["bar"] both collapsed
to {"bar"} and the equality check returned true, causing an early return
without persisting any change.

The infinispan adapter's check is the primary failure point in real
deployments: it guards the call to getDelegateForUpdate(), so the JPA
layer was never reached at all.

Fix: replace Set-based comparison with List-based comparison in both
adapters. CollectionUtil.collectionEquals already counts duplicates
correctly when given Lists, so ["bar", "bar"] vs. ["bar"] correctly
evaluates to not-equal and the update proceeds.

Includes a regression test that verifies duplicate-value reduction is
persisted correctly across transaction boundaries.

closes keycloak#38526

Signed-off-by: Niko Köbler <[email protected]>
Copy link
Copy Markdown

@keycloak-github-bot keycloak-github-bot Bot left a comment

Choose a reason for hiding this comment

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

Unreported flaky test detected, please review

@keycloak-github-bot
Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.adapter.servlet.SAMLServletAdapterTest#employeeSigPostNoIdpKeyTestCertSubjectAsKeyNameInKeyInfo

Keycloak CI - Adapter IT Strict Cookies

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

ryanemerson
ryanemerson previously approved these changes Apr 23, 2026
Copy link
Copy Markdown
Contributor

@ryanemerson ryanemerson left a comment

Choose a reason for hiding this comment

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

LGTM, thanks @dasniko

@ahus1 ahus1 changed the title fix: correctly handle duplicate values in setAttribute() (#38526) fix: correctly handle duplicate values in setAttribute() Apr 23, 2026
Signed-off-by: Alexander Schwartz <[email protected]>
@ahus1 ahus1 force-pushed the pr/38526-consistent-multi-value-attributes branch from 2721154 to f937a0c Compare April 23, 2026 10:53
@ahus1 ahus1 self-assigned this Apr 23, 2026
@ahus1
Copy link
Copy Markdown
Member

ahus1 commented Apr 23, 2026

Thank you for this PR @dasniko and thank you for the first review @ryanemerson.

I've reviewed it and found a similar log in the setSingleAttribute() method. I've pushed one more change and a test case that accompanies it.

The database stores the value as a "bag", which is unordered. Unfortunately there is no native Java type for that, so I chose to sort the list and then compare. Please have a look.

Once one of you has reviewed this change, I'll merge it and prepare the backports.

Copy link
Copy Markdown
Contributor

@ryanemerson ryanemerson left a comment

Choose a reason for hiding this comment

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

Thanks for the second pair of eyes @ahus1, good catch!

@dasniko
Copy link
Copy Markdown
Contributor Author

dasniko commented Apr 23, 2026

Thanks @ahus1! LGTM

@ahus1 ahus1 enabled auto-merge (squash) April 23, 2026 11:20
@keycloak-github-bot
Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.authz.PolicyEvaluationTest#

Keycloak CI - Base IT (3)

org.opentest4j.AssertionFailedError: expected: <PERMIT> but was: <null>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
...
org.opentest4j.AssertionFailedError: expected: <PERMIT> but was: <null>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
...
org.opentest4j.AssertionFailedError: expected: <PERMIT> but was: <null>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
...

Report flaky test

Copy link
Copy Markdown

@keycloak-github-bot keycloak-github-bot Bot left a comment

Choose a reason for hiding this comment

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

Unreported flaky test detected, please review

@ryanemerson
Copy link
Copy Markdown
Contributor

Re-running the Store model tests as UserSessionInitializerTest passes for me locally.

@ahus1 ahus1 merged commit e19df70 into keycloak:main Apr 23, 2026
138 of 140 checks passed
Copy link
Copy Markdown

@keycloak-github-bot keycloak-github-bot Bot left a comment

Choose a reason for hiding this comment

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

Unreported flaky test detected, please review

@keycloak-github-bot
Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.authz.PolicyEvaluationTest#

Keycloak CI - Base IT (3)

org.opentest4j.AssertionFailedError: expected: <PERMIT> but was: <null>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
...
org.opentest4j.AssertionFailedError: expected: <PERMIT> but was: <null>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
...
org.opentest4j.AssertionFailedError: expected: <PERMIT> but was: <null>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
...

Report flaky test

@dasniko dasniko deleted the pr/38526-consistent-multi-value-attributes branch April 23, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate user attribute values cannot be removed

4 participants