diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java index 10d6630339..e0551fcfe9 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.Set; import org.jboss.logging.Logger; +import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakSecurityContext; import org.keycloak.representations.AccessToken; import org.keycloak.util.UriUtils; @@ -82,4 +83,8 @@ public class AdapterUtils { if (name == null) name = token.getSubject(); return name; } + + public static KeycloakPrincipal createPrincipal(KeycloakDeployment deployment, RefreshableKeycloakSecurityContext securityContext) { + return new KeycloakPrincipal(getPrincipalName(deployment, securityContext.getToken()), securityContext); + } } diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CorsPreflightChecker.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CorsPreflightChecker.java deleted file mode 100755 index bfa0e4be4c..0000000000 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CorsPreflightChecker.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.keycloak.adapters.as7; - -import org.apache.catalina.connector.Request; -import org.apache.catalina.connector.Response; -import org.jboss.logging.Logger; -import org.keycloak.adapters.KeycloakDeployment; - -import javax.servlet.http.HttpServletResponse; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class CorsPreflightChecker { - private static final Logger log = Logger.getLogger(CorsPreflightChecker.class); - protected KeycloakDeployment deployment; - - public CorsPreflightChecker(KeycloakDeployment deployment) { - this.deployment = deployment; - } - - public boolean checkCorsPreflight(Request request, Response response) { - log.debugv("checkCorsPreflight {0}", request.getRequestURI()); - if (!request.getMethod().equalsIgnoreCase("OPTIONS")) { - log.debug("checkCorsPreflight: not options "); - return false; - - } - if (request.getHeader("Origin") == null) { - log.debug("checkCorsPreflight: no origin header"); - return false; - } - log.debug("Preflight request returning"); - response.setStatus(HttpServletResponse.SC_OK); - String origin = request.getHeader("Origin"); - response.setHeader("Access-Control-Allow-Origin", origin); - response.setHeader("Access-Control-Allow-Credentials", "true"); - String requestMethods = request.getHeader("Access-Control-Request-Method"); - if (requestMethods != null) { - if (deployment.getCorsAllowedMethods() != null) { - requestMethods = deployment.getCorsAllowedMethods(); - } - response.setHeader("Access-Control-Allow-Methods", requestMethods); - } - String allowHeaders = request.getHeader("Access-Control-Request-Headers"); - if (allowHeaders != null) { - if (deployment.getCorsAllowedHeaders() != null) { - allowHeaders = deployment.getCorsAllowedHeaders(); - } - response.setHeader("Access-Control-Allow-Headers", allowHeaders); - } - if (deployment.getCorsMaxAge() > -1) { - response.setHeader("Access-Control-Max-Age", Integer.toString(deployment.getCorsMaxAge())); - } - return true; - } - -} diff --git a/integration/jetty9/adapter/pom.xml b/integration/jetty9/adapter/pom.xml index 9e20aa10af..ba8ac3b7ae 100755 --- a/integration/jetty9/adapter/pom.xml +++ b/integration/jetty9/adapter/pom.xml @@ -4,7 +4,7 @@ keycloak-parent org.keycloak - 1.1.0-Alpha1-SNAPSHOT + 1.1.0.Beta2-SNAPSHOT ../../../pom.xml 4.0.0 @@ -12,7 +12,7 @@ keycloak-jetty9-adapter Keycloak Jetty 9 Integration - 9.1.0.v20131115 + 9.1.0.v20131115 @@ -65,42 +65,42 @@ org.eclipse.jetty jetty-server - ${jetty.version} + ${jetty9.version} provided org.eclipse.jetty jetty-jaas - ${jetty.version} + ${jetty9.version} provided org.eclipse.jetty jetty-util - ${jetty.version} + ${jetty9.version} provided org.eclipse.jetty jetty-webapp - ${jetty.version} + ${jetty9.version} provided org.eclipse.jetty jetty-security - ${jetty.version} + ${jetty9.version} provided org.eclipse.jetty jetty-servlet - ${jetty.version} + ${jetty9.version} provided diff --git a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java new file mode 100755 index 0000000000..aa685b33a8 --- /dev/null +++ b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java @@ -0,0 +1,105 @@ +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.CookieTokenStore; +import org.keycloak.adapters.HttpFacade; +import org.keycloak.adapters.KeycloakAccount; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.RefreshableKeycloakSecurityContext; +import org.keycloak.adapters.RequestAuthenticator; + +import java.util.Set; + +/** + * Handle storage of token info in cookie. Per-request object. + * + * @author Marek Posolda + */ +public class JettyCookieTokenStore implements AdapterTokenStore { + + private static final Logger log = Logger.getLogger(JettyCookieTokenStore.class); + + private Request request; + private HttpFacade facade; + private KeycloakDeployment deployment; + + private KeycloakPrincipal authenticatedPrincipal; + + public JettyCookieTokenStore(Request request, HttpFacade facade, KeycloakDeployment deployment) { + this.request = request; + this.facade = facade; + this.deployment = deployment; + } + + + @Override + public void checkCurrentToken() { + this.authenticatedPrincipal = checkPrincipalFromCookie(); + } + + @Override + public boolean isCached(RequestAuthenticator authenticator) { + // Assuming authenticatedPrincipal set by previous call of checkCurrentToken() during this request + if (authenticatedPrincipal != null) { + log.debug("remote logged in already. Establish state from cookie"); + RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext(); + + if (!securityContext.getRealm().equals(deployment.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); + return true; + } else { + return false; + } + } + + @Override + public void saveAccountInfo(KeycloakAccount account) { + RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext(); + CookieTokenStore.setTokenCookie(deployment, facade, securityContext); + } + + @Override + public void logout() { + CookieTokenStore.removeCookie(facade); + + } + + @Override + public void refreshCallback(RefreshableKeycloakSecurityContext secContext) { + CookieTokenStore.setTokenCookie(deployment, facade, secContext); + } + + /** + * Verify if we already have authenticated and active principal in cookie. Perform refresh if it's not active + * + * @return valid principal + */ + protected KeycloakPrincipal checkPrincipalFromCookie() { + KeycloakPrincipal principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this); + if (principal == null) { + log.debug("Account was not in cookie or was invalid"); + return null; + } + + RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext(); + + if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal; + boolean success = session.refreshExpiredToken(false); + if (success && session.isActive()) return principal; + + log.debugf("Cleanup and expire cookie for user %s after failed refresh", principal.getName()); + CookieTokenStore.removeCookie(facade); + return null; + } +} diff --git a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyHttpFacade.java b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyHttpFacade.java new file mode 100755 index 0000000000..75520ab8a6 --- /dev/null +++ b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyHttpFacade.java @@ -0,0 +1,185 @@ +package org.keycloak.adapters.jetty; + +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.HttpFacade; + +import javax.security.cert.X509Certificate; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JettyHttpFacade implements HttpFacade { + protected org.eclipse.jetty.server.Request request; + protected HttpServletResponse response; + protected RequestFacade requestFacade = new RequestFacade(); + protected ResponseFacade responseFacade = new ResponseFacade(); + + protected class RequestFacade implements Request { + @Override + public String getURI() { + StringBuffer buf = request.getRequestURL(); + if (request.getQueryString() != null) { + buf.append('?').append(request.getQueryString()); + } + return buf.toString(); + } + + @Override + public boolean isSecure() { + return request.isSecure(); + } + + @Override + public String getQueryParamValue(String paramName) { + return request.getParameter(paramName); + } + + @Override + public Cookie getCookie(String cookieName) { + if (request.getCookies() == null) return null; + javax.servlet.http.Cookie cookie = null; + for (javax.servlet.http.Cookie c : request.getCookies()) { + if (c.getName().equals(cookieName)) { + cookie = c; + break; + } + } + if (cookie == null) return null; + return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath()); + } + + @Override + public List getHeaders(String name) { + Enumeration headers = request.getHeaders(name); + if (headers == null) return null; + List list = new ArrayList(); + while (headers.hasMoreElements()) { + list.add(headers.nextElement()); + } + return list; + } + + @Override + public InputStream getInputStream() { + try { + return request.getInputStream(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getMethod() { + return request.getMethod(); + } + + @Override + public String getHeader(String name) { + return request.getHeader(name); + } + + @Override + public String getRemoteAddr() { + return request.getRemoteAddr(); + } + } + + protected class ResponseFacade implements Response { + protected boolean ended; + + @Override + public void setStatus(int status) { + response.setStatus(status); + } + + @Override + public void addHeader(String name, String value) { + response.addHeader(name, value); + } + + @Override + public void setHeader(String name, String value) { + response.setHeader(name, value); + } + + @Override + public void resetCookie(String name, String path) { + setCookie(name, "", null, path, 0, false, false); + } + + @Override + public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) { + javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(name, value); + if (domain != null) cookie.setDomain(domain); + if (path != null) cookie.setPath(path); + if (secure) cookie.setSecure(true); + if (httpOnly) cookie.setHttpOnly(httpOnly); + cookie.setMaxAge(maxAge); + response.addCookie(cookie); + } + + @Override + public OutputStream getOutputStream() { + try { + return response.getOutputStream(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void sendError(int code, String message) { + try { + response.sendError(code, message); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void end() { + ended = true; + } + + public boolean isEnded() { + return ended; + } + } + + public JettyHttpFacade(org.eclipse.jetty.server.Request request, HttpServletResponse response) { + this.request = request; + this.response = response; + } + + @Override + public Request getRequest() { + return requestFacade; + } + + @Override + public Response getResponse() { + return responseFacade; + } + + @Override + public KeycloakSecurityContext getSecurityContext() { + return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName()); + } + + @Override + public X509Certificate[] getCertificateChain() { + throw new IllegalStateException("Not supported yet"); + } + + public boolean isEnded() { + return responseFacade.isEnded(); + } +} diff --git a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java new file mode 100755 index 0000000000..64ce3a6503 --- /dev/null +++ b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java @@ -0,0 +1,152 @@ +package org.keycloak.adapters.jetty; + +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.security.authentication.FormAuthenticator; +import org.eclipse.jetty.server.HttpChannel; +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.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.io.IOException; +import java.security.Principal; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JettyRequestAuthenticator extends RequestAuthenticator { + + private static final Logger log = Logger.getLogger(JettyRequestAuthenticator.class); + protected KeycloakJettyAuthenticator valve; + protected Request request; + protected KeycloakPrincipal principal; + + public JettyRequestAuthenticator(KeycloakDeployment deployment, + KeycloakJettyAuthenticator valve, AdapterTokenStore tokenStore, + JettyHttpFacade facade, + Request request) { + super(facade, deployment, tokenStore, -1); + 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(request, request.getSession()); + } + } + }; + } + + protected void saveServletRequest(HttpServletRequest request, HttpSession session) { + // remember the current URI + 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(FormAuthenticator.__J_METHOD, request.getMethod()); + + if (MimeTypes.Type.FORM_ENCODED.is(request.getContentType()) && HttpMethod.POST.is(request.getMethod())) { + Request base_request = (request instanceof Request) ? (Request) request : HttpChannel + .getCurrentHttpChannel().getRequest(); + base_request.extractParameters(); + session.setAttribute(FormAuthenticator.__J_POST, new MultiMap(base_request.getParameters())); + } + } + } + } + + + @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) { + 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) { + Request base_request = HttpChannel.getCurrentHttpChannel().getRequest(); + base_request.setParameters(j_post); + } + session.removeAttribute(FormAuthenticator.__J_URI); + session.removeAttribute(FormAuthenticator.__J_METHOD); + session.removeAttribute(FormAuthenticator.__J_POST); + // } + } + } + } + + @Override + protected String getHttpSessionId(boolean create) { + HttpSession session = request.getSession(create); + return session != null ? session.getId() : null; + } +} diff --git a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java new file mode 100755 index 0000000000..8859e22b69 --- /dev/null +++ b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java @@ -0,0 +1,94 @@ +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; +import java.util.Set; + +/** + * 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()); + } + + @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; + 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 + } +} diff --git a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyUserSessionManagement.java b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyUserSessionManagement.java new file mode 100755 index 0000000000..4caa6e7aac --- /dev/null +++ b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyUserSessionManagement.java @@ -0,0 +1,33 @@ +package org.keycloak.adapters.jetty; + +import org.eclipse.jetty.server.SessionManager; +import org.keycloak.adapters.UserSessionManagement; + +import javax.servlet.http.HttpSession; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JettyUserSessionManagement implements UserSessionManagement { + protected SessionManager sessionManager; + + public JettyUserSessionManagement(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + @Override + public void logoutAll() { + // todo not implemented yet + } + + @Override + public void logoutHttpSessions(List ids) { + for (String id : ids) { + HttpSession httpSession = sessionManager.getHttpSession(id); + if (httpSession != null) httpSession.invalidate(); + } + + } +} diff --git a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java index 7e82b37768..9f29340412 100755 --- a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java +++ b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java @@ -1,560 +1,228 @@ -/* - * JBoss, Home of Professional Open Source - * - * Copyright 2013 Red Hat, Inc. and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.adapters.jetty; - -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.security.DefaultUserIdentity; -import org.eclipse.jetty.security.ServerAuthException; -import org.eclipse.jetty.security.UserAuthentication; -import org.eclipse.jetty.security.authentication.FormAuthenticator; -import org.eclipse.jetty.server.Authentication; -import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; -import org.eclipse.jetty.server.UserIdentity; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.MultiMap; -import org.eclipse.jetty.util.URIUtil; -import org.keycloak.adapters.AdapterConstants; -import org.keycloak.adapters.AdapterDeploymentContext; -import org.keycloak.adapters.KeycloakConfigResolver; -import org.keycloak.adapters.KeycloakDeployment; -import org.keycloak.adapters.KeycloakDeploymentBuilder; -import org.keycloak.adapters.NodesRegistrationManagement; -import org.w3c.dom.Document; - -import javax.security.auth.Subject; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionListener; -import javax.xml.crypto.dsig.CanonicalizationMethod; -import java.io.ByteArrayInputStream; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.security.Principal; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class KeycloakJettyAuthenticator extends FormAuthenticator { - private final static Logger log = Logger.getLogger(""+KeycloakJettyAuthenticator.class); - - protected ServletContext theServletContext = null; - - protected AdapterDeploymentContext deploymentContext; - protected NodesRegistrationManagement nodesRegistrationManagement; - protected int timerInterval = -1; - - protected Timer timer = null; - - public static final String EMPTY_PASSWORD = "EMPTY_STR"; - - protected boolean enableAudit = false; - - public static final String FORM_PRINCIPAL_NOTE = "picketlink.form.principal"; - public static final String FORM_ROLES_NOTE = "picketlink.form.roles"; - public static final String FORM_REQUEST_NOTE = "picketlink.REQUEST"; - - public static final String logoutPage = "/logout.html"; // get from configuration - - protected String serviceURL = null; - protected String identityURL = null; - protected String issuerID = null; - protected String configFile; - - // Whether the authenticator has to to save and restore request - protected boolean saveRestoreRequest = true; - - /** - * A Lock for Handler operations in the chain - */ - protected Lock chainLock = new ReentrantLock(); - protected String canonicalizationMethod = CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS; - - - public KeycloakJettyAuthenticator() { - } - - public KeycloakJettyAuthenticator(String login, String error, boolean dispatch) { - super(login, error, dispatch); - } - - @Override - public void setConfiguration(AuthConfiguration configuration) { - super.setConfiguration(configuration); - initializeKeycloak(); - } - - @SuppressWarnings("UseSpecificCatch") - @Override - public void initializeKeycloak() { - String contextPath = ContextHandler.getCurrentContext().getContextPath(); - ServletContext theServletContext = ContextHandler.getCurrentContext().getContext(contextPath); - // Possible scenarios: - // 1) The deployment has a keycloak.config.resolver specified and it exists: - // Outcome: adapter uses the resolver - // 2) The deployment has a keycloak.config.resolver and isn't valid (doesn't exists, isn't a resolver, ...) : - // Outcome: adapter is left unconfigured - // 3) The deployment doesn't have a keycloak.config.resolver , but has a keycloak.json (or equivalent) - // Outcome: adapter uses it - // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent) - // Outcome: adapter is left unconfigured - - String configResolverClass = theServletContext.getInitParameter("keycloak.config.resolver"); - if (configResolverClass != null) { - try { - KeycloakConfigResolver configResolver = (KeycloakConfigResolver) ContextHandler.getCurrentContext().getClassLoader().loadClass(configResolverClass).newInstance(); - deploymentContext = new AdapterDeploymentContext(configResolver); - log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass); - } catch (Exception ex) { - log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()}); - deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment()); - } - } else { - InputStream configInputStream = getConfigInputStream(theServletContext); - KeycloakDeployment kd; - if (configInputStream == null) { - log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests."); - kd = new KeycloakDeployment(); - } else { - kd = KeycloakDeploymentBuilder.build(configInputStream); - } - deploymentContext = new AdapterDeploymentContext(kd); - log.fine("Keycloak is using a per-deployment configuration."); - } - - theServletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext); - AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deploymentContext, getNext(), getContainer()); - setNext(actions); - - nodesRegistrationManagement = new NodesRegistrationManagement(); - } - - private static InputStream getJSONFromServletContext(ServletContext servletContext) { - String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME); - if (json == null) { - return null; - } - log.finest("**** using " + AdapterConstants.AUTH_DATA_PARAM_NAME); - log.finest(json); - return new ByteArrayInputStream(json.getBytes()); - } - - - private InputStream getConfigInputStream(ServletContext servletContext) { - InputStream is = getJSONFromServletContext(servletContext); - if (is == null) { - String path = servletContext.getInitParameter("keycloak.config.file"); - if (path == null) { - log.finest("**** using /WEB-INF/keycloak.json"); - is = servletContext.getResourceAsStream("/WEB-INF/keycloak.json"); - } else { - try { - is = new FileInputStream(path); - } catch (FileNotFoundException e) { - log.severe("NOT FOUND /WEB-INF/keycloak.json"); - throw new RuntimeException(e); - } - } - } - return is; - } - - - - @Override - public Authentication validateRequest(ServletRequest servletRequest, ServletResponse servletResponse, boolean mandatory) - throws ServerAuthException { - // TODO: Deal with character encoding - // request.setCharacterEncoding(xyz) - - String contextPath = ContextHandler.getCurrentContext().getContextPath(); - theServletContext = ContextHandler.getCurrentContext().getContext(contextPath); - - // Get the session - HttpServletRequest request = (HttpServletRequest) servletRequest; - HttpServletResponse response = (HttpServletResponse) servletResponse; - - HttpSession session = request.getSession(); - - System.out.println("Request ID=" + servletRequest.toString()); - System.out.println("Session ID=" + session.getId()); - - // check if this call is resulting from the redirect after successful authentication. - // if so, make the authentication successful and continue the original request - if (saveRestoreRequest && matchRequest(request)) { - Principal savedPrincipal = (Principal) session.getAttribute(FORM_PRINCIPAL_NOTE); - List savedRoles = (List) session.getAttribute(FORM_ROLES_NOTE); - Authentication registeredAuthentication = register(request, savedPrincipal, savedRoles); - - // try to restore the original request (including post data, etc...) - if (restoreRequest(request, session)) { - // success! user is authenticated; continue processing original request - return registeredAuthentication; - } else { - // no saved request found... - return Authentication.UNAUTHENTICATED; - } - } - ServiceProviderSAMLWorkflow serviceProviderSAMLWorkflow = new ServiceProviderSAMLWorkflow(); - serviceProviderSAMLWorkflow.setRedirectionHandler(new JettyRedirectionHandler()); - - // Eagerly look for Local LogOut - boolean localLogout = serviceProviderSAMLWorkflow.isLocalLogoutRequest(request); - - if (localLogout) { - try { - serviceProviderSAMLWorkflow.sendToLogoutPage(request, response, session, theServletContext, logoutPage); - } catch (ServletException e) { - logger.samlLogoutError(e); - throw new RuntimeException(e); - } catch (IOException e1) { - logger.samlLogoutError(e1); - throw new RuntimeException(e1); - } - return Authentication.UNAUTHENTICATED; - } - - String samlRequest = request.getParameter(GeneralConstants.SAML_REQUEST_KEY); - String samlResponse = request.getParameter(GeneralConstants.SAML_RESPONSE_KEY); - - Principal principal = request.getUserPrincipal(); - - try { - // If we have already authenticated the user and there is no request from IDP or logout from user - if (principal != null - && !(serviceProviderSAMLWorkflow.isLocalLogoutRequest(request) || isNotNull(samlRequest) || isNotNull(samlResponse))) - return Authentication.SEND_SUCCESS; - - // General User Request - if (!isNotNull(samlRequest) && !isNotNull(samlResponse)) { - return generalUserRequest(servletRequest, servletResponse, mandatory); - } - - // Handle a SAML Response from IDP - if (isNotNull(samlResponse)) { - return handleSAMLResponse(servletRequest, servletResponse, mandatory); - } - - // Handle SAML Requests from IDP - if (isNotNull(samlRequest)) { - return handleSAMLRequest(servletRequest, servletResponse, mandatory); - }// end if - - // local authentication - return localAuthentication(servletRequest, servletResponse, mandatory); - } catch (IOException e) { - if (StringUtil.isNotNull(spConfiguration.getErrorPage())) { - try { - request.getRequestDispatcher(spConfiguration.getErrorPage()).forward(request, response); - } catch (ServletException e1) { - logger.samlErrorPageForwardError(spConfiguration.getErrorPage(), e1); - } catch (IOException e1) { - logger.samlErrorPageForwardError(spConfiguration.getErrorPage(), e1); - } - return Authentication.UNAUTHENTICATED; - } else { - throw new RuntimeException(e); - } - } - } - - /** - * Handle the user invocation for the first time - * - * @param servletRequest - * @param servletResponse - * @param mandatory - * @return - * @throws java.io.IOException - */ - private Authentication generalUserRequest(ServletRequest servletRequest, ServletResponse servletResponse, boolean mandatory) - throws IOException, ServerAuthException { - //only perform SAML Authentication if it is mandatory - if(!mandatory){ - Request request = (Request) servletRequest; - return request.getAuthentication(); - } - ServiceProviderSAMLWorkflow serviceProviderSAMLWorkflow = new ServiceProviderSAMLWorkflow(); - serviceProviderSAMLWorkflow.setRedirectionHandler(new JettyRedirectionHandler()); - - HttpServletRequest request = (HttpServletRequest) servletRequest; - HttpServletResponse response = (HttpServletResponse) servletResponse; - - HttpSession session = request.getSession(false); - boolean willSendRequest = false; - - HTTPContext httpContext = new HTTPContext(request, response, theServletContext); - Set handlers = chain.handlers(); - - boolean postBinding = spConfiguration.getBindingType().equals("POST"); - - // Neither saml request nor response from IDP - // So this is a user request - SAML2HandlerResponse saml2HandlerResponse = null; - try { - ServiceProviderBaseProcessor baseProcessor = new ServiceProviderBaseProcessor(postBinding, serviceURL, - this.picketLinkConfiguration); - if (issuerID != null) - baseProcessor.setIssuer(issuerID); - - baseProcessor.setIdentityURL(identityURL); - baseProcessor.setAuditHelper(auditHelper); - - saml2HandlerResponse = baseProcessor.process(httpContext, handlers, chainLock); - } catch (ProcessingException pe) { - logger.samlSPHandleRequestError(pe); - throw new RuntimeException(pe); - } catch (ParsingException pe) { - logger.samlSPHandleRequestError(pe); - throw new RuntimeException(pe); - } catch (ConfigurationException pe) { - logger.samlSPHandleRequestError(pe); - throw new RuntimeException(pe); - } - - willSendRequest = saml2HandlerResponse.getSendRequest(); - - Document samlResponseDocument = saml2HandlerResponse.getResultingDocument(); - String relayState = saml2HandlerResponse.getRelayState(); - - String destination = saml2HandlerResponse.getDestination(); - String destinationQueryStringWithSignature = saml2HandlerResponse.getDestinationQueryStringWithSignature(); - - if (destination != null && samlResponseDocument != null) { - try { - if (saveRestoreRequest) { - this.saveRequest(request, session); - } - if (enableAudit) { - PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO); - auditEvent.setType(PicketLinkAuditEventType.REQUEST_TO_IDP); - auditEvent.setWhoIsAuditing(theServletContext.getContextPath()); - auditHelper.audit(auditEvent); - } - serviceProviderSAMLWorkflow.sendRequestToIDP(destination, samlResponseDocument, relayState, response, - willSendRequest, destinationQueryStringWithSignature, isHttpPostBinding()); - return Authentication.SEND_CONTINUE; - } catch (Exception e) { - logger.samlSPHandleRequestError(e); - throw logger.samlSPProcessingExceptionError(e); - } - } - - return localAuthentication(servletRequest, servletResponse, mandatory); - } - - protected boolean matchRequest(HttpServletRequest request) { - HttpSession session = request.getSession(false); - synchronized (session) { - String j_uri = (String) session.getAttribute(__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())) { - return true; - } - } - return false; - } - } - - protected Authentication register(HttpServletRequest httpServletRequest, Principal principal, List roles) { - if (roles == null) { - roles = new ArrayList(); - } - HttpSession session = httpServletRequest.getSession(false); - session.setAttribute(FORM_PRINCIPAL_NOTE, principal); - session.setAttribute(FORM_ROLES_NOTE, roles); - Request request = (Request) httpServletRequest; - Authentication authentication = request.getAuthentication(); - if (!(authentication instanceof UserAuthentication)) { - Subject theSubject = new Subject(); - String[] theRoles = new String[roles.size()]; - roles.toArray(theRoles); - - UserIdentity userIdentity = new DefaultUserIdentity(theSubject, principal, theRoles); - authentication = new UserAuthentication(getAuthMethod(), userIdentity); - request.setAuthentication(authentication); - } - return authentication; - } - - protected boolean restoreRequest(HttpServletRequest request, HttpSession session) { - synchronized (session) { - String j_uri = (String) session.getAttribute(__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(__J_POST); - if (j_post != null) { - Request base_request = HttpChannel.getCurrentHttpChannel().getRequest(); - base_request.setParameters(j_post); - } - session.removeAttribute(__J_URI); - session.removeAttribute(__J_METHOD); - session.removeAttribute(__J_POST); - // } - return true; - } - return false; - } - } - - protected void saveRequest(HttpServletRequest request, HttpSession session) { - // remember the current URI - 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(__J_URI) == null) { - StringBuffer buf = request.getRequestURL(); - if (request.getQueryString() != null) - buf.append("?").append(request.getQueryString()); - session.setAttribute(__J_URI, buf.toString()); - session.setAttribute(__J_METHOD, request.getMethod()); - - if (MimeTypes.Type.FORM_ENCODED.is(request.getContentType()) && HttpMethod.POST.is(request.getMethod())) { - Request base_request = (request instanceof Request) ? (Request) request : HttpChannel - .getCurrentHttpChannel().getRequest(); - base_request.extractParameters(); - session.setAttribute(__J_POST, new MultiMap(base_request.getParameters())); - } - } - } - } - - /** - * Fall back on local authentication at the service provider side - * - * @param servletRequest - * @param servletRequest - * @param mandatory - * @return - * @throws java.io.IOException - */ - protected Authentication localAuthentication(ServletRequest servletRequest, ServletResponse servletResponse, - boolean mandatory) throws IOException, ServerAuthException { - HttpServletRequest request = (HttpServletRequest) servletRequest; - HttpServletResponse response = (HttpServletResponse) servletResponse; - - if (request.getUserPrincipal() == null) { - logger.samlSPFallingBackToLocalFormAuthentication();// fallback - try { - return super.validateRequest(servletRequest, servletResponse, mandatory); - } catch (NoSuchMethodError e) { - /* - * // Use Reflection try { Method method = super.getClass().getMethod("authenticate", new Class[] { - * HttpServletRequest.class, HttpServletResponse.class, LoginConfig.class }); return (Boolean) - * method.invoke(this, new Object[] { request.getRequest(), response.getResponse(), loginConfig }); } catch - * (Exception ex) { throw logger.unableLocalAuthentication(ex); } - */ - } - } else { - return Authentication.SEND_SUCCESS; - } - return Authentication.UNAUTHENTICATED; - } - - protected boolean sessionIsValid(HttpSession session) { - try { - long sessionTime = session.getCreationTime(); - } catch (IllegalStateException ise) { - return false; - } - return true; - } - - protected String savedRequestURL(HttpSession session) { - StringBuilder builder = new StringBuilder(); - HttpServletRequest request = (HttpServletRequest) session.getAttribute(FORM_REQUEST_NOTE); - if (request != null) { - builder.append(request.getRequestURI()); - if (request.getQueryString() != null) { - builder.append("?").append(request.getQueryString()); - } - } - return builder.toString(); - } - - - /** - * An instance of {@link org.picketlink.identity.federation.core.saml.workflow.ServiceProviderSAMLWorkflow.RedirectionHandler} - * that performs JETTY specific redirection and post workflows - */ - public class JettyRedirectionHandler extends ServiceProviderSAMLWorkflow.RedirectionHandler { - @Override - public void sendRedirectForRequestor(String destination, HttpServletResponse response) throws IOException { - common(destination, response); - response.setHeader("Cache-Control", "no-cache, no-store"); - sendRedirect(response, destination); - } - - @Override - public void sendRedirectForResponder(String destination, HttpServletResponse response) throws IOException { - common(destination, response); - response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate,private"); - sendRedirect(response, destination); - } - - private void common(String destination, HttpServletResponse response) { - response.setCharacterEncoding("UTF-8"); - response.setHeader("Location", destination); - response.setHeader("Pragma", "no-cache"); - } - - private void sendRedirect(HttpServletResponse response, String destination) throws IOException { - // response.reset(); - response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - response.sendRedirect(destination); - } - } -} +package org.keycloak.adapters.jetty; + +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.DefaultUserIdentity; +import org.eclipse.jetty.security.ServerAuthException; +import org.eclipse.jetty.security.UserAuthentication; +import org.eclipse.jetty.security.authentication.FormAuthenticator; +import org.eclipse.jetty.server.Authentication; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.MultiMap; +import org.jboss.logging.Logger; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.adapters.AdapterConstants; +import org.keycloak.adapters.AdapterDeploymentContext; +import org.keycloak.adapters.AdapterTokenStore; +import org.keycloak.adapters.AdapterUtils; +import org.keycloak.adapters.AuthChallenge; +import org.keycloak.adapters.AuthOutcome; +import org.keycloak.adapters.AuthenticatedActionsHandler; +import org.keycloak.adapters.HttpFacade; +import org.keycloak.adapters.KeycloakConfigResolver; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.KeycloakDeploymentBuilder; +import org.keycloak.adapters.NodesRegistrationManagement; +import org.keycloak.adapters.PreAuthActionsHandler; +import org.keycloak.adapters.RefreshableKeycloakSecurityContext; +import org.keycloak.enums.TokenStore; + +import javax.security.auth.Subject; +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class KeycloakJettyAuthenticator extends FormAuthenticator { + private static final org.jboss.logging.Logger log = Logger.getLogger(KeycloakJettyAuthenticator.class); + protected AdapterDeploymentContext deploymentContext; + protected NodesRegistrationManagement nodesRegistrationManagement; + + public KeycloakJettyAuthenticator() { + super(); + } + + public KeycloakJettyAuthenticator(String login, String error, boolean dispatch) { + super(login, error, dispatch); + } + + @Override + public void setConfiguration(AuthConfiguration configuration) { + super.setConfiguration(configuration); + initializeKeycloak(); + } + + @SuppressWarnings("UseSpecificCatch") + public void initializeKeycloak() { + String contextPath = ContextHandler.getCurrentContext().getContextPath(); + ServletContext theServletContext = ContextHandler.getCurrentContext().getContext(contextPath); + // Possible scenarios: + // 1) The deployment has a keycloak.config.resolver specified and it exists: + // Outcome: adapter uses the resolver + // 2) The deployment has a keycloak.config.resolver and isn't valid (doesn't exists, isn't a resolver, ...) : + // Outcome: adapter is left unconfigured + // 3) The deployment doesn't have a keycloak.config.resolver , but has a keycloak.json (or equivalent) + // Outcome: adapter uses it + // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent) + // Outcome: adapter is left unconfigured + + String configResolverClass = theServletContext.getInitParameter("keycloak.config.resolver"); + if (configResolverClass != null) { + try { + KeycloakConfigResolver configResolver = (KeycloakConfigResolver) ContextHandler.getCurrentContext().getClassLoader().loadClass(configResolverClass).newInstance(); + deploymentContext = new AdapterDeploymentContext(configResolver); + log.infov("Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass); + } catch (Exception ex) { + log.infov("The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()}); + deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment()); + } + } else { + InputStream configInputStream = getConfigInputStream(theServletContext); + KeycloakDeployment kd; + if (configInputStream == null) { + log.debug("No adapter configuration. Keycloak is unconfigured and will deny all requests."); + kd = new KeycloakDeployment(); + } else { + kd = KeycloakDeploymentBuilder.build(configInputStream); + } + deploymentContext = new AdapterDeploymentContext(kd); + log.debug("Keycloak is using a per-deployment configuration."); + } + + theServletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext); + //AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deploymentContext, getNext(), getContainer()); + //setNext(actions); + + nodesRegistrationManagement = new NodesRegistrationManagement(); + } + + private static InputStream getJSONFromServletContext(ServletContext servletContext) { + String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME); + if (json == null) { + return null; + } + return new ByteArrayInputStream(json.getBytes()); + } + + + private InputStream getConfigInputStream(ServletContext servletContext) { + InputStream is = getJSONFromServletContext(servletContext); + if (is == null) { + String path = servletContext.getInitParameter("keycloak.config.file"); + if (path == null) { + is = servletContext.getResourceAsStream("/WEB-INF/keycloak.json"); + } else { + try { + is = new FileInputStream(path); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + } + return is; + } + + @Override + public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException { + if (log.isTraceEnabled()) { + log.trace("*** authenticate"); + } + Request request = HttpChannel.getCurrentHttpChannel().getRequest(); + JettyHttpFacade facade = new JettyHttpFacade(request, (HttpServletResponse)res); + KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); + if (deployment == null || !deployment.isConfigured()) { + log.debug("*** deployment isn't configured return false"); + return Authentication.UNAUTHENTICATED; + } + PreAuthActionsHandler handler = new PreAuthActionsHandler(new JettyUserSessionManagement(request.getSessionManager()), deploymentContext, facade); + if (handler.handleRequest()) { + return Authentication.SEND_SUCCESS; + } + AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment); + + nodesRegistrationManagement.tryRegister(deployment); + + JettyRequestAuthenticator authenticator = new JettyRequestAuthenticator(deployment, this, tokenStore, facade, request); + AuthOutcome outcome = authenticator.authenticate(); + if (outcome == AuthOutcome.AUTHENTICATED) { + if (facade.isEnded()) { + return Authentication.SEND_SUCCESS; + } + + Authentication authentication = register(request, authenticator.principal); + AuthenticatedActionsHandler authenticatedActionsHandler = new AuthenticatedActionsHandler(deployment, facade); + if (authenticatedActionsHandler.handledRequest()) { + return Authentication.SEND_SUCCESS; + } + return authentication; + + } + AuthChallenge challenge = authenticator.getChallenge(); + if (challenge != null) { + challenge.challenge(facade); + } + return Authentication.SEND_CONTINUE; + } + + @Override + public String getAuthMethod() { + return "KEYCLOAK"; + } + + public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE"; + protected 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); + } else { + store = new JettyCookieTokenStore(request, facade, resolvedDeployment); + } + + request.setAttribute(TOKEN_STORE_NOTE, store); + return store; + } + + protected Authentication register(HttpServletRequest httpServletRequest, KeycloakPrincipal principal) { + Set roles = AdapterUtils.getRolesFromSecurityContext(principal.getKeycloakSecurityContext()); + if (roles == null) { + roles = new HashSet(); + } + Request request = (Request) httpServletRequest; + Authentication authentication = request.getAuthentication(); + if (!(authentication instanceof UserAuthentication)) { + Subject theSubject = new Subject(); + String[] theRoles = new String[roles.size()]; + roles.toArray(theRoles); + + UserIdentity userIdentity = new DefaultUserIdentity(theSubject, principal, theRoles); + authentication = new UserAuthentication(getAuthMethod(), userIdentity); + request.setAuthentication(authentication); + } + return authentication; + } + + +} diff --git a/integration/pom.xml b/integration/pom.xml index 0c52175399..034f21c144 100755 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -20,7 +20,7 @@ jboss-adapter-core as7-eap6/adapter tomcat7/adapter - + jetty9/adapter undertow wildfly-adapter wildfly-subsystem diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CorsPreflightChecker.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CorsPreflightChecker.java deleted file mode 100755 index f4fc302391..0000000000 --- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CorsPreflightChecker.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.keycloak.adapters.tomcat7; - -import org.apache.catalina.connector.Request; -import org.apache.catalina.connector.Response; -import org.jboss.logging.Logger; -import org.keycloak.adapters.KeycloakDeployment; - -import javax.servlet.http.HttpServletResponse; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class CorsPreflightChecker { - private static final Logger log = Logger.getLogger(CorsPreflightChecker.class); - protected KeycloakDeployment deployment; - - public CorsPreflightChecker(KeycloakDeployment deployment) { - this.deployment = deployment; - } - - public boolean checkCorsPreflight(Request request, Response response) { - log.debugv("checkCorsPreflight {0}", request.getRequestURI()); - if (!request.getMethod().equalsIgnoreCase("OPTIONS")) { - log.debug("checkCorsPreflight: not options "); - return false; - - } - if (request.getHeader("Origin") == null) { - log.debug("checkCorsPreflight: no origin header"); - return false; - } - log.debug("Preflight request returning"); - response.setStatus(HttpServletResponse.SC_OK); - String origin = request.getHeader("Origin"); - response.setHeader("Access-Control-Allow-Origin", origin); - response.setHeader("Access-Control-Allow-Credentials", "true"); - String requestMethods = request.getHeader("Access-Control-Request-Method"); - if (requestMethods != null) { - if (deployment.getCorsAllowedMethods() != null) { - requestMethods = deployment.getCorsAllowedMethods(); - } - response.setHeader("Access-Control-Allow-Methods", requestMethods); - } - String allowHeaders = request.getHeader("Access-Control-Request-Headers"); - if (allowHeaders != null) { - if (deployment.getCorsAllowedHeaders() != null) { - allowHeaders = deployment.getCorsAllowedHeaders(); - } - response.setHeader("Access-Control-Allow-Headers", allowHeaders); - } - if (deployment.getCorsMaxAge() > -1) { - response.setHeader("Access-Control-Max-Age", Integer.toString(deployment.getCorsMaxAge())); - } - return true; - } - -} diff --git a/pom.xml b/pom.xml index f6164ff828..40305f343c 100755 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,7 @@ 1.4.5 6.0.2.Final 3.2.2 + 9.1.0.v20131115 1.6 diff --git a/testsuite/jetty9/pom.xml b/testsuite/jetty9/pom.xml new file mode 100755 index 0000000000..fd72e85d9c --- /dev/null +++ b/testsuite/jetty9/pom.xml @@ -0,0 +1,522 @@ + + + + keycloak-testsuite-pom + org.keycloak + 1.1.0.Beta2-SNAPSHOT + ../pom.xml + + 4.0.0 + + keycloak-testsuite-jetty9 + Keycloak Jetty 9 Integration TestSuite + + 9.1.0.v20131115 + + + + + + org.keycloak + keycloak-dependencies-server-all + ${project.version} + pom + + + org.keycloak + keycloak-admin-client + ${project.version} + + + log4j + log4j + + + org.slf4j + slf4j-api + 1.6.1 + + + org.slf4j + slf4j-log4j12 + 1.6.1 + + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.0_spec + + + org.jboss.resteasy + jaxrs-api + ${resteasy.version.latest} + + + org.jboss.resteasy + resteasy-jaxrs + ${resteasy.version.latest} + + + log4j + log4j + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + + + org.jboss.resteasy + resteasy-client + ${resteasy.version.latest} + + + org.jboss.resteasy + resteasy-crypto + ${resteasy.version.latest} + + + org.jboss.resteasy + resteasy-multipart-provider + ${resteasy.version.latest} + + + org.jboss.resteasy + resteasy-jackson-provider + ${resteasy.version.latest} + + + org.jboss.resteasy + resteasy-undertow + ${resteasy.version.latest} + + + com.google.zxing + javase + + + org.bouncycastle + bcprov-jdk16 + + + org.apache.httpcomponents + httpclient + ${keycloak.apache.httpcomponents.version} + + + org.keycloak + keycloak-ldap-federation + ${project.version} + + + org.keycloak + keycloak-undertow-adapter + ${project.version} + + + org.keycloak + keycloak-jetty9-adapter + ${project.version} + + + org.jboss.logging + jboss-logging + + + io.undertow + undertow-servlet + + + io.undertow + undertow-core + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-mapper-asl + + + org.codehaus.jackson + jackson-xc + + + junit + junit + + + org.hamcrest + hamcrest-all + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + + + com.h2database + h2 + + + org.hibernate + hibernate-entitymanager + + + com.icegreen + greenmail + + + org.slf4j + slf4j-api + + + + + org.infinispan + infinispan-core + + + org.seleniumhq.selenium + selenium-java + + + xml-apis + xml-apis + + + org.seleniumhq.selenium + selenium-chrome-driver + + + org.wildfly + wildfly-undertow + ${wildfly.version} + test + + + org.keycloak + keycloak-testsuite-integration + ${project.version} + test + + + org.keycloak + keycloak-testsuite-integration + ${project.version} + test-jar + test + + + org.eclipse.jetty + jetty-jaas + ${jetty9.version} + provided + + + + org.eclipse.jetty + jetty-util + ${jetty9.version} + provided + + + + org.eclipse.jetty + jetty-webapp + ${jetty9.version} + provided + + + + org.eclipse.jetty + jetty-security + ${jetty9.version} + provided + + + + org.eclipse.jetty + jetty-servlet + ${jetty9.version} + provided + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + test-jar + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.codehaus.mojo + exec-maven-plugin + + ${project.basedir} + + + + + + + + keycloak-server + + + + org.codehaus.mojo + exec-maven-plugin + + org.keycloak.testutils.KeycloakServer + + + + + + + mail-server + + + + org.codehaus.mojo + exec-maven-plugin + + org.keycloak.testutils.MailServer + + + + + + + totp + + + + org.codehaus.mojo + exec-maven-plugin + + org.keycloak.testutils.TotpGenerator + + + + + + + + jpa + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + jpa + jpa + jpa + jpa + + + + + + + + + mongo + + + localhost + 27018 + keycloak + true + 127.0.0.1 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + test + integration-test + + test + + + + mongo + mongo + mongo + mongo + ${keycloak.connectionsMongo.host} + ${keycloak.connectionsMongo.port} + ${keycloak.connectionsMongo.db} + ${keycloak.connectionsMongo.clearOnStartup} + ${keycloak.connectionsMongo.bindIp} + + + + + default-test + + true + + + + + + + + com.github.joelittlejohn.embedmongo + embedmongo-maven-plugin + + + start-mongodb + pre-integration-test + + start + + + ${keycloak.connectionsMongo.port} + file + ${project.build.directory}/mongodb.log + ${keycloak.connectionsMongo.bindIp} + + + + stop-mongodb + post-integration-test + + stop + + + + + + + + + + + infinispan + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + infinispan + infinispan + infinispan + + + + + + + + + + + + keycloak.connectionsJpa.driver + com.mysql.jdbc.Driver + + + mysql + + + mysql + mysql-connector-java + ${mysql.version} + + + + + + + + + keycloak.connectionsJpa.driver + org.postgresql.Driver + + + postgresql + + + org.postgresql + postgresql + ${postgresql.version} + + + + + + clean-jpa + + + + org.liquibase + liquibase-maven-plugin + + META-INF/jpa-changelog-master.xml + + ${keycloak.connectionsJpa.url} + ${keycloak.connectionsJpa.driver} + ${keycloak.connectionsJpa.user} + ${keycloak.connectionsJpa.password} + + false + + + + clean-jpa + clean + + dropAll + + + + + + + + + diff --git a/testsuite/jetty9/src/test/java/org/keycloak/testsuite/Jetty9Test.java b/testsuite/jetty9/src/test/java/org/keycloak/testsuite/Jetty9Test.java new file mode 100755 index 0000000000..abb019cd67 --- /dev/null +++ b/testsuite/jetty9/src/test/java/org/keycloak/testsuite/Jetty9Test.java @@ -0,0 +1,210 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.testsuite; + +import org.eclipse.jetty.security.ConstraintMapping; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.security.HashLoginService; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Password; +import org.eclipse.jetty.webapp.WebAppContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.OAuth2Constants; +import org.keycloak.adapters.jetty.KeycloakJettyAuthenticator; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.protocol.oidc.OpenIDConnectService; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.rule.AbstractKeycloakRule; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.testutils.KeycloakServer; +import org.openqa.selenium.WebDriver; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.UriBuilder; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.security.Principal; +import java.util.regex.Matcher; + +/** + * @author Stian Thorgersen + */ +public class Jetty9Test { + static String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth")) + .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8080/customer-portal").build("demo").toString(); + + @ClassRule + public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() { + @Override + protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) { + RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/jetty-test/demorealm.json"), RealmRepresentation.class); + RealmModel realm = manager.importRealm(representation); + } + }; + + public static class SendUsernameServlet extends HttpServlet { + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + resp.setContentType("text/plain"); + OutputStream stream = resp.getOutputStream(); + Principal principal = req.getUserPrincipal(); + if (principal == null) { + stream.write("null".getBytes()); + return; + } + String name = principal.getName(); + stream.write(name.getBytes()); + stream.write("\n".getBytes()); + KeycloakSecurityContext context = (KeycloakSecurityContext)req.getAttribute(KeycloakSecurityContext.class.getName()); + stream.write(context.getIdToken().getName().getBytes()); + stream.write("\n".getBytes()); + stream.write(logoutUri.getBytes()); + + } + @Override + protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } + } + + public static Server server = null; + protected static WebAppContext appContext = null; + + + protected static void deploySP() throws Exception { + appContext = new WebAppContext(); + appContext.setResourceBase(Jetty9Test.class.getClassLoader().getResource("jetty-test/webapp").toExternalForm()); + appContext.setContextPath("/customer-portal"); + appContext.setParentLoaderPriority(true); + + appContext.addServlet(new ServletHolder(new SendUsernameServlet()), "/*"); + + + ConstraintSecurityHandler securityHandler = formHandler(); + + KeycloakJettyAuthenticator authenticator = new KeycloakJettyAuthenticator(); + securityHandler.setAuthenticator(authenticator); + + appContext.setSecurityHandler(securityHandler); + } + + private static ConstraintSecurityHandler formHandler() { + Constraint constraint = new Constraint(); + constraint.setName(Constraint.__FORM_AUTH); + ; + constraint.setRoles(new String[] { "user", "admin" }); + constraint.setAuthenticate(true); + + ConstraintMapping constraintMapping = new ConstraintMapping(); + constraintMapping.setConstraint(constraint); + constraintMapping.setPathSpec("/*"); + + ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler(); + securityHandler.setConstraintMappings(new ConstraintMapping[] { constraintMapping }); + + HashLoginService loginService = new HashLoginService(); + securityHandler.setLoginService(loginService); + return securityHandler; + } + + + + @BeforeClass + public static void initJetty() throws Exception { + server = new Server(8080); + + deploySP(); + + HandlerCollection handlers = new HandlerCollection(); + handlers.setHandlers(new Handler[] { appContext }); + server.setHandler(handlers); + + server.start(); + } + + + + @AfterClass + public static void shutdownJetty() throws Exception { + server.stop(); + server.destroy(); + } + + @Rule + public WebRule webRule = new WebRule(this); + @WebResource + protected WebDriver driver; + @WebResource + protected LoginPage loginPage; + + public static final String LOGIN_URL = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); + @Test + public void testLoginSSOAndLogout() throws Exception { + driver.navigate().to("http://localhost:8080/customer-portal"); + System.out.println("Current url: " + driver.getCurrentUrl()); + Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + loginPage.login("bburke@redhat.com", "password"); + System.out.println("Current url: " + driver.getCurrentUrl()); + Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/"); + String pageSource = driver.getPageSource(); + System.out.println(pageSource); + Assert.assertTrue(pageSource.contains("Bill Burke")); + + // test logout + + String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth")) + .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8080/customer-portal").build("demo").toString(); + driver.navigate().to(logoutUri); + Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + driver.navigate().to("http://localhost:8080/customer-portal"); + String currentUrl = driver.getCurrentUrl(); + Assert.assertTrue(currentUrl.startsWith(LOGIN_URL)); + + + } + + @Test + @Ignore + public void runit() throws Exception { + Thread.sleep(10000000); + } +} diff --git a/testsuite/jetty9/src/test/resources/jetty-test/demorealm.json b/testsuite/jetty9/src/test/resources/jetty-test/demorealm.json new file mode 100755 index 0000000000..a4a6ec9903 --- /dev/null +++ b/testsuite/jetty9/src/test/resources/jetty-test/demorealm.json @@ -0,0 +1,58 @@ +{ + "id": "demo", + "realm": "demo", + "enabled": true, + "accessTokenLifespan": 3000, + "accessCodeLifespan": 10, + "accessCodeLifespanUserAction": 6000, + "sslRequired": "external", + "registrationAllowed": false, + "social": false, + "passwordCredentialGrantAllowed": true, + "updateProfileOnInitialSocialLogin": false, + "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", + "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "requiredCredentials": [ "password" ], + "users" : [ + { + "username" : "bburke@redhat.com", + "enabled": true, + "email" : "bburke@redhat.com", + "firstName": "Bill", + "lastName": "Burke", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": [ "user", "admin" ], + "applicationRoles": { + "account": [ "manage-account" ] + } + } + ], + "roles" : { + "realm" : [ + { + "name": "user", + "description": "User privileges" + }, + { + "name": "admin", + "description": "Administrator privileges" + } + ] + }, + "applications": [ + { + "name": "customer-portal", + "enabled": true, + "fullScopeAllowed": true, + "adminUrl": "http://localhost:8080/customer-portal", + "baseUrl": "http://localhost:8080/customer-portal", + "redirectUris": [ + "http://localhost:8080/customer-portal/*" + ], + "secret": "password" + } + ] +} diff --git a/testsuite/jetty9/src/test/resources/jetty-test/webapp/WEB-INF/keycloak.json b/testsuite/jetty9/src/test/resources/jetty-test/webapp/WEB-INF/keycloak.json new file mode 100755 index 0000000000..4e2fe1e556 --- /dev/null +++ b/testsuite/jetty9/src/test/resources/jetty-test/webapp/WEB-INF/keycloak.json @@ -0,0 +1,10 @@ +{ + "realm": "demo", + "resource": "customer-portal", + "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url": "http://localhost:8081/auth", + "ssl-required" : "external", + "credentials": { + "secret": "password" + } +} diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 5e63ce62df..28d0567fc4 100755 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -27,6 +27,7 @@ integration tomcat7 + jetty9 performance tools performance-web