From 08be04b337c412a76f57bdfc1ab4207fb499d825 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 17 Dec 2014 22:29:18 -0500 Subject: [PATCH] saved requests --- .../main/java/org/keycloak/util/UriUtils.java | 40 ++++ .../keycloak/adapters/AdapterTokenStore.java | 3 + .../adapters/OAuthRequestAuthenticator.java | 14 +- .../AbstractJettyRequestAuthenticator.java | 148 -------------- ...va => AbstractJettySessionTokenStore.java} | 193 +++++++++--------- .../AbstractKeycloakJettyAuthenticator.java | 15 +- .../adapters/jetty/JettyCookieTokenStore.java | 10 + .../adapters/jetty/JettyHttpFacade.java | 8 +- .../jetty/JettyRequestAuthenticator.java | 85 ++++++++ .../jetty/JettyRequestAuthenticator.java | 32 --- .../jetty/JettySessionTokenStore.java | 93 +++++++++ .../jetty/KeycloakJettyAuthenticator.java | 4 +- integration/jetty/jetty9.1/pom.xml | 14 ++ .../jetty/JettyRequestAuthenticator.java | 32 --- .../jetty/JettySessionTokenStore.java | 94 +++++++++ .../jetty/KeycloakJettyAuthenticator.java | 4 +- .../jetty/JettyRequestAuthenticator.java | 32 --- .../jetty/JettySessionTokenStore.java | 95 +++++++++ .../jetty/KeycloakJettyAuthenticator.java | 13 +- .../AbstractKeycloakAuthenticatorValve.java | 10 +- .../tomcat/CatalinaCookieTokenStore.java | 10 + .../adapters/tomcat/CatalinaHttpFacade.java | 8 +- .../tomcat/CatalinaRequestAuthenticator.java | 29 +-- .../tomcat/CatalinaSessionTokenStore.java | 25 ++- .../AbstractUndertowRequestAuthenticator.java | 7 +- .../adapters/undertow/SavedRequest.java | 131 ++++++++++++ .../undertow/ServletRequestAuthenticator.java | 8 + .../undertow/ServletSessionTokenStore.java | 15 ++ .../undertow/UndertowCookieTokenStore.java | 10 + .../undertow/UndertowSessionTokenStore.java | 10 + .../testsuite/adapter/AdapterTest.java | 8 +- .../adapter/AdapterTestStrategy.java | 32 +++ .../keycloak/testsuite/adapter/InputPage.java | 35 ++++ .../testsuite/adapter/InputServlet.java | 42 ++++ .../testsuite/adapter/SessionServlet.java | 76 +++---- .../testsuite/pages/AbstractPage.java | 4 +- .../testsuite/pages/OAuthGrantPage.java | 2 +- .../testsuite/rule/AbstractKeycloakRule.java | 4 + .../resources/adapter-test/demorealm.json | 10 + .../adapter-test/input-keycloak.json | 10 + .../org/keycloak/testsuite/Jetty9Test.java | 6 + .../resources/adapter-test/demorealm.json | 10 + .../input-portal/WEB-INF/jetty-web.xml | 29 +++ .../input-portal/WEB-INF/keycloak.json | 10 + .../adapter-test/input-portal/WEB-INF/web.xml | 40 ++++ .../org/keycloak/testsuite/Jetty9Test.java | 6 + .../resources/adapter-test/demorealm.json | 10 + .../input-portal/WEB-INF/jetty-web.xml | 29 +++ .../input-portal/WEB-INF/keycloak.json | 10 + .../adapter-test/input-portal/WEB-INF/web.xml | 40 ++++ .../org/keycloak/testsuite/Jetty9Test.java | 6 + .../resources/adapter-test/demorealm.json | 10 + .../input-portal/WEB-INF/jetty-web.xml | 29 +++ .../input-portal/WEB-INF/keycloak.json | 10 + .../adapter-test/input-portal/WEB-INF/web.xml | 40 ++++ .../org/keycloak/testsuite/TomcatTest.java | 6 + .../resources/adapter-test/demorealm.json | 10 + .../input-portal/META-INF/context.xml | 3 + .../input-portal/WEB-INF/keycloak.json | 10 + .../adapter-test/input-portal/WEB-INF/web.xml | 40 ++++ .../org/keycloak/testsuite/Tomcat7Test.java | 7 + .../resources/adapter-test/demorealm.json | 10 + .../input-portal/META-INF/context.xml | 3 + .../input-portal/WEB-INF/keycloak.json | 10 + .../adapter-test/input-portal/WEB-INF/web.xml | 40 ++++ .../org/keycloak/testsuite/TomcatTest.java | 8 + .../resources/adapter-test/demorealm.json | 10 + .../input-portal/META-INF/context.xml | 3 + .../input-portal/WEB-INF/keycloak.json | 10 + .../adapter-test/input-portal/WEB-INF/web.xml | 40 ++++ 70 files changed, 1465 insertions(+), 445 deletions(-) mode change 100644 => 100755 core/src/main/java/org/keycloak/util/UriUtils.java mode change 100644 => 100755 integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java delete mode 100755 integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractJettyRequestAuthenticator.java rename integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/{JettySessionTokenStore.java => AbstractJettySessionTokenStore.java} (80%) create mode 100755 integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java delete mode 100755 integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java create mode 100755 integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java delete mode 100755 integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java create mode 100755 integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java delete mode 100755 integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java create mode 100755 integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java create mode 100755 integration/undertow/src/main/java/org/keycloak/adapters/undertow/SavedRequest.java create mode 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputPage.java create mode 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java mode change 100644 => 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/SessionServlet.java mode change 100644 => 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java mode change 100644 => 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java create mode 100755 testsuite/integration/src/test/resources/adapter-test/input-keycloak.json create mode 100755 testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml create mode 100755 testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json create mode 100755 testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml create mode 100755 testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml create mode 100755 testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json create mode 100755 testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml create mode 100755 testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml create mode 100755 testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json create mode 100755 testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml create mode 100755 testsuite/tomcat6/src/test/resources/adapter-test/input-portal/META-INF/context.xml create mode 100755 testsuite/tomcat6/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json create mode 100755 testsuite/tomcat6/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml create mode 100755 testsuite/tomcat7/src/test/resources/adapter-test/input-portal/META-INF/context.xml create mode 100755 testsuite/tomcat7/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json create mode 100755 testsuite/tomcat7/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml create mode 100755 testsuite/tomcat8/src/test/resources/adapter-test/input-portal/META-INF/context.xml create mode 100755 testsuite/tomcat8/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json create mode 100755 testsuite/tomcat8/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml diff --git a/core/src/main/java/org/keycloak/util/UriUtils.java b/core/src/main/java/org/keycloak/util/UriUtils.java old mode 100644 new mode 100755 index 3f0f9afa62..70c68244ec --- a/core/src/main/java/org/keycloak/util/UriUtils.java +++ b/core/src/main/java/org/keycloak/util/UriUtils.java @@ -1,6 +1,8 @@ package org.keycloak.util; +import java.io.UnsupportedEncodingException; import java.net.URI; +import java.net.URLDecoder; import java.util.regex.Pattern; /** @@ -23,4 +25,42 @@ public class UriUtils { return originPattern.matcher(url).matches(); } + public static MultivaluedHashMap decodeQueryString(String queryString) { + MultivaluedHashMap map = new MultivaluedHashMap(); + if (queryString == null || queryString.equals("")) return map; + + String[] params = queryString.split("&"); + + for (String param : params) + { + if (param.indexOf('=') >= 0) + { + String[] nv = param.split("=", 2); + try + { + String name = URLDecoder.decode(nv[0], "UTF-8"); + String val = nv.length > 1 ? nv[1] : ""; + map.add(name, URLDecoder.decode(val, "UTF-8")); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + else + { + try + { + String name = URLDecoder.decode(param, "UTF-8"); + map.add(name, ""); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + } + return map; + } + } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java old mode 100644 new mode 100755 index 0d3be9621a..d07bffa3bd --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java @@ -38,4 +38,7 @@ public interface AdapterTokenStore { * @param securityContext context where refresh was performed */ void refreshCallback(RefreshableKeycloakSecurityContext securityContext); + + void saveRequest(); + boolean restoreRequest(); } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java index 312576e046..48b5ed9c9a 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java @@ -19,11 +19,12 @@ import java.util.concurrent.atomic.AtomicLong; * @author Bill Burke * @version $Revision: 1 $ */ -public abstract class OAuthRequestAuthenticator { +public class OAuthRequestAuthenticator { private static final Logger log = Logger.getLogger(OAuthRequestAuthenticator.class); protected KeycloakDeployment deployment; protected RequestAuthenticator reqAuthenticator; protected int sslRedirectPort; + protected AdapterTokenStore tokenStore; protected String tokenString; protected String idTokenString; protected IDToken idToken; @@ -33,11 +34,12 @@ public abstract class OAuthRequestAuthenticator { protected String refreshToken; protected String strippedOauthParametersRequestUri; - public OAuthRequestAuthenticator(RequestAuthenticator requestAuthenticator, HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort) { + public OAuthRequestAuthenticator(RequestAuthenticator requestAuthenticator, HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort, AdapterTokenStore tokenStore) { this.reqAuthenticator = requestAuthenticator; this.facade = facade; this.deployment = deployment; this.sslRedirectPort = sslRedirectPort; + this.tokenStore = tokenStore; } public AuthChallenge getChallenge() { @@ -200,7 +202,7 @@ public abstract class OAuthRequestAuthenticator { } else { log.debug("redirecting to auth server"); challenge = loginRedirect(); - saveRequest(); + tokenStore.saveRequest(); return AuthOutcome.NOT_ATTEMPTED; } } else { @@ -214,12 +216,6 @@ public abstract class OAuthRequestAuthenticator { } - /** - * Cache the request so that when we get redirected back, it gets invoked - * - */ - protected abstract void saveRequest(); - protected AuthChallenge challenge(final int code) { return new AuthChallenge() { @Override diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractJettyRequestAuthenticator.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractJettyRequestAuthenticator.java deleted file mode 100755 index c78169e181..0000000000 --- a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractJettyRequestAuthenticator.java +++ /dev/null @@ -1,148 +0,0 @@ -package org.keycloak.adapters.jetty; - -import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.security.authentication.FormAuthenticator; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.MultiMap; -import org.jboss.logging.Logger; -import org.keycloak.KeycloakPrincipal; -import org.keycloak.KeycloakSecurityContext; -import org.keycloak.adapters.AdapterTokenStore; -import org.keycloak.adapters.AdapterUtils; -import org.keycloak.adapters.HttpFacade; -import org.keycloak.adapters.KeycloakAccount; -import org.keycloak.adapters.KeycloakDeployment; -import org.keycloak.adapters.OAuthRequestAuthenticator; -import org.keycloak.adapters.RefreshableKeycloakSecurityContext; -import org.keycloak.adapters.RequestAuthenticator; -import org.keycloak.enums.TokenStore; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; -import java.security.Principal; -import java.util.Set; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public abstract class AbstractJettyRequestAuthenticator extends RequestAuthenticator { - public final static String __J_METHOD = "org.eclipse.jetty.security.HTTP_METHOD"; - protected static final Logger log = Logger.getLogger(AbstractJettyRequestAuthenticator.class); - protected AbstractKeycloakJettyAuthenticator valve; - protected Request request; - protected KeycloakPrincipal principal; - - public AbstractJettyRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, AbstractKeycloakJettyAuthenticator valve, Request request) { - super(facade, deployment, tokenStore, sslRedirectPort); - this.valve = valve; - this.request = request; - } - - @Override - protected OAuthRequestAuthenticator createOAuthAuthenticator() { - return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) { - @Override - protected void saveRequest() { - if (deployment.getTokenStore() == TokenStore.SESSION) { - saveServletRequest(); - } - } - }; - } - - @Override - protected void completeOAuthAuthentication(final KeycloakPrincipal skp) { - principal = skp; - final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext(); - final Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext); - KeycloakAccount account = new KeycloakAccount() { - - @Override - public Principal getPrincipal() { - return skp; - } - - @Override - public Set getRoles() { - return roles; - } - - @Override - public KeycloakSecurityContext getKeycloakSecurityContext() { - return securityContext; - } - - }; - request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); - this.tokenStore.saveAccountInfo(account); - } - - @Override - protected void completeBearerAuthentication(KeycloakPrincipal principal, String method) { - this.principal = principal; - RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext(); - Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext); - if (log.isDebugEnabled()) { - log.debug("Completing bearer authentication. Bearer roles: " + roles); - } - request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); - } - - protected void restoreRequest() { - HttpSession session = request.getSession(false); - if (session == null) return; - synchronized (session) { - String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI); - if (j_uri != null) { - // check if the request is for the same url as the original and restore - // params if it was a post - StringBuffer buf = request.getRequestURL(); - if (request.getQueryString() != null) - buf.append("?").append(request.getQueryString()); - - /* - * if (j_uri.equals(buf.toString())) { - */ - MultiMap j_post = (MultiMap) session.getAttribute(FormAuthenticator.__J_POST); - if (j_post != null) { - restoreFormParameters(j_post, request); - } - session.removeAttribute(FormAuthenticator.__J_URI); - session.removeAttribute(__J_METHOD); - session.removeAttribute(FormAuthenticator.__J_POST); - // } - } - } - } - - @Override - protected String getHttpSessionId(boolean create) { - HttpSession session = request.getSession(create); - return session != null ? session.getId() : null; - } - - protected void saveServletRequest() { - // remember the current URI - HttpSession session = request.getSession(); - synchronized (session) { - // But only if it is not set already, or we save every uri that leads to a login form redirect - if (session.getAttribute(FormAuthenticator.__J_URI) == null) { - StringBuffer buf = request.getRequestURL(); - if (request.getQueryString() != null) - buf.append("?").append(request.getQueryString()); - session.setAttribute(FormAuthenticator.__J_URI, buf.toString()); - session.setAttribute(__J_METHOD, request.getMethod()); - - if ("application/x-www-form-urlencoded".equals(request.getContentType()) && "POST".equalsIgnoreCase(request.getMethod())) { - MultiMap formParameters = extractFormParameters(request); - session.setAttribute(FormAuthenticator.__J_POST, formParameters); - } - } - } - } - - protected abstract MultiMap extractFormParameters(Request base_request); - - protected abstract void restoreFormParameters(MultiMap j_post, Request base_request); -} diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractJettySessionTokenStore.java similarity index 80% rename from integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java rename to integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractJettySessionTokenStore.java index 1f40ee0a60..f6be3da0a6 100755 --- a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java +++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractJettySessionTokenStore.java @@ -1,94 +1,99 @@ -package org.keycloak.adapters.jetty; - -import org.eclipse.jetty.server.Request; -import org.jboss.logging.Logger; -import org.keycloak.KeycloakPrincipal; -import org.keycloak.KeycloakSecurityContext; -import org.keycloak.adapters.AdapterTokenStore; -import org.keycloak.adapters.AdapterUtils; -import org.keycloak.adapters.KeycloakAccount; -import org.keycloak.adapters.KeycloakDeployment; -import org.keycloak.adapters.RefreshableKeycloakSecurityContext; -import org.keycloak.adapters.RequestAuthenticator; - -import javax.servlet.http.HttpSession; - -/** - * Handle storage of token info in HTTP Session. Per-request object - * - * @author Marek Posolda - */ -public class JettySessionTokenStore implements AdapterTokenStore { - - private static final Logger log = Logger.getLogger(JettySessionTokenStore.class); - - private Request request; - private KeycloakDeployment deployment; - - public JettySessionTokenStore(Request request, KeycloakDeployment deployment) { - this.request = request; - this.deployment = deployment; - } - - @Override - public void checkCurrentToken() { - if (request.getSession(false) == null) return; - RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSession().getAttribute(KeycloakSecurityContext.class.getName()); - if (session == null) return; - - // just in case session got serialized - if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this); - - if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return; - - // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will - // not be updated - boolean success = session.refreshExpiredToken(false); - if (success && session.isActive()) return; - - // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session - request.getSession().removeAttribute(KeycloakSecurityContext.class.getName()); - request.getSession().invalidate(); - } - - @Override - public boolean isCached(RequestAuthenticator authenticator) { - if (request.getSession(false) == null || request.getSession().getAttribute(KeycloakSecurityContext.class.getName()) == null) - return false; - log.debug("remote logged in already. Establish state from session"); - - RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSession().getAttribute(KeycloakSecurityContext.class.getName()); - if (!deployment.getRealm().equals(securityContext.getRealm())) { - log.debug("Account from cookie is from a different realm than for the request."); - return false; - } - - securityContext.setCurrentRequestInfo(deployment, this); - request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); - - AbstractJettyRequestAuthenticator jettyAuthenticator = (AbstractJettyRequestAuthenticator) authenticator; - KeycloakPrincipal principal = AdapterUtils.createPrincipal(deployment, securityContext); - jettyAuthenticator.principal = principal; - jettyAuthenticator.restoreRequest(); - return true; - } - - @Override - public void saveAccountInfo(KeycloakAccount account) { - RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext(); - request.getSession().setAttribute(KeycloakSecurityContext.class.getName(), securityContext); - } - - @Override - public void logout() { - HttpSession session = request.getSession(false); - if (session != null) { - session.removeAttribute(KeycloakSecurityContext.class.getName()); - } - } - - @Override - public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) { - // no-op - } -} +package org.keycloak.adapters.jetty; + +import org.eclipse.jetty.security.authentication.FormAuthenticator; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.MultiMap; +import org.jboss.logging.Logger; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.AdapterTokenStore; +import org.keycloak.adapters.AdapterUtils; +import org.keycloak.adapters.KeycloakAccount; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.RefreshableKeycloakSecurityContext; +import org.keycloak.adapters.RequestAuthenticator; +import org.keycloak.util.MultivaluedHashMap; + +import javax.servlet.http.HttpSession; + +/** + * Handle storage of token info in HTTP Session. Per-request object + * + * @author Marek Posolda + */ +public abstract class AbstractJettySessionTokenStore implements AdapterTokenStore { + public final static String __J_METHOD = "org.eclipse.jetty.security.HTTP_METHOD"; + + private static final Logger log = Logger.getLogger(AbstractJettySessionTokenStore.class); + + private Request request; + protected KeycloakDeployment deployment; + + public AbstractJettySessionTokenStore(Request request, KeycloakDeployment deployment) { + this.request = request; + this.deployment = deployment; + } + + @Override + public void checkCurrentToken() { + if (request.getSession(false) == null) return; + RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSession().getAttribute(KeycloakSecurityContext.class.getName()); + if (session == null) return; + + // just in case session got serialized + if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this); + + if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return; + + // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will + // not be updated + boolean success = session.refreshExpiredToken(false); + if (success && session.isActive()) return; + + // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session + request.getSession().removeAttribute(KeycloakSecurityContext.class.getName()); + request.getSession().invalidate(); + } + + @Override + public boolean isCached(RequestAuthenticator authenticator) { + if (request.getSession(false) == null || request.getSession().getAttribute(KeycloakSecurityContext.class.getName()) == null) + return false; + log.debug("remote logged in already. Establish state from session"); + + RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSession().getAttribute(KeycloakSecurityContext.class.getName()); + if (!deployment.getRealm().equals(securityContext.getRealm())) { + log.debug("Account from cookie is from a different realm than for the request."); + return false; + } + + securityContext.setCurrentRequestInfo(deployment, this); + request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); + + JettyRequestAuthenticator jettyAuthenticator = (JettyRequestAuthenticator) authenticator; + KeycloakPrincipal principal = AdapterUtils.createPrincipal(deployment, securityContext); + jettyAuthenticator.principal = principal; + restoreRequest(); + return true; + } + + @Override + public void saveAccountInfo(KeycloakAccount account) { + RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) account.getKeycloakSecurityContext(); + request.getSession().setAttribute(KeycloakSecurityContext.class.getName(), securityContext); + } + + @Override + public void logout() { + HttpSession session = request.getSession(false); + if (session != null) { + session.removeAttribute(KeycloakSecurityContext.class.getName()); + } + } + + @Override + public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) { + // no-op + } + +} diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractKeycloakJettyAuthenticator.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractKeycloakJettyAuthenticator.java index 3d813e60a6..2e78eed9f3 100755 --- a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractKeycloakJettyAuthenticator.java +++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/AbstractKeycloakJettyAuthenticator.java @@ -65,14 +65,14 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica return new ByteArrayInputStream(json.getBytes()); } - public static AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) { + public AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) { AdapterTokenStore store = (AdapterTokenStore)request.getAttribute(TOKEN_STORE_NOTE); if (store != null) { return store; } if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) { - store = new JettySessionTokenStore(request, resolvedDeployment); + store = createSessionTokenStore(request, resolvedDeployment); } else { store = new JettyCookieTokenStore(request, facade, resolvedDeployment); } @@ -81,7 +81,9 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica return store; } - public static void logoutCurrent(Request request) { + public abstract AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment); + + public void logoutCurrent(Request request) { AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext)request.getAttribute(AdapterDeploymentContext.class.getName()); KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName()); if (ksc != null) { @@ -212,7 +214,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica nodesRegistrationManagement.tryRegister(deployment); tokenStore.checkCurrentToken(); - AbstractJettyRequestAuthenticator authenticator = createRequestAuthenticator(request, facade, deployment, tokenStore); + JettyRequestAuthenticator authenticator = createRequestAuthenticator(request, facade, deployment, tokenStore); AuthOutcome outcome = authenticator.authenticate(); if (outcome == AuthOutcome.AUTHENTICATED) { if (facade.isEnded()) { @@ -238,7 +240,10 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica protected abstract Request resolveRequest(ServletRequest req); - protected abstract AbstractJettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore); + protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade, + KeycloakDeployment deployment, AdapterTokenStore tokenStore) { + return new JettyRequestAuthenticator(facade, deployment, tokenStore, -1, request); + } @Override public String getAuthMethod() { diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java index c0b1685e3d..3020f7536a 100755 --- a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java +++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java @@ -99,4 +99,14 @@ public class JettyCookieTokenStore implements AdapterTokenStore { CookieTokenStore.removeCookie(facade); return null; } + + @Override + public void saveRequest() { + + } + + @Override + public boolean restoreRequest() { + return false; + } } diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyHttpFacade.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyHttpFacade.java index 75520ab8a6..5a8c4c3ade 100755 --- a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyHttpFacade.java +++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyHttpFacade.java @@ -2,6 +2,8 @@ package org.keycloak.adapters.jetty; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.HttpFacade; +import org.keycloak.util.MultivaluedHashMap; +import org.keycloak.util.UriUtils; import javax.security.cert.X509Certificate; import javax.servlet.http.HttpServletResponse; @@ -21,6 +23,7 @@ public class JettyHttpFacade implements HttpFacade { protected HttpServletResponse response; protected RequestFacade requestFacade = new RequestFacade(); protected ResponseFacade responseFacade = new ResponseFacade(); + protected MultivaluedHashMap queryParameters; protected class RequestFacade implements Request { @Override @@ -39,7 +42,10 @@ public class JettyHttpFacade implements HttpFacade { @Override public String getQueryParamValue(String paramName) { - return request.getParameter(paramName); + if (queryParameters == null) { + queryParameters = UriUtils.decodeQueryString(request.getQueryString()); + } + return queryParameters.getFirst(paramName); } @Override diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java new file mode 100755 index 0000000000..0556b01e01 --- /dev/null +++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java @@ -0,0 +1,85 @@ +package org.keycloak.adapters.jetty; + +import org.eclipse.jetty.server.Request; +import org.jboss.logging.Logger; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.AdapterTokenStore; +import org.keycloak.adapters.AdapterUtils; +import org.keycloak.adapters.HttpFacade; +import org.keycloak.adapters.KeycloakAccount; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.OAuthRequestAuthenticator; +import org.keycloak.adapters.RefreshableKeycloakSecurityContext; +import org.keycloak.adapters.RequestAuthenticator; + +import javax.servlet.http.HttpSession; +import java.security.Principal; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JettyRequestAuthenticator extends RequestAuthenticator { + protected static final Logger log = Logger.getLogger(JettyRequestAuthenticator.class); + protected Request request; + protected KeycloakPrincipal principal; + + public JettyRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) { + super(facade, deployment, tokenStore, sslRedirectPort); + this.request = request; + } + + @Override + protected OAuthRequestAuthenticator createOAuthAuthenticator() { + return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore); + } + + @Override + protected void completeOAuthAuthentication(final KeycloakPrincipal skp) { + principal = skp; + final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext(); + final Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext); + KeycloakAccount account = new KeycloakAccount() { + + @Override + public Principal getPrincipal() { + return skp; + } + + @Override + public Set getRoles() { + return roles; + } + + @Override + public KeycloakSecurityContext getKeycloakSecurityContext() { + return securityContext; + } + + }; + request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); + this.tokenStore.saveAccountInfo(account); + } + + @Override + protected void completeBearerAuthentication(KeycloakPrincipal principal, String method) { + this.principal = principal; + RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext(); + Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext); + if (log.isDebugEnabled()) { + log.debug("Completing bearer authentication. Bearer roles: " + roles); + } + request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); + } + + + @Override + protected String getHttpSessionId(boolean create) { + HttpSession session = request.getSession(create); + return session != null ? session.getId() : null; + } + + +} diff --git a/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java b/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java deleted file mode 100755 index 60059f2b6f..0000000000 --- a/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.keycloak.adapters.jetty; - -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.MultiMap; -import org.keycloak.adapters.AdapterTokenStore; -import org.keycloak.adapters.KeycloakDeployment; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class JettyRequestAuthenticator extends AbstractJettyRequestAuthenticator { - - public JettyRequestAuthenticator(KeycloakDeployment deployment, - AbstractKeycloakJettyAuthenticator valve, AdapterTokenStore tokenStore, - JettyHttpFacade facade, - Request request) { - super(facade, deployment, tokenStore, -1, valve, request); - } - - - @Override - protected MultiMap extractFormParameters(Request base_request) { - MultiMap formParameters = new MultiMap(); - base_request.extractParameters(); - return base_request.getParameters(); - } - @Override - protected void restoreFormParameters(MultiMap j_post, Request base_request) { - base_request.setParameters(j_post); - } -} diff --git a/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java b/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java new file mode 100755 index 0000000000..e2dcfa5aac --- /dev/null +++ b/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java @@ -0,0 +1,93 @@ +package org.keycloak.adapters.jetty; + +import org.eclipse.jetty.security.authentication.FormAuthenticator; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.MultiMap; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.util.MultivaluedHashMap; + +import javax.servlet.http.HttpSession; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JettySessionTokenStore extends AbstractJettySessionTokenStore { + public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS"; + protected Request myRequest; + + public JettySessionTokenStore(Request request, KeycloakDeployment deployment) { + super(request, deployment); + this.myRequest = request; // for IDE/compilation purposes + } + + protected MultiMap extractFormParameters(Request base_request) { + MultiMap formParameters = new MultiMap(); + base_request.extractParameters(); + return base_request.getParameters(); + } + protected void restoreFormParameters(MultiMap j_post, Request base_request) { + base_request.setParameters(j_post); + } + + public boolean restoreRequest() { + HttpSession session = myRequest.getSession(false); + if (session == null) return false; + synchronized (session) { + String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI); + if (j_uri != null) { + // check if the request is for the same url as the original and restore + // params if it was a post + StringBuffer buf = myRequest.getRequestURL(); + if (myRequest.getQueryString() != null) + buf.append("?").append(myRequest.getQueryString()); + if (j_uri.equals(buf.toString())) { + String method = (String)session.getAttribute(__J_METHOD); + myRequest.setMethod(method); + MultivaluedHashMap j_post = (MultivaluedHashMap) session.getAttribute(CACHED_FORM_PARAMETERS); + if (j_post != null) { + MultiMap map = new MultiMap(); + for (String key : j_post.keySet()) { + for (String val : j_post.getList(key)) { + map.add(key, val); + } + } + restoreFormParameters(map, myRequest); + } + session.removeAttribute(FormAuthenticator.__J_URI); + session.removeAttribute(__J_METHOD); + session.removeAttribute(FormAuthenticator.__J_POST); + } + return true; + } + } + return false; + } + + public void saveRequest() { + // remember the current URI + HttpSession session = myRequest.getSession(); + synchronized (session) { + // But only if it is not set already, or we save every uri that leads to a login form redirect + if (session.getAttribute(FormAuthenticator.__J_URI) == null) { + StringBuffer buf = myRequest.getRequestURL(); + if (myRequest.getQueryString() != null) + buf.append("?").append(myRequest.getQueryString()); + session.setAttribute(FormAuthenticator.__J_URI, buf.toString()); + session.setAttribute(__J_METHOD, myRequest.getMethod()); + + if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) { + MultiMap formParameters = extractFormParameters(myRequest); + MultivaluedHashMap map = new MultivaluedHashMap(); + for (String key : formParameters.keySet()) { + for (Object value : formParameters.getValues(key)) { + map.add(key, (String) value); + } + } + session.setAttribute(CACHED_FORM_PARAMETERS, map); + } + } + } + } + +} diff --git a/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java index 06e1d99374..d1ae6e0040 100755 --- a/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java +++ b/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java @@ -21,8 +21,8 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat @Override - protected AbstractJettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore) { - return new JettyRequestAuthenticator(deployment, this, tokenStore, facade, request); + public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) { + return new JettySessionTokenStore(request, resolvedDeployment); } @Override diff --git a/integration/jetty/jetty9.1/pom.xml b/integration/jetty/jetty9.1/pom.xml index 0ce853bb45..6c23f5ad88 100755 --- a/integration/jetty/jetty9.1/pom.xml +++ b/integration/jetty/jetty9.1/pom.xml @@ -36,6 +36,20 @@ org.keycloak keycloak-jetty-core ${project.version} + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty + jetty-util + + + org.eclipse.jetty + jetty-security + + org.apache.httpcomponents diff --git a/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java b/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java deleted file mode 100755 index 60059f2b6f..0000000000 --- a/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.keycloak.adapters.jetty; - -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.MultiMap; -import org.keycloak.adapters.AdapterTokenStore; -import org.keycloak.adapters.KeycloakDeployment; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class JettyRequestAuthenticator extends AbstractJettyRequestAuthenticator { - - public JettyRequestAuthenticator(KeycloakDeployment deployment, - AbstractKeycloakJettyAuthenticator valve, AdapterTokenStore tokenStore, - JettyHttpFacade facade, - Request request) { - super(facade, deployment, tokenStore, -1, valve, request); - } - - - @Override - protected MultiMap extractFormParameters(Request base_request) { - MultiMap formParameters = new MultiMap(); - base_request.extractParameters(); - return base_request.getParameters(); - } - @Override - protected void restoreFormParameters(MultiMap j_post, Request base_request) { - base_request.setParameters(j_post); - } -} diff --git a/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java b/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java new file mode 100755 index 0000000000..24fc9b258b --- /dev/null +++ b/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java @@ -0,0 +1,94 @@ +package org.keycloak.adapters.jetty; + +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.security.authentication.FormAuthenticator; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.MultiMap; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.util.MultivaluedHashMap; + +import javax.servlet.http.HttpSession; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JettySessionTokenStore extends AbstractJettySessionTokenStore { + public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS"; + protected Request myRequest; + + public JettySessionTokenStore(Request request, KeycloakDeployment deployment) { + super(request, deployment); + this.myRequest = request; // for IDE/compilation purposes + } + + protected MultiMap extractFormParameters(Request base_request) { + MultiMap formParameters = new MultiMap(); + base_request.extractParameters(); + return base_request.getParameters(); + } + protected void restoreFormParameters(MultiMap j_post, Request base_request) { + base_request.setParameters(j_post); + } + + public boolean restoreRequest() { + HttpSession session = myRequest.getSession(false); + if (session == null) return false; + synchronized (session) { + String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI); + if (j_uri != null) { + // check if the request is for the same url as the original and restore + // params if it was a post + StringBuffer buf = myRequest.getRequestURL(); + if (myRequest.getQueryString() != null) + buf.append("?").append(myRequest.getQueryString()); + if (j_uri.equals(buf.toString())) { + String method = (String)session.getAttribute(__J_METHOD); + myRequest.setMethod(HttpMethod.valueOf(method.toUpperCase()), method); + MultivaluedHashMap j_post = (MultivaluedHashMap) session.getAttribute(CACHED_FORM_PARAMETERS); + if (j_post != null) { + MultiMap map = new MultiMap(); + for (String key : j_post.keySet()) { + for (String val : j_post.getList(key)) { + map.add(key, val); + } + } + restoreFormParameters(map, myRequest); + } + session.removeAttribute(FormAuthenticator.__J_URI); + session.removeAttribute(__J_METHOD); + session.removeAttribute(FormAuthenticator.__J_POST); + } + return true; + } + } + return false; + } + + public void saveRequest() { + // remember the current URI + HttpSession session = myRequest.getSession(); + synchronized (session) { + // But only if it is not set already, or we save every uri that leads to a login form redirect + if (session.getAttribute(FormAuthenticator.__J_URI) == null) { + StringBuffer buf = myRequest.getRequestURL(); + if (myRequest.getQueryString() != null) + buf.append("?").append(myRequest.getQueryString()); + session.setAttribute(FormAuthenticator.__J_URI, buf.toString()); + session.setAttribute(__J_METHOD, myRequest.getMethod()); + + if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) { + MultiMap formParameters = extractFormParameters(myRequest); + MultivaluedHashMap map = new MultivaluedHashMap(); + for (String key : formParameters.keySet()) { + for (Object value : formParameters.getValues(key)) { + map.add(key, (String) value); + } + } + session.setAttribute(CACHED_FORM_PARAMETERS, map); + } + } + } + } + +} diff --git a/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java index 7ce92f8782..db15f33f2a 100755 --- a/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java +++ b/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java @@ -21,8 +21,8 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat @Override - protected AbstractJettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore) { - return new JettyRequestAuthenticator(deployment, this, tokenStore, facade, request); + public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) { + return new JettySessionTokenStore(request, resolvedDeployment); } @Override diff --git a/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java b/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java deleted file mode 100755 index 9b08794b66..0000000000 --- a/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.keycloak.adapters.jetty; - -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.MultiMap; -import org.keycloak.adapters.AdapterTokenStore; -import org.keycloak.adapters.KeycloakDeployment; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class JettyRequestAuthenticator extends AbstractJettyRequestAuthenticator { - - public JettyRequestAuthenticator(KeycloakDeployment deployment, - AbstractKeycloakJettyAuthenticator valve, AdapterTokenStore tokenStore, - JettyHttpFacade facade, - Request request) { - super(facade, deployment, tokenStore, -1, valve, request); - } - - - @Override - protected MultiMap extractFormParameters(Request base_request) { - MultiMap formParameters = new MultiMap(); - base_request.extractFormParameters(formParameters); - return formParameters; - } - @Override - protected void restoreFormParameters(MultiMap j_post, Request base_request) { - base_request.setContentParameters(j_post); - } -} diff --git a/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java b/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java new file mode 100755 index 0000000000..4ff9af9aab --- /dev/null +++ b/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java @@ -0,0 +1,95 @@ +package org.keycloak.adapters.jetty; + +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.security.authentication.FormAuthenticator; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.MultiMap; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.util.MultivaluedHashMap; + +import javax.servlet.http.HttpSession; +import java.lang.reflect.Field; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JettySessionTokenStore extends AbstractJettySessionTokenStore { + public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS"; + protected Request myRequest; + + public JettySessionTokenStore(Request request, KeycloakDeployment deployment) { + super(request, deployment); + this.myRequest = request; // for IDE/compilation purposes + } + + protected MultiMap extractFormParameters(Request base_request) { + MultiMap formParameters = new MultiMap(); + base_request.extractFormParameters(formParameters); + return formParameters; + } + protected void restoreFormParameters(MultiMap j_post, Request base_request) { + base_request.setContentParameters(j_post); + } + + public boolean restoreRequest() { + HttpSession session = myRequest.getSession(false); + if (session == null) return false; + synchronized (session) { + String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI); + if (j_uri != null) { + // check if the request is for the same url as the original and restore + // params if it was a post + StringBuffer buf = myRequest.getRequestURL(); + if (myRequest.getQueryString() != null) + buf.append("?").append(myRequest.getQueryString()); + if (j_uri.equals(buf.toString())) { + String method = (String)session.getAttribute(__J_METHOD); + myRequest.setMethod(HttpMethod.valueOf(method.toUpperCase()), method); + MultivaluedHashMap j_post = (MultivaluedHashMap) session.getAttribute(CACHED_FORM_PARAMETERS); + if (j_post != null) { + MultiMap map = new MultiMap(); + for (String key : j_post.keySet()) { + for (String val : j_post.getList(key)) { + map.add(key, val); + } + } + restoreFormParameters(map, myRequest); + } + session.removeAttribute(FormAuthenticator.__J_URI); + session.removeAttribute(__J_METHOD); + session.removeAttribute(FormAuthenticator.__J_POST); + } + return true; + } + } + return false; + } + + public void saveRequest() { + // remember the current URI + HttpSession session = myRequest.getSession(); + synchronized (session) { + // But only if it is not set already, or we save every uri that leads to a login form redirect + if (session.getAttribute(FormAuthenticator.__J_URI) == null) { + StringBuffer buf = myRequest.getRequestURL(); + if (myRequest.getQueryString() != null) + buf.append("?").append(myRequest.getQueryString()); + session.setAttribute(FormAuthenticator.__J_URI, buf.toString()); + session.setAttribute(__J_METHOD, myRequest.getMethod()); + + if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) { + MultiMap formParameters = extractFormParameters(myRequest); + MultivaluedHashMap map = new MultivaluedHashMap(); + for (String key : formParameters.keySet()) { + for (Object value : formParameters.getValues(key)) { + map.add(key, (String) value); + } + } + session.setAttribute(CACHED_FORM_PARAMETERS, map); + } + } + } + } + +} diff --git a/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java index d38e7d44cc..b43e27cc8d 100755 --- a/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java +++ b/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java @@ -20,12 +20,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat } - @Override - protected AbstractJettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore) { - return new JettyRequestAuthenticator(deployment, this, tokenStore, facade, request); - } - - @Override + @Override protected Request resolveRequest(ServletRequest req) { return (req instanceof Request) ? (Request)req : HttpChannel.getCurrentHttpChannel().getRequest(); } @@ -40,6 +35,8 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat }; } - - + @Override + public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) { + return new JettySessionTokenStore(request, resolvedDeployment); + } } diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java index cb05e72c13..acf33a4b78 100755 --- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java +++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java @@ -5,9 +5,12 @@ import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; import org.apache.catalina.Manager; +import org.apache.catalina.authenticator.Constants; import org.apache.catalina.authenticator.FormAuthenticator; +import org.apache.catalina.authenticator.SavedRequest; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; +import org.apache.tomcat.util.buf.ByteChunk; import org.keycloak.KeycloakSecurityContext; import org.keycloak.constants.AdapterConstants; import org.keycloak.adapters.AdapterDeploymentContext; @@ -24,12 +27,15 @@ import org.keycloak.enums.TokenStore; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.Enumeration; +import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; import org.keycloak.adapters.KeycloakConfigResolver; @@ -180,7 +186,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat nodesRegistrationManagement.tryRegister(deployment); - CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, tokenStore, facade, request, createPrincipalFactory()); + CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, tokenStore, facade, request, createPrincipalFactory()); AuthOutcome outcome = authenticator.authenticate(); if (outcome == AuthOutcome.AUTHENTICATED) { if (facade.isEnded()) { @@ -225,7 +231,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat } if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) { - store = new CatalinaSessionTokenStore(request, resolvedDeployment, userSessionManagement, createPrincipalFactory()); + store = new CatalinaSessionTokenStore(request, resolvedDeployment, userSessionManagement, createPrincipalFactory(), this); } else { store = new CatalinaCookieTokenStore(request, facade, resolvedDeployment, createPrincipalFactory()); } diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaCookieTokenStore.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaCookieTokenStore.java index 229efa3fe9..b0b21ab8f6 100755 --- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaCookieTokenStore.java +++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaCookieTokenStore.java @@ -84,6 +84,16 @@ public class CatalinaCookieTokenStore implements AdapterTokenStore { CookieTokenStore.setTokenCookie(deployment, facade, secContext); } + @Override + public void saveRequest() { + + } + + @Override + public boolean restoreRequest() { + return false; + } + /** * Verify if we already have authenticated and active principal in cookie. Perform refresh if it's not active * diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java index cf76d13740..784a1fa3b1 100755 --- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java +++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java @@ -2,7 +2,9 @@ package org.keycloak.adapters.tomcat; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.HttpFacade; +import org.keycloak.util.MultivaluedHashMap; import org.keycloak.util.ServerCookie; +import org.keycloak.util.UriUtils; import javax.security.cert.X509Certificate; import javax.servlet.http.HttpServletResponse; @@ -22,6 +24,7 @@ public class CatalinaHttpFacade implements HttpFacade { protected HttpServletResponse response; protected RequestFacade requestFacade = new RequestFacade(); protected ResponseFacade responseFacade = new ResponseFacade(); + protected MultivaluedHashMap queryParameters; protected class RequestFacade implements Request { @Override @@ -40,7 +43,10 @@ public class CatalinaHttpFacade implements HttpFacade { @Override public String getQueryParamValue(String paramName) { - return request.getParameter(paramName); + if (queryParameters == null) { + queryParameters = UriUtils.decodeQueryString(request.getQueryString()); + } + return queryParameters.getFirst(paramName); } @Override diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaRequestAuthenticator.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaRequestAuthenticator.java index 13d6835d4d..58989ef4d0 100755 --- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaRequestAuthenticator.java +++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaRequestAuthenticator.java @@ -27,36 +27,22 @@ import javax.servlet.http.HttpSession; */ public class CatalinaRequestAuthenticator extends RequestAuthenticator { private static final Logger log = Logger.getLogger(""+CatalinaRequestAuthenticator.class); - protected AbstractKeycloakAuthenticatorValve valve; protected Request request; protected GenericPrincipalFactory principalFactory; public CatalinaRequestAuthenticator(KeycloakDeployment deployment, - AbstractKeycloakAuthenticatorValve valve, AdapterTokenStore tokenStore, + AdapterTokenStore tokenStore, CatalinaHttpFacade facade, Request request, GenericPrincipalFactory principalFactory) { super(facade, deployment, tokenStore, request.getConnector().getRedirectPort()); - this.valve = valve; this.request = request; this.principalFactory = principalFactory; } @Override protected OAuthRequestAuthenticator createOAuthAuthenticator() { - return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) { - @Override - protected void saveRequest() { - try { - // Support saving request just for TokenStore.SESSION TODO: Add to tokenStore spi? - if (deployment.getTokenStore() == TokenStore.SESSION) { - valve.keycloakSaveRequest(request); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }; + return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore); } @Override @@ -99,17 +85,6 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator { request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); } - protected void restoreRequest() { - if (request.getSessionInternal().getNote(Constants.FORM_REQUEST_NOTE) != null) { - if (valve.keycloakRestoreRequest(request)) { - log.finer("restoreRequest"); - } else { - log.finer("Restore of original request failed"); - throw new RuntimeException("Restore of original request failed"); - } - } - } - @Override protected String getHttpSessionId(boolean create) { HttpSession session = request.getSession(create); diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java index 761a1d5d86..0e9cce1f73 100755 --- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java +++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java @@ -1,5 +1,6 @@ package org.keycloak.adapters.tomcat; +import java.io.IOException; import java.util.Set; import java.util.logging.Logger; @@ -24,12 +25,18 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore { private KeycloakDeployment deployment; private CatalinaUserSessionManagement sessionManagement; protected GenericPrincipalFactory principalFactory; + protected AbstractKeycloakAuthenticatorValve valve; - public CatalinaSessionTokenStore(Request request, KeycloakDeployment deployment, CatalinaUserSessionManagement sessionManagement, GenericPrincipalFactory principalFactory) { + + public CatalinaSessionTokenStore(Request request, KeycloakDeployment deployment, + CatalinaUserSessionManagement sessionManagement, + GenericPrincipalFactory principalFactory, + AbstractKeycloakAuthenticatorValve valve) { this.request = request; this.deployment = deployment; this.sessionManagement = sessionManagement; this.principalFactory = principalFactory; + this.valve = valve; } @Override @@ -81,7 +88,7 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore { request.setUserPrincipal(principal); request.setAuthType("KEYCLOAK"); - ((CatalinaRequestAuthenticator)authenticator).restoreRequest(); + restoreRequest(); return true; } @@ -112,4 +119,18 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore { public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) { // no-op } + + @Override + public void saveRequest() { + try { + valve.keycloakSaveRequest(request); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean restoreRequest() { + return valve.keycloakRestoreRequest(request); + } } diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java index 5ef0734800..f154ba3698 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java @@ -53,12 +53,7 @@ public abstract class AbstractUndertowRequestAuthenticator extends RequestAuthen @Override protected OAuthRequestAuthenticator createOAuthAuthenticator() { - return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) { - @Override - protected void saveRequest() { - // todo - } - }; + return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore); } @Override diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SavedRequest.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SavedRequest.java new file mode 100755 index 0000000000..3aae9c6dbb --- /dev/null +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SavedRequest.java @@ -0,0 +1,131 @@ +package org.keycloak.adapters.undertow; +import io.undertow.UndertowLogger; +import io.undertow.UndertowOptions; +import io.undertow.server.Connectors; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.session.Session; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.spec.HttpSessionImpl; +import io.undertow.util.HeaderMap; +import io.undertow.util.HeaderValues; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.ImmediatePooled; + +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.util.Iterator; + +/** + * Saved servlet request. + * + * Note bill burke: I had to fork this because Undertow was automatically restoring the request before the code could be processed and redirected. + * + * @author Stuart Douglas + */ +public class SavedRequest implements Serializable { + + private static final String SESSION_KEY = SavedRequest.class.getName(); + + private final byte[] data; + private final int dataLength; + private final HttpString method; + private final String requestUri; + private final HeaderMap headerMap; + + public SavedRequest(byte[] data, int dataLength, HttpString method, String requestUri, HeaderMap headerMap) { + this.data = data; + this.dataLength = dataLength; + this.method = method; + this.requestUri = requestUri; + this.headerMap = headerMap; + } + + public static void trySaveRequest(final HttpServerExchange exchange) { + int maxSize = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_BUFFERED_REQUEST_SIZE, 16384); + if (maxSize > 0) { + //if this request has a body try and cache the response + if (!exchange.isRequestComplete()) { + final long requestContentLength = exchange.getRequestContentLength(); + if (requestContentLength > maxSize) { + UndertowLogger.REQUEST_LOGGER.debugf("Request to %s was to large to save", exchange.getRequestURI()); + return;//failed to save the request, we just return + } + //TODO: we should really be used pooled buffers + //TODO: we should probably limit the number of saved requests at any given time + byte[] buffer = new byte[maxSize]; + int read = 0; + int res = 0; + InputStream in = exchange.getInputStream(); + try { + while ((res = in.read(buffer, read, buffer.length - read)) > 0) { + read += res; + if (read == maxSize) { + UndertowLogger.REQUEST_LOGGER.debugf("Request to %s was to large to save", exchange.getRequestURI()); + return;//failed to save the request, we just return + } + } + HeaderMap headers = new HeaderMap(); + for(HeaderValues entry : exchange.getRequestHeaders()) { + if(entry.getHeaderName().equals(Headers.CONTENT_LENGTH) || + entry.getHeaderName().equals(Headers.TRANSFER_ENCODING) || + entry.getHeaderName().equals(Headers.CONNECTION)) { + continue; + } + headers.putAll(entry.getHeaderName(), entry); + } + SavedRequest request = new SavedRequest(buffer, read, exchange.getRequestMethod(), exchange.getRequestURI(), exchange.getRequestHeaders()); + final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + HttpSessionImpl session = sc.getCurrentServletContext().getSession(exchange, true); + Session underlyingSession; + if(System.getSecurityManager() == null) { + underlyingSession = session.getSession(); + } else { + underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); + } + underlyingSession.setAttribute(SESSION_KEY, request); + } catch (IOException e) { + UndertowLogger.REQUEST_IO_LOGGER.ioException(e); + } + } + } + } + + public static void tryRestoreRequest(final HttpServerExchange exchange, HttpSession session) { + if(session instanceof HttpSessionImpl) { + + Session underlyingSession; + if(System.getSecurityManager() == null) { + underlyingSession = ((HttpSessionImpl) session).getSession(); + } else { + underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); + } + SavedRequest request = (SavedRequest) underlyingSession.getAttribute(SESSION_KEY); + if(request != null) { + if(request.requestUri.equals(exchange.getRequestURI()) && exchange.isRequestComplete()) { + UndertowLogger.REQUEST_LOGGER.debugf("restoring request body for request to %s", request.requestUri); + exchange.setRequestMethod(request.method); + Connectors.ungetRequestBytes(exchange, new ImmediatePooled(ByteBuffer.wrap(request.data, 0, request.dataLength))); + underlyingSession.removeAttribute(SESSION_KEY); + //clear the existing header map of everything except the connection header + //TODO: are there other headers we should preserve? + Iterator headerIterator = exchange.getRequestHeaders().iterator(); + while (headerIterator.hasNext()) { + HeaderValues header = headerIterator.next(); + if(!header.getHeaderName().equals(Headers.CONNECTION)) { + headerIterator.remove(); + } + } + for(HeaderValues header : request.headerMap) { + exchange.getRequestHeaders().putAll(header.getHeaderName(), header); + } + } + } + } + } + +} diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java index d28ff29406..806fb9f0f9 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java @@ -19,12 +19,15 @@ package org.keycloak.adapters.undertow; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.util.SavedRequest; import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.HttpFacade; import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.OAuthRequestAuthenticator; import org.keycloak.adapters.RefreshableKeycloakSecurityContext; +import org.keycloak.enums.TokenStore; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -43,6 +46,11 @@ public class ServletRequestAuthenticator extends AbstractUndertowRequestAuthenti super(facade, deployment, sslRedirectPort, securityContext, exchange, tokenStore); } + @Override + protected OAuthRequestAuthenticator createOAuthAuthenticator() { + return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore); + } + @Override protected void propagateKeycloakContext(KeycloakUndertowAccount account) { super.propagateKeycloakContext(account); diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java index 2168d9d1b6..74ebf1cd20 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java @@ -64,6 +64,7 @@ public class ServletSessionTokenStore implements AdapterTokenStore { log.debug("Cached account found"); securityContext.authenticationComplete(account, "KEYCLOAK", false); ((AbstractUndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account); + restoreRequest(); return true; } else { log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session"); @@ -105,6 +106,20 @@ public class ServletSessionTokenStore implements AdapterTokenStore { // no-op } + @Override + public void saveRequest() { + SavedRequest.trySaveRequest(exchange); + + } + + @Override + public boolean restoreRequest() { + HttpSession session = getSession(false); + if (session == null) return false; + SavedRequest.tryRestoreRequest(exchange, session); + return false; + } + protected HttpSession getSession(boolean create) { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest(); diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java index ccd695fac5..7dddb74762 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java @@ -80,4 +80,14 @@ public class UndertowCookieTokenStore implements AdapterTokenStore { public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) { CookieTokenStore.setTokenCookie(deployment, facade, securityContext); } + + @Override + public void saveRequest() { + + } + + @Override + public boolean restoreRequest() { + return false; + } } diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java index e5f013cfa7..0dae4c9a43 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java @@ -70,6 +70,16 @@ public class UndertowSessionTokenStore implements AdapterTokenStore { } } + @Override + public void saveRequest() { + + } + + @Override + public boolean restoreRequest() { + return false; + } + @Override public void saveAccountInfo(KeycloakAccount account) { Session session = Sessions.getOrCreateSession(exchange); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java index 6932a81f66..cef068a618 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java @@ -74,7 +74,6 @@ import java.util.concurrent.atomic.AtomicInteger; */ public class AdapterTest { - public static final String LOGIN_URL = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); public static PublicKey realmPublicKey; @ClassRule public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() { @@ -99,6 +98,8 @@ public class AdapterTest { System.setProperty("my.host.name", "localhost"); url = getClass().getResource("/adapter-test/session-keycloak.json"); deployApplication("session-portal", "/session-portal", SessionServlet.class, url.getPath(), "user"); + url = getClass().getResource("/adapter-test/input-keycloak.json"); + deployApplication("input-portal", "/input-portal", InputServlet.class, url.getPath(), "user", true, null, "/secured/*"); } }; @@ -110,6 +111,11 @@ public class AdapterTest { testStrategy.testLoginSSOAndLogout(); } + @Test + public void testSavedPostRequest() throws Exception { + testStrategy.testSavedPostRequest(); + } + @Test public void testServletRequestLogout() throws Exception { testStrategy.testServletRequestLogout(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index e32ff1fdac..d50fd71ac0 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -87,6 +87,9 @@ public class AdapterTestStrategy extends ExternalResource { @WebResource protected LoginPage loginPage; + @WebResource + protected InputPage inputPage; + protected String LOGIN_URL = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri(AUTH_SERVER_URL)).build("demo").toString(); public AdapterTestStrategy(String AUTH_SERVER_URL, String APP_SERVER_BASE_URL, AbstractKeycloakRule keycloakRule) { @@ -132,6 +135,35 @@ public class AdapterTestStrategy extends ExternalResource { } } + @Test + public void testSavedPostRequest() throws Exception { + // test login to customer-portal which does a bearer request to customer-db + driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal"); + System.out.println("Current url: " + driver.getCurrentUrl()); + Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/input-portal" + slash); + inputPage.execute("hello"); + + Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + loginPage.login("bburke@redhat.com", "password"); + System.out.println("Current url: " + driver.getCurrentUrl()); + Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/input-portal/secured/post"); + String pageSource = driver.getPageSource(); + System.out.println(pageSource); + Assert.assertTrue(pageSource.contains("parameter=hello")); + + // test logout + + String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri(AUTH_SERVER_URL)) + .queryParam(OAuth2Constants.REDIRECT_URI, APP_SERVER_BASE_URL + "/customer-portal").build("demo").toString(); + driver.navigate().to(logoutUri); + Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + driver.navigate().to(APP_SERVER_BASE_URL + "/product-portal"); + Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + driver.navigate().to(APP_SERVER_BASE_URL + "/customer-portal"); + Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + } + + @Test public void testLoginSSOAndLogout() throws Exception { // test login to customer-portal which does a bearer request to customer-db diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputPage.java new file mode 100755 index 0000000000..bd09991c90 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputPage.java @@ -0,0 +1,35 @@ +package org.keycloak.testsuite.adapter; + +import org.keycloak.testsuite.pages.AbstractPage; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class InputPage extends AbstractPage { + @FindBy(id = "parameter") + private WebElement parameter; + + @FindBy(name = "submit") + private WebElement submit; + + public void execute(String param) { + parameter.clear(); + parameter.sendKeys(param); + + submit.click(); + } + + + public boolean isCurrent() { + return driver.getTitle().equals("Input Page"); + } + + @Override + public void open() { + } + + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java new file mode 100755 index 0000000000..db4c1788f1 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java @@ -0,0 +1,42 @@ +package org.keycloak.testsuite.adapter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * @author Bill Burke + */ +public class InputServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String appBase = System.getProperty("app.server.base.url", "http://localhost:8081"); + String actionUrl = appBase + "/input-portal/secured/post"; + + + resp.setContentType("text/html"); + PrintWriter pw = resp.getWriter(); + pw.printf("%s", "Input Page"); + pw.printf("
", actionUrl); + pw.println(""); + pw.println("
"); + pw.print(""); + pw.flush(); + + + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setContentType("text/plain"); + PrintWriter pw = resp.getWriter(); + pw.printf("parameter="+req.getParameter("parameter")); + pw.flush(); + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/SessionServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/SessionServlet.java old mode 100644 new mode 100755 index c7c4d85239..f9c5d0bf1b --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/SessionServlet.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/SessionServlet.java @@ -1,38 +1,38 @@ -package org.keycloak.testsuite.adapter; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -/** - * @author Marek Posolda - */ -public class SessionServlet extends HttpServlet { - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String counter = increaseAndGetCounter(req); - - resp.setContentType("text/html"); - PrintWriter pw = resp.getWriter(); - pw.printf("%s", "Session Test"); - pw.printf("Counter=%s", counter); - pw.print(""); - pw.flush(); - - - } - - private String increaseAndGetCounter(HttpServletRequest req) { - HttpSession session = req.getSession(); - Integer counter = (Integer)session.getAttribute("counter"); - counter = (counter == null) ? 1 : counter + 1; - session.setAttribute("counter", counter); - return String.valueOf(counter); - } -} +package org.keycloak.testsuite.adapter; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +/** + * @author Marek Posolda + */ +public class SessionServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String counter = increaseAndGetCounter(req); + + resp.setContentType("text/html"); + PrintWriter pw = resp.getWriter(); + pw.printf("%s", "Session Test"); + pw.printf("Counter=%s", counter); + pw.print(""); + pw.flush(); + + + } + + private String increaseAndGetCounter(HttpServletRequest req) { + HttpSession session = req.getSession(); + Integer counter = (Integer)session.getAttribute("counter"); + counter = (counter == null) ? 1 : counter + 1; + session.setAttribute("counter", counter); + return String.valueOf(counter); + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java old mode 100644 new mode 100755 index c17b86a5fb..4de2ca28e8 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java @@ -39,8 +39,8 @@ public abstract class AbstractPage { isCurrent()); } - abstract boolean isCurrent(); + abstract public boolean isCurrent(); - abstract void open() throws Exception; + abstract public void open() throws Exception; } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java old mode 100644 new mode 100755 index 7fdbe068f1..baa1ae9a73 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java @@ -49,7 +49,7 @@ public class OAuthGrantPage extends AbstractPage { } @Override - void open() { + public void open() { } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java index b89f2c37ac..d618567899 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java @@ -150,6 +150,10 @@ public abstract class AbstractKeycloakRule extends ExternalResource { public void deployApplication(String name, String contextPath, Class servletClass, String adapterConfigPath, String role, boolean isConstrained, Class keycloakConfigResolver) { String constraintUrl = "/*"; + deployApplication(name, contextPath, servletClass, adapterConfigPath, role, isConstrained, keycloakConfigResolver, constraintUrl); + } + + public void deployApplication(String name, String contextPath, Class servletClass, String adapterConfigPath, String role, boolean isConstrained, Class keycloakConfigResolver, String constraintUrl) { DeploymentInfo di = createDeploymentInfo(name, contextPath, servletClass); if (null == keycloakConfigResolver) { di.addInitParameter("keycloak.config.file", adapterConfigPath); diff --git a/testsuite/integration/src/test/resources/adapter-test/demorealm.json b/testsuite/integration/src/test/resources/adapter-test/demorealm.json index 9a5da2428a..ec53bd7d86 100755 --- a/testsuite/integration/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/integration/src/test/resources/adapter-test/demorealm.json @@ -125,6 +125,16 @@ "http://localhost:8081/session-portal/*" ], "secret": "password" + }, + { + "name": "input-portal", + "enabled": true, + "adminUrl": "http://localhost:8081/input-portal", + "baseUrl": "http://localhost:8081/input-portal", + "redirectUris": [ + "http://localhost:8081/input-portal/*" + ], + "secret": "password" } ], "oauthClients": [ diff --git a/testsuite/integration/src/test/resources/adapter-test/input-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/input-keycloak.json new file mode 100755 index 0000000000..0b4b165302 --- /dev/null +++ b/testsuite/integration/src/test/resources/adapter-test/input-keycloak.json @@ -0,0 +1,10 @@ +{ + "realm" : "demo", + "resource" : "input-portal", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url" : "http://${my.host.name}:8081/auth", + "ssl-required" : "external", + "credentials" : { + "secret": "password" + } +} \ No newline at end of file diff --git a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty9Test.java b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty9Test.java index 40da8ed1b0..acf51ce7ce 100755 --- a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty9Test.java +++ b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty9Test.java @@ -95,6 +95,7 @@ public class Jetty9Test { list.add(new WebAppContext(new File(base, "customer-db").toString(), "/customer-db")); list.add(new WebAppContext(new File(base, "product-portal").toString(), "/product-portal")); list.add(new WebAppContext(new File(base, "session-portal").toString(), "/session-portal")); + list.add(new WebAppContext(new File(base, "input-portal").toString(), "/input-portal")); list.add(new WebAppContext(new File(base, "secure-portal").toString(), "/secure-portal")); @@ -117,6 +118,11 @@ public class Jetty9Test { @Rule public AdapterTestStrategy testStrategy = new AdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8082", keycloakRule, true); + @Test + public void testSavedPostRequest() throws Exception { + testStrategy.testSavedPostRequest(); + } + @Test public void testLoginSSOAndLogout() throws Exception { testStrategy.testLoginSSOAndLogout(); diff --git a/testsuite/jetty/jetty81/src/test/resources/adapter-test/demorealm.json b/testsuite/jetty/jetty81/src/test/resources/adapter-test/demorealm.json index 9359cc9460..ce40aec420 100755 --- a/testsuite/jetty/jetty81/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/jetty/jetty81/src/test/resources/adapter-test/demorealm.json @@ -125,6 +125,16 @@ "http://localhost:8082/session-portal/*" ], "secret": "password" + }, + { + "name": "input-portal", + "enabled": true, + "adminUrl": "http://localhost:8082/input-portal", + "baseUrl": "http://localhost:8082/input-portal", + "redirectUris": [ + "http://localhost:8082/input-portal/*" + ], + "secret": "password" } ], "oauthClients": [ diff --git a/testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml b/testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml new file mode 100755 index 0000000000..1ec566d075 --- /dev/null +++ b/testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json b/testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json new file mode 100755 index 0000000000..0b4b165302 --- /dev/null +++ b/testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json @@ -0,0 +1,10 @@ +{ + "realm" : "demo", + "resource" : "input-portal", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url" : "http://${my.host.name}:8081/auth", + "ssl-required" : "external", + "credentials" : { + "secret": "password" + } +} \ No newline at end of file diff --git a/testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml b/testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml new file mode 100755 index 0000000000..e4adb37990 --- /dev/null +++ b/testsuite/jetty/jetty81/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml @@ -0,0 +1,40 @@ + + + + adapter-test + + + Servlet + org.keycloak.testsuite.adapter.InputServlet + + + + Servlet + /* + + + + + Users + /secured/* + + + user + + + + + BASIC + demo + + + + admin + + + user + + diff --git a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java index 528a2e0850..19a8efd2e8 100755 --- a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java +++ b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java @@ -95,6 +95,7 @@ public class Jetty9Test { list.add(new WebAppContext(new File(base, "customer-db").toString(), "/customer-db")); list.add(new WebAppContext(new File(base, "product-portal").toString(), "/product-portal")); list.add(new WebAppContext(new File(base, "session-portal").toString(), "/session-portal")); + list.add(new WebAppContext(new File(base, "input-portal").toString(), "/input-portal")); list.add(new WebAppContext(new File(base, "secure-portal").toString(), "/secure-portal")); @@ -117,6 +118,11 @@ public class Jetty9Test { @Rule public AdapterTestStrategy testStrategy = new AdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8082", keycloakRule, true); + @Test + public void testSavedPostRequest() throws Exception { + testStrategy.testSavedPostRequest(); + } + @Test public void testLoginSSOAndLogout() throws Exception { testStrategy.testLoginSSOAndLogout(); diff --git a/testsuite/jetty/jetty91/src/test/resources/adapter-test/demorealm.json b/testsuite/jetty/jetty91/src/test/resources/adapter-test/demorealm.json index 9359cc9460..ce40aec420 100755 --- a/testsuite/jetty/jetty91/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/jetty/jetty91/src/test/resources/adapter-test/demorealm.json @@ -125,6 +125,16 @@ "http://localhost:8082/session-portal/*" ], "secret": "password" + }, + { + "name": "input-portal", + "enabled": true, + "adminUrl": "http://localhost:8082/input-portal", + "baseUrl": "http://localhost:8082/input-portal", + "redirectUris": [ + "http://localhost:8082/input-portal/*" + ], + "secret": "password" } ], "oauthClients": [ diff --git a/testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml b/testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml new file mode 100755 index 0000000000..1ec566d075 --- /dev/null +++ b/testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json b/testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json new file mode 100755 index 0000000000..0b4b165302 --- /dev/null +++ b/testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json @@ -0,0 +1,10 @@ +{ + "realm" : "demo", + "resource" : "input-portal", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url" : "http://${my.host.name}:8081/auth", + "ssl-required" : "external", + "credentials" : { + "secret": "password" + } +} \ No newline at end of file diff --git a/testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml b/testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml new file mode 100755 index 0000000000..e4adb37990 --- /dev/null +++ b/testsuite/jetty/jetty91/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml @@ -0,0 +1,40 @@ + + + + adapter-test + + + Servlet + org.keycloak.testsuite.adapter.InputServlet + + + + Servlet + /* + + + + + Users + /secured/* + + + user + + + + + BASIC + demo + + + + admin + + + user + + diff --git a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java index 528a2e0850..4d3c439705 100755 --- a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java +++ b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java @@ -95,6 +95,7 @@ public class Jetty9Test { list.add(new WebAppContext(new File(base, "customer-db").toString(), "/customer-db")); list.add(new WebAppContext(new File(base, "product-portal").toString(), "/product-portal")); list.add(new WebAppContext(new File(base, "session-portal").toString(), "/session-portal")); + list.add(new WebAppContext(new File(base, "input-portal").toString(), "/input-portal")); list.add(new WebAppContext(new File(base, "secure-portal").toString(), "/secure-portal")); @@ -122,6 +123,11 @@ public class Jetty9Test { testStrategy.testLoginSSOAndLogout(); } + @Test + public void testSavedPostRequest() throws Exception { + testStrategy.testSavedPostRequest(); + } + @Test public void testServletRequestLogout() throws Exception { testStrategy.testServletRequestLogout(); diff --git a/testsuite/jetty/jetty92/src/test/resources/adapter-test/demorealm.json b/testsuite/jetty/jetty92/src/test/resources/adapter-test/demorealm.json index 9359cc9460..ce40aec420 100755 --- a/testsuite/jetty/jetty92/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/jetty/jetty92/src/test/resources/adapter-test/demorealm.json @@ -125,6 +125,16 @@ "http://localhost:8082/session-portal/*" ], "secret": "password" + }, + { + "name": "input-portal", + "enabled": true, + "adminUrl": "http://localhost:8082/input-portal", + "baseUrl": "http://localhost:8082/input-portal", + "redirectUris": [ + "http://localhost:8082/input-portal/*" + ], + "secret": "password" } ], "oauthClients": [ diff --git a/testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml b/testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml new file mode 100755 index 0000000000..1ec566d075 --- /dev/null +++ b/testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/jetty-web.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json b/testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json new file mode 100755 index 0000000000..0b4b165302 --- /dev/null +++ b/testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json @@ -0,0 +1,10 @@ +{ + "realm" : "demo", + "resource" : "input-portal", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url" : "http://${my.host.name}:8081/auth", + "ssl-required" : "external", + "credentials" : { + "secret": "password" + } +} \ No newline at end of file diff --git a/testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml b/testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml new file mode 100755 index 0000000000..e4adb37990 --- /dev/null +++ b/testsuite/jetty/jetty92/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml @@ -0,0 +1,40 @@ + + + + adapter-test + + + Servlet + org.keycloak.testsuite.adapter.InputServlet + + + + Servlet + /* + + + + + Users + /secured/* + + + user + + + + + BASIC + demo + + + + admin + + + user + + diff --git a/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatTest.java b/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatTest.java index 4f2a29e081..d24a80de61 100755 --- a/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatTest.java +++ b/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatTest.java @@ -83,6 +83,7 @@ public class TomcatTest { tomcat.deploy("/product-portal", "product-portal"); tomcat.deploy("/secure-portal", "secure-portal"); tomcat.deploy("/session-portal", "session-portal"); + tomcat.deploy("/input-portal", "input-portal"); tomcat.start(); @@ -102,6 +103,11 @@ public class TomcatTest { testStrategy.testLoginSSOAndLogout(); } + @Test + public void testSavedPostRequest() throws Exception { + testStrategy.testSavedPostRequest(); + } + @Test public void testServletRequestLogout() throws Exception { // can't test this. Servlet 2.5 doesn't have logout() diff --git a/testsuite/tomcat6/src/test/resources/adapter-test/demorealm.json b/testsuite/tomcat6/src/test/resources/adapter-test/demorealm.json index 9359cc9460..ce40aec420 100755 --- a/testsuite/tomcat6/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/tomcat6/src/test/resources/adapter-test/demorealm.json @@ -125,6 +125,16 @@ "http://localhost:8082/session-portal/*" ], "secret": "password" + }, + { + "name": "input-portal", + "enabled": true, + "adminUrl": "http://localhost:8082/input-portal", + "baseUrl": "http://localhost:8082/input-portal", + "redirectUris": [ + "http://localhost:8082/input-portal/*" + ], + "secret": "password" } ], "oauthClients": [ diff --git a/testsuite/tomcat6/src/test/resources/adapter-test/input-portal/META-INF/context.xml b/testsuite/tomcat6/src/test/resources/adapter-test/input-portal/META-INF/context.xml new file mode 100755 index 0000000000..6f24639879 --- /dev/null +++ b/testsuite/tomcat6/src/test/resources/adapter-test/input-portal/META-INF/context.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/testsuite/tomcat6/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json b/testsuite/tomcat6/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json new file mode 100755 index 0000000000..0b4b165302 --- /dev/null +++ b/testsuite/tomcat6/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json @@ -0,0 +1,10 @@ +{ + "realm" : "demo", + "resource" : "input-portal", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url" : "http://${my.host.name}:8081/auth", + "ssl-required" : "external", + "credentials" : { + "secret": "password" + } +} \ No newline at end of file diff --git a/testsuite/tomcat6/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml b/testsuite/tomcat6/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml new file mode 100755 index 0000000000..e4adb37990 --- /dev/null +++ b/testsuite/tomcat6/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml @@ -0,0 +1,40 @@ + + + + adapter-test + + + Servlet + org.keycloak.testsuite.adapter.InputServlet + + + + Servlet + /* + + + + + Users + /secured/* + + + user + + + + + BASIC + demo + + + + admin + + + user + + diff --git a/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java b/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java index 70da95d6bc..b79b085245 100755 --- a/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java +++ b/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java @@ -87,6 +87,7 @@ public class Tomcat7Test { tomcat.addWebapp("/product-portal", new File(base, "product-portal").toString()); tomcat.addWebapp("/secure-portal", new File(base, "secure-portal").toString()); tomcat.addWebapp("/session-portal", new File(base, "session-portal").toString()); + tomcat.addWebapp("/input-portal", new File(base, "input-portal").toString()); tomcat.start(); //tomcat.getServer().await(); @@ -106,6 +107,12 @@ public class Tomcat7Test { testStrategy.testLoginSSOAndLogout(); } + @Test + public void testSavedPostRequest() throws Exception { + testStrategy.testSavedPostRequest(); + } + + @Test public void testServletRequestLogout() throws Exception { testStrategy.testServletRequestLogout(); diff --git a/testsuite/tomcat7/src/test/resources/adapter-test/demorealm.json b/testsuite/tomcat7/src/test/resources/adapter-test/demorealm.json index 9359cc9460..ce40aec420 100755 --- a/testsuite/tomcat7/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/tomcat7/src/test/resources/adapter-test/demorealm.json @@ -125,6 +125,16 @@ "http://localhost:8082/session-portal/*" ], "secret": "password" + }, + { + "name": "input-portal", + "enabled": true, + "adminUrl": "http://localhost:8082/input-portal", + "baseUrl": "http://localhost:8082/input-portal", + "redirectUris": [ + "http://localhost:8082/input-portal/*" + ], + "secret": "password" } ], "oauthClients": [ diff --git a/testsuite/tomcat7/src/test/resources/adapter-test/input-portal/META-INF/context.xml b/testsuite/tomcat7/src/test/resources/adapter-test/input-portal/META-INF/context.xml new file mode 100755 index 0000000000..6f24639879 --- /dev/null +++ b/testsuite/tomcat7/src/test/resources/adapter-test/input-portal/META-INF/context.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/testsuite/tomcat7/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json b/testsuite/tomcat7/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json new file mode 100755 index 0000000000..0b4b165302 --- /dev/null +++ b/testsuite/tomcat7/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json @@ -0,0 +1,10 @@ +{ + "realm" : "demo", + "resource" : "input-portal", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url" : "http://${my.host.name}:8081/auth", + "ssl-required" : "external", + "credentials" : { + "secret": "password" + } +} \ No newline at end of file diff --git a/testsuite/tomcat7/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml b/testsuite/tomcat7/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml new file mode 100755 index 0000000000..e4adb37990 --- /dev/null +++ b/testsuite/tomcat7/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml @@ -0,0 +1,40 @@ + + + + adapter-test + + + Servlet + org.keycloak.testsuite.adapter.InputServlet + + + + Servlet + /* + + + + + Users + /secured/* + + + user + + + + + BASIC + demo + + + + admin + + + user + + diff --git a/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatTest.java b/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatTest.java index 9ff92d802a..e4834b6bfd 100755 --- a/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatTest.java +++ b/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatTest.java @@ -87,6 +87,7 @@ public class TomcatTest { tomcat.addWebapp("/product-portal", new File(base, "product-portal").toString()); tomcat.addWebapp("/secure-portal", new File(base, "secure-portal").toString()); tomcat.addWebapp("/session-portal", new File(base, "session-portal").toString()); + tomcat.addWebapp("/input-portal", new File(base, "input-portal").toString()); tomcat.start(); //tomcat.getServer().await(); @@ -106,6 +107,13 @@ public class TomcatTest { testStrategy.testLoginSSOAndLogout(); } + @Test + public void testSavedPostRequest() throws Exception { + testStrategy.testSavedPostRequest(); + } + + + @Test public void testServletRequestLogout() throws Exception { testStrategy.testServletRequestLogout(); diff --git a/testsuite/tomcat8/src/test/resources/adapter-test/demorealm.json b/testsuite/tomcat8/src/test/resources/adapter-test/demorealm.json index 9359cc9460..ce40aec420 100755 --- a/testsuite/tomcat8/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/tomcat8/src/test/resources/adapter-test/demorealm.json @@ -125,6 +125,16 @@ "http://localhost:8082/session-portal/*" ], "secret": "password" + }, + { + "name": "input-portal", + "enabled": true, + "adminUrl": "http://localhost:8082/input-portal", + "baseUrl": "http://localhost:8082/input-portal", + "redirectUris": [ + "http://localhost:8082/input-portal/*" + ], + "secret": "password" } ], "oauthClients": [ diff --git a/testsuite/tomcat8/src/test/resources/adapter-test/input-portal/META-INF/context.xml b/testsuite/tomcat8/src/test/resources/adapter-test/input-portal/META-INF/context.xml new file mode 100755 index 0000000000..6f24639879 --- /dev/null +++ b/testsuite/tomcat8/src/test/resources/adapter-test/input-portal/META-INF/context.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/testsuite/tomcat8/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json b/testsuite/tomcat8/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json new file mode 100755 index 0000000000..0b4b165302 --- /dev/null +++ b/testsuite/tomcat8/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json @@ -0,0 +1,10 @@ +{ + "realm" : "demo", + "resource" : "input-portal", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url" : "http://${my.host.name}:8081/auth", + "ssl-required" : "external", + "credentials" : { + "secret": "password" + } +} \ No newline at end of file diff --git a/testsuite/tomcat8/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml b/testsuite/tomcat8/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml new file mode 100755 index 0000000000..e4adb37990 --- /dev/null +++ b/testsuite/tomcat8/src/test/resources/adapter-test/input-portal/WEB-INF/web.xml @@ -0,0 +1,40 @@ + + + + adapter-test + + + Servlet + org.keycloak.testsuite.adapter.InputServlet + + + + Servlet + /* + + + + + Users + /secured/* + + + user + + + + + BASIC + demo + + + + admin + + + user + +