keycloak-aplcache
Changes
services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticatorFactory.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java 5(+2 -3)
Details
diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java
index ac16874..91b0a80 100755
--- a/common/src/main/java/org/keycloak/common/Profile.java
+++ b/common/src/main/java/org/keycloak/common/Profile.java
@@ -19,7 +19,14 @@ package org.keycloak.common;
import java.io.File;
import java.io.FileInputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Properties;
+import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -27,43 +34,87 @@ import java.util.Properties;
*/
public class Profile {
+ public enum Feature {
+ AUTHORIZATION, IMPERSONATION, SCRIPTS
+ }
+
private enum ProfileValue {
- PRODUCT, PREVIEW, COMMUNITY
+ PRODUCT(Feature.AUTHORIZATION, Feature.SCRIPTS),
+ PREVIEW,
+ COMMUNITY;
+
+ private List<Feature> disabled;
+
+ ProfileValue() {
+ this.disabled = Collections.emptyList();
+ }
+
+ ProfileValue(Feature... disabled) {
+ this.disabled = Arrays.asList(disabled);
+ }
}
- private static ProfileValue value = load();
+ private static final Profile CURRENT = new Profile();
- static ProfileValue load() {
- String profile = null;
+ private final ProfileValue profile;
+
+ private final Set<Feature> disabledFeatures = new HashSet<>();
+
+ private Profile() {
try {
- profile = System.getProperty("keycloak.profile");
- if (profile == null) {
- String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
- if (jbossServerConfigDir != null) {
- File file = new File(jbossServerConfigDir, "profile.properties");
- if (file.isFile()) {
- Properties props = new Properties();
- props.load(new FileInputStream(file));
- profile = props.getProperty("profile");
+ 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());
+ }
+
+ disabledFeatures.addAll(profile.disabled);
+
+ 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")) {
+ disabledFeatures.add(f);
}
}
}
} catch (Exception e) {
- }
-
- if (profile == null) {
- return ProfileValue.valueOf(Version.DEFAULT_PROFILE.toUpperCase());
- } else {
- return ProfileValue.valueOf(profile.toUpperCase());
+ throw new RuntimeException(e);
}
}
public static String getName() {
- return value.name().toLowerCase();
+ return CURRENT.profile.name().toLowerCase();
+ }
+
+ public static Set<Feature> getDisabledFeatures() {
+ return CURRENT.disabledFeatures;
}
- public static boolean isPreviewEnabled() {
- return value.ordinal() >= ProfileValue.PREVIEW.ordinal();
+ public static boolean isFeatureEnabled(Feature feature) {
+ return !CURRENT.disabledFeatures.contains(feature);
}
}
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 3c474d0..c0cbea8 100644
--- a/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java
@@ -19,18 +19,26 @@ package org.keycloak.representations.info;
import org.keycloak.common.Profile;
+import java.util.LinkedList;
+import java.util.List;
+
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ProfileInfoRepresentation {
private String name;
- private boolean previewEnabled;
+ private List<String> disabledFeatures;
public static ProfileInfoRepresentation create() {
ProfileInfoRepresentation info = new ProfileInfoRepresentation();
- info.setName(Profile.getName());
- info.setPreviewEnabled(Profile.isPreviewEnabled());
+
+ info.name = Profile.getName();
+ info.disabledFeatures = new LinkedList<>();
+ for (Profile.Feature f : Profile.getDisabledFeatures()) {
+ info.disabledFeatures.add(f.name());
+ }
+
return info;
}
@@ -38,16 +46,8 @@ public class ProfileInfoRepresentation {
return name;
}
- public void setName(String name) {
- this.name = name;
- }
-
- public boolean isPreviewEnabled() {
- return previewEnabled;
- }
-
- public void setPreviewEnabled(boolean previewEnabled) {
- this.previewEnabled = previewEnabled;
+ public List<String> getDisabledFeatures() {
+ return disabledFeatures;
}
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticatorFactory.java
index b2e8b98..4a1003c 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticatorFactory.java
@@ -153,6 +153,6 @@ public class ScriptBasedAuthenticatorFactory implements AuthenticatorFactory, En
@Override
public boolean isSupported() {
- return Profile.isPreviewEnabled();
+ return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 1275022..c97a8f5 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -164,7 +164,7 @@ public class ClientResource {
RepresentationToModel.updateClient(rep, client);
- if (Profile.isPreviewEnabled()) {
+ if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
authorization().enable();
} else {
@@ -190,7 +190,7 @@ public class ClientResource {
ClientRepresentation representation = ModelToRepresentation.toRepresentation(client);
- if (Profile.isPreviewEnabled()) {
+ if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
representation.setAuthorizationServicesEnabled(authorization().isEnabled());
}
@@ -577,7 +577,7 @@ public class ClientResource {
@Path("/authz")
public AuthorizationService authorization() {
- ProfileHelper.requirePreview();
+ ProfileHelper.requireFeature(Profile.Feature.AUTHORIZATION);
AuthorizationService resource = new AuthorizationService(this.session, this.client, this.auth);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 49e719b..99a0862 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -23,6 +23,7 @@ import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.common.ClientConnection;
+import org.keycloak.common.Profile;
import org.keycloak.common.util.Time;
import org.keycloak.credential.CredentialModel;
import org.keycloak.email.EmailException;
@@ -70,6 +71,7 @@ import org.keycloak.models.UserManager;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.AccountService;
import org.keycloak.services.validation.Validation;
+import org.keycloak.utils.ProfileHelper;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -318,6 +320,8 @@ public class UsersResource {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public Map<String, Object> impersonate(final @PathParam("id") String id) {
+ ProfileHelper.requireFeature(Profile.Feature.IMPERSONATION);
+
auth.init(RealmAuth.Resource.IMPERSONATION);
auth.requireManage();
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 3ee7938..1508042 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -22,6 +22,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.AuthorizationService;
import org.keycloak.common.ClientConnection;
+import org.keycloak.common.Profile;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientModel;
@@ -264,7 +265,7 @@ public class RealmsResource {
@Path("{realm}/authz")
public Object getAuthorizationService(@PathParam("realm") String name) {
- ProfileHelper.requirePreview();
+ ProfileHelper.requireFeature(Profile.Feature.AUTHORIZATION);
init(name);
AuthorizationProvider authorization = this.session.getProvider(AuthorizationProvider.class);
diff --git a/services/src/main/java/org/keycloak/utils/ProfileHelper.java b/services/src/main/java/org/keycloak/utils/ProfileHelper.java
index 719bd24..b1a29f4 100644
--- a/services/src/main/java/org/keycloak/utils/ProfileHelper.java
+++ b/services/src/main/java/org/keycloak/utils/ProfileHelper.java
@@ -27,9 +27,9 @@ import javax.ws.rs.core.Response;
*/
public class ProfileHelper {
- public static void requirePreview() {
- if (!Profile.isPreviewEnabled()) {
- throw new WebApplicationException("Feature not available in current profile", Response.Status.NOT_IMPLEMENTED);
+ public static void requireFeature(Profile.Feature feature) {
+ if (!Profile.isFeatureEnabled(feature)) {
+ throw new WebApplicationException("Feature not enabled", Response.Status.NOT_IMPLEMENTED);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java
index 7fbf59b..9dd598f 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java
@@ -26,11 +26,11 @@ import org.keycloak.common.Profile;
public class ProfileAssume {
public static void assumePreview() {
- Assume.assumeTrue("Ignoring test as community/preview profile is not enabled", Profile.isPreviewEnabled());
+ Assume.assumeTrue("Ignoring test as community/preview profile is not enabled", !Profile.getName().equals("product"));
}
public static void assumePreviewDisabled() {
- Assume.assumeFalse("Ignoring test as community/preview profile is enabled", Profile.isPreviewEnabled());
+ Assume.assumeFalse("Ignoring test as community/preview profile is enabled", !Profile.getName().equals("product"));
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
index 18c1616..fc8b25b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin.authentication;
import org.junit.Test;
import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory;
+import org.keycloak.common.Profile;
import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation;
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.testsuite.Assert;
@@ -32,8 +33,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import static org.keycloak.common.Profile.isPreviewEnabled;
-
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@@ -137,7 +136,7 @@ public class ProvidersTest extends AbstractAuthenticationTest {
"Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions.");
addProviderInfo(result, "auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server.");
addProviderInfo(result, "auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form.");
- if (isPreviewEnabled()) {
+ if (Profile.isFeatureEnabled(Profile.Feature.SCRIPTS)) {
addProviderInfo(result, "auth-script-based", "Script", "Script based authentication. Allows to define custom authentication logic via JavaScript.");
}
addProviderInfo(result, "auth-spnego", "Kerberos", "Initiates the SPNEGO protocol. Most often used with Kerberos.");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
index 3e16c4b..3c296a1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
@@ -379,7 +379,7 @@ public class ExportImportUtil {
Assert.assertNotNull(linked);
Assert.assertEquals("my-service-user", linked.getUsername());
- if (Profile.isPreviewEnabled()) {
+ if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
assertAuthorizationSettings(realmRsc);
}
}
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 88dbfcd..7045050 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
@@ -898,6 +898,7 @@ 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
info=Info
providers=Providers
server-time=Server Time
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 2dc08ea..af99e7d 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -110,7 +110,7 @@
<input ng-model="client.serviceAccountsEnabled" name="serviceAccountsEnabled" id="serviceAccountsEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
</div>
- <div class="form-group" data-ng-show="serverInfo.profileInfo.previewEnabled && protocol == 'openid-connect'">
+ <div class="form-group" data-ng-show="serverInfo.profileInfo.disabledFeatures.indexOf('AUTHORIZATION') == -1 && protocol == 'openid-connect'">
<label class="col-md-2 control-label" for="authorizationServicesEnabled">{{:: 'authz-authorization-services-enabled' | translate}}</label>
<kc-tooltip>{{:: 'authz-authorization-services-enabled.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
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 299a93b..4a4b296 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
@@ -16,7 +16,11 @@
</tr>
<tr>
<td width="20%">{{:: 'server-profile' | translate}}</td>
- <td>{{serverInfo.profileInfo.name}}</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>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html
index 36510c5..4864acf 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html
@@ -31,7 +31,7 @@
<th class="w-15">{{:: 'email' | translate}}</th>
<th class="w-15">{{:: 'last-name' | translate}}</th>
<th class="w-15">{{:: 'first-name' | translate}}</th>
- <th colspan="{{access.impersonation == true ? '3' : '2'}}">{{:: 'actions' | translate}}</th>
+ <th colspan="{{serverInfo.profileInfo.disabledFeatures.indexOf('IMPERSONATION') == -1 && access.impersonation == true ? '3' : '2'}}">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tfoot data-ng-show="users && (users.length >= query.max || query.first > 0)">
@@ -53,7 +53,7 @@
<td class="clip">{{user.lastName}}</td>
<td class="clip">{{user.firstName}}</td>
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">{{:: 'edit' | translate}}</td>
- <td data-ng-show="access.impersonation" class="kc-action-cell" data-ng-click="impersonate(user.id)">{{:: 'impersonate' | translate}}</td>
+ <td data-ng-show="serverInfo.profileInfo.disabledFeatures.indexOf('IMPERSONATION') == -1 && access.impersonation" class="kc-action-cell" data-ng-click="impersonate(user.id)">{{:: 'impersonate' | translate}}</td>
<td data-ng-show="access.manageUsers" class="kc-action-cell" data-ng-click="removeUser(user)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="!users || users.length == 0">