parent
600fff4f6f
commit
1de9c201c6
49 changed files with 838 additions and 659 deletions
|
@ -17,16 +17,20 @@
|
|||
|
||||
package org.keycloak.common;
|
||||
|
||||
import static org.keycloak.common.Profile.Type.DEPRECATED;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.profile.ProfileConfigResolver;
|
||||
import org.keycloak.common.profile.ProfileException;
|
||||
import org.keycloak.common.profile.PropertiesFileProfileConfigResolver;
|
||||
import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -34,167 +38,74 @@ import org.jboss.logging.Logger;
|
|||
*/
|
||||
public class Profile {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Profile.class);
|
||||
|
||||
private static Profile CURRENT;
|
||||
private final ProfileValue profile;
|
||||
private final Set<Feature> disabledFeatures = new HashSet<>();
|
||||
private final Set<Feature> previewFeatures = new HashSet<>();
|
||||
private final Set<Feature> experimentalFeatures = new HashSet<>();
|
||||
private final Set<Feature> deprecatedFeatures = new HashSet<>();
|
||||
private final PropertyResolver propertyResolver;
|
||||
public Profile(PropertyResolver resolver) {
|
||||
this.propertyResolver = resolver;
|
||||
Config config = new Config();
|
||||
|
||||
profile = ProfileValue.valueOf(config.getProfile().toUpperCase());
|
||||
|
||||
for (Feature f : Feature.values()) {
|
||||
Boolean enabled = config.getConfig(f);
|
||||
Type type = f.getType();
|
||||
|
||||
switch (type) {
|
||||
case DEFAULT:
|
||||
if (enabled != null && !enabled) {
|
||||
disabledFeatures.add(f);
|
||||
}
|
||||
break;
|
||||
case DEPRECATED:
|
||||
deprecatedFeatures.add(f);
|
||||
case DISABLED_BY_DEFAULT:
|
||||
if (enabled == null || !enabled) {
|
||||
disabledFeatures.add(f);
|
||||
} else if (DEPRECATED.equals(type)) {
|
||||
logger.warnf("Deprecated feature enabled: " + f.name().toLowerCase());
|
||||
}
|
||||
break;
|
||||
case PREVIEW:
|
||||
previewFeatures.add(f);
|
||||
if ((enabled == null || !enabled) && !profile.equals(ProfileValue.PREVIEW)) {
|
||||
disabledFeatures.add(f);
|
||||
} else {
|
||||
logger.info("Preview feature enabled: " + f.name().toLowerCase());
|
||||
}
|
||||
break;
|
||||
case EXPERIMENTAL:
|
||||
experimentalFeatures.add(f);
|
||||
if (enabled == null || !enabled) {
|
||||
disabledFeatures.add(f);
|
||||
} else {
|
||||
logger.warn("Experimental feature enabled: " + f.name().toLowerCase());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!disabledFeatures.contains(Feature.ADMIN2) || !disabledFeatures.contains(Feature.ADMIN)) && disabledFeatures.contains(Feature.ADMIN_API)) {
|
||||
throw new RuntimeException(String.format("Invalid value for feature: %s needs to be enabled because it is required by feature %s.",
|
||||
Feature.ADMIN_API, Arrays.asList(Feature.ADMIN, Feature.ADMIN2)));
|
||||
}
|
||||
}
|
||||
|
||||
private static Profile getInstance() {
|
||||
if (CURRENT == null) {
|
||||
CURRENT = new Profile(null);
|
||||
}
|
||||
return CURRENT;
|
||||
}
|
||||
|
||||
public static void setInstance(Profile instance) {
|
||||
CURRENT = instance;
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
PropertyResolver resolver = null;
|
||||
|
||||
if (CURRENT != null) {
|
||||
resolver = CURRENT.propertyResolver;
|
||||
}
|
||||
|
||||
CURRENT = new Profile(resolver);
|
||||
}
|
||||
|
||||
public static String getName() {
|
||||
return getInstance().profile.name().toLowerCase();
|
||||
}
|
||||
|
||||
public static Set<Feature> getDisabledFeatures() {
|
||||
return getInstance().disabledFeatures;
|
||||
}
|
||||
|
||||
public static Set<Feature> getPreviewFeatures() {
|
||||
return getInstance().previewFeatures;
|
||||
}
|
||||
|
||||
public static Set<Feature> getExperimentalFeatures() {
|
||||
return getInstance().experimentalFeatures;
|
||||
}
|
||||
|
||||
public static Set<Feature> getDeprecatedFeatures() {
|
||||
return getInstance().deprecatedFeatures;
|
||||
}
|
||||
|
||||
public static boolean isFeatureEnabled(Feature feature) {
|
||||
return !getInstance().disabledFeatures.contains(feature);
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
DEFAULT,
|
||||
DISABLED_BY_DEFAULT,
|
||||
PREVIEW,
|
||||
EXPERIMENTAL,
|
||||
DEPRECATED;
|
||||
}
|
||||
|
||||
public enum Feature {
|
||||
AUTHORIZATION("Authorization Service", Type.DEFAULT),
|
||||
ACCOUNT2("New Account Management Console", Type.DEFAULT),
|
||||
|
||||
ACCOUNT_API("Account Management REST API", Type.DEFAULT),
|
||||
ACCOUNT2("New Account Management Console", Type.DEFAULT, Feature.ACCOUNT_API),
|
||||
|
||||
ADMIN_FINE_GRAINED_AUTHZ("Fine-Grained Admin Permissions", Type.PREVIEW),
|
||||
/**
|
||||
* Controls the availability of the Admin REST-API.
|
||||
*/
|
||||
|
||||
ADMIN_API("Admin API", Type.DEFAULT),
|
||||
|
||||
/**
|
||||
* Controls the availability of the legacy admin-console.
|
||||
* Note that the admin-console requires the {@link #ADMIN_API} feature.
|
||||
*/
|
||||
@Deprecated
|
||||
ADMIN("Legacy Admin Console", Type.DEPRECATED),
|
||||
|
||||
/**
|
||||
* Controls the availability of the admin-console.
|
||||
* Note that the admin-console requires the {@link #ADMIN_API} feature.
|
||||
*/
|
||||
ADMIN2("New Admin Console", Type.DEFAULT),
|
||||
DOCKER("Docker Registry protocol", Type.DISABLED_BY_DEFAULT),
|
||||
IMPERSONATION("Ability for admins to impersonate users", Type.DEFAULT),
|
||||
OPENSHIFT_INTEGRATION("Extension to enable securing OpenShift", Type.PREVIEW),
|
||||
SCRIPTS("Write custom authenticators using JavaScript", Type.PREVIEW),
|
||||
TOKEN_EXCHANGE("Token Exchange Service", Type.PREVIEW),
|
||||
WEB_AUTHN("W3C Web Authentication (WebAuthn)", Type.DEFAULT),
|
||||
CLIENT_POLICIES("Client configuration policies", Type.DEFAULT),
|
||||
CIBA("OpenID Connect Client Initiated Backchannel Authentication (CIBA)", Type.DEFAULT),
|
||||
MAP_STORAGE("New store", Type.EXPERIMENTAL),
|
||||
PAR("OAuth 2.0 Pushed Authorization Requests (PAR)", Type.DEFAULT),
|
||||
DECLARATIVE_USER_PROFILE("Configure user profiles using a declarative style", Type.PREVIEW),
|
||||
DYNAMIC_SCOPES("Dynamic OAuth 2.0 scopes", Type.EXPERIMENTAL),
|
||||
CLIENT_SECRET_ROTATION("Client Secret Rotation", Type.PREVIEW),
|
||||
STEP_UP_AUTHENTICATION("Step-up Authentication", Type.DEFAULT),
|
||||
RECOVERY_CODES("Recovery codes", Type.PREVIEW),
|
||||
UPDATE_EMAIL("Update Email Action", Type.PREVIEW),
|
||||
JS_ADAPTER("Host keycloak.js and keycloak-authz.js through the Keycloak sever", Type.DEFAULT);
|
||||
ADMIN2("New Admin Console", Type.DEFAULT, Feature.ADMIN_API),
|
||||
|
||||
DOCKER("Docker Registry protocol", Type.DISABLED_BY_DEFAULT),
|
||||
|
||||
IMPERSONATION("Ability for admins to impersonate users", Type.DEFAULT),
|
||||
|
||||
OPENSHIFT_INTEGRATION("Extension to enable securing OpenShift", Type.PREVIEW),
|
||||
|
||||
SCRIPTS("Write custom authenticators using JavaScript", Type.PREVIEW),
|
||||
|
||||
TOKEN_EXCHANGE("Token Exchange Service", Type.PREVIEW),
|
||||
|
||||
WEB_AUTHN("W3C Web Authentication (WebAuthn)", Type.DEFAULT),
|
||||
|
||||
CLIENT_POLICIES("Client configuration policies", Type.DEFAULT),
|
||||
|
||||
CIBA("OpenID Connect Client Initiated Backchannel Authentication (CIBA)", Type.DEFAULT),
|
||||
|
||||
MAP_STORAGE("New store", Type.EXPERIMENTAL),
|
||||
|
||||
PAR("OAuth 2.0 Pushed Authorization Requests (PAR)", Type.DEFAULT),
|
||||
|
||||
DECLARATIVE_USER_PROFILE("Configure user profiles using a declarative style", Type.PREVIEW),
|
||||
|
||||
DYNAMIC_SCOPES("Dynamic OAuth 2.0 scopes", Type.EXPERIMENTAL),
|
||||
|
||||
CLIENT_SECRET_ROTATION("Client Secret Rotation", Type.PREVIEW),
|
||||
|
||||
STEP_UP_AUTHENTICATION("Step-up Authentication", Type.DEFAULT),
|
||||
|
||||
RECOVERY_CODES("Recovery codes", Type.PREVIEW),
|
||||
|
||||
UPDATE_EMAIL("Update Email Action", Type.PREVIEW),
|
||||
|
||||
JS_ADAPTER("Host keycloak.js and keycloak-authz.js through the Keycloak sever", Type.DEFAULT);
|
||||
|
||||
private final Type type;
|
||||
private String label;
|
||||
|
||||
private Set<Feature> dependencies;
|
||||
Feature(String label, Type type) {
|
||||
this.label = label;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
Feature(String label, Type type, Feature... dependencies) {
|
||||
this.label = label;
|
||||
this.type = type;
|
||||
this.dependencies = Arrays.stream(dependencies).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return name().toLowerCase().replaceAll("_", "-");
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
@ -202,87 +113,161 @@ public class Profile {
|
|||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Set<Feature> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
DEFAULT("Default"),
|
||||
DISABLED_BY_DEFAULT("Disabled by default"),
|
||||
PREVIEW("Preview"),
|
||||
EXPERIMENTAL("Experimental"),
|
||||
DEPRECATED("Deprecated");
|
||||
|
||||
private String label;
|
||||
|
||||
Type(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum ProfileValue {
|
||||
private static final Logger logger = Logger.getLogger(Profile.class);
|
||||
|
||||
private static final List<ProfileConfigResolver> DEFAULT_RESOLVERS = new LinkedList<>();
|
||||
static {
|
||||
DEFAULT_RESOLVERS.add(new PropertiesProfileConfigResolver(System.getProperties()));
|
||||
DEFAULT_RESOLVERS.add(new PropertiesFileProfileConfigResolver());
|
||||
};
|
||||
|
||||
private static Profile CURRENT;
|
||||
|
||||
private final ProfileName profileName;
|
||||
|
||||
private final Map<Feature, Boolean> features;
|
||||
|
||||
public static Profile defaults() {
|
||||
return configure();
|
||||
}
|
||||
|
||||
public static Profile configure(ProfileConfigResolver... resolvers) {
|
||||
ProfileName profile = Arrays.stream(resolvers).map(ProfileConfigResolver::getProfileName).filter(Objects::nonNull).findFirst().orElse(ProfileName.DEFAULT);
|
||||
Map<Feature, Boolean> features = Arrays.stream(Feature.values()).collect(Collectors.toMap(f -> f, f -> isFeatureEnabled(profile, f, resolvers)));
|
||||
verifyConfig(features);
|
||||
|
||||
CURRENT = new Profile(profile, features);
|
||||
return CURRENT;
|
||||
}
|
||||
|
||||
public static Profile init(ProfileName profileName, Map<Feature, Boolean> features) {
|
||||
CURRENT = new Profile(profileName, features);
|
||||
return CURRENT;
|
||||
}
|
||||
|
||||
private Profile(ProfileName profileName, Map<Feature, Boolean> features) {
|
||||
this.profileName = profileName;
|
||||
this.features = Collections.unmodifiableMap(features);
|
||||
|
||||
logUnsupportedFeatures();
|
||||
}
|
||||
|
||||
public static Profile getInstance() {
|
||||
return CURRENT;
|
||||
}
|
||||
|
||||
public static boolean isFeatureEnabled(Feature feature) {
|
||||
return getInstance().features.get(feature);
|
||||
}
|
||||
|
||||
public ProfileName getName() {
|
||||
return profileName;
|
||||
}
|
||||
|
||||
public Set<Feature> getAllFeatures() {
|
||||
return features.keySet();
|
||||
}
|
||||
|
||||
public Set<Feature> getDisabledFeatures() {
|
||||
return features.entrySet().stream().filter(e -> !e.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public Set<Feature> getPreviewFeatures() {
|
||||
return getFeatures(Feature.Type.PREVIEW);
|
||||
}
|
||||
|
||||
public Set<Feature> getExperimentalFeatures() {
|
||||
return getFeatures(Feature.Type.EXPERIMENTAL);
|
||||
}
|
||||
|
||||
public Set<Feature> getDeprecatedFeatures() {
|
||||
return getFeatures(Feature.Type.DEPRECATED);
|
||||
}
|
||||
|
||||
public Set<Feature> getFeatures(Feature.Type type) {
|
||||
return features.keySet().stream().filter(f -> f.getType().equals(type)).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public Map<Feature, Boolean> getFeatures() {
|
||||
return features;
|
||||
}
|
||||
|
||||
public enum ProfileName {
|
||||
DEFAULT,
|
||||
PREVIEW
|
||||
}
|
||||
|
||||
public interface PropertyResolver {
|
||||
String resolve(String feature);
|
||||
private static Boolean isFeatureEnabled(ProfileName profile, Feature feature, ProfileConfigResolver... resolvers) {
|
||||
ProfileConfigResolver.FeatureConfig configuration = Arrays.stream(resolvers).map(r -> r.getFeatureConfig(feature))
|
||||
.filter(r -> !r.equals(ProfileConfigResolver.FeatureConfig.UNCONFIGURED))
|
||||
.findFirst()
|
||||
.orElse(ProfileConfigResolver.FeatureConfig.UNCONFIGURED);
|
||||
switch (configuration) {
|
||||
case ENABLED:
|
||||
return true;
|
||||
case DISABLED:
|
||||
return false;
|
||||
default:
|
||||
switch (feature.getType()) {
|
||||
case DEFAULT:
|
||||
return true;
|
||||
case PREVIEW:
|
||||
return profile.equals(ProfileName.PREVIEW) ? true : false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Config {
|
||||
|
||||
private Properties properties;
|
||||
|
||||
public Config() {
|
||||
properties = new Properties();
|
||||
|
||||
try {
|
||||
String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
|
||||
if (jbossServerConfigDir != null) {
|
||||
File file = new File(jbossServerConfigDir, "profile.properties");
|
||||
if (file.isFile()) {
|
||||
try (FileInputStream is = new FileInputStream(file)) {
|
||||
properties.load(is);
|
||||
}
|
||||
private static void verifyConfig(Map<Feature, Boolean> features) {
|
||||
for (Feature f : features.keySet()) {
|
||||
if (f.getDependencies() != null) {
|
||||
for (Feature d : f.getDependencies()) {
|
||||
if (!features.get(d)) {
|
||||
throw new ProfileException("Feature " + f.getKey() + " depends on disabled feature " + d.getKey());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getProfile() {
|
||||
String profile = getProperty("keycloak.profile");
|
||||
if (profile != null) {
|
||||
return profile;
|
||||
}
|
||||
private void logUnsupportedFeatures() {
|
||||
logUnsuportedFeatures(Feature.Type.PREVIEW, Logger.Level.INFO);
|
||||
logUnsuportedFeatures(Feature.Type.EXPERIMENTAL, Logger.Level.WARN);
|
||||
logUnsuportedFeatures(Feature.Type.DEPRECATED, Logger.Level.WARN);
|
||||
}
|
||||
|
||||
profile = properties.getProperty("profile");
|
||||
if (profile != null) {
|
||||
if (profile.equals("community")) {
|
||||
profile = "default";
|
||||
}
|
||||
private void logUnsuportedFeatures(Feature.Type type, Logger.Level level) {
|
||||
String enabledFeaturesOfType = features.entrySet().stream()
|
||||
.filter(e -> e.getValue() && e.getKey().getType().equals(type))
|
||||
.map(e -> e.getKey().getKey()).sorted().collect(Collectors.joining(", "));
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
return ProfileValue.DEFAULT.name();
|
||||
}
|
||||
|
||||
public Boolean getConfig(Feature feature) {
|
||||
String config = getProperty("keycloak.profile.feature." + feature.name().toLowerCase());
|
||||
|
||||
if (config == null) {
|
||||
config = properties.getProperty("feature." + feature.name().toLowerCase());
|
||||
}
|
||||
|
||||
if (config == null) {
|
||||
return null;
|
||||
} else if (config.equals("enabled")) {
|
||||
return Boolean.TRUE;
|
||||
} else if (config.equals("disabled")) {
|
||||
return Boolean.FALSE;
|
||||
} else {
|
||||
throw new RuntimeException("Invalid value for feature " + config);
|
||||
}
|
||||
}
|
||||
|
||||
private String getProperty(String name) {
|
||||
String value = System.getProperty(name);
|
||||
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (propertyResolver != null) {
|
||||
return propertyResolver.resolve(name);
|
||||
}
|
||||
|
||||
return null;
|
||||
if (!enabledFeaturesOfType.isEmpty()) {
|
||||
logger.logv(level, "{0} features enabled: {1}", type.getLabel(), enabledFeaturesOfType);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package org.keycloak.common.profile;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CommaSeparatedListProfileConfigResolver implements ProfileConfigResolver {
|
||||
|
||||
private Set<String> enabledFeatures;
|
||||
private Set<String> disabledFeatures;
|
||||
|
||||
public CommaSeparatedListProfileConfigResolver(String enabledFeatures, String disabledFeatures) {
|
||||
if (enabledFeatures != null) {
|
||||
this.enabledFeatures = Arrays.stream(enabledFeatures.split(",")).collect(Collectors.toSet());
|
||||
}
|
||||
if (disabledFeatures != null) {
|
||||
this.disabledFeatures = Arrays.stream(disabledFeatures.split(",")).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Profile.ProfileName getProfileName() {
|
||||
if (enabledFeatures != null && enabledFeatures.contains(Profile.ProfileName.PREVIEW.name().toLowerCase())) {
|
||||
return Profile.ProfileName.PREVIEW;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeatureConfig getFeatureConfig(Profile.Feature feature) {
|
||||
String key = feature.getKey();
|
||||
if (enabledFeatures != null && enabledFeatures.contains(key)) {
|
||||
return FeatureConfig.ENABLED;
|
||||
} else if (disabledFeatures != null && disabledFeatures.contains(key)) {
|
||||
return FeatureConfig.DISABLED;
|
||||
}
|
||||
return FeatureConfig.UNCONFIGURED;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.keycloak.common.profile;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
|
||||
public interface ProfileConfigResolver {
|
||||
|
||||
Profile.ProfileName getProfileName();
|
||||
|
||||
FeatureConfig getFeatureConfig(Profile.Feature feature);
|
||||
|
||||
public enum FeatureConfig {
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
UNCONFIGURED
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package org.keycloak.common.profile;
|
||||
|
||||
public class ProfileException extends RuntimeException {
|
||||
|
||||
public ProfileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ProfileException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package org.keycloak.common.profile;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
public class PropertiesFileProfileConfigResolver implements ProfileConfigResolver {
|
||||
|
||||
private Properties properties;
|
||||
|
||||
public PropertiesFileProfileConfigResolver() {
|
||||
try {
|
||||
String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
|
||||
if (jbossServerConfigDir != null) {
|
||||
File file = new File(jbossServerConfigDir, "profile.properties");
|
||||
if (file.isFile()) {
|
||||
try (FileInputStream is = new FileInputStream(file)) {
|
||||
properties = new Properties();
|
||||
properties.load(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ProfileException("Failed to load profile propeties file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Profile.ProfileName getProfileName() {
|
||||
if (properties != null) {
|
||||
String profile = properties.getProperty("profile");
|
||||
if (profile != null) {
|
||||
return Profile.ProfileName.valueOf(profile.toUpperCase());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeatureConfig getFeatureConfig(Profile.Feature feature) {
|
||||
if (properties != null) {
|
||||
String config = properties.getProperty("feature." + feature.name().toLowerCase());
|
||||
if (config != null) {
|
||||
switch (config) {
|
||||
case "enabled":
|
||||
return FeatureConfig.ENABLED;
|
||||
case "disabled":
|
||||
return FeatureConfig.DISABLED;
|
||||
default:
|
||||
throw new ProfileException("Invalid config value '" + config + "' for feature " + feature.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
return FeatureConfig.UNCONFIGURED;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package org.keycloak.common.profile;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public class PropertiesProfileConfigResolver implements ProfileConfigResolver {
|
||||
|
||||
private Properties properties;
|
||||
|
||||
public PropertiesProfileConfigResolver(Properties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Profile.ProfileName getProfileName() {
|
||||
String profile = properties.getProperty("keycloak.profile");
|
||||
return profile != null ? Profile.ProfileName.valueOf(profile.toUpperCase()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeatureConfig getFeatureConfig(Profile.Feature feature) {
|
||||
String config = properties.getProperty("keycloak.profile.feature." + feature.name().toLowerCase());
|
||||
if (config != null) {
|
||||
switch (config) {
|
||||
case "enabled":
|
||||
return FeatureConfig.ENABLED;
|
||||
case "disabled":
|
||||
return FeatureConfig.DISABLED;
|
||||
default:
|
||||
throw new ProfileException("Invalid config value '" + config + "' for feature " + feature.getKey());
|
||||
}
|
||||
}
|
||||
return FeatureConfig.UNCONFIGURED;
|
||||
}
|
||||
}
|
|
@ -1,54 +1,198 @@
|
|||
package org.keycloak.common;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.common.profile.CommaSeparatedListProfileConfigResolver;
|
||||
import org.keycloak.common.profile.ProfileException;
|
||||
import org.keycloak.common.profile.PropertiesFileProfileConfigResolver;
|
||||
import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ProfileTest {
|
||||
|
||||
private static final Profile.Feature DEFAULT_FEATURE = Profile.Feature.AUTHORIZATION;
|
||||
private static final Profile.Feature DISABLED_BY_DEFAULT_FEATURE = Profile.Feature.DOCKER;
|
||||
private static final Profile.Feature PREVIEW_FEATURE = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ;
|
||||
private static final Profile.Feature EXPERIMENTAL_FEATURE = Profile.Feature.DYNAMIC_SCOPES;
|
||||
private static final Profile.Feature DEPRECATED_FEATURE = Profile.Feature.ADMIN;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void checkDefaults() {
|
||||
Assert.assertEquals("default", Profile.getName());
|
||||
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
|
||||
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
Assert.assertEquals(Profile.Feature.Type.DEFAULT, DEFAULT_FEATURE.getType());
|
||||
Assert.assertEquals(Profile.Feature.Type.DISABLED_BY_DEFAULT, DISABLED_BY_DEFAULT_FEATURE.getType());
|
||||
Assert.assertEquals(Profile.Feature.Type.PREVIEW, PREVIEW_FEATURE.getType());
|
||||
Assert.assertEquals(Profile.Feature.Type.EXPERIMENTAL, EXPERIMENTAL_FEATURE.getType());
|
||||
Assert.assertEquals(Profile.Feature.Type.DEPRECATED, DEPRECATED_FEATURE.getType());
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
Profile.defaults();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configWithSystemProperties() {
|
||||
Assert.assertEquals("default", Profile.getName());
|
||||
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
|
||||
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION));
|
||||
assertTrue(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
|
||||
public void checkDefaults() {
|
||||
Profile profile = Profile.defaults();
|
||||
|
||||
System.setProperty("keycloak.profile", "preview");
|
||||
System.setProperty("keycloak.profile.feature.docker", "enabled");
|
||||
System.setProperty("keycloak.profile.feature.impersonation", "disabled");
|
||||
System.setProperty("keycloak.profile.feature.upload_scripts", "enabled");
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(DEFAULT_FEATURE));
|
||||
Assert.assertFalse(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE));
|
||||
Assert.assertFalse(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
||||
Assert.assertFalse(Profile.isFeatureEnabled(EXPERIMENTAL_FEATURE));
|
||||
Assert.assertFalse(Profile.isFeatureEnabled(DEPRECATED_FEATURE));
|
||||
|
||||
Profile.init();
|
||||
Assert.assertEquals(Profile.ProfileName.DEFAULT, profile.getName());
|
||||
assertEquals(profile.getDisabledFeatures(), Profile.Feature.ADMIN, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL);
|
||||
assertEquals(profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL);
|
||||
}
|
||||
|
||||
Assert.assertEquals("preview", Profile.getName());
|
||||
assertTrue(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
|
||||
assertTrue(Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION));
|
||||
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
|
||||
@Test
|
||||
public void checkFailureIfDependencyDisabled() {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("keycloak.profile.feature.account_api", "disabled");
|
||||
|
||||
System.getProperties().remove("keycloak.profile");
|
||||
System.getProperties().remove("keycloak.profile.feature.docker");
|
||||
System.getProperties().remove("keycloak.profile.feature.impersonation");
|
||||
System.getProperties().remove("keycloak.profile.feature.upload_scripts");
|
||||
try {
|
||||
Profile.configure(new PropertiesProfileConfigResolver(properties));
|
||||
} catch (ProfileException e) {
|
||||
Assert.assertEquals("Feature account2 depends on disabled feature account-api", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Profile.init();
|
||||
@Test
|
||||
public void checkErrorOnBadConfig() {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("keycloak.profile.feature.account_api", "invalid");
|
||||
|
||||
try {
|
||||
Profile.configure(new PropertiesProfileConfigResolver(properties));
|
||||
} catch (ProfileException e) {
|
||||
Assert.assertEquals("Invalid config value 'invalid' for feature account-api", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enablePreviewWithProperties() {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("keycloak.profile", "preview");
|
||||
Profile.configure(new PropertiesProfileConfigResolver(properties));
|
||||
|
||||
Assert.assertEquals(Profile.ProfileName.PREVIEW, Profile.getInstance().getName());
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enablePreviewWithCommaSeparatedList() {
|
||||
Profile.configure(new CommaSeparatedListProfileConfigResolver("preview", null));
|
||||
|
||||
Assert.assertEquals(Profile.ProfileName.PREVIEW, Profile.getInstance().getName());
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enablePreviewWithPropertiesFile() throws IOException {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("profile", "preview");
|
||||
|
||||
Path tempDirectory = Files.createTempDirectory("jboss-config");
|
||||
System.setProperty("jboss.server.config.dir", tempDirectory.toString());
|
||||
|
||||
Path profileProperties = tempDirectory.resolve("profile.properties");
|
||||
|
||||
properties.store(new FileOutputStream(profileProperties.toFile()), "");
|
||||
|
||||
Profile.configure(new PropertiesFileProfileConfigResolver());
|
||||
|
||||
Assert.assertEquals(Profile.ProfileName.PREVIEW, Profile.getInstance().getName());
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
||||
|
||||
Files.delete(profileProperties);
|
||||
Files.delete(tempDirectory);
|
||||
System.getProperties().remove("jboss.server.config.dir");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configWithCommaSeparatedList() {
|
||||
String enabledFeatures = DISABLED_BY_DEFAULT_FEATURE.getKey() + "," + PREVIEW_FEATURE.getKey() + "," + EXPERIMENTAL_FEATURE.getKey() + "," + DEPRECATED_FEATURE.getKey();
|
||||
String disabledFeatures = DEFAULT_FEATURE.getKey();
|
||||
Profile.configure(new CommaSeparatedListProfileConfigResolver(enabledFeatures, disabledFeatures));
|
||||
|
||||
Assert.assertFalse(Profile.isFeatureEnabled(DEFAULT_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(EXPERIMENTAL_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(DEPRECATED_FEATURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configWithProperties() {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("keycloak.profile.feature." + DEFAULT_FEATURE.name().toLowerCase(), "disabled");
|
||||
properties.setProperty("keycloak.profile.feature." + DISABLED_BY_DEFAULT_FEATURE.name().toLowerCase(), "enabled");
|
||||
properties.setProperty("keycloak.profile.feature." + PREVIEW_FEATURE.name().toLowerCase(), "enabled");
|
||||
properties.setProperty("keycloak.profile.feature." + EXPERIMENTAL_FEATURE.name().toLowerCase(), "enabled");
|
||||
properties.setProperty("keycloak.profile.feature." + DEPRECATED_FEATURE.name().toLowerCase(), "enabled");
|
||||
|
||||
Profile.configure(new PropertiesProfileConfigResolver(properties));
|
||||
|
||||
Assert.assertFalse(Profile.isFeatureEnabled(DEFAULT_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(EXPERIMENTAL_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(DEPRECATED_FEATURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configWithPropertiesFile() throws IOException {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("feature." + DEFAULT_FEATURE.name().toLowerCase(), "disabled");
|
||||
properties.setProperty("feature." + DISABLED_BY_DEFAULT_FEATURE.name().toLowerCase(), "enabled");
|
||||
properties.setProperty("feature." + PREVIEW_FEATURE.name().toLowerCase(), "enabled");
|
||||
properties.setProperty("feature." + EXPERIMENTAL_FEATURE.name().toLowerCase(), "enabled");
|
||||
properties.setProperty("feature." + DEPRECATED_FEATURE.name().toLowerCase(), "enabled");
|
||||
|
||||
Path tempDirectory = Files.createTempDirectory("jboss-config");
|
||||
System.setProperty("jboss.server.config.dir", tempDirectory.toString());
|
||||
|
||||
Path profileProperties = tempDirectory.resolve("profile.properties");
|
||||
|
||||
properties.store(new FileOutputStream(profileProperties.toFile()), "");
|
||||
|
||||
Profile.configure(new PropertiesFileProfileConfigResolver());
|
||||
|
||||
Assert.assertFalse(Profile.isFeatureEnabled(DEFAULT_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(EXPERIMENTAL_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(DEPRECATED_FEATURE));
|
||||
|
||||
Files.delete(profileProperties);
|
||||
Files.delete(tempDirectory);
|
||||
System.getProperties().remove("jboss.server.config.dir");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configWithMultipleResolvers() {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("keycloak.profile.feature." + PREVIEW_FEATURE.name().toLowerCase(), "enabled");
|
||||
|
||||
Profile.configure(new CommaSeparatedListProfileConfigResolver(DISABLED_BY_DEFAULT_FEATURE.getKey(), ""), new PropertiesProfileConfigResolver(properties));
|
||||
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE));
|
||||
Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
||||
}
|
||||
|
||||
public static void assertEquals(Set<Profile.Feature> actual, Profile.Feature... expected) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.common.Profile;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -36,10 +37,12 @@ public class ProfileInfoRepresentation {
|
|||
public static ProfileInfoRepresentation create() {
|
||||
ProfileInfoRepresentation info = new ProfileInfoRepresentation();
|
||||
|
||||
info.name = Profile.getName();
|
||||
info.disabledFeatures = names(Profile.getDisabledFeatures());
|
||||
info.previewFeatures = names(Profile.getPreviewFeatures());
|
||||
info.experimentalFeatures = names(Profile.getExperimentalFeatures());
|
||||
Profile profile = Profile.getInstance();
|
||||
|
||||
info.name = profile.getName().name().toLowerCase();
|
||||
info.disabledFeatures = names(profile.getDisabledFeatures());
|
||||
info.previewFeatures = names(profile.getPreviewFeatures());
|
||||
info.experimentalFeatures = names(profile.getExperimentalFeatures());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
|
|
@ -13,26 +13,26 @@ public class Features {
|
|||
|
||||
public Features() {
|
||||
this.features = Arrays.stream(Profile.Feature.values())
|
||||
.filter(f -> !f.getType().equals(Profile.Type.EXPERIMENTAL))
|
||||
.filter(f -> !f.getType().equals(Profile.Feature.Type.EXPERIMENTAL))
|
||||
.map(f -> new Feature(f))
|
||||
.sorted(Comparator.comparing(Feature::getName))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Feature> getSupported() {
|
||||
return features.stream().filter(f -> f.getType().equals(Profile.Type.DEFAULT)).collect(Collectors.toList());
|
||||
return features.stream().filter(f -> f.getType().equals(Profile.Feature.Type.DEFAULT)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Feature> getSupportedDisabledByDefault() {
|
||||
return features.stream().filter(f -> f.getType().equals(Profile.Type.DISABLED_BY_DEFAULT)).collect(Collectors.toList());
|
||||
return features.stream().filter(f -> f.getType().equals(Profile.Feature.Type.DISABLED_BY_DEFAULT)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Feature> getDeprecated() {
|
||||
return features.stream().filter(f -> f.getType().equals(Profile.Type.DEPRECATED)).collect(Collectors.toList());
|
||||
return features.stream().filter(f -> f.getType().equals(Profile.Feature.Type.DEPRECATED)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Feature> getPreview() {
|
||||
return features.stream().filter(f -> f.getType().equals(Profile.Type.PREVIEW)).collect(Collectors.toList());
|
||||
return features.stream().filter(f -> f.getType().equals(Profile.Feature.Type.PREVIEW)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public class Feature {
|
||||
|
@ -44,14 +44,14 @@ public class Features {
|
|||
}
|
||||
|
||||
public String getName() {
|
||||
return profileFeature.name().toLowerCase().replaceAll("_", "-");
|
||||
return profileFeature.getKey();
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return profileFeature.getLabel();
|
||||
}
|
||||
|
||||
private Profile.Type getType() {
|
||||
private Profile.Feature.Type getType() {
|
||||
return profileFeature.getType();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.config;
|
|||
import org.keycloak.common.Profile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -27,10 +28,12 @@ public class FeatureOptions {
|
|||
List<String> features = new ArrayList<>();
|
||||
|
||||
for (Profile.Feature value : Profile.Feature.values()) {
|
||||
features.add(value.name().toLowerCase().replace('_', '-'));
|
||||
features.add(value.getKey());
|
||||
}
|
||||
|
||||
features.add(Profile.Type.PREVIEW.name().toLowerCase());
|
||||
features.add(Profile.Feature.Type.PREVIEW.name().toLowerCase());
|
||||
|
||||
Collections.sort(features);
|
||||
|
||||
return features;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package org.keycloak.quarkus.deployment;
|
||||
|
||||
import io.quarkus.builder.item.SimpleBuildItem;
|
||||
|
||||
public final class ConfigBuildItem extends SimpleBuildItem {
|
||||
}
|
|
@ -94,6 +94,8 @@ import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
|||
import org.jboss.resteasy.spi.ResteasyDeployment;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.crypto.FipsMode;
|
||||
import org.keycloak.common.profile.PropertiesFileProfileConfigResolver;
|
||||
import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
||||
import org.keycloak.config.SecurityOptions;
|
||||
import org.keycloak.config.StorageOptions;
|
||||
import org.keycloak.config.TransactionOptions;
|
||||
|
@ -101,7 +103,7 @@ import org.keycloak.connections.jpa.JpaConnectionProvider;
|
|||
import org.keycloak.connections.jpa.JpaConnectionSpi;
|
||||
import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory;
|
||||
import org.keycloak.protocol.saml.mappers.DeployedScriptSAMLProtocolMapper;
|
||||
import org.keycloak.quarkus.runtime.QuarkusProfile;
|
||||
import org.keycloak.quarkus.runtime.QuarkusProfileConfigResolver;
|
||||
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
||||
import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
|
||||
|
@ -207,6 +209,26 @@ class KeycloakProcessor {
|
|||
return new FeatureBuildItem("keycloak");
|
||||
}
|
||||
|
||||
@Record(ExecutionTime.STATIC_INIT)
|
||||
@BuildStep
|
||||
ConfigBuildItem initConfig(KeycloakRecorder recorder) {
|
||||
Config.init(new MicroProfileConfigProvider());
|
||||
recorder.initConfig();
|
||||
return new ConfigBuildItem();
|
||||
}
|
||||
|
||||
@Record(ExecutionTime.STATIC_INIT)
|
||||
@BuildStep
|
||||
@Consume(ConfigBuildItem.class)
|
||||
ProfileBuildItem configureProfile(KeycloakRecorder recorder) {
|
||||
Profile profile = Profile.configure(
|
||||
new QuarkusProfileConfigResolver(),
|
||||
new PropertiesFileProfileConfigResolver()); // Need profile.properties for now as testsuite relies on it
|
||||
recorder.configureProfile(profile.getName(), profile.getFeatures());
|
||||
|
||||
return new ProfileBuildItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Configures the persistence unit for Quarkus.
|
||||
*
|
||||
|
@ -331,8 +353,8 @@ class KeycloakProcessor {
|
|||
*/
|
||||
@Record(ExecutionTime.STATIC_INIT)
|
||||
@BuildStep
|
||||
@Consume(ProfileBuildItem.class)
|
||||
KeycloakSessionFactoryPreInitBuildItem configureKeycloakSessionFactory(KeycloakRecorder recorder, List<PersistenceXmlDescriptorBuildItem> descriptors) {
|
||||
Profile.setInstance(new QuarkusProfile());
|
||||
Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories = new HashMap<>();
|
||||
Map<Class<? extends Provider>, String> defaultProviders = new HashMap<>();
|
||||
Map<String, ProviderFactory> preConfiguredProviders = new HashMap<>();
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package org.keycloak.quarkus.deployment;
|
||||
|
||||
import io.quarkus.builder.item.SimpleBuildItem;
|
||||
|
||||
public final class ProfileBuildItem extends SimpleBuildItem {
|
||||
}
|
|
@ -64,6 +64,14 @@ public class KeycloakRecorder {
|
|||
public static final String DEFAULT_HEALTH_ENDPOINT = "/health";
|
||||
public static final String DEFAULT_METRICS_ENDPOINT = "/metrics";
|
||||
|
||||
public void initConfig() {
|
||||
Config.init(new MicroProfileConfigProvider());
|
||||
}
|
||||
|
||||
public void configureProfile(Profile.ProfileName profileName, Map<Profile.Feature, Boolean> features) {
|
||||
Profile.init(profileName, features);
|
||||
}
|
||||
|
||||
public void configureLiquibase(Map<String, List<String>> services) {
|
||||
ServiceLocator locator = Scope.getCurrentScope().getServiceLocator();
|
||||
if (locator instanceof FastServiceLocator)
|
||||
|
@ -75,8 +83,6 @@ public class KeycloakRecorder {
|
|||
Map<Class<? extends Provider>, String> defaultProviders,
|
||||
Map<String, ProviderFactory> preConfiguredProviders,
|
||||
List<ClasspathThemeProviderFactory.ThemesRepresentation> themes, boolean reaugmented) {
|
||||
Config.init(new MicroProfileConfigProvider());
|
||||
Profile.setInstance(new QuarkusProfile());
|
||||
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, themes, reaugmented));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* 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.quarkus.runtime;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawPersistedProperty;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
|
||||
public class QuarkusProfile extends Profile {
|
||||
|
||||
public QuarkusProfile() {
|
||||
super(new DefaultPropertyResolver());
|
||||
}
|
||||
|
||||
private static class DefaultPropertyResolver implements PropertyResolver {
|
||||
|
||||
@Override
|
||||
public String resolve(String key) {
|
||||
if (isFeaturePresent(key, getCurrentValue("kc.features"))) {
|
||||
if (isPreviewProfileKey(key)) {
|
||||
return Profile.Type.PREVIEW.name();
|
||||
}
|
||||
|
||||
return "enabled";
|
||||
}
|
||||
|
||||
if (isFeaturePresent(key, getCurrentValue("kc.features-disabled"))) {
|
||||
if (!isPreviewProfileKey(key)) {
|
||||
return "disabled";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isFeaturePresent(String key, String features) {
|
||||
if (features == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String feature : features.split(",")) {
|
||||
if (isPreviewProfileKey(key)) {
|
||||
try {
|
||||
Profile.Type profileType = Profile.Type.valueOf(feature);
|
||||
|
||||
if (Profile.Type.PREVIEW.equals(profileType)) {
|
||||
return true;
|
||||
}
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key.substring(key.lastIndexOf('.') + 1).toUpperCase().equals(feature)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isPreviewProfileKey(String key) {
|
||||
return key.equals("keycloak.profile");
|
||||
}
|
||||
|
||||
private String getCurrentValue(String name) {
|
||||
String enabledFeatures = getRawPersistedProperty(name).orElse(null);
|
||||
|
||||
if (enabledFeatures == null) {
|
||||
enabledFeatures = Configuration.getRawValue(name);
|
||||
}
|
||||
|
||||
if (enabledFeatures == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return enabledFeatures.toUpperCase().replace('-', '_');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.keycloak.quarkus.runtime;
|
||||
|
||||
import org.keycloak.common.profile.CommaSeparatedListProfileConfigResolver;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
|
||||
public class QuarkusProfileConfigResolver extends CommaSeparatedListProfileConfigResolver {
|
||||
|
||||
public QuarkusProfileConfigResolver() {
|
||||
super(getConfig("kc.features"), getConfig("kc.features-disabled"));
|
||||
}
|
||||
|
||||
private static String getConfig(String key) {
|
||||
return Configuration.getRawPersistedProperty(key)
|
||||
.orElse(Configuration.getRawValue(key));
|
||||
}
|
||||
|
||||
}
|
|
@ -2,9 +2,7 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
|||
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.config.FeatureOptions;
|
||||
import org.keycloak.config.StorageOptions;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
||||
|
||||
import static java.util.Optional.of;
|
||||
import static org.keycloak.config.StorageOptions.STORAGE;
|
||||
|
@ -42,7 +40,7 @@ final class FeaturePropertyMappers {
|
|||
|
||||
Set<String> featureSet = new HashSet<>(List.of(features.orElse("").split(",")));
|
||||
|
||||
featureSet.add(Profile.Feature.MAP_STORAGE.name().replace('_', '-'));
|
||||
featureSet.add(Profile.Feature.MAP_STORAGE.getKey());
|
||||
|
||||
return of(String.join(",", featureSet));
|
||||
}
|
||||
|
|
|
@ -52,10 +52,18 @@ public class FeaturesDistTest {
|
|||
|
||||
@Test
|
||||
@Launch({StartDev.NAME, "--features=preview", "--features-disabled=token-exchange"})
|
||||
public void testPreviewFeatureDisabledInPreviewMode(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertStartedDevMode();
|
||||
assertFalse(cliResult.getOutput().contains("token-exchange"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({StartDev.NAME, "--features=token-exchange", "--features-disabled=token-exchange"})
|
||||
public void testEnablePrecedenceOverDisable(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertStartedDevMode();
|
||||
assertPreviewFeaturesEnabled((CLIResult) result);
|
||||
assertThat(cliResult.getOutput(), containsString("Preview features enabled: token-exchange"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -65,8 +73,7 @@ public class FeaturesDistTest {
|
|||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertStartedDevMode();
|
||||
assertThat(cliResult.getOutput(), CoreMatchers.allOf(
|
||||
containsString("Preview feature enabled: admin_fine_grained_authz"),
|
||||
containsString("Preview feature enabled: token_exchange")));
|
||||
containsString("Preview features enabled: admin-fine-grained-authz, token-exchange")));
|
||||
assertFalse(cliResult.getOutput().contains("declarative-user-profile"));
|
||||
}
|
||||
|
||||
|
@ -77,17 +84,12 @@ public class FeaturesDistTest {
|
|||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertStartedDevMode();
|
||||
assertThat(cliResult.getOutput(), CoreMatchers.allOf(
|
||||
containsString("Preview feature enabled: admin_fine_grained_authz"),
|
||||
containsString("Preview feature enabled: token_exchange")));
|
||||
containsString("Preview features enabled: admin-fine-grained-authz, token-exchange")));
|
||||
assertFalse(cliResult.getOutput().contains("declarative-user-profile"));
|
||||
}
|
||||
|
||||
private void assertPreviewFeaturesEnabled(CLIResult result) {
|
||||
assertThat(result.getOutput(), CoreMatchers.allOf(
|
||||
containsString("Preview feature enabled: admin_fine_grained_authz"),
|
||||
containsString("Preview feature enabled: openshift_integration"),
|
||||
containsString("Preview feature enabled: scripts"),
|
||||
containsString("Preview feature enabled: token_exchange"),
|
||||
containsString("Preview feature enabled: declarative_user_profile")));
|
||||
containsString("Preview features enabled: admin-fine-grained-authz, client-secret-rotation, declarative-user-profile, openshift-integration, recovery-codes, scripts, token-exchange, update-email")));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ public class ChmStorageDistTest {
|
|||
}
|
||||
|
||||
private void assertExpectedMessages(CLIResult cliResult, RawDistRootPath distPath) {
|
||||
cliResult.assertMessage("Experimental feature enabled: map_storage");
|
||||
cliResult.assertMessage("Experimental features enabled: map-storage");
|
||||
cliResult.assertMessage("Hibernate ORM is disabled because no JPA entities were found");
|
||||
Assert.assertFalse(distPath.getDistRootPath().resolve("data").resolve("h2").toFile().exists());
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ public class HotRodStoreDistTest {
|
|||
@Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" })
|
||||
void testSuccessful(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertMessage("Experimental feature enabled: map_storage");
|
||||
cliResult.assertMessage("Experimental features enabled: map-storage");
|
||||
cliResult.assertMessage("[org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory] (main) HotRod client configuration was successful.");
|
||||
cliResult.assertStarted();
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ public class JPAStoreDistTest {
|
|||
@Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" })
|
||||
void testSuccessful(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertMessage("Experimental feature enabled: map_storage");
|
||||
cliResult.assertMessage("Experimental features enabled: map-storage");
|
||||
cliResult.assertMessage("[org.keycloak.models.map.storage.jpa.liquibase.updater.MapJpaLiquibaseUpdaterProvider] (main) Initializing database schema. Using changelog META-INF/jpa-realms-changelog.xml");
|
||||
cliResult.assertStarted();
|
||||
}
|
||||
|
|
|
@ -43,19 +43,19 @@ Transaction:
|
|||
|
||||
Feature:
|
||||
|
||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||
web-authn, client-policies, ciba, map-storage, par,
|
||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
||||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||
openshift-integration, par, preview, recovery-codes, scripts,
|
||||
step-up-authentication, token-exchange, update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: authorization,
|
||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||
web-authn, client-policies, ciba, map-storage, par,
|
||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||
openshift-integration, par, preview, recovery-codes, scripts,
|
||||
step-up-authentication, token-exchange, update-email, web-authn.
|
||||
|
||||
HTTP/TLS:
|
||||
|
||||
|
|
|
@ -66,19 +66,19 @@ Transaction:
|
|||
|
||||
Feature:
|
||||
|
||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||
web-authn, client-policies, ciba, map-storage, par,
|
||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
||||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||
openshift-integration, par, preview, recovery-codes, scripts,
|
||||
step-up-authentication, token-exchange, update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: authorization,
|
||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||
web-authn, client-policies, ciba, map-storage, par,
|
||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||
openshift-integration, par, preview, recovery-codes, scripts,
|
||||
step-up-authentication, token-exchange, update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
||||
|
|
|
@ -124,19 +124,19 @@ Transaction:
|
|||
|
||||
Feature:
|
||||
|
||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||
web-authn, client-policies, ciba, map-storage, par,
|
||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
||||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||
openshift-integration, par, preview, recovery-codes, scripts,
|
||||
step-up-authentication, token-exchange, update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: authorization,
|
||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||
web-authn, client-policies, ciba, map-storage, par,
|
||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||
openshift-integration, par, preview, recovery-codes, scripts,
|
||||
step-up-authentication, token-exchange, update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
||||
|
|
|
@ -72,19 +72,19 @@ Transaction:
|
|||
|
||||
Feature:
|
||||
|
||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||
web-authn, client-policies, ciba, map-storage, par,
|
||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
||||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||
openshift-integration, par, preview, recovery-codes, scripts,
|
||||
step-up-authentication, token-exchange, update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: authorization,
|
||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||
web-authn, client-policies, ciba, map-storage, par,
|
||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||
openshift-integration, par, preview, recovery-codes, scripts,
|
||||
step-up-authentication, token-exchange, update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
||||
|
|
|
@ -130,19 +130,19 @@ Transaction:
|
|||
|
||||
Feature:
|
||||
|
||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||
web-authn, client-policies, ciba, map-storage, par,
|
||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
||||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||
openshift-integration, par, preview, recovery-codes, scripts,
|
||||
step-up-authentication, token-exchange, update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: authorization,
|
||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||
web-authn, client-policies, ciba, map-storage, par,
|
||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||
openshift-integration, par, preview, recovery-codes, scripts,
|
||||
step-up-authentication, token-exchange, update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
|||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
initBuiltIns();
|
||||
this.providerConfig = new OIDCProviderConfig(config);
|
||||
if (providerConfig.isLegacyLogoutRedirectUri()) {
|
||||
logger.warnf("Deprecated switch '%s' is enabled. Please try to disable it and update your clients to use OpenID Connect compliant way for RP-initiated logout.", CONFIG_LEGACY_LOGOUT_REDIRECT_URI);
|
||||
|
@ -129,9 +130,9 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
|||
return builtins;
|
||||
}
|
||||
|
||||
static Map<String, ProtocolMapperModel> builtins = new HashMap<>();
|
||||
private Map<String, ProtocolMapperModel> builtins = new HashMap<>();
|
||||
|
||||
static {
|
||||
void initBuiltIns() {
|
||||
ProtocolMapperModel model;
|
||||
model = UserPropertyMapper.createClaimMapper(USERNAME,
|
||||
"username",
|
||||
|
@ -218,7 +219,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
|||
}
|
||||
}
|
||||
|
||||
private static void createUserAttributeMapper(String name, String attrName, String claimName, String type) {
|
||||
private void createUserAttributeMapper(String name, String attrName, String claimName, String type) {
|
||||
ProtocolMapperModel model = UserAttributeMapper.createClaimMapper(name,
|
||||
attrName,
|
||||
claimName, type,
|
||||
|
@ -297,7 +298,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
|||
}
|
||||
|
||||
|
||||
public static ClientScopeModel addRolesClientScope(RealmModel newRealm) {
|
||||
public ClientScopeModel addRolesClientScope(RealmModel newRealm) {
|
||||
ClientScopeModel rolesScope = KeycloakModelUtils.getClientScopeByName(newRealm, ROLES_SCOPE);
|
||||
if (rolesScope == null) {
|
||||
rolesScope = newRealm.addClientScope(ROLES_SCOPE);
|
||||
|
@ -320,7 +321,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
|||
}
|
||||
|
||||
|
||||
public static ClientScopeModel addWebOriginsClientScope(RealmModel newRealm) {
|
||||
public ClientScopeModel addWebOriginsClientScope(RealmModel newRealm) {
|
||||
ClientScopeModel originsScope = KeycloakModelUtils.getClientScopeByName(newRealm, WEB_ORIGINS_SCOPE);
|
||||
if (originsScope == null) {
|
||||
originsScope = newRealm.addClientScope(WEB_ORIGINS_SCOPE);
|
||||
|
@ -347,7 +348,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
|||
* @param newRealm the realm to which the {@code microprofile-jwt} scope is to be added.
|
||||
* @return a reference to the {@code microprofile-jwt} client scope that was either created or already exists in the realm.
|
||||
*/
|
||||
public static ClientScopeModel addMicroprofileJWTClientScope(RealmModel newRealm) {
|
||||
public ClientScopeModel addMicroprofileJWTClientScope(RealmModel newRealm) {
|
||||
ClientScopeModel microprofileScope = KeycloakModelUtils.getClientScopeByName(newRealm, MICROPROFILE_JWT_SCOPE);
|
||||
if (microprofileScope == null) {
|
||||
microprofileScope = newRealm.addClientScope(MICROPROFILE_JWT_SCOPE);
|
||||
|
@ -366,7 +367,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
|||
}
|
||||
|
||||
|
||||
public static void addAcrClientScope(RealmModel newRealm) {
|
||||
public void addAcrClientScope(RealmModel newRealm) {
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) {
|
||||
ClientScopeModel acrScope = KeycloakModelUtils.getClientScopeByName(newRealm, ACR_SCOPE);
|
||||
if (acrScope == null) {
|
||||
|
|
|
@ -23,17 +23,15 @@ import org.keycloak.models.ClientScopeModel;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.LoginProtocolFactory;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -84,26 +82,29 @@ public class DefaultMigrationProvider implements MigrationProvider {
|
|||
new RealmManager(session).setupAdminCli(realm);
|
||||
}
|
||||
|
||||
private OIDCLoginProtocolFactory getOIDCLoginProtocolFactory() {
|
||||
return (OIDCLoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addOIDCRolesClientScope(RealmModel realm) {
|
||||
return OIDCLoginProtocolFactory.addRolesClientScope(realm);
|
||||
return getOIDCLoginProtocolFactory().addRolesClientScope(realm);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addOIDCWebOriginsClientScope(RealmModel realm) {
|
||||
return OIDCLoginProtocolFactory.addWebOriginsClientScope(realm);
|
||||
return getOIDCLoginProtocolFactory().addWebOriginsClientScope(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addOIDCMicroprofileJWTClientScope(RealmModel realm) {
|
||||
return OIDCLoginProtocolFactory.addMicroprofileJWTClientScope(realm);
|
||||
return getOIDCLoginProtocolFactory().addMicroprofileJWTClientScope(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOIDCAcrClientScope(RealmModel realm) {
|
||||
OIDCLoginProtocolFactory.addAcrClientScope(realm);
|
||||
getOIDCLoginProtocolFactory().addAcrClientScope(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -112,6 +112,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.UUID;
|
||||
|
@ -890,89 +891,66 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/list-disabled-features")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Set<Profile.Feature> listDisabledFeatures() {
|
||||
return Profile.getInstance().getDisabledFeatures();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/enable-feature/{feature}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response enableFeature(@PathParam("feature") String feature) {
|
||||
Profile.Feature featureProfile;
|
||||
|
||||
try {
|
||||
featureProfile = Profile.Feature.valueOf(feature);
|
||||
} catch (IllegalArgumentException e) {
|
||||
System.err.printf("Feature '%s' doesn't exist!!\n", feature);
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
if (Profile.isFeatureEnabled(featureProfile))
|
||||
return Response.noContent().build();
|
||||
|
||||
FeatureDeployerUtil.initBeforeChangeFeature(featureProfile);
|
||||
|
||||
System.setProperty("keycloak.profile.feature." + featureProfile.toString().toLowerCase(), "enabled");
|
||||
|
||||
String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
|
||||
// If we are in jboss-based container, we need to write profile.properties file, otherwise the change in system property will disappear after restart
|
||||
if (jbossServerConfigDir != null) {
|
||||
setFeatureInProfileFile(new File(jbossServerConfigDir, "profile.properties"), featureProfile, "enabled");
|
||||
}
|
||||
|
||||
Profile.init();
|
||||
|
||||
FeatureDeployerUtil.deployFactoriesAfterFeatureEnabled(featureProfile);
|
||||
|
||||
if (Profile.isFeatureEnabled(featureProfile))
|
||||
return Response.noContent().build();
|
||||
else
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Set<Profile.Feature> enableFeature(@PathParam("feature") String feature) {
|
||||
return updateFeature(feature, true);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/disable-feature/{feature}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response disableFeature(@PathParam("feature") String feature) {
|
||||
Profile.Feature featureProfile;
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Set<Profile.Feature> disableFeature(@PathParam("feature") String feature) {
|
||||
return updateFeature(feature, false);
|
||||
}
|
||||
|
||||
private Set<Profile.Feature> updateFeature(String featureKey, boolean shouldEnable) {
|
||||
Profile.Feature feature;
|
||||
|
||||
try {
|
||||
featureProfile = Profile.Feature.valueOf(feature);
|
||||
feature = Profile.Feature.valueOf(featureKey);
|
||||
} catch (IllegalArgumentException e) {
|
||||
System.err.printf("Feature '%s' doesn't exist!!\n", feature);
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
System.err.printf("Feature '%s' doesn't exist!!\n", featureKey);
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
if (!Profile.isFeatureEnabled(featureProfile))
|
||||
return Response.noContent().build();
|
||||
if (Profile.getInstance().getFeatures().get(feature) != shouldEnable) {
|
||||
FeatureDeployerUtil.initBeforeChangeFeature(feature);
|
||||
|
||||
FeatureDeployerUtil.initBeforeChangeFeature(featureProfile);
|
||||
String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
|
||||
// If we are in jboss-based container, we need to write profile.properties file, otherwise the change in system property will disappear after restart
|
||||
if (jbossServerConfigDir != null) {
|
||||
setFeatureInProfileFile(new File(jbossServerConfigDir, "profile.properties"), feature, shouldEnable ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
disableFeatureProperties(featureProfile);
|
||||
Profile current = Profile.getInstance();
|
||||
|
||||
String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
|
||||
// If we are in jboss-based container, we need to write profile.properties file, otherwise the change in system property will disappear after restart
|
||||
if (jbossServerConfigDir != null) {
|
||||
setFeatureInProfileFile(new File(jbossServerConfigDir, "profile.properties"), featureProfile, "disabled");
|
||||
Map<Profile.Feature, Boolean> updatedFeatures = new HashMap<>();
|
||||
updatedFeatures.putAll(current.getFeatures());
|
||||
updatedFeatures.put(feature, shouldEnable);
|
||||
|
||||
Profile.init(current.getName(), updatedFeatures);
|
||||
|
||||
if (shouldEnable) {
|
||||
FeatureDeployerUtil.deployFactoriesAfterFeatureEnabled(feature);
|
||||
} else {
|
||||
FeatureDeployerUtil.undeployFactoriesAfterFeatureDisabled(feature);
|
||||
}
|
||||
}
|
||||
|
||||
Profile.init();
|
||||
|
||||
FeatureDeployerUtil.undeployFactoriesAfterFeatureDisabled(featureProfile);
|
||||
|
||||
if (!Profile.isFeatureEnabled(featureProfile))
|
||||
return Response.noContent().build();
|
||||
else
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
return Profile.getInstance().getDisabledFeatures();
|
||||
}
|
||||
|
||||
/**
|
||||
* KEYCLOAK-12958
|
||||
*/
|
||||
private void disableFeatureProperties(Profile.Feature feature) {
|
||||
Profile.Type type = feature.getType();
|
||||
if (type.equals(Profile.Type.DEFAULT)) {
|
||||
System.setProperty("keycloak.profile.feature." + feature.toString().toLowerCase(), "disabled");
|
||||
} else {
|
||||
System.getProperties().remove("keycloak.profile.feature." + feature.toString().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/set-system-property")
|
||||
|
|
|
@ -18,14 +18,9 @@
|
|||
package org.keycloak.testsuite;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.representations.info.ProfileInfoRepresentation;
|
||||
import org.keycloak.testsuite.util.AdminClientUtil;
|
||||
import org.keycloak.testsuite.arquillian.TestContext;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -33,51 +28,34 @@ import java.util.Set;
|
|||
*/
|
||||
public class ProfileAssume {
|
||||
|
||||
private static Set<String> disabledFeatures;
|
||||
private static String profile;
|
||||
private static Set<Profile.Feature> DISABLED_FEATURES;
|
||||
private static TestContext TEST_CONTEXT;
|
||||
|
||||
private static void updateProfile() {
|
||||
String host = System.getProperty("auth.server.host", "localhost");
|
||||
String port = System.getProperty("auth.server.http.port", "8180");
|
||||
boolean adapterCompatTesting = Boolean.parseBoolean(System.getProperty("testsuite.adapter.compat.testing"));
|
||||
|
||||
String authServerContextRoot = "http://" + host + ":" + port;
|
||||
try (Keycloak adminClient = AdminClientUtil.createAdminClient(adapterCompatTesting, authServerContextRoot)) {
|
||||
ProfileInfoRepresentation profileInfo = adminClient.serverInfo().getInfo().getProfileInfo();
|
||||
profile = profileInfo.getName();
|
||||
List<String> disabled = profileInfo.getDisabledFeatures();
|
||||
disabledFeatures = Collections.unmodifiableSet(new HashSet<>(disabled));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to obtain profile / features info from serverinfo endpoint of " + authServerContextRoot, e);
|
||||
if (DISABLED_FEATURES == null) {
|
||||
DISABLED_FEATURES = TEST_CONTEXT.getTestingClient().testing().listDisabledFeatures();
|
||||
}
|
||||
}
|
||||
|
||||
public static void assumeFeatureEnabled(Profile.Feature feature) {
|
||||
updateProfile();
|
||||
Assume.assumeTrue("Ignoring test as feature " + feature.name() + " is not enabled", isFeatureEnabled(feature));
|
||||
Assume.assumeTrue("Ignoring test as feature " + feature.getKey() + " is not enabled", isFeatureEnabled(feature));
|
||||
}
|
||||
|
||||
public static void assumeFeatureDisabled(Profile.Feature feature) {
|
||||
Assume.assumeTrue("Ignoring test as feature " + feature.name() + " is enabled", !isFeatureEnabled(feature));
|
||||
}
|
||||
|
||||
public static void assumePreview() {
|
||||
updateProfile();
|
||||
Assume.assumeTrue("Ignoring test as community/preview profile is not enabled", !profile.equals("product"));
|
||||
}
|
||||
|
||||
public static void assumePreviewDisabled() {
|
||||
updateProfile();
|
||||
Assume.assumeFalse("Ignoring test as community/preview profile is enabled", !profile.equals("product"));
|
||||
}
|
||||
|
||||
public static void assumeCommunity() {
|
||||
updateProfile();
|
||||
Assume.assumeTrue("Ignoring test as community profile is not enabled", profile.equals("community"));
|
||||
Assume.assumeTrue("Ignoring test as feature " + feature.getKey() + " is enabled", !isFeatureEnabled(feature));
|
||||
}
|
||||
|
||||
public static boolean isFeatureEnabled(Profile.Feature feature) {
|
||||
updateProfile();
|
||||
return !disabledFeatures.contains(feature.name());
|
||||
return !DISABLED_FEATURES.contains(feature);
|
||||
}
|
||||
|
||||
public static void updateDisabledFeatures(Set<Profile.Feature> disabledFeatures) {
|
||||
DISABLED_FEATURES = disabledFeatures;
|
||||
}
|
||||
|
||||
public static void setTestContext(TestContext testContext) {
|
||||
TEST_CONTEXT = testContext;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.keycloak.admin.client.Keycloak;
|
|||
import org.keycloak.common.util.StringPropertyReplacer;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.error.KeycloakErrorHandler;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
|
||||
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableVault;
|
||||
|
@ -462,6 +463,7 @@ public class AuthServerTestEnricher {
|
|||
public void initializeTestContext(@Observes(precedence = 2) BeforeClass event) throws Exception {
|
||||
TestContext testContext = new TestContext(suiteContext, event.getTestClass().getJavaClass());
|
||||
testContextProducer.set(testContext);
|
||||
ProfileAssume.setTestContext(testContext);
|
||||
|
||||
boolean wasUpdated = false;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.jboss.arquillian.core.api.annotation.Inject;
|
|||
import org.jboss.arquillian.test.spi.TestResult;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.reflections.Reflections;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
|
||||
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
||||
|
||||
|
@ -50,7 +51,7 @@ public class ModelTestExecutor extends LocalTestExecuter {
|
|||
super.execute(event);
|
||||
} else {
|
||||
TestResult result = new TestResult();
|
||||
if (annotation.skipForMapStorage() && Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE)) {
|
||||
if (annotation.skipForMapStorage() && ProfileAssume.isFeatureEnabled(Profile.Feature.MAP_STORAGE)) {
|
||||
result = TestResult.skipped();
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -27,11 +27,6 @@ public @interface DisableFeature {
|
|||
*/
|
||||
boolean skipRestart() default false;
|
||||
|
||||
/**
|
||||
* The feature will be disabled only if the `product` profile is activated
|
||||
*/
|
||||
boolean onlyForProduct() default false;
|
||||
|
||||
/**
|
||||
* Feature disable should be the last action in @Before context.
|
||||
* If the test halted, the feature is returned to the previous state.
|
||||
|
|
|
@ -27,11 +27,6 @@ public @interface EnableFeature {
|
|||
*/
|
||||
boolean skipRestart() default false;
|
||||
|
||||
/**
|
||||
* The feature will be enabled only if the `product` profile is activated
|
||||
*/
|
||||
boolean onlyForProduct() default false;
|
||||
|
||||
/**
|
||||
* Feature enable should be the last action in @Before context.
|
||||
* If the test halted, the feature is returned to the previous state.
|
||||
|
|
|
@ -71,21 +71,18 @@ public class KeycloakContainerFeaturesController {
|
|||
private Profile.Feature feature;
|
||||
private boolean skipRestart;
|
||||
private FeatureAction action;
|
||||
private boolean onlyForProduct;
|
||||
private final AnnotatedElement annotatedElement;
|
||||
|
||||
public UpdateFeature(Profile.Feature feature, boolean skipRestart, FeatureAction action, boolean onlyForProduct
|
||||
, AnnotatedElement annotatedElement) {
|
||||
public UpdateFeature(Profile.Feature feature, boolean skipRestart, FeatureAction action, AnnotatedElement annotatedElement) {
|
||||
this.feature = feature;
|
||||
this.skipRestart = skipRestart;
|
||||
this.action = action;
|
||||
this.onlyForProduct = onlyForProduct;
|
||||
this.annotatedElement = annotatedElement;
|
||||
}
|
||||
|
||||
private void assertPerformed() {
|
||||
assertThat("An annotation requested to " + action.name() +
|
||||
" feature " + feature.name() + ", however after performing this operation " +
|
||||
" feature " + feature.getKey() + ", however after performing this operation " +
|
||||
"the feature is not in desired state" ,
|
||||
ProfileAssume.isFeatureEnabled(feature),
|
||||
is(action == FeatureAction.ENABLE));
|
||||
|
@ -122,10 +119,6 @@ public class KeycloakContainerFeaturesController {
|
|||
return action;
|
||||
}
|
||||
|
||||
public boolean isOnlyForProduct() {
|
||||
return onlyForProduct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -147,7 +140,6 @@ public class KeycloakContainerFeaturesController {
|
|||
|
||||
private void updateFeatures(Set<UpdateFeature> updateFeatures) throws Exception {
|
||||
updateFeatures = updateFeatures.stream()
|
||||
.filter(this::skipForProduct)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
updateFeatures.forEach(UpdateFeature::performAction);
|
||||
|
@ -160,11 +152,6 @@ public class KeycloakContainerFeaturesController {
|
|||
updateFeatures.forEach(UpdateFeature::assertPerformed);
|
||||
}
|
||||
|
||||
// KEYCLOAK-12958 WebAuthn profile product/project
|
||||
private boolean skipForProduct(UpdateFeature feature) {
|
||||
return !feature.onlyForProduct || Profile.getName().equals("product");
|
||||
}
|
||||
|
||||
private void checkAnnotatedElementForFeatureAnnotations(AnnotatedElement annotatedElement, State state) throws Exception {
|
||||
Set<UpdateFeature> updateFeatureSet = new HashSet<>();
|
||||
|
||||
|
@ -191,13 +178,12 @@ public class KeycloakContainerFeaturesController {
|
|||
|
||||
ret.addAll(Arrays.stream(annotatedElement.getAnnotationsByType(EnableFeature.class))
|
||||
.map(annotation -> new UpdateFeature(annotation.value(), annotation.skipRestart(),
|
||||
state == State.BEFORE ? FeatureAction.ENABLE : FeatureAction.DISABLE, annotation.onlyForProduct(), annotatedElement))
|
||||
state == State.BEFORE ? FeatureAction.ENABLE : FeatureAction.DISABLE, annotatedElement))
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
ret.addAll(Arrays.stream(annotatedElement.getAnnotationsByType(DisableFeature.class))
|
||||
.map(annotation -> new UpdateFeature(annotation.value(), annotation.skipRestart(),
|
||||
state == State.BEFORE ? FeatureAction.DISABLE : FeatureAction.ENABLE, annotation.onlyForProduct(),
|
||||
annotatedElement))
|
||||
state == State.BEFORE ? FeatureAction.DISABLE : FeatureAction.ENABLE, annotatedElement))
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -22,7 +22,9 @@ import javax.ws.rs.core.Response;
|
|||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
|
||||
import org.junit.Assert;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
import org.keycloak.testsuite.client.resources.TestApplicationResource;
|
||||
import org.keycloak.testsuite.client.resources.TestExampleCompanyResource;
|
||||
import org.keycloak.testsuite.client.resources.TestSamlApplicationResource;
|
||||
|
@ -31,6 +33,9 @@ import org.keycloak.testsuite.runonserver.*;
|
|||
import org.keycloak.testsuite.util.AdminClientUtil;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
|
@ -74,15 +79,15 @@ public class KeycloakTestingClient implements AutoCloseable {
|
|||
}
|
||||
|
||||
public void enableFeature(Profile.Feature feature) {
|
||||
try (Response response = testing().enableFeature(feature.toString())) {
|
||||
assertEquals(204, response.getStatus());
|
||||
}
|
||||
Set<Profile.Feature> disabledFeatures = testing().enableFeature(feature.toString());
|
||||
Assert.assertFalse(disabledFeatures.contains(feature));
|
||||
ProfileAssume.updateDisabledFeatures(disabledFeatures);
|
||||
}
|
||||
|
||||
public void disableFeature(Profile.Feature feature) {
|
||||
try (Response response = testing().disableFeature(feature.toString())) {
|
||||
assertEquals(204, response.getStatus());
|
||||
}
|
||||
Set<Profile.Feature> disabledFeatures = testing().disableFeature(feature.toString());
|
||||
Assert.assertTrue(disabledFeatures.contains(feature));
|
||||
ProfileAssume.updateDisabledFeatures(disabledFeatures);
|
||||
}
|
||||
|
||||
public TestApplicationResource testApp() { return target.proxy(TestApplicationResource.class); }
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.testsuite.client.resources;
|
||||
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
|
@ -38,6 +39,8 @@ import javax.ws.rs.core.Response;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.infinispan.commons.time.TimeService;
|
||||
|
||||
/**
|
||||
|
@ -332,15 +335,22 @@ public interface TestingResource {
|
|||
@Produces(MediaType.TEXT_HTML_UTF_8)
|
||||
String getJavascriptTestingEnvironment();
|
||||
|
||||
@GET
|
||||
@Path("/list-disabled-features")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Set<Profile.Feature> listDisabledFeatures();
|
||||
|
||||
@POST
|
||||
@Path("/enable-feature/{feature}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
Response enableFeature(@PathParam("feature") String feature);
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Set<Profile.Feature> enableFeature(@PathParam("feature") String feature);
|
||||
|
||||
@POST
|
||||
@Path("/disable-feature/{feature}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
Response disableFeature(@PathParam("feature") String feature);
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Set<Profile.Feature> disableFeature(@PathParam("feature") String feature);
|
||||
|
||||
/**
|
||||
* If property-value is null, the system property will be unset (removed) on the server
|
||||
|
|
|
@ -879,8 +879,6 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
|
||||
@Test
|
||||
public void testOIDCUiLocalesParamForwarding() {
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
RealmRepresentation demoRealmRep = testRealmResource().toRepresentation();
|
||||
boolean enabled = demoRealmRep.isInternationalizationEnabled();
|
||||
String defaultLocale = demoRealmRep.getDefaultLocale();
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.common.Profile;
|
|||
import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation;
|
||||
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -141,7 +142,7 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
|||
"Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions.");
|
||||
addProviderInfo(result, "auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server.");
|
||||
addProviderInfo(result, "auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form.");
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.SCRIPTS)) {
|
||||
if (ProfileAssume.isFeatureEnabled(Profile.Feature.SCRIPTS)) {
|
||||
addProviderInfo(result, "auth-script-based", "Script", "Script based authentication. Allows to define custom authentication logic via JavaScript.");
|
||||
}
|
||||
addProviderInfo(result, "auth-spnego", "Kerberos", "Initiates the SPNEGO protocol. Most often used with Kerberos.");
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package org.keycloak.testsuite.admin.authentication;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
public class ScriptBasedAuthenticatorTest extends AbstractAuthenticationTest {
|
||||
|
||||
@Test
|
||||
public void checkIfTurnedOffWithProductProfile() throws InterruptedException {
|
||||
ProfileAssume.assumePreviewDisabled();
|
||||
|
||||
HashMap<String, String> params = new HashMap<>();
|
||||
params.put("newName", "Copy-of-browser");
|
||||
authMgmtResource.copy("browser", params);
|
||||
|
||||
params.put("provider", "auth-script-based");
|
||||
try {
|
||||
authMgmtResource.addExecution("Copy-of-browser", params);
|
||||
Assert.fail("Adding script based authenticator should fail with product profile");
|
||||
} catch (BadRequestException ex) {
|
||||
//Expected
|
||||
}
|
||||
}
|
||||
}
|
|
@ -604,10 +604,8 @@ public class SocialLoginTest extends AbstractKeycloakTest {
|
|||
String username = users.get(0).getUsername();
|
||||
checkFeature(501, username);
|
||||
|
||||
Response tokenResp = testingClient.testing().enableFeature(Profile.Feature.TOKEN_EXCHANGE.toString());
|
||||
assertEquals(200, tokenResp.getStatus());
|
||||
testingClient.enableFeature(Profile.Feature.TOKEN_EXCHANGE);
|
||||
|
||||
ProfileAssume.assumeFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE);
|
||||
Client httpClient = AdminClientUtil.createResteasyClient();
|
||||
|
||||
try {
|
||||
|
@ -690,8 +688,7 @@ public class SocialLoginTest extends AbstractKeycloakTest {
|
|||
adminClient.realm(REALM).identityProviders().get(idp.getAlias()).update(idp);
|
||||
} finally {
|
||||
httpClient.close();
|
||||
tokenResp = testingClient.testing().disableFeature(Profile.Feature.TOKEN_EXCHANGE.toString());
|
||||
assertEquals(200, tokenResp.getStatus());
|
||||
testingClient.disableFeature(Profile.Feature.TOKEN_EXCHANGE);
|
||||
checkFeature(501, username);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.crossdc;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.common.Profile;
|
||||
|
@ -86,6 +87,10 @@ public class ActionTokenCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
@Test
|
||||
@InitialDcState(authServers = ServerSetup.ALL_NODES_IN_FIRST_DC_FIRST_NODE_IN_SECOND_DC)
|
||||
// KEYCLOAK-17584: Temporarily disable the test for 'community' profile till KEYCLOAK-17628 isn't fixed. In other words till:
|
||||
// * The test is either rewritten to start using the new Wildfly subsystem for base metrics introduced in Wildfly 22,
|
||||
// * Or Keycloak is able to load the Eclipse MicroProfile Metrics subsystem from the microprofile Galleon feature-pack
|
||||
@Ignore
|
||||
public void sendResetPasswordEmailSuccessWorksInCrossDc(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc0Node0Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=1, cacheName=InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc0Node1Statistics,
|
||||
|
@ -93,11 +98,6 @@ public class ActionTokenCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
log.debug("--DC: START sendResetPasswordEmailSuccessWorksInCrossDc");
|
||||
|
||||
// KEYCLOAK-17584: Temporarily disable the test for 'community' profile till KEYCLOAK-17628 isn't fixed. In other words till:
|
||||
// * The test is either rewritten to start using the new Wildfly subsystem for base metrics introduced in Wildfly 22,
|
||||
// * Or Keycloak is able to load the Eclipse MicroProfile Metrics subsystem from the microprofile Galleon feature-pack
|
||||
Assume.assumeTrue("Ignoring test as product profile is not enabled", Profile.getName().equals("product"));
|
||||
|
||||
cacheDc0Node1Statistics.waitToBecomeAvailable(10, TimeUnit.SECONDS);
|
||||
|
||||
Comparable originalNumberOfEntries = cacheDc0Node0Statistics.getSingleStatistics(Constants.STAT_CACHE_NUMBER_OF_ENTRIES_IN_MEMORY);
|
||||
|
|
|
@ -60,8 +60,6 @@ public class AccountPageTest extends AbstractI18NTest {
|
|||
|
||||
@Test
|
||||
public void testLocalizedReferrerLinkContent() {
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
RealmResource testRealm = testRealm();
|
||||
List<ClientRepresentation> foundClients = testRealm.clients().findByClientId("var-named-test-app");
|
||||
if (foundClients.isEmpty()) {
|
||||
|
|
|
@ -97,8 +97,6 @@ public class EmailTest extends AbstractI18NTest {
|
|||
|
||||
@Test
|
||||
public void restPasswordEmailGerman() throws IOException, MessagingException {
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
changeUserLocale("de");
|
||||
|
||||
loginPage.open();
|
||||
|
@ -118,8 +116,6 @@ public class EmailTest extends AbstractI18NTest {
|
|||
//KEYCLOAK-7478
|
||||
@Test
|
||||
public void changeLocaleOnInfoPage() throws InterruptedException, IOException, MessagingException {
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
UserResource testUser = ApiUtil.findUserByUsernameId(testRealm(), "login-test");
|
||||
testUser.executeActionsEmail(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
|
||||
|
||||
|
|
|
@ -88,8 +88,6 @@ public class LoginPageTest extends AbstractI18NTest {
|
|||
|
||||
@Test
|
||||
public void languageDropdown() {
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
loginPage.open();
|
||||
Assert.assertEquals("English", loginPage.getLanguageDropdownText());
|
||||
|
||||
|
@ -123,8 +121,6 @@ public class LoginPageTest extends AbstractI18NTest {
|
|||
|
||||
@Test
|
||||
public void acceptLanguageHeader() throws IOException {
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
try(CloseableHttpClient httpClient = (CloseableHttpClient) new HttpClientBuilder().build()) {
|
||||
ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient);
|
||||
ResteasyClient client = ((ResteasyClientBuilder) ResteasyClientBuilder.newBuilder()).httpEngine(engine).build();
|
||||
|
@ -156,8 +152,6 @@ public class LoginPageTest extends AbstractI18NTest {
|
|||
// KEYCLOAK-3887
|
||||
@Test
|
||||
public void languageChangeRequiredActions() {
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
|
||||
UserRepresentation userRep = user.toRepresentation();
|
||||
userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
|
||||
|
@ -183,8 +177,6 @@ public class LoginPageTest extends AbstractI18NTest {
|
|||
// KEYCLOAK-3887
|
||||
@Test
|
||||
public void languageChangeConsentScreen() {
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
// Set client, which requires consent
|
||||
oauth.clientId("third-party");
|
||||
|
||||
|
@ -210,8 +202,6 @@ public class LoginPageTest extends AbstractI18NTest {
|
|||
|
||||
@Test
|
||||
public void languageUserUpdates() {
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.openLanguage("Deutsch");
|
||||
|
||||
|
|
|
@ -516,8 +516,6 @@ public class X509BrowserLoginTest extends AbstractX509AuthenticationTest {
|
|||
// KEYCLOAK-6866
|
||||
@Test
|
||||
public void changeLocaleOnX509InfoPage() {
|
||||
ProfileAssume.assumeCommunity();
|
||||
|
||||
AuthenticatorConfigRepresentation cfg = newConfig("x509-browser-config", createLoginSubjectEmail2UsernameOrEmailConfig().getConfig());
|
||||
String cfgId = createConfig(browserExecution.getId(), cfg);
|
||||
Assert.assertNotNull(cfgId);
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
|
|||
import org.keycloak.authorization.policy.provider.PolicySpi;
|
||||
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||
import org.keycloak.cluster.ClusterSpi;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.component.ComponentFactoryProviderFactory;
|
||||
import org.keycloak.component.ComponentFactorySpi;
|
||||
|
@ -60,6 +62,7 @@ import java.lang.management.LockInfo;
|
|||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -290,6 +293,13 @@ public abstract class KeycloakModelTest {
|
|||
LOG.debugf("Creating factory %d in %s using the following configuration:\n %s", factoryIndex, threadName, CONFIG);
|
||||
|
||||
DefaultKeycloakSessionFactory res = new DefaultKeycloakSessionFactory() {
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
Profile.configure(new PropertiesProfileConfigResolver(System.getProperties()));
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabled(ProviderFactory factory, Scope scope) {
|
||||
return super.isEnabled(factory, scope) && isFactoryAllowed(factory);
|
||||
|
|
|
@ -23,6 +23,9 @@ import java.nio.file.Files;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.profile.PropertiesFileProfileConfigResolver;
|
||||
import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
||||
import org.keycloak.platform.PlatformProvider;
|
||||
|
||||
public class TestPlatform implements PlatformProvider {
|
||||
|
@ -31,6 +34,13 @@ public class TestPlatform implements PlatformProvider {
|
|||
|
||||
private File tmpDir;
|
||||
|
||||
public TestPlatform() {
|
||||
Profile.configure(
|
||||
new PropertiesProfileConfigResolver(System.getProperties()),
|
||||
new PropertiesFileProfileConfigResolver()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartup(Runnable startupHook) {
|
||||
startupHook.run();
|
||||
|
|
Loading…
Reference in a new issue