Common library and dependencies shared with server and all adapters for the Keycloak identity and access management system
—
This document covers the profile management functionality in the org.keycloak.common.profile package that provides advanced feature flag management, configuration resolvers, and profile customization capabilities.
The ProfileConfigResolver interface defines the contract for resolving profile configurations from various sources.
public interface ProfileConfigResolver {
/**
* Gets the profile name to use
*/
Profile.ProfileName getProfileName();
/**
* Gets the configuration for a specific feature
*/
FeatureConfig getFeatureConfig(String feature);
}public enum FeatureConfig {
/**
* Feature is explicitly enabled
*/
ENABLED,
/**
* Feature is explicitly disabled
*/
DISABLED,
/**
* Feature configuration not specified, use default
*/
UNCONFIGURED
}The PropertiesProfileConfigResolver class resolves profile configuration from Java properties.
public class PropertiesProfileConfigResolver implements ProfileConfigResolver {
/**
* Constructor taking properties object
*/
public PropertiesProfileConfigResolver(Properties properties);
/**
* Constructor taking property getter function
*/
public PropertiesProfileConfigResolver(UnaryOperator<String> getter);
/**
* Gets profile name from properties
*/
public Profile.ProfileName getProfileName();
/**
* Gets feature configuration from properties
*/
public FeatureConfig getFeatureConfig(String feature);
/**
* Gets property key for a feature
*/
public static String getPropertyKey(Feature feature);
/**
* Gets property key for a feature string
*/
public static String getPropertyKey(String feature);
}// Configure with Properties object
Properties props = new Properties();
props.setProperty("kc.profile", "preview");
props.setProperty("kc.features.authorization", "enabled");
props.setProperty("kc.features.scripts", "disabled");
ProfileConfigResolver resolver = new PropertiesProfileConfigResolver(props);
// Configure with system property getter
ProfileConfigResolver systemResolver = new PropertiesProfileConfigResolver(
System::getProperty
);
// Configure with environment variable getter
ProfileConfigResolver envResolver = new PropertiesProfileConfigResolver(
key -> System.getenv(key.replace('.', '_').toUpperCase())
);
// Get property keys for features
String authzKey = PropertiesProfileConfigResolver.getPropertyKey(Feature.AUTHORIZATION);
// Result: "kc.features.authorization"
String scriptsKey = PropertiesProfileConfigResolver.getPropertyKey("scripts");
// Result: "kc.features.scripts"The CommaSeparatedListProfileConfigResolver class parses comma-separated feature lists.
public class CommaSeparatedListProfileConfigResolver implements ProfileConfigResolver {
/**
* Constructor taking enabled and disabled feature lists
*/
public CommaSeparatedListProfileConfigResolver(String enabledFeatures, String disabledFeatures);
/**
* Gets profile name (always returns default)
*/
public Profile.ProfileName getProfileName();
/**
* Gets feature configuration from the lists
*/
public FeatureConfig getFeatureConfig(String feature);
}// Configure with feature lists
String enabled = "authorization,scripts,docker";
String disabled = "web-authn,recovery-codes";
ProfileConfigResolver resolver = new CommaSeparatedListProfileConfigResolver(enabled, disabled);
// Check feature configuration
FeatureConfig authzConfig = resolver.getFeatureConfig("authorization");
// Result: FeatureConfig.ENABLED
FeatureConfig webauthnConfig = resolver.getFeatureConfig("web-authn");
// Result: FeatureConfig.DISABLED
FeatureConfig unconfiguredConfig = resolver.getFeatureConfig("token-exchange");
// Result: FeatureConfig.UNCONFIGURED
// Empty lists
ProfileConfigResolver emptyResolver = new CommaSeparatedListProfileConfigResolver(null, null);
FeatureConfig config = emptyResolver.getFeatureConfig("any-feature");
// Result: FeatureConfig.UNCONFIGUREDThe ProfileException class represents runtime exceptions related to profile operations.
public class ProfileException extends RuntimeException {
/**
* Constructor with message
*/
public ProfileException(String message);
/**
* Constructor with message and cause
*/
public ProfileException(String message, Throwable cause);
}public void validateProfileConfiguration(ProfileConfigResolver resolver) {
try {
Profile.ProfileName profileName = resolver.getProfileName();
if (profileName == null) {
throw new ProfileException("Profile name cannot be null");
}
} catch (Exception e) {
throw new ProfileException("Failed to validate profile configuration", e);
}
}public class MultiSourceProfileConfigResolver implements ProfileConfigResolver {
private final List<ProfileConfigResolver> resolvers;
public MultiSourceProfileConfigResolver(ProfileConfigResolver... resolvers) {
this.resolvers = Arrays.asList(resolvers);
}
@Override
public Profile.ProfileName getProfileName() {
// Use first non-null profile name
for (ProfileConfigResolver resolver : resolvers) {
Profile.ProfileName name = resolver.getProfileName();
if (name != null) {
return name;
}
}
return null;
}
@Override
public FeatureConfig getFeatureConfig(String feature) {
// Use first explicit configuration (not UNCONFIGURED)
for (ProfileConfigResolver resolver : resolvers) {
FeatureConfig config = resolver.getFeatureConfig(feature);
if (config != FeatureConfig.UNCONFIGURED) {
return config;
}
}
return FeatureConfig.UNCONFIGURED;
}
}public class ProfileConfigValidator {
public static void validateFeatureConfig(String feature, FeatureConfig config, Set<String> validFeatures) {
if (config != FeatureConfig.UNCONFIGURED && !validFeatures.contains(feature)) {
throw new ProfileException("Unknown feature: " + feature);
}
}
public static void validateProfileName(Profile.ProfileName profileName) {
if (profileName == null) {
throw new ProfileException("Profile name cannot be null");
}
}
public static void validateDependencies(Map<String, FeatureConfig> featureConfigs) {
for (Map.Entry<String, FeatureConfig> entry : featureConfigs.entrySet()) {
if (entry.getValue() == FeatureConfig.ENABLED) {
validateFeatureDependencies(entry.getKey(), featureConfigs);
}
}
}
private static void validateFeatureDependencies(String feature, Map<String, FeatureConfig> configs) {
// Get feature dependencies from Profile.Feature enum
try {
Profile.Feature f = Profile.Feature.valueOf(feature.toUpperCase().replace('-', '_'));
Set<Profile.Feature> dependencies = f.getDependencies();
for (Profile.Feature dep : dependencies) {
String depName = dep.getKey();
FeatureConfig depConfig = configs.get(depName);
if (depConfig == FeatureConfig.DISABLED) {
throw new ProfileException(
String.format("Feature %s requires %s to be enabled", feature, depName)
);
}
}
} catch (IllegalArgumentException e) {
// Feature not found in enum, skip validation
}
}
}public class EnvironmentProfileConfigResolver implements ProfileConfigResolver {
private final String profilePrefix;
private final String featurePrefix;
public EnvironmentProfileConfigResolver(String profilePrefix, String featurePrefix) {
this.profilePrefix = profilePrefix;
this.featurePrefix = featurePrefix;
}
@Override
public Profile.ProfileName getProfileName() {
String profileValue = System.getenv(profilePrefix + "PROFILE");
if (profileValue == null) {
return null;
}
try {
return Profile.ProfileName.valueOf(profileValue.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ProfileException("Invalid profile name: " + profileValue);
}
}
@Override
public FeatureConfig getFeatureConfig(String feature) {
String envKey = featurePrefix + feature.toUpperCase().replace('-', '_');
String value = System.getenv(envKey);
if (value == null) {
return FeatureConfig.UNCONFIGURED;
}
switch (value.toLowerCase()) {
case "true":
case "enabled":
case "on":
return FeatureConfig.ENABLED;
case "false":
case "disabled":
case "off":
return FeatureConfig.DISABLED;
default:
throw new ProfileException("Invalid feature config value: " + value + " for " + envKey);
}
}
}public class JsonProfileConfigResolver implements ProfileConfigResolver {
private final JsonObject config;
public JsonProfileConfigResolver(String jsonConfig) {
try {
this.config = JsonParser.parseString(jsonConfig).getAsJsonObject();
} catch (Exception e) {
throw new ProfileException("Invalid JSON configuration", e);
}
}
@Override
public Profile.ProfileName getProfileName() {
if (config.has("profile")) {
String profileName = config.get("profile").getAsString();
try {
return Profile.ProfileName.valueOf(profileName.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ProfileException("Invalid profile name: " + profileName);
}
}
return null;
}
@Override
public FeatureConfig getFeatureConfig(String feature) {
if (config.has("features")) {
JsonObject features = config.getAsJsonObject("features");
if (features.has(feature)) {
boolean enabled = features.get(feature).getAsBoolean();
return enabled ? FeatureConfig.ENABLED : FeatureConfig.DISABLED;
}
}
return FeatureConfig.UNCONFIGURED;
}
}public class ProfileManager {
public static Profile configureProfile() {
// Create multiple configuration sources
ProfileConfigResolver[] resolvers = {
// 1. System properties (highest priority)
new PropertiesProfileConfigResolver(System::getProperty),
// 2. Environment variables
new EnvironmentProfileConfigResolver("KC_", "KC_FEATURE_"),
// 3. Configuration file
createFileConfigResolver("keycloak.properties"),
// 4. Default configuration
new CommaSeparatedListProfileConfigResolver("authorization", null)
};
// Combine resolvers with priority order
ProfileConfigResolver resolver = new MultiSourceProfileConfigResolver(resolvers);
// Validate configuration
validateConfiguration(resolver);
// Configure profile
return Profile.configure(resolver);
}
private static ProfileConfigResolver createFileConfigResolver(String filename) {
try {
Properties props = new Properties();
try (InputStream is = ProfileManager.class.getClassLoader().getResourceAsStream(filename)) {
if (is != null) {
props.load(is);
}
}
return new PropertiesProfileConfigResolver(props);
} catch (Exception e) {
throw new ProfileException("Failed to load configuration file: " + filename, e);
}
}
private static void validateConfiguration(ProfileConfigResolver resolver) {
try {
ProfileConfigValidator.validateProfileName(resolver.getProfileName());
// Validate key features
Set<String> keyFeatures = Set.of("authorization", "scripts", "docker", "web-authn");
for (String feature : keyFeatures) {
FeatureConfig config = resolver.getFeatureConfig(feature);
ProfileConfigValidator.validateFeatureConfig(feature, config,
Profile.getAllUnversionedFeatureNames());
}
} catch (Exception e) {
throw new ProfileException("Configuration validation failed", e);
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-keycloak--keycloak-common