[KEYCLOAK-17173] - Support for script providers in keycloak.x

This commit is contained in:
Pedro Igor 2021-02-18 23:30:33 -03:00
parent 1dc0b005fe
commit ffadbc3ba3
7 changed files with 243 additions and 34 deletions

View file

@ -30,12 +30,16 @@ import org.keycloak.scripting.ScriptingProvider;
*/ */
public final class DeployedScriptPolicyFactory extends JSPolicyProviderFactory { public final class DeployedScriptPolicyFactory extends JSPolicyProviderFactory {
private final ScriptProviderMetadata metadata; private ScriptProviderMetadata metadata;
public DeployedScriptPolicyFactory(ScriptProviderMetadata metadata) { public DeployedScriptPolicyFactory(ScriptProviderMetadata metadata) {
this.metadata = metadata; this.metadata = metadata;
} }
public DeployedScriptPolicyFactory() {
// for reflection
}
@Override @Override
public String getId() { public String getId() {
return metadata.getId(); return metadata.getId();
@ -81,4 +85,12 @@ public final class DeployedScriptPolicyFactory extends JSPolicyProviderFactory {
policy.setDescription(metadata.getDescription()); policy.setDescription(metadata.getDescription());
super.onCreate(policy, representation, authorization); super.onCreate(policy, representation, authorization);
} }
public ScriptProviderMetadata getMetadata() {
return metadata;
}
public void setMetadata(ScriptProviderMetadata metadata) {
this.metadata = metadata;
}
} }

View file

@ -59,10 +59,18 @@ public class ScriptProviderMetadata {
return fileName; return fileName;
} }
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getDescription() { public String getDescription() {
return description; return description;
} }
public void setDescription(String description) {
this.description = description;
}
public String getCode() { public String getCode() {
return code; return code;
} }

View file

@ -19,12 +19,21 @@ package org.keycloak.quarkus.deployment;
import static org.keycloak.configuration.Configuration.getPropertyNames; import static org.keycloak.configuration.Configuration.getPropertyNames;
import static org.keycloak.configuration.Configuration.getRawValue; import static org.keycloak.configuration.Configuration.getRawValue;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.POLICIES;
import javax.persistence.spi.PersistenceUnitTransactionType; import javax.persistence.spi.PersistenceUnitTransactionType;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -32,6 +41,9 @@ import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
import java.util.Properties; import java.util.Properties;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
@ -46,7 +58,12 @@ import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.ResteasyDeployment; import org.jboss.resteasy.spi.ResteasyDeployment;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.authentication.AuthenticatorSpi;
import org.keycloak.authentication.authenticators.browser.DeployedScriptAuthenticatorFactory;
import org.keycloak.authorization.policy.provider.PolicySpi;
import org.keycloak.authorization.policy.provider.js.DeployedScriptPolicyFactory;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.config.ConfigProviderFactory; import org.keycloak.config.ConfigProviderFactory;
import org.keycloak.configuration.Configuration; import org.keycloak.configuration.Configuration;
import org.keycloak.configuration.KeycloakConfigSourceProvider; import org.keycloak.configuration.KeycloakConfigSourceProvider;
@ -54,6 +71,8 @@ import org.keycloak.configuration.MicroProfileConfigProvider;
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory; import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory; import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory;
import org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider; import org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider;
import org.keycloak.protocol.ProtocolMapperSpi;
import org.keycloak.protocol.oidc.mappers.DeployedScriptOIDCProtocolMapper;
import org.keycloak.provider.EnvironmentDependentProviderFactory; import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.KeycloakDeploymentInfo; import org.keycloak.provider.KeycloakDeploymentInfo;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
@ -70,18 +89,42 @@ import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem; import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem;
import io.quarkus.vertx.http.deployment.FilterBuildItem; import io.quarkus.vertx.http.deployment.FilterBuildItem;
import org.keycloak.representations.provider.ScriptProviderDescriptor;
import org.keycloak.representations.provider.ScriptProviderMetadata;
import org.keycloak.services.NotFoundHandler; import org.keycloak.services.NotFoundHandler;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.health.KeycloakMetricsHandler; import org.keycloak.services.health.KeycloakMetricsHandler;
import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.transaction.JBossJtaTransactionManagerLookup; import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
import org.keycloak.util.Environment; import org.keycloak.util.Environment;
import org.keycloak.util.JsonSerialization;
class KeycloakProcessor { class KeycloakProcessor {
private static final Logger logger = Logger.getLogger(KeycloakProcessor.class); private static final Logger logger = Logger.getLogger(KeycloakProcessor.class);
private static final String JAR_FILE_SEPARATOR = "!/";
private static final String DEFAULT_HEALTH_ENDPOINT = "/health"; private static final String DEFAULT_HEALTH_ENDPOINT = "/health";
private static final Map<String, Function<ScriptProviderMetadata, ProviderFactory>> DEPLOYEABLE_SCRIPT_PROVIDERS = new HashMap<>();
private static final String KEYCLOAK_SCRIPTS_JSON_PATH = "META-INF/keycloak-scripts.json";
static {
DEPLOYEABLE_SCRIPT_PROVIDERS.put(AUTHENTICATORS, KeycloakProcessor::registerScriptAuthenticator);
DEPLOYEABLE_SCRIPT_PROVIDERS.put(POLICIES, KeycloakProcessor::registerScriptPolicy);
DEPLOYEABLE_SCRIPT_PROVIDERS.put(MAPPERS, KeycloakProcessor::registerScriptMapper);
}
private static ProviderFactory registerScriptAuthenticator(ScriptProviderMetadata metadata) {
return new DeployedScriptAuthenticatorFactory(metadata);
}
private static ProviderFactory registerScriptPolicy(ScriptProviderMetadata metadata) {
return new DeployedScriptPolicyFactory(metadata);
}
private static ProviderFactory registerScriptMapper(ScriptProviderMetadata metadata) {
return new DeployedScriptOIDCProtocolMapper(metadata);
}
@BuildStep @BuildStep
FeatureBuildItem getFeature() { FeatureBuildItem getFeature() {
@ -125,8 +168,9 @@ class KeycloakProcessor {
Profile.setInstance(recorder.createProfile()); Profile.setInstance(recorder.createProfile());
Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories = new HashMap<>(); Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories = new HashMap<>();
Map<Class<? extends Provider>, String> defaultProviders = new HashMap<>(); Map<Class<? extends Provider>, String> defaultProviders = new HashMap<>();
Map<String, ProviderFactory> preConfiguredProviders = new HashMap<>();
for (Map.Entry<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> entry : loadFactories() for (Map.Entry<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> entry : loadFactories(preConfiguredProviders)
.entrySet()) { .entrySet()) {
checkProviders(entry.getKey(), entry.getValue(), defaultProviders); checkProviders(entry.getKey(), entry.getValue(), defaultProviders);
@ -139,7 +183,7 @@ class KeycloakProcessor {
} }
} }
recorder.configSessionFactory(factories, defaultProviders, Environment.isRebuild()); recorder.configSessionFactory(factories, defaultProviders, preConfiguredProviders, Environment.isRebuild());
} }
/** /**
@ -245,15 +289,22 @@ class KeycloakProcessor {
hotFiles.produce(new HotDeploymentWatchedFileBuildItem("META-INF/keycloak.properties")); hotFiles.produce(new HotDeploymentWatchedFileBuildItem("META-INF/keycloak.properties"));
} }
private Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> loadFactories() { private Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> loadFactories(
Map<String, ProviderFactory> preConfiguredProviders) {
loadConfig(); loadConfig();
ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), new BuildClassLoader()); BuildClassLoader providerClassLoader = new BuildClassLoader();
ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), providerClassLoader);
Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> factories = new HashMap<>(); Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> factories = new HashMap<>();
for (Spi spi : pm.loadSpis()) { for (Spi spi : pm.loadSpis()) {
Map<Class<? extends Provider>, Map<String, ProviderFactory>> providers = new HashMap<>(); Map<Class<? extends Provider>, Map<String, ProviderFactory>> providers = new HashMap<>();
List<ProviderFactory> loadedFactories = new ArrayList<>(pm.load(spi));
Map<String, ProviderFactory> deployedScriptProviders = loadDeployedScriptProviders(providerClassLoader, spi);
for (ProviderFactory factory : pm.load(spi)) { loadedFactories.addAll(deployedScriptProviders.values());
preConfiguredProviders.putAll(deployedScriptProviders);
for (ProviderFactory factory : loadedFactories) {
if (Arrays.asList( if (Arrays.asList(
JBossJtaTransactionManagerLookup.class, JBossJtaTransactionManagerLookup.class,
DefaultJpaConnectionProviderFactory.class, DefaultJpaConnectionProviderFactory.class,
@ -282,6 +333,86 @@ class KeycloakProcessor {
return factories; return factories;
} }
private Map<String, ProviderFactory> loadDeployedScriptProviders(BuildClassLoader providerClassLoader, Spi spi) {
Map<String, ProviderFactory> providers = new HashMap<>();
if (supportsDeployeableScripts(spi)) {
try {
Enumeration<URL> urls = providerClassLoader.getResources(KEYCLOAK_SCRIPTS_JSON_PATH);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
int fileSeparator = url.getFile().indexOf(JAR_FILE_SEPARATOR);
if (fileSeparator != -1) {
JarFile jarFile = new JarFile(url.getFile().substring("file:".length(), fileSeparator));
JarEntry descriptorEntry = jarFile.getJarEntry(KEYCLOAK_SCRIPTS_JSON_PATH);
ScriptProviderDescriptor descriptor;
try (InputStream is = jarFile.getInputStream(descriptorEntry)) {
descriptor = JsonSerialization.readValue(is, ScriptProviderDescriptor.class);
}
for (Map.Entry<String, List<ScriptProviderMetadata>> entry : descriptor.getProviders().entrySet()) {
if (isScriptForSpi(spi, entry.getKey())) {
for (ScriptProviderMetadata metadata : entry.getValue()) {
ProviderFactory provider = createDeployableScriptProvider(jarFile, entry, metadata);
providers.put(metadata.getId(), provider);
}
}
}
}
}
} catch (IOException e) {
throw new RuntimeException("Failed to discover script providers", e);
}
}
return providers;
}
private ProviderFactory createDeployableScriptProvider(JarFile jarFile, Map.Entry<String, List<ScriptProviderMetadata>> entry,
ScriptProviderMetadata metadata) throws IOException {
String fileName = metadata.getFileName();
if (fileName == null) {
throw new RuntimeException("You must provide the script file name");
}
JarEntry scriptFile = jarFile.getJarEntry(fileName);
try (InputStream in = jarFile.getInputStream(scriptFile)) {
metadata.setCode(StreamUtil.readString(in, StandardCharsets.UTF_8));
}
metadata.setId(new StringBuilder("script").append("-").append(fileName).toString());
String name = metadata.getName();
if (name == null) {
name = fileName;
}
metadata.setName(name);
return DEPLOYEABLE_SCRIPT_PROVIDERS.get(entry.getKey()).apply(metadata);
}
private boolean isScriptForSpi(Spi spi, String type) {
if (spi instanceof ProtocolMapperSpi && MAPPERS.equals(type)) {
return true;
} else if (spi instanceof PolicySpi && POLICIES.equals(type)) {
return true;
} else if (spi instanceof AuthenticatorSpi && AUTHENTICATORS.equals(type)) {
return true;
}
return false;
}
private boolean supportsDeployeableScripts(Spi spi) {
return spi instanceof ProtocolMapperSpi || spi instanceof PolicySpi || spi instanceof AuthenticatorSpi;
}
private boolean isEnabled(ProviderFactory factory, Config.Scope scope) { private boolean isEnabled(ProviderFactory factory, Config.Scope scope) {
if (!scope.getBoolean("enabled", true)) { if (!scope.getBoolean("enabled", true)) {
return false; return false;

View file

@ -31,13 +31,16 @@ public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionF
private static QuarkusKeycloakSessionFactory INSTANCE; private static QuarkusKeycloakSessionFactory INSTANCE;
private final Boolean reaugmented; private final Boolean reaugmented;
private final Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories; private final Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories;
private Map<String, ProviderFactory> preConfiguredProviders;
public QuarkusKeycloakSessionFactory( public QuarkusKeycloakSessionFactory(
Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories, Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories,
Map<Class<? extends Provider>, String> defaultProviders, Map<Class<? extends Provider>, String> defaultProviders,
Map<String, ProviderFactory> preConfiguredProviders,
Boolean reaugmented) { Boolean reaugmented) {
this.provider = defaultProviders; this.provider = defaultProviders;
this.factories = factories; this.factories = factories;
this.preConfiguredProviders = preConfiguredProviders;
this.reaugmented = reaugmented; this.reaugmented = reaugmented;
} }
@ -54,7 +57,12 @@ public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionF
for (Spi spi : spis) { for (Spi spi : spis) {
for (Map<String, Class<? extends ProviderFactory>> factoryClazz : factories.get(spi).values()) { for (Map<String, Class<? extends ProviderFactory>> factoryClazz : factories.get(spi).values()) {
for (Map.Entry<String, Class<? extends ProviderFactory>> entry : factoryClazz.entrySet()) { for (Map.Entry<String, Class<? extends ProviderFactory>> entry : factoryClazz.entrySet()) {
ProviderFactory factory = lookupProviderFactory(entry.getValue()); ProviderFactory factory = preConfiguredProviders.get(entry.getKey());
if (factory == null) {
factory = lookupProviderFactory(entry.getValue());
}
Config.Scope scope = Config.scope(spi.getName(), factory.getId()); Config.Scope scope = Config.scope(spi.getName(), factory.getId());
factory.init(scope); factory.init(scope);

View file

@ -77,9 +77,10 @@ public class KeycloakRecorder {
public void configSessionFactory( public void configSessionFactory(
Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories, Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories,
Map<Class<? extends Provider>, String> defaultProviders, Map<Class<? extends Provider>, String> defaultProviders,
Map<String, ProviderFactory> preConfiguredProviders,
Boolean reaugmented) { Boolean reaugmented) {
Profile.setInstance(createProfile()); Profile.setInstance(createProfile());
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, reaugmented)); QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, reaugmented));
} }
/** /**

View file

@ -16,13 +16,17 @@
*/ */
package org.keycloak.authentication.authenticators.browser; package org.keycloak.authentication.authenticators.browser;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import org.keycloak.Config;
import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator; import org.keycloak.authentication.Authenticator;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.provider.ScriptProviderMetadata; import org.keycloak.representations.provider.ScriptProviderMetadata;
/** /**
@ -30,17 +34,16 @@ import org.keycloak.representations.provider.ScriptProviderMetadata;
*/ */
public final class DeployedScriptAuthenticatorFactory extends ScriptBasedAuthenticatorFactory { public final class DeployedScriptAuthenticatorFactory extends ScriptBasedAuthenticatorFactory {
private final AuthenticatorConfigModel model; private ScriptProviderMetadata metadata;
private AuthenticatorConfigModel model;
private List<ProviderConfigProperty> configProperties;
public DeployedScriptAuthenticatorFactory(ScriptProviderMetadata metadata) { public DeployedScriptAuthenticatorFactory(ScriptProviderMetadata metadata) {
model = new AuthenticatorConfigModel(); this.metadata = metadata;
}
model.setId(metadata.getId()); public DeployedScriptAuthenticatorFactory() {
model.setAlias(metadata.getName()); // for reflection
model.setConfig(new HashMap<>());
model.getConfig().put("scriptName", metadata.getName());
model.getConfig().put("scriptCode", metadata.getCode());
model.getConfig().put("scriptDescription", metadata.getDescription());
} }
@Override @Override
@ -55,7 +58,7 @@ public final class DeployedScriptAuthenticatorFactory extends ScriptBasedAuthent
@Override @Override
public String getId() { public String getId() {
return model.getId(); return metadata.getId();
} }
@Override @Override
@ -82,4 +85,36 @@ public final class DeployedScriptAuthenticatorFactory extends ScriptBasedAuthent
public boolean isSupported() { public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS); return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS);
} }
@Override
public void init(Config.Scope config) {
model = createModel(metadata);
configProperties = super.getConfigProperties();
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
public void setMetadata(ScriptProviderMetadata metadata) {
this.metadata = metadata;
}
public ScriptProviderMetadata getMetadata() {
return metadata;
}
private AuthenticatorConfigModel createModel(ScriptProviderMetadata metadata) {
AuthenticatorConfigModel model = new AuthenticatorConfigModel();
model.setId(metadata.getId());
model.setAlias(metadata.getName());
model.setConfig(new HashMap<>());
model.getConfig().put("scriptName", metadata.getName());
model.getConfig().put("scriptCode", metadata.getCode());
model.getConfig().put("scriptDescription", metadata.getDescription());
return model;
}
} }

View file

@ -18,6 +18,7 @@ package org.keycloak.protocol.oidc.mappers;
import java.util.List; import java.util.List;
import org.keycloak.Config;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.ProtocolMapperUtils;
@ -25,30 +26,20 @@ import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder; import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.representations.provider.ScriptProviderMetadata; import org.keycloak.representations.provider.ScriptProviderMetadata;
public final class DeployedScriptOIDCProtocolMapper extends ScriptBasedOIDCProtocolMapper { public class DeployedScriptOIDCProtocolMapper extends ScriptBasedOIDCProtocolMapper {
private static final List<ProviderConfigProperty> configProperties; private List<ProviderConfigProperty> configProperties;
static { protected ScriptProviderMetadata metadata;
configProperties = ProviderConfigurationBuilder.create()
.property()
.name(ProtocolMapperUtils.MULTIVALUED)
.label(ProtocolMapperUtils.MULTIVALUED_LABEL)
.helpText(ProtocolMapperUtils.MULTIVALUED_HELP_TEXT)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue(false)
.add()
.build();
OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserPropertyMapper.class);
}
private final ScriptProviderMetadata metadata;
public DeployedScriptOIDCProtocolMapper(ScriptProviderMetadata metadata) { public DeployedScriptOIDCProtocolMapper(ScriptProviderMetadata metadata) {
this.metadata = metadata; this.metadata = metadata;
} }
public DeployedScriptOIDCProtocolMapper() {
// for reflection
}
@Override @Override
public String getId() { public String getId() {
return metadata.getId(); return metadata.getId();
@ -69,6 +60,21 @@ public final class DeployedScriptOIDCProtocolMapper extends ScriptBasedOIDCProto
return metadata.getCode(); return metadata.getCode();
} }
@Override
public void init(Config.Scope config) {
configProperties = ProviderConfigurationBuilder.create()
.property()
.name(ProtocolMapperUtils.MULTIVALUED)
.label(ProtocolMapperUtils.MULTIVALUED_LABEL)
.helpText(ProtocolMapperUtils.MULTIVALUED_HELP_TEXT)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue(false)
.add()
.build();
OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserPropertyMapper.class);
}
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return configProperties; return configProperties;
} }
@ -77,4 +83,12 @@ public final class DeployedScriptOIDCProtocolMapper extends ScriptBasedOIDCProto
public boolean isSupported() { public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS); return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS);
} }
public void setMetadata(ScriptProviderMetadata metadata) {
this.metadata = metadata;
}
public ScriptProviderMetadata getMetadata() {
return metadata;
}
} }