diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 02095903a9cff..5138f26ac0fdd 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -24,7 +24,7 @@ 0.2.0 0.0.12 0.34.0 - 3.0.0.Final + 3.0.1.Final 1.0.0.Final 1.3 1.0.1 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 f9775d62682b0..468ddc56e4e7a 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,5 +1,7 @@ package io.quarkus.keycloak.pep.deployment; +import java.util.Map; + import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.annotations.BuildStep; @@ -13,6 +15,7 @@ import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.runtime.OidcBuildTimeConfig; import io.quarkus.oidc.runtime.OidcConfig; +import io.quarkus.vertx.http.deployment.RequireBodyHandlerBuildItem; public class KeycloakPolicyEnforcerBuildStep { @@ -21,6 +24,34 @@ FeatureBuildItem featureBuildItem() { return new FeatureBuildItem(FeatureBuildItem.KEYCLOAK_AUTHORIZATION); } + @BuildStep + RequireBodyHandlerBuildItem requireBody(KeycloakPolicyEnforcerConfig config) { + if (config.policyEnforcer.enable) { + if (isBodyClaimInformationPointDefined(config.policyEnforcer.claimInformationPoint.simpleConfig)) { + return new RequireBodyHandlerBuildItem(); + } + for (KeycloakPolicyEnforcerConfig.KeycloakConfigPolicyEnforcer.PathConfig path : config.policyEnforcer.paths + .values()) { + if (isBodyClaimInformationPointDefined(path.claimInformationPoint.simpleConfig)) { + return new RequireBodyHandlerBuildItem(); + } + } + } + return null; + } + + private boolean isBodyClaimInformationPointDefined(Map> claims) { + for (Map.Entry> entry : claims.entrySet()) { + Map value = entry.getValue(); + + if (value.get(entry.getKey()).contains("request.body")) { + return true; + } + } + + return false; + } + @BuildStep public AdditionalBeanBuildItem beans(KeycloakPolicyEnforcerConfig config) { if (config.policyEnforcer.enable) { diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerConfig.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerConfig.java index 9f6545124b69f..118ed1247c348 100644 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerConfig.java +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerConfig.java @@ -41,13 +41,13 @@ public static class KeycloakConfigPolicyEnforcer { * Specifies how policies are enforced. */ @ConfigItem(defaultValue = "ENFORCING") - String enforcementMode; + public String enforcementMode; /** * Specifies the paths to protect. */ @ConfigItem - Map paths; + public Map paths; /** * Defines how the policy enforcer should track associations between paths in your application and resources defined in @@ -56,7 +56,7 @@ public static class KeycloakConfigPolicyEnforcer { * protected resources */ @ConfigItem - PathCacheConfig pathCache; + public PathCacheConfig pathCache; /** * Specifies how the adapter should fetch the server for resources associated with paths in your application. If true, @@ -65,14 +65,14 @@ public static class KeycloakConfigPolicyEnforcer { * enforcer is going to fetch resources on-demand accordingly with the path being requested */ @ConfigItem(defaultValue = "true") - boolean lazyLoadPaths; + public boolean lazyLoadPaths; /** * Defines a set of one or more claims that must be resolved and pushed to the Keycloak server in order to make these * claims available to policies */ @ConfigItem - ClaimInformationPointConfig claimInformationPoint; + public ClaimInformationPointConfig claimInformationPoint; /** * Specifies how scopes should be mapped to HTTP methods. If set to true, the policy enforcer will use the HTTP method @@ -80,7 +80,7 @@ public static class KeycloakConfigPolicyEnforcer { * the current request to check whether or not access should be granted */ @ConfigItem - boolean httpMethodAsScope; + public boolean httpMethodAsScope; @ConfigGroup public static class PathConfig { @@ -89,13 +89,13 @@ public static class PathConfig { * The name of a resource on the server that is to be associated with a given path */ @ConfigItem - Optional name; + public Optional name; /** * A URI relative to the application’s context path that should be protected by the policy enforcer */ @ConfigItem - Optional path; + public Optional path; /** * The HTTP methods (for example, GET, POST, PATCH) to protect and how they are associated with the scopes for a @@ -103,14 +103,14 @@ public static class PathConfig { * resource in the server */ @ConfigItem - Map methods; + public Map methods; /** * Specifies how policies are enforced */ @DefaultConverter @ConfigItem(defaultValue = "ENFORCING") - PolicyEnforcerConfig.EnforcementMode enforcementMode; + public PolicyEnforcerConfig.EnforcementMode enforcementMode; /** * Defines a set of one or more claims that must be resolved and pushed to the Keycloak server in order to make @@ -118,7 +118,7 @@ public static class PathConfig { * claims available to policies */ @ConfigItem - ClaimInformationPointConfig claimInformationPoint; + public ClaimInformationPointConfig claimInformationPoint; } @ConfigGroup @@ -128,20 +128,20 @@ public static class MethodConfig { * The name of the HTTP method */ @ConfigItem - String method; + public String method; /** * An array of strings with the scopes associated with the method */ @ConfigItem - List scopes; + public List scopes; /** * A string referencing the enforcement mode for the scopes associated with a method */ @DefaultConverter @ConfigItem(defaultValue = "ALL") - PolicyEnforcerConfig.ScopeEnforcementMode scopesEnforcementMode; + public PolicyEnforcerConfig.ScopeEnforcementMode scopesEnforcementMode; } @ConfigGroup @@ -151,13 +151,13 @@ public static class PathCacheConfig { * Defines the time in milliseconds when the entry should be expired */ @ConfigItem(defaultValue = "1000") - int maxEntries = 1000; + public int maxEntries = 1000; /** * Defines the limit of entries that should be kept in the cache */ @ConfigItem(defaultValue = "30000") - long lifespan = 30000; + public long lifespan = 30000; } @ConfigGroup @@ -167,13 +167,13 @@ public static class ClaimInformationPointConfig { * */ @ConfigItem(name = ConfigItem.PARENT) - Map>> complexConfig; + public Map>> complexConfig; /** * */ @ConfigItem(name = ConfigItem.PARENT) - Map> simpleConfig; + public Map> simpleConfig; } } } diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java index 78a8b5fb934fa..df2342cf3d153 100644 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java @@ -1,6 +1,5 @@ package io.quarkus.keycloak.pep.runtime; -import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; @@ -23,8 +22,10 @@ import io.quarkus.oidc.AccessTokenCredential; import io.quarkus.security.credential.TokenCredential; import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.vertx.http.runtime.VertxInputStream; import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.impl.CookieImpl; @@ -107,7 +108,16 @@ public Cookie getCookie(String cookieName) { @Override public String getHeader(String name) { - return request.getHeader(name); + //TODO: this logic should be removed once KEYCLOAK-12412 is fixed + String value = request.getHeader(name); + + if (name.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE.toString())) { + if (value.indexOf(';') != -1) { + return value.substring(0, value.indexOf(';')); + } + } + + return value; } @Override @@ -122,7 +132,17 @@ public InputStream getInputStream() { @Override public InputStream getInputStream(boolean buffered) { - return new BufferedInputStream(new ByteArrayInputStream(routingContext.getBody().getBytes())); + try { + if (routingContext.getBody() != null) { + return new ByteArrayInputStream(routingContext.getBody().getBytes()); + } + if (routingContext.request().isEnded()) { + return new ByteArrayInputStream(new byte[0]); + } + return new VertxInputStream(request); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java index d676b8ab814c2..ccd63565410d4 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java +++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java @@ -1,5 +1,6 @@ package io.quarkus.resteasy.runtime.standalone; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -19,6 +20,7 @@ import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.security.identity.CurrentIdentityAssociation; import io.quarkus.vertx.http.runtime.CurrentVertxRequest; +import io.quarkus.vertx.http.runtime.VertxInputStream; import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; import io.vertx.core.Context; import io.vertx.core.Handler; @@ -61,9 +63,13 @@ public VertxRequestHandler(Vertx vertx, public void handle(RoutingContext request) { // have to create input stream here. Cannot execute in another thread // otherwise request handlers may not get set up before request ends - VertxInputStream is; + InputStream is; try { - is = new VertxInputStream(request.request()); + if (request.getBody() != null) { + is = new ByteArrayInputStream(request.getBody().getBytes()); + } else { + is = new VertxInputStream(request.request()); + } } catch (IOException e) { request.fail(e); return; diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java index f9e7394440a1b..d66f8d71f3e48 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java @@ -353,7 +353,8 @@ public void run() { return new Handler() { @Override public void handle(RoutingContext event) { - VertxHttpExchange exchange = new VertxHttpExchange(event.request(), allocator, executorService, event); + VertxHttpExchange exchange = new VertxHttpExchange(event.request(), allocator, executorService, event, + event.getBody()); Optional maxBodySize = httpConfiguration.limits.maxBodySize; if (maxBodySize.isPresent()) { exchange.setMaxEntitySize(maxBodySize.get().asLongValue()); diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java new file mode 100644 index 0000000000000..c5743fcd30385 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java @@ -0,0 +1,17 @@ +package io.quarkus.vertx.http.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +public final class BodyHandlerBuildItem extends SimpleBuildItem { + private final Handler handler; + + public BodyHandlerBuildItem(Handler handler) { + this.handler = handler; + } + + public Handler getHandler() { + return handler; + } +} 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 new file mode 100644 index 0000000000000..dbfd8541499a3 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java @@ -0,0 +1,10 @@ +package io.quarkus.vertx.http.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * 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 { +} 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 2dcebfca9208d..3f79ed6ee306c 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 @@ -35,8 +35,10 @@ import io.quarkus.vertx.http.runtime.VertxHttpRecorder; import io.quarkus.vertx.http.runtime.cors.CORSRecorder; import io.quarkus.vertx.http.runtime.filters.Filter; +import io.vertx.core.Handler; import io.vertx.core.impl.VertxImpl; import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; class VertxHttpProcessor { @@ -97,6 +99,12 @@ VertxWebRouterBuildItem initializeRouter(VertxHttpRecorder recorder, return new VertxWebRouterBuildItem(router); } + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + BodyHandlerBuildItem bodyHandler(VertxHttpRecorder recorder, HttpConfiguration httpConfiguration) { + return new BodyHandlerBuildItem(recorder.createBodyHandler(httpConfiguration)); + } + @BuildStep @Record(ExecutionTime.RUNTIME_INIT) ServiceStartBuildItem finalizeRouter( @@ -106,7 +114,9 @@ ServiceStartBuildItem finalizeRouter( List defaultRoutes, List filters, VertxWebRouterBuildItem router, EventLoopCountBuildItem eventLoopCount, HttpBuildTimeConfig httpBuildTimeConfig, HttpConfiguration httpConfiguration, - BuildProducer reflectiveClass, List websocketSubProtocols) + BuildProducer reflectiveClass, List websocketSubProtocols, + List requireBodyHandlerBuildItems, + BodyHandlerBuildItem bodyHandlerBuildItem) throws BuildException, IOException { Optional defaultRoute; if (defaultRoutes == null || defaultRoutes.isEmpty()) { @@ -124,9 +134,14 @@ ServiceStartBuildItem finalizeRouter( .filter(f -> f.getHandler() != null) .map(FilterBuildItem::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; + recorder.finalizeRouter(beanContainer.getValue(), defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null), - listOfFilters, vertx.getVertx(), router.getRouter(), httpBuildTimeConfig.rootPath, launchMode.getLaunchMode()); + listOfFilters, vertx.getVertx(), router.getRouter(), httpBuildTimeConfig.rootPath, launchMode.getLaunchMode(), + !requireBodyHandlerBuildItems.isEmpty(), bodyHandler); boolean startVirtual = requireVirtual.isPresent() || httpBuildTimeConfig.virtual; if (startVirtual) { 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 4b7b13dfbc37a..69568666ea191 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 @@ -35,6 +35,7 @@ import io.quarkus.runtime.Timing; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.configuration.ConfigInstantiator; +import io.quarkus.runtime.configuration.MemorySize; import io.quarkus.vertx.core.runtime.VertxCoreRecorder; import io.quarkus.vertx.core.runtime.config.VertxConfiguration; import io.quarkus.vertx.http.runtime.filters.Filter; @@ -61,6 +62,7 @@ import io.vertx.ext.web.Route; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.BodyHandler; @Recorder public class VertxHttpRecorder { @@ -169,7 +171,8 @@ public void startServer(RuntimeValue vertxRuntimeValue, ShutdownContext s public void finalizeRouter(BeanContainer container, Consumer defaultRouteHandler, List filterList, RuntimeValue vertx, - RuntimeValue runtimeValue, String rootPath, LaunchMode launchMode) { + RuntimeValue runtimeValue, String rootPath, LaunchMode launchMode, boolean requireBodyHandler, + Handler bodyHandler) { // install the default route at the end Router router = runtimeValue.getValue(); @@ -200,6 +203,18 @@ public void finalizeRouter(BeanContainer container, Consumer defaultRoute container.instance(RouterProducer.class).initialize(resumingRouter); router.route().last().failureHandler(new QuarkusErrorHandler(launchMode.isDevOrTest())); + if (requireBodyHandler) { + //if this is set then everything needs the body handler installed + //TODO: config etc + router.route().order(Integer.MIN_VALUE).handler(new Handler() { + @Override + public void handle(RoutingContext routingContext) { + routingContext.request().resume(); + bodyHandler.handle(routingContext); + } + }); + } + if (rootPath.equals("/")) { if (hotReplacementHandler != null) { router.route().order(-1).handler(hotReplacementHandler); @@ -626,4 +641,24 @@ public static Handler getRootHandler() { return ACTUAL_ROOT; } + public Handler createBodyHandler(HttpConfiguration httpConfiguration) { + BodyHandler bodyHandler = BodyHandler.create(); + Optional maxBodySize = httpConfiguration.limits.maxBodySize; + if (maxBodySize.isPresent()) { + bodyHandler.setBodyLimit(maxBodySize.get().asLongValue()); + } + final BodyConfig bodyConfig = httpConfiguration.body; + bodyHandler.setHandleFileUploads(bodyConfig.handleFileUploads); + bodyHandler.setUploadsDirectory(bodyConfig.uploadsDirectory); + bodyHandler.setDeleteUploadedFilesOnEnd(bodyConfig.deleteUploadedFilesOnEnd); + bodyHandler.setMergeFormAttributes(bodyConfig.mergeFormAttributes); + bodyHandler.setPreallocateBodyBuffer(bodyConfig.preallocateBodyBuffer); + return new Handler() { + @Override + public void handle(RoutingContext event) { + event.request().resume(); + bodyHandler.handle(event); + } + }; + } } diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxInputStream.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java similarity index 95% rename from extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxInputStream.java rename to extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java index 9c97bc82489f9..357f77de923aa 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxInputStream.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java @@ -1,4 +1,4 @@ -package io.quarkus.resteasy.runtime.standalone; +package io.quarkus.vertx.http.runtime; import java.io.IOException; import java.io.InputStream; @@ -10,6 +10,7 @@ import io.vertx.core.Context; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpServerRequest; public class VertxInputStream extends InputStream { @@ -80,6 +81,7 @@ public int available() throws IOException { if (finished) { return -1; } + return exchange.readBytesAvailable(); } @@ -199,7 +201,14 @@ public int readBytesAvailable() { if (input1 != null) { return input1.getByteBuf().readableBytes(); } - return 0; + + String length = request.getHeader(HttpHeaders.CONTENT_LENGTH); + + if (length == null) { + return 0; + } + + return Integer.parseInt(length); } } diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java index 715dbafcde91b..d8e3e968eef7b 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java @@ -4,6 +4,10 @@ import io.vertx.core.Handler; import io.vertx.ext.web.RoutingContext; +/** + * use {@link io.quarkus.vertx.http.deployment.BodyHandlerBuildItem} instead + */ +@Deprecated public final class BodyHandlerBuildItem extends SimpleBuildItem { private final Handler handler; diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java index 8b98af3df949c..ed69c72f5731e 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -48,9 +48,9 @@ import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.vertx.http.deployment.FilterBuildItem; +import io.quarkus.vertx.http.deployment.RequireBodyHandlerBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.runtime.HandlerType; -import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.quarkus.vertx.web.Route; import io.quarkus.vertx.web.RouteBase; import io.quarkus.vertx.web.RouteFilter; @@ -149,9 +149,8 @@ void validateBeanDeployment( } @BuildStep - @Record(ExecutionTime.RUNTIME_INIT) - BodyHandlerBuildItem bodyHandler(VertxWebRecorder recorder, HttpConfiguration httpConfiguration) { - return new BodyHandlerBuildItem(recorder.createBodyHandler(httpConfiguration)); + BodyHandlerBuildItem bodyHandler(io.quarkus.vertx.http.deployment.BodyHandlerBuildItem realOne) { + return new BodyHandlerBuildItem(realOne.getHandler()); } @BuildStep @@ -163,9 +162,10 @@ void addAdditionalRoutes( BuildProducer generatedClass, AnnotationProxyBuildItem annotationProxy, BuildProducer reflectiveClasses, - BodyHandlerBuildItem bodyHandler, + io.quarkus.vertx.http.deployment.BodyHandlerBuildItem bodyHandler, BuildProducer routeProducer, - BuildProducer filterProducer) throws IOException { + BuildProducer filterProducer, + List bodyHandlerRequired) throws IOException { ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClass, true); diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java index ffc08969aa4fb..e7b3051717b6b 100644 --- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java @@ -1,20 +1,15 @@ package io.quarkus.vertx.web.runtime; import java.lang.reflect.InvocationTargetException; -import java.util.Optional; import java.util.function.Function; import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.runtime.configuration.MemorySize; -import io.quarkus.vertx.http.runtime.BodyConfig; -import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.quarkus.vertx.http.runtime.RouterProducer; import io.quarkus.vertx.web.Route; import io.vertx.core.Handler; import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; -import io.vertx.ext.web.handler.BodyHandler; @Recorder public class VertxWebRecorder { @@ -66,33 +61,14 @@ public io.vertx.ext.web.Route apply(Router router) { route.consumes(consumes); } } - route.handler(bodyHandler); + if (bodyHandler != null) { + route.handler(bodyHandler); + } return route; } }; } - public Handler createBodyHandler(HttpConfiguration httpConfiguration) { - BodyHandler bodyHandler = BodyHandler.create(); - Optional maxBodySize = httpConfiguration.limits.maxBodySize; - if (maxBodySize.isPresent()) { - bodyHandler.setBodyLimit(maxBodySize.get().asLongValue()); - } - final BodyConfig bodyConfig = httpConfiguration.body; - bodyHandler.setHandleFileUploads(bodyConfig.handleFileUploads); - bodyHandler.setUploadsDirectory(bodyConfig.uploadsDirectory); - bodyHandler.setDeleteUploadedFilesOnEnd(bodyConfig.deleteUploadedFilesOnEnd); - bodyHandler.setMergeFormAttributes(bodyConfig.mergeFormAttributes); - bodyHandler.setPreallocateBodyBuffer(bodyConfig.preallocateBodyBuffer); - return new Handler() { - @Override - public void handle(RoutingContext event) { - event.request().resume(); - bodyHandler.handle(event); - } - }; - } - private String ensureStartWithSlash(String path) { if (path.startsWith("/")) { return path; diff --git a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java index a3ebd29a35ced..beb96fe320251 100644 --- a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java +++ b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java @@ -1,20 +1,26 @@ package io.quarkus.it.keycloak; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import javax.inject.Inject; import javax.security.auth.AuthPermission; +import javax.ws.rs.Consumes; import javax.ws.rs.ForbiddenException; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import org.keycloak.representations.idm.authorization.Permission; import io.quarkus.security.identity.SecurityIdentity; +import io.vertx.core.http.HttpServerRequest; @Path("/api/permission") public class ProtectedResource { @@ -47,4 +53,15 @@ public List claimProtected() { public List httpResponseClaimProtected() { return identity.getAttribute("permissions"); } + + @Path("/body-claim") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public List bodyClaim(Map body, @Context HttpServerRequest request) { + if (body == null && !body.containsKey("from-body")) { + return Collections.emptyList(); + } + return identity.getAttribute("permissions"); + } } diff --git a/integration-tests/keycloak-authorization/src/main/resources/application.properties b/integration-tests/keycloak-authorization/src/main/resources/application.properties index 1aef8e6e968db..2989b9924ea5d 100644 --- a/integration-tests/keycloak-authorization/src/main/resources/application.properties +++ b/integration-tests/keycloak-authorization/src/main/resources/application.properties @@ -32,3 +32,8 @@ quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.headers.Au # Disables policy enforcement for a path quarkus.keycloak.policy-enforcer.paths.4.path=/api/public 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.claim-information-point.claims.from-body={request.body['/from-body']} + diff --git a/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/PolicyEnforcerTest.java b/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/PolicyEnforcerTest.java index 0b87ae3d1a65f..8612f498e253a 100644 --- a/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/PolicyEnforcerTest.java +++ b/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/PolicyEnforcerTest.java @@ -26,6 +26,7 @@ import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; /** * @author Pedro Igor @@ -116,6 +117,7 @@ private static ClientRepresentation createClient(String clientId) { configurePermissionResourcePermission(authorizationSettings); configureClaimBasedPermission(authorizationSettings); configureHttpResponseClaimBasedPermission(authorizationSettings); + configureBodyClaimBasedPermission(authorizationSettings); client.setAuthorizationSettings(authorizationSettings); @@ -155,6 +157,20 @@ private static void configureHttpResponseClaimBasedPermission(ResourceServerRepr "/api/permission/http-response-claim-protected"), policy); } + private static void configureBodyClaimBasedPermission(ResourceServerRepresentation settings) { + PolicyRepresentation policy = createJSPolicy("Body Claim-Based Policy", + "var context = $evaluation.getContext();\n" + + "print(context.getAttributes().toMap());" + + "var attributes = context.getAttributes();\n" + + "\n" + + "if (attributes.containsValue('from-body', 'grant')) {\n" + + " $evaluation.grant();\n" + + "}", + settings); + createPermission(settings, createResource(settings, "Body Claim Protected Resource", + "/api/permission/body-claim"), policy); + } + private static void createPermission(ResourceServerRepresentation settings, ResourceRepresentation resource, PolicyRepresentation policy) { PolicyRepresentation permission = new PolicyRepresentation(); @@ -260,6 +276,18 @@ public void testHttpResponseFromExternalServiceAsClaim() { .statusCode(403); } + @Test + public void testBodyClaim() { + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .contentType(ContentType.JSON) + .body("{\"from-body\": \"grant\"}") + .when() + .post("/api/permission/body-claim") + .then() + .statusCode(200) + .and().body(Matchers.containsString("Body Claim Protected Resource")); + } + @Test public void testPublicResource() { RestAssured.given()