diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java
index 6f1d19327e..cceb8d7770 100644
--- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java
@@ -18,7 +18,9 @@
package org.keycloak.adapters.elytron;
+import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.CookieImpl;
+import io.undertow.servlet.handlers.ServletRequestContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore;
@@ -38,6 +40,10 @@ import org.wildfly.security.http.Scope;
import javax.security.auth.callback.CallbackHandler;
import javax.security.cert.X509Certificate;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
@@ -248,7 +254,26 @@ class ElytronHttpFacade implements OIDCHttpFacade {
}
if (buffered) {
- return inputStream = new BufferedInputStream(request.getInputStream());
+ HttpScope exchangeScope = getScope(Scope.EXCHANGE);
+ HttpServerExchange exchange = ProtectedHttpServerExchange.class.cast(exchangeScope.getAttachment(UNDERTOW_EXCHANGE)).getExchange();
+ ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ ServletRequest servletRequest = context.getServletRequest();
+
+ inputStream = new BufferedInputStream(exchange.getInputStream());
+
+ context.setServletRequest(new HttpServletRequestWrapper((HttpServletRequest) servletRequest) {
+ @Override
+ public ServletInputStream getInputStream() {
+ inputStream.mark(0);
+ return new ServletInputStream() {
+ @Override
+ public int read() throws IOException {
+ return inputStream.read();
+ }
+ };
+ }
+ });
+ return inputStream;
}
return request.getInputStream();
diff --git a/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
index d47b363d94..c8d812b1f8 100755
--- a/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
+++ b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -23,6 +23,7 @@ import io.undertow.server.handlers.form.FormData;
import io.undertow.server.handlers.form.FormData.FormValue;
import io.undertow.server.handlers.form.FormDataParser;
import io.undertow.server.handlers.form.FormParserFactory;
+import io.undertow.servlet.handlers.ServletRequestContext;
import io.undertow.util.AttachmentKey;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
@@ -32,6 +33,10 @@ import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.KeycloakUriBuilder;
import javax.security.cert.X509Certificate;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedInputStream;
import java.io.IOException;
@@ -186,7 +191,24 @@ public class UndertowHttpFacade implements HttpFacade {
}
if (buffered) {
- return inputStream = new BufferedInputStream(exchange.getInputStream());
+ ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ ServletRequest servletRequest = context.getServletRequest();
+
+ inputStream = new BufferedInputStream(exchange.getInputStream());
+
+ context.setServletRequest(new HttpServletRequestWrapper((HttpServletRequest) servletRequest) {
+ @Override
+ public ServletInputStream getInputStream() {
+ inputStream.mark(0);
+ return new ServletInputStream() {
+ @Override
+ public int read() throws IOException {
+ return inputStream.read();
+ }
+ };
+ }
+ });
+ return inputStream;
}
return exchange.getInputStream();
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-claim-information-point-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-claim-information-point-authz-service.json
index 04f09064c5..ebfe93e62f 100644
--- a/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-claim-information-point-authz-service.json
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-claim-information-point-authz-service.json
@@ -8,6 +8,7 @@
"credentials": {
"secret": "secret"
},
+ "autodetect-bearer-only": true,
"policy-enforcer": {
"on-deny-redirect-to" : "/servlet-authz-app/accessDenied.jsp",
"lazy-load-paths": true,
@@ -19,6 +20,14 @@
"request-claim": "{request.parameter['request-claim']}"
}
}
+ },
+ {
+ "path": "/protected/filter/body",
+ "claim-information-point": {
+ "claims": {
+ "request-claim": "{request.body}"
+ }
+ }
}
]
}
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
index 56f81cd63e..1c209d9af2 100755
--- a/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
@@ -29,6 +29,11 @@
${project.version}
provided
+
+ org.jboss.spec.javax.servlet
+ jboss-servlet-api_4.0_spec
+ provided
+
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
index 1a1a4a7bae..873272bdc5 100644
--- a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
@@ -64,6 +64,10 @@
{
"name": "Multiple URL resource",
"uris": ["/keycloak-7269/sub-resource1/*", "/keycloak-7269/sub-resource2/{whatever-pattern}/page.jsp"]
+ },
+ {
+ "name": "Resource Protected With Body Claim",
+ "uri": "/protected/filter/body"
}
],
"policies": [
@@ -221,6 +225,16 @@
"config": {
"code": "var context = $evaluation.getContext();\nvar attributes = context.getAttributes();\nvar claim = attributes.getValue('request-claim');\n\nif (claim && claim.asString(0) == 'expected-value') {\n $evaluation.grant();\n}"
}
+ },
+ {
+ "name": "Resource Protected With Body Claim Permission",
+ "type": "resource",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
+ "config": {
+ "resources": "[\"Resource Protected With Body Claim\"]",
+ "applyPolicies": "[\"Any User Policy\"]"
+ }
}
]
}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json
index 371e4510f5..5b8334de92 100644
--- a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json
@@ -86,6 +86,7 @@
"adminUrl": "/servlet-authz-app",
"bearerOnly": false,
"authorizationServicesEnabled": true,
+ "directAccessGrantsEnabled": true,
"redirectUris": [
"/servlet-authz-app/*"
],
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/java/org/keycloak/testsuite/servletauthz/TestFilter.java b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/java/org/keycloak/testsuite/servletauthz/TestFilter.java
new file mode 100644
index 0000000000..a70c0b9de6
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/java/org/keycloak/testsuite/servletauthz/TestFilter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.servletauthz;
+
+import javax.servlet.FilterChain;
+import javax.servlet.GenericFilter;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author Pedro Igor
+ */
+public class TestFilter extends GenericFilter {
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ HttpServletRequest req = (HttpServletRequest) request;
+
+ if (req.getRequestURI().endsWith("/body")) {
+ Map body = JsonSerialization.readValue(request.getInputStream(), Map.class);
+ response.setContentType("application/json");
+ PrintWriter writer = response.getWriter();
+ writer.println(JsonSerialization.writeValueAsString(body));
+ writer.flush();
+ }
+ }
+}
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml
index 352b34f009..3372bbe4e2 100644
--- a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml
@@ -23,6 +23,16 @@
/public-html.html
+
+
+ TestFilter
+ org.keycloak.testsuite.servletauthz.TestFilter
+
+
+
+ TestFilter
+ /protected/filter/*
+
KEYCLOAK
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/ServletAuthzCIPAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/ServletAuthzCIPAdapterTest.java
index c026399e9f..78e335b82a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/ServletAuthzCIPAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/ServletAuthzCIPAdapterTest.java
@@ -16,13 +16,23 @@
*/
package org.keycloak.testsuite.adapter.example.authorization;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
import java.io.File;
import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
import org.junit.Test;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
/**
@@ -64,4 +74,25 @@ public class ServletAuthzCIPAdapterTest extends AbstractServletAuthzAdapterTest
assertWasDenied();
});
}
+
+ @Test
+ public void testReuseBodyAfterClaimProcessing() {
+ performTests(() -> {
+ OAuthClient.AccessTokenResponse response = oauth.realm("servlet-authz").clientId("servlet-authz-app")
+ .doGrantAccessTokenRequest("secret", "alice", "alice");
+ Client client = ClientBuilder.newClient();
+ Map body = new HashMap();
+
+ body.put("test", "test-value");
+
+ Response post = client.target(getResourceServerUrl() + "/protected/filter/body")
+ .request()
+ .header(HttpHeaders.AUTHORIZATION, "Bearer " + response.getAccessToken())
+ .post(Entity.entity(body, MediaType.APPLICATION_JSON_TYPE));
+
+ body = post.readEntity(Map.class);
+
+ Assert.assertEquals("test-value", body.get("test"));
+ });
+ }
}
diff --git a/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/undertow/SimpleWebXmlParser.java b/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/undertow/SimpleWebXmlParser.java
index a77b6efcc4..96ec3d1dea 100644
--- a/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/undertow/SimpleWebXmlParser.java
+++ b/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/undertow/SimpleWebXmlParser.java
@@ -74,7 +74,7 @@ class SimpleWebXmlParser {
ElementWrapper loadOnStartupEw = servlet.getElementByTagName("load-on-startup");
Integer loadOnStartup = loadOnStartupEw == null ? null : Integer.valueOf(loadOnStartupEw.getText());
- Class extends Servlet> servletClazz = (Class extends Servlet>) Class.forName(servletClass);
+ Class extends Servlet> servletClazz = (Class extends Servlet>) Class.forName(servletClass, false, di.getClassLoader());
ServletInfo undertowServlet = new ServletInfo(servletName, servletClazz);
if (servletMappings.containsKey(servletName)) {
@@ -101,7 +101,7 @@ class SimpleWebXmlParser {
String filterName = filter.getElementByTagName("filter-name").getText();
String filterClass = filter.getElementByTagName("filter-class").getText();
- Class extends Filter> filterClazz = (Class extends Filter>) Class.forName(filterClass);
+ Class extends Filter> filterClazz = (Class extends Filter>) Class.forName(filterClass, false, di.getClassLoader());
FilterInfo undertowFilter = new FilterInfo(filterName, filterClazz);
List initParams = filter.getElementsByTagName("init-param");
diff --git a/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/undertow/UndertowWarClassLoader.java b/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/undertow/UndertowWarClassLoader.java
index a36e68cb1d..4ecd7a0fd4 100644
--- a/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/undertow/UndertowWarClassLoader.java
+++ b/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/undertow/UndertowWarClassLoader.java
@@ -19,8 +19,10 @@ package org.keycloak.testsuite.utils.undertow;
+import java.io.IOException;
import java.io.InputStream;
+import org.apache.commons.io.IOUtils;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.Node;
@@ -36,6 +38,15 @@ public class UndertowWarClassLoader extends ClassLoader {
this.archive = archive;
}
+ @Override
+ protected Class> findClass(String name) {
+ try (InputStream resourceAsStream = getResourceAsStream(name.replace('.', '/') + ".class")) {
+ byte[] bytes = IOUtils.toByteArray(resourceAsStream);
+ return defineClass(name, bytes, 0, bytes.length);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to find class [" + name + "]", e);
+ }
+ }
@Override
public InputStream getResourceAsStream(String name) {