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.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -83,7 +84,7 @@ public class Profile {
|
|||
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
|
||||
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),
|
||||
|
||||
|
@ -122,21 +123,27 @@ public class Profile {
|
|||
private final String label;
|
||||
private final String unversionedKey;
|
||||
private final String key;
|
||||
private final BooleanSupplier isAvailable;
|
||||
|
||||
private Set<Feature> dependencies;
|
||||
private int version;
|
||||
|
||||
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
|
||||
*/
|
||||
Feature(String label, Type type, int version, Feature... dependencies) {
|
||||
Feature(String label, Type type, int version, BooleanSupplier isAvailable, Feature... dependencies) {
|
||||
this.label = label;
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
this.isAvailable = isAvailable;
|
||||
this.key = name().toLowerCase().replaceAll("_", "-");
|
||||
if (this.name().endsWith("_V" + version)) {
|
||||
unversionedKey = key.substring(0, key.length() - (String.valueOf(version).length() + 2));
|
||||
|
@ -190,6 +197,10 @@ public class Profile {
|
|||
return version;
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return isAvailable == null || isAvailable.getAsBoolean();
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
// in priority order
|
||||
DEFAULT("Default"),
|
||||
|
@ -238,6 +249,9 @@ public class Profile {
|
|||
Feature enabledFeature = null;
|
||||
if (unversionedConfig == FeatureConfig.ENABLED) {
|
||||
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)) {
|
||||
throw new ProfileException(String.format("Feature %s cannot be disabled.", unversionedFeature));
|
||||
}
|
||||
|
@ -259,13 +273,17 @@ public class Profile {
|
|||
enabledFeature.getVersionedKey(), f.getVersionedKey()));
|
||||
}
|
||||
// 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;
|
||||
isExplicitlyEnabledFeature = true;
|
||||
break;
|
||||
case DISABLED:
|
||||
throw new ProfileException("Feature " + f.getVersionedKey() + " should not be disabled using a versioned key.");
|
||||
default:
|
||||
if (unversionedConfig == FeatureConfig.UNCONFIGURED && enabledFeature == null && isEnabledByDefault(profile, f)) {
|
||||
if (unversionedConfig == FeatureConfig.UNCONFIGURED && enabledFeature == null
|
||||
&& isEnabledByDefault(profile, f) && f.isAvailable()) {
|
||||
enabledFeature = f;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -12,10 +12,14 @@ import org.keycloak.common.profile.CommaSeparatedListProfileConfigResolver;
|
|||
import org.keycloak.common.profile.ProfileException;
|
||||
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.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -228,6 +232,32 @@ public class ProfileTest {
|
|||
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) {
|
||||
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