diff --git a/core/src/main/java/org/keycloak/representations/userprofile/config/UPConfig.java b/core/src/main/java/org/keycloak/representations/userprofile/config/UPConfig.java index 60639dc3c3..f417bf23de 100644 --- a/core/src/main/java/org/keycloak/representations/userprofile/config/UPConfig.java +++ b/core/src/main/java/org/keycloak/representations/userprofile/config/UPConfig.java @@ -28,7 +28,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; /** * Configuration of the User Profile for one realm. - * + * * @author Vlastimil Elias * */ diff --git a/services/src/main/java/org/keycloak/userprofile/DeclarativeUserProfileProvider.java b/services/src/main/java/org/keycloak/userprofile/DeclarativeUserProfileProvider.java index 60b60f8864..f40cd81a52 100644 --- a/services/src/main/java/org/keycloak/userprofile/DeclarativeUserProfileProvider.java +++ b/services/src/main/java/org/keycloak/userprofile/DeclarativeUserProfileProvider.java @@ -71,7 +71,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider { /** * Method used for predicate which returns true if any of the configuredScopes is requested in current auth flow. - * + * * @param context to get current auth flow from * @param configuredScopes to be evaluated * @return @@ -248,13 +248,13 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider { protected UserProfileMetadata decorateUserProfileForCache(UserProfileMetadata decoratedMetadata, UPConfig parsedConfig) { UserProfileContext context = decoratedMetadata.getContext(); - if (parsedConfig == null) { + if (parsedConfig == null || parsedConfig.getAttributes() == null) { return decoratedMetadata; } Map groupsByName = asHashMap(parsedConfig.getGroups()); int guiOrder = 0; - + for (UPAttribute attrConfig : parsedConfig.getAttributes()) { String attributeName = attrConfig.getName(); @@ -413,7 +413,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider { private Map asHashMap(List groups) { return groups.stream().collect(Collectors.toMap(g -> g.getName(), g -> g)); } - + private AttributeGroupMetadata toAttributeGroupMeta(UPGroup group) { if (group == null) { return null; diff --git a/services/src/main/java/org/keycloak/userprofile/config/UPConfigUtils.java b/services/src/main/java/org/keycloak/userprofile/config/UPConfigUtils.java index 3c318c10ea..d91dd3f6c2 100644 --- a/services/src/main/java/org/keycloak/userprofile/config/UPConfigUtils.java +++ b/services/src/main/java/org/keycloak/userprofile/config/UPConfigUtils.java @@ -50,7 +50,7 @@ import org.keycloak.validate.Validators; /** * Utility methods to work with User Profile Configurations - * + * * @author Vlastimil Elias * */ @@ -112,13 +112,13 @@ public class UPConfigUtils { errors.addAll(validateAttributeGroups(config)); return errors; } - + private static List validateAttributeGroups(UPConfig config) { long groupsWithoutName = config.getGroups().stream().filter(g -> g.getName() == null).collect(Collectors.counting()); - + if (groupsWithoutName > 0) { String errorMessage = "Name is mandatory for groups, found " + groupsWithoutName + " group(s) without name."; - return Collections.singletonList(errorMessage); + return Collections.singletonList(errorMessage); } return Collections.emptyList(); } @@ -128,13 +128,11 @@ public class UPConfigUtils { Set groups = config.getGroups().stream() .map(g -> g.getName()) .collect(Collectors.toSet()); - + if (config.getAttributes() != null) { Set attNamesCache = new HashSet<>(); config.getAttributes().forEach((attribute) -> validateAttribute(session, attribute, groups, errors, attNamesCache)); errors.addAll(validateRootAttributes(config)); - } else { - errors.add("UserProfile configuration without 'attributes' section is not allowed"); } return errors; @@ -200,13 +198,13 @@ public class UPConfigUtils { if (attributeConfig.getSelector() != null) { validateScopes(attributeConfig.getSelector().getScopes(), "selector.scopes", attributeName, errors, session); } - + if (attributeConfig.getGroup() != null) { if (!groups.contains(attributeConfig.getGroup())) { errors.add("Attribute '" + attributeName + "' references unknown group '" + attributeConfig.getGroup() + "'"); } } - + if (attributeConfig.getAnnotations()!=null) { validateAnnotations(attributeConfig.getAnnotations(), errors, attributeName); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java index a035aead53..f01783d761 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java @@ -1315,6 +1315,39 @@ public class UserProfileTest extends AbstractUserProfileTest { profile.validate(); } + @Test + public void testNullAttributesInConfig() { + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testNullAttributesInConfig); + } + + private static void testNullAttributesInConfig(KeycloakSession session) { + UserProfileProvider provider = getUserProfileProvider(session); + UPConfig config = UPConfigUtils.parseSystemDefaultConfig(); + config.setAttributes(null); + config.setUnmanagedAttributePolicy(UnmanagedAttributePolicy.ENABLED); + + provider.setConfiguration(config); + + Map attributes = new HashMap<>(); + + attributes.put(UserModel.USERNAME, "user"); + attributes.put(UserModel.FIRST_NAME, "John"); + attributes.put(UserModel.LAST_NAME, "Doe"); + attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org"); + + UserProfile profile = provider.create(UserProfileContext.USER_API, attributes); + + profile.validate(); + + config.setAttributes(Collections.emptyList()); + try { + provider.setConfiguration(config); + Assert.fail("Expected to fail as we are trying to remove required attributes email and username"); + } catch (ComponentValidationException cve) { + //ignore + } + } + @Test public void testCustomAttributeOptional() { getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testCustomAttributeOptional);