From 5cef24b19c82069f2c9896258abf8f24cea2a507 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Thu, 5 Dec 2019 21:34:18 -0300 Subject: [PATCH 1/2] Use body handler to allow request buffering Fixes #5959 This change allows the body handler to be used for Undertow and RESTEasy standalone. When the request is fully buffered it can be consumed multiple times, which allows keycloak to also process it. --- bom/runtime/pom.xml | 2 +- .../KeycloakPolicyEnforcerBuildStep.java | 31 ++++++++++++++++ .../runtime/KeycloakPolicyEnforcerConfig.java | 36 +++++++++--------- .../keycloak/pep/runtime/VertxHttpFacade.java | 14 ++++++- .../standalone/VertxRequestHandler.java | 10 ++++- .../runtime/UndertowDeploymentRecorder.java | 3 +- .../http/deployment/BodyHandlerBuildItem.java | 17 +++++++++ .../RequireBodyHandlerBuildItem.java | 10 +++++ .../http/deployment/VertxHttpProcessor.java | 19 +++++++++- .../vertx/http/runtime/VertxHttpRecorder.java | 37 ++++++++++++++++++- .../vertx/http/runtime}/VertxInputStream.java | 13 ++++++- .../web/deployment/BodyHandlerBuildItem.java | 4 ++ .../web/deployment/VertxWebProcessor.java | 12 +++--- .../vertx/web/runtime/VertxWebRecorder.java | 30 ++------------- .../it/keycloak/ProtectedResource.java | 17 +++++++++ .../src/main/resources/application.properties | 5 +++ .../it/keycloak/PolicyEnforcerTest.java | 28 ++++++++++++++ 17 files changed, 226 insertions(+), 62 deletions(-) create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java rename extensions/{resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone => vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime}/VertxInputStream.java (95%) 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..674c7daa8b476 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,6 +22,7 @@ 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.HttpServerRequest; @@ -122,7 +122,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() From a00a4797d1ff561e009d6f75baae2be82203c4b9 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Fri, 6 Dec 2019 07:38:40 -0300 Subject: [PATCH 2/2] [fixes #5959] - Temporary fix to content type resolution that fixes processing of json body --- .../keycloak/pep/runtime/VertxHttpFacade.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 674c7daa8b476..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 @@ -25,6 +25,7 @@ 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