diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java index 73d821fbcd..0c13b17aeb 100755 --- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java +++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java @@ -18,7 +18,6 @@ package org.keycloak.examples.authenticator; import org.jboss.resteasy.spi.HttpResponse; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.Authenticator; @@ -89,15 +88,15 @@ public class SecretQuestionAuthenticator implements Authenticator { } URI uri = context.getUriInfo().getBaseUriBuilder().path("realms").path(context.getRealm().getName()).build(); - addCookie("SECRET_QUESTION_ANSWERED", "true", + addCookie(context, "SECRET_QUESTION_ANSWERED", "true", uri.getRawPath(), null, null, maxCookieAge, false, true); } - public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) { - HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); + public void addCookie(AuthenticationFlowContext context, String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) { + HttpResponse response = context.getSession().getContext().getContextObject(HttpResponse.class); StringBuffer cookieBuf = new StringBuffer(); ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly); String cookie = cookieBuf.toString(); diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/BasicAuthFilter.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/BasicAuthFilter.java index dabf5bda81..f4750b6b6c 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/BasicAuthFilter.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/BasicAuthFilter.java @@ -17,13 +17,13 @@ package org.keycloak.admin.client.resource; -import org.jboss.resteasy.util.Base64; - import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.core.HttpHeaders; import java.io.IOException; +import org.keycloak.common.util.Base64; + /** * @author rodrigo.sasaki@icarros.com.br */ @@ -40,7 +40,7 @@ public class BasicAuthFilter implements ClientRequestFilter { @Override public void filter(ClientRequestContext requestContext) throws IOException { String pair = username + ":" + password; - String authHeader = "Basic " + new String(Base64.encodeBytes(pair.getBytes())); + String authHeader = "Basic " + Base64.encodeBytes(pair.getBytes()); requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader); } diff --git a/pom.xml b/pom.xml index dff636490e..f68750bdf1 100755 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,7 @@ 1.0.3.Final 1.2.17 3.7.0.Final + 4.3.1.Final 3.7.0.Final 20180219.1 1.7.22 @@ -314,6 +315,25 @@ + + org.jboss.resteasy + resteasy-core + ${resteasy4.version} + + + log4j + log4j + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + org.jboss.resteasy resteasy-multipart-provider diff --git a/services/pom.xml b/services/pom.xml index 655e1de0e3..c079560735 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -113,6 +113,11 @@ resteasy-jaxrs provided + + org.jboss.resteasy + resteasy-core + true + org.jboss.spec.javax.ws.rs jboss-jaxrs-api_2.1_spec diff --git a/services/src/main/java/org/keycloak/common/util/Resteasy.java b/services/src/main/java/org/keycloak/common/util/Resteasy.java new file mode 100644 index 0000000000..206247dc73 --- /dev/null +++ b/services/src/main/java/org/keycloak/common/util/Resteasy.java @@ -0,0 +1,156 @@ +/* + * 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.common.util; + +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.core.ResteasyContext; +import org.jboss.resteasy.spi.ResteasyProviderFactory; + +/** + *

Provides a layer of indirection to abstract invocations to Resteasy internal APIs. Making also possible to use different + * versions of Resteasy (e.g.: v3 and v4) depending on the stack that the server is running. + * + *

The methods herein provided are basically related with accessing context data from Resteasy, which changed in latest versions of Resteasy. + * + *

It is important to use this class when access to context data is necessary in order to avoid incompatibilities with future + * versions of Resteasy. + * + * @author Pedro Igor + */ +public final class Resteasy { + + private static final BiConsumer PUSH_CONTEXT; + private static final BiConsumer PUSH_DEFAULT_OBJECT; + private static final Function PULL_CONTEXT; + private static final Runnable CLEAR_CONTEXT; + + static { + if (isRestEasy4()) { + PUSH_CONTEXT = new BiConsumer() { + @Override + public void accept(Class p1, Object p2) { + ResteasyContext.pushContext(p1, p2); + } + }; + PUSH_DEFAULT_OBJECT = new BiConsumer() { + @Override + public void accept(Class p1, Object p2) { + ResteasyContext.getContextData(org.jboss.resteasy.spi.Dispatcher.class).getDefaultContextObjects() + .put(p1, p2); + } + }; + PULL_CONTEXT = new Function() { + @Override + public Object apply(Class p1) { + return ResteasyContext.getContextData(p1); + } + }; + CLEAR_CONTEXT = new Runnable() { + @Override + public void run() { + ResteasyContext.clearContextData(); + } + }; + } else { + PUSH_CONTEXT = new BiConsumer() { + @Override + public void accept(Class p1, Object p2) { + ResteasyProviderFactory.getInstance().pushContext(p1, p2); + } + }; + PUSH_DEFAULT_OBJECT = new BiConsumer() { + @Override + public void accept(Class p1, Object p2) { + ResteasyProviderFactory.getInstance().getContextData(Dispatcher.class).getDefaultContextObjects() + .put(p1, p2); + } + }; + PULL_CONTEXT = new Function() { + @Override + public Object apply(Class p1) { + return ResteasyProviderFactory.getInstance().getContextData(p1); + } + }; + CLEAR_CONTEXT = new Runnable() { + @Override + public void run() { + ResteasyProviderFactory.getInstance().clearContextData(); + } + }; + } + } + + /** + * Push the given {@code instance} with type/key {@code type} to the Resteasy context associated with the current thread. + * + * @param type the type/key to associate the {@code instance} with + * @param instance the instance + */ + public static void pushContext(Class type, Object instance) { + PUSH_CONTEXT.accept(type, instance); + } + + /** + * Lookup the instance associated with the given type/key {@code type} from the Resteasy context associated with the current thread. + * + * @param type the type/key to lookup + * @return the instance associated with the given {@code type} or null if non-existent. + */ + public static R getContextData(Class type) { + return (R) PULL_CONTEXT.apply(type); + } + + /** + * Clear the Resteasy context associated with the current thread. + */ + public static void clearContextData() { + CLEAR_CONTEXT.run(); + } + + /** + * Push the given {@code instance} with type/key {@code type} to the Resteasy global context. + * + * @param type the type/key to associate the {@code instance} with + * @param instance the instance + */ + public static void pushDefaultContextObject(Class type, Object instance) { + PUSH_DEFAULT_OBJECT.accept(type, instance); + } + + private static boolean isRestEasy4() { + try { + return Class.forName("org.jboss.resteasy.core.ResteasyContext") != null; + } catch (ClassNotFoundException ignore) { + return false; + } + } + + /** + * Only necessary because keycloak-common is constrained to JDK 1.7. + */ + private interface BiConsumer { + void accept(T p1, S p2); + } + + /** + * Only necessary because keycloak-common is constrained to JDK 1.7. + */ + private interface Function { + R apply(T p1); + } +} diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java index 4600e63fcb..ec44a7db57 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java @@ -17,8 +17,8 @@ package org.keycloak.services; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.common.ClientConnection; +import org.keycloak.common.util.Resteasy; import org.keycloak.locale.LocaleSelectorProvider; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakContext; @@ -84,7 +84,7 @@ public class DefaultKeycloakContext implements KeycloakContext { @Override public T getContextObject(Class clazz) { - return ResteasyProviderFactory.getContextData(clazz); + return Resteasy.getContextData(clazz); } @Override diff --git a/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java b/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java index 71d26b166d..72731bc7d8 100644 --- a/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java +++ b/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java @@ -5,6 +5,7 @@ import org.jboss.resteasy.spi.Failure; import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.Config; +import org.keycloak.common.util.Resteasy; import org.keycloak.forms.login.freemarker.model.UrlBean; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakTransaction; @@ -55,7 +56,7 @@ public class KeycloakErrorHandler implements ExceptionMapper { @Override public Response toResponse(Throwable throwable) { - KeycloakTransaction tx = ResteasyProviderFactory.getContextData(KeycloakTransaction.class); + KeycloakTransaction tx = Resteasy.getContextData(KeycloakTransaction.class); tx.setRollbackOnly(); int statusCode = getStatusCode(throwable); diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java index 41876455c8..90f2b7008a 100755 --- a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java +++ b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java @@ -19,6 +19,7 @@ package org.keycloak.services.filters; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.common.ClientConnection; +import org.keycloak.common.util.Resteasy; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakTransaction; @@ -52,7 +53,7 @@ public class KeycloakSessionServletFilter implements Filter { KeycloakSessionFactory sessionFactory = (KeycloakSessionFactory) servletRequest.getServletContext().getAttribute(KeycloakSessionFactory.class.getName()); KeycloakSession session = sessionFactory.create(); - ResteasyProviderFactory.pushContext(KeycloakSession.class, session); + Resteasy.pushContext(KeycloakSession.class, session); ClientConnection connection = new ClientConnection() { @Override public String getRemoteAddr() { @@ -80,10 +81,10 @@ public class KeycloakSessionServletFilter implements Filter { } }; session.getContext().setConnection(connection); - ResteasyProviderFactory.pushContext(ClientConnection.class, connection); + Resteasy.pushContext(ClientConnection.class, connection); KeycloakTransaction tx = session.getTransactionManager(); - ResteasyProviderFactory.pushContext(KeycloakTransaction.class, tx); + Resteasy.pushContext(KeycloakTransaction.class, tx); tx.begin(); try { @@ -128,7 +129,7 @@ public class KeycloakSessionServletFilter implements Filter { } session.close(); - ResteasyProviderFactory.clearContextData(); + Resteasy.clearContextData(); } @Override diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakTransactionCommitter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakTransactionCommitter.java index ecb321f556..a7e3415030 100644 --- a/services/src/main/java/org/keycloak/services/filters/KeycloakTransactionCommitter.java +++ b/services/src/main/java/org/keycloak/services/filters/KeycloakTransactionCommitter.java @@ -20,7 +20,7 @@ package org.keycloak.services.filters; -import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.keycloak.common.util.Resteasy; import org.keycloak.models.KeycloakTransaction; import javax.ws.rs.container.ContainerRequestContext; @@ -35,7 +35,7 @@ public class KeycloakTransactionCommitter implements ContainerResponseFilter { @Override public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException { - KeycloakTransaction tx = ResteasyProviderFactory.getContextData(KeycloakTransaction.class); + KeycloakTransaction tx = Resteasy.getContextData(KeycloakTransaction.class); if (tx != null && tx.isActive()) { if (tx.getRollbackOnly()) { tx.rollback(); diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 18178c0797..e7154e39e7 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -22,8 +22,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.jboss.dmr.ModelNode; import org.jboss.logging.Logger; import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.Config; +import org.keycloak.common.util.Resteasy; import org.keycloak.common.util.SystemEnvProperties; import org.keycloak.exportimport.ExportImportManager; import org.keycloak.migration.MigrationModelManager; @@ -106,19 +106,21 @@ public class KeycloakApplication extends Application { protected KeycloakSessionFactory sessionFactory; protected String contextPath; - public KeycloakApplication(@Context ServletContext context, @Context Dispatcher dispatcher) { + public KeycloakApplication() { try { + ServletContext context = Resteasy.getContextData(ServletContext.class); + if ("true".equals(context.getInitParameter(KEYCLOAK_EMBEDDED))) { embedded = true; } - + loadConfig(context); this.contextPath = context.getContextPath(); this.sessionFactory = createSessionFactory(); - dispatcher.getDefaultContextObjects().put(KeycloakApplication.class, this); - ResteasyProviderFactory.pushContext(KeycloakApplication.class, this); // for injection + Resteasy.pushDefaultContextObject(KeycloakApplication.class, this); + Resteasy.pushContext(KeycloakApplication.class, this); // for injection context.setAttribute(KeycloakSessionFactory.class.getName(), this.sessionFactory); singletons.add(new RobotsResource()); diff --git a/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java b/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java index b7b5c10bb9..b32fce76e1 100755 --- a/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java +++ b/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java @@ -18,8 +18,8 @@ package org.keycloak.services.util; import org.jboss.resteasy.spi.HttpResponse; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.Config; +import org.keycloak.common.util.Resteasy; import javax.ws.rs.core.CacheControl; @@ -29,7 +29,7 @@ import javax.ws.rs.core.CacheControl; public class CacheControlUtil { public static void noBackButtonCacheControlHeader() { - HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); + HttpResponse response = Resteasy.getContextData(HttpResponse.class); response.getOutputHeaders().putSingle("Cache-Control", "no-store, must-revalidate, max-age=0"); } diff --git a/services/src/main/java/org/keycloak/services/util/CookieHelper.java b/services/src/main/java/org/keycloak/services/util/CookieHelper.java index b31f9f782b..b773d0b94f 100755 --- a/services/src/main/java/org/keycloak/services/util/CookieHelper.java +++ b/services/src/main/java/org/keycloak/services/util/CookieHelper.java @@ -25,7 +25,7 @@ import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.jboss.resteasy.spi.HttpResponse; -import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.keycloak.common.util.Resteasy; import org.keycloak.common.util.ServerCookie; import javax.ws.rs.core.Cookie; @@ -53,7 +53,7 @@ public class CookieHelper { * @param httpOnly */ public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) { - HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); + HttpResponse response = Resteasy.getContextData(HttpResponse.class); StringBuffer cookieBuf = new StringBuffer(); ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly); String cookie = cookieBuf.toString(); @@ -62,7 +62,7 @@ public class CookieHelper { public static Set getCookieValue(String name) { - HttpHeaders headers = ResteasyProviderFactory.getContextData(HttpHeaders.class); + HttpHeaders headers = Resteasy.getContextData(HttpHeaders.class); Set cookiesVal = new HashSet<>(); diff --git a/services/src/main/java/org/keycloak/services/util/P3PHelper.java b/services/src/main/java/org/keycloak/services/util/P3PHelper.java index 4777d13277..6539c4dc17 100644 --- a/services/src/main/java/org/keycloak/services/util/P3PHelper.java +++ b/services/src/main/java/org/keycloak/services/util/P3PHelper.java @@ -18,7 +18,7 @@ package org.keycloak.services.util; import org.jboss.resteasy.spi.HttpResponse; -import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.keycloak.common.util.Resteasy; /** * IE requires P3P header to allow loading cookies from iframes when domain differs from main page (see KEYCLOAK-2828 for more details) @@ -28,7 +28,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory; public class P3PHelper { public static void addP3PHeader() { - HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); + HttpResponse response = Resteasy.getContextData(HttpResponse.class); response.getOutputHeaders().putSingle("P3P", "CP=\"This is not a P3P policy!\""); } diff --git a/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java b/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java index d5a1a06f64..17b1a0bceb 100755 --- a/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java @@ -16,7 +16,6 @@ */ package org.keycloak.social.google; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.OAuth2Constants; import org.keycloak.broker.oidc.OIDCIdentityProvider; import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; @@ -63,7 +62,7 @@ public class GoogleIdentityProvider extends OIDCIdentityProvider implements Soci protected String getUserInfoUrl() { String uri = super.getUserInfoUrl(); if (((GoogleIdentityProviderConfig)getConfig()).isUserIp()) { - ClientConnection connection = ResteasyProviderFactory.getContextData(ClientConnection.class); + ClientConnection connection = session.getContext().getConnection(); if (connection != null) { uri = KeycloakUriBuilder.fromUri(super.getUserInfoUrl()).queryParam("userIp", connection.getRemoteAddr()).build().toString(); } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java index b854fffe89..fe51ff3e72 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java @@ -133,7 +133,7 @@ public class TestApplicationResourceProvider implements RealmResourceProvider { sb.append("" + title + ""); sb.append("Form parameters:
"); - HttpRequest request = ResteasyProviderFactory.getContextData(HttpRequest.class); + HttpRequest request = session.getContext().getContextObject(HttpRequest.class); MultivaluedMap formParams = request.getDecodedFormParameters(); for (String paramName : formParams.keySet()) { sb.append(paramName).append(": ").append("" + title + ""); sb.append("Form parameters:
"); - HttpRequest request = ResteasyProviderFactory.getContextData(HttpRequest.class); + HttpRequest request = session.getContext().getContextObject(HttpRequest.class); MultivaluedMap formParams = request.getDecodedFormParameters(); for (String paramName : formParams.keySet()) { sb.append(paramName).append(": ").append("").append(formParams.getFirst(paramName)).append("
");