keycloak-memoizeit

Changes

common/pom.xml 5(+5 -0)

Details

common/pom.xml 5(+5 -0)

diff --git a/common/pom.xml b/common/pom.xml
index 4ec3062..9df7512 100755
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -51,6 +51,11 @@
             <artifactId>bcpkix-jdk15on</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java
index 04d4f0b..e7a2a23 100755
--- a/common/src/main/java/org/keycloak/common/Profile.java
+++ b/common/src/main/java/org/keycloak/common/Profile.java
@@ -17,12 +17,12 @@
 
 package org.keycloak.common;
 
+import org.jboss.logging.Logger;
+
 import java.io.File;
 import java.io.FileInputStream;
-import java.util.Arrays;
-import java.util.Collections;
+import java.io.IOException;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Properties;
 import java.util.Set;
 
@@ -32,95 +32,101 @@ import java.util.Set;
  */
 public class Profile {
 
-    public enum Feature {
-        ACCOUNT2,
-        ADMIN_FINE_GRAINED_AUTHZ,
-        DOCKER,
-        IMPERSONATION,
-        OPENSHIFT_INTEGRATION,
-        SCRIPTS,
-        TOKEN_EXCHANGE
-    }
+    private static final Logger logger = Logger.getLogger(Profile.class);
 
-    private enum ProductValue {
-        KEYCLOAK(),
-        RHSSO(Feature.ACCOUNT2);
+    public enum Type {
+        DEFAULT,
+        DISABLED_BY_DEFAULT,
+        PREVIEW,
+        EXPERIMENTAL
+    }
 
-        private List<Feature> excluded;
+    public enum Feature {
+        ACCOUNT2(Type.EXPERIMENTAL),
+        ACCOUNT_API(Type.PREVIEW),
+        ADMIN_FINE_GRAINED_AUTHZ(Type.PREVIEW),
+        DOCKER(Type.DISABLED_BY_DEFAULT),
+        IMPERSONATION(Type.DEFAULT),
+        OPENSHIFT_INTEGRATION(Type.DEFAULT),
+        SCRIPTS(Type.PREVIEW),
+        TOKEN_EXCHANGE(Type.PREVIEW);
+
+        private Type type;
+
+        Feature(Type type) {
+            this.type = type;
+        }
 
-        ProductValue(Feature... excluded) {
-            this.excluded = Arrays.asList(excluded);
+        public Type getType() {
+            return type;
         }
     }
 
-    private enum ProfileValue {
-        PRODUCT(Feature.ADMIN_FINE_GRAINED_AUTHZ, Feature.SCRIPTS, Feature.DOCKER, Feature.ACCOUNT2, Feature.TOKEN_EXCHANGE),
-        PREVIEW(Feature.ACCOUNT2),
-        COMMUNITY(Feature.DOCKER, Feature.ACCOUNT2);
-
-        private List<Feature> disabled;
+    private enum ProductValue {
+        KEYCLOAK,
+        RHSSO
+    }
 
-        ProfileValue(Feature... disabled) {
-            this.disabled = Arrays.asList(disabled);
-        }
+    private enum ProfileValue {
+        COMMUNITY,
+        PRODUCT,
+        PREVIEW
     }
 
-    private static final Profile CURRENT = new Profile();
+    private static Profile CURRENT = new Profile();
 
     private final ProductValue product;
 
     private final ProfileValue profile;
 
     private final Set<Feature> disabledFeatures = new HashSet<>();
+    private final Set<Feature> previewFeatures = new HashSet<>();
+    private final Set<Feature> experimentalFeatures = new HashSet<>();
 
     private Profile() {
-        product = "rh-sso".equals(Version.NAME) ? ProductValue.RHSSO : ProductValue.KEYCLOAK;
+        Config config = new Config();
 
-        try {
-            Properties props = new Properties();
-
-            String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
-            if (jbossServerConfigDir != null) {
-                File file = new File(jbossServerConfigDir, "profile.properties");
-                if (file.isFile()) {
-                    props.load(new FileInputStream(file));
-                }
-            }
-
-            if (System.getProperties().containsKey("keycloak.profile")) {
-                props.setProperty("profile", System.getProperty("keycloak.profile"));
-            }
-
-            for (String k : System.getProperties().stringPropertyNames()) {
-                if (k.startsWith("keycloak.profile.feature.")) {
-                    props.put(k.replace("keycloak.profile.feature.", "feature."), System.getProperty(k));
-                }
-            }
-
-            if (props.containsKey("profile")) {
-                profile = ProfileValue.valueOf(props.getProperty("profile").toUpperCase());
-            } else {
-                profile = ProfileValue.valueOf(Version.DEFAULT_PROFILE.toUpperCase());
-            }
+        product = "rh-sso".equals(Version.NAME) ? ProductValue.RHSSO : ProductValue.KEYCLOAK;
+        profile = ProfileValue.valueOf(config.getProfile().toUpperCase());
 
-            disabledFeatures.addAll(profile.disabled);
-            disabledFeatures.removeAll(product.excluded);
+        for (Feature f : Feature.values()) {
+            Boolean enabled = config.getConfig(f);
 
-            for (String k : props.stringPropertyNames()) {
-                if (k.startsWith("feature.")) {
-                    Feature f = Feature.valueOf(k.replace("feature.", "").toUpperCase());
-                    if (props.get(k).equals("enabled")) {
-                        disabledFeatures.remove(f);
-                    } else if (props.get(k).equals("disabled")) {
+            switch (f.getType()) {
+                case DEFAULT:
+                    if (enabled != null && !enabled) {
                         disabledFeatures.add(f);
                     }
-                }
+                    break;
+                case DISABLED_BY_DEFAULT:
+                    if (enabled == null || !enabled) {
+                        disabledFeatures.add(f);
+                    }
+                    break;
+                case PREVIEW:
+                    previewFeatures.add(f);
+                    if (enabled == null || !enabled) {
+                        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;
             }
-        } catch (Exception e) {
-            throw new RuntimeException(e);
         }
     }
 
+    public static void init() {
+        CURRENT = new Profile();
+    }
+
     public static String getName() {
         return CURRENT.profile.name().toLowerCase();
     }
@@ -129,11 +135,68 @@ public class Profile {
         return CURRENT.disabledFeatures;
     }
 
+    public static Set<Feature> getPreviewFeatures() {
+        return CURRENT.previewFeatures;
+    }
+
+    public static Set<Feature> getExperimentalFeatures() {
+        return CURRENT.experimentalFeatures;
+    }
+
     public static boolean isFeatureEnabled(Feature feature) {
-        if (CURRENT.product.excluded.contains(feature)) {
-            return false;
-        }
         return !CURRENT.disabledFeatures.contains(feature);
     }
 
+    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()) {
+                        properties.load(new FileInputStream(file));
+                    }
+                }
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public String getProfile() {
+            String profile = System.getProperty("keycloak.profile");
+            if (profile != null) {
+                return profile;
+            }
+
+            profile = properties.getProperty("profile");
+            if (profile != null) {
+                return profile;
+            }
+
+            return Version.DEFAULT_PROFILE;
+        }
+
+        public Boolean getConfig(Feature feature) {
+            String config = System.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);
+            }
+        }
+    }
+
 }
diff --git a/common/src/test/java/org/keycloak/common/ProfileTest.java b/common/src/test/java/org/keycloak/common/ProfileTest.java
new file mode 100644
index 0000000..827e08e
--- /dev/null
+++ b/common/src/test/java/org/keycloak/common/ProfileTest.java
@@ -0,0 +1,97 @@
+package org.keycloak.common;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Properties;
+import java.util.Set;
+
+public class ProfileTest {
+
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    @Test
+    public void checkDefaults() {
+        Assert.assertEquals("community", Profile.getName());
+        assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE);
+        assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE);
+        assertEquals(Profile.getExperimentalFeatures(), Profile.Feature.ACCOUNT2);
+    }
+
+    @Test
+    public void configWithSystemProperties() {
+        Assert.assertEquals("community", Profile.getName());
+        Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
+        Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
+
+        System.setProperty("keycloak.profile", "preview");
+        System.setProperty("keycloak.profile.feature.docker", "enabled");
+        System.setProperty("keycloak.profile.feature.impersonation", "disabled");
+
+        Profile.init();
+
+        Assert.assertEquals("preview", Profile.getName());
+        Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
+        Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
+
+        System.getProperties().remove("keycloak.profile");
+        System.getProperties().remove("keycloak.profile.feature.docker");
+        System.getProperties().remove("keycloak.profile.feature.impersonation");
+
+        Profile.init();
+    }
+
+    @Test
+    public void configWithPropertiesFile() throws IOException {
+        Assert.assertEquals("community", Profile.getName());
+        Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
+        Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
+
+        File d = temporaryFolder.newFolder();
+        File f = new File(d, "profile.properties");
+
+        Properties p = new Properties();
+        p.setProperty("profile", "preview");
+        p.setProperty("feature.docker", "enabled");
+        p.setProperty("feature.impersonation", "disabled");
+        PrintWriter pw = new PrintWriter(f);
+        p.list(pw);
+        pw.close();
+
+        System.setProperty("jboss.server.config.dir", d.getAbsolutePath());
+
+        Profile.init();
+
+        Assert.assertEquals("preview", Profile.getName());
+        Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.DOCKER));
+        Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION));
+
+        System.getProperties().remove("jboss.server.config.dir");
+
+        Profile.init();
+    }
+
+    public static void assertEquals(Set<Profile.Feature> actual, Profile.Feature... expected) {
+        Profile.Feature[] a = actual.toArray(new Profile.Feature[actual.size()]);
+        Arrays.sort(a, new FeatureComparator());
+        Arrays.sort(expected, new FeatureComparator());
+        Assert.assertArrayEquals(a, expected);
+    }
+
+    private static class FeatureComparator implements Comparator<Profile.Feature> {
+        @Override
+        public int compare(Profile.Feature o1, Profile.Feature o2) {
+            return o1.name().compareTo(o2.name());
+        }
+    }
+
+}
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 c0cbea8..52481bc 100644
--- a/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java
@@ -21,6 +21,7 @@ import org.keycloak.common.Profile;
 
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -29,15 +30,16 @@ public class ProfileInfoRepresentation {
 
     private String name;
     private List<String> disabledFeatures;
+    private List<String> previewFeatures;
+    private List<String> experimentalFeatures;
 
     public static ProfileInfoRepresentation create() {
         ProfileInfoRepresentation info = new ProfileInfoRepresentation();
 
         info.name = Profile.getName();
-        info.disabledFeatures = new LinkedList<>();
-        for (Profile.Feature f : Profile.getDisabledFeatures()) {
-            info.disabledFeatures.add(f.name());
-        }
+        info.disabledFeatures = names(Profile.getDisabledFeatures());
+        info.previewFeatures = names(Profile.getPreviewFeatures());
+        info.experimentalFeatures = names(Profile.getExperimentalFeatures());
 
         return info;
     }
@@ -50,4 +52,20 @@ public class ProfileInfoRepresentation {
         return disabledFeatures;
     }
 
+    public List<String> getPreviewFeatures() {
+        return previewFeatures;
+    }
+
+    public List<String> getExperimentalFeatures() {
+        return experimentalFeatures;
+    }
+
+    private static List<String> names(Set<Profile.Feature> featureSet) {
+        List<String> l = new LinkedList();
+        for (Profile.Feature f : featureSet) {
+            l.add(f.name());
+        }
+        return l;
+    }
+
 }
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
index 4afb80d..695dc1a 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
@@ -27,6 +27,7 @@
         <module name="org.bouncycastle" />
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
+        <module name="org.jboss.logging"/>
         <module name="sun.jdk" optional="true" />
     </dependencies>
 
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
index 4afb80d..695dc1a 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
@@ -27,6 +27,7 @@
         <module name="org.bouncycastle" />
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
+        <module name="org.jboss.logging"/>
         <module name="sun.jdk" optional="true" />
     </dependencies>
 
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml
index 81578ae..7e96423 100755
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml
@@ -27,6 +27,7 @@
         <module name="org.bouncycastle" />
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
+        <module name="org.jboss.logging"/>
         <module name="sun.jdk" optional="true" />
     </dependencies>
 
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml
index 7b024b1..ee73fde 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml
@@ -24,6 +24,7 @@
         <module name="org.bouncycastle" />
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
+        <module name="org.jboss.logging"/>
         <module name="sun.jdk" optional="true" />
     </dependencies>
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
index 4afb80d..695dc1a 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
@@ -27,6 +27,7 @@
         <module name="org.bouncycastle" />
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
+        <module name="org.jboss.logging"/>
         <module name="sun.jdk" optional="true" />
     </dependencies>
 
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
index 4afb80d..695dc1a 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
@@ -27,6 +27,7 @@
         <module name="org.bouncycastle" />
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
+        <module name="org.jboss.logging"/>
         <module name="sun.jdk" optional="true" />
     </dependencies>
 
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
index 0a39dd4..72fe538 100755
--- a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
@@ -206,7 +206,7 @@ public class AccountRestService {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public Response sessions() {
-        checkAccount2Enabled();
+        checkAccountApiEnabled();
         List<SessionRepresentation> reps = new LinkedList<>();
 
         List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
@@ -244,7 +244,7 @@ public class AccountRestService {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public Response sessionsLogout(@QueryParam("current") boolean removeCurrent) {
-        checkAccount2Enabled();
+        checkAccountApiEnabled();
         UserSessionModel userSession = auth.getSession();
 
         List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
@@ -268,7 +268,7 @@ public class AccountRestService {
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
     public Response sessionLogout(@QueryParam("id") String id) {
-        checkAccount2Enabled();
+        checkAccountApiEnabled();
         UserSessionModel userSession = session.sessions().getUserSession(realm, id);
         if (userSession != null && userSession.getUser().equals(user)) {
             AuthenticationManager.backchannelLogout(session, userSession, true);
@@ -278,7 +278,7 @@ public class AccountRestService {
 
     @Path("/credentials")
     public AccountCredentialResource credentials() {
-        checkAccount2Enabled();
+        checkAccountApiEnabled();
         return new AccountCredentialResource(session, event, user);
     }
 
@@ -286,8 +286,8 @@ public class AccountRestService {
     // TODO Applications
     // TODO Logs
     
-    private static void checkAccount2Enabled() {
-        if (!Profile.isFeatureEnabled(Profile.Feature.ACCOUNT2)) {
+    private static void checkAccountApiEnabled() {
+        if (!Profile.isFeatureEnabled(Profile.Feature.ACCOUNT_API)) {
             throw new NotFoundException();
         }
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java
index b24ec81..0cbda1b 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java
@@ -44,7 +44,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.junit.Assert.*;
 import org.keycloak.services.messages.Messages;
 
-import static org.keycloak.common.Profile.Feature.ACCOUNT2;
+import static org.keycloak.common.Profile.Feature.ACCOUNT_API;
 import static org.keycloak.testsuite.ProfileAssume.assumeFeatureEnabled;
 
 /**
@@ -234,7 +234,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
 
     @Test
     public void testGetSessions() throws IOException {
-        assumeFeatureEnabled(ACCOUNT2);
+        assumeFeatureEnabled(ACCOUNT_API);
         
         List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(tokenUtil.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
 
@@ -243,14 +243,14 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
 
     @Test
     public void testGetPasswordDetails() throws IOException {
-        assumeFeatureEnabled(ACCOUNT2);
+        assumeFeatureEnabled(ACCOUNT_API);
         
         getPasswordDetails();
     }
 
     @Test
     public void testPostPasswordUpdate() throws IOException {
-        assumeFeatureEnabled(ACCOUNT2);
+        assumeFeatureEnabled(ACCOUNT_API);
         
         //Get the time of lastUpdate
         AccountCredentialResource.PasswordDetails initialDetails = getPasswordDetails();
@@ -275,7 +275,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
 
     @Test
     public void testPasswordConfirmation() throws IOException {
-        assumeFeatureEnabled(ACCOUNT2);
+        assumeFeatureEnabled(ACCOUNT_API);
         
         updatePassword("password", "Str0ng3rP4ssw0rd", "confirmationDoesNotMatch", 400);
 
@@ -318,7 +318,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
 
     @Test
     public void testDeleteSession() throws IOException {
-        assumeFeatureEnabled(ACCOUNT2);
+        assumeFeatureEnabled(ACCOUNT_API);
         
         TokenUtil viewToken = new TokenUtil("view-account-access", "password");
         String sessionId = oauth.doLogin("view-account-access", "password").getSessionState();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java
index 9971da7..ec042b2 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java
@@ -26,6 +26,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory;
 import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
+import org.keycloak.common.Profile;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventType;
@@ -64,6 +65,11 @@ public class ScriptAuthenticatorTest extends AbstractFlowTest {
 
     public static final String EXECUTION_ID = "scriptAuth";
 
+    @BeforeClass
+    public static void verifyEnvironment() {
+        ProfileAssume.assumeFeatureEnabled(Profile.Feature.SCRIPTS);
+    }
+
     @Override
     public void configureTestRealm(RealmRepresentation testRealm) {
 
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 7876630..4c1c915 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -1121,11 +1121,17 @@ include-representation.tooltip=Include JSON representation for create and update
 clear-admin-events.tooltip=Deletes all admin events in the database.
 server-version=Server Version
 server-profile=Server Profile
-server-disabled=Server Disabled Features
+server-disabled=Disabled Features
+server-disabled.tooltip=Features that are not currently enabled. Some features are not enabled by default. This applies to all preview and experimental features.
+server-preview=Preview Features
+server-preview.tooltip=Preview features are not supported in production use and may be significantly changed or removed in the future.
+server-experimental=Experimental Features
+server-experimental.tooltip=Experimental features are experimental features that may not be fully function. Never use experimental features in production.
 info=Info
 providers=Providers
 server-time=Server Time
 server-uptime=Server Uptime
+profile=Profile
 memory=Memory
 total-memory=Total Memory
 free-memory=Free Memory
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html b/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html
index 4a4b296..6d24419 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html
@@ -15,14 +15,6 @@
             <td>{{serverInfo.systemInfo.version}}</td>
         </tr>
         <tr>
-            <td width="20%">{{:: 'server-profile' | translate}}</td>
-            <td>{{serverInfo.profileInfo.name | capitalize}}</td>
-        </tr>
-        <tr data-ng-if="serverInfo.profileInfo.disabledFeatures.length > 0">
-            <td width="20%">{{:: 'server-disabled' | translate}}</td>
-            <td>{{serverInfo.profileInfo.disabledFeatures.join(', ').toLowerCase() | capitalize}}</td>
-        </tr>
-        <tr>
             <td>{{:: 'server-time' | translate}}</td>
             <td>{{serverInfo.systemInfo.serverTime}}</td>
         </tr>
@@ -32,6 +24,37 @@
         </tr>
     </table>
 
+    <table class="table table-striped table-bordered">
+        <legend>{{:: 'profile' | translate}}</legend>
+
+        <tr>
+            <td width="20%">{{:: 'server-profile' | translate}}</td>
+            <td>{{serverInfo.profileInfo.name | capitalize}}</td>
+        </tr>
+        <tr data-ng-if="serverInfo.profileInfo.disabledFeatures.length > 0">
+            <td width="20%">
+                <span>{{:: 'server-disabled' | translate}}</span>
+                <kc-tooltip>{{:: 'server-disabled.tooltip' | translate}}</kc-tooltip>
+            </td>
+            <td>{{serverInfo.profileInfo.disabledFeatures.sort().join(', ')}}</td>
+
+        </tr>
+        <tr data-ng-if="serverInfo.profileInfo.previewFeatures.length > 0">
+            <td width="20%">
+                <span>{{:: 'server-preview' | translate}}</span>
+                <kc-tooltip>{{:: 'server-preview.tooltip' | translate}}</kc-tooltip>
+            </td>
+            <td>{{serverInfo.profileInfo.previewFeatures.sort().join(', ')}}</td>
+        </tr>
+        <tr data-ng-if="serverInfo.profileInfo.experimentalFeatures.length > 0">
+            <td width="20%">
+                <span>{{:: 'server-experimental' | translate}}</span>
+                <kc-tooltip>{{:: 'server-experimental.tooltip' | translate}}</kc-tooltip>
+            </td>
+            <td>{{serverInfo.profileInfo.experimentalFeatures.sort().join(', ')}}</td>
+        </tr>
+    </table>
+
     <fieldset>
         <legend>{{:: 'memory' | translate}}</legend>
         <table class="table table-striped table-bordered" style="margin-top: 0;">