From 1de9c201c6720259ee58a13f26f32e9de43fa363 Mon Sep 17 00:00:00 2001 From: stianst Date: Fri, 28 Oct 2022 12:09:35 +0200 Subject: [PATCH] Refactor Profile Closes #15206 --- .../java/org/keycloak/common/Profile.java | 429 +++++++++--------- ...mmaSeparatedListProfileConfigResolver.java | 41 ++ .../common/profile/ProfileConfigResolver.java | 17 + .../common/profile/ProfileException.java | 12 + .../PropertiesFileProfileConfigResolver.java | 59 +++ .../PropertiesProfileConfigResolver.java | 36 ++ .../java/org/keycloak/common/ProfileTest.java | 198 ++++++-- .../info/ProfileInfoRepresentation.java | 11 +- .../org/keycloak/guides/maven/Features.java | 14 +- .../org/keycloak/config/FeatureOptions.java | 7 +- .../quarkus/deployment/ConfigBuildItem.java | 6 + .../quarkus/deployment/KeycloakProcessor.java | 26 +- .../quarkus/deployment/ProfileBuildItem.java | 6 + .../quarkus/runtime/KeycloakRecorder.java | 10 +- .../quarkus/runtime/QuarkusProfile.java | 97 ---- .../runtime/QuarkusProfileConfigResolver.java | 17 + .../mappers/FeaturePropertyMappers.java | 4 +- .../it/cli/dist/FeaturesDistTest.java | 22 +- .../it/storage/map/ChmStorageDistTest.java | 2 +- .../it/storage/map/HotRodStoreDistTest.java | 2 +- .../it/storage/map/JPAStoreDistTest.java | 2 +- ...ommandTest.testBuildHelp.unix.approved.txt | 26 +- ...andTest.testStartDevHelp.unix.approved.txt | 26 +- ...Test.testStartDevHelpAll.unix.approved.txt | 26 +- ...ommandTest.testStartHelp.unix.approved.txt | 26 +- ...andTest.testStartHelpAll.unix.approved.txt | 26 +- .../oidc/OIDCLoginProtocolFactory.java | 15 +- .../migration/DefaultMigrationProvider.java | 15 +- .../rest/TestingResourceProvider.java | 104 ++--- .../org/keycloak/testsuite/ProfileAssume.java | 54 +-- .../arquillian/AuthServerTestEnricher.java | 2 + .../arquillian/ModelTestExecutor.java | 3 +- .../arquillian/annotation/DisableFeature.java | 5 - .../arquillian/annotation/EnableFeature.java | 5 - .../KeycloakContainerFeaturesController.java | 22 +- .../client/KeycloakTestingClient.java | 17 +- .../client/resources/TestingResource.java | 14 +- .../servlet/DemoServletsAdapterTest.java | 2 - .../admin/authentication/ProvidersTest.java | 3 +- .../ScriptBasedAuthenticatorTest.java | 31 -- .../testsuite/broker/SocialLoginTest.java | 7 +- .../crossdc/ActionTokenCrossDCTest.java | 10 +- .../forms/LevelOfAssuranceFlowTest.java | 2 +- .../testsuite/i18n/AccountPageTest.java | 2 - .../keycloak/testsuite/i18n/EmailTest.java | 4 - .../testsuite/i18n/LoginPageTest.java | 10 - .../testsuite/x509/X509BrowserLoginTest.java | 2 - .../testsuite/model/KeycloakModelTest.java | 10 + .../org/keycloak/testsuite/TestPlatform.java | 10 + 49 files changed, 838 insertions(+), 659 deletions(-) create mode 100644 common/src/main/java/org/keycloak/common/profile/CommaSeparatedListProfileConfigResolver.java create mode 100644 common/src/main/java/org/keycloak/common/profile/ProfileConfigResolver.java create mode 100644 common/src/main/java/org/keycloak/common/profile/ProfileException.java create mode 100644 common/src/main/java/org/keycloak/common/profile/PropertiesFileProfileConfigResolver.java create mode 100644 common/src/main/java/org/keycloak/common/profile/PropertiesProfileConfigResolver.java create mode 100644 quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/ConfigBuildItem.java create mode 100644 quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/ProfileBuildItem.java delete mode 100644 quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/QuarkusProfile.java create mode 100644 quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/QuarkusProfileConfigResolver.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ScriptBasedAuthenticatorTest.java diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java index 2a6ded3c0f..44c6108410 100755 --- a/common/src/main/java/org/keycloak/common/Profile.java +++ b/common/src/main/java/org/keycloak/common/Profile.java @@ -17,16 +17,20 @@ package org.keycloak.common; -import static org.keycloak.common.Profile.Type.DEPRECATED; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Properties; -import java.util.Set; import org.jboss.logging.Logger; +import org.keycloak.common.profile.ProfileConfigResolver; +import org.keycloak.common.profile.ProfileException; +import org.keycloak.common.profile.PropertiesFileProfileConfigResolver; +import org.keycloak.common.profile.PropertiesProfileConfigResolver; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; /** * @author Bill Burke @@ -34,167 +38,74 @@ import org.jboss.logging.Logger; */ public class Profile { - private static final Logger logger = Logger.getLogger(Profile.class); - - private static Profile CURRENT; - private final ProfileValue profile; - private final Set disabledFeatures = new HashSet<>(); - private final Set previewFeatures = new HashSet<>(); - private final Set experimentalFeatures = new HashSet<>(); - private final Set 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 getDisabledFeatures() { - return getInstance().disabledFeatures; - } - - public static Set getPreviewFeatures() { - return getInstance().previewFeatures; - } - - public static Set getExperimentalFeatures() { - return getInstance().experimentalFeatures; - } - - public static Set getDeprecatedFeatures() { - return getInstance().deprecatedFeatures; - } - - public static boolean isFeatureEnabled(Feature feature) { - return !getInstance().disabledFeatures.contains(feature); - } - - public enum Type { - DEFAULT, - DISABLED_BY_DEFAULT, - PREVIEW, - EXPERIMENTAL, - DEPRECATED; - } - public enum Feature { AUTHORIZATION("Authorization Service", Type.DEFAULT), - ACCOUNT2("New Account Management Console", Type.DEFAULT), + ACCOUNT_API("Account Management REST API", Type.DEFAULT), + ACCOUNT2("New Account Management Console", Type.DEFAULT, Feature.ACCOUNT_API), + ADMIN_FINE_GRAINED_AUTHZ("Fine-Grained Admin Permissions", Type.PREVIEW), - /** - * Controls the availability of the Admin REST-API. - */ + ADMIN_API("Admin API", Type.DEFAULT), - /** - * Controls the availability of the legacy admin-console. - * Note that the admin-console requires the {@link #ADMIN_API} feature. - */ @Deprecated ADMIN("Legacy Admin Console", Type.DEPRECATED), - /** - * Controls the availability of the admin-console. - * Note that the admin-console requires the {@link #ADMIN_API} feature. - */ - ADMIN2("New Admin Console", Type.DEFAULT), - DOCKER("Docker Registry protocol", Type.DISABLED_BY_DEFAULT), - IMPERSONATION("Ability for admins to impersonate users", Type.DEFAULT), - OPENSHIFT_INTEGRATION("Extension to enable securing OpenShift", Type.PREVIEW), - SCRIPTS("Write custom authenticators using JavaScript", Type.PREVIEW), - TOKEN_EXCHANGE("Token Exchange Service", Type.PREVIEW), - WEB_AUTHN("W3C Web Authentication (WebAuthn)", Type.DEFAULT), - CLIENT_POLICIES("Client configuration policies", Type.DEFAULT), - CIBA("OpenID Connect Client Initiated Backchannel Authentication (CIBA)", Type.DEFAULT), - MAP_STORAGE("New store", Type.EXPERIMENTAL), - PAR("OAuth 2.0 Pushed Authorization Requests (PAR)", Type.DEFAULT), - DECLARATIVE_USER_PROFILE("Configure user profiles using a declarative style", Type.PREVIEW), - DYNAMIC_SCOPES("Dynamic OAuth 2.0 scopes", Type.EXPERIMENTAL), - CLIENT_SECRET_ROTATION("Client Secret Rotation", Type.PREVIEW), - STEP_UP_AUTHENTICATION("Step-up Authentication", Type.DEFAULT), - RECOVERY_CODES("Recovery codes", Type.PREVIEW), - UPDATE_EMAIL("Update Email Action", Type.PREVIEW), - JS_ADAPTER("Host keycloak.js and keycloak-authz.js through the Keycloak sever", Type.DEFAULT); + ADMIN2("New Admin Console", Type.DEFAULT, Feature.ADMIN_API), + DOCKER("Docker Registry protocol", Type.DISABLED_BY_DEFAULT), + + IMPERSONATION("Ability for admins to impersonate users", Type.DEFAULT), + + OPENSHIFT_INTEGRATION("Extension to enable securing OpenShift", Type.PREVIEW), + + SCRIPTS("Write custom authenticators using JavaScript", Type.PREVIEW), + + TOKEN_EXCHANGE("Token Exchange Service", Type.PREVIEW), + + WEB_AUTHN("W3C Web Authentication (WebAuthn)", Type.DEFAULT), + + CLIENT_POLICIES("Client configuration policies", Type.DEFAULT), + + CIBA("OpenID Connect Client Initiated Backchannel Authentication (CIBA)", Type.DEFAULT), + + MAP_STORAGE("New store", Type.EXPERIMENTAL), + + PAR("OAuth 2.0 Pushed Authorization Requests (PAR)", Type.DEFAULT), + + DECLARATIVE_USER_PROFILE("Configure user profiles using a declarative style", Type.PREVIEW), + + DYNAMIC_SCOPES("Dynamic OAuth 2.0 scopes", Type.EXPERIMENTAL), + + CLIENT_SECRET_ROTATION("Client Secret Rotation", Type.PREVIEW), + + STEP_UP_AUTHENTICATION("Step-up Authentication", Type.DEFAULT), + + RECOVERY_CODES("Recovery codes", Type.PREVIEW), + + UPDATE_EMAIL("Update Email Action", Type.PREVIEW), + + JS_ADAPTER("Host keycloak.js and keycloak-authz.js through the Keycloak sever", Type.DEFAULT); private final Type type; private String label; + private Set dependencies; Feature(String label, Type type) { this.label = label; this.type = type; } + Feature(String label, Type type, Feature... dependencies) { + this.label = label; + this.type = type; + this.dependencies = Arrays.stream(dependencies).collect(Collectors.toSet()); + } + + public String getKey() { + return name().toLowerCase().replaceAll("_", "-"); + } + public String getLabel() { return label; } @@ -202,88 +113,162 @@ public class Profile { public Type getType() { return type; } + + public Set 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 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 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 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 features) { + CURRENT = new Profile(profileName, features); + return CURRENT; + } + + private Profile(ProfileName profileName, Map 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 getAllFeatures() { + return features.keySet(); + } + + public Set getDisabledFeatures() { + return features.entrySet().stream().filter(e -> !e.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet()); + } + + public Set getPreviewFeatures() { + return getFeatures(Feature.Type.PREVIEW); + } + + public Set getExperimentalFeatures() { + return getFeatures(Feature.Type.EXPERIMENTAL); + } + + public Set getDeprecatedFeatures() { + return getFeatures(Feature.Type.DEPRECATED); + } + + public Set getFeatures(Feature.Type type) { + return features.keySet().stream().filter(f -> f.getType().equals(type)).collect(Collectors.toSet()); + } + + public Map getFeatures() { + return features; + } + + public enum ProfileName { DEFAULT, PREVIEW } - public interface PropertyResolver { - String resolve(String feature); + private static Boolean isFeatureEnabled(ProfileName profile, Feature feature, ProfileConfigResolver... resolvers) { + ProfileConfigResolver.FeatureConfig configuration = Arrays.stream(resolvers).map(r -> r.getFeatureConfig(feature)) + .filter(r -> !r.equals(ProfileConfigResolver.FeatureConfig.UNCONFIGURED)) + .findFirst() + .orElse(ProfileConfigResolver.FeatureConfig.UNCONFIGURED); + switch (configuration) { + case ENABLED: + return true; + case DISABLED: + return false; + default: + switch (feature.getType()) { + case DEFAULT: + return true; + case PREVIEW: + return profile.equals(ProfileName.PREVIEW) ? true : false; + default: + return false; + } + } } - private class Config { - - private Properties properties; - - public Config() { - properties = new Properties(); - - try { - String jbossServerConfigDir = System.getProperty("jboss.server.config.dir"); - if (jbossServerConfigDir != null) { - File file = new File(jbossServerConfigDir, "profile.properties"); - if (file.isFile()) { - try (FileInputStream is = new FileInputStream(file)) { - properties.load(is); - } + private static void verifyConfig(Map features) { + for (Feature f : features.keySet()) { + if (f.getDependencies() != null) { + for (Feature d : f.getDependencies()) { + if (!features.get(d)) { + throw new ProfileException("Feature " + f.getKey() + " depends on disabled feature " + d.getKey()); } } - } catch (IOException e) { - throw new RuntimeException(e); } } - - public String getProfile() { - String profile = getProperty("keycloak.profile"); - if (profile != null) { - return profile; - } - - 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; - } } -} \ No newline at end of file + 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); + } + } + +} diff --git a/common/src/main/java/org/keycloak/common/profile/CommaSeparatedListProfileConfigResolver.java b/common/src/main/java/org/keycloak/common/profile/CommaSeparatedListProfileConfigResolver.java new file mode 100644 index 0000000000..212aedf48a --- /dev/null +++ b/common/src/main/java/org/keycloak/common/profile/CommaSeparatedListProfileConfigResolver.java @@ -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 enabledFeatures; + private Set 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; + } +} diff --git a/common/src/main/java/org/keycloak/common/profile/ProfileConfigResolver.java b/common/src/main/java/org/keycloak/common/profile/ProfileConfigResolver.java new file mode 100644 index 0000000000..b6a2be965a --- /dev/null +++ b/common/src/main/java/org/keycloak/common/profile/ProfileConfigResolver.java @@ -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 + } + +} diff --git a/common/src/main/java/org/keycloak/common/profile/ProfileException.java b/common/src/main/java/org/keycloak/common/profile/ProfileException.java new file mode 100644 index 0000000000..5f9e9cef4a --- /dev/null +++ b/common/src/main/java/org/keycloak/common/profile/ProfileException.java @@ -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); + } +} diff --git a/common/src/main/java/org/keycloak/common/profile/PropertiesFileProfileConfigResolver.java b/common/src/main/java/org/keycloak/common/profile/PropertiesFileProfileConfigResolver.java new file mode 100644 index 0000000000..c3d72d38c1 --- /dev/null +++ b/common/src/main/java/org/keycloak/common/profile/PropertiesFileProfileConfigResolver.java @@ -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; + } +} diff --git a/common/src/main/java/org/keycloak/common/profile/PropertiesProfileConfigResolver.java b/common/src/main/java/org/keycloak/common/profile/PropertiesProfileConfigResolver.java new file mode 100644 index 0000000000..d6fe5d82ca --- /dev/null +++ b/common/src/main/java/org/keycloak/common/profile/PropertiesProfileConfigResolver.java @@ -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; + } +} diff --git a/common/src/test/java/org/keycloak/common/ProfileTest.java b/common/src/test/java/org/keycloak/common/ProfileTest.java index 95f7f1a69a..b4ff592657 100644 --- a/common/src/test/java/org/keycloak/common/ProfileTest.java +++ b/common/src/test/java/org/keycloak/common/ProfileTest.java @@ -1,54 +1,198 @@ package org.keycloak.common; +import org.junit.After; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.keycloak.common.Profile.Feature; +import org.keycloak.common.profile.CommaSeparatedListProfileConfigResolver; +import org.keycloak.common.profile.ProfileException; +import org.keycloak.common.profile.PropertiesFileProfileConfigResolver; +import org.keycloak.common.profile.PropertiesProfileConfigResolver; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.Comparator; +import java.util.Properties; import java.util.Set; -import static org.junit.Assert.assertTrue; - public class ProfileTest { + private static final Profile.Feature DEFAULT_FEATURE = Profile.Feature.AUTHORIZATION; + private static final Profile.Feature DISABLED_BY_DEFAULT_FEATURE = Profile.Feature.DOCKER; + private static final Profile.Feature PREVIEW_FEATURE = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ; + private static final Profile.Feature EXPERIMENTAL_FEATURE = Profile.Feature.DYNAMIC_SCOPES; + private static final Profile.Feature DEPRECATED_FEATURE = Profile.Feature.ADMIN; + @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); - @Test - public void checkDefaults() { - Assert.assertEquals("default", Profile.getName()); - assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL); - assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL); + @BeforeClass + public static void beforeClass() { + Assert.assertEquals(Profile.Feature.Type.DEFAULT, DEFAULT_FEATURE.getType()); + Assert.assertEquals(Profile.Feature.Type.DISABLED_BY_DEFAULT, DISABLED_BY_DEFAULT_FEATURE.getType()); + Assert.assertEquals(Profile.Feature.Type.PREVIEW, PREVIEW_FEATURE.getType()); + Assert.assertEquals(Profile.Feature.Type.EXPERIMENTAL, EXPERIMENTAL_FEATURE.getType()); + Assert.assertEquals(Profile.Feature.Type.DEPRECATED, DEPRECATED_FEATURE.getType()); + } + + @After + public void afterTest() { + Profile.defaults(); } @Test - public void configWithSystemProperties() { - Assert.assertEquals("default", Profile.getName()); - Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.DOCKER)); - Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION)); - assertTrue(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION)); + public void checkDefaults() { + Profile profile = Profile.defaults(); - System.setProperty("keycloak.profile", "preview"); - System.setProperty("keycloak.profile.feature.docker", "enabled"); - System.setProperty("keycloak.profile.feature.impersonation", "disabled"); - System.setProperty("keycloak.profile.feature.upload_scripts", "enabled"); + Assert.assertTrue(Profile.isFeatureEnabled(DEFAULT_FEATURE)); + Assert.assertFalse(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE)); + Assert.assertFalse(Profile.isFeatureEnabled(PREVIEW_FEATURE)); + Assert.assertFalse(Profile.isFeatureEnabled(EXPERIMENTAL_FEATURE)); + Assert.assertFalse(Profile.isFeatureEnabled(DEPRECATED_FEATURE)); - Profile.init(); + Assert.assertEquals(Profile.ProfileName.DEFAULT, profile.getName()); + assertEquals(profile.getDisabledFeatures(), Profile.Feature.ADMIN, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL); + assertEquals(profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL); + } - Assert.assertEquals("preview", Profile.getName()); - assertTrue(Profile.isFeatureEnabled(Profile.Feature.DOCKER)); - assertTrue(Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION)); - Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION)); + @Test + public void checkFailureIfDependencyDisabled() { + Properties properties = new Properties(); + properties.setProperty("keycloak.profile.feature.account_api", "disabled"); - System.getProperties().remove("keycloak.profile"); - System.getProperties().remove("keycloak.profile.feature.docker"); - System.getProperties().remove("keycloak.profile.feature.impersonation"); - System.getProperties().remove("keycloak.profile.feature.upload_scripts"); + try { + Profile.configure(new PropertiesProfileConfigResolver(properties)); + } catch (ProfileException e) { + Assert.assertEquals("Feature account2 depends on disabled feature account-api", e.getMessage()); + } + } - Profile.init(); + @Test + public void checkErrorOnBadConfig() { + Properties properties = new Properties(); + properties.setProperty("keycloak.profile.feature.account_api", "invalid"); + + try { + Profile.configure(new PropertiesProfileConfigResolver(properties)); + } catch (ProfileException e) { + Assert.assertEquals("Invalid config value 'invalid' for feature account-api", e.getMessage()); + } + } + + @Test + public void enablePreviewWithProperties() { + Properties properties = new Properties(); + properties.setProperty("keycloak.profile", "preview"); + Profile.configure(new PropertiesProfileConfigResolver(properties)); + + Assert.assertEquals(Profile.ProfileName.PREVIEW, Profile.getInstance().getName()); + Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE)); + } + + @Test + public void enablePreviewWithCommaSeparatedList() { + Profile.configure(new CommaSeparatedListProfileConfigResolver("preview", null)); + + Assert.assertEquals(Profile.ProfileName.PREVIEW, Profile.getInstance().getName()); + Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE)); + } + + @Test + public void enablePreviewWithPropertiesFile() throws IOException { + Properties properties = new Properties(); + properties.setProperty("profile", "preview"); + + Path tempDirectory = Files.createTempDirectory("jboss-config"); + System.setProperty("jboss.server.config.dir", tempDirectory.toString()); + + Path profileProperties = tempDirectory.resolve("profile.properties"); + + properties.store(new FileOutputStream(profileProperties.toFile()), ""); + + Profile.configure(new PropertiesFileProfileConfigResolver()); + + Assert.assertEquals(Profile.ProfileName.PREVIEW, Profile.getInstance().getName()); + Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE)); + + Files.delete(profileProperties); + Files.delete(tempDirectory); + System.getProperties().remove("jboss.server.config.dir"); + } + + @Test + public void configWithCommaSeparatedList() { + String enabledFeatures = DISABLED_BY_DEFAULT_FEATURE.getKey() + "," + PREVIEW_FEATURE.getKey() + "," + EXPERIMENTAL_FEATURE.getKey() + "," + DEPRECATED_FEATURE.getKey(); + String disabledFeatures = DEFAULT_FEATURE.getKey(); + Profile.configure(new CommaSeparatedListProfileConfigResolver(enabledFeatures, disabledFeatures)); + + Assert.assertFalse(Profile.isFeatureEnabled(DEFAULT_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(EXPERIMENTAL_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(DEPRECATED_FEATURE)); + } + + @Test + public void configWithProperties() { + Properties properties = new Properties(); + properties.setProperty("keycloak.profile.feature." + DEFAULT_FEATURE.name().toLowerCase(), "disabled"); + properties.setProperty("keycloak.profile.feature." + DISABLED_BY_DEFAULT_FEATURE.name().toLowerCase(), "enabled"); + properties.setProperty("keycloak.profile.feature." + PREVIEW_FEATURE.name().toLowerCase(), "enabled"); + properties.setProperty("keycloak.profile.feature." + EXPERIMENTAL_FEATURE.name().toLowerCase(), "enabled"); + properties.setProperty("keycloak.profile.feature." + DEPRECATED_FEATURE.name().toLowerCase(), "enabled"); + + Profile.configure(new PropertiesProfileConfigResolver(properties)); + + Assert.assertFalse(Profile.isFeatureEnabled(DEFAULT_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(EXPERIMENTAL_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(DEPRECATED_FEATURE)); + } + + @Test + public void configWithPropertiesFile() throws IOException { + Properties properties = new Properties(); + properties.setProperty("feature." + DEFAULT_FEATURE.name().toLowerCase(), "disabled"); + properties.setProperty("feature." + DISABLED_BY_DEFAULT_FEATURE.name().toLowerCase(), "enabled"); + properties.setProperty("feature." + PREVIEW_FEATURE.name().toLowerCase(), "enabled"); + properties.setProperty("feature." + EXPERIMENTAL_FEATURE.name().toLowerCase(), "enabled"); + properties.setProperty("feature." + DEPRECATED_FEATURE.name().toLowerCase(), "enabled"); + + Path tempDirectory = Files.createTempDirectory("jboss-config"); + System.setProperty("jboss.server.config.dir", tempDirectory.toString()); + + Path profileProperties = tempDirectory.resolve("profile.properties"); + + properties.store(new FileOutputStream(profileProperties.toFile()), ""); + + Profile.configure(new PropertiesFileProfileConfigResolver()); + + Assert.assertFalse(Profile.isFeatureEnabled(DEFAULT_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(EXPERIMENTAL_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(DEPRECATED_FEATURE)); + + Files.delete(profileProperties); + Files.delete(tempDirectory); + System.getProperties().remove("jboss.server.config.dir"); + } + + @Test + public void configWithMultipleResolvers() { + Properties properties = new Properties(); + properties.setProperty("keycloak.profile.feature." + PREVIEW_FEATURE.name().toLowerCase(), "enabled"); + + Profile.configure(new CommaSeparatedListProfileConfigResolver(DISABLED_BY_DEFAULT_FEATURE.getKey(), ""), new PropertiesProfileConfigResolver(properties)); + + Assert.assertTrue(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE)); + Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE)); } public static void assertEquals(Set actual, Profile.Feature... expected) { diff --git a/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java index 52481bcd66..34e9e54d6c 100644 --- a/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java @@ -22,6 +22,7 @@ import org.keycloak.common.Profile; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * @author Stian Thorgersen @@ -36,10 +37,12 @@ public class ProfileInfoRepresentation { public static ProfileInfoRepresentation create() { ProfileInfoRepresentation info = new ProfileInfoRepresentation(); - info.name = Profile.getName(); - info.disabledFeatures = names(Profile.getDisabledFeatures()); - info.previewFeatures = names(Profile.getPreviewFeatures()); - info.experimentalFeatures = names(Profile.getExperimentalFeatures()); + Profile profile = Profile.getInstance(); + + info.name = profile.getName().name().toLowerCase(); + info.disabledFeatures = names(profile.getDisabledFeatures()); + info.previewFeatures = names(profile.getPreviewFeatures()); + info.experimentalFeatures = names(profile.getExperimentalFeatures()); return info; } diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Features.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Features.java index 5ac77a2712..c50e32b2b2 100644 --- a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Features.java +++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Features.java @@ -13,26 +13,26 @@ public class Features { public Features() { this.features = Arrays.stream(Profile.Feature.values()) - .filter(f -> !f.getType().equals(Profile.Type.EXPERIMENTAL)) + .filter(f -> !f.getType().equals(Profile.Feature.Type.EXPERIMENTAL)) .map(f -> new Feature(f)) .sorted(Comparator.comparing(Feature::getName)) .collect(Collectors.toList()); } public List 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 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 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 getPreview() { - return features.stream().filter(f -> f.getType().equals(Profile.Type.PREVIEW)).collect(Collectors.toList()); + return features.stream().filter(f -> f.getType().equals(Profile.Feature.Type.PREVIEW)).collect(Collectors.toList()); } public class Feature { @@ -44,14 +44,14 @@ public class Features { } public String getName() { - return profileFeature.name().toLowerCase().replaceAll("_", "-"); + return profileFeature.getKey(); } public String getDescription() { return profileFeature.getLabel(); } - private Profile.Type getType() { + private Profile.Feature.Type getType() { return profileFeature.getType(); } diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/FeatureOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/FeatureOptions.java index 860693f870..200469a791 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/FeatureOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/FeatureOptions.java @@ -3,6 +3,7 @@ package org.keycloak.config; import org.keycloak.common.Profile; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -27,10 +28,12 @@ public class FeatureOptions { List features = new ArrayList<>(); for (Profile.Feature value : Profile.Feature.values()) { - features.add(value.name().toLowerCase().replace('_', '-')); + features.add(value.getKey()); } - features.add(Profile.Type.PREVIEW.name().toLowerCase()); + features.add(Profile.Feature.Type.PREVIEW.name().toLowerCase()); + + Collections.sort(features); return features; } diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/ConfigBuildItem.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/ConfigBuildItem.java new file mode 100644 index 0000000000..e2fa616a1e --- /dev/null +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/ConfigBuildItem.java @@ -0,0 +1,6 @@ +package org.keycloak.quarkus.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class ConfigBuildItem extends SimpleBuildItem { +} diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index 92ec803e6e..8c509952b4 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -94,6 +94,8 @@ import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; import org.jboss.resteasy.spi.ResteasyDeployment; import org.keycloak.Config; import org.keycloak.common.crypto.FipsMode; +import org.keycloak.common.profile.PropertiesFileProfileConfigResolver; +import org.keycloak.common.profile.PropertiesProfileConfigResolver; import org.keycloak.config.SecurityOptions; import org.keycloak.config.StorageOptions; import org.keycloak.config.TransactionOptions; @@ -101,7 +103,7 @@ import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.connections.jpa.JpaConnectionSpi; import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory; import org.keycloak.protocol.saml.mappers.DeployedScriptSAMLProtocolMapper; -import org.keycloak.quarkus.runtime.QuarkusProfile; +import org.keycloak.quarkus.runtime.QuarkusProfileConfigResolver; import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper; @@ -207,6 +209,26 @@ class KeycloakProcessor { return new FeatureBuildItem("keycloak"); } + @Record(ExecutionTime.STATIC_INIT) + @BuildStep + ConfigBuildItem initConfig(KeycloakRecorder recorder) { + Config.init(new MicroProfileConfigProvider()); + recorder.initConfig(); + return new ConfigBuildItem(); + } + + @Record(ExecutionTime.STATIC_INIT) + @BuildStep + @Consume(ConfigBuildItem.class) + ProfileBuildItem configureProfile(KeycloakRecorder recorder) { + Profile profile = Profile.configure( + new QuarkusProfileConfigResolver(), + new PropertiesFileProfileConfigResolver()); // Need profile.properties for now as testsuite relies on it + recorder.configureProfile(profile.getName(), profile.getFeatures()); + + return new ProfileBuildItem(); + } + /** *

Configures the persistence unit for Quarkus. * @@ -331,8 +353,8 @@ class KeycloakProcessor { */ @Record(ExecutionTime.STATIC_INIT) @BuildStep + @Consume(ProfileBuildItem.class) KeycloakSessionFactoryPreInitBuildItem configureKeycloakSessionFactory(KeycloakRecorder recorder, List descriptors) { - Profile.setInstance(new QuarkusProfile()); Map, Map>>> factories = new HashMap<>(); Map, String> defaultProviders = new HashMap<>(); Map preConfiguredProviders = new HashMap<>(); diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/ProfileBuildItem.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/ProfileBuildItem.java new file mode 100644 index 0000000000..7b497608fb --- /dev/null +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/ProfileBuildItem.java @@ -0,0 +1,6 @@ +package org.keycloak.quarkus.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class ProfileBuildItem extends SimpleBuildItem { +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java index d5b08030d0..c7d887d902 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java @@ -64,6 +64,14 @@ public class KeycloakRecorder { public static final String DEFAULT_HEALTH_ENDPOINT = "/health"; public static final String DEFAULT_METRICS_ENDPOINT = "/metrics"; + public void initConfig() { + Config.init(new MicroProfileConfigProvider()); + } + + public void configureProfile(Profile.ProfileName profileName, Map features) { + Profile.init(profileName, features); + } + public void configureLiquibase(Map> services) { ServiceLocator locator = Scope.getCurrentScope().getServiceLocator(); if (locator instanceof FastServiceLocator) @@ -75,8 +83,6 @@ public class KeycloakRecorder { Map, String> defaultProviders, Map preConfiguredProviders, List themes, boolean reaugmented) { - Config.init(new MicroProfileConfigProvider()); - Profile.setInstance(new QuarkusProfile()); QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, themes, reaugmented)); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/QuarkusProfile.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/QuarkusProfile.java deleted file mode 100644 index a8e64a1b18..0000000000 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/QuarkusProfile.java +++ /dev/null @@ -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('-', '_'); - } - } -} diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/QuarkusProfileConfigResolver.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/QuarkusProfileConfigResolver.java new file mode 100644 index 0000000000..f1319577a1 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/QuarkusProfileConfigResolver.java @@ -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)); + } + +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/FeaturePropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/FeaturePropertyMappers.java index c50087be10..5b9cd8a2bd 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/FeaturePropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/FeaturePropertyMappers.java @@ -2,9 +2,7 @@ package org.keycloak.quarkus.runtime.configuration.mappers; import org.keycloak.common.Profile; import org.keycloak.config.FeatureOptions; -import org.keycloak.config.StorageOptions; import org.keycloak.quarkus.runtime.configuration.Configuration; -import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import static java.util.Optional.of; import static org.keycloak.config.StorageOptions.STORAGE; @@ -42,7 +40,7 @@ final class FeaturePropertyMappers { Set 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)); } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FeaturesDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FeaturesDistTest.java index c3149a137b..260890995d 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FeaturesDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FeaturesDistTest.java @@ -52,10 +52,18 @@ public class FeaturesDistTest { @Test @Launch({StartDev.NAME, "--features=preview", "--features-disabled=token-exchange"}) + public void testPreviewFeatureDisabledInPreviewMode(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertStartedDevMode(); + assertFalse(cliResult.getOutput().contains("token-exchange")); + } + + @Test + @Launch({StartDev.NAME, "--features=token-exchange", "--features-disabled=token-exchange"}) public void testEnablePrecedenceOverDisable(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertStartedDevMode(); - assertPreviewFeaturesEnabled((CLIResult) result); + assertThat(cliResult.getOutput(), containsString("Preview features enabled: token-exchange")); } @Test @@ -65,8 +73,7 @@ public class FeaturesDistTest { CLIResult cliResult = (CLIResult) result; cliResult.assertStartedDevMode(); assertThat(cliResult.getOutput(), CoreMatchers.allOf( - containsString("Preview feature enabled: admin_fine_grained_authz"), - containsString("Preview feature enabled: token_exchange"))); + containsString("Preview features enabled: admin-fine-grained-authz, token-exchange"))); assertFalse(cliResult.getOutput().contains("declarative-user-profile")); } @@ -77,17 +84,12 @@ public class FeaturesDistTest { CLIResult cliResult = (CLIResult) result; cliResult.assertStartedDevMode(); assertThat(cliResult.getOutput(), CoreMatchers.allOf( - containsString("Preview feature enabled: admin_fine_grained_authz"), - containsString("Preview feature enabled: token_exchange"))); + containsString("Preview features enabled: admin-fine-grained-authz, token-exchange"))); assertFalse(cliResult.getOutput().contains("declarative-user-profile")); } private void assertPreviewFeaturesEnabled(CLIResult result) { assertThat(result.getOutput(), CoreMatchers.allOf( - containsString("Preview feature enabled: admin_fine_grained_authz"), - containsString("Preview feature enabled: openshift_integration"), - containsString("Preview feature enabled: scripts"), - containsString("Preview feature enabled: token_exchange"), - containsString("Preview feature enabled: declarative_user_profile"))); + containsString("Preview features enabled: admin-fine-grained-authz, client-secret-rotation, declarative-user-profile, openshift-integration, recovery-codes, scripts, token-exchange, update-email"))); } } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/ChmStorageDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/ChmStorageDistTest.java index 8a19b4b682..3739bc98fd 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/ChmStorageDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/ChmStorageDistTest.java @@ -48,7 +48,7 @@ public class ChmStorageDistTest { } private void assertExpectedMessages(CLIResult cliResult, RawDistRootPath distPath) { - cliResult.assertMessage("Experimental feature enabled: map_storage"); + cliResult.assertMessage("Experimental features enabled: map-storage"); cliResult.assertMessage("Hibernate ORM is disabled because no JPA entities were found"); Assert.assertFalse(distPath.getDistRootPath().resolve("data").resolve("h2").toFile().exists()); } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/HotRodStoreDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/HotRodStoreDistTest.java index 16e75f4a01..320ffe6fe4 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/HotRodStoreDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/HotRodStoreDistTest.java @@ -32,7 +32,7 @@ public class HotRodStoreDistTest { @Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" }) void testSuccessful(LaunchResult result) { CLIResult cliResult = (CLIResult) result; - cliResult.assertMessage("Experimental feature enabled: map_storage"); + cliResult.assertMessage("Experimental features enabled: map-storage"); cliResult.assertMessage("[org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory] (main) HotRod client configuration was successful."); cliResult.assertStarted(); } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/JPAStoreDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/JPAStoreDistTest.java index 274adc92a8..3cd1cde040 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/JPAStoreDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/map/JPAStoreDistTest.java @@ -33,7 +33,7 @@ public class JPAStoreDistTest { @Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" }) void testSuccessful(LaunchResult result) { CLIResult cliResult = (CLIResult) result; - cliResult.assertMessage("Experimental feature enabled: map_storage"); + cliResult.assertMessage("Experimental features enabled: map-storage"); cliResult.assertMessage("[org.keycloak.models.map.storage.jpa.liquibase.updater.MapJpaLiquibaseUpdaterProvider] (main) Initializing database schema. Using changelog META-INF/jpa-realms-changelog.xml"); cliResult.assertStarted(); } diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testBuildHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testBuildHelp.unix.approved.txt index 5b405bcb3a..cfefb3a758 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testBuildHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testBuildHelp.unix.approved.txt @@ -43,19 +43,19 @@ Transaction: Feature: ---features Enables a set of one or more features. Possible values are: authorization, - account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2, - docker, impersonation, openshift-integration, scripts, token-exchange, - web-authn, client-policies, ciba, map-storage, par, - declarative-user-profile, dynamic-scopes, client-secret-rotation, - step-up-authentication, recovery-codes, update-email, js-adapter, preview. +--features Enables a set of one or more features. Possible values are: account-api, + account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization, + ciba, client-policies, client-secret-rotation, declarative-user-profile, + docker, dynamic-scopes, impersonation, js-adapter, map-storage, + openshift-integration, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, update-email, web-authn. --features-disabled - Disables a set of one or more features. Possible values are: authorization, - account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2, - docker, impersonation, openshift-integration, scripts, token-exchange, - web-authn, client-policies, ciba, map-storage, par, - declarative-user-profile, dynamic-scopes, client-secret-rotation, - step-up-authentication, recovery-codes, update-email, js-adapter, preview. + Disables a set of one or more features. Possible values are: account-api, + account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization, + ciba, client-policies, client-secret-rotation, declarative-user-profile, + docker, dynamic-scopes, impersonation, js-adapter, map-storage, + openshift-integration, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, update-email, web-authn. HTTP/TLS: @@ -104,4 +104,4 @@ Examples: Change the relative path: - $ kc.sh build --http-relative-path=/auth \ No newline at end of file + $ kc.sh build --http-relative-path=/auth diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelp.unix.approved.txt index b3074df076..05243edaa6 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelp.unix.approved.txt @@ -66,19 +66,19 @@ Transaction: Feature: ---features Enables a set of one or more features. Possible values are: authorization, - account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2, - docker, impersonation, openshift-integration, scripts, token-exchange, - web-authn, client-policies, ciba, map-storage, par, - declarative-user-profile, dynamic-scopes, client-secret-rotation, - step-up-authentication, recovery-codes, update-email, js-adapter, preview. +--features Enables a set of one or more features. Possible values are: account-api, + account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization, + ciba, client-policies, client-secret-rotation, declarative-user-profile, + docker, dynamic-scopes, impersonation, js-adapter, map-storage, + openshift-integration, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, update-email, web-authn. --features-disabled - Disables a set of one or more features. Possible values are: authorization, - account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2, - docker, impersonation, openshift-integration, scripts, token-exchange, - web-authn, client-policies, ciba, map-storage, par, - declarative-user-profile, dynamic-scopes, client-secret-rotation, - step-up-authentication, recovery-codes, update-email, js-adapter, preview. + Disables a set of one or more features. Possible values are: account-api, + account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization, + ciba, client-policies, client-secret-rotation, declarative-user-profile, + docker, dynamic-scopes, impersonation, js-adapter, map-storage, + openshift-integration, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, update-email, web-authn. Hostname: @@ -221,4 +221,4 @@ Logging: 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 -options. \ No newline at end of file +options. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.unix.approved.txt index 2b35d521b8..5cf437cec3 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.unix.approved.txt @@ -124,19 +124,19 @@ Transaction: Feature: ---features Enables a set of one or more features. Possible values are: authorization, - account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2, - docker, impersonation, openshift-integration, scripts, token-exchange, - web-authn, client-policies, ciba, map-storage, par, - declarative-user-profile, dynamic-scopes, client-secret-rotation, - step-up-authentication, recovery-codes, update-email, js-adapter, preview. +--features Enables a set of one or more features. Possible values are: account-api, + account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization, + ciba, client-policies, client-secret-rotation, declarative-user-profile, + docker, dynamic-scopes, impersonation, js-adapter, map-storage, + openshift-integration, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, update-email, web-authn. --features-disabled - Disables a set of one or more features. Possible values are: authorization, - account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2, - docker, impersonation, openshift-integration, scripts, token-exchange, - web-authn, client-policies, ciba, map-storage, par, - declarative-user-profile, dynamic-scopes, client-secret-rotation, - step-up-authentication, recovery-codes, update-email, js-adapter, preview. + Disables a set of one or more features. Possible values are: account-api, + account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization, + ciba, client-policies, client-secret-rotation, declarative-user-profile, + docker, dynamic-scopes, impersonation, js-adapter, map-storage, + openshift-integration, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, update-email, web-authn. Hostname: @@ -285,4 +285,4 @@ Security (Experimental): 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 -options. \ No newline at end of file +options. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelp.unix.approved.txt index 9c44c129b5..c49e939ee2 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelp.unix.approved.txt @@ -72,19 +72,19 @@ Transaction: Feature: ---features Enables a set of one or more features. Possible values are: authorization, - account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2, - docker, impersonation, openshift-integration, scripts, token-exchange, - web-authn, client-policies, ciba, map-storage, par, - declarative-user-profile, dynamic-scopes, client-secret-rotation, - step-up-authentication, recovery-codes, update-email, js-adapter, preview. +--features Enables a set of one or more features. Possible values are: account-api, + account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization, + ciba, client-policies, client-secret-rotation, declarative-user-profile, + docker, dynamic-scopes, impersonation, js-adapter, map-storage, + openshift-integration, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, update-email, web-authn. --features-disabled - Disables a set of one or more features. Possible values are: authorization, - account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2, - docker, impersonation, openshift-integration, scripts, token-exchange, - web-authn, client-policies, ciba, map-storage, par, - declarative-user-profile, dynamic-scopes, client-secret-rotation, - step-up-authentication, recovery-codes, update-email, js-adapter, preview. + Disables a set of one or more features. Possible values are: account-api, + account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization, + ciba, client-policies, client-secret-rotation, declarative-user-profile, + docker, dynamic-scopes, impersonation, js-adapter, map-storage, + openshift-integration, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, update-email, web-authn. Hostname: @@ -231,4 +231,4 @@ By default, this command tries to update the server configuration by running a $ kc.sh start '--optimized' By doing that, the server should start faster based on any previous -configuration you have set when manually running the 'build' command. \ No newline at end of file +configuration you have set when manually running the 'build' command. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.unix.approved.txt index 98ccbd8c43..9d4f3c6f5b 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.unix.approved.txt @@ -130,19 +130,19 @@ Transaction: Feature: ---features Enables a set of one or more features. Possible values are: authorization, - account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2, - docker, impersonation, openshift-integration, scripts, token-exchange, - web-authn, client-policies, ciba, map-storage, par, - declarative-user-profile, dynamic-scopes, client-secret-rotation, - step-up-authentication, recovery-codes, update-email, js-adapter, preview. +--features Enables a set of one or more features. Possible values are: account-api, + account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization, + ciba, client-policies, client-secret-rotation, declarative-user-profile, + docker, dynamic-scopes, impersonation, js-adapter, map-storage, + openshift-integration, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, update-email, web-authn. --features-disabled - Disables a set of one or more features. Possible values are: authorization, - account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2, - docker, impersonation, openshift-integration, scripts, token-exchange, - web-authn, client-policies, ciba, map-storage, par, - declarative-user-profile, dynamic-scopes, client-secret-rotation, - step-up-authentication, recovery-codes, update-email, js-adapter, preview. + Disables a set of one or more features. Possible values are: account-api, + account2, admin, admin-api, admin-fine-grained-authz, admin2, authorization, + ciba, client-policies, client-secret-rotation, declarative-user-profile, + docker, dynamic-scopes, impersonation, js-adapter, map-storage, + openshift-integration, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, update-email, web-authn. Hostname: @@ -295,4 +295,4 @@ By default, this command tries to update the server configuration by running a $ kc.sh start '--optimized' By doing that, the server should start faster based on any previous -configuration you have set when manually running the 'build' command. \ No newline at end of file +configuration you have set when manually running the 'build' command. diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java index 23a959e552..0adc9e623a 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java @@ -110,6 +110,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { @Override public void init(Config.Scope config) { + initBuiltIns(); this.providerConfig = new OIDCProviderConfig(config); if (providerConfig.isLegacyLogoutRedirectUri()) { logger.warnf("Deprecated switch '%s' is enabled. Please try to disable it and update your clients to use OpenID Connect compliant way for RP-initiated logout.", CONFIG_LEGACY_LOGOUT_REDIRECT_URI); @@ -129,9 +130,9 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { return builtins; } - static Map builtins = new HashMap<>(); + private Map builtins = new HashMap<>(); - static { + void initBuiltIns() { ProtocolMapperModel model; model = UserPropertyMapper.createClaimMapper(USERNAME, "username", @@ -218,7 +219,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { } } - private static void createUserAttributeMapper(String name, String attrName, String claimName, String type) { + private void createUserAttributeMapper(String name, String attrName, String claimName, String type) { ProtocolMapperModel model = UserAttributeMapper.createClaimMapper(name, attrName, claimName, type, @@ -297,7 +298,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { } - public static ClientScopeModel addRolesClientScope(RealmModel newRealm) { + public ClientScopeModel addRolesClientScope(RealmModel newRealm) { ClientScopeModel rolesScope = KeycloakModelUtils.getClientScopeByName(newRealm, ROLES_SCOPE); if (rolesScope == null) { rolesScope = newRealm.addClientScope(ROLES_SCOPE); @@ -320,7 +321,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { } - public static ClientScopeModel addWebOriginsClientScope(RealmModel newRealm) { + public ClientScopeModel addWebOriginsClientScope(RealmModel newRealm) { ClientScopeModel originsScope = KeycloakModelUtils.getClientScopeByName(newRealm, WEB_ORIGINS_SCOPE); if (originsScope == null) { originsScope = newRealm.addClientScope(WEB_ORIGINS_SCOPE); @@ -347,7 +348,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { * @param newRealm the realm to which the {@code microprofile-jwt} scope is to be added. * @return a reference to the {@code microprofile-jwt} client scope that was either created or already exists in the realm. */ - public static ClientScopeModel addMicroprofileJWTClientScope(RealmModel newRealm) { + public ClientScopeModel addMicroprofileJWTClientScope(RealmModel newRealm) { ClientScopeModel microprofileScope = KeycloakModelUtils.getClientScopeByName(newRealm, MICROPROFILE_JWT_SCOPE); if (microprofileScope == null) { microprofileScope = newRealm.addClientScope(MICROPROFILE_JWT_SCOPE); @@ -366,7 +367,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { } - public static void addAcrClientScope(RealmModel newRealm) { + public void addAcrClientScope(RealmModel newRealm) { if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { ClientScopeModel acrScope = KeycloakModelUtils.getClientScopeByName(newRealm, ACR_SCOPE); if (acrScope == null) { diff --git a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java index f16afcf4af..74a8d5b0b2 100755 --- a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java +++ b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java @@ -23,17 +23,15 @@ import org.keycloak.models.ClientScopeModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocolFactory; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; -import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.services.managers.RealmManager; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -84,26 +82,29 @@ public class DefaultMigrationProvider implements MigrationProvider { new RealmManager(session).setupAdminCli(realm); } + private OIDCLoginProtocolFactory getOIDCLoginProtocolFactory() { + return (OIDCLoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, OIDCLoginProtocol.LOGIN_PROTOCOL); + } @Override public ClientScopeModel addOIDCRolesClientScope(RealmModel realm) { - return OIDCLoginProtocolFactory.addRolesClientScope(realm); + return getOIDCLoginProtocolFactory().addRolesClientScope(realm); } @Override public ClientScopeModel addOIDCWebOriginsClientScope(RealmModel realm) { - return OIDCLoginProtocolFactory.addWebOriginsClientScope(realm); + return getOIDCLoginProtocolFactory().addWebOriginsClientScope(realm); } @Override public ClientScopeModel addOIDCMicroprofileJWTClientScope(RealmModel realm) { - return OIDCLoginProtocolFactory.addMicroprofileJWTClientScope(realm); + return getOIDCLoginProtocolFactory().addMicroprofileJWTClientScope(realm); } @Override public void addOIDCAcrClientScope(RealmModel realm) { - OIDCLoginProtocolFactory.addAcrClientScope(realm); + getOIDCLoginProtocolFactory().addAcrClientScope(realm); } @Override diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java index f586674f16..36fdf2afda 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java @@ -112,6 +112,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.UUID; @@ -890,89 +891,66 @@ public class TestingResourceProvider implements RealmResourceProvider { } } + @GET + @Path("/list-disabled-features") + @Produces(MediaType.APPLICATION_JSON) + public Set listDisabledFeatures() { + return Profile.getInstance().getDisabledFeatures(); + } + @POST @Path("/enable-feature/{feature}") @Consumes(MediaType.APPLICATION_JSON) - public Response enableFeature(@PathParam("feature") String feature) { - Profile.Feature featureProfile; - - try { - featureProfile = Profile.Feature.valueOf(feature); - } catch (IllegalArgumentException e) { - System.err.printf("Feature '%s' doesn't exist!!\n", feature); - return Response.status(Response.Status.NOT_FOUND).build(); - } - - if (Profile.isFeatureEnabled(featureProfile)) - return Response.noContent().build(); - - FeatureDeployerUtil.initBeforeChangeFeature(featureProfile); - - System.setProperty("keycloak.profile.feature." + featureProfile.toString().toLowerCase(), "enabled"); - - String jbossServerConfigDir = System.getProperty("jboss.server.config.dir"); - // If we are in jboss-based container, we need to write profile.properties file, otherwise the change in system property will disappear after restart - if (jbossServerConfigDir != null) { - setFeatureInProfileFile(new File(jbossServerConfigDir, "profile.properties"), featureProfile, "enabled"); - } - - Profile.init(); - - FeatureDeployerUtil.deployFactoriesAfterFeatureEnabled(featureProfile); - - if (Profile.isFeatureEnabled(featureProfile)) - return Response.noContent().build(); - else - return Response.status(Response.Status.NOT_FOUND).build(); + @Produces(MediaType.APPLICATION_JSON) + public Set enableFeature(@PathParam("feature") String feature) { + return updateFeature(feature, true); } @POST @Path("/disable-feature/{feature}") @Consumes(MediaType.APPLICATION_JSON) - public Response disableFeature(@PathParam("feature") String feature) { - Profile.Feature featureProfile; + @Produces(MediaType.APPLICATION_JSON) + public Set disableFeature(@PathParam("feature") String feature) { + return updateFeature(feature, false); + } + + private Set updateFeature(String featureKey, boolean shouldEnable) { + Profile.Feature feature; try { - featureProfile = Profile.Feature.valueOf(feature); + feature = Profile.Feature.valueOf(featureKey); } catch (IllegalArgumentException e) { - System.err.printf("Feature '%s' doesn't exist!!\n", feature); - return Response.status(Response.Status.NOT_FOUND).build(); + System.err.printf("Feature '%s' doesn't exist!!\n", featureKey); + throw new BadRequestException(); } - if (!Profile.isFeatureEnabled(featureProfile)) - return Response.noContent().build(); + if (Profile.getInstance().getFeatures().get(feature) != shouldEnable) { + FeatureDeployerUtil.initBeforeChangeFeature(feature); - FeatureDeployerUtil.initBeforeChangeFeature(featureProfile); + String jbossServerConfigDir = System.getProperty("jboss.server.config.dir"); + // If we are in jboss-based container, we need to write profile.properties file, otherwise the change in system property will disappear after restart + if (jbossServerConfigDir != null) { + setFeatureInProfileFile(new File(jbossServerConfigDir, "profile.properties"), feature, shouldEnable ? "enabled" : "disabled"); + } - disableFeatureProperties(featureProfile); + Profile current = Profile.getInstance(); - String jbossServerConfigDir = System.getProperty("jboss.server.config.dir"); - // If we are in jboss-based container, we need to write profile.properties file, otherwise the change in system property will disappear after restart - if (jbossServerConfigDir != null) { - setFeatureInProfileFile(new File(jbossServerConfigDir, "profile.properties"), featureProfile, "disabled"); + Map updatedFeatures = new HashMap<>(); + updatedFeatures.putAll(current.getFeatures()); + updatedFeatures.put(feature, shouldEnable); + + Profile.init(current.getName(), updatedFeatures); + + if (shouldEnable) { + FeatureDeployerUtil.deployFactoriesAfterFeatureEnabled(feature); + } else { + FeatureDeployerUtil.undeployFactoriesAfterFeatureDisabled(feature); + } } - Profile.init(); - - FeatureDeployerUtil.undeployFactoriesAfterFeatureDisabled(featureProfile); - - if (!Profile.isFeatureEnabled(featureProfile)) - return Response.noContent().build(); - else - return Response.status(Response.Status.NOT_FOUND).build(); + return Profile.getInstance().getDisabledFeatures(); } - /** - * KEYCLOAK-12958 - */ - private void disableFeatureProperties(Profile.Feature feature) { - Profile.Type type = feature.getType(); - if (type.equals(Profile.Type.DEFAULT)) { - System.setProperty("keycloak.profile.feature." + feature.toString().toLowerCase(), "disabled"); - } else { - System.getProperties().remove("keycloak.profile.feature." + feature.toString().toLowerCase()); - } - } @GET @Path("/set-system-property") diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java index 5aad6bd10b..f5a184189a 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java @@ -18,14 +18,9 @@ package org.keycloak.testsuite; import org.junit.Assume; -import org.keycloak.admin.client.Keycloak; import org.keycloak.common.Profile; -import org.keycloak.representations.info.ProfileInfoRepresentation; -import org.keycloak.testsuite.util.AdminClientUtil; +import org.keycloak.testsuite.arquillian.TestContext; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.Set; /** @@ -33,51 +28,34 @@ import java.util.Set; */ public class ProfileAssume { - private static Set disabledFeatures; - private static String profile; + private static Set DISABLED_FEATURES; + private static TestContext TEST_CONTEXT; private static void updateProfile() { - String host = System.getProperty("auth.server.host", "localhost"); - String port = System.getProperty("auth.server.http.port", "8180"); - boolean adapterCompatTesting = Boolean.parseBoolean(System.getProperty("testsuite.adapter.compat.testing")); - - String authServerContextRoot = "http://" + host + ":" + port; - try (Keycloak adminClient = AdminClientUtil.createAdminClient(adapterCompatTesting, authServerContextRoot)) { - ProfileInfoRepresentation profileInfo = adminClient.serverInfo().getInfo().getProfileInfo(); - profile = profileInfo.getName(); - List disabled = profileInfo.getDisabledFeatures(); - disabledFeatures = Collections.unmodifiableSet(new HashSet<>(disabled)); - } catch (Exception e) { - throw new RuntimeException("Failed to obtain profile / features info from serverinfo endpoint of " + authServerContextRoot, e); + if (DISABLED_FEATURES == null) { + DISABLED_FEATURES = TEST_CONTEXT.getTestingClient().testing().listDisabledFeatures(); } } public static void assumeFeatureEnabled(Profile.Feature feature) { updateProfile(); - Assume.assumeTrue("Ignoring test as feature " + feature.name() + " is not enabled", isFeatureEnabled(feature)); + Assume.assumeTrue("Ignoring test as feature " + feature.getKey() + " is not enabled", isFeatureEnabled(feature)); } public static void assumeFeatureDisabled(Profile.Feature feature) { - Assume.assumeTrue("Ignoring test as feature " + feature.name() + " is enabled", !isFeatureEnabled(feature)); - } - - public static void assumePreview() { - updateProfile(); - Assume.assumeTrue("Ignoring test as community/preview profile is not enabled", !profile.equals("product")); - } - - public static void assumePreviewDisabled() { - updateProfile(); - Assume.assumeFalse("Ignoring test as community/preview profile is enabled", !profile.equals("product")); - } - - public static void assumeCommunity() { - updateProfile(); - Assume.assumeTrue("Ignoring test as community profile is not enabled", profile.equals("community")); + Assume.assumeTrue("Ignoring test as feature " + feature.getKey() + " is enabled", !isFeatureEnabled(feature)); } public static boolean isFeatureEnabled(Profile.Feature feature) { updateProfile(); - return !disabledFeatures.contains(feature.name()); + return !DISABLED_FEATURES.contains(feature); + } + + public static void updateDisabledFeatures(Set disabledFeatures) { + DISABLED_FEATURES = disabledFeatures; + } + + public static void setTestContext(TestContext testContext) { + TEST_CONTEXT = testContext; } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java index 97e7131670..572002beb7 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java @@ -43,6 +43,7 @@ import org.keycloak.admin.client.Keycloak; import org.keycloak.common.util.StringPropertyReplacer; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.error.KeycloakErrorHandler; +import org.keycloak.testsuite.ProfileAssume; import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider; import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected; import org.keycloak.testsuite.arquillian.annotation.EnableVault; @@ -462,6 +463,7 @@ public class AuthServerTestEnricher { public void initializeTestContext(@Observes(precedence = 2) BeforeClass event) throws Exception { TestContext testContext = new TestContext(suiteContext, event.getTestClass().getJavaClass()); testContextProducer.set(testContext); + ProfileAssume.setTestContext(testContext); boolean wasUpdated = false; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ModelTestExecutor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ModelTestExecutor.java index 68766f503b..4c73d9f49c 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ModelTestExecutor.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ModelTestExecutor.java @@ -28,6 +28,7 @@ import org.jboss.arquillian.core.api.annotation.Inject; import org.jboss.arquillian.test.spi.TestResult; import org.keycloak.common.Profile; import org.keycloak.common.util.reflections.Reflections; +import org.keycloak.testsuite.ProfileAssume; import org.keycloak.testsuite.arquillian.annotation.ModelTest; import org.keycloak.testsuite.client.KeycloakTestingClient; @@ -50,7 +51,7 @@ public class ModelTestExecutor extends LocalTestExecuter { super.execute(event); } else { TestResult result = new TestResult(); - if (annotation.skipForMapStorage() && Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE)) { + if (annotation.skipForMapStorage() && ProfileAssume.isFeatureEnabled(Profile.Feature.MAP_STORAGE)) { result = TestResult.skipped(); } else { diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/DisableFeature.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/DisableFeature.java index 9088ac87fb..41ae3a6a6f 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/DisableFeature.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/DisableFeature.java @@ -27,11 +27,6 @@ public @interface DisableFeature { */ boolean skipRestart() default false; - /** - * The feature will be disabled only if the `product` profile is activated - */ - boolean onlyForProduct() default false; - /** * Feature disable should be the last action in @Before context. * If the test halted, the feature is returned to the previous state. diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/EnableFeature.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/EnableFeature.java index fd587025da..ebd54161b7 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/EnableFeature.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/EnableFeature.java @@ -27,11 +27,6 @@ public @interface EnableFeature { */ boolean skipRestart() default false; - /** - * The feature will be enabled only if the `product` profile is activated - */ - boolean onlyForProduct() default false; - /** * Feature enable should be the last action in @Before context. * If the test halted, the feature is returned to the previous state. diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakContainerFeaturesController.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakContainerFeaturesController.java index 0427cdc304..3c3c43ac1c 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakContainerFeaturesController.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakContainerFeaturesController.java @@ -71,21 +71,18 @@ public class KeycloakContainerFeaturesController { private Profile.Feature feature; private boolean skipRestart; private FeatureAction action; - private boolean onlyForProduct; private final AnnotatedElement annotatedElement; - public UpdateFeature(Profile.Feature feature, boolean skipRestart, FeatureAction action, boolean onlyForProduct - , AnnotatedElement annotatedElement) { + public UpdateFeature(Profile.Feature feature, boolean skipRestart, FeatureAction action, AnnotatedElement annotatedElement) { this.feature = feature; this.skipRestart = skipRestart; this.action = action; - this.onlyForProduct = onlyForProduct; this.annotatedElement = annotatedElement; } private void assertPerformed() { assertThat("An annotation requested to " + action.name() + - " feature " + feature.name() + ", however after performing this operation " + + " feature " + feature.getKey() + ", however after performing this operation " + "the feature is not in desired state" , ProfileAssume.isFeatureEnabled(feature), is(action == FeatureAction.ENABLE)); @@ -122,10 +119,6 @@ public class KeycloakContainerFeaturesController { return action; } - public boolean isOnlyForProduct() { - return onlyForProduct; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -147,7 +140,6 @@ public class KeycloakContainerFeaturesController { private void updateFeatures(Set updateFeatures) throws Exception { updateFeatures = updateFeatures.stream() - .filter(this::skipForProduct) .collect(Collectors.toSet()); updateFeatures.forEach(UpdateFeature::performAction); @@ -160,11 +152,6 @@ public class KeycloakContainerFeaturesController { updateFeatures.forEach(UpdateFeature::assertPerformed); } - // KEYCLOAK-12958 WebAuthn profile product/project - private boolean skipForProduct(UpdateFeature feature) { - return !feature.onlyForProduct || Profile.getName().equals("product"); - } - private void checkAnnotatedElementForFeatureAnnotations(AnnotatedElement annotatedElement, State state) throws Exception { Set updateFeatureSet = new HashSet<>(); @@ -191,13 +178,12 @@ public class KeycloakContainerFeaturesController { ret.addAll(Arrays.stream(annotatedElement.getAnnotationsByType(EnableFeature.class)) .map(annotation -> new UpdateFeature(annotation.value(), annotation.skipRestart(), - state == State.BEFORE ? FeatureAction.ENABLE : FeatureAction.DISABLE, annotation.onlyForProduct(), annotatedElement)) + state == State.BEFORE ? FeatureAction.ENABLE : FeatureAction.DISABLE, annotatedElement)) .collect(Collectors.toSet())); ret.addAll(Arrays.stream(annotatedElement.getAnnotationsByType(DisableFeature.class)) .map(annotation -> new UpdateFeature(annotation.value(), annotation.skipRestart(), - state == State.BEFORE ? FeatureAction.DISABLE : FeatureAction.ENABLE, annotation.onlyForProduct(), - annotatedElement)) + state == State.BEFORE ? FeatureAction.DISABLE : FeatureAction.ENABLE, annotatedElement)) .collect(Collectors.toSet())); return ret; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java index a4f998919d..cb9194839e 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java @@ -22,7 +22,9 @@ import javax.ws.rs.core.Response; import org.jboss.resteasy.client.jaxrs.ResteasyClient; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget; +import org.junit.Assert; import org.keycloak.common.Profile; +import org.keycloak.testsuite.ProfileAssume; import org.keycloak.testsuite.client.resources.TestApplicationResource; import org.keycloak.testsuite.client.resources.TestExampleCompanyResource; import org.keycloak.testsuite.client.resources.TestSamlApplicationResource; @@ -31,6 +33,9 @@ import org.keycloak.testsuite.runonserver.*; import org.keycloak.testsuite.util.AdminClientUtil; import org.keycloak.util.JsonSerialization; +import java.util.List; +import java.util.Set; + import static org.junit.Assert.assertEquals; /** @@ -74,15 +79,15 @@ public class KeycloakTestingClient implements AutoCloseable { } public void enableFeature(Profile.Feature feature) { - try (Response response = testing().enableFeature(feature.toString())) { - assertEquals(204, response.getStatus()); - } + Set disabledFeatures = testing().enableFeature(feature.toString()); + Assert.assertFalse(disabledFeatures.contains(feature)); + ProfileAssume.updateDisabledFeatures(disabledFeatures); } public void disableFeature(Profile.Feature feature) { - try (Response response = testing().disableFeature(feature.toString())) { - assertEquals(204, response.getStatus()); - } + Set disabledFeatures = testing().disableFeature(feature.toString()); + Assert.assertTrue(disabledFeatures.contains(feature)); + ProfileAssume.updateDisabledFeatures(disabledFeatures); } public TestApplicationResource testApp() { return target.proxy(TestApplicationResource.class); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java index 75841a5ec1..08ff3c5f66 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java @@ -18,6 +18,7 @@ package org.keycloak.testsuite.client.resources; import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.common.Profile; import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.EventRepresentation; @@ -38,6 +39,8 @@ import javax.ws.rs.core.Response; import java.util.List; import java.util.Map; +import java.util.Set; + import org.infinispan.commons.time.TimeService; /** @@ -332,15 +335,22 @@ public interface TestingResource { @Produces(MediaType.TEXT_HTML_UTF_8) String getJavascriptTestingEnvironment(); + @GET + @Path("/list-disabled-features") + @Produces(MediaType.APPLICATION_JSON) + Set listDisabledFeatures(); + @POST @Path("/enable-feature/{feature}") @Consumes(MediaType.APPLICATION_JSON) - Response enableFeature(@PathParam("feature") String feature); + @Produces(MediaType.APPLICATION_JSON) + Set enableFeature(@PathParam("feature") String feature); @POST @Path("/disable-feature/{feature}") @Consumes(MediaType.APPLICATION_JSON) - Response disableFeature(@PathParam("feature") String feature); + @Produces(MediaType.APPLICATION_JSON) + Set disableFeature(@PathParam("feature") String feature); /** * If property-value is null, the system property will be unset (removed) on the server diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java index fd41baa9cd..98baf0f13c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java @@ -879,8 +879,6 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest { @Test public void testOIDCUiLocalesParamForwarding() { - ProfileAssume.assumeCommunity(); - RealmRepresentation demoRealmRep = testRealmResource().toRepresentation(); boolean enabled = demoRealmRep.isInternationalizationEnabled(); String defaultLocale = demoRealmRep.getDefaultLocale(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java index f064b3c243..f6459d05c8 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java @@ -23,6 +23,7 @@ import org.keycloak.common.Profile; import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation; import org.keycloak.representations.idm.ConfigPropertyRepresentation; import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.ProfileAssume; import javax.ws.rs.NotFoundException; import java.util.ArrayList; @@ -141,7 +142,7 @@ public class ProvidersTest extends AbstractAuthenticationTest { "Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions."); addProviderInfo(result, "auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server."); addProviderInfo(result, "auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form."); - if (Profile.isFeatureEnabled(Profile.Feature.SCRIPTS)) { + if (ProfileAssume.isFeatureEnabled(Profile.Feature.SCRIPTS)) { addProviderInfo(result, "auth-script-based", "Script", "Script based authentication. Allows to define custom authentication logic via JavaScript."); } addProviderInfo(result, "auth-spnego", "Kerberos", "Initiates the SPNEGO protocol. Most often used with Kerberos."); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ScriptBasedAuthenticatorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ScriptBasedAuthenticatorTest.java deleted file mode 100644 index 3466928a90..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ScriptBasedAuthenticatorTest.java +++ /dev/null @@ -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 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 - } - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java index 9db51feb27..60fc23eeac 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java @@ -604,10 +604,8 @@ public class SocialLoginTest extends AbstractKeycloakTest { String username = users.get(0).getUsername(); checkFeature(501, username); - Response tokenResp = testingClient.testing().enableFeature(Profile.Feature.TOKEN_EXCHANGE.toString()); - assertEquals(200, tokenResp.getStatus()); + testingClient.enableFeature(Profile.Feature.TOKEN_EXCHANGE); - ProfileAssume.assumeFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE); Client httpClient = AdminClientUtil.createResteasyClient(); try { @@ -690,8 +688,7 @@ public class SocialLoginTest extends AbstractKeycloakTest { adminClient.realm(REALM).identityProviders().get(idp.getAlias()).update(idp); } finally { httpClient.close(); - tokenResp = testingClient.testing().disableFeature(Profile.Feature.TOKEN_EXCHANGE.toString()); - assertEquals(200, tokenResp.getStatus()); + testingClient.disableFeature(Profile.Feature.TOKEN_EXCHANGE); checkFeature(501, username); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java index 73ef39a64a..d74efb3ffd 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java @@ -16,6 +16,7 @@ */ package org.keycloak.testsuite.crossdc; +import org.junit.Ignore; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.common.Profile; @@ -86,6 +87,10 @@ public class ActionTokenCrossDCTest extends AbstractAdminCrossDCTest { @Test @InitialDcState(authServers = ServerSetup.ALL_NODES_IN_FIRST_DC_FIRST_NODE_IN_SECOND_DC) + // KEYCLOAK-17584: Temporarily disable the test for 'community' profile till KEYCLOAK-17628 isn't fixed. In other words till: + // * The test is either rewritten to start using the new Wildfly subsystem for base metrics introduced in Wildfly 22, + // * Or Keycloak is able to load the Eclipse MicroProfile Metrics subsystem from the microprofile Galleon feature-pack + @Ignore public void sendResetPasswordEmailSuccessWorksInCrossDc( @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc0Node0Statistics, @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=1, cacheName=InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc0Node1Statistics, @@ -93,11 +98,6 @@ public class ActionTokenCrossDCTest extends AbstractAdminCrossDCTest { @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { log.debug("--DC: START sendResetPasswordEmailSuccessWorksInCrossDc"); - // KEYCLOAK-17584: Temporarily disable the test for 'community' profile till KEYCLOAK-17628 isn't fixed. In other words till: - // * The test is either rewritten to start using the new Wildfly subsystem for base metrics introduced in Wildfly 22, - // * Or Keycloak is able to load the Eclipse MicroProfile Metrics subsystem from the microprofile Galleon feature-pack - Assume.assumeTrue("Ignoring test as product profile is not enabled", Profile.getName().equals("product")); - cacheDc0Node1Statistics.waitToBecomeAvailable(10, TimeUnit.SECONDS); Comparable originalNumberOfEntries = cacheDc0Node0Statistics.getSingleStatistics(Constants.STAT_CACHE_NUMBER_OF_ENTRIES_IN_MEMORY); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LevelOfAssuranceFlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LevelOfAssuranceFlowTest.java index 3390363f75..eb50142456 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LevelOfAssuranceFlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LevelOfAssuranceFlowTest.java @@ -717,4 +717,4 @@ public class LevelOfAssuranceFlowTest extends AbstractTestRealmKeycloakTest { Assert.assertEquals(expectedError, errorPage.getError()); events.clear(); } -} \ No newline at end of file +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/AccountPageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/AccountPageTest.java index 0e649fde6e..d7cbc38296 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/AccountPageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/AccountPageTest.java @@ -60,8 +60,6 @@ public class AccountPageTest extends AbstractI18NTest { @Test public void testLocalizedReferrerLinkContent() { - ProfileAssume.assumeCommunity(); - RealmResource testRealm = testRealm(); List foundClients = testRealm.clients().findByClientId("var-named-test-app"); if (foundClients.isEmpty()) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java index 51a32b7a26..e65da22483 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java @@ -97,8 +97,6 @@ public class EmailTest extends AbstractI18NTest { @Test public void restPasswordEmailGerman() throws IOException, MessagingException { - ProfileAssume.assumeCommunity(); - changeUserLocale("de"); loginPage.open(); @@ -118,8 +116,6 @@ public class EmailTest extends AbstractI18NTest { //KEYCLOAK-7478 @Test public void changeLocaleOnInfoPage() throws InterruptedException, IOException, MessagingException { - ProfileAssume.assumeCommunity(); - UserResource testUser = ApiUtil.findUserByUsernameId(testRealm(), "login-test"); testUser.executeActionsEmail(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString())); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java index 591575db99..767940db7f 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java @@ -88,8 +88,6 @@ public class LoginPageTest extends AbstractI18NTest { @Test public void languageDropdown() { - ProfileAssume.assumeCommunity(); - loginPage.open(); Assert.assertEquals("English", loginPage.getLanguageDropdownText()); @@ -123,8 +121,6 @@ public class LoginPageTest extends AbstractI18NTest { @Test public void acceptLanguageHeader() throws IOException { - ProfileAssume.assumeCommunity(); - try(CloseableHttpClient httpClient = (CloseableHttpClient) new HttpClientBuilder().build()) { ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient); ResteasyClient client = ((ResteasyClientBuilder) ResteasyClientBuilder.newBuilder()).httpEngine(engine).build(); @@ -156,8 +152,6 @@ public class LoginPageTest extends AbstractI18NTest { // KEYCLOAK-3887 @Test public void languageChangeRequiredActions() { - ProfileAssume.assumeCommunity(); - UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost"); UserRepresentation userRep = user.toRepresentation(); userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString())); @@ -183,8 +177,6 @@ public class LoginPageTest extends AbstractI18NTest { // KEYCLOAK-3887 @Test public void languageChangeConsentScreen() { - ProfileAssume.assumeCommunity(); - // Set client, which requires consent oauth.clientId("third-party"); @@ -210,8 +202,6 @@ public class LoginPageTest extends AbstractI18NTest { @Test public void languageUserUpdates() { - ProfileAssume.assumeCommunity(); - loginPage.open(); loginPage.openLanguage("Deutsch"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java index f909e10664..57f78a86ce 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java @@ -516,8 +516,6 @@ public class X509BrowserLoginTest extends AbstractX509AuthenticationTest { // KEYCLOAK-6866 @Test public void changeLocaleOnX509InfoPage() { - ProfileAssume.assumeCommunity(); - AuthenticatorConfigRepresentation cfg = newConfig("x509-browser-config", createLoginSubjectEmail2UsernameOrEmailConfig().getConfig()); String cfgId = createConfig(browserExecution.getId(), cfg); Assert.assertNotNull(cfgId); diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/KeycloakModelTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/KeycloakModelTest.java index c2e3224523..e2f4b8fe4e 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/KeycloakModelTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/KeycloakModelTest.java @@ -24,6 +24,8 @@ import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.policy.provider.PolicySpi; import org.keycloak.authorization.store.StoreFactorySpi; import org.keycloak.cluster.ClusterSpi; +import org.keycloak.common.Profile; +import org.keycloak.common.profile.PropertiesProfileConfigResolver; import org.keycloak.common.util.Time; import org.keycloak.component.ComponentFactoryProviderFactory; import org.keycloak.component.ComponentFactorySpi; @@ -60,6 +62,7 @@ import java.lang.management.LockInfo; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -290,6 +293,13 @@ public abstract class KeycloakModelTest { LOG.debugf("Creating factory %d in %s using the following configuration:\n %s", factoryIndex, threadName, CONFIG); DefaultKeycloakSessionFactory res = new DefaultKeycloakSessionFactory() { + + @Override + public void init() { + Profile.configure(new PropertiesProfileConfigResolver(System.getProperties())); + super.init(); + } + @Override protected boolean isEnabled(ProviderFactory factory, Scope scope) { return super.isEnabled(factory, scope) && isFactoryAllowed(factory); diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/TestPlatform.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/TestPlatform.java index 544b13f853..81d034f5fc 100644 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/TestPlatform.java +++ b/testsuite/utils/src/main/java/org/keycloak/testsuite/TestPlatform.java @@ -23,6 +23,9 @@ import java.nio.file.Files; import org.jboss.logging.Logger; import org.keycloak.Config; +import org.keycloak.common.Profile; +import org.keycloak.common.profile.PropertiesFileProfileConfigResolver; +import org.keycloak.common.profile.PropertiesProfileConfigResolver; import org.keycloak.platform.PlatformProvider; public class TestPlatform implements PlatformProvider { @@ -31,6 +34,13 @@ public class TestPlatform implements PlatformProvider { private File tmpDir; + public TestPlatform() { + Profile.configure( + new PropertiesProfileConfigResolver(System.getProperties()), + new PropertiesFileProfileConfigResolver() + ); + } + @Override public void onStartup(Runnable startupHook) { startupHook.run();