Avoid creating the component when there is no component and configuration is not provided

Closes #20970

Co-authored-by: Pedro Igor <psilva@redhat.com>
This commit is contained in:
Pedro Igor 2023-10-05 10:08:45 -03:00 committed by Alexander Schwartz
parent 9df1c781eb
commit 7385ed56c7
7 changed files with 208 additions and 176 deletions

View file

@ -27,10 +27,13 @@ import java.io.ByteArrayInputStream;
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.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -103,6 +106,7 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
} }
private String defaultRawConfig; private String defaultRawConfig;
private static final Map<UserProfileContext, UserProfileMetadata> DEFAULT_METADATA = Collections.synchronizedMap(new HashMap<>());
public DeclarativeUserProfileProvider() { public DeclarativeUserProfileProvider() {
defaultRawConfig = UPConfigUtils.readDefaultConfig(); defaultRawConfig = UPConfigUtils.readDefaultConfig();
@ -154,16 +158,21 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
return decoratedMetadata; return decoratedMetadata;
} }
ComponentModel model = getComponentModelOrCreate(session); ComponentModel component = getComponentModel().orElse(null);
Map<UserProfileContext, UserProfileMetadata> metadataMap = model.getNote(PARSED_CONFIG_COMPONENT_KEY);
if (component == null) {
return DEFAULT_METADATA.computeIfAbsent(context, (c) -> decorateUserProfileForCache(decoratedMetadata, getParsedConfig(defaultRawConfig)));
}
Map<UserProfileContext, UserProfileMetadata> metadataMap = component.getNote(PARSED_CONFIG_COMPONENT_KEY);
// not cached, create a note with cache // not cached, create a note with cache
if (metadataMap == null) { if (metadataMap == null) {
metadataMap = new ConcurrentHashMap<>(); metadataMap = new ConcurrentHashMap<>();
model.setNote(PARSED_CONFIG_COMPONENT_KEY, metadataMap); component.setNote(PARSED_CONFIG_COMPONENT_KEY, metadataMap);
} }
return metadataMap.computeIfAbsent(context, (c) -> decorateUserProfileForCache(decoratedMetadata, model)); return metadataMap.computeIfAbsent(context, createUserDefinedProfileDecorator(session, decoratedMetadata, component));
} }
@Override @Override
@ -203,7 +212,10 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
return defaultRawConfig; return defaultRawConfig;
} }
String cfg = getConfigJsonFromComponentModel(getComponentModel()); Optional<ComponentModel> component = getComponentModel();
if (component.isPresent()) {
String cfg = getConfigJsonFromComponentModel(component.get());
if (isBlank(cfg)) { if (isBlank(cfg)) {
return defaultRawConfig; return defaultRawConfig;
@ -212,15 +224,27 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
return cfg; return cfg;
} }
return defaultRawConfig;
}
@Override @Override
public void setConfiguration(String configuration) { public void setConfiguration(String configuration) {
ComponentModel component = getComponentModel(); RealmModel realm = session.getContext().getRealm();
Optional<ComponentModel> optionalComponent = realm.getComponentsStream(realm.getId(), UserProfileProvider.class.getName()).findAny();
if (isBlank(configuration) && !optionalComponent.isPresent()) {
return;
}
ComponentModel component = optionalComponent.isPresent() ? optionalComponent.get() : createComponentModel();
removeConfigJsonFromComponentModel(component); removeConfigJsonFromComponentModel(component);
RealmModel realm = session.getContext().getRealm(); if (isBlank(configuration)) {
realm.removeComponent(component);
return;
}
if (!isBlank(configuration)) {
// store new parts // store new parts
List<String> parts = UPConfigUtils.getChunks(configuration, 3800); List<String> parts = UPConfigUtils.getChunks(configuration, 3800);
MultivaluedHashMap<String, String> config = component.getConfig(); MultivaluedHashMap<String, String> config = component.getConfig();
@ -234,9 +258,6 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
} }
realm.updateComponent(component); realm.updateComponent(component);
} else {
realm.removeComponent(component);
}
} }
@Override @Override
@ -255,22 +276,18 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
return PROVIDER_PRIORITY; return PROVIDER_PRIORITY;
} }
public ComponentModel getComponentModel() { private Optional<ComponentModel> getComponentModel() {
return getComponentModelOrCreate(session); RealmModel realm = session.getContext().getRealm();
return realm.getComponentsStream(realm.getId(), UserProfileProvider.class.getName()).findAny();
} }
/** /**
* Decorate basic metadata provided from {@link AbstractUserProfileProvider} based on 'per realm' configuration. * Decorate basic metadata provided from {@link AbstractUserProfileProvider} based on 'per realm' configuration.
* This method is called for each {@link UserProfileContext} in each realm, and metadata are cached then and this * This method is called for each {@link UserProfileContext} in each realm, and metadata are cached then and this
* method is called again only if configuration changes. * method is called again only if configuration changes.
*
* @param decoratedMetadata base to be decorated based on configuration loaded from component model
* @param model component model to get "per realm" configuration from
* @return decorated metadata
*/ */
protected UserProfileMetadata decorateUserProfileForCache(UserProfileMetadata decoratedMetadata, ComponentModel model) { protected UserProfileMetadata decorateUserProfileForCache(UserProfileMetadata decoratedMetadata, UPConfig parsedConfig) {
UserProfileContext context = decoratedMetadata.getContext(); UserProfileContext context = decoratedMetadata.getContext();
UPConfig parsedConfig = getParsedConfig(model);
// do not change config for REGISTRATION_USER_CREATION context, everything important is covered thanks to REGISTRATION_PROFILE // do not change config for REGISTRATION_USER_CREATION context, everything important is covered thanks to REGISTRATION_PROFILE
// do not change config for UPDATE_EMAIL context, validations are already set and do not need including anything else from the configuration // do not change config for UPDATE_EMAIL context, validations are already set and do not need including anything else from the configuration
@ -282,7 +299,6 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
} }
Map<String, UPGroup> groupsByName = asHashMap(parsedConfig.getGroups()); Map<String, UPGroup> groupsByName = asHashMap(parsedConfig.getGroups());
RealmModel realm = session.getContext().getRealm();
int guiOrder = 0; int guiOrder = 0;
for (UPAttribute attrConfig : parsedConfig.getAttributes()) { for (UPAttribute attrConfig : parsedConfig.getAttributes()) {
@ -346,6 +362,8 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
guiOrder++; guiOrder++;
validators.add(new AttributeValidatorMetadata(ImmutableAttributeValidator.ID));
if (isBuiltInAttribute(attributeName)) { if (isBuiltInAttribute(attributeName)) {
// make sure username and email are writable if permissions are not set // make sure username and email are writable if permissions are not set
if (permissions == null || permissions.isEmpty()) { if (permissions == null || permissions.isEmpty()) {
@ -381,14 +399,6 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
} }
} }
// Add ImmutableAttributeValidator to ensure that attributes that are configured
// as read-only are marked as such.
// Skip this for username in realms with username = email to allow change of email
// address on initial login with profile via idp
if (!realm.isRegistrationEmailAsUsername() && UserModel.EMAIL.equals(attributeName)) {
validators.add(new AttributeValidatorMetadata(ImmutableAttributeValidator.ID));
}
List<AttributeMetadata> existingMetadata = decoratedMetadata.getAttribute(attributeName); List<AttributeMetadata> existingMetadata = decoratedMetadata.getAttribute(attributeName);
if (existingMetadata.isEmpty()) { if (existingMetadata.isEmpty()) {
@ -406,7 +416,6 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
.setRequired(required); .setRequired(required);
} }
} else { } else {
validators.add(new AttributeValidatorMetadata(ImmutableAttributeValidator.ID));
decoratedMetadata.addAttribute(attributeName, guiOrder, validators, selector, writeAllowed, required, readAllowed) decoratedMetadata.addAttribute(attributeName, guiOrder, validators, selector, writeAllowed, required, readAllowed)
.addAnnotations(annotations) .addAnnotations(annotations)
.setAttributeDisplayName(attrConfig.getDisplayName()) .setAttributeDisplayName(attrConfig.getDisplayName())
@ -440,24 +449,11 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
/** /**
* Get parsed config file configured in model. Default one used if not configured. * Get parsed config file configured in model. Default one used if not configured.
*
* @param model to take config from
* @return parsed configuration
*/ */
protected UPConfig getParsedConfig(ComponentModel model) { protected UPConfig getParsedConfig(String rawConfig) {
String rawConfig = getConfigJsonFromComponentModel(model);
if (!isBlank(rawConfig)) { if (!isBlank(rawConfig)) {
try { try {
UPConfig upc = parseConfig(rawConfig); return parseConfig(rawConfig);
//validate configuration to catch things like changed/removed validators etc, and warn early and clearly about this problem
List<String> errors = UPConfigUtils.validate(session, upc);
if (!errors.isEmpty()) {
throw new RuntimeException("UserProfile configuration for realm '" + session.getContext().getRealm().getName() + "' is invalid: " + errors.toString());
}
return upc;
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("UserProfile configuration for realm '" + session.getContext().getRealm().getName() + "' is invalid:" + e.getMessage(), e); throw new RuntimeException("UserProfile configuration for realm '" + session.getContext().getRealm().getName() + "' is invalid:" + e.getMessage(), e);
} }
@ -470,23 +466,13 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
return readConfig(new ByteArrayInputStream(rawConfig.getBytes("UTF-8"))); return readConfig(new ByteArrayInputStream(rawConfig.getBytes("UTF-8")));
} }
/**
* Get component to store our "per realm" configuration into.
*
* @param session to be used, and take realm from
* @return component
*/
private ComponentModel getComponentModelOrCreate(KeycloakSession session) {
RealmModel realm = session.getContext().getRealm();
return realm.getComponentsStream(realm.getId(), UserProfileProvider.class.getName()).findAny().orElseGet(() -> realm.addComponentModel(createComponentModel()));
}
/** /**
* Create the component model to store configuration * Create the component model to store configuration
* @return component model * @return component model
*/ */
protected ComponentModel createComponentModel() { protected ComponentModel createComponentModel() {
return new DeclarativeUserProfileModel(getId()); RealmModel realm = session.getContext().getRealm();
return realm.addComponentModel(new DeclarativeUserProfileModel(getId()));
} }
/** /**
@ -538,4 +524,18 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
public boolean isEnabled(RealmModel realm) { public boolean isEnabled(RealmModel realm) {
return isDeclarativeConfigurationEnabled && realm.getAttribute(REALM_USER_PROFILE_ENABLED, false); return isDeclarativeConfigurationEnabled && realm.getAttribute(REALM_USER_PROFILE_ENABLED, false);
} }
private Function<UserProfileContext, UserProfileMetadata> createUserDefinedProfileDecorator(KeycloakSession session, UserProfileMetadata decoratedMetadata, ComponentModel component) {
return (c) -> {
UPConfig parsedConfig = getParsedConfig(getConfigJsonFromComponentModel(component));
//validate configuration to catch things like changed/removed validators etc, and warn early and clearly about this problem
List<String> errors = UPConfigUtils.validate(session, parsedConfig);
if (!errors.isEmpty()) {
throw new RuntimeException("UserProfile configuration for realm '" + session.getContext().getRealm().getName() + "' is invalid: " + errors.toString());
}
return decorateUserProfileForCache(decoratedMetadata, parsedConfig);
};
}
} }

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.userprofile.validator; package org.keycloak.userprofile.validator;
import static org.keycloak.common.util.CollectionUtil.collectionEquals;
import static org.keycloak.validate.Validators.notBlankValidator; import static org.keycloak.validate.Validators.notBlankValidator;
import java.util.List; import java.util.List;
@ -23,8 +24,10 @@ import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.keycloak.common.util.CollectionUtil; import org.keycloak.common.util.CollectionUtil;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.userprofile.AttributeContext; import org.keycloak.userprofile.AttributeContext;
import org.keycloak.userprofile.AttributeValidatorMetadata;
import org.keycloak.userprofile.UserProfileAttributeValidationContext; import org.keycloak.userprofile.UserProfileAttributeValidationContext;
import org.keycloak.validate.SimpleValidator; import org.keycloak.validate.SimpleValidator;
import org.keycloak.validate.ValidationContext; import org.keycloak.validate.ValidationContext;
@ -61,10 +64,27 @@ public class ImmutableAttributeValidator implements SimpleValidator {
List<String> currentValue = user.getAttributeStream(inputHint).filter(Objects::nonNull).collect(Collectors.toList()); List<String> currentValue = user.getAttributeStream(inputHint).filter(Objects::nonNull).collect(Collectors.toList());
List<String> values = (List<String>) input; List<String> values = (List<String>) input;
if (!CollectionUtil.collectionEquals(currentValue, values) && isReadOnly(attributeContext)) { if (!collectionEquals(currentValue, values) && isReadOnly(attributeContext)) {
if (currentValue.isEmpty() && !notBlankValidator().validate(values).isValid()) { if (currentValue.isEmpty() && !notBlankValidator().validate(values).isValid()) {
return context; return context;
} }
RealmModel realm = ac.getSession().getContext().getRealm();
if (realm.isRegistrationEmailAsUsername()) {
String attributeName = attributeContext.getMetadata().getName();
if (UserModel.EMAIL.equals(attributeName)) {
return context;
}
List<String> email = attributeContext.getAttributes().getValues(UserModel.EMAIL);
if (UserModel.USERNAME.equals(attributeName) && collectionEquals(values, email)) {
return context;
}
}
context.addError(new ValidationError(ID, inputHint, DEFAULT_ERROR_MESSAGE)); context.addError(new ValidationError(ID, inputHint, DEFAULT_ERROR_MESSAGE));
} }

View file

@ -2476,12 +2476,23 @@ public class UserTest extends AbstractAdminTest {
@Test @Test
public void updateUserWithNewUsernameNotPossible() { public void updateUserWithNewUsernameNotPossible() {
RealmRepresentation realmRep = realm.toRepresentation();
assertFalse(realmRep.isEditUsernameAllowed());
String id = createUser(); String id = createUser();
UserResource user = realm.users().get(id); UserResource user = realm.users().get(id);
UserRepresentation userRep = user.toRepresentation(); UserRepresentation userRep = user.toRepresentation();
userRep.setUsername("user11"); userRep.setUsername("user11");
try {
updateUser(user, userRep); updateUser(user, userRep);
if (isDeclarativeUserProfile()) {
fail("Should fail because realm does not allow edit username");
}
} catch (BadRequestException expected) {
ErrorRepresentation error = expected.getResponse().readEntity(ErrorRepresentation.class);
assertEquals("Attribute username is read only.", error.getErrorMessage());
}
userRep = realm.users().get(id).toRepresentation(); userRep = realm.users().get(id).toRepresentation();
assertEquals("user1", userRep.getUsername()); assertEquals("user1", userRep.getUsername());

View file

@ -69,6 +69,7 @@ import org.keycloak.testsuite.util.OAuthClient.AccessTokenResponse;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.testsuite.utils.tls.TLSUtils; import org.keycloak.testsuite.utils.tls.TLSUtils;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.BadRequestException;
@ -87,6 +88,7 @@ import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
@ -978,4 +980,21 @@ public class RealmTest extends AbstractAdminTest {
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(clientDbId), client, ResourceType.CLIENT); assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(clientDbId), client, ResourceType.CLIENT);
} }
@Test
public void testNoUserProfileProviderComponentUponRealmChange() {
String realmName = "new-realm";
RealmRepresentation rep = new RealmRepresentation();
rep.setRealm(realmName);
adminClient.realms().create(rep);
getCleanup().addCleanup(() -> adminClient.realms().realm(realmName).remove());
assertThat(adminClient.realm(realmName).components().query(null, UserProfileProvider.class.getName()), empty());
rep.setDisplayName("displayName");
adminClient.realm(realmName).update(rep);
// this used to return non-empty collection
assertThat(adminClient.realm(realmName).components().query(null, UserProfileProvider.class.getName()), empty());
}
} }

View file

@ -25,9 +25,12 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.junit.Before;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -37,10 +40,11 @@ import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.userprofile.DeclarativeUserProfileProvider; import org.keycloak.testsuite.forms.VerifyProfileTest;
import org.keycloak.userprofile.UserProfileProvider; import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.userprofile.config.UPAttribute; import org.keycloak.userprofile.config.UPAttribute;
import org.keycloak.userprofile.config.UPConfig; import org.keycloak.userprofile.config.UPConfig;
import org.keycloak.userprofile.config.UPConfigUtils;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
/** /**
@ -63,12 +67,27 @@ public abstract class AbstractUserProfileTest extends AbstractTestRealmKeycloakT
session.getContext().setAuthenticationSession(createAuthenticationSession(realm.getClientByClientId(clientId), requestedScopes)); session.getContext().setAuthenticationSession(createAuthenticationSession(realm.getClientByClientId(clientId), requestedScopes));
} }
protected static DeclarativeUserProfileProvider getDynamicUserProfileProvider(KeycloakSession session) { protected static Optional<ComponentModel> setAndGetDefaultConfiguration(KeycloakSession session) {
UserProfileProvider provider = session.getProvider(UserProfileProvider.class); setDefaultConfiguration(session);
return getComponentModel(session);
}
provider.setConfiguration(null); protected static Optional<ComponentModel> getComponentModel(KeycloakSession session) {
RealmModel realm = session.getContext().getRealm();
return realm.getComponentsStream(realm.getId(), UserProfileProvider.class.getName()).findAny();
}
return (DeclarativeUserProfileProvider) provider; protected static void setDefaultConfiguration(KeycloakSession session) {
setConfiguration(session, UPConfigUtils.readDefaultConfig());
}
protected static void setConfiguration(KeycloakSession session, String config) {
UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration(config);
}
protected static UserProfileProvider getUserProfileProvider(KeycloakSession session) {
return session.getProvider(UserProfileProvider.class);
} }
/** /**
@ -272,4 +291,12 @@ public abstract class AbstractUserProfileTest extends AbstractTestRealmKeycloakT
} }
testRealm.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString()); testRealm.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
} }
@Before
public void resetConfigBeforeTest() {
VerifyProfileTest.disableDynamicUserProfile(testRealm());
RealmRepresentation realm = testRealm().toRepresentation();
VerifyProfileTest.enableDynamicUserProfile(realm);
testRealm().update(realm);
}
} }

View file

@ -20,7 +20,6 @@
package org.keycloak.testsuite.user.profile; package org.keycloak.testsuite.user.profile;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -35,10 +34,13 @@ import org.keycloak.testsuite.runonserver.RunOnServer;
import org.keycloak.userprofile.DeclarativeUserProfileProvider; import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.userprofile.UserProfile; import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileContext; import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.userprofile.config.UPConfigUtils;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
/** /**
* @author <a href="mailto:joerg.matysiak@bosch.io">Jörg Matysiak</a> * @author <a href="mailto:joerg.matysiak@bosch.io">Jörg Matysiak</a>
@ -52,10 +54,13 @@ public class CustomUserProfileTest extends AbstractUserProfileTest {
} }
private static void testCustomUserProfileProviderIsActive(KeycloakSession session) { private static void testCustomUserProfileProviderIsActive(KeycloakSession session) {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
assertEquals(CustomUserProfileProvider.class.getName(), provider.getClass().getName()); assertEquals(CustomUserProfileProvider.class.getName(), provider.getClass().getName());
assertTrue(provider instanceof CustomUserProfileProvider); assertTrue(provider instanceof CustomUserProfileProvider);
assertEquals("custom-user-profile", provider.getComponentModel().getProviderId()); provider.setConfiguration(UPConfigUtils.readDefaultConfig());
Optional<ComponentModel> component = getComponentModel(session);
assertTrue(component.isPresent());
assertEquals("custom-user-profile", component.get().getProviderId());
} }
@Test @Test
@ -64,7 +69,7 @@ public class CustomUserProfileTest extends AbstractUserProfileTest {
} }
private static void testInvalidConfiguration(KeycloakSession session) { private static void testInvalidConfiguration(KeycloakSession session) {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
try { try {
provider.setConfiguration("{\"validateConfigAttribute\": true}"); provider.setConfiguration("{\"validateConfigAttribute\": true}");
@ -80,19 +85,16 @@ public class CustomUserProfileTest extends AbstractUserProfileTest {
} }
private static void testConfigurationChunks(KeycloakSession session) throws IOException { private static void testConfigurationChunks(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
String newConfig = generateLargeProfileConfig(); String newConfig = generateLargeProfileConfig();
provider.setConfiguration(newConfig); provider.setConfiguration(newConfig);
component = provider.getComponentModel(); Optional<ComponentModel> component = getComponentModel(session);
assertTrue(component.isPresent());
// assert config is persisted in 2 pieces // assert config is persisted in 2 pieces
Assert.assertEquals("2", component.get(DeclarativeUserProfileProvider.UP_PIECES_COUNT_COMPONENT_CONFIG_KEY)); Assert.assertEquals("2", component.get().get(DeclarativeUserProfileProvider.UP_PIECES_COUNT_COMPONENT_CONFIG_KEY));
// assert config is returned correctly // assert config is returned correctly
Assert.assertEquals(newConfig, provider.getConfiguration()); Assert.assertEquals(newConfig, provider.getConfiguration());
} }
@ -104,7 +106,7 @@ public class CustomUserProfileTest extends AbstractUserProfileTest {
} }
private static void testDefaultConfig(KeycloakSession session) { private static void testDefaultConfig(KeycloakSession session) {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
// reset configuration to default // reset configuration to default
provider.setConfiguration(null); provider.setConfiguration(null);

View file

@ -39,6 +39,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -121,7 +122,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.USERNAME, "profiled-user"); attributes.put(UserModel.USERNAME, "profiled-user");
UserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [{\"name\": \"address\", \"required\": {}, \"permissions\": {\"edit\": [\"user\"]}}]}"); provider.setConfiguration("{\"attributes\": [{\"name\": \"address\", \"required\": {}, \"permissions\": {\"edit\": [\"user\"]}}]}");
@ -157,7 +158,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.USERNAME, "profiled-user"); attributes.put(UserModel.USERNAME, "profiled-user");
UserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [{\"name\": \"business.address\", \"required\": {\"scopes\": [\"customer\"]}, \"permissions\": {\"edit\": [\"user\"]}}]}"); provider.setConfiguration("{\"attributes\": [{\"name\": \"business.address\", \"required\": {\"scopes\": [\"customer\"]}, \"permissions\": {\"edit\": [\"user\"]}}]}");
@ -281,7 +282,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testValidateComplianceWithUserProfile(KeycloakSession session) throws IOException { private static void testValidateComplianceWithUserProfile(KeycloakSession session) throws IOException {
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().addUser(realm, "profiled-user"); UserModel user = session.users().addUser(realm, "profiled-user");
UserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -325,7 +326,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testGetProfileAttributes(KeycloakSession session) { private static void testGetProfileAttributes(KeycloakSession session) {
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 = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [{\"name\": \"address\", \"required\": {}, \"permissions\": {\"edit\": [\"user\"]}}]}"); provider.setConfiguration("{\"attributes\": [{\"name\": \"address\", \"required\": {}, \"permissions\": {\"edit\": [\"user\"]}}]}");
@ -367,7 +368,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testGetProfileAttributeGroups(KeycloakSession session) { private static void testGetProfileAttributeGroups(KeycloakSession session) {
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 = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
String configuration = "{\n" + String configuration = "{\n" +
" \"attributes\": [\n" + " \"attributes\": [\n" +
@ -426,7 +427,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testCreateAndUpdateUser(KeycloakSession session) throws IOException { private static void testCreateAndUpdateUser(KeycloakSession session) throws IOException {
UserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = JsonSerialization.readValue(provider.getConfiguration(), UPConfig.class); UPConfig config = JsonSerialization.readValue(provider.getConfiguration(), UPConfig.class);
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -510,7 +511,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put("address", Arrays.asList("fixed-address")); attributes.put("address", Arrays.asList("fixed-address"));
attributes.put("department", Arrays.asList("sales")); attributes.put("department", Arrays.asList("sales"));
UserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [{\"name\": \"department\", \"permissions\": {\"edit\": [\"admin\"]}}]}"); provider.setConfiguration("{\"attributes\": [{\"name\": \"department\", \"permissions\": {\"edit\": [\"admin\"]}}]}");
@ -559,7 +560,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId()); attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
attributes.put(UserModel.EMAIL, "readonly@foo.bar"); attributes.put(UserModel.EMAIL, "readonly@foo.bar");
UserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
// configure email r/o for user // configure email r/o for user
provider.setConfiguration("{\"attributes\": [{\"name\": \"email\", \"permissions\": {\"edit\": [ \"admin\"]}}]}"); provider.setConfiguration("{\"attributes\": [{\"name\": \"email\", \"permissions\": {\"edit\": [ \"admin\"]}}]}");
@ -601,7 +602,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId()); attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
attributes.put(UserModel.EMAIL, "canchange@foo.bar"); attributes.put(UserModel.EMAIL, "canchange@foo.bar");
UserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
// configure email r/w for user // configure email r/w for user
provider.setConfiguration("{\"attributes\": [{\"name\": \"email\", \"permissions\": {\"edit\": [ \"user\", \"admin\"]}}]}"); provider.setConfiguration("{\"attributes\": [{\"name\": \"email\", \"permissions\": {\"edit\": [ \"user\", \"admin\"]}}]}");
@ -640,7 +641,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put("department", Arrays.asList("sales")); attributes.put("department", Arrays.asList("sales"));
attributes.put("phone", Arrays.asList("fixed-phone")); attributes.put("phone", Arrays.asList("fixed-phone"));
UserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [{\"name\": \"department\", \"permissions\": {\"edit\": [\"admin\"]}}," provider.setConfiguration("{\"attributes\": [{\"name\": \"department\", \"permissions\": {\"edit\": [\"admin\"]}},"
+ "{\"name\": \"phone\", \"permissions\": {\"edit\": [\"admin\"]}}," + "{\"name\": \"phone\", \"permissions\": {\"edit\": [\"admin\"]}},"
@ -707,8 +708,10 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testComponentModelId(KeycloakSession session) { private static void testComponentModelId(KeycloakSession session) {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); setDefaultConfiguration(session);
assertEquals("declarative-user-profile", provider.getComponentModel().getProviderId()); Optional<ComponentModel> component = getComponentModel(session);
assertTrue(component.isPresent());
assertEquals("declarative-user-profile", component.get().getProviderId());
} }
@Test @Test
@ -717,7 +720,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testInvalidConfiguration(KeycloakSession session) { private static void testInvalidConfiguration(KeycloakSession session) {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
try { try {
provider.setConfiguration("{\"validateConfigAttribute\": true}"); provider.setConfiguration("{\"validateConfigAttribute\": true}");
@ -734,15 +737,14 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testConfigurationChunks(KeycloakSession session) throws IOException { private static void testConfigurationChunks(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); ComponentModel component = setAndGetDefaultConfiguration(session).orElse(null);
ComponentModel component = provider.getComponentModel();
assertNotNull(component); assertNotNull(component);
String newConfig = generateLargeProfileConfig(); String newConfig = generateLargeProfileConfig();
provider.setConfiguration(newConfig); UserProfileProvider provider = getUserProfileProvider(session);
component = provider.getComponentModel(); provider.setConfiguration(newConfig);
component = getComponentModel(session).orElse(null);
// assert config is persisted in 2 pieces // assert config is persisted in 2 pieces
Assert.assertEquals("2", component.get(DeclarativeUserProfileProvider.UP_PIECES_COUNT_COMPONENT_CONFIG_KEY)); Assert.assertEquals("2", component.get(DeclarativeUserProfileProvider.UP_PIECES_COUNT_COMPONENT_CONFIG_KEY));
@ -756,17 +758,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testResetConfiguration(KeycloakSession session) throws IOException { private static void testResetConfiguration(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); setConfiguration(session, null);
assertFalse(getComponentModel(session).isPresent());
provider.setConfiguration(null);
Assert.assertNull(provider.getComponentModel().get(DeclarativeUserProfileProvider.UP_PIECES_COUNT_COMPONENT_CONFIG_KEY));
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
Assert.assertTrue(component.getConfig().isEmpty());
} }
@Test @Test
@ -775,7 +768,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testDefaultConfig(KeycloakSession session) { private static void testDefaultConfig(KeycloakSession session) {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
// reset configuration to default // reset configuration to default
provider.setConfiguration(null); provider.setConfiguration(null);
@ -825,11 +818,6 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testCustomValidationForUsername(KeycloakSession session) throws IOException { private static void testCustomValidationForUsername(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -843,6 +831,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
config.addAttribute(attribute); config.addAttribute(attribute);
UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration(JsonSerialization.writeValueAsString(config)); provider.setConfiguration(JsonSerialization.writeValueAsString(config));
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
@ -883,7 +872,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testRemoveDefaultValidationFromUsername(KeycloakSession session) throws IOException { private static void testRemoveDefaultValidationFromUsername(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
// reset configuration to default // reset configuration to default
provider.setConfiguration(null); provider.setConfiguration(null);
@ -926,11 +915,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testOptionalAttributes(KeycloakSession session) throws IOException { private static void testOptionalAttributes(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
attribute.setName(UserModel.FIRST_NAME); attribute.setName(UserModel.FIRST_NAME);
@ -985,11 +970,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testCustomAttributeRequired(KeycloakSession session) throws IOException { private static void testCustomAttributeRequired(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -1052,11 +1033,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testCustomAttributeOptional(KeycloakSession session) throws IOException { private static void testCustomAttributeOptional(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -1105,11 +1082,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testRequiredIfUser(KeycloakSession session) throws IOException { private static void testRequiredIfUser(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -1172,11 +1145,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testRequiredIfAdmin(KeycloakSession session) throws IOException { private static void testRequiredIfAdmin(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -1227,11 +1196,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testNoValidationsIfUserReadOnly(KeycloakSession session) throws IOException { private static void testNoValidationsIfUserReadOnly(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -1275,11 +1240,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testNoValidationsIfAdminReadOnly(KeycloakSession session) throws IOException { private static void testNoValidationsIfAdminReadOnly(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -1320,7 +1281,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testIgnoreReadOnlyAttribute(KeycloakSession session) throws IOException { private static void testIgnoreReadOnlyAttribute(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute firstName = new UPAttribute(); UPAttribute firstName = new UPAttribute();
@ -1395,7 +1356,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
maria.setAttribute(LDAPConstants.LDAP_ID, List.of("1")); maria.setAttribute(LDAPConstants.LDAP_ID, List.of("1"));
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
Map<String, List<String>> attributes = new HashMap<>(); Map<String, List<String>> attributes = new HashMap<>();
attributes.put(LDAPConstants.LDAP_ID, List.of("2")); attributes.put(LDAPConstants.LDAP_ID, List.of("2"));
@ -1416,11 +1377,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testRequiredByClientScope(KeycloakSession session) throws IOException { private static void testRequiredByClientScope(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -1505,11 +1462,7 @@ 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(); RealmModel realm = session.getContext().getRealm();
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
UPConfig config = new UPConfig(); UPConfig config = new UPConfig();
UPAttribute attribute = new UPAttribute(); UPAttribute attribute = new UPAttribute();
@ -1540,7 +1493,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
} }
private static void testUsernameAndEmailPermissionNotSetIfEmpty(KeycloakSession session) throws IOException { private static void testUsernameAndEmailPermissionNotSetIfEmpty(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
UPConfig config = JsonSerialization.readValue(provider.getConfiguration(), UPConfig.class); UPConfig config = JsonSerialization.readValue(provider.getConfiguration(), UPConfig.class);
for (UPAttribute attribute : config.getAttributes()) { for (UPAttribute attribute : config.getAttributes()) {
@ -1581,7 +1534,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put("test-attribute", Arrays.asList("Test Value")); attributes.put("test-attribute", Arrays.asList("Test Value"));
attributes.put("foo", Arrays.asList("foo")); attributes.put("foo", Arrays.asList("foo"));
UserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [" provider.setConfiguration("{\"attributes\": ["
+ "{\"name\": \"test-attribute\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}}," + "{\"name\": \"test-attribute\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}},"
@ -1664,7 +1617,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.FIRST_NAME, List.of("")); attributes.put(UserModel.FIRST_NAME, List.of(""));
attributes.put("test-attribute", List.of("")); attributes.put("test-attribute", List.of(""));
UserProfileProvider provider = getDynamicUserProfileProvider(session); UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [" provider.setConfiguration("{\"attributes\": ["
+ "{\"name\": \"test-attribute\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}}," + "{\"name\": \"test-attribute\", \"permissions\": {\"edit\": [\"admin\", \"user\"]}},"