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:
parent
5628370099
commit
afc5cb4d14
12 changed files with 466 additions and 76 deletions
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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" ]
|
||||
|
|
Loading…
Reference in a new issue