Do not allow removing username and email from user profile configuration

Closes #25147

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2023-12-08 07:45:49 -03:00 committed by Alexander Schwartz
parent 0a95159e07
commit 78ba7d4a38
17 changed files with 261 additions and 277 deletions

View file

@ -49,6 +49,26 @@ public class UPAttribute {
this.name = name != null ? name.trim() : null; this.name = name != null ? name.trim() : null;
} }
public UPAttribute(String name, UPGroup group) {
this(name);
this.group = group.getName();
}
public UPAttribute(String name, UPAttributePermissions permissions, UPAttributeRequired required, UPAttributeSelector selector) {
this(name);
this.permissions = permissions;
this.required = required;
this.selector = selector;
}
public UPAttribute(String name, UPAttributePermissions permissions, UPAttributeRequired required) {
this(name, permissions, required, null);
}
public UPAttribute(String name, UPAttributePermissions permissions) {
this(name, permissions, null);
}
public String getName() { public String getName() {
return name; return name;
} }

View file

@ -33,6 +33,15 @@ public class UPAttributePermissions {
private Set<String> view = Collections.emptySet(); private Set<String> view = Collections.emptySet();
private Set<String> edit = Collections.emptySet(); private Set<String> edit = Collections.emptySet();
public UPAttributePermissions() {
// for reflection
}
public UPAttributePermissions(Set<String> view, Set<String> edit) {
this.view = view;
this.edit = edit;
}
public Set<String> getView() { public Set<String> getView() {
return view; return view;
} }

View file

@ -33,6 +33,15 @@ public class UPAttributeRequired {
private Set<String> roles; private Set<String> roles;
private Set<String> scopes; private Set<String> scopes;
public UPAttributeRequired() {
// for reflection
}
public UPAttributeRequired(Set<String> roles, Set<String> scopes) {
this.roles = roles;
this.scopes = scopes;
}
/** /**
* Check if this config means that the attribute is ALWAYS required. * Check if this config means that the attribute is ALWAYS required.
* *

View file

@ -30,6 +30,14 @@ public class UPAttributeSelector {
private Set<String> scopes; private Set<String> scopes;
public UPAttributeSelector() {
// for reflection
}
public UPAttributeSelector(Set<String> scopes) {
this.scopes = scopes;
}
public Set<String> getScopes() { public Set<String> getScopes() {
return scopes; return scopes;
} }

View file

@ -50,16 +50,21 @@ public class UPConfig {
this.attributes = attributes; this.attributes = attributes;
} }
public UPConfig addAttribute(UPAttribute attribute) { public UPConfig addOrReplaceAttribute(UPAttribute attribute) {
if (attributes == null) { if (attributes == null) {
attributes = new ArrayList<>(); attributes = new ArrayList<>();
} }
removeAttribute(attribute.getName());
attributes.add(attribute); attributes.add(attribute);
return this; return this;
} }
public boolean removeAttribute(String name) {
return attributes != null && attributes.removeIf(attribute -> attribute.getName().equals(name));
}
public List<UPGroup> getGroups() { public List<UPGroup> getGroups() {
if (groups == null) { if (groups == null) {
return Collections.emptyList(); return Collections.emptyList();

View file

@ -33,6 +33,14 @@ public class UPGroup {
private String displayDescription; private String displayDescription;
private Map<String, Object> annotations; private Map<String, Object> annotations;
public UPGroup() {
// for reflection
}
public UPGroup(String name) {
this.name = name;
}
public String getName() { public String getName() {
return name; return name;
} }

View file

@ -131,6 +131,8 @@ describe("User profile tabs", () => {
{ {
"attributes": [ "attributes": [
{ {
"name": "email"{downArrow},
{
"name": "username", "name": "username",
"validations": { "validations": {
"length": { "length": {

View file

@ -191,6 +191,7 @@ export const AttributesTab = () => {
{ {
title: t("delete"), title: t("delete"),
isActionable: ({ name }) => !RESTRICTED_ATTRIBUTES.includes(name!), isActionable: ({ name }) => !RESTRICTED_ATTRIBUTES.includes(name!),
isDisabled: RESTRICTED_ATTRIBUTES.includes(name!),
onClick: (_key, _idx, component) => { onClick: (_key, _idx, component) => {
setAttributeToDelete(component.name); setAttributeToDelete(component.name);
toggleDeleteDialog(); toggleDeleteDialog();

View file

@ -23,6 +23,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -36,6 +37,7 @@ import org.keycloak.common.util.StreamUtil;
import org.keycloak.models.ClientScopeModel; import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.userprofile.config.UPAttribute; import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPConfig; import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.userprofile.UserProfileContext; import org.keycloak.userprofile.UserProfileContext;
@ -128,6 +130,7 @@ public class UPConfigUtils {
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));
} else { } else {
errors.add("UserProfile configuration without 'attributes' section is not allowed"); errors.add("UserProfile configuration without 'attributes' section is not allowed");
} }
@ -135,6 +138,25 @@ public class UPConfigUtils {
return errors; return errors;
} }
private static List<String> validateRootAttributes(UPConfig config) {
List<UPAttribute> attributes = config.getAttributes();
if (attributes == null) {
return Collections.emptyList();
}
List<String> errors = new ArrayList<>();
List<String> attributeNames = attributes.stream().map(UPAttribute::getName).collect(Collectors.toList());
for (String name : Arrays.asList(UserModel.USERNAME, UserModel.EMAIL)) {
if (!attributeNames.contains(name)) {
errors.add("The attribute '" + name + "' can not be removed");
}
}
return errors;
}
/** /**
* Validate attribute configuration * Validate attribute configuration
* *
@ -179,7 +201,7 @@ public class UPConfigUtils {
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() + "'");
} }
} }
@ -304,4 +326,12 @@ public class UPConfigUtils {
throw new RuntimeException("Failed to load default user profile config file", cause); throw new RuntimeException("Failed to load default user profile config file", cause);
} }
} }
public static UPConfig parseDefaultConfig() {
try {
return JsonSerialization.readValue(readDefaultConfig(), UPConfig.class);
} catch (IOException e) {
throw new RuntimeException("Failed to parse default user profile configuration", e);
}
}
} }

View file

@ -61,7 +61,7 @@ public class UserTestWithUserProfile extends UserTest {
UPConfig upConfig = realm.users().userProfile().getConfiguration(); UPConfig upConfig = realm.users().userProfile().getConfiguration();
for (String name : managedAttributes) { for (String name : managedAttributes) {
upConfig.addAttribute(createAttributeMetadata(name)); upConfig.addOrReplaceAttribute(createAttributeMetadata(name));
} }
VerifyProfileTest.setUserProfileConfiguration(realm, JsonSerialization.writeValueAsString(upConfig)); VerifyProfileTest.setUserProfileConfiguration(realm, JsonSerialization.writeValueAsString(upConfig));

View file

@ -44,6 +44,7 @@ import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPConfig; import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.userprofile.config.UPGroup; import org.keycloak.representations.userprofile.config.UPGroup;
import org.keycloak.testsuite.util.JsonTestUtils; import org.keycloak.testsuite.util.JsonTestUtils;
import org.keycloak.userprofile.config.UPConfigUtils;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -66,7 +67,7 @@ public class UserProfileAdminTest extends AbstractAdminTest {
@Test @Test
public void testSetDefaultConfig() { public void testSetDefaultConfig() {
UPConfig config = new UPConfig().addAttribute(new UPAttribute("test")); UPConfig config = UPConfigUtils.parseDefaultConfig().addOrReplaceAttribute(new UPAttribute("test"));
UserProfileResource userProfile = testRealm().users().userProfile(); UserProfileResource userProfile = testRealm().users().userProfile();
userProfile.update(config); userProfile.update(config);
getCleanup().addCleanup(() -> testRealm().users().userProfile().update(null)); getCleanup().addCleanup(() -> testRealm().users().userProfile().update(null));

View file

@ -47,6 +47,7 @@ import org.keycloak.testsuite.runonserver.RunHelpers;
import org.keycloak.testsuite.util.JsonTestUtils; import org.keycloak.testsuite.util.JsonTestUtils;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.userprofile.DeclarativeUserProfileProvider; import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.util.JsonSerialization;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -273,8 +274,8 @@ public class ExportImportTest extends AbstractKeycloakTest {
realmRes.update(realmRep); realmRes.update(realmRep);
//add some non-default config //add some non-default config
VerifyProfileTest.setUserProfileConfiguration(realmRes, VerifyProfileTest.CONFIGURATION_FOR_USER_EDIT); UPConfig persistedConfig = VerifyProfileTest.setUserProfileConfiguration(realmRes, VerifyProfileTest.CONFIGURATION_FOR_USER_EDIT);
//export //export
TestingExportImportResource exportImport = testingClient.testing().exportImport(); TestingExportImportResource exportImport = testingClient.testing().exportImport();
exportImport.setProvider(SingleFileExportProviderFactory.PROVIDER_ID); exportImport.setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
@ -297,7 +298,7 @@ public class ExportImportTest extends AbstractKeycloakTest {
MultivaluedHashMap<String, String> config = userProfileComponents.get(0).getConfig(); MultivaluedHashMap<String, String> config = userProfileComponents.get(0).getConfig();
assertThat(config, notNullValue()); assertThat(config, notNullValue());
assertThat(config.size(), equalTo(1)); assertThat(config.size(), equalTo(1));
JsonTestUtils.assertJsonEquals(config.getFirst(DeclarativeUserProfileProvider.UP_COMPONENT_CONFIG_KEY), VerifyProfileTest.CONFIGURATION_FOR_USER_EDIT, UPConfig.class); JsonTestUtils.assertJsonEquals(config.getFirst(DeclarativeUserProfileProvider.UP_COMPONENT_CONFIG_KEY), JsonSerialization.writeValueAsString(persistedConfig), UPConfig.class);
} }
@Test @Test

View file

@ -108,7 +108,7 @@ public class LDAPAdminRestApiWithUserProfileTest extends LDAPAdminRestApiTest {
attribute.setPermissions(permissions); attribute.setPermissions(permissions);
upConfig.addAttribute(attribute); upConfig.addOrReplaceAttribute(attribute);
setUserProfileConfiguration(testRealm(), writeValueAsString(upConfig)); setUserProfileConfiguration(testRealm(), writeValueAsString(upConfig));
} }

View file

@ -22,12 +22,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED; import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -46,6 +49,9 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPAttributeRequired;
import org.keycloak.representations.userprofile.config.UPConfig; import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
@ -1159,7 +1165,7 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", " + VALIDATIONS_LENGTH + "}" + "{\"name\": \"department\"," + PERMISSIONS_ALL + ", " + VALIDATIONS_LENGTH + "}"
+ "]}"; + "]}";
setUserProfileConfiguration(customConfig); UPConfig persistedConfig = setUserProfileConfiguration(customConfig);
RealmResource realmRes = testRealm(); RealmResource realmRes = testRealm();
disableDynamicUserProfile(realmRes, false); disableDynamicUserProfile(realmRes, false);
@ -1167,7 +1173,7 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
enableDynamicUserProfile(realm); enableDynamicUserProfile(realm);
testRealm().update(realm); testRealm().update(realm);
JsonTestUtils.assertJsonEquals(customConfig, realmRes.users().userProfile().getConfiguration()); JsonTestUtils.assertJsonEquals(JsonSerialization.writeValueAsString(persistedConfig), realmRes.users().userProfile().getConfiguration());
} }
protected UserRepresentation getUser(String userId) { protected UserRepresentation getUser(String userId) {
@ -1186,8 +1192,8 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
testRealm().users().get(userId).update(ur); testRealm().users().get(userId).update(ur);
} }
protected void setUserProfileConfiguration(String configuration) { protected UPConfig setUserProfileConfiguration(String configuration) {
setUserProfileConfiguration(testRealm(), configuration); return setUserProfileConfiguration(testRealm(), configuration);
} }
public static void enableDynamicUserProfile(RealmRepresentation testRealm) { public static void enableDynamicUserProfile(RealmRepresentation testRealm) {
@ -1214,10 +1220,27 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
} }
public static void setUserProfileConfiguration(RealmResource testRealm, String configuration) { public static UPConfig setUserProfileConfiguration(RealmResource testRealm, String configuration) {
try { try {
UPConfig config = configuration == null ? null : JsonSerialization.readValue(configuration, UPConfig.class); UPConfig config = configuration == null ? null : JsonSerialization.readValue(configuration, UPConfig.class);
if (config != null) {
UPAttribute username = config.getAttribute(UserModel.USERNAME);
if (username == null) {
config.addOrReplaceAttribute(new UPAttribute(UserModel.USERNAME));
}
UPAttribute email = config.getAttribute(UserModel.EMAIL);
if (email == null) {
config.addOrReplaceAttribute(new UPAttribute(UserModel.EMAIL, new UPAttributePermissions(Set.of(ROLE_USER, ROLE_ADMIN), Set.of(ROLE_USER, ROLE_ADMIN)), new UPAttributeRequired(Set.of(ROLE_USER), Set.of())));
}
}
testRealm.users().userProfile().update(config); testRealm.users().userProfile().update(config);
return config;
} catch (IOException ioe) { } catch (IOException ioe) {
throw new RuntimeException("Failed to read configuration", ioe); throw new RuntimeException("Failed to read configuration", ioe);
} }

View file

@ -104,7 +104,7 @@ public abstract class AbstractUserProfileTest extends AbstractTestRealmKeycloakT
Map<String, Object> validatorConfig = new HashMap<>(); Map<String, Object> validatorConfig = new HashMap<>();
validatorConfig.put("min", 3); validatorConfig.put("min", 3);
attribute.addValidation("length", validatorConfig); attribute.addValidation("length", validatorConfig);
config.addAttribute(attribute); config.addOrReplaceAttribute(attribute);
} }
String newConfig = JsonSerialization.writeValueAsString(config); String newConfig = JsonSerialization.writeValueAsString(config);
return newConfig; return newConfig;

View file

@ -29,6 +29,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN; import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER; import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
import static org.keycloak.userprofile.config.UPConfigUtils.parseDefaultConfig;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
@ -58,6 +59,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy; import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
import org.keycloak.representations.userprofile.config.UPGroup;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.testsuite.runonserver.RunOnServer; import org.keycloak.testsuite.runonserver.RunOnServer;
import org.keycloak.testsuite.util.LDAPRule; import org.keycloak.testsuite.util.LDAPRule;
@ -75,7 +77,6 @@ import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileContext; import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.userprofile.UserProfileProvider; import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.userprofile.ValidationException; import org.keycloak.userprofile.ValidationException;
import org.keycloak.userprofile.config.UPConfigUtils;
import org.keycloak.userprofile.validator.UsernameIDNHomographValidator; import org.keycloak.userprofile.validator.UsernameIDNHomographValidator;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.validate.ValidationError; import org.keycloak.validate.ValidationError;
@ -111,7 +112,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
@Test @Test
public void testReadOnlyAllowed() throws Exception { public void testReadOnlyAllowed() throws Exception {
// create a user with attribute foo value 123 allowed by the profile now but disallowed later // create a user with attribute foo value 123 allowed by the profile now but disallowed later
UPConfig config = JsonSerialization.readValue("{\"attributes\": [{\"name\": \"foo\", \"permissions\": {\"edit\": [\"admin\"]}}]}", UPConfig.class); UPConfig config = parseDefaultConfig();
config.addOrReplaceAttribute(new UPAttribute("foo", new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN))));
RealmResource realmRes = testRealm(); RealmResource realmRes = testRealm();
realmRes.users().userProfile().update(config); realmRes.users().userProfile().update(config);
@ -131,8 +133,10 @@ public class UserProfileTest extends AbstractUserProfileTest {
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().getUserById(realm, userId); UserModel user = session.users().getUserById(realm, userId);
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [{\"name\": \"foo\", \"validations\": {\"length\": {\"min\": \"5\", \"max\": \"15\"}}, \"permissions\": {\"edit\": [\"admin\"]}}]}"); UPConfig upConfig = provider.getConfiguration();
upConfig.getAttribute("foo")
.setValidations(Map.of("length", Map.of("min", "5", "max", "15")));
provider.setConfiguration(JsonSerialization.writeValueAsString(upConfig));
Map<String, List<String>> attributes = new HashMap<>(user.getAttributes()); Map<String, List<String>> attributes = new HashMap<>(user.getAttributes());
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes, user); UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes, user);
profile.validate(); profile.validate();
@ -143,7 +147,11 @@ public class UserProfileTest extends AbstractUserProfileTest {
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().getUserById(realm, userId); UserModel user = session.users().getUserById(realm, userId);
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [{\"name\": \"foo\", \"validations\": {\"length\": {\"min\": \"5\", \"max\": \"15\"}}, \"permissions\": {\"edit\": [\"admin\", \"user\"]}}]}"); UPConfig upConfig = provider.getConfiguration();
UPAttribute changedFoo = upConfig.getAttribute("foo");
changedFoo.setPermissions(new UPAttributePermissions(Set.of(), Set.of(ROLE_USER, ROLE_ADMIN)));
changedFoo.setValidations(Map.of("length", Map.of("min", "5", "max", "15")));
provider.setConfiguration(JsonSerialization.writeValueAsString(upConfig));
Map<String, List<String>> attributes = new HashMap<>(user.getAttributes()); Map<String, List<String>> attributes = new HashMap<>(user.getAttributes());
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes, user); UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes, user);
@ -174,7 +182,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testCustomAttributeInAnyContext); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testCustomAttributeInAnyContext);
} }
private static void testCustomAttributeInAnyContext(KeycloakSession session) { private static void testCustomAttributeInAnyContext(KeycloakSession session) throws IOException {
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, "profiled-user"); attributes.put(UserModel.USERNAME, "profiled-user");
@ -183,8 +191,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org"); attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
provider.setConfiguration("{\"attributes\": [{\"name\": \"address\", \"required\": {}, \"permissions\": {\"edit\": [\"user\"]}}]}"); config.addOrReplaceAttribute(new UPAttribute("address", new UPAttributePermissions(Set.of(), Set.of(ROLE_USER)), new UPAttributeRequired()));
provider.setConfiguration(JsonSerialization.writeValueAsString(config));
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes); UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
@ -198,10 +207,12 @@ public class UserProfileTest extends AbstractUserProfileTest {
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, "address"); containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, "address");
// not writable in user api, no validation should happen
profile = provider.create(UserProfileContext.USER_API, attributes);
profile.validate();
attributes.put("address", "myaddress"); attributes.put("address", "myaddress");
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes); profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
profile.validate(); profile.validate();
} }
@ -210,7 +221,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testResolveProfile); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testResolveProfile);
} }
private static void testResolveProfile(KeycloakSession session) { private static void testResolveProfile(KeycloakSession session) throws IOException {
configureAuthenticationSession(session); configureAuthenticationSession(session);
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
@ -221,8 +232,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org"); attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
provider.setConfiguration("{\"attributes\": [{\"name\": \"business.address\", \"required\": {\"scopes\": [\"customer\"]}, \"permissions\": {\"edit\": [\"user\"]}}]}"); config.addOrReplaceAttribute(new UPAttribute("business.address", new UPAttributePermissions(Set.of(), Set.of(ROLE_USER)), new UPAttributeRequired(Set.of(), Set.of("customer"))));
provider.setConfiguration(JsonSerialization.writeValueAsString(config));
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes); UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
@ -346,21 +358,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
UserModel user = session.users().addUser(realm, "profiled-user"); UserModel user = session.users().addUser(realm, "profiled-user");
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); config.addOrReplaceAttribute(new UPAttribute("address", new UPAttributePermissions(Set.of(), Set.of(ROLE_USER)), new UPAttributeRequired()));
attribute.setName("address");
UPAttributeRequired requirements = new UPAttributeRequired();
attribute.setRequired(requirements);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Collections.singleton(ROLE_USER));
attribute.setPermissions(permissions);
config.addAttribute(attribute);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
UserProfile profile = provider.create(UserProfileContext.ACCOUNT, user); UserProfile profile = provider.create(UserProfileContext.ACCOUNT, user);
@ -388,15 +387,17 @@ public class UserProfileTest extends AbstractUserProfileTest {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testGetProfileAttributes); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testGetProfileAttributes);
} }
private static void testGetProfileAttributes(KeycloakSession session) { private static void testGetProfileAttributes(KeycloakSession session) throws IOException {
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().addUser(realm, org.keycloak.models.utils.KeycloakModelUtils.generateId()); UserModel user = session.users().addUser(realm, org.keycloak.models.utils.KeycloakModelUtils.generateId());
user.setFirstName("John"); user.setFirstName("John");
user.setLastName("John"); user.setLastName("John");
user.setEmail(org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org"); user.setEmail(org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [{\"name\": \"address\", \"required\": {}, \"permissions\": {\"edit\": [\"user\"]}}]}"); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
config.addOrReplaceAttribute(new UPAttribute("address", new UPAttributePermissions(Set.of(), Set.of(ROLE_USER)), new UPAttributeRequired()));
provider.setConfiguration(JsonSerialization.writeValueAsString(config));
UserProfile profile = provider.create(UserProfileContext.ACCOUNT, user); UserProfile profile = provider.create(UserProfileContext.ACCOUNT, user);
Attributes attributes = profile.getAttributes(); Attributes attributes = profile.getAttributes();
@ -433,38 +434,21 @@ public class UserProfileTest extends AbstractUserProfileTest {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testGetProfileAttributeGroups); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testGetProfileAttributeGroups);
} }
private static void testGetProfileAttributeGroups(KeycloakSession session) { private static void testGetProfileAttributeGroups(KeycloakSession session) throws IOException {
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().addUser(realm, org.keycloak.models.utils.KeycloakModelUtils.generateId()); UserModel user = session.users().addUser(realm, org.keycloak.models.utils.KeycloakModelUtils.generateId());
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
String configuration = "{\n" + UPGroup companyAddress = new UPGroup("companyaddress");
" \"attributes\": [\n" + companyAddress.setDisplayHeader("header");
" {\n" + companyAddress.setDisplayDescription("description");
" \"name\": \"address\",\n" + config.addGroup(companyAddress);
" \"group\": \"companyaddress\"\n" + config.addOrReplaceAttribute(new UPAttribute("address", companyAddress));
" },\n" + UPGroup groupWithAnnotation = new UPGroup("groupwithanno");
" {\n" + groupWithAnnotation.setAnnotations(Map.of("anno1", "value1", "anno2", "value2"));
" \"name\": \"second\",\n" + config.addGroup(groupWithAnnotation);
" \"group\": \"groupwithanno" + "\"\n" + config.addOrReplaceAttribute(new UPAttribute("second", groupWithAnnotation));
" }\n" + provider.setConfiguration(JsonSerialization.writeValueAsString(config));
" ],\n" +
" \"groups\": [\n" +
" {\n" +
" \"name\": \"companyaddress\",\n" +
" \"displayHeader\": \"header\",\n" +
" \"displayDescription\": \"description\"\n" +
" },\n" +
" {\n" +
" \"name\": \"groupwithanno\",\n" +
" \"annotations\": {\n" +
" \"anno1\": \"value1\",\n" +
" \"anno2\": \"value2\"\n" +
" }\n" +
" }\n" +
" ]\n" +
"}\n";
provider.setConfiguration(configuration);
UserProfile profile = provider.create(UserProfileContext.ACCOUNT, user); UserProfile profile = provider.create(UserProfileContext.ACCOUNT, user);
Attributes attributes = profile.getAttributes(); Attributes attributes = profile.getAttributes();
@ -498,20 +482,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = provider.getConfiguration(); UPConfig config = provider.getConfiguration();
UPAttribute attribute = new UPAttribute(); config.addOrReplaceAttribute(new UPAttribute("address", new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN, ROLE_USER))));
attribute.setName("address"); config.addOrReplaceAttribute(new UPAttribute("business.address", new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN, ROLE_USER))));
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(new HashSet<>(Arrays.asList("admin", "user")));
attribute.setPermissions(permissions);
config.addAttribute(attribute);
attribute = new UPAttribute();
attribute.setName("business.address");
permissions = new UPAttributePermissions();
permissions.setEdit(new HashSet<>(Arrays.asList("admin", "user")));
attribute.setPermissions(permissions);
config.addAttribute(attribute);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
@ -572,7 +544,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testReadonlyUpdates); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testReadonlyUpdates);
} }
private static void testReadonlyUpdates(KeycloakSession session) { private static void testReadonlyUpdates(KeycloakSession session) throws IOException {
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId()); attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
@ -583,8 +555,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put("department", Arrays.asList("sales")); attributes.put("department", Arrays.asList("sales"));
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
provider.setConfiguration("{\"attributes\": [{\"name\": \"department\", \"permissions\": {\"edit\": [\"admin\"]}}]}"); config.addOrReplaceAttribute(new UPAttribute("department", new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN))));
provider.setConfiguration(JsonSerialization.writeValueAsString(config));
UserProfile profile = provider.create(UserProfileContext.ACCOUNT, attributes); UserProfile profile = provider.create(UserProfileContext.ACCOUNT, attributes);
UserModel user = profile.create(); UserModel user = profile.create();
@ -625,7 +598,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testReadonlyEmailCannotBeUpdated); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testReadonlyEmailCannotBeUpdated);
} }
private static void testReadonlyEmailCannotBeUpdated(KeycloakSession session) { private static void testReadonlyEmailCannotBeUpdated(KeycloakSession session) throws IOException {
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId()); attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
@ -634,9 +607,11 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.EMAIL, "readonly@foo.bar"); attributes.put(UserModel.EMAIL, "readonly@foo.bar");
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
config.addOrReplaceAttribute(new UPAttribute("email", new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN))));
// configure email r/o for user // configure email r/o for user
provider.setConfiguration("{\"attributes\": [{\"name\": \"email\", \"permissions\": {\"edit\": [ \"admin\"]}}]}"); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
UserProfile profile = provider.create(UserProfileContext.ACCOUNT, attributes); UserProfile profile = provider.create(UserProfileContext.ACCOUNT, attributes);
UserModel user = profile.create(); UserModel user = profile.create();
@ -653,15 +628,18 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.EMAIL, "cannot-change@foo.bar"); attributes.put(UserModel.EMAIL, "cannot-change@foo.bar");
profile = provider.create(UserProfileContext.ACCOUNT, attributes, user); profile = provider.create(UserProfileContext.ACCOUNT, attributes, user);
try { try {
profile.update(); profile.update();
fail("Should fail since email is read only"); fail("Should fail since email is read only");
} catch (ValidationException ve) { } catch (ValidationException ve) {
assertTrue(ve.isAttributeOnError("email")); assertTrue(ve.isAttributeOnError("email"));
} }
assertEquals("E-Mail address shouldn't be changed", "readonly@foo.bar", user.getEmail()); assertEquals("E-Mail address shouldn't be changed", "readonly@foo.bar", user.getEmail());
attributes.put(UserModel.EMAIL, "admin-can-change@foo.bar");
profile = provider.create(UserProfileContext.USER_API, attributes, user);
profile.update();
assertEquals("admin-can-change@foo.bar", user.getEmail());
} }
@Test @Test
@ -669,7 +647,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testUpdateEmail); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testUpdateEmail);
} }
private static void testUpdateEmail(KeycloakSession session) { private static void testUpdateEmail(KeycloakSession session) throws IOException {
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId()); attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
@ -678,9 +656,12 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.EMAIL, "canchange@foo.bar"); attributes.put(UserModel.EMAIL, "canchange@foo.bar");
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
config.getAttribute("email").getPermissions().setEdit(Set.of(ROLE_USER, ROLE_ADMIN));
// configure email r/w for user // configure email r/w for user
provider.setConfiguration("{\"attributes\": [{\"name\": \"email\", \"permissions\": {\"edit\": [ \"user\", \"admin\"]}}]}"); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
UserProfile profile = provider.create(UserProfileContext.ACCOUNT, attributes); UserProfile profile = provider.create(UserProfileContext.ACCOUNT, attributes);
UserModel user = profile.create(); UserModel user = profile.create();
@ -708,7 +689,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testDoNotUpdateUndefinedAttributes); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testDoNotUpdateUndefinedAttributes);
} }
private static void testDoNotUpdateUndefinedAttributes(KeycloakSession session) { private static void testDoNotUpdateUndefinedAttributes(KeycloakSession session) throws IOException {
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId()); attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
@ -720,10 +701,12 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put("phone", Arrays.asList("fixed-phone")); attributes.put("phone", Arrays.asList("fixed-phone"));
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
config.addOrReplaceAttribute(new UPAttribute("department", new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN))));
config.addOrReplaceAttribute(new UPAttribute("phone", new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN))));
config.addOrReplaceAttribute(new UPAttribute("address", new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN))));
provider.setConfiguration(JsonSerialization.writeValueAsString(config));
provider.setConfiguration("{\"attributes\": [{\"name\": \"department\", \"permissions\": {\"edit\": [\"admin\"]}},"
+ "{\"name\": \"phone\", \"permissions\": {\"edit\": [\"admin\"]}},"
+ "{\"name\": \"address\", \"permissions\": {\"edit\": [\"admin\"]}}]}");
UserProfile profile = provider.create(UserProfileContext.ACCOUNT, attributes); UserProfile profile = provider.create(UserProfileContext.ACCOUNT, attributes);
UserModel user = profile.create(); UserModel user = profile.create();
assertThat(profile.getAttributes().nameSet(), assertThat(profile.getAttributes().nameSet(),
@ -737,8 +720,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
profile.update((attributeName, userModel, oldValue) -> assertTrue(attributesUpdated.add(attributeName))); profile.update((attributeName, userModel, oldValue) -> assertTrue(attributesUpdated.add(attributeName)));
assertThat(attributesUpdated, containsInAnyOrder("department", "address", "phone")); assertThat(attributesUpdated, containsInAnyOrder("department", "address", "phone"));
provider.setConfiguration("{\"attributes\": [{\"name\": \"department\", \"permissions\": {\"edit\": [\"admin\"]}}," config.removeAttribute("address");
+ "{\"name\": \"phone\", \"permissions\": {\"edit\": [\"admin\"]}}]}"); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
attributesUpdated.clear(); attributesUpdated.clear();
attributes.remove("address"); attributes.remove("address");
attributes.put("department", "foo"); attributes.put("department", "foo");
@ -748,9 +731,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
assertThat(attributesUpdated, containsInAnyOrder("department", "phone")); assertThat(attributesUpdated, containsInAnyOrder("department", "phone"));
assertTrue(user.getAttributes().containsKey("address")); assertTrue(user.getAttributes().containsKey("address"));
provider.setConfiguration("{\"attributes\": [{\"name\": \"department\", \"permissions\": {\"edit\": [\"admin\"]}}," config.addOrReplaceAttribute(new UPAttribute("address", new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN))));
+ "{\"name\": \"phone\", \"permissions\": {\"edit\": [\"admin\"]}}," provider.setConfiguration(JsonSerialization.writeValueAsString(config));
+ "{\"name\": \"address\", \"permissions\": {\"edit\": [\"admin\"]}}]}");
attributes.put("department", "foo"); attributes.put("department", "foo");
attributes.put("phone", "foo"); attributes.put("phone", "foo");
attributes.put("address", "bar"); attributes.put("address", "bar");
@ -875,10 +857,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testCustomValidationForUsername(KeycloakSession session) throws IOException { private static void testCustomValidationForUsername(KeycloakSession session) throws IOException {
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute(UserModel.USERNAME);
attribute.setName(UserModel.USERNAME);
Map<String, Object> validatorConfig = new HashMap<>(); Map<String, Object> validatorConfig = new HashMap<>();
@ -886,7 +866,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
attribute.addValidation(LengthValidator.ID, validatorConfig); attribute.addValidation(LengthValidator.ID, validatorConfig);
config.addAttribute(attribute); config.addOrReplaceAttribute(attribute);
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
@ -916,7 +896,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
provider.setConfiguration(null); provider.setConfiguration(null);
attributes.put(UserModel.USERNAME, "user"); attributes.put(UserModel.USERNAME, ROLE_USER);
attributes.put(UserModel.EMAIL, "user@keycloak.org"); attributes.put(UserModel.EMAIL, "user@keycloak.org");
attributes.put(UserModel.FIRST_NAME, "Joe"); attributes.put(UserModel.FIRST_NAME, "Joe");
attributes.put(UserModel.LAST_NAME, "Doe"); attributes.put(UserModel.LAST_NAME, "Doe");
@ -976,18 +956,18 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testOptionalAttributes(KeycloakSession session) throws IOException { private static void testOptionalAttributes(KeycloakSession session) throws IOException {
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
attribute.setName(UserModel.FIRST_NAME); attribute.setName(UserModel.FIRST_NAME);
Map<String, Object> validatorConfig = new HashMap<>(); Map<String, Object> validatorConfig = new HashMap<>();
validatorConfig.put(LengthValidator.KEY_MAX, 4); validatorConfig.put(LengthValidator.KEY_MAX, 4);
attribute.addValidation(LengthValidator.ID, validatorConfig); attribute.addValidation(LengthValidator.ID, validatorConfig);
config.addAttribute(attribute); config.addOrReplaceAttribute(attribute);
attribute = new UPAttribute(); attribute = new UPAttribute();
attribute.setName(UserModel.LAST_NAME); attribute.setName(UserModel.LAST_NAME);
attribute.addValidation(LengthValidator.ID, validatorConfig); attribute.addValidation(LengthValidator.ID, validatorConfig);
config.addAttribute(attribute); config.addOrReplaceAttribute(attribute);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
@ -1034,7 +1014,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testCustomAttributeRequired(KeycloakSession session) throws IOException { private static void testCustomAttributeRequired(KeycloakSession session) throws IOException {
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
attribute.setName(ATT_ADDRESS); attribute.setName(ATT_ADDRESS);
@ -1053,7 +1033,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
permissions.setEdit(Collections.singleton(ROLE_USER)); permissions.setEdit(Collections.singleton(ROLE_USER));
attribute.setPermissions(permissions); attribute.setPermissions(permissions);
config.addAttribute(attribute); config.addOrReplaceAttribute(attribute);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
@ -1100,7 +1080,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testCustomAttributeOptional(KeycloakSession session) throws IOException { private static void testCustomAttributeOptional(KeycloakSession session) throws IOException {
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
attribute.setName(ATT_ADDRESS); attribute.setName(ATT_ADDRESS);
@ -1109,7 +1089,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
validatorConfig.put(LengthValidator.KEY_MIN, 4); validatorConfig.put(LengthValidator.KEY_MIN, 4);
attribute.addValidation(LengthValidator.ID, validatorConfig); attribute.addValidation(LengthValidator.ID, validatorConfig);
config.addAttribute(attribute); config.addOrReplaceAttribute(attribute);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
@ -1152,23 +1132,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testRequiredIfUser(KeycloakSession session) throws IOException { private static void testRequiredIfUser(KeycloakSession session) throws IOException {
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); config.addOrReplaceAttribute(new UPAttribute(ATT_ADDRESS, new UPAttributePermissions(Set.of(), Set.of(ROLE_USER)), new UPAttributeRequired(Set.of(ROLE_USER), Set.of())));
attribute.setName(ATT_ADDRESS);
UPAttributeRequired requirements = new UPAttributeRequired();
requirements.setRoles(Collections.singleton(ROLE_USER));
attribute.setRequired(requirements);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Collections.singleton(ROLE_USER));
attribute.setPermissions(permissions);
config.addAttribute(attribute);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
@ -1215,23 +1180,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testRequiredIfAdmin(KeycloakSession session) throws IOException { private static void testRequiredIfAdmin(KeycloakSession session) throws IOException {
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); config.addOrReplaceAttribute(new UPAttribute(ATT_ADDRESS, new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN)), new UPAttributeRequired(Set.of(ROLE_ADMIN), Set.of())));
attribute.setName(ATT_ADDRESS);
UPAttributeRequired requirements = new UPAttributeRequired();
requirements.setRoles(Collections.singleton(ROLE_ADMIN));
attribute.setRequired(requirements);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Collections.singleton(UPConfigUtils.ROLE_ADMIN));
attribute.setPermissions(permissions);
config.addAttribute(attribute);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
@ -1269,20 +1219,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testNoValidationsIfUserReadOnly(KeycloakSession session) throws IOException { private static void testNoValidationsIfUserReadOnly(KeycloakSession session) throws IOException {
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); config.addOrReplaceAttribute(new UPAttribute(ATT_ADDRESS, new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN)), new UPAttributeRequired()));
attribute.setName(ATT_ADDRESS);
UPAttributeRequired requirements = new UPAttributeRequired();
attribute.setRequired(requirements);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Collections.singleton(UPConfigUtils.ROLE_ADMIN));
attribute.setPermissions(permissions);
config.addAttribute(attribute);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
@ -1314,20 +1252,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testNoValidationsIfAdminReadOnly(KeycloakSession session) throws IOException { private static void testNoValidationsIfAdminReadOnly(KeycloakSession session) throws IOException {
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); config.addOrReplaceAttribute(new UPAttribute(ATT_ADDRESS, new UPAttributePermissions(Set.of(), Set.of(ROLE_USER)), new UPAttributeRequired()));
attribute.setName(ATT_ADDRESS);
UPAttributeRequired requirements = new UPAttributeRequired();
attribute.setRequired(requirements);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Collections.singleton(UPConfigUtils.ROLE_USER));
attribute.setPermissions(permissions);
config.addAttribute(attribute);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
@ -1355,29 +1281,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testIgnoreReadOnlyAttribute(KeycloakSession session) throws IOException { private static void testIgnoreReadOnlyAttribute(KeycloakSession session) throws IOException {
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute firstName = new UPAttribute(); config.addOrReplaceAttribute(new UPAttribute(ATT_ADDRESS, new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER)), new UPAttributeRequired(Set.of(ROLE_USER), Set.of())));
config.addOrReplaceAttribute(new UPAttribute(UserModel.FIRST_NAME, new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER)), new UPAttributeRequired(Set.of(ROLE_USER), Set.of())));
firstName.setName(UserModel.FIRST_NAME);
UPAttribute address = new UPAttribute();
address.setName(ATT_ADDRESS);
UPAttributeRequired requirements = new UPAttributeRequired();
requirements.setRoles(Collections.singleton(UPConfigUtils.ROLE_USER));
address.setRequired(requirements);
firstName.setRequired(requirements);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Collections.singleton(UPConfigUtils.ROLE_USER));
permissions.setView(Collections.singleton(ROLE_ADMIN));
address.setPermissions(permissions);
firstName.setPermissions(permissions);
config.addAttribute(address);
config.addAttribute(firstName);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
@ -1451,23 +1357,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testRequiredByClientScope(KeycloakSession session) throws IOException { private static void testRequiredByClientScope(KeycloakSession session) throws IOException {
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); config.addOrReplaceAttribute(new UPAttribute(ATT_ADDRESS, new UPAttributePermissions(Set.of(), Set.of(ROLE_USER)), new UPAttributeRequired(Set.of(), Set.of("client-a"))));
attribute.setName(ATT_ADDRESS);
UPAttributeRequired requirements = new UPAttributeRequired();
requirements.setScopes(Collections.singleton("client-a"));
attribute.setRequired(requirements);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Collections.singleton("user"));
attribute.setPermissions(permissions);
config.addAttribute(attribute);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
@ -1534,23 +1425,10 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testConfigurationInvalidScope(KeycloakSession session) throws IOException { private static void testConfigurationInvalidScope(KeycloakSession session) throws IOException {
RealmModel realm = session.getContext().getRealm();
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); config.addOrReplaceAttribute(new UPAttribute(ATT_ADDRESS, new UPAttributePermissions(Set.of(), Set.of(ROLE_USER)),
new UPAttributeRequired(Set.of(), Set.of("invalid")), new UPAttributeSelector(Set.of("invalid"))));
attribute.setName(ATT_ADDRESS);
UPAttributeRequired requirements = new UPAttributeRequired();
requirements.setScopes(Collections.singleton("invalid"));
attribute.setRequired(requirements);
attribute.setSelector(new UPAttributeSelector());
attribute.getSelector().setScopes(Collections.singleton("invalid"));
config.addAttribute(attribute);
try { try {
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
@ -1599,7 +1477,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testDoNotRemoveAttributes); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testDoNotRemoveAttributes);
} }
private static void testDoNotRemoveAttributes(KeycloakSession session) { private static void testDoNotRemoveAttributes(KeycloakSession session) throws IOException {
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId()); attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
@ -1608,11 +1486,14 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put("foo", Arrays.asList("foo")); attributes.put("foo", Arrays.asList("foo"));
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
config.removeAttribute(UserModel.FIRST_NAME);
config.removeAttribute(UserModel.LAST_NAME);
config.addOrReplaceAttribute(new UPAttribute("test-attribute", new UPAttributePermissions(Set.of(), Set.of(ROLE_USER, ROLE_ADMIN))));
config.addOrReplaceAttribute(new UPAttribute("foo", new UPAttributePermissions(Set.of(), Set.of(ROLE_USER, ROLE_ADMIN))));
config.addOrReplaceAttribute(new UPAttribute("email", new UPAttributePermissions(Set.of(), Set.of(ROLE_USER, ROLE_ADMIN))));
provider.setConfiguration("{\"attributes\": [" provider.setConfiguration(JsonSerialization.writeValueAsString(config));
+ "{\"name\": \"test-attribute\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}},"
+ "{\"name\": \"foo\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}},"
+ "{\"name\": \"email\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}}]}");
UserProfile profile = provider.create(UserProfileContext.USER_API, attributes); UserProfile profile = provider.create(UserProfileContext.USER_API, attributes);
UserModel user = profile.create(); UserModel user = profile.create();
@ -1650,10 +1531,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
assertEquals("Test Value", userAttributes.getFirstValue("test-attribute")); assertEquals("Test Value", userAttributes.getFirstValue("test-attribute"));
assertNull(userAttributes.getFirstValue("foo")); assertNull(userAttributes.getFirstValue("foo"));
provider.setConfiguration("{\"attributes\": [" config.addOrReplaceAttribute(new UPAttribute("test-attribute", new UPAttributePermissions(Set.of(), Set.of(ROLE_USER))));
+ "{\"name\": \"test-attribute\", \"permissions\": {\"edit\": [\"user\"]}}," provider.setConfiguration(JsonSerialization.writeValueAsString(config));
+ "{\"name\": \"foo\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}},"
+ "{\"name\": \"email\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}}]}");
attributes.remove("test-attribute"); attributes.remove("test-attribute");
profile = provider.create(UserProfileContext.USER_API, attributes, user); profile = provider.create(UserProfileContext.USER_API, attributes, user);
profile.update(true); profile.update(true);
@ -1663,10 +1542,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
assertEquals("new-email@test.com", userAttributes.getFirstValue(UserModel.EMAIL)); assertEquals("new-email@test.com", userAttributes.getFirstValue(UserModel.EMAIL));
assertEquals("Test Value", userAttributes.getFirstValue("test-attribute")); assertEquals("Test Value", userAttributes.getFirstValue("test-attribute"));
provider.setConfiguration("{\"attributes\": [" config.addOrReplaceAttribute(new UPAttribute("test-attribute", new UPAttributePermissions(Set.of(), Set.of(ROLE_USER, ROLE_ADMIN))));
+ "{\"name\": \"test-attribute\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}}," provider.setConfiguration(JsonSerialization.writeValueAsString(config));
+ "{\"name\": \"foo\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}},"
+ "{\"name\": \"email\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}}]}");
attributes.remove("test-attribute"); attributes.remove("test-attribute");
profile = provider.create(UserProfileContext.USER_API, attributes, user); profile = provider.create(UserProfileContext.USER_API, attributes, user);
profile.update(true); profile.update(true);
@ -1682,7 +1559,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testRemoveEmptyRootAttribute); getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testRemoveEmptyRootAttribute);
} }
private static void testRemoveEmptyRootAttribute(KeycloakSession session) { private static void testRemoveEmptyRootAttribute(KeycloakSession session) throws IOException {
Map<String, List<String>> attributes = new HashMap<>(); Map<String, List<String>> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, List.of(org.keycloak.models.utils.KeycloakModelUtils.generateId())); attributes.put(UserModel.USERNAME, List.of(org.keycloak.models.utils.KeycloakModelUtils.generateId()));
@ -1691,12 +1568,12 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put("test-attribute", List.of("")); attributes.put("test-attribute", List.of(""));
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = parseDefaultConfig();
provider.setConfiguration("{\"attributes\": [" config.addOrReplaceAttribute(new UPAttribute("test-attribute", new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN, ROLE_USER))));
+ "{\"name\": \"test-attribute\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}}," config.addOrReplaceAttribute(new UPAttribute(UserModel.FIRST_NAME, new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN, ROLE_USER))));
+ "{\"name\": \"firstName\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}}," config.addOrReplaceAttribute(new UPAttribute(UserModel.LAST_NAME, new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN, ROLE_USER))));
+ "{\"name\": \"lastName\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}}," config.addOrReplaceAttribute(new UPAttribute(UserModel.EMAIL, new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN, ROLE_USER))));
+ "{\"name\": \"email\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}}]}"); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
UserProfile profile = provider.create(UserProfileContext.USER_API, attributes); UserProfile profile = provider.create(UserProfileContext.USER_API, attributes);
UserModel user = profile.create(); UserModel user = profile.create();
@ -1730,12 +1607,10 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testRemoveOptionalAttributesFromDefaultConfigIfNotSet(KeycloakSession session) throws IOException { private static void testRemoveOptionalAttributesFromDefaultConfigIfNotSet(KeycloakSession session) throws IOException {
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute attribute = new UPAttribute(); config.addOrReplaceAttribute(new UPAttribute("foo"));
config.removeAttribute(UserModel.FIRST_NAME);
attribute.setName("foo"); config.removeAttribute(UserModel.LAST_NAME);
config.addAttribute(attribute);
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
@ -1754,10 +1629,10 @@ public class UserProfileTest extends AbstractUserProfileTest {
UPAttribute firstName = new UPAttribute(); UPAttribute firstName = new UPAttribute();
firstName.setName(UserModel.FIRST_NAME); firstName.setName(UserModel.FIRST_NAME);
config.addAttribute(firstName); config.addOrReplaceAttribute(firstName);
UPAttribute lastName = new UPAttribute(); UPAttribute lastName = new UPAttribute();
lastName.setName(UserModel.LAST_NAME); lastName.setName(UserModel.LAST_NAME);
config.addAttribute(lastName); config.addOrReplaceAttribute(lastName);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes, user); profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes, user);
assertTrue(profile.getAttributes().contains(UserModel.FIRST_NAME)); assertTrue(profile.getAttributes().contains(UserModel.FIRST_NAME));
@ -1770,16 +1645,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testUnmanagedPolicy(KeycloakSession session) throws IOException { private static void testUnmanagedPolicy(KeycloakSession session) throws IOException {
UPConfig config = new UPConfig(); UPConfig config = parseDefaultConfig();
UPAttribute bar = new UPAttribute("bar"); config.addOrReplaceAttribute(new UPAttribute("bar", new UPAttributePermissions(Set.of(), Set.of(ROLE_USER, ROLE_ADMIN))));
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Set.of("user", "admin"));
bar.setPermissions(permissions);
config.addAttribute(bar);
UserProfileProvider provider = getUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));

View file

@ -198,7 +198,7 @@ public class UPConfigParserTest extends AbstractTestRealmKeycloakTest {
UPConfig config = loadValidConfig(); UPConfig config = loadValidConfig();
//we run this test without KeycloakSession so validator configs are not validated here //we run this test without KeycloakSession so validator configs are not validated here
UPAttribute attConfig = config.getAttributes().get(1); UPAttribute attConfig = config.getAttributes().get(2);
attConfig.setName(null); attConfig.setName(null);
List<String> errors = validate(session, config); List<String> errors = validate(session, config);
@ -209,7 +209,7 @@ public class UPConfigParserTest extends AbstractTestRealmKeycloakTest {
Assert.assertEquals(1, errors.size()); Assert.assertEquals(1, errors.size());
// duplicate attribute name // duplicate attribute name
attConfig.setName("firstName"); attConfig.setName("lastName");
errors = validate(session, config); errors = validate(session, config);
Assert.assertEquals(1, errors.size()); Assert.assertEquals(1, errors.size());