From ff4f11637b401d7019425a08862eb974e9deff4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Wed, 20 Mar 2024 12:45:24 +0100 Subject: [PATCH] Move Keycloak Enforcer Tenant config to runtime and allow multiple paths --- .../security-keycloak-authorization.adoc | 16 +-- .../KeycloakPolicyEnforcerBuildStep.java | 52 ++----- .../KeycloakPolicyEnforcerRecorder.java | 132 ++++++++++++++---- .../KeycloakPolicyEnforcerTenantConfig.java | 7 + .../RequireBodyHandlerBuildItem.java | 44 ++++++ .../http/deployment/VertxHttpProcessor.java | 11 +- .../vertx/http/runtime/VertxHttpRecorder.java | 33 +++-- .../src/main/resources/application.properties | 57 +++----- 8 files changed, 214 insertions(+), 138 deletions(-) diff --git a/docs/src/main/asciidoc/security-keycloak-authorization.adoc b/docs/src/main/asciidoc/security-keycloak-authorization.adoc index 36ca2db1514e4..94d844883ebc9 100644 --- a/docs/src/main/asciidoc/security-keycloak-authorization.adoc +++ b/docs/src/main/asciidoc/security-keycloak-authorization.adoc @@ -413,7 +413,7 @@ There's no need to deactivate policy checks for a Keycloak Authorization Policy [source,properties] ---- -quarkus.keycloak.policy-enforcer.paths.1.path=/api/public +quarkus.keycloak.policy-enforcer.paths.1.paths=/api/public quarkus.keycloak.policy-enforcer.paths.1.enforcement-mode=DISABLED ---- @@ -421,7 +421,7 @@ To block access to the public resource to anonymous users, you can create an enf [source,properties] ---- -quarkus.keycloak.policy-enforcer.paths.1.path=/api/public-enforcing +quarkus.keycloak.policy-enforcer.paths.1.paths=/api/public-enforcing quarkus.keycloak.policy-enforcer.paths.1.enforcement-mode=ENFORCING ---- @@ -438,15 +438,13 @@ For example: ---- # path policy with enforced scope 'read' for method 'GET' quarkus.keycloak.policy-enforcer.paths.1.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.1.path=/api/protected/standard-way +quarkus.keycloak.policy-enforcer.paths.1.paths=/api/protected/standard-way quarkus.keycloak.policy-enforcer.paths.1.methods.get.method=GET quarkus.keycloak.policy-enforcer.paths.1.methods.get.scopes=read <1> # path policies without scope quarkus.keycloak.policy-enforcer.paths.2.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.2.path=/api/protected/programmatic-way -quarkus.keycloak.policy-enforcer.paths.3.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.3.path=/api/protected/annotation-way +quarkus.keycloak.policy-enforcer.paths.2.paths=/api/protected/programmatic-way,/api/protected/annotation-way ---- <1> User must have resource permission 'Scope Permission Resource' and scope 'read' @@ -532,7 +530,7 @@ quarkus.oidc.credentials.secret=secret quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE quarkus.keycloak.policy-enforcer.paths.1.name=Permission Resource -quarkus.keycloak.policy-enforcer.paths.1.path=/api/permission +quarkus.keycloak.policy-enforcer.paths.1.paths=/api/permission quarkus.keycloak.policy-enforcer.paths.1.claim-information-point.claims.static-claim=static-claim # Service Tenant @@ -543,7 +541,7 @@ quarkus.oidc.service-tenant.credentials.secret=secret quarkus.keycloak.service-tenant.policy-enforcer.enforcement-mode=PERMISSIVE quarkus.keycloak.service-tenant.policy-enforcer.paths.1.name=Permission Resource Service -quarkus.keycloak.service-tenant.policy-enforcer.paths.1.path=/api/permission +quarkus.keycloak.service-tenant.policy-enforcer.paths.1.paths=/api/permission quarkus.keycloak.service-tenant.policy-enforcer.paths.1.claim-information-point.claims.static-claim=static-claim @@ -557,7 +555,7 @@ quarkus.oidc.webapp-tenant.roles.source=accesstoken quarkus.keycloak.webapp-tenant.policy-enforcer.enforcement-mode=PERMISSIVE quarkus.keycloak.webapp-tenant.policy-enforcer.paths.1.name=Permission Resource WebApp -quarkus.keycloak.webapp-tenant.policy-enforcer.paths.1.path=/api/permission +quarkus.keycloak.webapp-tenant.policy-enforcer.paths.1.paths=/api/permission quarkus.keycloak.webapp-tenant.policy-enforcer.paths.1.claim-information-point.claims.static-claim=static-claim ---- diff --git a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java index 6707c41021fd7..f7968dd2f9248 100644 --- a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java +++ b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java @@ -1,6 +1,5 @@ package io.quarkus.keycloak.pep.deployment; -import java.util.Map; import java.util.function.BooleanSupplier; import jakarta.inject.Singleton; @@ -17,60 +16,29 @@ import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerBuildTimeConfig; import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerConfig; import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerRecorder; -import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerTenantConfig; -import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerTenantConfig.KeycloakConfigPolicyEnforcer.PathConfig; import io.quarkus.keycloak.pep.runtime.PolicyEnforcerResolver; import io.quarkus.oidc.deployment.OidcBuildTimeConfig; import io.quarkus.oidc.runtime.OidcConfig; import io.quarkus.runtime.TlsConfig; import io.quarkus.vertx.http.deployment.RequireBodyHandlerBuildItem; +import io.quarkus.vertx.http.runtime.HttpConfiguration; @BuildSteps(onlyIf = KeycloakPolicyEnforcerBuildStep.IsEnabled.class) public class KeycloakPolicyEnforcerBuildStep { + @Record(ExecutionTime.RUNTIME_INIT) @BuildStep - RequireBodyHandlerBuildItem requireBody(OidcBuildTimeConfig oidcBuildTimeConfig, KeycloakPolicyEnforcerConfig config) { + RequireBodyHandlerBuildItem requireBody(OidcBuildTimeConfig oidcBuildTimeConfig, + KeycloakPolicyEnforcerRecorder recorder, + KeycloakPolicyEnforcerConfig runtimeConfig) { if (oidcBuildTimeConfig.enabled) { - if (isBodyHandlerRequired(config.defaultTenant)) { - return new RequireBodyHandlerBuildItem(); - } - for (KeycloakPolicyEnforcerTenantConfig tenantConfig : config.namedTenants.values()) { - if (isBodyHandlerRequired(tenantConfig)) { - return new RequireBodyHandlerBuildItem(); - } - } + return new RequireBodyHandlerBuildItem(recorder.createBodyHandlerRequiredEvaluator(runtimeConfig)); } return null; } - private static boolean isBodyHandlerRequired(KeycloakPolicyEnforcerTenantConfig config) { - if (isBodyClaimInformationPointDefined(config.policyEnforcer.claimInformationPoint.simpleConfig)) { - return true; - } - for (PathConfig path : config.policyEnforcer.paths.values()) { - if (isBodyClaimInformationPointDefined(path.claimInformationPoint.simpleConfig)) { - return true; - } - } - return false; - } - - private static boolean isBodyClaimInformationPointDefined(Map> claims) { - for (Map.Entry> entry : claims.entrySet()) { - Map value = entry.getValue(); - - for (String nestedValue : value.values()) { - if (nestedValue.contains("request.body")) { - return true; - } - } - } - - return false; - } - @BuildStep - public AdditionalBeanBuildItem beans(OidcBuildTimeConfig oidcBuildTimeConfig, KeycloakPolicyEnforcerConfig config) { + public AdditionalBeanBuildItem beans(OidcBuildTimeConfig oidcBuildTimeConfig) { if (oidcBuildTimeConfig.enabled) { return AdditionalBeanBuildItem.builder().setUnremovable() .addBeanClass(KeycloakPolicyEnforcerAuthorizer.class).build(); @@ -86,12 +54,12 @@ ExtensionSslNativeSupportBuildItem enableSslInNative() { @Record(ExecutionTime.RUNTIME_INIT) @BuildStep public SyntheticBeanBuildItem setup(OidcBuildTimeConfig oidcBuildTimeConfig, OidcConfig oidcRunTimeConfig, - TlsConfig tlsConfig, - KeycloakPolicyEnforcerConfig keycloakConfig, KeycloakPolicyEnforcerRecorder recorder) { + TlsConfig tlsConfig, KeycloakPolicyEnforcerConfig keycloakConfig, KeycloakPolicyEnforcerRecorder recorder, + HttpConfiguration httpConfiguration) { if (oidcBuildTimeConfig.enabled) { return SyntheticBeanBuildItem.configure(PolicyEnforcerResolver.class).unremovable() .types(PolicyEnforcerResolver.class) - .supplier(recorder.setup(oidcRunTimeConfig, keycloakConfig, tlsConfig)) + .supplier(recorder.setup(oidcRunTimeConfig, keycloakConfig, tlsConfig, httpConfiguration)) .scope(Singleton.class) .setRuntimeInit() .done(); diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerRecorder.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerRecorder.java index 7165eb7e39c79..6912a69e3d2b2 100644 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerRecorder.java +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerRecorder.java @@ -2,17 +2,24 @@ import java.net.URI; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.keycloak.adapters.authorization.PolicyEnforcer; import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig; import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerTenantConfig.KeycloakConfigPolicyEnforcer.ClaimInformationPointConfig; +import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerTenantConfig.KeycloakConfigPolicyEnforcer.MethodConfig; import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerTenantConfig.KeycloakConfigPolicyEnforcer.PathCacheConfig; +import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerTenantConfig.KeycloakConfigPolicyEnforcer.PathConfig; import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.OidcTenantConfig.ApplicationType; @@ -26,14 +33,26 @@ @Recorder public class KeycloakPolicyEnforcerRecorder { - final HttpConfiguration httpConfiguration; - public KeycloakPolicyEnforcerRecorder(HttpConfiguration httpConfiguration) { - this.httpConfiguration = httpConfiguration; + public BooleanSupplier createBodyHandlerRequiredEvaluator(KeycloakPolicyEnforcerConfig config) { + return new BooleanSupplier() { + @Override + public boolean getAsBoolean() { + if (isBodyHandlerRequired(config.defaultTenant)) { + return true; + } + for (KeycloakPolicyEnforcerTenantConfig tenantConfig : config.namedTenants.values()) { + if (isBodyHandlerRequired(tenantConfig)) { + return true; + } + } + return false; + } + }; } public Supplier setup(OidcConfig oidcConfig, KeycloakPolicyEnforcerConfig config, - TlsConfig tlsConfig) { + TlsConfig tlsConfig, HttpConfiguration httpConfiguration) { PolicyEnforcer defaultPolicyEnforcer = createPolicyEnforcer(oidcConfig.defaultTenant, config.defaultTenant, tlsConfig); Map policyEnforcerTenants = new HashMap(); for (Map.Entry tenant : config.namedTenants.entrySet()) { @@ -97,8 +116,7 @@ private static PolicyEnforcer createPolicyEnforcer(OidcTenantConfig oidcConfig, adapterConfig.setProxyUrl(host + ":" + oidcConfig.proxy.port); } - PolicyEnforcerConfig enforcerConfig = getPolicyEnforcerConfig(keycloakPolicyEnforcerConfig, - adapterConfig); + PolicyEnforcerConfig enforcerConfig = getPolicyEnforcerConfig(keycloakPolicyEnforcerConfig); adapterConfig.setPolicyEnforcerConfig(enforcerConfig); @@ -138,8 +156,7 @@ private static Map> getClaimInformationPointConfig(C return cipConfig; } - private static PolicyEnforcerConfig getPolicyEnforcerConfig(KeycloakPolicyEnforcerTenantConfig config, - AdapterConfig adapterConfig) { + private static PolicyEnforcerConfig getPolicyEnforcerConfig(KeycloakPolicyEnforcerTenantConfig config) { PolicyEnforcerConfig enforcerConfig = new PolicyEnforcerConfig(); enforcerConfig.setLazyLoadPaths(config.policyEnforcer.lazyLoadPaths); @@ -155,29 +172,86 @@ private static PolicyEnforcerConfig getPolicyEnforcerConfig(KeycloakPolicyEnforc enforcerConfig.setClaimInformationPointConfig( getClaimInformationPointConfig(config.policyEnforcer.claimInformationPoint)); - enforcerConfig.setPaths(config.policyEnforcer.paths.values().stream().map( - pathConfig -> { - PolicyEnforcerConfig.PathConfig config1 = new PolicyEnforcerConfig.PathConfig(); - - config1.setName(pathConfig.name.orElse(null)); - config1.setPath(pathConfig.path.orElse(null)); - config1.setEnforcementMode(pathConfig.enforcementMode); - config1.setMethods(pathConfig.methods.values().stream().map( - methodConfig -> { - PolicyEnforcerConfig.MethodConfig mConfig = new PolicyEnforcerConfig.MethodConfig(); - - mConfig.setMethod(methodConfig.method); - mConfig.setScopes(methodConfig.scopes); - mConfig.setScopesEnforcementMode(methodConfig.scopesEnforcementMode); - - return mConfig; - }).collect(Collectors.toList())); - config1.setClaimInformationPointConfig( - getClaimInformationPointConfig(pathConfig.claimInformationPoint)); - - return config1; + enforcerConfig.setPaths(config.policyEnforcer.paths.values().stream().flatMap( + new Function>() { + @Override + public Stream apply(PathConfig pathConfig) { + var paths = getPathConfigPaths(pathConfig); + if (paths.isEmpty()) { + return Stream.of(createKeycloakPathConfig(pathConfig, null)); + } else { + return paths.stream().map(new Function() { + @Override + public PolicyEnforcerConfig.PathConfig apply(String path) { + return createKeycloakPathConfig(pathConfig, path); + } + }); + } + } }).collect(Collectors.toList())); return enforcerConfig; } + + private static Set getPathConfigPaths(PathConfig pathConfig) { + Set paths = new HashSet<>(); + if (pathConfig.path.isPresent()) { + paths.add(pathConfig.path.get()); + } + if (pathConfig.paths.isPresent()) { + paths.addAll(pathConfig.paths.get()); + } + return paths; + } + + private static PolicyEnforcerConfig.PathConfig createKeycloakPathConfig(PathConfig pathConfig, String path) { + PolicyEnforcerConfig.PathConfig config1 = new PolicyEnforcerConfig.PathConfig(); + + config1.setName(pathConfig.name.orElse(null)); + config1.setPath(path); + config1.setEnforcementMode(pathConfig.enforcementMode); + config1.setMethods(pathConfig.methods.values().stream().map( + new Function() { + @Override + public PolicyEnforcerConfig.MethodConfig apply(MethodConfig methodConfig) { + PolicyEnforcerConfig.MethodConfig mConfig = new PolicyEnforcerConfig.MethodConfig(); + + mConfig.setMethod(methodConfig.method); + mConfig.setScopes(methodConfig.scopes); + mConfig.setScopesEnforcementMode(methodConfig.scopesEnforcementMode); + + return mConfig; + } + }).collect(Collectors.toList())); + config1.setClaimInformationPointConfig( + getClaimInformationPointConfig(pathConfig.claimInformationPoint)); + return config1; + } + + private static boolean isBodyHandlerRequired(KeycloakPolicyEnforcerTenantConfig config) { + if (isBodyClaimInformationPointDefined(config.policyEnforcer.claimInformationPoint.simpleConfig)) { + return true; + } + for (PathConfig path : config.policyEnforcer.paths + .values()) { + if (isBodyClaimInformationPointDefined(path.claimInformationPoint.simpleConfig)) { + return true; + } + } + return false; + } + + private static boolean isBodyClaimInformationPointDefined(Map> claims) { + for (Map.Entry> entry : claims.entrySet()) { + Map value = entry.getValue(); + + for (String nestedValue : value.values()) { + if (nestedValue.contains("request.body")) { + return true; + } + } + } + + return false; + } } diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerTenantConfig.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerTenantConfig.java index bacdca9a5f291..13d83057d824a 100644 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerTenantConfig.java +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerTenantConfig.java @@ -84,9 +84,16 @@ public static class PathConfig { /** * A URI relative to the application’s context path that should be protected by the policy enforcer */ + @Deprecated(since = "Quarkus 3.10") // use the 'paths' configuration property @ConfigItem public Optional path; + /** + * HTTP request paths that should be protected by the policy enforcer + */ + @ConfigItem + public Optional> paths; + /** * The HTTP methods (for example, GET, POST, PATCH) to protect and how they are associated with the scopes for a * given diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java index dbfd8541499a3..ba065549b83ed 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java @@ -1,10 +1,54 @@ package io.quarkus.vertx.http.deployment; +import java.util.List; +import java.util.Objects; +import java.util.function.BooleanSupplier; + import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.vertx.http.runtime.VertxHttpRecorder; /** * This is a marker that indicates that the body handler should be installed * on all routes, as an extension requires the request to be fully buffered. */ public final class RequireBodyHandlerBuildItem extends MultiBuildItem { + + private final BooleanSupplier bodyHandlerRequiredCondition; + + /** + * Creates {@link RequireBodyHandlerBuildItem} that requires body handler unconditionally installed on all routes. + */ + public RequireBodyHandlerBuildItem() { + bodyHandlerRequiredCondition = null; + } + + /** + * Creates {@link RequireBodyHandlerBuildItem} that requires body handler installed on all routes if the supplier returns + * true. + * + * @param bodyHandlerRequiredCondition supplier that returns true at runtime if the body handler should be created + */ + public RequireBodyHandlerBuildItem(BooleanSupplier bodyHandlerRequiredCondition) { + this.bodyHandlerRequiredCondition = bodyHandlerRequiredCondition; + } + + private BooleanSupplier getBodyHandlerRequiredCondition() { + return bodyHandlerRequiredCondition; + } + + public static BooleanSupplier[] getBodyHandlerRequiredConditions(List bodyHandlerBuildItems) { + if (bodyHandlerBuildItems.isEmpty()) { + return new BooleanSupplier[] {}; + } + BooleanSupplier[] customRuntimeConditions = bodyHandlerBuildItems + .stream() + .map(RequireBodyHandlerBuildItem::getBodyHandlerRequiredCondition) + .filter(Objects::nonNull) + .toArray(BooleanSupplier[]::new); + if (customRuntimeConditions.length == bodyHandlerBuildItems.size()) { + return customRuntimeConditions; + } + // at least one item requires body handler unconditionally + return new BooleanSupplier[] { new VertxHttpRecorder.AlwaysCreateBodyHandlerSupplier() }; + } } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index dc19568107b9a..d30df7e5e7b2f 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -1,6 +1,7 @@ package io.quarkus.vertx.http.deployment; import static io.quarkus.runtime.TemplateHtmlBuilder.adjustRoot; +import static io.quarkus.vertx.http.deployment.RequireBodyHandlerBuildItem.getBodyHandlerRequiredConditions; import static io.quarkus.vertx.http.deployment.RouteBuildItem.RouteType.FRAMEWORK_ROUTE; import java.io.IOException; @@ -67,11 +68,9 @@ import io.quarkus.vertx.http.runtime.filters.Filter; import io.quarkus.vertx.http.runtime.filters.GracefulShutdownFilter; import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig; -import io.vertx.core.Handler; import io.vertx.core.http.impl.Http1xServerRequest; import io.vertx.core.impl.VertxImpl; import io.vertx.ext.web.Router; -import io.vertx.ext.web.RoutingContext; class VertxHttpProcessor { @@ -362,10 +361,6 @@ ServiceStartBuildItem finalizeRouter( .filter(f -> f.getHandler() != null) .map(ManagementInterfaceFilterBuildItem::toFilter).collect(Collectors.toList()); - //if the body handler is required then we know it is installed for all routes, so we don't need to register it here - Handler bodyHandler = !requireBodyHandlerBuildItems.isEmpty() ? bodyHandlerBuildItem.getHandler() - : null; - Optional> mainRouter = httpRouteRouter.getMainRouter() != null ? Optional.of(httpRouteRouter.getMainRouter()) : Optional.empty(); @@ -396,8 +391,8 @@ ServiceStartBuildItem finalizeRouter( httpRootPathBuildItem.getRootPath(), nonApplicationRootPathBuildItem.getNonApplicationRootPath(), launchMode.getLaunchMode(), - !requireBodyHandlerBuildItems.isEmpty(), bodyHandler, gracefulShutdownFilter, - shutdownConfig, executorBuildItem.getExecutorProxy()); + getBodyHandlerRequiredConditions(requireBodyHandlerBuildItems), bodyHandlerBuildItem.getHandler(), + gracefulShutdownFilter, shutdownConfig, executorBuildItem.getExecutorProxy()); return new ServiceStartBuildItem("vertx-http"); } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index b2ea34acc7f61..ac5ca3a408310 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -345,7 +345,7 @@ public void finalizeRouter(BeanContainer container, Consumer defaultRoute RuntimeValue httpRouterRuntimeValue, RuntimeValue mutinyRouter, RuntimeValue frameworkRouter, RuntimeValue managementRouter, String rootPath, String nonRootPath, - LaunchMode launchMode, boolean requireBodyHandler, + LaunchMode launchMode, BooleanSupplier[] requireBodyHandlerConditions, Handler bodyHandler, GracefulShutdownFilter gracefulShutdownFilter, ShutdownConfig shutdownConfig, Executor executor) { @@ -387,16 +387,19 @@ public void finalizeRouter(BeanContainer container, Consumer defaultRoute httpRouteRouter.route().last().failureHandler( new QuarkusErrorHandler(launchMode.isDevOrTest(), httpConfiguration.unhandledErrorContentTypeDefault)); - if (requireBodyHandler) { - //if this is set then everything needs the body handler installed - //TODO: config etc - httpRouteRouter.route().order(RouteConstants.ROUTE_ORDER_BODY_HANDLER).handler(new Handler() { - @Override - public void handle(RoutingContext routingContext) { - routingContext.request().resume(); - bodyHandler.handle(routingContext); - } - }); + for (BooleanSupplier requireBodyHandlerCondition : requireBodyHandlerConditions) { + if (requireBodyHandlerCondition.getAsBoolean()) { + //if this is set then everything needs the body handler installed + //TODO: config etc + httpRouteRouter.route().order(RouteConstants.ROUTE_ORDER_BODY_HANDLER).handler(new Handler() { + @Override + public void handle(RoutingContext routingContext) { + routingContext.request().resume(); + bodyHandler.handle(routingContext); + } + }); + break; + } } HttpServerCommonHandlers.enforceMaxBodySize(httpConfiguration.limits, httpRouteRouter); @@ -1490,4 +1493,12 @@ public void accept(Cookie cookie, HttpServerRequest request) { } }; } + + public static class AlwaysCreateBodyHandlerSupplier implements BooleanSupplier { + + @Override + public boolean getAsBoolean() { + return true; + } + } } diff --git a/integration-tests/keycloak-authorization/src/main/resources/application.properties b/integration-tests/keycloak-authorization/src/main/resources/application.properties index a8714fb1b906a..18e8a230fc3cf 100644 --- a/integration-tests/keycloak-authorization/src/main/resources/application.properties +++ b/integration-tests/keycloak-authorization/src/main/resources/application.properties @@ -21,11 +21,11 @@ quarkus.keycloak.policy-enforcer.paths.1.path=/api/permission quarkus.keycloak.policy-enforcer.paths.1.claim-information-point.claims.static-claim=static-claim # Defines a claim which value references a request parameter -quarkus.keycloak.policy-enforcer.paths.2.path=/api/permission/claim-protected +quarkus.keycloak.policy-enforcer.paths.2.paths=/api/permission/claim-protected quarkus.keycloak.policy-enforcer.paths.2.claim-information-point.claims.grant={request.parameter['grant']} # Defines a claim which value is based on the response from an external service -quarkus.keycloak.policy-enforcer.paths.3.path=/api/permission/http-response-claim-protected +quarkus.keycloak.policy-enforcer.paths.3.paths=/api/permission/http-response-claim-protected quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.claims.user-name=/userName quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.url=http://localhost:8081/api/users/me quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.method=GET @@ -33,61 +33,40 @@ quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.headers.Co quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.headers.Authorization=Bearer {keycloak.access_token} # Disables policy enforcement for a path -quarkus.keycloak.policy-enforcer.paths.4.path=/api/public +quarkus.keycloak.policy-enforcer.paths.4.paths=/api/public,/api/public-token quarkus.keycloak.policy-enforcer.paths.4.enforcement-mode=DISABLED # Defines a claim which value is based on the response from an external service -quarkus.keycloak.policy-enforcer.paths.5.path=/api/permission/body-claim +quarkus.keycloak.policy-enforcer.paths.5.paths=/api/permission/body-claim quarkus.keycloak.policy-enforcer.paths.5.claim-information-point.claims.from-body={request.body['/from-body']} quarkus.keycloak.policy-enforcer.paths.6.name=Root -quarkus.keycloak.policy-enforcer.paths.6.path=/* +quarkus.keycloak.policy-enforcer.paths.6.paths=/* quarkus.keycloak.policy-enforcer.paths.6.enforcement-mode=DISABLED quarkus.keycloak.policy-enforcer.paths.7.name=API -quarkus.keycloak.policy-enforcer.paths.7.path=/api2/* +quarkus.keycloak.policy-enforcer.paths.7.paths=/api2/* quarkus.keycloak.policy-enforcer.paths.7.enforcement-mode=ENFORCING quarkus.keycloak.policy-enforcer.paths.8.name=Public -quarkus.keycloak.policy-enforcer.paths.8.path=/hello +quarkus.keycloak.policy-enforcer.paths.8.paths=/hello quarkus.keycloak.policy-enforcer.paths.8.enforcement-mode=DISABLED quarkus.keycloak.policy-enforcer.paths.9.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.9.path=/api/permission/scope +quarkus.keycloak.policy-enforcer.paths.9.paths=/api/permission/scope,/api/permission/scopes/annotation-way-denied,/api/permission/scopes/programmatic-way-denied,/api/permission/annotation/scope-read,/api/permission/annotation/scope-write,/api/permission/scopes/programmatic-way,/api/permission/scopes/annotation-way -quarkus.keycloak.policy-enforcer.paths.10.path=/api/public-enforcing +quarkus.keycloak.policy-enforcer.paths.10.paths=/api/public-enforcing quarkus.keycloak.policy-enforcer.paths.10.enforcement-mode=ENFORCING -quarkus.keycloak.policy-enforcer.paths.11.path=/api/public-token -quarkus.keycloak.policy-enforcer.paths.11.enforcement-mode=DISABLED +quarkus.keycloak.policy-enforcer.paths.11.name=Scope Permission Resource +quarkus.keycloak.policy-enforcer.paths.11.paths=/api/permission/scopes/standard-way +quarkus.keycloak.policy-enforcer.paths.11.methods.get.method=GET +quarkus.keycloak.policy-enforcer.paths.11.methods.get.scopes=read quarkus.keycloak.policy-enforcer.paths.12.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.12.path=/api/permission/annotation/scope-read - -quarkus.keycloak.policy-enforcer.paths.13.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.13.path=/api/permission/annotation/scope-write - -quarkus.keycloak.policy-enforcer.paths.14.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.14.path=/api/permission/scopes/programmatic-way - -quarkus.keycloak.policy-enforcer.paths.15.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.15.path=/api/permission/scopes/standard-way -quarkus.keycloak.policy-enforcer.paths.15.methods.get.method=GET -quarkus.keycloak.policy-enforcer.paths.15.methods.get.scopes=read - -quarkus.keycloak.policy-enforcer.paths.16.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.16.path=/api/permission/scopes/annotation-way - -quarkus.keycloak.policy-enforcer.paths.17.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.17.path=/api/permission/scopes/standard-way-denied -quarkus.keycloak.policy-enforcer.paths.17.methods.get.method=GET -quarkus.keycloak.policy-enforcer.paths.17.methods.get.scopes=write - -quarkus.keycloak.policy-enforcer.paths.18.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.18.path=/api/permission/scopes/annotation-way-denied - -quarkus.keycloak.policy-enforcer.paths.19.name=Scope Permission Resource -quarkus.keycloak.policy-enforcer.paths.19.path=/api/permission/scopes/programmatic-way-denied +quarkus.keycloak.policy-enforcer.paths.12.paths=/api/permission/scopes/standard-way-denied +quarkus.keycloak.policy-enforcer.paths.12.methods.get.method=GET +quarkus.keycloak.policy-enforcer.paths.12.methods.get.scopes=write # Service Tenant quarkus.oidc.api-permission-tenant.auth-server-url=${quarkus.oidc.auth-server-url} @@ -95,7 +74,7 @@ quarkus.oidc.api-permission-tenant.client-id=quarkus-app quarkus.oidc.api-permission-tenant.credentials.secret=secret quarkus.keycloak.api-permission-tenant.policy-enforcer.paths.1.name=Permission Resource Tenant -quarkus.keycloak.api-permission-tenant.policy-enforcer.paths.1.path=/api-permission-tenant +quarkus.keycloak.api-permission-tenant.policy-enforcer.paths.1.paths=/api-permission-tenant quarkus.keycloak.api-permission-tenant.policy-enforcer.paths.1.claim-information-point.claims.static-claim=static-claim # Web App Tenant @@ -106,7 +85,7 @@ quarkus.oidc.api-permission-webapp.application-type=web-app quarkus.oidc.api-permission-webapp.roles.source=accesstoken quarkus.keycloak.api-permission-webapp.policy-enforcer.paths.1.name=Permission Resource WebApp -quarkus.keycloak.api-permission-webapp.policy-enforcer.paths.1.path=/api-permission-webapp +quarkus.keycloak.api-permission-webapp.policy-enforcer.paths.1.paths=/api-permission-webapp quarkus.keycloak.api-permission-webapp.policy-enforcer.paths.1.claim-information-point.claims.static-claim=static-claim admin-url=${keycloak.url}