From edaad4c887e86142e5bbc50ed1b04ae70e1d320b Mon Sep 17 00:00:00 2001 From: rmartinc Date: Mon, 2 Feb 2026 16:56:21 +0100 Subject: [PATCH] Mark Token Exchange v1 as deprecated but in preview Closes #45791 Signed-off-by: rmartinc --- .../java/org/keycloak/common/Profile.java | 22 ++++++++----- .../java/org/keycloak/common/ProfileTest.java | 7 +++++ .../info/FeatureRepresentation.java | 9 ++++++ js/apps/admin-ui/src/dashboard/Dashboard.tsx | 31 ++++++++----------- .../src/defs/featureRepresentation.ts | 1 + .../admin/info/ServerInfoAdminResource.java | 1 + 6 files changed, 45 insertions(+), 26 deletions(-) diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java index 8e141056582..06247260aa2 100755 --- a/common/src/main/java/org/keycloak/common/Profile.java +++ b/common/src/main/java/org/keycloak/common/Profile.java @@ -76,7 +76,7 @@ public class Profile { SCRIPTS("Write custom authenticators using JavaScript", Type.PREVIEW), - TOKEN_EXCHANGE("Token Exchange Service", Type.PREVIEW, 1), + TOKEN_EXCHANGE("Token Exchange Service", Type.PREVIEW, 1, true, null, null), TOKEN_EXCHANGE_STANDARD_V2("Standard Token Exchange version 2", Type.DEFAULT, 2), TOKEN_EXCHANGE_EXTERNAL_INTERNAL_V2("External to Internal Token Exchange version 2", Type.EXPERIMENTAL, 2), @@ -172,32 +172,34 @@ public class Profile { private final BooleanSupplier isAvailable; private final FeatureUpdatePolicy updatePolicy; private final Set dependencies; + private final boolean deprecated; private final int version; Feature(String label, Type type, Feature... dependencies) { - this(label, type, 1, null, null, dependencies); + this(label, type, 1, type == Type.DEPRECATED, null, null, dependencies); } Feature(String label, Type type, FeatureUpdatePolicy updatePolicy, Feature... dependencies) { - this(label, type, 1, null, updatePolicy, dependencies); + this(label, type, 1, type == Type.DEPRECATED, null, updatePolicy, dependencies); } Feature(String label, Type type, int version, FeatureUpdatePolicy updatePolicy, Feature... dependencies) { - this(label, type, version, null, updatePolicy, dependencies); + this(label, type, version, type == Type.DEPRECATED, null, updatePolicy, dependencies); } Feature(String label, Type type, int version, Feature... dependencies) { - this(label, type, version, null, null, dependencies); + this(label, type, version, type == Type.DEPRECATED, null, null, dependencies); } Feature(String label, Type type, int version, BooleanSupplier isAvailable, Feature... dependencies) { - this(label, type, version, isAvailable, null, dependencies); + this(label, type, version, type == Type.DEPRECATED, isAvailable, null, dependencies); } - Feature(String label, Type type, int version, BooleanSupplier isAvailable, FeatureUpdatePolicy updatePolicy, Feature... dependencies) { + Feature(String label, Type type, int version, boolean deprecated, BooleanSupplier isAvailable, FeatureUpdatePolicy updatePolicy, Feature... dependencies) { this.label = label; this.type = type; this.version = version; + this.deprecated = type == Type.DEPRECATED || deprecated; this.isAvailable = isAvailable; this.updatePolicy = updatePolicy == null ? FeatureUpdatePolicy.ROLLING : updatePolicy; this.key = name().toLowerCase().replaceAll("_", "-"); @@ -252,6 +254,10 @@ public class Profile { return version; } + public boolean isDeprecated() { + return deprecated; + } + public boolean isAvailable() { return isAvailable == null || isAvailable.getAsBoolean(); } @@ -513,7 +519,7 @@ public class Profile { } public Set getDeprecatedFeatures() { - return getFeatures(Feature.Type.DEPRECATED); + return features.keySet().stream().filter(Feature::isDeprecated).collect(Collectors.toSet()); } public Set getFeatures(Feature.Type type) { diff --git a/common/src/test/java/org/keycloak/common/ProfileTest.java b/common/src/test/java/org/keycloak/common/ProfileTest.java index cb5ab8a4492..7100b1fcdba 100644 --- a/common/src/test/java/org/keycloak/common/ProfileTest.java +++ b/common/src/test/java/org/keycloak/common/ProfileTest.java @@ -66,11 +66,16 @@ public class ProfileTest { Profile profile = Profile.defaults(); Assert.assertTrue(Profile.isFeatureEnabled(DEFAULT_FEATURE)); + Assert.assertFalse(DEFAULT_FEATURE.isDeprecated()); Assert.assertFalse(Profile.isFeatureEnabled(DISABLED_BY_DEFAULT_FEATURE)); + Assert.assertFalse(DISABLED_BY_DEFAULT_FEATURE.isDeprecated()); Assert.assertFalse(Profile.isFeatureEnabled(PREVIEW_FEATURE)); Assert.assertFalse(Profile.isFeatureEnabled(EXPERIMENTAL_FEATURE)); + Assert.assertFalse(EXPERIMENTAL_FEATURE.isDeprecated()); if (DEPRECATED_FEATURE != null) { Assert.assertFalse(Profile.isFeatureEnabled(DEPRECATED_FEATURE)); + MatcherAssert.assertThat(profile.getDeprecatedFeatures(), Matchers.hasItem(DEPRECATED_FEATURE)); + Assert.assertTrue(DEPRECATED_FEATURE.isDeprecated()); } else { MatcherAssert.assertThat(profile.getDeprecatedFeatures(), Matchers.empty()); } @@ -79,6 +84,8 @@ public class ProfileTest { MatcherAssert.assertThat(profile.getDisabledFeatures(), Matchers.hasItem(DISABLED_BY_DEFAULT_FEATURE)); MatcherAssert.assertThat(profile.getPreviewFeatures(), Matchers.hasItem(PREVIEW_FEATURE)); + Assert.assertTrue(Profile.Feature.TOKEN_EXCHANGE.isDeprecated()); + Assert.assertEquals(Profile.Feature.Type.PREVIEW, Profile.Feature.TOKEN_EXCHANGE.getType()); } @Test diff --git a/core/src/main/java/org/keycloak/representations/info/FeatureRepresentation.java b/core/src/main/java/org/keycloak/representations/info/FeatureRepresentation.java index 4667844eed5..481c7ce668a 100644 --- a/core/src/main/java/org/keycloak/representations/info/FeatureRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/info/FeatureRepresentation.java @@ -7,6 +7,7 @@ public class FeatureRepresentation { private String label; private FeatureType type; private boolean isEnabled; + private Boolean deprecated; private Set dependencies; public FeatureRepresentation() { @@ -36,6 +37,14 @@ public class FeatureRepresentation { this.type = type; } + public Boolean isDeprecated() { + return deprecated; + } + + public void setDeprecated(Boolean deprecated) { + this.deprecated = Boolean.TRUE.equals(deprecated) ? deprecated : null; + } + public boolean isEnabled() { return isEnabled; } diff --git a/js/apps/admin-ui/src/dashboard/Dashboard.tsx b/js/apps/admin-ui/src/dashboard/Dashboard.tsx index c891a74232a..8473c15ef5a 100644 --- a/js/apps/admin-ui/src/dashboard/Dashboard.tsx +++ b/js/apps/admin-ui/src/dashboard/Dashboard.tsx @@ -77,27 +77,22 @@ type FeatureItemProps = { const FeatureItem = ({ feature }: FeatureItemProps) => { const { t } = useTranslation(); + const label = feature.type.toLowerCase(); + const color = feature.deprecated + ? "grey" + : feature.type === FeatureType.Default || + feature.type === FeatureType.DisabledByDefault + ? "green" + : feature.type === FeatureType.Preview || + feature.type === FeatureType.PreviewDisabledByDefault + ? "blue" + : feature.type === FeatureType.Experimental + ? "orange" + : "red"; return ( {feature.name}  - {feature.type === FeatureType.Experimental && ( - - )} - {feature.type === FeatureType.Preview && ( - - )} - {feature.type === FeatureType.PreviewDisabledByDefault && ( - - )} - {feature.type === FeatureType.Default && ( - - )} - {feature.type === FeatureType.DisabledByDefault && ( - - )} - {feature.type === FeatureType.Deprecated && ( - - )} + ); }; diff --git a/js/libs/keycloak-admin-client/src/defs/featureRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/featureRepresentation.ts index b618d86b61c..31ef18ca923 100644 --- a/js/libs/keycloak-admin-client/src/defs/featureRepresentation.ts +++ b/js/libs/keycloak-admin-client/src/defs/featureRepresentation.ts @@ -3,6 +3,7 @@ export default interface FeatureRepresentation { label: string; type: FeatureType; enabled: boolean; + deprecated?: boolean; dependencies: string[]; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java index c2fa09854b0..3cd9e4a959c 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java @@ -445,6 +445,7 @@ public class ServerInfoAdminResource { featureRep.setLabel(feature.getLabel()); featureRep.setType(FeatureType.valueOf(feature.getType().name())); featureRep.setEnabled(isEnabled); + featureRep.setDeprecated(feature.isDeprecated()); featureRep.setDependencies(feature.getDependencies() != null ? feature.getDependencies().stream().map(Enum::name).collect(Collectors.toSet()) : Collections.emptySet()); return featureRep;