diff --git a/services/src/main/java/org/keycloak/userprofile/validator/ImmutableAttributeValidator.java b/services/src/main/java/org/keycloak/userprofile/validator/ImmutableAttributeValidator.java index 6758f23a17..6358bab3f0 100644 --- a/services/src/main/java/org/keycloak/userprofile/validator/ImmutableAttributeValidator.java +++ b/services/src/main/java/org/keycloak/userprofile/validator/ImmutableAttributeValidator.java @@ -16,6 +16,8 @@ */ package org.keycloak.userprofile.validator; +import static org.keycloak.validate.Validators.notBlankValidator; + import java.util.List; import java.util.stream.Collectors; @@ -26,6 +28,7 @@ import org.keycloak.validate.SimpleValidator; import org.keycloak.validate.ValidationContext; import org.keycloak.validate.ValidationError; import org.keycloak.validate.ValidatorConfig; +import org.keycloak.validate.Validators; /** * A validator that fails when the attribute is marked as read only and its value has changed. @@ -62,8 +65,10 @@ public class ImmutableAttributeValidator implements SimpleValidator { List values = (List) input; if (!(currentValue.containsAll(values) && currentValue.size() == values.size())) { + if (currentValue.isEmpty() && !notBlankValidator().validate(values).isValid()) { + return context; + } context.addError(new ValidationError(ID, inputHint, DEFAULT_ERROR_MESSAGE)); - return context; } return context; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/DeclarativeUserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/DeclarativeUserTest.java index 4fc9997128..a700a43ce6 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/DeclarativeUserTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/DeclarativeUserTest.java @@ -12,11 +12,13 @@ import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER import javax.ws.rs.core.Response; +import java.util.Collections; import java.util.List; import java.util.Map; import org.junit.Before; import org.junit.Test; +import org.keycloak.admin.client.resource.UserResource; import org.keycloak.common.Profile; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; @@ -104,6 +106,40 @@ public class DeclarativeUserTest extends AbstractAdminTest { assertTrue(attributes.containsKey("custom-hidden")); } + @Test + public void testUpdateUnsetAttributeWithEmptyValue() { + setUserProfileConfiguration(this.realm, "{\"attributes\": [" + + "{\"name\": \"username\", " + PERMISSIONS_ALL + "}," + + "{\"name\": \"firstName\", " + PERMISSIONS_ALL + "}," + + "{\"name\": \"email\", " + PERMISSIONS_ALL + "}," + + "{\"name\": \"lastName\", " + PERMISSIONS_ALL + "}," + + "{\"name\": \"attr1\", " + PERMISSIONS_ALL + "}," + + "{\"name\": \"attr2\"}]}"); + + UserRepresentation user1 = new UserRepresentation(); + user1.setUsername("user1"); + // set an attribute to later remove it from the configuration + user1.singleAttribute("attr1", "some-value"); + String user1Id = createUser(user1); + + // remove the attr1 attribute from the configuration + setUserProfileConfiguration(this.realm, "{\"attributes\": [" + + "{\"name\": \"username\", " + PERMISSIONS_ALL + "}," + + "{\"name\": \"firstName\", " + PERMISSIONS_ALL + "}," + + "{\"name\": \"email\", " + PERMISSIONS_ALL + "}," + + "{\"name\": \"lastName\", " + PERMISSIONS_ALL + "}," + + "{\"name\": \"attr2\"}]}"); + + UserResource userResource = realm.users().get(user1Id); + user1 = userResource.toRepresentation(); + Map> attributes = user1.getAttributes(); + attributes.put("attr2", Collections.singletonList("")); + // should be able to update the user when a read-only attribute has an empty or null value + userResource.update(user1); + attributes.put("attr2", null); + userResource.update(user1); + } + private String createUser(UserRepresentation userRep) { Response response = realm.users().create(userRep); String createdId = ApiUtil.getCreatedId(response);