Support multivalued user attributes in SCIM - #50865
Conversation
There was a problem hiding this comment.
Pull request overview
Adds SCIM exposure of all values from multivalued user-profile attributes.
Changes:
- Returns collections for multivalued attributes.
- Serializes
.valueattributes as SCIM complex arrays. - Adds integration coverage for simple and complex custom attributes.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
AbstractUserModelSchema.java |
Reads all values for multivalued attributes. |
UserExtensionModelSchema.java |
Maps multivalued extension values to SCIM arrays. |
UserTest.java |
Tests both supported multivalued representations. |
48f10e5 to
160abdf
Compare
| private Attribute<UserModel, User> createCustomAttribute(Object scimName, boolean isMultivalued) { | ||
| Attribute.Builder<UserModel, User> builder = Attribute.<UserModel, User>simple(scimName.toString()); | ||
| if (isMultivalued) { | ||
| builder = builder.multivalued() |
72e3b60 to
8d864bc
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
scim/model/src/main/java/org/keycloak/scim/model/user/UserExtensionModelSchema.java:225
- A filtered PATCH remove on these new multivalued mappings deletes the entire attribute. For example,
remove assurance[value eq "x"]resolves the mapper through its.valuealias, butScimFilterToJsonNodeConverterreturnsnullwhencomplexType == null; this branch then interprets that as remove-all instead of removing onlyx. The filter conversion needs to support these simple-backed multivalued mappings (and this case should be covered by a PATCH test).
if (values == null || values.isEmpty()) {
model.removeAttribute(name);
return;
scim/model/src/main/java/org/keycloak/scim/model/user/UserExtensionModelSchema.java:322
- Multivalued nested mappings are only shaped correctly when the sub-attribute is named
value. A mapping such asroles.typefalls through and emits"roles":{"type":[...]}, while the schema change declaresrolesitself multivalued (which requires an array of complex objects). Either serialize every multivalued nested mapping as an array of objects or reject non-valuemappings during validation.
// Handle multivalued attributes annotated as "<parent>.value"
// so that SCIM gets: "<parent>": [ { "value": "..." }, ... ]
if ("value".equals(attributeName) && value instanceof Collection<?> values) {
|
@cgeorgilakis All commits need to be signed-off |
98b0ee4 to
e27196c
Compare
There was a problem hiding this comment.
🟡 Not ready to approve
Nested custom attributes are not correctly published in schema discovery, and unsupported nested mappings produce inconsistent representations.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (2)
scim/model/src/main/java/org/keycloak/scim/model/user/UserExtensionModelSchema.java:322
- Only multivalued
.valuemappings enter this array-of-objects branch. A lone multivalued mapping such as<parent>.typepasses the new validation but is emitted as{ "parent": { "type": [...] } }, while the schema now declaresparentmultivalued; writes from the declared array shape also collapse to an empty string. Either reject multivalued nested mappings other than.value, or implement the same array-of-objects handling for them.
// Handle multivalued attributes annotated as "<parent>.value"
// so that SCIM gets: "<parent>": [ { "value": "..." }, ... ]
if ("value".equals(attributeName) && value instanceof Collection<?> values) {
scim/model/src/main/java/org/keycloak/scim/model/schema/SchemaResourceTypeProvider.java:94
- This parent is only added to the local
topLevelAttributesmap. For the internalUserExtensionModelSchema, that representation is discarded at the end ofbuildSchema; unlike the simple-extension branch at line 148, it never adds the nested attribute toschemasunderparentName. Consequently the newassurance.valuemapping is absent from/Schemas(and thismultiValuedflag is never exposed), so nested custom parents must also be registered on their actual extension schema.
p.setMultiValued(attribute.isMultivalued());
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
Nested multivalued sub-attributes other than .value produce a representation that contradicts the advertised SCIM schema.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (2)
scim/model/src/main/java/org/keycloak/scim/model/user/UserExtensionModelSchema.java:322
- A multivalued mapping to a nested sub-attribute other than
.value(for example,roles.type) still falls through to a map containing a list, producing"roles":{"type":[...]}even though the schema now advertisesrolesas a multivalued complex attribute. Encode every nested multivalued mapping as an array of objects and decode the corresponding sub-attribute on input, or reject these unsupported mappings explicitly.
// Handle multivalued attributes annotated as "<parent>.value"
// so that SCIM gets: "<parent>": [ { "value": "..." }, ... ]
if ("value".equals(attributeName) && value instanceof Collection<?> values) {
scim/model/src/main/java/org/keycloak/scim/model/schema/SchemaResourceTypeProvider.java:94
- The changed
/Schemascontract is not covered by the new tests:SchemaTestextensively assertsmultiValuedmetadata, but no test verifies that a multivalued custom complex parent is reported asmultiValued: true. Add a schema-discovery assertion for the newassurancemapping so regressions in this branch are detected.
p.setMultiValued(attribute.isMultivalued());
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
I know it. It was a temporary commit based on your comment. |
1530ada to
ddf9ec2
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Nested mapping shapes and cross-schema parent collisions can produce incorrect SCIM responses and discovery schemas.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (2)
scim/model/src/main/java/org/keycloak/scim/model/user/UserExtensionModelSchema.java:164
- This validation still accepts a multivalued nested mapping such as
phones.type. The mapper marksphonesas multivalued in discovery, but the representation setter handles collections specially only for.value, producingphones: {"type": [...]}instead of the advertised array of complex values; either reject multivalued non-.valuemappings or serialize them consistently.
boolean hasMultivaluedValue = mappings.stream()
.anyMatch(mapping -> "value".equals(mapping.subAttributeName()) && mapping.multivalued());
List<ExtensionSubAttributeMapping> siblings = mappings.stream()
.filter(mapping -> !("value".equals(mapping.subAttributeName()) && mapping.multivalued()))
.toList();
scim/model/src/main/java/org/keycloak/scim/model/schema/SchemaResourceTypeProvider.java:102
- Nested parents are cached only by
topName, so if two custom schema URNs both define (for example)contact.value, this callback runs only for the first schema. The second schema is then omitted from/Schemas, while its sub-attribute is appended to the first schema's parent; include the schema URN in the cache key for extension attributes.
addToExtensionSchema(parentName, p);
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Changes recommended
Filtered removals can erase all values, and schema discovery can produce missing or inaccurate extension definitions.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (3)
scim/model/src/main/java/org/keycloak/scim/model/user/UserExtensionModelSchema.java:225
- A filtered PATCH removal such as
assurance[value eq "x"]clears the entire attribute instead of onlyx. This mapper is still a simple attribute (complexType == null), soScimFilterToJsonNodeConverterreturnsnullfor the filter; this branch then callsremoveAttribute, making the selective-removal logic below unreachable for the complex.valueform.
if (values == null || values.isEmpty()) {
model.removeAttribute(name);
return;
scim/model/src/main/java/org/keycloak/scim/model/schema/SchemaResourceTypeProvider.java:102
- Nested parents are cached only by
topName, so if two extension schemas both define (for example)roles.value, this registration runs only for the first schema and the second/Schemas/{urn}omitsroles. Key extension parents by schema URN plus name while keeping the exposed attribute name unchanged.
addToExtensionSchema(parentName, p);
scim/model/src/main/java/org/keycloak/scim/model/schema/SchemaResourceTypeProvider.java:90
- This applies the leaf mapper's cardinality to every nested parent. For a multivalued mapping such as
parent.type, the representation setter emits oneparentobject containing a list intype, while discovery now declaresparentas an array (and the sub-attribute as scalar), so the advertised schema contradicts actual responses. Either reserve parent multivaluedness for the special.valueshape or emit arrays of complex objects for every multivalued nested mapping.
p.setMultiValued(attribute.isMultivalued());
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
6630bd6 to
9bff647
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Filtered removal and non-value nested mappings currently produce incorrect behavior.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (2)
scim/model/src/main/java/org/keycloak/scim/model/user/UserExtensionModelSchema.java:221
- A multivalued
<parent>.valuemapper is still built as a simple attribute (complexType == null). Consequently, a valid filtered PATCH such asremove <schema>:assurance[value eq "x"]makesScimFilterToJsonNodeConverterreturnnull, and this remover deletes the entire Keycloak attribute instead of onlyx; model this as a filterable complex value attribute (or otherwise preserve the filter value) and cover filtered removal.
private Attribute<UserModel, User> createCustomAttribute(Object scimName, boolean isMultivalued) {
Attribute.Builder<UserModel, User> builder = Attribute.<UserModel, User>simple(scimName.toString());
if (isMultivalued) {
builder = builder.multivalued()
scim/model/src/main/java/org/keycloak/scim/model/user/UserExtensionModelSchema.java:322
- This converts collections to a multivalued complex shape only when the leaf is named
value. A multivalued profile attribute mapped to another supported nested path (for example<schema>:roles.type) is emitted as{roles: {type: [...]}}, whileSchemaResourceTypeProvidernow declaresrolesas a multivalued complex attribute with a single-valued sub-attribute; either generalize the array-of-objects conversion to the mapped leaf or reject these mappings during validation.
// Handle multivalued attributes annotated as "<parent>.value"
// so that SCIM gets: "<parent>": [ { "value": "..." }, ... ]
if ("value".equals(attributeName) && value instanceof Collection<?> values) {
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Changes recommended
Filtered PATCH removal or replacement can discard unmatched multivalued attribute values.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
scim/model/src/main/java/org/keycloak/scim/model/user/UserExtensionModelSchema.java:225
- Filtered PATCH operations on the new complex
.valueshape delete all values. These mappers still havecomplexType == null, soScimFilterToJsonNodeConverterreturnsnullfor a path such asassurance[value eq "x"]; this branch then removes the entire model attribute, and filteredremove/replaceloses every unmatched value. Make this mapping filterable as a complex value (or extend filter conversion) before using the selective remover.
.withModelRemover((model, name, values) -> {
if (values == null || values.isEmpty()) {
model.removeAttribute(name);
return;
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Closes keycloak#50856 Signed-off-by: cgeorgilakis-grnet <cgeorgilakis@admin.grnet.gr>
9bff647 to
87fff81
Compare
Unreported flaky test detectedIf 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#employeeSigPostNoIdpKeyTestNoKeyNameInKeyInfoKeycloak CI - Adapter IT Strict Cookies |
pedroigor
left a comment
There was a problem hiding this comment.
Thanks, @cgeorgilakis.
Created #51525 as a follow-up.
|
@cgeorgilakis Can you please rebase and force-push? |
Closes #50856