parent
600fff4f6f
commit
1de9c201c6
49 changed files with 838 additions and 659 deletions
|
@ -17,16 +17,20 @@
|
||||||
|
|
||||||
package org.keycloak.common;
|
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.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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -34,167 +38,74 @@ import org.jboss.logging.Logger;
|
||||||
*/
|
*/
|
||||||
public class Profile {
|
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 {
|
public enum Feature {
|
||||||
AUTHORIZATION("Authorization Service", Type.DEFAULT),
|
AUTHORIZATION("Authorization Service", Type.DEFAULT),
|
||||||
ACCOUNT2("New Account Management Console", Type.DEFAULT),
|
|
||||||
ACCOUNT_API("Account Management REST API", 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),
|
ADMIN_FINE_GRAINED_AUTHZ("Fine-Grained Admin Permissions", Type.PREVIEW),
|
||||||
/**
|
|
||||||
* Controls the availability of the Admin REST-API.
|
|
||||||
*/
|
|
||||||
ADMIN_API("Admin API", Type.DEFAULT),
|
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
|
@Deprecated
|
||||||
ADMIN("Legacy Admin Console", Type.DEPRECATED),
|
ADMIN("Legacy Admin Console", Type.DEPRECATED),
|
||||||
|
|
||||||
/**
|
ADMIN2("New Admin Console", Type.DEFAULT, Feature.ADMIN_API),
|
||||||
* 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);
|
|
||||||
|
|
||||||
|
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 final Type type;
|
||||||
private String label;
|
private String label;
|
||||||
|
|
||||||
|
private Set<Feature> dependencies;
|
||||||
Feature(String label, Type type) {
|
Feature(String label, Type type) {
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.type = type;
|
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() {
|
public String getLabel() {
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
@ -202,88 +113,162 @@ public class Profile {
|
||||||
public Type getType() {
|
public Type getType() {
|
||||||
return type;
|
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,
|
DEFAULT,
|
||||||
PREVIEW
|
PREVIEW
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface PropertyResolver {
|
private static Boolean isFeatureEnabled(ProfileName profile, Feature feature, ProfileConfigResolver... resolvers) {
|
||||||
String resolve(String feature);
|
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 static void verifyConfig(Map<Feature, Boolean> features) {
|
||||||
|
for (Feature f : features.keySet()) {
|
||||||
private Properties properties;
|
if (f.getDependencies() != null) {
|
||||||
|
for (Feature d : f.getDependencies()) {
|
||||||
public Config() {
|
if (!features.get(d)) {
|
||||||
properties = new Properties();
|
throw new ProfileException("Feature " + f.getKey() + " depends on disabled feature " + d.getKey());
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProfile() {
|
|
||||||
String profile = getProperty("keycloak.profile");
|
|
||||||
if (profile != null) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
profile = properties.getProperty("profile");
|
|
||||||
if (profile != null) {
|
|
||||||
if (profile.equals("community")) {
|
|
||||||
profile = "default";
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private void logUnsupportedFeatures() {
|
||||||
|
logUnsuportedFeatures(Feature.Type.PREVIEW, Logger.Level.INFO);
|
||||||
|
logUnsuportedFeatures(Feature.Type.EXPERIMENTAL, Logger.Level.WARN);
|
||||||
|
logUnsuportedFeatures(Feature.Type.DEPRECATED, Logger.Level.WARN);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(", "));
|
||||||
|
|
||||||
|
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;
|
package org.keycloak.common;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
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.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
public class ProfileTest {
|
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
|
@Rule
|
||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
@Test
|
@BeforeClass
|
||||||
public void checkDefaults() {
|
public static void beforeClass() {
|
||||||
Assert.assertEquals("default", Profile.getName());
|
Assert.assertEquals(Profile.Feature.Type.DEFAULT, DEFAULT_FEATURE.getType());
|
||||||
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);
|
Assert.assertEquals(Profile.Feature.Type.DISABLED_BY_DEFAULT, DISABLED_BY_DEFAULT_FEATURE.getType());
|
||||||
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);
|
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
|
@Test
|
||||||
public void configWithSystemProperties() {
|
public void checkDefaults() {
|
||||||
Assert.assertEquals("default", Profile.getName());
|
Profile profile = Profile.defaults();
|
||||||
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
|
|
||||||
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION));
|
|
||||||
assertTrue(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
|
|
||||||
|
|
||||||
System.setProperty("keycloak.profile", "preview");
|
Assert.assertTrue(Profile.isFeatureEnabled(DEFAULT_FEATURE));
|
||||||
System.setProperty("keycloak.profile.feature.docker", "enabled");
|
Assert.assertFalse(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE));
|
||||||
System.setProperty("keycloak.profile.feature.impersonation", "disabled");
|
Assert.assertFalse(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
||||||
System.setProperty("keycloak.profile.feature.upload_scripts", "enabled");
|
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());
|
@Test
|
||||||
assertTrue(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
|
public void checkFailureIfDependencyDisabled() {
|
||||||
assertTrue(Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION));
|
Properties properties = new Properties();
|
||||||
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
|
properties.setProperty("keycloak.profile.feature.account_api", "disabled");
|
||||||
|
|
||||||
System.getProperties().remove("keycloak.profile");
|
try {
|
||||||
System.getProperties().remove("keycloak.profile.feature.docker");
|
Profile.configure(new PropertiesProfileConfigResolver(properties));
|
||||||
System.getProperties().remove("keycloak.profile.feature.impersonation");
|
} catch (ProfileException e) {
|
||||||
System.getProperties().remove("keycloak.profile.feature.upload_scripts");
|
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) {
|
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.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -36,10 +37,12 @@ public class ProfileInfoRepresentation {
|
||||||
public static ProfileInfoRepresentation create() {
|
public static ProfileInfoRepresentation create() {
|
||||||
ProfileInfoRepresentation info = new ProfileInfoRepresentation();
|
ProfileInfoRepresentation info = new ProfileInfoRepresentation();
|
||||||
|
|
||||||
info.name = Profile.getName();
|
Profile profile = Profile.getInstance();
|
||||||
info.disabledFeatures = names(Profile.getDisabledFeatures());
|
|
||||||
info.previewFeatures = names(Profile.getPreviewFeatures());
|
info.name = profile.getName().name().toLowerCase();
|
||||||
info.experimentalFeatures = names(Profile.getExperimentalFeatures());
|
info.disabledFeatures = names(profile.getDisabledFeatures());
|
||||||
|
info.previewFeatures = names(profile.getPreviewFeatures());
|
||||||
|
info.experimentalFeatures = names(profile.getExperimentalFeatures());
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,26 +13,26 @@ public class Features {
|
||||||
|
|
||||||
public Features() {
|
public Features() {
|
||||||
this.features = Arrays.stream(Profile.Feature.values())
|
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))
|
.map(f -> new Feature(f))
|
||||||
.sorted(Comparator.comparing(Feature::getName))
|
.sorted(Comparator.comparing(Feature::getName))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Feature> getSupported() {
|
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() {
|
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() {
|
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() {
|
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 {
|
public class Feature {
|
||||||
|
@ -44,14 +44,14 @@ public class Features {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return profileFeature.name().toLowerCase().replaceAll("_", "-");
|
return profileFeature.getKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return profileFeature.getLabel();
|
return profileFeature.getLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Profile.Type getType() {
|
private Profile.Feature.Type getType() {
|
||||||
return profileFeature.getType();
|
return profileFeature.getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.config;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -27,10 +28,12 @@ public class FeatureOptions {
|
||||||
List<String> features = new ArrayList<>();
|
List<String> features = new ArrayList<>();
|
||||||
|
|
||||||
for (Profile.Feature value : Profile.Feature.values()) {
|
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;
|
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.jboss.resteasy.spi.ResteasyDeployment;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.common.crypto.FipsMode;
|
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.SecurityOptions;
|
||||||
import org.keycloak.config.StorageOptions;
|
import org.keycloak.config.StorageOptions;
|
||||||
import org.keycloak.config.TransactionOptions;
|
import org.keycloak.config.TransactionOptions;
|
||||||
|
@ -101,7 +103,7 @@ import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||||
import org.keycloak.connections.jpa.JpaConnectionSpi;
|
import org.keycloak.connections.jpa.JpaConnectionSpi;
|
||||||
import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory;
|
import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory;
|
||||||
import org.keycloak.protocol.saml.mappers.DeployedScriptSAMLProtocolMapper;
|
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.PersistedConfigSource;
|
||||||
import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource;
|
import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource;
|
||||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
|
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
|
||||||
|
@ -207,6 +209,26 @@ class KeycloakProcessor {
|
||||||
return new FeatureBuildItem("keycloak");
|
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.
|
* <p>Configures the persistence unit for Quarkus.
|
||||||
*
|
*
|
||||||
|
@ -331,8 +353,8 @@ class KeycloakProcessor {
|
||||||
*/
|
*/
|
||||||
@Record(ExecutionTime.STATIC_INIT)
|
@Record(ExecutionTime.STATIC_INIT)
|
||||||
@BuildStep
|
@BuildStep
|
||||||
|
@Consume(ProfileBuildItem.class)
|
||||||
KeycloakSessionFactoryPreInitBuildItem configureKeycloakSessionFactory(KeycloakRecorder recorder, List<PersistenceXmlDescriptorBuildItem> descriptors) {
|
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<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories = new HashMap<>();
|
||||||
Map<Class<? extends Provider>, String> defaultProviders = new HashMap<>();
|
Map<Class<? extends Provider>, String> defaultProviders = new HashMap<>();
|
||||||
Map<String, ProviderFactory> preConfiguredProviders = new HashMap<>();
|
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_HEALTH_ENDPOINT = "/health";
|
||||||
public static final String DEFAULT_METRICS_ENDPOINT = "/metrics";
|
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) {
|
public void configureLiquibase(Map<String, List<String>> services) {
|
||||||
ServiceLocator locator = Scope.getCurrentScope().getServiceLocator();
|
ServiceLocator locator = Scope.getCurrentScope().getServiceLocator();
|
||||||
if (locator instanceof FastServiceLocator)
|
if (locator instanceof FastServiceLocator)
|
||||||
|
@ -75,8 +83,6 @@ public class KeycloakRecorder {
|
||||||
Map<Class<? extends Provider>, String> defaultProviders,
|
Map<Class<? extends Provider>, String> defaultProviders,
|
||||||
Map<String, ProviderFactory> preConfiguredProviders,
|
Map<String, ProviderFactory> preConfiguredProviders,
|
||||||
List<ClasspathThemeProviderFactory.ThemesRepresentation> themes, boolean reaugmented) {
|
List<ClasspathThemeProviderFactory.ThemesRepresentation> themes, boolean reaugmented) {
|
||||||
Config.init(new MicroProfileConfigProvider());
|
|
||||||
Profile.setInstance(new QuarkusProfile());
|
|
||||||
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, themes, reaugmented));
|
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.common.Profile;
|
||||||
import org.keycloak.config.FeatureOptions;
|
import org.keycloak.config.FeatureOptions;
|
||||||
import org.keycloak.config.StorageOptions;
|
|
||||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||||
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
|
||||||
|
|
||||||
import static java.util.Optional.of;
|
import static java.util.Optional.of;
|
||||||
import static org.keycloak.config.StorageOptions.STORAGE;
|
import static org.keycloak.config.StorageOptions.STORAGE;
|
||||||
|
@ -42,7 +40,7 @@ final class FeaturePropertyMappers {
|
||||||
|
|
||||||
Set<String> featureSet = new HashSet<>(List.of(features.orElse("").split(",")));
|
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));
|
return of(String.join(",", featureSet));
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,10 +52,18 @@ public class FeaturesDistTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Launch({StartDev.NAME, "--features=preview", "--features-disabled=token-exchange"})
|
@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) {
|
public void testEnablePrecedenceOverDisable(LaunchResult result) {
|
||||||
CLIResult cliResult = (CLIResult) result;
|
CLIResult cliResult = (CLIResult) result;
|
||||||
cliResult.assertStartedDevMode();
|
cliResult.assertStartedDevMode();
|
||||||
assertPreviewFeaturesEnabled((CLIResult) result);
|
assertThat(cliResult.getOutput(), containsString("Preview features enabled: token-exchange"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -65,8 +73,7 @@ public class FeaturesDistTest {
|
||||||
CLIResult cliResult = (CLIResult) result;
|
CLIResult cliResult = (CLIResult) result;
|
||||||
cliResult.assertStartedDevMode();
|
cliResult.assertStartedDevMode();
|
||||||
assertThat(cliResult.getOutput(), CoreMatchers.allOf(
|
assertThat(cliResult.getOutput(), CoreMatchers.allOf(
|
||||||
containsString("Preview feature enabled: admin_fine_grained_authz"),
|
containsString("Preview features enabled: admin-fine-grained-authz, token-exchange")));
|
||||||
containsString("Preview feature enabled: token_exchange")));
|
|
||||||
assertFalse(cliResult.getOutput().contains("declarative-user-profile"));
|
assertFalse(cliResult.getOutput().contains("declarative-user-profile"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,17 +84,12 @@ public class FeaturesDistTest {
|
||||||
CLIResult cliResult = (CLIResult) result;
|
CLIResult cliResult = (CLIResult) result;
|
||||||
cliResult.assertStartedDevMode();
|
cliResult.assertStartedDevMode();
|
||||||
assertThat(cliResult.getOutput(), CoreMatchers.allOf(
|
assertThat(cliResult.getOutput(), CoreMatchers.allOf(
|
||||||
containsString("Preview feature enabled: admin_fine_grained_authz"),
|
containsString("Preview features enabled: admin-fine-grained-authz, token-exchange")));
|
||||||
containsString("Preview feature enabled: token_exchange")));
|
|
||||||
assertFalse(cliResult.getOutput().contains("declarative-user-profile"));
|
assertFalse(cliResult.getOutput().contains("declarative-user-profile"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertPreviewFeaturesEnabled(CLIResult result) {
|
private void assertPreviewFeaturesEnabled(CLIResult result) {
|
||||||
assertThat(result.getOutput(), CoreMatchers.allOf(
|
assertThat(result.getOutput(), CoreMatchers.allOf(
|
||||||
containsString("Preview feature enabled: admin_fine_grained_authz"),
|
containsString("Preview features enabled: admin-fine-grained-authz, client-secret-rotation, declarative-user-profile, openshift-integration, recovery-codes, scripts, token-exchange, update-email")));
|
||||||
containsString("Preview feature enabled: openshift_integration"),
|
|
||||||
containsString("Preview feature enabled: scripts"),
|
|
||||||
containsString("Preview feature enabled: token_exchange"),
|
|
||||||
containsString("Preview feature enabled: declarative_user_profile")));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class ChmStorageDistTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertExpectedMessages(CLIResult cliResult, RawDistRootPath distPath) {
|
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");
|
cliResult.assertMessage("Hibernate ORM is disabled because no JPA entities were found");
|
||||||
Assert.assertFalse(distPath.getDistRootPath().resolve("data").resolve("h2").toFile().exists());
|
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" })
|
@Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" })
|
||||||
void testSuccessful(LaunchResult result) {
|
void testSuccessful(LaunchResult result) {
|
||||||
CLIResult cliResult = (CLIResult) 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.assertMessage("[org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory] (main) HotRod client configuration was successful.");
|
||||||
cliResult.assertStarted();
|
cliResult.assertStarted();
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class JPAStoreDistTest {
|
||||||
@Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" })
|
@Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" })
|
||||||
void testSuccessful(LaunchResult result) {
|
void testSuccessful(LaunchResult result) {
|
||||||
CLIResult cliResult = (CLIResult) 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.assertMessage("[org.keycloak.models.map.storage.jpa.liquibase.updater.MapJpaLiquibaseUpdaterProvider] (main) Initializing database schema. Using changelog META-INF/jpa-realms-changelog.xml");
|
||||||
cliResult.assertStarted();
|
cliResult.assertStarted();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,19 +43,19 @@ Transaction:
|
||||||
|
|
||||||
Feature:
|
Feature:
|
||||||
|
|
||||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||||
web-authn, client-policies, ciba, map-storage, par,
|
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
openshift-integration, par, preview, recovery-codes, scripts,
|
||||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
step-up-authentication, token-exchange, update-email, web-authn.
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: authorization,
|
Disables a set of one or more features. Possible values are: account-api,
|
||||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||||
web-authn, client-policies, ciba, map-storage, par,
|
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
openshift-integration, par, preview, recovery-codes, scripts,
|
||||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
step-up-authentication, token-exchange, update-email, web-authn.
|
||||||
|
|
||||||
HTTP/TLS:
|
HTTP/TLS:
|
||||||
|
|
||||||
|
@ -104,4 +104,4 @@ Examples:
|
||||||
|
|
||||||
Change the relative path:
|
Change the relative path:
|
||||||
|
|
||||||
$ kc.sh build --http-relative-path=/auth
|
$ kc.sh build --http-relative-path=/auth
|
||||||
|
|
|
@ -66,19 +66,19 @@ Transaction:
|
||||||
|
|
||||||
Feature:
|
Feature:
|
||||||
|
|
||||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||||
web-authn, client-policies, ciba, map-storage, par,
|
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
openshift-integration, par, preview, recovery-codes, scripts,
|
||||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
step-up-authentication, token-exchange, update-email, web-authn.
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: authorization,
|
Disables a set of one or more features. Possible values are: account-api,
|
||||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||||
web-authn, client-policies, ciba, map-storage, par,
|
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
openshift-integration, par, preview, recovery-codes, scripts,
|
||||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
step-up-authentication, token-exchange, update-email, web-authn.
|
||||||
|
|
||||||
Hostname:
|
Hostname:
|
||||||
|
|
||||||
|
@ -221,4 +221,4 @@ Logging:
|
||||||
Do NOT start the server using this command when deploying to production.
|
Do NOT start the server using this command when deploying to production.
|
||||||
|
|
||||||
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
||||||
options.
|
options.
|
||||||
|
|
|
@ -124,19 +124,19 @@ Transaction:
|
||||||
|
|
||||||
Feature:
|
Feature:
|
||||||
|
|
||||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||||
web-authn, client-policies, ciba, map-storage, par,
|
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
openshift-integration, par, preview, recovery-codes, scripts,
|
||||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
step-up-authentication, token-exchange, update-email, web-authn.
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: authorization,
|
Disables a set of one or more features. Possible values are: account-api,
|
||||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||||
web-authn, client-policies, ciba, map-storage, par,
|
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
openshift-integration, par, preview, recovery-codes, scripts,
|
||||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
step-up-authentication, token-exchange, update-email, web-authn.
|
||||||
|
|
||||||
Hostname:
|
Hostname:
|
||||||
|
|
||||||
|
@ -285,4 +285,4 @@ Security (Experimental):
|
||||||
Do NOT start the server using this command when deploying to production.
|
Do NOT start the server using this command when deploying to production.
|
||||||
|
|
||||||
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
||||||
options.
|
options.
|
||||||
|
|
|
@ -72,19 +72,19 @@ Transaction:
|
||||||
|
|
||||||
Feature:
|
Feature:
|
||||||
|
|
||||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||||
web-authn, client-policies, ciba, map-storage, par,
|
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
openshift-integration, par, preview, recovery-codes, scripts,
|
||||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
step-up-authentication, token-exchange, update-email, web-authn.
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: authorization,
|
Disables a set of one or more features. Possible values are: account-api,
|
||||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||||
web-authn, client-policies, ciba, map-storage, par,
|
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
openshift-integration, par, preview, recovery-codes, scripts,
|
||||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
step-up-authentication, token-exchange, update-email, web-authn.
|
||||||
|
|
||||||
Hostname:
|
Hostname:
|
||||||
|
|
||||||
|
@ -231,4 +231,4 @@ By default, this command tries to update the server configuration by running a
|
||||||
$ kc.sh start '--optimized'
|
$ kc.sh start '--optimized'
|
||||||
|
|
||||||
By doing that, the server should start faster based on any previous
|
By doing that, the server should start faster based on any previous
|
||||||
configuration you have set when manually running the 'build' command.
|
configuration you have set when manually running the 'build' command.
|
||||||
|
|
|
@ -130,19 +130,19 @@ Transaction:
|
||||||
|
|
||||||
Feature:
|
Feature:
|
||||||
|
|
||||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||||
web-authn, client-policies, ciba, map-storage, par,
|
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
openshift-integration, par, preview, recovery-codes, scripts,
|
||||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
step-up-authentication, token-exchange, update-email, web-authn.
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: authorization,
|
Disables a set of one or more features. Possible values are: account-api,
|
||||||
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization,
|
||||||
docker, impersonation, openshift-integration, scripts, token-exchange,
|
ciba, client-policies, client-secret-rotation, declarative-user-profile,
|
||||||
web-authn, client-policies, ciba, map-storage, par,
|
docker, dynamic-scopes, impersonation, js-adapter, map-storage,
|
||||||
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
openshift-integration, par, preview, recovery-codes, scripts,
|
||||||
step-up-authentication, recovery-codes, update-email, js-adapter, preview.
|
step-up-authentication, token-exchange, update-email, web-authn.
|
||||||
|
|
||||||
Hostname:
|
Hostname:
|
||||||
|
|
||||||
|
@ -295,4 +295,4 @@ By default, this command tries to update the server configuration by running a
|
||||||
$ kc.sh start '--optimized'
|
$ kc.sh start '--optimized'
|
||||||
|
|
||||||
By doing that, the server should start faster based on any previous
|
By doing that, the server should start faster based on any previous
|
||||||
configuration you have set when manually running the 'build' command.
|
configuration you have set when manually running the 'build' command.
|
||||||
|
|
|
@ -110,6 +110,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
|
initBuiltIns();
|
||||||
this.providerConfig = new OIDCProviderConfig(config);
|
this.providerConfig = new OIDCProviderConfig(config);
|
||||||
if (providerConfig.isLegacyLogoutRedirectUri()) {
|
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);
|
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;
|
return builtins;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<String, ProtocolMapperModel> builtins = new HashMap<>();
|
private Map<String, ProtocolMapperModel> builtins = new HashMap<>();
|
||||||
|
|
||||||
static {
|
void initBuiltIns() {
|
||||||
ProtocolMapperModel model;
|
ProtocolMapperModel model;
|
||||||
model = UserPropertyMapper.createClaimMapper(USERNAME,
|
model = UserPropertyMapper.createClaimMapper(USERNAME,
|
||||||
"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,
|
ProtocolMapperModel model = UserAttributeMapper.createClaimMapper(name,
|
||||||
attrName,
|
attrName,
|
||||||
claimName, type,
|
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);
|
ClientScopeModel rolesScope = KeycloakModelUtils.getClientScopeByName(newRealm, ROLES_SCOPE);
|
||||||
if (rolesScope == null) {
|
if (rolesScope == null) {
|
||||||
rolesScope = newRealm.addClientScope(ROLES_SCOPE);
|
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);
|
ClientScopeModel originsScope = KeycloakModelUtils.getClientScopeByName(newRealm, WEB_ORIGINS_SCOPE);
|
||||||
if (originsScope == null) {
|
if (originsScope == null) {
|
||||||
originsScope = newRealm.addClientScope(WEB_ORIGINS_SCOPE);
|
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.
|
* @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.
|
* @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);
|
ClientScopeModel microprofileScope = KeycloakModelUtils.getClientScopeByName(newRealm, MICROPROFILE_JWT_SCOPE);
|
||||||
if (microprofileScope == null) {
|
if (microprofileScope == null) {
|
||||||
microprofileScope = newRealm.addClientScope(MICROPROFILE_JWT_SCOPE);
|
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)) {
|
if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) {
|
||||||
ClientScopeModel acrScope = KeycloakModelUtils.getClientScopeByName(newRealm, ACR_SCOPE);
|
ClientScopeModel acrScope = KeycloakModelUtils.getClientScopeByName(newRealm, ACR_SCOPE);
|
||||||
if (acrScope == null) {
|
if (acrScope == null) {
|
||||||
|
|
|
@ -23,17 +23,15 @@ import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.protocol.LoginProtocolFactory;
|
import org.keycloak.protocol.LoginProtocolFactory;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
|
||||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -84,26 +82,29 @@ public class DefaultMigrationProvider implements MigrationProvider {
|
||||||
new RealmManager(session).setupAdminCli(realm);
|
new RealmManager(session).setupAdminCli(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OIDCLoginProtocolFactory getOIDCLoginProtocolFactory() {
|
||||||
|
return (OIDCLoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientScopeModel addOIDCRolesClientScope(RealmModel realm) {
|
public ClientScopeModel addOIDCRolesClientScope(RealmModel realm) {
|
||||||
return OIDCLoginProtocolFactory.addRolesClientScope(realm);
|
return getOIDCLoginProtocolFactory().addRolesClientScope(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientScopeModel addOIDCWebOriginsClientScope(RealmModel realm) {
|
public ClientScopeModel addOIDCWebOriginsClientScope(RealmModel realm) {
|
||||||
return OIDCLoginProtocolFactory.addWebOriginsClientScope(realm);
|
return getOIDCLoginProtocolFactory().addWebOriginsClientScope(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientScopeModel addOIDCMicroprofileJWTClientScope(RealmModel realm) {
|
public ClientScopeModel addOIDCMicroprofileJWTClientScope(RealmModel realm) {
|
||||||
return OIDCLoginProtocolFactory.addMicroprofileJWTClientScope(realm);
|
return getOIDCLoginProtocolFactory().addMicroprofileJWTClientScope(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addOIDCAcrClientScope(RealmModel realm) {
|
public void addOIDCAcrClientScope(RealmModel realm) {
|
||||||
OIDCLoginProtocolFactory.addAcrClientScope(realm);
|
getOIDCLoginProtocolFactory().addAcrClientScope(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -112,6 +112,7 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.UUID;
|
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
|
@POST
|
||||||
@Path("/enable-feature/{feature}")
|
@Path("/enable-feature/{feature}")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public Response enableFeature(@PathParam("feature") String feature) {
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
Profile.Feature featureProfile;
|
public Set<Profile.Feature> enableFeature(@PathParam("feature") String feature) {
|
||||||
|
return updateFeature(feature, true);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/disable-feature/{feature}")
|
@Path("/disable-feature/{feature}")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public Response disableFeature(@PathParam("feature") String feature) {
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
Profile.Feature featureProfile;
|
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 {
|
try {
|
||||||
featureProfile = Profile.Feature.valueOf(feature);
|
feature = Profile.Feature.valueOf(featureKey);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
System.err.printf("Feature '%s' doesn't exist!!\n", feature);
|
System.err.printf("Feature '%s' doesn't exist!!\n", featureKey);
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
throw new BadRequestException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Profile.isFeatureEnabled(featureProfile))
|
if (Profile.getInstance().getFeatures().get(feature) != shouldEnable) {
|
||||||
return Response.noContent().build();
|
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");
|
Map<Profile.Feature, Boolean> updatedFeatures = new HashMap<>();
|
||||||
// If we are in jboss-based container, we need to write profile.properties file, otherwise the change in system property will disappear after restart
|
updatedFeatures.putAll(current.getFeatures());
|
||||||
if (jbossServerConfigDir != null) {
|
updatedFeatures.put(feature, shouldEnable);
|
||||||
setFeatureInProfileFile(new File(jbossServerConfigDir, "profile.properties"), featureProfile, "disabled");
|
|
||||||
|
Profile.init(current.getName(), updatedFeatures);
|
||||||
|
|
||||||
|
if (shouldEnable) {
|
||||||
|
FeatureDeployerUtil.deployFactoriesAfterFeatureEnabled(feature);
|
||||||
|
} else {
|
||||||
|
FeatureDeployerUtil.undeployFactoriesAfterFeatureDisabled(feature);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Profile.init();
|
return Profile.getInstance().getDisabledFeatures();
|
||||||
|
|
||||||
FeatureDeployerUtil.undeployFactoriesAfterFeatureDisabled(featureProfile);
|
|
||||||
|
|
||||||
if (!Profile.isFeatureEnabled(featureProfile))
|
|
||||||
return Response.noContent().build();
|
|
||||||
else
|
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
@GET
|
||||||
@Path("/set-system-property")
|
@Path("/set-system-property")
|
||||||
|
|
|
@ -18,14 +18,9 @@
|
||||||
package org.keycloak.testsuite;
|
package org.keycloak.testsuite;
|
||||||
|
|
||||||
import org.junit.Assume;
|
import org.junit.Assume;
|
||||||
import org.keycloak.admin.client.Keycloak;
|
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.representations.info.ProfileInfoRepresentation;
|
import org.keycloak.testsuite.arquillian.TestContext;
|
||||||
import org.keycloak.testsuite.util.AdminClientUtil;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,51 +28,34 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class ProfileAssume {
|
public class ProfileAssume {
|
||||||
|
|
||||||
private static Set<String> disabledFeatures;
|
private static Set<Profile.Feature> DISABLED_FEATURES;
|
||||||
private static String profile;
|
private static TestContext TEST_CONTEXT;
|
||||||
|
|
||||||
private static void updateProfile() {
|
private static void updateProfile() {
|
||||||
String host = System.getProperty("auth.server.host", "localhost");
|
if (DISABLED_FEATURES == null) {
|
||||||
String port = System.getProperty("auth.server.http.port", "8180");
|
DISABLED_FEATURES = TEST_CONTEXT.getTestingClient().testing().listDisabledFeatures();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void assumeFeatureEnabled(Profile.Feature feature) {
|
public static void assumeFeatureEnabled(Profile.Feature feature) {
|
||||||
updateProfile();
|
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) {
|
public static void assumeFeatureDisabled(Profile.Feature feature) {
|
||||||
Assume.assumeTrue("Ignoring test as feature " + feature.name() + " is enabled", !isFeatureEnabled(feature));
|
Assume.assumeTrue("Ignoring test as feature " + feature.getKey() + " 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"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isFeatureEnabled(Profile.Feature feature) {
|
public static boolean isFeatureEnabled(Profile.Feature feature) {
|
||||||
updateProfile();
|
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.common.util.StringPropertyReplacer;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.error.KeycloakErrorHandler;
|
import org.keycloak.services.error.KeycloakErrorHandler;
|
||||||
|
import org.keycloak.testsuite.ProfileAssume;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
|
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
|
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.EnableVault;
|
import org.keycloak.testsuite.arquillian.annotation.EnableVault;
|
||||||
|
@ -462,6 +463,7 @@ public class AuthServerTestEnricher {
|
||||||
public void initializeTestContext(@Observes(precedence = 2) BeforeClass event) throws Exception {
|
public void initializeTestContext(@Observes(precedence = 2) BeforeClass event) throws Exception {
|
||||||
TestContext testContext = new TestContext(suiteContext, event.getTestClass().getJavaClass());
|
TestContext testContext = new TestContext(suiteContext, event.getTestClass().getJavaClass());
|
||||||
testContextProducer.set(testContext);
|
testContextProducer.set(testContext);
|
||||||
|
ProfileAssume.setTestContext(testContext);
|
||||||
|
|
||||||
boolean wasUpdated = false;
|
boolean wasUpdated = false;
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.jboss.arquillian.core.api.annotation.Inject;
|
||||||
import org.jboss.arquillian.test.spi.TestResult;
|
import org.jboss.arquillian.test.spi.TestResult;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.util.reflections.Reflections;
|
import org.keycloak.common.util.reflections.Reflections;
|
||||||
|
import org.keycloak.testsuite.ProfileAssume;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
|
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
|
||||||
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ public class ModelTestExecutor extends LocalTestExecuter {
|
||||||
super.execute(event);
|
super.execute(event);
|
||||||
} else {
|
} else {
|
||||||
TestResult result = new TestResult();
|
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();
|
result = TestResult.skipped();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -27,11 +27,6 @@ public @interface DisableFeature {
|
||||||
*/
|
*/
|
||||||
boolean skipRestart() default false;
|
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.
|
* Feature disable should be the last action in @Before context.
|
||||||
* If the test halted, the feature is returned to the previous state.
|
* If the test halted, the feature is returned to the previous state.
|
||||||
|
|
|
@ -27,11 +27,6 @@ public @interface EnableFeature {
|
||||||
*/
|
*/
|
||||||
boolean skipRestart() default false;
|
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.
|
* Feature enable should be the last action in @Before context.
|
||||||
* If the test halted, the feature is returned to the previous state.
|
* If the test halted, the feature is returned to the previous state.
|
||||||
|
|
|
@ -71,21 +71,18 @@ public class KeycloakContainerFeaturesController {
|
||||||
private Profile.Feature feature;
|
private Profile.Feature feature;
|
||||||
private boolean skipRestart;
|
private boolean skipRestart;
|
||||||
private FeatureAction action;
|
private FeatureAction action;
|
||||||
private boolean onlyForProduct;
|
|
||||||
private final AnnotatedElement annotatedElement;
|
private final AnnotatedElement annotatedElement;
|
||||||
|
|
||||||
public UpdateFeature(Profile.Feature feature, boolean skipRestart, FeatureAction action, boolean onlyForProduct
|
public UpdateFeature(Profile.Feature feature, boolean skipRestart, FeatureAction action, AnnotatedElement annotatedElement) {
|
||||||
, AnnotatedElement annotatedElement) {
|
|
||||||
this.feature = feature;
|
this.feature = feature;
|
||||||
this.skipRestart = skipRestart;
|
this.skipRestart = skipRestart;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.onlyForProduct = onlyForProduct;
|
|
||||||
this.annotatedElement = annotatedElement;
|
this.annotatedElement = annotatedElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertPerformed() {
|
private void assertPerformed() {
|
||||||
assertThat("An annotation requested to " + action.name() +
|
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" ,
|
"the feature is not in desired state" ,
|
||||||
ProfileAssume.isFeatureEnabled(feature),
|
ProfileAssume.isFeatureEnabled(feature),
|
||||||
is(action == FeatureAction.ENABLE));
|
is(action == FeatureAction.ENABLE));
|
||||||
|
@ -122,10 +119,6 @@ public class KeycloakContainerFeaturesController {
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOnlyForProduct() {
|
|
||||||
return onlyForProduct;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
@ -147,7 +140,6 @@ public class KeycloakContainerFeaturesController {
|
||||||
|
|
||||||
private void updateFeatures(Set<UpdateFeature> updateFeatures) throws Exception {
|
private void updateFeatures(Set<UpdateFeature> updateFeatures) throws Exception {
|
||||||
updateFeatures = updateFeatures.stream()
|
updateFeatures = updateFeatures.stream()
|
||||||
.filter(this::skipForProduct)
|
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
updateFeatures.forEach(UpdateFeature::performAction);
|
updateFeatures.forEach(UpdateFeature::performAction);
|
||||||
|
@ -160,11 +152,6 @@ public class KeycloakContainerFeaturesController {
|
||||||
updateFeatures.forEach(UpdateFeature::assertPerformed);
|
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 {
|
private void checkAnnotatedElementForFeatureAnnotations(AnnotatedElement annotatedElement, State state) throws Exception {
|
||||||
Set<UpdateFeature> updateFeatureSet = new HashSet<>();
|
Set<UpdateFeature> updateFeatureSet = new HashSet<>();
|
||||||
|
|
||||||
|
@ -191,13 +178,12 @@ public class KeycloakContainerFeaturesController {
|
||||||
|
|
||||||
ret.addAll(Arrays.stream(annotatedElement.getAnnotationsByType(EnableFeature.class))
|
ret.addAll(Arrays.stream(annotatedElement.getAnnotationsByType(EnableFeature.class))
|
||||||
.map(annotation -> new UpdateFeature(annotation.value(), annotation.skipRestart(),
|
.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()));
|
.collect(Collectors.toSet()));
|
||||||
|
|
||||||
ret.addAll(Arrays.stream(annotatedElement.getAnnotationsByType(DisableFeature.class))
|
ret.addAll(Arrays.stream(annotatedElement.getAnnotationsByType(DisableFeature.class))
|
||||||
.map(annotation -> new UpdateFeature(annotation.value(), annotation.skipRestart(),
|
.map(annotation -> new UpdateFeature(annotation.value(), annotation.skipRestart(),
|
||||||
state == State.BEFORE ? FeatureAction.DISABLE : FeatureAction.ENABLE, annotation.onlyForProduct(),
|
state == State.BEFORE ? FeatureAction.DISABLE : FeatureAction.ENABLE, annotatedElement))
|
||||||
annotatedElement))
|
|
||||||
.collect(Collectors.toSet()));
|
.collect(Collectors.toSet()));
|
||||||
|
|
||||||
return ret;
|
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.ResteasyClient;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
|
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.testsuite.ProfileAssume;
|
||||||
import org.keycloak.testsuite.client.resources.TestApplicationResource;
|
import org.keycloak.testsuite.client.resources.TestApplicationResource;
|
||||||
import org.keycloak.testsuite.client.resources.TestExampleCompanyResource;
|
import org.keycloak.testsuite.client.resources.TestExampleCompanyResource;
|
||||||
import org.keycloak.testsuite.client.resources.TestSamlApplicationResource;
|
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.testsuite.util.AdminClientUtil;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,15 +79,15 @@ public class KeycloakTestingClient implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableFeature(Profile.Feature feature) {
|
public void enableFeature(Profile.Feature feature) {
|
||||||
try (Response response = testing().enableFeature(feature.toString())) {
|
Set<Profile.Feature> disabledFeatures = testing().enableFeature(feature.toString());
|
||||||
assertEquals(204, response.getStatus());
|
Assert.assertFalse(disabledFeatures.contains(feature));
|
||||||
}
|
ProfileAssume.updateDisabledFeatures(disabledFeatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disableFeature(Profile.Feature feature) {
|
public void disableFeature(Profile.Feature feature) {
|
||||||
try (Response response = testing().disableFeature(feature.toString())) {
|
Set<Profile.Feature> disabledFeatures = testing().disableFeature(feature.toString());
|
||||||
assertEquals(204, response.getStatus());
|
Assert.assertTrue(disabledFeatures.contains(feature));
|
||||||
}
|
ProfileAssume.updateDisabledFeatures(disabledFeatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestApplicationResource testApp() { return target.proxy(TestApplicationResource.class); }
|
public TestApplicationResource testApp() { return target.proxy(TestApplicationResource.class); }
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.testsuite.client.resources;
|
package org.keycloak.testsuite.client.resources;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
|
@ -38,6 +39,8 @@ import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.infinispan.commons.time.TimeService;
|
import org.infinispan.commons.time.TimeService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -332,15 +335,22 @@ public interface TestingResource {
|
||||||
@Produces(MediaType.TEXT_HTML_UTF_8)
|
@Produces(MediaType.TEXT_HTML_UTF_8)
|
||||||
String getJavascriptTestingEnvironment();
|
String getJavascriptTestingEnvironment();
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/list-disabled-features")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
Set<Profile.Feature> listDisabledFeatures();
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/enable-feature/{feature}")
|
@Path("/enable-feature/{feature}")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
Response enableFeature(@PathParam("feature") String feature);
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
Set<Profile.Feature> enableFeature(@PathParam("feature") String feature);
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/disable-feature/{feature}")
|
@Path("/disable-feature/{feature}")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@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
|
* 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
|
@Test
|
||||||
public void testOIDCUiLocalesParamForwarding() {
|
public void testOIDCUiLocalesParamForwarding() {
|
||||||
ProfileAssume.assumeCommunity();
|
|
||||||
|
|
||||||
RealmRepresentation demoRealmRep = testRealmResource().toRepresentation();
|
RealmRepresentation demoRealmRep = testRealmResource().toRepresentation();
|
||||||
boolean enabled = demoRealmRep.isInternationalizationEnabled();
|
boolean enabled = demoRealmRep.isInternationalizationEnabled();
|
||||||
String defaultLocale = demoRealmRep.getDefaultLocale();
|
String defaultLocale = demoRealmRep.getDefaultLocale();
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.common.Profile;
|
||||||
import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation;
|
import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation;
|
||||||
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
|
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
|
||||||
import org.keycloak.testsuite.Assert;
|
import org.keycloak.testsuite.Assert;
|
||||||
|
import org.keycloak.testsuite.ProfileAssume;
|
||||||
|
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
import java.util.ArrayList;
|
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.");
|
"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-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.");
|
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-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.");
|
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();
|
String username = users.get(0).getUsername();
|
||||||
checkFeature(501, username);
|
checkFeature(501, username);
|
||||||
|
|
||||||
Response tokenResp = testingClient.testing().enableFeature(Profile.Feature.TOKEN_EXCHANGE.toString());
|
testingClient.enableFeature(Profile.Feature.TOKEN_EXCHANGE);
|
||||||
assertEquals(200, tokenResp.getStatus());
|
|
||||||
|
|
||||||
ProfileAssume.assumeFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE);
|
|
||||||
Client httpClient = AdminClientUtil.createResteasyClient();
|
Client httpClient = AdminClientUtil.createResteasyClient();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -690,8 +688,7 @@ public class SocialLoginTest extends AbstractKeycloakTest {
|
||||||
adminClient.realm(REALM).identityProviders().get(idp.getAlias()).update(idp);
|
adminClient.realm(REALM).identityProviders().get(idp.getAlias()).update(idp);
|
||||||
} finally {
|
} finally {
|
||||||
httpClient.close();
|
httpClient.close();
|
||||||
tokenResp = testingClient.testing().disableFeature(Profile.Feature.TOKEN_EXCHANGE.toString());
|
testingClient.disableFeature(Profile.Feature.TOKEN_EXCHANGE);
|
||||||
assertEquals(200, tokenResp.getStatus());
|
|
||||||
checkFeature(501, username);
|
checkFeature(501, username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.crossdc;
|
package org.keycloak.testsuite.crossdc;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.keycloak.admin.client.Keycloak;
|
import org.keycloak.admin.client.Keycloak;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
|
@ -86,6 +87,10 @@ public class ActionTokenCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@InitialDcState(authServers = ServerSetup.ALL_NODES_IN_FIRST_DC_FIRST_NODE_IN_SECOND_DC)
|
@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(
|
public void sendResetPasswordEmailSuccessWorksInCrossDc(
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc0Node0Statistics,
|
@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,
|
@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 {
|
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||||
log.debug("--DC: START sendResetPasswordEmailSuccessWorksInCrossDc");
|
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);
|
cacheDc0Node1Statistics.waitToBecomeAvailable(10, TimeUnit.SECONDS);
|
||||||
|
|
||||||
Comparable originalNumberOfEntries = cacheDc0Node0Statistics.getSingleStatistics(Constants.STAT_CACHE_NUMBER_OF_ENTRIES_IN_MEMORY);
|
Comparable originalNumberOfEntries = cacheDc0Node0Statistics.getSingleStatistics(Constants.STAT_CACHE_NUMBER_OF_ENTRIES_IN_MEMORY);
|
||||||
|
|
|
@ -717,4 +717,4 @@ public class LevelOfAssuranceFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
Assert.assertEquals(expectedError, errorPage.getError());
|
Assert.assertEquals(expectedError, errorPage.getError());
|
||||||
events.clear();
|
events.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,8 +60,6 @@ public class AccountPageTest extends AbstractI18NTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocalizedReferrerLinkContent() {
|
public void testLocalizedReferrerLinkContent() {
|
||||||
ProfileAssume.assumeCommunity();
|
|
||||||
|
|
||||||
RealmResource testRealm = testRealm();
|
RealmResource testRealm = testRealm();
|
||||||
List<ClientRepresentation> foundClients = testRealm.clients().findByClientId("var-named-test-app");
|
List<ClientRepresentation> foundClients = testRealm.clients().findByClientId("var-named-test-app");
|
||||||
if (foundClients.isEmpty()) {
|
if (foundClients.isEmpty()) {
|
||||||
|
|
|
@ -97,8 +97,6 @@ public class EmailTest extends AbstractI18NTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void restPasswordEmailGerman() throws IOException, MessagingException {
|
public void restPasswordEmailGerman() throws IOException, MessagingException {
|
||||||
ProfileAssume.assumeCommunity();
|
|
||||||
|
|
||||||
changeUserLocale("de");
|
changeUserLocale("de");
|
||||||
|
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
|
@ -118,8 +116,6 @@ public class EmailTest extends AbstractI18NTest {
|
||||||
//KEYCLOAK-7478
|
//KEYCLOAK-7478
|
||||||
@Test
|
@Test
|
||||||
public void changeLocaleOnInfoPage() throws InterruptedException, IOException, MessagingException {
|
public void changeLocaleOnInfoPage() throws InterruptedException, IOException, MessagingException {
|
||||||
ProfileAssume.assumeCommunity();
|
|
||||||
|
|
||||||
UserResource testUser = ApiUtil.findUserByUsernameId(testRealm(), "login-test");
|
UserResource testUser = ApiUtil.findUserByUsernameId(testRealm(), "login-test");
|
||||||
testUser.executeActionsEmail(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
|
testUser.executeActionsEmail(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
|
||||||
|
|
||||||
|
|
|
@ -88,8 +88,6 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void languageDropdown() {
|
public void languageDropdown() {
|
||||||
ProfileAssume.assumeCommunity();
|
|
||||||
|
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
Assert.assertEquals("English", loginPage.getLanguageDropdownText());
|
Assert.assertEquals("English", loginPage.getLanguageDropdownText());
|
||||||
|
|
||||||
|
@ -123,8 +121,6 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void acceptLanguageHeader() throws IOException {
|
public void acceptLanguageHeader() throws IOException {
|
||||||
ProfileAssume.assumeCommunity();
|
|
||||||
|
|
||||||
try(CloseableHttpClient httpClient = (CloseableHttpClient) new HttpClientBuilder().build()) {
|
try(CloseableHttpClient httpClient = (CloseableHttpClient) new HttpClientBuilder().build()) {
|
||||||
ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient);
|
ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient);
|
||||||
ResteasyClient client = ((ResteasyClientBuilder) ResteasyClientBuilder.newBuilder()).httpEngine(engine).build();
|
ResteasyClient client = ((ResteasyClientBuilder) ResteasyClientBuilder.newBuilder()).httpEngine(engine).build();
|
||||||
|
@ -156,8 +152,6 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
// KEYCLOAK-3887
|
// KEYCLOAK-3887
|
||||||
@Test
|
@Test
|
||||||
public void languageChangeRequiredActions() {
|
public void languageChangeRequiredActions() {
|
||||||
ProfileAssume.assumeCommunity();
|
|
||||||
|
|
||||||
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
|
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
|
||||||
UserRepresentation userRep = user.toRepresentation();
|
UserRepresentation userRep = user.toRepresentation();
|
||||||
userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
|
userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
|
||||||
|
@ -183,8 +177,6 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
// KEYCLOAK-3887
|
// KEYCLOAK-3887
|
||||||
@Test
|
@Test
|
||||||
public void languageChangeConsentScreen() {
|
public void languageChangeConsentScreen() {
|
||||||
ProfileAssume.assumeCommunity();
|
|
||||||
|
|
||||||
// Set client, which requires consent
|
// Set client, which requires consent
|
||||||
oauth.clientId("third-party");
|
oauth.clientId("third-party");
|
||||||
|
|
||||||
|
@ -210,8 +202,6 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void languageUserUpdates() {
|
public void languageUserUpdates() {
|
||||||
ProfileAssume.assumeCommunity();
|
|
||||||
|
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.openLanguage("Deutsch");
|
loginPage.openLanguage("Deutsch");
|
||||||
|
|
||||||
|
|
|
@ -516,8 +516,6 @@ public class X509BrowserLoginTest extends AbstractX509AuthenticationTest {
|
||||||
// KEYCLOAK-6866
|
// KEYCLOAK-6866
|
||||||
@Test
|
@Test
|
||||||
public void changeLocaleOnX509InfoPage() {
|
public void changeLocaleOnX509InfoPage() {
|
||||||
ProfileAssume.assumeCommunity();
|
|
||||||
|
|
||||||
AuthenticatorConfigRepresentation cfg = newConfig("x509-browser-config", createLoginSubjectEmail2UsernameOrEmailConfig().getConfig());
|
AuthenticatorConfigRepresentation cfg = newConfig("x509-browser-config", createLoginSubjectEmail2UsernameOrEmailConfig().getConfig());
|
||||||
String cfgId = createConfig(browserExecution.getId(), cfg);
|
String cfgId = createConfig(browserExecution.getId(), cfg);
|
||||||
Assert.assertNotNull(cfgId);
|
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.policy.provider.PolicySpi;
|
||||||
import org.keycloak.authorization.store.StoreFactorySpi;
|
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||||
import org.keycloak.cluster.ClusterSpi;
|
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.common.util.Time;
|
||||||
import org.keycloak.component.ComponentFactoryProviderFactory;
|
import org.keycloak.component.ComponentFactoryProviderFactory;
|
||||||
import org.keycloak.component.ComponentFactorySpi;
|
import org.keycloak.component.ComponentFactorySpi;
|
||||||
|
@ -60,6 +62,7 @@ import java.lang.management.LockInfo;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.lang.management.ThreadInfo;
|
import java.lang.management.ThreadInfo;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
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);
|
LOG.debugf("Creating factory %d in %s using the following configuration:\n %s", factoryIndex, threadName, CONFIG);
|
||||||
|
|
||||||
DefaultKeycloakSessionFactory res = new DefaultKeycloakSessionFactory() {
|
DefaultKeycloakSessionFactory res = new DefaultKeycloakSessionFactory() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
Profile.configure(new PropertiesProfileConfigResolver(System.getProperties()));
|
||||||
|
super.init();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isEnabled(ProviderFactory factory, Scope scope) {
|
protected boolean isEnabled(ProviderFactory factory, Scope scope) {
|
||||||
return super.isEnabled(factory, scope) && isFactoryAllowed(factory);
|
return super.isEnabled(factory, scope) && isFactoryAllowed(factory);
|
||||||
|
|
|
@ -23,6 +23,9 @@ import java.nio.file.Files;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
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;
|
import org.keycloak.platform.PlatformProvider;
|
||||||
|
|
||||||
public class TestPlatform implements PlatformProvider {
|
public class TestPlatform implements PlatformProvider {
|
||||||
|
@ -31,6 +34,13 @@ public class TestPlatform implements PlatformProvider {
|
||||||
|
|
||||||
private File tmpDir;
|
private File tmpDir;
|
||||||
|
|
||||||
|
public TestPlatform() {
|
||||||
|
Profile.configure(
|
||||||
|
new PropertiesProfileConfigResolver(System.getProperties()),
|
||||||
|
new PropertiesFileProfileConfigResolver()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStartup(Runnable startupHook) {
|
public void onStartup(Runnable startupHook) {
|
||||||
startupHook.run();
|
startupHook.run();
|
||||||
|
|
Loading…
Reference in a new issue