Conversation
The cache fields address their config entries with an array index (config.maxLifespan[0]), which only resolves as long as the form holds the raw ComponentRepresentation, as it does on the LDAP and Kerberos screens. The custom provider screen loads through convertToFormValues(), which unwraps single valued entries into plain strings, so the index resolved against a string and yielded its first character. Registering a control on an index path writes the resolved value back into the form, so the truncation happened on render and did not need an edit to be persisted: a stored max lifespan of 50 was shown as 5 and saved as 5, an eviction time of 12:30 as 01:03. Header.tsx saves from the enable/disable toggle without a dirty check, so toggling a custom provider off and on was enough to lose the configured value. Restore the multivalued shape of those entries after conversion. The helper is placed next to the fields whose names it mirrors, so that a cache field added later cannot be missed, and so that the regression test can cover the conversion without pulling in the surrounding screen. Closes keycloak#52029 Signed-off-by: Niko Köbler <niko@n-k.de>
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
All reviewed changes are covered by regression tests, with no unresolved issues.
Review effort: Balanced
Findings: None
What changed in this PR
Fixes truncated cache-policy values on custom provider forms by restoring array-shaped configuration values before rendering.
Changes:
- Normalizes five cache configuration fields.
- Applies normalization when loading custom providers.
- Adds regression tests for custom, LDAP, and Kerberos paths.
| File | Description |
|---|---|
js/apps/admin-ui/src/user-federation/shared/SettingsCache.tsx |
Adds cache form-shape normalization. |
js/apps/admin-ui/src/user-federation/shared/SettingsCache.test.tsx |
Tests loading, rendering, and saving cache values. |
js/apps/admin-ui/src/user-federation/custom/CustomProviderSettings.tsx |
Applies normalization after form conversion. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #52029
What this fixes
SettingsCacheaddresses its config entries with an array index (config.maxLifespan[0]). That only resolves as long as the form holds the rawComponentRepresentation, which is the case on the LDAP and Kerberos screens — both load withform.reset(component).The custom provider screen loads with
convertToFormValues(), which deliberately unwraps single-valued config entries into plain strings, because that is whatDynamicComponentsexpects for the provider's own properties. The index then resolved against a string and yielded its first character.Registering a control on an index path writes the resolved value back into the form, so the truncation happens on render and does not need an edit to be persisted:
maxLifespan: 505["5"]12:3001/03["1"]/["3"]shared/Header.tsxsaves from the enable/disable toggle without a dirty check, so toggling a custom provider off and on is enough to lose the configured value.This is option A as agreed in the issue: the multivalued shape of the five config entries that
SettingsCacherenders is restored after conversion, so the custom provider screen ends up with the same form shape as the LDAP and Kerberos screens. The provider's own properties stay unwrapped, and the existing save path accepts arrays unchanged.A note on placement
The issue phrased option A as normalizing inside
CustomProviderSettings.tsx. The helper is inSettingsCache.tsxinstead, called from the custom screen, for two reasons:Behaviour is identical either way; happy to inline it if you would rather keep the diff to a single file.
Testing
SettingsCache.test.tsxcovers both load paths — the custom provider screen'sconvertToFormValues+setupCacheForm, and theform.reset(component)the LDAP and Kerberos screens use. Five assertions on the resulting form values, plus six that render the real controls and check the displayed value, an untouched save and an edited save, which is what catches the write-back.Verified that 4 of the 11 fail without the fix, including both custom provider render assertions (
expected '5' to be '50'), while every LDAP and Kerberos case stays green. Full admin-ui suite: 76/76. eslint, prettier and tsc are clean for the touched files.No Playwright test: the e2e job starts a plain
kc.sh start-devwith no deployed providers, and the custom provider screen only exists for an actually deployedUserStorageProvider, so covering it there would mean shipping a provider JAR with the e2e setup. Worth noting that the existing Kerberos cache policy specs only assert the policy label, never the value, which is why this went unnoticed.Left out on purpose
NumberControldoesvalue + 1on a string, so stepping a stored50with the+button yields501— on every screen, LDAP included. That is an independent, pre-existing defect in a shared control used in around 20 places, so it is tracked separately in #53126 rather than fixed here.Credit
#52198 by @MahathirMohammadShuvo addresses the same issue and he offered to defer this one. His analysis surfaced the render-time write-back and the missing dirty check in
Header.tsx, both of which are reflected above, as well as theNumberControldefect. His variant makesSettingsCacheitself shape-agnostic by deriving the field name from the shape present, which notably does not need theNumberControlspecial casing that option B was rejected for — worth a look if you would rather fix the shared component than the calling screen.Generative AI disclosure
Claude Code was used to investigate the root cause and to draft the fix, the tests and this description. All changes were reviewed by me before submitting.