KEYCLOAK-19617 Simplify creation of custom user profiles

* DeclarativeUserProfileProvider passes its ID to DeclarativeUserProfileModel, so this also works for derived classes.
* Moved creation of declarative user profile model to a protected factory method to allow subclasses to provide their own implementation.
* Added integration tests for custom user profile
* configured declarative-user-profile as default user profile provider in test servers
* Restore previously configured default provider after test with special provider settings
* Some refactoring in SpiProviderSwitchingUtils
This commit is contained in:
Joerg Matysiak 2021-09-29 15:42:26 +02:00 committed by Pedro Igor
parent 5628370099
commit afc5cb4d14
12 changed files with 466 additions and 76 deletions

View file

@ -42,7 +42,6 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@ -420,14 +419,22 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
}
/**
* Get componenet to store our "per realm" configuration into.
* Get component to store our "per realm" configuration into.
*
* @param session to be used, and take realm from
* @return componenet
* @return component
*/
private ComponentModel getComponentModelOrCreate(KeycloakSession session) {
RealmModel realm = session.getContext().getRealm();
return realm.getComponentsStream(realm.getId(), UserProfileProvider.class.getName()).findAny().orElseGet(() -> realm.addComponentModel(new DeclarativeUserProfileModel()));
return realm.getComponentsStream(realm.getId(), UserProfileProvider.class.getName()).findAny().orElseGet(() -> realm.addComponentModel(createComponentModel()));
}
/**
* Create the component model to store configuration
* @return component model
*/
protected ComponentModel createComponentModel() {
return new DeclarativeUserProfileModel(getId());
}
/**

View file

@ -28,8 +28,8 @@ import org.keycloak.userprofile.UserProfileProvider;
*/
public class DeclarativeUserProfileModel extends ComponentModel {
public DeclarativeUserProfileModel() {
setProviderId(DeclarativeUserProfileProvider.ID);
public DeclarativeUserProfileModel(String providerId) {
setProviderId(providerId);
setProviderType(UserProfileProvider.class.getName());
}
}

View file

@ -17,8 +17,8 @@ echo ** Adding provider **
echo ** Adding max-detail-length to eventsStore spi **
/subsystem=keycloak-server/spi=eventsStore/provider=jpa/:write-attribute(name=properties.max-detail-length,value=${keycloak.eventsStore.maxDetailLength:1000})
echo ** Adding spi=userProfile with legacy-user-profile configuration of read-only attributes **
/subsystem=keycloak-server/spi=userProfile/:add
echo ** Adding spi=userProfile with default provider and legacy-user-profile configuration of read-only attributes **
/subsystem=keycloak-server/spi=userProfile/:add(default-provider="declarative-user-profile")
/subsystem=keycloak-server/spi=userProfile/provider=declarative-user-profile/:add(properties={},enabled=true)
/subsystem=keycloak-server/spi=userProfile/provider=declarative-user-profile/:map-put(name=properties,key=read-only-attributes,value=[deniedFoo,deniedBar*,deniedSome/thing,deniedsome*thing])
/subsystem=keycloak-server/spi=userProfile/provider=declarative-user-profile/:map-put(name=properties,key=admin-read-only-attributes,value=[deniedSomeAdmin])

View file

@ -0,0 +1,53 @@
package org.keycloak.testsuite.user.profile;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel;
import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.userprofile.UserProfileMetadata;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.userprofile.config.UPConfigUtils;
import java.util.Map;
public class CustomUserProfileProvider extends DeclarativeUserProfileProvider {
public static final String ID = "custom-user-profile";
public CustomUserProfileProvider() {
super();
}
public CustomUserProfileProvider(KeycloakSession session,
Map<UserProfileContext, UserProfileMetadata> metadataRegistry, String defaultRawConfig) {
super(session, metadataRegistry, defaultRawConfig);
}
@Override
protected UserProfileProvider create(KeycloakSession session,
Map<UserProfileContext, UserProfileMetadata> metadataRegistry) {
return new CustomUserProfileProvider(session, metadataRegistry, UPConfigUtils.readDefaultConfig());
}
@Override
public String getId() {
return ID;
}
@Override
public UserProfile create(UserProfileContext context, UserModel user) {
return this.create(context, user.getAttributes(), user);
}
@Override
public UserProfile create(UserProfileContext context, Map<String, ?> attributes, UserModel user) {
return super.create(context, attributes, user);
}
@Override
public UserProfile create(UserProfileContext context, Map<String, ?> attributes) {
return this.create(context, attributes, (UserModel) null);
}
}

View file

@ -0,0 +1,19 @@
#
# /*
# * Copyright 2021 Red Hat, Inc. and/or its affiliates
# * and other contributors as indicated by the @author tags.
# *
# * Licensed under the Apache License, Version 2.0 (the "License");
# * you may not use this file except in compliance with the License.
# * You may obtain a copy of the License at
# *
# * http://www.apache.org/licenses/LICENSE-2.0
# *
# * Unless required by applicable law or agreed to in writing, software
# * distributed under the License is distributed on an "AS IS" BASIS,
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# * See the License for the specific language governing permissions and
# * limitations under the License.
# */
#
org.keycloak.testsuite.user.profile.CustomUserProfileProvider

View file

@ -889,7 +889,7 @@ public class AuthServerTestEnricher {
boolean wasUpdated = false;
if (event.getTestClass().isAnnotationPresent(SetDefaultProvider.class)) {
SpiProvidersSwitchingUtils.removeProvider(suiteContext, event.getTestClass().getAnnotation(SetDefaultProvider.class));
SpiProvidersSwitchingUtils.resetProvider(suiteContext, event.getTestClass().getAnnotation(SetDefaultProvider.class));
wasUpdated = true;
}

View file

@ -8,6 +8,7 @@ import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
@ -19,9 +20,9 @@ import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@ -45,7 +46,10 @@ import org.keycloak.testsuite.arquillian.SuiteContext;
public class KeycloakQuarkusServerDeployableContainer implements DeployableContainer<KeycloakQuarkusConfiguration> {
private static final Logger log = Logger.getLogger(KeycloakQuarkusServerDeployableContainer.class);
public static final String CONF_PERSISTED_PROPERTIES_FILENAME = "conf/persisted.properties";
public static final String SPI_PROVIDER_PROPERTY_PREFIX = "kc.spi-";
public static final String SPI_PROVIDER_PROPERTY_SUFFIX = "-provider";
private KeycloakQuarkusConfiguration configuration;
private Process container;
private static AtomicBoolean restart = new AtomicBoolean();
@ -125,6 +129,23 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
}
/**
* Get the currently configured default provider for the passed spi.
*
* @param spi the spi to get the current default provider for (dash-cased)
* @return the current configured provider or null if not configured
*/
public String getCurrentlyConfiguredSpiProviderFor(String spi) {
try {
File persistedPropertiesFile = configuration.getProvidersPath().resolve(CONF_PERSISTED_PROPERTIES_FILENAME).toFile();
Properties props = new Properties();
props.load(new FileInputStream(persistedPropertiesFile));
return props.get(SPI_PROVIDER_PROPERTY_PREFIX + spi + SPI_PROVIDER_PROPERTY_SUFFIX).toString().trim();
} catch (IOException e) {
return null;
}
}
private Process startContainer() throws IOException {
ProcessBuilder pb = new ProcessBuilder(getProcessCommands());
File wrkDir = configuration.getProvidersPath().resolve("bin").toFile();
@ -170,11 +191,20 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
}
commands.add("--cluster=" + System.getProperty("auth.server.quarkus.cluster.config", "local"));
commands.addAll(getAdditionalBuildArgs());
if (!containsUserProfileSpiConfiguration(commands)) {
// ensure that at least one user profile provider is configured
commands.add("--spi-user-profile-provider=declarative-user-profile");
}
return commands.toArray(new String[0]);
}
private boolean containsUserProfileSpiConfiguration(List<String> commands) {
return commands.stream().anyMatch(s -> s.startsWith("--spi-user-profile-provider="));
}
private void waitForReadiness() throws MalformedURLException, LifecycleException {
SuiteContext suiteContext = this.suiteContext.get();
//TODO: not sure if the best endpoint but it makes sure that everything is properly initialized. Once we have

View file

@ -1,85 +1,219 @@
package org.keycloak.testsuite.util;
import org.jboss.arquillian.container.spi.Container;
import org.jboss.logging.Logger;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.arquillian.ContainerInfo;
import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer;
import org.wildfly.extras.creaper.core.online.CliException;
import org.wildfly.extras.creaper.core.online.ModelNodeResult;
import org.wildfly.extras.creaper.core.online.OnlineManagementClient;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class SpiProvidersSwitchingUtils {
private static final String SUBSYSTEM_KEYCLOAK_SERVER_SPI = "/subsystem=keycloak-server/spi=";
private static final String KEYCLOAKX_ARG_SPI_PREFIX = "--spi-";
private static final Map<String, String> originalSettingsBackup = new ConcurrentHashMap<>();
protected static final Logger log = Logger.getLogger(SpiProvidersSwitchingUtils.class);
private SpiProvidersSwitchingUtils() {}
public static void addProviderDefaultValue(SuiteContext suiteContext, SetDefaultProvider annotation) throws IOException, CliException {
ContainerInfo authServerInfo = suiteContext.getAuthServerInfo();
if (authServerInfo.isUndertow()) {
System.setProperty("keycloak." + annotation.spi() + ".provider", annotation.providerId());
} else if (authServerInfo.isQuarkus()) {
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) authServerInfo.getArquillianContainer().getDeployableContainer();
container.setAdditionalBuildArgs(Collections.singletonList(KEYCLOAKX_ARG_SPI_PREFIX + toDashCase(annotation.spi()) + "-provider=" + annotation.providerId()));
} else {
OnlineManagementClient client = AuthServerTestEnricher.getManagementClient();
if (annotation.onlyUpdateDefault()) {
client.execute(SUBSYSTEM_KEYCLOAK_SERVER_SPI + annotation.spi() + ":write-attribute(name=default-provider, value=" + annotation.providerId() + ")");
} else {
client.execute(SUBSYSTEM_KEYCLOAK_SERVER_SPI + annotation.spi() + "/:add(default-provider=\"" + annotation.providerId() + "\")");
private enum SpiSwitcher {
UNDERTOW {
@Override
public Optional<String> getCurrentDefaultProvider(Container container, String spiName) {
return Optional.ofNullable(System.getProperty(getProviderPropertyName(spiName)));
}
client.close();
@Override
public void setDefaultProvider(Container container, String spiName, String providerId) {
System.setProperty(getProviderPropertyName(spiName), providerId);
}
@Override
public void removeProviderConfig(Container container, String spiName) {
System.clearProperty(getProviderPropertyName(spiName));
}
private String getProviderPropertyName(String spiName) {
return "keycloak." + spiName + ".provider";
}
},
WILDFLY {
@Override
public Optional<String> getCurrentDefaultProvider(Container container, String spiName) {
String cliCmd = SUBSYSTEM_KEYCLOAK_SERVER_SPI + spiName + ":read-attribute(name=default-provider)";
return runInCli(cliCmd).filter(ModelNodeResult::isSuccess)
.map(n -> n.get("result").asString());
}
@Override
public void setDefaultProvider(Container container, String spiName, String providerId) {
runInCli(SUBSYSTEM_KEYCLOAK_SERVER_SPI + spiName + "/:add(default-provider=\"" + providerId + "\")");
}
@Override
public void updateDefaultProvider(Container container, String spiName, String providerId) {
runInCli(SUBSYSTEM_KEYCLOAK_SERVER_SPI + spiName + ":write-attribute(name=default-provider, value="
+ providerId + ")");
}
@Override
public void unsetDefaultProvider(Container container, String spiName) {
runInCli(SUBSYSTEM_KEYCLOAK_SERVER_SPI + spiName + ":/:undefine-attribute(name=default-provider)");
}
@Override
public void removeProviderConfig(Container container, String spiName) {
runInCli(SUBSYSTEM_KEYCLOAK_SERVER_SPI + spiName + "/:remove");
}
public Optional<ModelNodeResult> runInCli(String cliCmd) {
try (
OnlineManagementClient client = AuthServerTestEnricher.getManagementClient();
) {
return Optional.ofNullable(client.execute(cliCmd));
} catch (CliException | IOException e) {
// return empty optional, see below
}
return Optional.empty();
}
},
QUARKUS {
@Override
public Optional<String> getCurrentDefaultProvider(Container container, String spiName) {
return Optional.ofNullable(
getQuarkusContainer(container).getCurrentlyConfiguredSpiProviderFor(toDashCase(spiName)));
}
@Override
public void setDefaultProvider(Container container, String spiName, String providerId) {
getQuarkusContainer(container).setAdditionalBuildArgs(Collections
.singletonList(KEYCLOAKX_ARG_SPI_PREFIX + toDashCase(spiName) + "-provider=" + providerId));
}
@Override
public void removeProviderConfig(Container container, String spiName) {
getQuarkusContainer(container).setAdditionalBuildArgs(Collections
.singletonList(KEYCLOAKX_ARG_SPI_PREFIX + toDashCase(spiName) + "-provider=default"));
}
private KeycloakQuarkusServerDeployableContainer getQuarkusContainer(Container container) {
return (KeycloakQuarkusServerDeployableContainer) container.getDeployableContainer();
}
/**
* Parses the non-standard SPI-Name format to the standardized format
* we use in the Keycloak.X Configuration
*
* @param s possibly non-standard spi name
* @return standardized spi name in dash-case. e.g. userProfile -> user-profile
*/
private String toDashCase(String s) {
StringBuilder sb = new StringBuilder(s.length());
boolean l = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (l && Character.isUpperCase(c)) {
sb.append('-');
c = Character.toLowerCase(c);
l = false;
} else {
l = Character.isLowerCase(c);
}
sb.append(c);
}
return sb.toString();
}
};
public abstract Optional<String> getCurrentDefaultProvider(Container container, String spiName);
public abstract void setDefaultProvider(Container container, String spiName, String providerId);
public void updateDefaultProvider(Container container, String spiName, String providerId) {
setDefaultProvider(container, spiName, providerId);
}
public void unsetDefaultProvider(Container container, String spiName) {
removeProviderConfig(container, spiName);
}
public abstract void removeProviderConfig(Container container, String spiName);
public static SpiSwitcher getSpiSwitcherFor(ContainerInfo containerInfo) {
if (containerInfo.isUndertow()) {
return SpiSwitcher.UNDERTOW;
} else if (containerInfo.isQuarkus()) {
return SpiSwitcher.QUARKUS;
}
return SpiSwitcher.WILDFLY;
}
}
public static void removeProvider(SuiteContext suiteContext, SetDefaultProvider annotation) throws IOException, CliException {
ContainerInfo authServerInfo = suiteContext.getAuthServerInfo();
private SpiProvidersSwitchingUtils() {
}
if (authServerInfo.isUndertow()) {
System.clearProperty("keycloak." + annotation.spi() + ".provider");
} else if (authServerInfo.isQuarkus()) {
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) authServerInfo.getArquillianContainer().getDeployableContainer();
container.resetConfiguration();
container.setAdditionalBuildArgs(Collections.singletonList(KEYCLOAKX_ARG_SPI_PREFIX + toDashCase(annotation.spi()) + "-provider=default"));
public static void addProviderDefaultValue(SuiteContext suiteContext, SetDefaultProvider annotation) {
ContainerInfo authServerInfo = suiteContext.getAuthServerInfo();
SpiSwitcher spiSwitcher = SpiSwitcher.getSpiSwitcherFor(authServerInfo);
String spi = annotation.spi();
Container container = authServerInfo.getArquillianContainer();
log.infof("Setting default provider for %s to %s", spi, annotation.providerId());
if (annotation.onlyUpdateDefault()) {
spiSwitcher.getCurrentDefaultProvider(container, spi).ifPresent(v -> originalSettingsBackup.put(spi, v));
spiSwitcher.updateDefaultProvider(container, spi, annotation.providerId());
} else {
OnlineManagementClient client = AuthServerTestEnricher.getManagementClient();
if (annotation.onlyUpdateDefault()) {
client.execute(SUBSYSTEM_KEYCLOAK_SERVER_SPI + annotation.spi() + "/:undefine-attribute(name=default-provider)");
} else {
client.execute(SUBSYSTEM_KEYCLOAK_SERVER_SPI + annotation.spi() + "/:remove");
}
client.close();
spiSwitcher.setDefaultProvider(container, spi, annotation.providerId());
}
}
/**
* Parses the non-standard SPI-Name format to the standardized format
* we use in the Keycloak.X Configuration
* @param s possibly non-standard spi name
* @return standardized spi name in dash-case. e.g. userProfile -> user-profile
*/
private static String toDashCase(String s) {
StringBuilder sb = new StringBuilder(s.length());
boolean l = false;
public static void resetProvider(SuiteContext suiteContext, SetDefaultProvider annotation) {
ContainerInfo authServerInfo = suiteContext.getAuthServerInfo();
SpiSwitcher spiSwitcher = SpiSwitcher.getSpiSwitcherFor(authServerInfo);
String spi = annotation.spi();
Container container = authServerInfo.getArquillianContainer();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (l && Character.isUpperCase(c)) {
sb.append('-');
c = Character.toLowerCase(c);
l = false;
if (annotation.onlyUpdateDefault()) {
String originalValue = originalSettingsBackup.get(spi);
log.infof("Resetting default provider for %s to %s", spi,
originalValue == null ? "<null>" : originalValue);
if (originalValue != null) {
spiSwitcher.updateDefaultProvider(container, spi, originalValue);
} else {
l = Character.isLowerCase(c);
spiSwitcher.unsetDefaultProvider(container, spi);
}
sb.append(c);
} else {
log.infof("Removing default provider for %s to %s", spi);
spiSwitcher.removeProviderConfig(container, spi);
}
}
public static void removeProvider(SuiteContext suiteContext, SetDefaultProvider annotation) {
ContainerInfo authServerInfo = suiteContext.getAuthServerInfo();
SpiSwitcher spiSwitcher = SpiSwitcher.getSpiSwitcherFor(authServerInfo);
String spi = annotation.spi();
Container container = authServerInfo.getArquillianContainer();
log.infof("Removing default provider setting for %s", spi);
if (annotation.onlyUpdateDefault()) {
spiSwitcher.unsetDefaultProvider(container, spi);
} else {
spiSwitcher.removeProviderConfig(container, spi);
}
return sb.toString();
}
}

View file

@ -21,6 +21,7 @@ package org.keycloak.testsuite.user.profile;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ -39,6 +40,9 @@ import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.userprofile.config.UPAttribute;
import org.keycloak.userprofile.config.UPConfig;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -69,6 +73,26 @@ public abstract class AbstractUserProfileTest extends AbstractTestRealmKeycloakT
return (DeclarativeUserProfileProvider) provider;
}
/**
* Generate big configuration to test slicing in the persistence/component config
* @return a configuration that is expected to be split into 2 slices
* @throws IOException
*/
protected static String generateLargeProfileConfig() throws IOException {
UPConfig config = new UPConfig();
for (int i = 0; i < 80; i++) {
UPAttribute attribute = new UPAttribute();
attribute.setName(UserModel.USERNAME+i);
Map<String, Object> validatorConfig = new HashMap<>();
validatorConfig.put("min", 3);
attribute.addValidation("length", validatorConfig);
config.addAttribute(attribute);
}
String newConfig = JsonSerialization.writeValueAsString(config);
return newConfig;
}
protected static AuthenticationSessionModel createAuthenticationSession(ClientModel client, Set<String> scopes) {
return new AuthenticationSessionModel() {
@Override

View file

@ -0,0 +1,124 @@
/*
*
* * Copyright 2021 Red Hat, Inc. and/or its affiliates
* * and other contributors as indicated by the @author tags.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.keycloak.testsuite.user.profile;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel;
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
import org.keycloak.testsuite.runonserver.RunOnServer;
import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileContext;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:joerg.matysiak@bosch.io">Jörg Matysiak</a>
*/
@SetDefaultProvider(spi="userProfile", providerId="custom-user-profile", onlyUpdateDefault = true)
public class CustomUserProfileTest extends AbstractUserProfileTest {
@Test
public void testCustomUserProfileProviderIsActive() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) CustomUserProfileTest::testCustomUserProfileProviderIsActive);
}
private static void testCustomUserProfileProviderIsActive(KeycloakSession session) {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session);
assertEquals(CustomUserProfileProvider.class.getName(), provider.getClass().getName());
assertTrue(provider instanceof CustomUserProfileProvider);
assertEquals("custom-user-profile", provider.getComponentModel().getProviderId());
}
@Test
public void testInvalidConfiguration() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) CustomUserProfileTest::testInvalidConfiguration);
}
private static void testInvalidConfiguration(KeycloakSession session) {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session);
try {
provider.setConfiguration("{\"validateConfigAttribute\": true}");
fail("Should fail validation");
} catch (ComponentValidationException ve) {
// OK
}
}
@Test
public void testConfigurationChunks() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) CustomUserProfileTest::testConfigurationChunks);
}
private static void testConfigurationChunks(KeycloakSession session) throws IOException {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session);
ComponentModel component = provider.getComponentModel();
assertNotNull(component);
String newConfig = generateLargeProfileConfig();
provider.setConfiguration(newConfig);
component = provider.getComponentModel();
// assert config is persisted in 2 pieces
Assert.assertEquals("2", component.get(DeclarativeUserProfileProvider.UP_PIECES_COUNT_COMPONENT_CONFIG_KEY));
// assert config is returned correctly
Assert.assertEquals(newConfig, provider.getConfiguration());
}
@Test
public void testDefaultConfig() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) CustomUserProfileTest::testDefaultConfig);
}
private static void testDefaultConfig(KeycloakSession session) {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session);
// reset configuration to default
provider.setConfiguration(null);
Map<String, Object> attributes = new HashMap<>();
// ensure correct entered values can be validated
attributes.put(UserModel.USERNAME, "jdoeusername");
attributes.put(UserModel.FIRST_NAME, "John");
attributes.put(UserModel.LAST_NAME, "Doe");
attributes.put(UserModel.EMAIL, "jdoe@acme.org");
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
profile.validate();
}
}

View file

@ -610,6 +610,16 @@ public class UserProfileTest extends AbstractUserProfileTest {
assertFalse(user.getAttributes().containsKey("prefixedAttributeName"));
}
@Test
public void testComponentModelId() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testComponentModelId);
}
private static void testComponentModelId(KeycloakSession session) {
DeclarativeUserProfileProvider provider = getDynamicUserProfileProvider(session);
assertEquals("declarative-user-profile", provider.getComponentModel().getProviderId());
}
@Test
public void testInvalidConfiguration() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testInvalidConfiguration);
@ -638,18 +648,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
assertNotNull(component);
// generate big configuration to test slicing in the persistence/component config
UPConfig config = new UPConfig();
for (int i = 0; i < 80; i++) {
UPAttribute attribute = new UPAttribute();
attribute.setName(UserModel.USERNAME+i);
Map<String, Object> validatorConfig = new HashMap<>();
validatorConfig.put("min", 3);
attribute.addValidation("length", validatorConfig);
config.addAttribute(attribute);
}
String newConfig = JsonSerialization.writeValueAsString(config);
String newConfig = generateLargeProfileConfig();
provider.setConfiguration(newConfig);
component = provider.getComponentModel();

View file

@ -280,7 +280,7 @@
},
"userProfile": {
"provider": "${keycloak.userProfile.provider:}",
"provider": "${keycloak.userProfile.provider:declarative-user-profile}",
"declarative-user-profile": {
"read-only-attributes": [ "deniedFoo", "deniedBar*", "deniedSome/thing", "deniedsome*thing" ],
"admin-read-only-attributes": [ "deniedSomeAdmin" ]