Cannot set unmanagedAttributePolicy without profile attributes

Closes #31153

Signed-off-by: Martin Kanis <mkanis@redhat.com>
This commit is contained in:
Martin Kanis 2024-07-17 11:25:56 +02:00 committed by Pedro Igor
parent 5526976d1c
commit e5848bdcf9
4 changed files with 45 additions and 14 deletions

View file

@ -28,7 +28,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
/** /**
* Configuration of the User Profile for one realm. * Configuration of the User Profile for one realm.
* *
* @author Vlastimil Elias <velias@redhat.com> * @author Vlastimil Elias <velias@redhat.com>
* *
*/ */

View file

@ -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. * 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 context to get current auth flow from
* @param configuredScopes to be evaluated * @param configuredScopes to be evaluated
* @return * @return
@ -248,13 +248,13 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
protected UserProfileMetadata decorateUserProfileForCache(UserProfileMetadata decoratedMetadata, UPConfig parsedConfig) { protected UserProfileMetadata decorateUserProfileForCache(UserProfileMetadata decoratedMetadata, UPConfig parsedConfig) {
UserProfileContext context = decoratedMetadata.getContext(); UserProfileContext context = decoratedMetadata.getContext();
if (parsedConfig == null) { if (parsedConfig == null || parsedConfig.getAttributes() == null) {
return decoratedMetadata; return decoratedMetadata;
} }
Map<String, UPGroup> groupsByName = asHashMap(parsedConfig.getGroups()); Map<String, UPGroup> groupsByName = asHashMap(parsedConfig.getGroups());
int guiOrder = 0; int guiOrder = 0;
for (UPAttribute attrConfig : parsedConfig.getAttributes()) { for (UPAttribute attrConfig : parsedConfig.getAttributes()) {
String attributeName = attrConfig.getName(); String attributeName = attrConfig.getName();
@ -413,7 +413,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
private Map<String, UPGroup> asHashMap(List<UPGroup> groups) { private Map<String, UPGroup> asHashMap(List<UPGroup> groups) {
return groups.stream().collect(Collectors.toMap(g -> g.getName(), g -> g)); return groups.stream().collect(Collectors.toMap(g -> g.getName(), g -> g));
} }
private AttributeGroupMetadata toAttributeGroupMeta(UPGroup group) { private AttributeGroupMetadata toAttributeGroupMeta(UPGroup group) {
if (group == null) { if (group == null) {
return null; return null;

View file

@ -50,7 +50,7 @@ import org.keycloak.validate.Validators;
/** /**
* Utility methods to work with User Profile Configurations * Utility methods to work with User Profile Configurations
* *
* @author Vlastimil Elias <velias@redhat.com> * @author Vlastimil Elias <velias@redhat.com>
* *
*/ */
@ -112,13 +112,13 @@ public class UPConfigUtils {
errors.addAll(validateAttributeGroups(config)); errors.addAll(validateAttributeGroups(config));
return errors; return errors;
} }
private static List<String> validateAttributeGroups(UPConfig config) { private static List<String> validateAttributeGroups(UPConfig config) {
long groupsWithoutName = config.getGroups().stream().filter(g -> g.getName() == null).collect(Collectors.counting()); long groupsWithoutName = config.getGroups().stream().filter(g -> g.getName() == null).collect(Collectors.counting());
if (groupsWithoutName > 0) { if (groupsWithoutName > 0) {
String errorMessage = "Name is mandatory for groups, found " + groupsWithoutName + " group(s) without name."; String errorMessage = "Name is mandatory for groups, found " + groupsWithoutName + " group(s) without name.";
return Collections.singletonList(errorMessage); return Collections.singletonList(errorMessage);
} }
return Collections.emptyList(); return Collections.emptyList();
} }
@ -128,13 +128,11 @@ public class UPConfigUtils {
Set<String> groups = config.getGroups().stream() Set<String> groups = config.getGroups().stream()
.map(g -> g.getName()) .map(g -> g.getName())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (config.getAttributes() != null) { if (config.getAttributes() != null) {
Set<String> attNamesCache = new HashSet<>(); Set<String> attNamesCache = new HashSet<>();
config.getAttributes().forEach((attribute) -> validateAttribute(session, attribute, groups, errors, attNamesCache)); config.getAttributes().forEach((attribute) -> validateAttribute(session, attribute, groups, errors, attNamesCache));
errors.addAll(validateRootAttributes(config)); errors.addAll(validateRootAttributes(config));
} else {
errors.add("UserProfile configuration without 'attributes' section is not allowed");
} }
return errors; return errors;
@ -200,13 +198,13 @@ public class UPConfigUtils {
if (attributeConfig.getSelector() != null) { if (attributeConfig.getSelector() != null) {
validateScopes(attributeConfig.getSelector().getScopes(), "selector.scopes", attributeName, errors, session); validateScopes(attributeConfig.getSelector().getScopes(), "selector.scopes", attributeName, errors, session);
} }
if (attributeConfig.getGroup() != null) { if (attributeConfig.getGroup() != null) {
if (!groups.contains(attributeConfig.getGroup())) { if (!groups.contains(attributeConfig.getGroup())) {
errors.add("Attribute '" + attributeName + "' references unknown group '" + attributeConfig.getGroup() + "'"); errors.add("Attribute '" + attributeName + "' references unknown group '" + attributeConfig.getGroup() + "'");
} }
} }
if (attributeConfig.getAnnotations()!=null) { if (attributeConfig.getAnnotations()!=null) {
validateAnnotations(attributeConfig.getAnnotations(), errors, attributeName); validateAnnotations(attributeConfig.getAnnotations(), errors, attributeName);
} }

View file

@ -1315,6 +1315,39 @@ public class UserProfileTest extends AbstractUserProfileTest {
profile.validate(); 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<String, Object> 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 @Test
public void testCustomAttributeOptional() { public void testCustomAttributeOptional() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testCustomAttributeOptional); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testCustomAttributeOptional);