KEYCLOAK-15773 Control availability of admin api and admin-console via feature flags

Inline profile checks for enabled admin-console to avoid issues during
static initialization with quarkus.

Potentially Re-enable admin-api feature if admin-console is enabled
via the admin/admin2 feature flag.

Add legacy admin console as deprecated feature flag
Throw exception if admin-api feature is disabled but admin-console is enabled

Adapt ProfileTest

Consider adminConsoleEnabled flag in QuarkusWelcomeResource
Fix check for Admin-Console / Admin-API feature dependency.

Add new features to approved help output files

Co-authored-by: Stian Thorgersen <stian@redhat.com>
Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
This commit is contained in:
Thomas Darimont 2020-09-30 17:03:26 +02:00 committed by Pedro Igor
parent 3518362002
commit 962a685b7b
12 changed files with 127 additions and 56 deletions

View file

@ -22,6 +22,7 @@ import static org.keycloak.common.Profile.Type.DEPRECATED;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
@ -89,6 +90,11 @@ public class Profile {
break;
}
}
if ((!disabledFeatures.contains(Feature.ADMIN2) || !disabledFeatures.contains(Feature.ADMIN)) && disabledFeatures.contains(Feature.ADMIN_API)) {
throw new RuntimeException(String.format("Invalid value for feature: %s needs to be enabled because it is required by feature %s.",
Feature.ADMIN_API, Arrays.asList(Feature.ADMIN, Feature.ADMIN2)));
}
}
private static Profile getInstance() {
@ -153,6 +159,22 @@ public class Profile {
ACCOUNT2("New Account Management Console", Type.DEFAULT),
ACCOUNT_API("Account Management REST API", Type.DEFAULT),
ADMIN_FINE_GRAINED_AUTHZ("Fine-Grained Admin Permissions", Type.PREVIEW),
/**
* Controls the availability of the Admin REST-API.
*/
ADMIN_API("Admin API", Type.DEFAULT),
/**
* Controls the availability of the legacy admin-console.
* Note that the admin-console requires the {@link #ADMIN_API} feature.
*/
@Deprecated
ADMIN("Legacy Admin Console", Type.DEPRECATED),
/**
* Controls the availability of the admin-console.
* Note that the admin-console requires the {@link #ADMIN_API} feature.
*/
ADMIN2("New Admin Console", Type.DEFAULT),
DOCKER("Docker Registry protocol", Type.DISABLED_BY_DEFAULT),
IMPERSONATION("Ability for admins to impersonate users", Type.DEFAULT),

View file

@ -24,7 +24,7 @@ public class ProfileTest {
@Test
public void checkDefaultsKeycloak() {
Assert.assertEquals("community", Profile.getName());
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
}
@ -36,7 +36,7 @@ public class ProfileTest {
Profile.init();
Assert.assertEquals("product", Profile.getName());
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
System.setProperty("keycloak.profile", "community");

View file

@ -19,6 +19,7 @@ package org.keycloak.quarkus.runtime.services.resources;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.Profile;
import org.keycloak.common.Version;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.MimeTypeUtil;
@ -173,6 +174,7 @@ public class QuarkusWelcomeResource {
Map<String, Object> map = new HashMap<>();
map.put("adminConsoleEnabled", isAdminConsoleEnabled());
map.put("productName", Version.NAME);
map.put("productNameFull", Version.NAME_FULL);
@ -250,6 +252,10 @@ public class QuarkusWelcomeResource {
return shouldBootstrap.get();
}
private static boolean isAdminConsoleEnabled() {
return Profile.isFeatureEnabled(Profile.Feature.ADMIN2) || Profile.isFeatureEnabled(Profile.Feature.ADMIN);
}
private boolean isLocal() {
try {
ClientConnection clientConnection = session.getContext().getConnection();

View file

@ -44,18 +44,18 @@ Transaction:
Feature:
--features <feature> Enables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
HTTP/TLS:

View file

@ -67,18 +67,18 @@ Transaction:
Feature:
--features <feature> Enables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
Hostname:

View file

@ -128,18 +128,18 @@ Transaction:
Feature:
--features <feature> Enables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
Hostname:

View file

@ -73,18 +73,18 @@ Transaction:
Feature:
--features <feature> Enables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
Hostname:

View file

@ -134,18 +134,18 @@ Transaction:
Feature:
--features <feature> Enables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
Hostname:

View file

@ -19,8 +19,8 @@ package org.keycloak.services.resources;
import com.fasterxml.jackson.core.type.TypeReference;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.util.Resteasy;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.config.ConfigProviderFactory;
@ -101,7 +101,9 @@ public class KeycloakApplication extends Application {
singletons.add(new RobotsResource());
singletons.add(new RealmsResource());
singletons.add(new AdminRoot());
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_API)) {
singletons.add(new AdminRoot());
}
classes.add(ThemeResource.class);
classes.add(JsResource.class);

View file

@ -18,6 +18,7 @@ package org.keycloak.services.resources;
import org.jboss.logging.Logger;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.Profile;
import org.keycloak.common.Version;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.MimeTypeUtil;
@ -176,6 +177,7 @@ public class WelcomeResource {
Map<String, Object> map = new HashMap<>();
map.put("adminConsoleEnabled", isAdminConsoleEnabled());
map.put("productName", Version.NAME);
map.put("productNameFull", Version.NAME_FULL);
@ -215,6 +217,10 @@ public class WelcomeResource {
}
}
private static boolean isAdminConsoleEnabled() {
return Profile.isFeatureEnabled(Profile.Feature.ADMIN2) || Profile.isFeatureEnabled(Profile.Feature.ADMIN);
}
private Theme getTheme() {
try {
return session.theme().getTheme(Theme.Type.WELCOME);

View file

@ -23,9 +23,9 @@ import javax.ws.rs.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import javax.ws.rs.NotAuthorizedException;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.Profile;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.TokenManager;
@ -97,6 +97,11 @@ public class AdminRoot {
*/
@GET
public Response masterRealmAdminConsoleRedirect() {
if (!isAdminConsoleEnabled()) {
return Response.status(Response.Status.NOT_FOUND).build();
}
RealmModel master = new RealmManager(session).getKeycloakAdminstrationRealm();
return Response.status(302).location(
session.getContext().getUri(UrlType.ADMIN).getBaseUriBuilder().path(AdminRoot.class).path(AdminRoot.class, "getAdminConsole").path("/").build(master.getName())
@ -112,6 +117,11 @@ public class AdminRoot {
@Path("index.{html:html}") // expression is actually "index.html" but this is a hack to get around jax-doclet bug
@GET
public Response masterRealmAdminConsoleRedirectHtml() {
if (!isAdminConsoleEnabled()) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return masterRealmAdminConsoleRedirect();
}
@ -142,6 +152,11 @@ public class AdminRoot {
*/
@Path("{realm}/console")
public AdminConsole getAdminConsole(final @PathParam("realm") String name) {
if (!isAdminConsoleEnabled()) {
throw new NotFoundException();
}
RealmManager realmManager = new RealmManager(session);
RealmModel realm = locateRealm(name, realmManager);
AdminConsole service = new AdminConsole(realm);
@ -198,6 +213,11 @@ public class AdminRoot {
*/
@Path("realms")
public Object getRealmsAdmin(@Context final HttpHeaders headers) {
if (!isAdminApiEnabled()) {
throw new NotFoundException();
}
if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
return new AdminCorsPreflightService(request);
}
@ -222,6 +242,11 @@ public class AdminRoot {
*/
@Path("serverinfo")
public Object getServerInfo(@Context final HttpHeaders headers) {
if (!isAdminApiEnabled()) {
throw new NotFoundException();
}
if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
return new AdminCorsPreflightService(request);
}
@ -277,4 +302,11 @@ public class AdminRoot {
}
}
private static boolean isAdminApiEnabled() {
return Profile.isFeatureEnabled(Profile.Feature.ADMIN_API);
}
private static boolean isAdminConsoleEnabled() {
return Profile.isFeatureEnabled(Profile.Feature.ADMIN2) || Profile.isFeatureEnabled(Profile.Feature.ADMIN);
}
}

View file

@ -52,6 +52,7 @@
<h1>Welcome to <strong>${productNameFull}</strong></h1>
</div>
<div class="row">
<#if adminConsoleEnabled>
<div class="col-xs-12 col-sm-4">
<div class="card-pf h-l">
<#if successMessage?has_content>
@ -93,6 +94,7 @@
<button id="create-button" type="submit" class="btn btn-primary">Create</button>
</form>
</#if>
<div class="welcome-primary-link">
<h3><a href="${adminUrl}"><img src="welcome-content/user.png">Administration Console <i class="fa fa-angle-right link" aria-hidden="true"></i></a></h3>
<div class="description">
@ -101,6 +103,7 @@
</div>
</div>
</div>
</#if> <#-- adminConsoleEnabled -->
<div class="col-xs-12 col-sm-4">
<div class="card-pf h-l">
<h3><a href="${properties.documentationUrl}"><img class="doc-img" src="welcome-content/admin-console.png">Documentation <i class="fa fa-angle-right link" aria-hidden="true"></i></a></h3>