Add availability for features and make kerberos use it
Closes #30730 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
ca26524259
commit
c20dbc5c32
2 changed files with 63 additions and 4 deletions
|
@ -34,6 +34,7 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ public class Profile {
|
||||||
STEP_UP_AUTHENTICATION("Step-up Authentication", Type.DEFAULT),
|
STEP_UP_AUTHENTICATION("Step-up Authentication", Type.DEFAULT),
|
||||||
|
|
||||||
// Check if kerberos is available in underlying JVM and auto-detect if feature should be enabled or disabled by default based on that
|
// Check if kerberos is available in underlying JVM and auto-detect if feature should be enabled or disabled by default based on that
|
||||||
KERBEROS("Kerberos", KerberosJdkProvider.getProvider().isKerberosAvailable() ? Type.DEFAULT : Type.DISABLED_BY_DEFAULT),
|
KERBEROS("Kerberos", Type.DEFAULT, 1, () -> KerberosJdkProvider.getProvider().isKerberosAvailable()),
|
||||||
|
|
||||||
RECOVERY_CODES("Recovery codes", Type.PREVIEW),
|
RECOVERY_CODES("Recovery codes", Type.PREVIEW),
|
||||||
|
|
||||||
|
@ -122,21 +123,27 @@ public class Profile {
|
||||||
private final String label;
|
private final String label;
|
||||||
private final String unversionedKey;
|
private final String unversionedKey;
|
||||||
private final String key;
|
private final String key;
|
||||||
|
private final BooleanSupplier isAvailable;
|
||||||
|
|
||||||
private Set<Feature> dependencies;
|
private Set<Feature> dependencies;
|
||||||
private int version;
|
private int version;
|
||||||
|
|
||||||
Feature(String label, Type type, Feature... dependencies) {
|
Feature(String label, Type type, Feature... dependencies) {
|
||||||
this(label, type, 1, dependencies);
|
this(label, type, 1, null, dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
Feature(String label, Type type, int version, Feature... dependencies) {
|
||||||
|
this(label, type, version, null, dependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* allowNameKey should be false for new versioned features to disallow using a legacy name, like account2
|
* allowNameKey should be false for new versioned features to disallow using a legacy name, like account2
|
||||||
*/
|
*/
|
||||||
Feature(String label, Type type, int version, Feature... dependencies) {
|
Feature(String label, Type type, int version, BooleanSupplier isAvailable, Feature... dependencies) {
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
|
this.isAvailable = isAvailable;
|
||||||
this.key = name().toLowerCase().replaceAll("_", "-");
|
this.key = name().toLowerCase().replaceAll("_", "-");
|
||||||
if (this.name().endsWith("_V" + version)) {
|
if (this.name().endsWith("_V" + version)) {
|
||||||
unversionedKey = key.substring(0, key.length() - (String.valueOf(version).length() + 2));
|
unversionedKey = key.substring(0, key.length() - (String.valueOf(version).length() + 2));
|
||||||
|
@ -190,6 +197,10 @@ public class Profile {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return isAvailable == null || isAvailable.getAsBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
// in priority order
|
// in priority order
|
||||||
DEFAULT("Default"),
|
DEFAULT("Default"),
|
||||||
|
@ -238,6 +249,9 @@ public class Profile {
|
||||||
Feature enabledFeature = null;
|
Feature enabledFeature = null;
|
||||||
if (unversionedConfig == FeatureConfig.ENABLED) {
|
if (unversionedConfig == FeatureConfig.ENABLED) {
|
||||||
enabledFeature = entry.getValue().iterator().next();
|
enabledFeature = entry.getValue().iterator().next();
|
||||||
|
if (!enabledFeature.isAvailable()) {
|
||||||
|
throw new ProfileException(String.format("Feature %s cannot be enabled as it is not available.", unversionedFeature));
|
||||||
|
}
|
||||||
} else if (unversionedConfig == FeatureConfig.DISABLED && ESSENTIAL_FEATURES.contains(unversionedFeature)) {
|
} else if (unversionedConfig == FeatureConfig.DISABLED && ESSENTIAL_FEATURES.contains(unversionedFeature)) {
|
||||||
throw new ProfileException(String.format("Feature %s cannot be disabled.", unversionedFeature));
|
throw new ProfileException(String.format("Feature %s cannot be disabled.", unversionedFeature));
|
||||||
}
|
}
|
||||||
|
@ -259,13 +273,17 @@ public class Profile {
|
||||||
enabledFeature.getVersionedKey(), f.getVersionedKey()));
|
enabledFeature.getVersionedKey(), f.getVersionedKey()));
|
||||||
}
|
}
|
||||||
// even if something else was enabled by default, explicitly enabling a lower priority feature takes precedence
|
// even if something else was enabled by default, explicitly enabling a lower priority feature takes precedence
|
||||||
|
if (!f.isAvailable()) {
|
||||||
|
throw new ProfileException(String.format("Feature %s cannot be enabled as it is not available.", f.getVersionedKey()));
|
||||||
|
}
|
||||||
enabledFeature = f;
|
enabledFeature = f;
|
||||||
isExplicitlyEnabledFeature = true;
|
isExplicitlyEnabledFeature = true;
|
||||||
break;
|
break;
|
||||||
case DISABLED:
|
case DISABLED:
|
||||||
throw new ProfileException("Feature " + f.getVersionedKey() + " should not be disabled using a versioned key.");
|
throw new ProfileException("Feature " + f.getVersionedKey() + " should not be disabled using a versioned key.");
|
||||||
default:
|
default:
|
||||||
if (unversionedConfig == FeatureConfig.UNCONFIGURED && enabledFeature == null && isEnabledByDefault(profile, f)) {
|
if (unversionedConfig == FeatureConfig.UNCONFIGURED && enabledFeature == null
|
||||||
|
&& isEnabledByDefault(profile, f) && f.isAvailable()) {
|
||||||
enabledFeature = f;
|
enabledFeature = f;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -12,10 +12,14 @@ import org.keycloak.common.profile.CommaSeparatedListProfileConfigResolver;
|
||||||
import org.keycloak.common.profile.ProfileException;
|
import org.keycloak.common.profile.ProfileException;
|
||||||
import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
||||||
|
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.util.AbstractMap;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -228,6 +232,32 @@ public class ProfileTest {
|
||||||
Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
Assert.assertTrue(Profile.isFeatureEnabled(PREVIEW_FEATURE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void kerberosConfigAvailability() {
|
||||||
|
// remove SunJGSS to remove kerberos availability
|
||||||
|
Map.Entry<Integer, Provider> removed = removeSecurityProvider("SunJGSS");
|
||||||
|
try {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty(PropertiesProfileConfigResolver.getPropertyKey(Profile.Feature.KERBEROS), "enabled");
|
||||||
|
ProfileException e = Assert.assertThrows(ProfileException.class, () -> Profile.configure(new PropertiesProfileConfigResolver(properties)));
|
||||||
|
Assert.assertEquals("Feature kerberos cannot be enabled as it is not available.", e.getMessage());
|
||||||
|
|
||||||
|
Profile.defaults();
|
||||||
|
properties.setProperty(PropertiesProfileConfigResolver.getPropertyKey(Profile.Feature.KERBEROS), "disabled");
|
||||||
|
Profile.configure(new PropertiesProfileConfigResolver(properties));
|
||||||
|
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.KERBEROS));
|
||||||
|
|
||||||
|
Profile.defaults();
|
||||||
|
properties.clear();
|
||||||
|
Profile.configure(new PropertiesProfileConfigResolver(properties));
|
||||||
|
Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.KERBEROS));
|
||||||
|
} finally {
|
||||||
|
if (removed != null) {
|
||||||
|
Security.insertProviderAt(removed.getValue(), removed.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void assertEquals(Set<Profile.Feature> actual, Collection<Profile.Feature> expected) {
|
public static void assertEquals(Set<Profile.Feature> actual, Collection<Profile.Feature> expected) {
|
||||||
MatcherAssert.assertThat(actual, Matchers.equalTo(expected));
|
MatcherAssert.assertThat(actual, Matchers.equalTo(expected));
|
||||||
}
|
}
|
||||||
|
@ -243,4 +273,15 @@ public class ProfileTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map.Entry<Integer, Provider> removeSecurityProvider(String name) {
|
||||||
|
int position = 1;
|
||||||
|
for (Provider p : Security.getProviders()) {
|
||||||
|
if (name.equals(p.getName())) {
|
||||||
|
Security.removeProvider(name);
|
||||||
|
return new AbstractMap.SimpleEntry<>(position, p);
|
||||||
|
}
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue