From 5d275de8b2a6b10cbe73741a7e7f75cf27bf0af2 Mon Sep 17 00:00:00 2001 From: Davide Ungari Date: Fri, 18 Apr 2014 08:32:42 +0200 Subject: [PATCH 1/2] #dev integrated adapter tomcat7 --- integration/pom.xml | 2 +- integration/tomcat7/adapter/pom.xml | 88 +++++++++ .../keycloak/adapters/tomcat7/Actions.java | 13 ++ .../tomcat7/AuthenticatedActionsValve.java | 49 +++++ .../adapters/tomcat7/CatalinaHttpFacade.java | 181 ++++++++++++++++++ .../tomcat7/CatalinaRequestAuthenticator.java | 123 ++++++++++++ .../CatalinaSecurityContextHelper.java | 131 +++++++++++++ .../CatalinaUserSessionManagement.java | 137 +++++++++++++ .../tomcat7/CorsPreflightChecker.java | 59 ++++++ .../tomcat7/KeycloakAuthenticatorValve.java | 178 +++++++++++++++++ .../adapters/tomcat7/SimpleGroup.java | 42 ++++ .../adapters/tomcat7/SimplePrincipal.java | 51 +++++ 12 files changed, 1053 insertions(+), 1 deletion(-) create mode 100755 integration/tomcat7/adapter/pom.xml create mode 100755 integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/Actions.java create mode 100755 integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/AuthenticatedActionsValve.java create mode 100755 integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaHttpFacade.java create mode 100755 integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java create mode 100755 integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSecurityContextHelper.java create mode 100755 integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java create mode 100755 integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CorsPreflightChecker.java create mode 100755 integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java create mode 100644 integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimpleGroup.java create mode 100644 integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimplePrincipal.java diff --git a/integration/pom.xml b/integration/pom.xml index 366904c5719..15a2196e28e 100755 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -19,12 +19,12 @@ servlet-oauth-client jboss-adapter-core as7-eap6/adapter + tomcat7/adapter undertow wildfly-adapter wildfly-subsystem as7-eap-subsystem js installed - diff --git a/integration/tomcat7/adapter/pom.xml b/integration/tomcat7/adapter/pom.xml new file mode 100755 index 00000000000..b3259a65823 --- /dev/null +++ b/integration/tomcat7/adapter/pom.xml @@ -0,0 +1,88 @@ + + + + keycloak-parent + org.keycloak + 1.0-alpha-3 + ../../../pom.xml + + 4.0.0 + + keycloak-tomcat7-adapter + Keycloak Tomcat7 Integration + + + + + org.keycloak + keycloak-core + ${project.version} + + + org.keycloak + keycloak-adapter-core + ${project.version} + + + org.keycloak + keycloak-jboss-adapter-core + ${project.version} + + + org.apache.httpcomponents + httpclient + ${keycloak.apache.httpcomponents.version} + + + net.iharder + base64 + + + org.bouncycastle + bcprov-jdk16 + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-mapper-asl + + + org.codehaus.jackson + jackson-xc + + + org.apache.tomcat + tomcat-servlet-api + 7.0.52 + provided + + + org.apache.tomcat + tomcat-catalina + 7.0.52 + provided + + + + junit + junit + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + + diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/Actions.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/Actions.java new file mode 100755 index 00000000000..75830615205 --- /dev/null +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/Actions.java @@ -0,0 +1,13 @@ +package org.keycloak.adapters.tomcat7; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface Actions { + public static final String J_OAUTH_ADMIN_FORCED_LOGOUT = "j_oauth_admin_forced_logout"; + public static final String J_OAUTH_LOGOUT = "j_oauth_logout"; + public static final String J_OAUTH_RESOLVE_ACCESS_CODE = "j_oauth_resolve_access_code"; + public static final String J_OAUTH_REMOTE_LOGOUT = "j_oauth_remote_logout"; + public static final String J_OAUTH_TOKEN_GRANT = "j_oauth_token_grant"; +} diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/AuthenticatedActionsValve.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/AuthenticatedActionsValve.java new file mode 100755 index 00000000000..9088533faae --- /dev/null +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/AuthenticatedActionsValve.java @@ -0,0 +1,49 @@ +package org.keycloak.adapters.tomcat7; + +import java.io.IOException; +import java.util.logging.Logger; + +import javax.management.ObjectName; +import javax.servlet.ServletException; + +import org.apache.catalina.Container; +import org.apache.catalina.Valve; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.catalina.valves.ValveBase; +import org.keycloak.adapters.AuthenticatedActionsHandler; +import org.keycloak.adapters.KeycloakDeployment; + +/** + * Pre-installed actions that must be authenticated + *

+ * Actions include: + *

+ * CORS Origin Check and Response headers + * k_query_bearer_token: Get bearer token from server for Javascripts CORS requests + * + * @author Davide Ungari + * @version $Revision: 1 $ + */ +public class AuthenticatedActionsValve extends ValveBase { + private static final Logger log = Logger.getLogger(""+AuthenticatedActionsValve.class); + protected KeycloakDeployment deployment; + + public AuthenticatedActionsValve(KeycloakDeployment deployment, Valve next, Container container, ObjectName objectName) { + this.deployment = deployment; + if (next == null) throw new RuntimeException("WTF is next null?!"); + setNext(next); + setContainer(container); + } + + + @Override + public void invoke(Request request, Response response) throws IOException, ServletException { + log.finer("AuthenticatedActionsValve.invoke" + request.getRequestURI()); + AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new CatalinaHttpFacade(request, response)); + if (handler.handledRequest()) { + return; + } + getNext().invoke(request, response); + } +} diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaHttpFacade.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaHttpFacade.java new file mode 100755 index 00000000000..8c4618fbe21 --- /dev/null +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaHttpFacade.java @@ -0,0 +1,181 @@ +package org.keycloak.adapters.tomcat7; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.security.cert.X509Certificate; +import javax.servlet.http.HttpServletResponse; + +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.HttpFacade; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class CatalinaHttpFacade implements HttpFacade { + protected org.apache.catalina.connector.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); + } + } + + 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 CatalinaHttpFacade(org.apache.catalina.connector.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/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java new file mode 100755 index 00000000000..7ed1f1d371c --- /dev/null +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java @@ -0,0 +1,123 @@ +package org.keycloak.adapters.tomcat7; + +import java.io.IOException; +import java.security.Principal; +import java.util.Collections; +import java.util.Set; +import java.util.logging.Logger; + +import org.apache.catalina.Session; +import org.apache.catalina.authenticator.Constants; +import org.apache.catalina.connector.Request; +import org.apache.catalina.realm.GenericPrincipal; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.OAuthRequestAuthenticator; +import org.keycloak.adapters.RefreshableKeycloakSecurityContext; +import org.keycloak.adapters.RequestAuthenticator; +import org.keycloak.representations.AccessToken; + +/** + * @author Davide Ungari + * @version $Revision: 1 $ + */ +public class CatalinaRequestAuthenticator extends RequestAuthenticator { + private static final Logger log = Logger.getLogger(""+CatalinaRequestAuthenticator.class); + protected KeycloakAuthenticatorValve valve; + protected CatalinaUserSessionManagement userSessionManagement; + protected Request request; + + public CatalinaRequestAuthenticator(KeycloakDeployment deployment, + KeycloakAuthenticatorValve valve, CatalinaUserSessionManagement userSessionManagement, + CatalinaHttpFacade facade, + Request request) { + super(facade, deployment, request.getConnector().getRedirectPort()); + this.valve = valve; + this.userSessionManagement = userSessionManagement; + this.request = request; + } + + @Override + protected OAuthRequestAuthenticator createOAuthAuthenticator() { + return new OAuthRequestAuthenticator(facade, deployment, sslRedirectPort) { + @Override + protected void saveRequest() { + try { + valve.keycloakSaveRequest(request); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + } + + @Override + protected void completeOAuthAuthentication(KeycloakPrincipal skp, RefreshableKeycloakSecurityContext securityContext) { + Set roles = getRolesFromToken(securityContext); + GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext); + Session session = request.getSessionInternal(true); + session.setPrincipal(principal); + session.setAuthType("OAUTH"); + session.setNote(KeycloakSecurityContext.class.getName(), securityContext); + String username = securityContext.getToken().getSubject(); + log.finer("userSessionManage.login: " + username); + userSessionManagement.login(session, username); + } + + @Override + protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext securityContext) { + Set roles = getRolesFromToken(securityContext); + for (String role : roles) { + log.info("Bearer role: " + role); + } + Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext); + request.setUserPrincipal(generalPrincipal); + request.setAuthType("KEYCLOAK"); + request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); + } + + protected Set getRolesFromToken(RefreshableKeycloakSecurityContext session) { + Set roles = null; + if (deployment.isUseResourceRoleMappings()) { + AccessToken.Access access = session.getToken().getResourceAccess(deployment.getResourceName()); + if (access != null) roles = access.getRoles(); + } else { + AccessToken.Access access = session.getToken().getRealmAccess(); + if (access != null) roles = access.getRoles(); + } + if (roles == null) roles = Collections.emptySet(); + return roles; + } + + @Override + protected boolean isCached() { + if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) + return false; + log.finer("remote logged in already"); + GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal(); + request.setUserPrincipal(principal); + request.setAuthType("KEYCLOAK"); + Session session = request.getSessionInternal(); + if (session != null) { + RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getNote(KeycloakSecurityContext.class.getName()); + if (securityContext != null) { + securityContext.setDeployment(deployment); + request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); + } + } + restoreRequest(); + return true; + } + + 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"); + } + } + } +} diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSecurityContextHelper.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSecurityContextHelper.java new file mode 100755 index 00000000000..b4e9a59c3f0 --- /dev/null +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSecurityContextHelper.java @@ -0,0 +1,131 @@ +package org.keycloak.adapters.tomcat7; + +import java.security.Principal; +import java.security.acl.Group; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.security.auth.Subject; + +import org.apache.catalina.Realm; +import org.apache.catalina.realm.GenericPrincipal; +import org.keycloak.KeycloakSecurityContext; + +/** + * @author Davide Ungari + * @version $Revision: 1 $ + */ +public class CatalinaSecurityContextHelper { + public GenericPrincipal createPrincipal(Realm realm, final Principal identity, final Set roleSet, final KeycloakSecurityContext securityContext) { +// KeycloakAccount account = new KeycloakAccount() { +// @Override +// public Principal getPrincipal() { +// return identity; +// } +// +// @Override +// public Set getRoles() { +// return roleSet; +// } +// +// @Override +// public KeycloakSecurityContext getKeycloakSecurityContext() { +// return securityContext; +// } +// }; + Subject subject = new Subject(); + Set principals = subject.getPrincipals(); + principals.add(identity); + Group[] roleSets = getRoleSets(roleSet); + for (int g = 0; g < roleSets.length; g++) { + Group group = roleSets[g]; + String name = group.getName(); + Group subjectGroup = createGroup(name, principals); +// if (subjectGroup instanceof NestableGroup) { +// /* A NestableGroup only allows Groups to be added to it so we +// need to add a SimpleGroup to subjectRoles to contain the roles +// */ +// SimpleGroup tmp = new SimpleGroup("Roles"); +// subjectGroup.addMember(tmp); +// subjectGroup = tmp; +// } + // Copy the group members to the Subject group + Enumeration members = group.members(); + while (members.hasMoreElements()) { + Principal role = (Principal) members.nextElement(); + subjectGroup.addMember(role); + } + } + Principal userPrincipal = getPrincipal(subject); + List rolesAsStringList = new ArrayList(); + rolesAsStringList.addAll(roleSet); + GenericPrincipal principal = new GenericPrincipal(userPrincipal.getName(), null, rolesAsStringList, userPrincipal, null); + return principal; + } + + /** + * Get the Principal given the authenticated Subject. Currently the first subject that is not of type {@code Group} is + * considered or the single subject inside the CallerPrincipal group. + * + * @param subject + * @return the authenticated subject + */ + protected Principal getPrincipal(Subject subject) { + Principal principal = null; + Principal callerPrincipal = null; + if (subject != null) { + Set principals = subject.getPrincipals(); + if (principals != null && !principals.isEmpty()) { + for (Principal p : principals) { + if (!(p instanceof Group) && principal == null) { + principal = p; + } +// if (p instanceof Group) { +// Group g = Group.class.cast(p); +// if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) { +// Enumeration e = g.members(); +// if (e.hasMoreElements()) +// callerPrincipal = e.nextElement(); +// } +// } + } + } + } + return callerPrincipal == null ? principal : callerPrincipal; + } + + protected Group createGroup(String name, Set principals) { + Group roles = null; + Iterator iter = principals.iterator(); + while (iter.hasNext()) { + Object next = iter.next(); + if ((next instanceof Group) == false) + continue; + Group grp = (Group) next; + if (grp.getName().equals(name)) { + roles = grp; + break; + } + } + // If we did not find a group create one + if (roles == null) { + roles = new SimpleGroup(name); + principals.add(roles); + } + return roles; + } + + protected Group[] getRoleSets(Collection roleSet) { + SimpleGroup roles = new SimpleGroup("Roles"); + Group[] roleSets = {roles}; + for (String role : roleSet) { + roles.addMember(new SimplePrincipal(role)); + } + return roleSets; + } + +} diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java new file mode 100755 index 00000000000..a84284e5e13 --- /dev/null +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java @@ -0,0 +1,137 @@ +package org.keycloak.adapters.tomcat7; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.apache.catalina.Session; +import org.apache.catalina.SessionEvent; +import org.apache.catalina.SessionListener; +import org.apache.catalina.realm.GenericPrincipal; +import org.keycloak.adapters.UserSessionManagement; + +/** + * Manages relationship to users and sessions so that forced admin logout can be implemented + * + * @author Davide Ungari + * @version $Revision: 1 $ + */ +public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement { + private static final Logger log = Logger.getLogger(""+CatalinaUserSessionManagement.class); + protected ConcurrentHashMap userSessionMap = new ConcurrentHashMap(); + + public static class UserSessions { + protected Map sessions = new ConcurrentHashMap(); + protected long loggedIn = System.currentTimeMillis(); + + + public Map getSessions() { + return sessions; + } + + public long getLoggedIn() { + return loggedIn; + } + } + + @Override + public int getActiveSessions() { + int active = 0; + synchronized (userSessionMap) { + for (UserSessions sessions : userSessionMap.values()) { + active += sessions.getSessions().size(); + } + + } + return active; + } + + /** + * + * @param username + * @return null if user not logged in + */ + @Override + public Long getUserLoginTime(String username) { + UserSessions sessions = userSessionMap.get(username); + if (sessions == null) return null; + return sessions.getLoggedIn(); + } + + @Override + public Set getActiveUsers() { + HashSet set = new HashSet(); + set.addAll(userSessionMap.keySet()); + return set; + } + + protected void login(Session session, String username) { + synchronized (userSessionMap) { + UserSessions userSessions = userSessionMap.get(username); + if (userSessions == null) { + userSessions = new UserSessions(); + userSessionMap.put(username, userSessions); + } + userSessions.getSessions().put(session.getId(), session); + } + session.addSessionListener(this); + } + + @Override + public void logoutAll() { + List users = new ArrayList(); + users.addAll(userSessionMap.keySet()); + for (String user : users) logout(user); + } + + @Override + public void logout(String user) { + log.finer("logoutUser: " + user); + UserSessions sessions = null; + synchronized (userSessionMap) { + sessions = userSessionMap.remove(user); + + } + if (sessions == null) { + log.finer("no session for user: " + user); + return; + + } + + log.finer("found session for user"); + for (Session session : sessions.getSessions().values()) { + session.setPrincipal(null); + session.setAuthType(null); + session.getSession().invalidate(); + } + } + + public void sessionEvent(SessionEvent event) { + // We only care about session destroyed events + if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType()) + && (!Session.SESSION_PASSIVATED_EVENT.equals(event.getType()))) + return; + + // Look up the single session id associated with this session (if any) + Session session = event.getSession(); + GenericPrincipal principal = (GenericPrincipal) session.getPrincipal(); + if (principal == null) return; + session.setPrincipal(null); + session.setAuthType(null); + + String username = principal.getUserPrincipal().getName(); + synchronized (userSessionMap) { + UserSessions sessions = userSessionMap.get(username); + if (sessions != null) { + sessions.getSessions().remove(session.getId()); + if (sessions.getSessions().isEmpty()) { + userSessionMap.remove(username); + } + } + } + } +} 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 new file mode 100755 index 00000000000..d6148721571 --- /dev/null +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CorsPreflightChecker.java @@ -0,0 +1,59 @@ +package org.keycloak.adapters.tomcat7; + +import java.util.logging.Logger; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.keycloak.adapters.KeycloakDeployment; + +/** + * @author Davide Ungari + * @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.finer("checkCorsPreflight " + request.getRequestURI()); + if (!request.getMethod().equalsIgnoreCase("OPTIONS")) { + log.finer("checkCorsPreflight: not options "); + return false; + + } + if (request.getHeader("Origin") == null) { + log.finer("checkCorsPreflight: no origin header"); + return false; + } + log.finer("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/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java new file mode 100755 index 00000000000..2dd4e975635 --- /dev/null +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java @@ -0,0 +1,178 @@ +package org.keycloak.adapters.tomcat7; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.Context; +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.LifecycleListener; +import org.apache.catalina.authenticator.FormAuthenticator; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.deploy.LoginConfig; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.AdapterConstants; +import org.keycloak.adapters.AuthChallenge; +import org.keycloak.adapters.AuthOutcome; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.KeycloakDeploymentBuilder; +import org.keycloak.adapters.PreAuthActionsHandler; +import org.keycloak.adapters.RefreshableKeycloakSecurityContext; + +/** + * Web deployment whose security is managed by a remote OAuth Skeleton Key + * authentication server + *

+ * Redirects browser to remote authentication server if not logged in. Also + * allows OAuth Bearer Token requests that contain a Skeleton Key bearer tokens. + * + * @author Davide Ungari + * @version $Revision: 1 $ + */ +public class KeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener { + private final static Logger log = Logger.getLogger(""+KeycloakAuthenticatorValve.class); + protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement(); + protected KeycloakDeployment deployment; + + @Override + public void lifecycleEvent(LifecycleEvent event) { + if (event.getType() == Lifecycle.START_EVENT) { + try { + startDeployment(); + } catch (LifecycleException e) { + e.printStackTrace(); + } + } + } + + public void startDeployment() throws LifecycleException { + super.start(); + StandardContext standardContext = (StandardContext) context; + standardContext.addLifecycleListener(this); + cache = false; + } + + public void initInternal() { + this.deployment = KeycloakDeploymentBuilder.build(getConfigInputStream(context)); + log.info("deployment realm:" + deployment.getRealm() + " resource:" + deployment.getResourceName()); + AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deployment, getNext(), getContainer(), getObjectName()); + setNext(actions); + } + + private static InputStream getJSONFromServletContext(ServletContext servletContext) { + String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME); + if (json == null) { + return null; + } + log.info("**** using " + AdapterConstants.AUTH_DATA_PARAM_NAME); + log.info(json); + return new ByteArrayInputStream(json.getBytes()); + } + + private static InputStream getConfigInputStream(Context context) { + InputStream is = getJSONFromServletContext(context.getServletContext()); + if (is == null) { + String path = context.getServletContext().getInitParameter("keycloak.config.file"); + if (path == null) { + log.info("**** using /WEB-INF/keycloak.json"); + is = context.getServletContext().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 void invoke(Request request, Response response) throws IOException, ServletException { + try { + PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deployment, + new CatalinaHttpFacade(request, response)); + if (handler.handleRequest()) { + return; + } + checkKeycloakSession(request); + super.invoke(request, response); + } finally { + } + } + + @Override + public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException { + CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response); + CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, userSessionManagement, facade, request); + AuthOutcome outcome = authenticator.authenticate(); + if (outcome == AuthOutcome.AUTHENTICATED) { + if (facade.isEnded()) { + return false; + } + return true; + } + AuthChallenge challenge = authenticator.getChallenge(); + if (challenge != null) { + challenge.challenge(facade); + } + return false; + } + + /** + * Checks that access token is still valid. Will attempt refresh of token if + * it is not. + * + * @param request + */ + protected void checkKeycloakSession(Request request) { + if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) + return; + RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal() + .getNote(KeycloakSecurityContext.class.getName()); + if (session == null) + return; + // just in case session got serialized + session.setDeployment(deployment); + if (session.isActive()) + return; + + // FYI: A refresh requires same scope, so same roles will be set. + // Otherwise, refresh will fail and token will + // not be updated + session.refreshExpiredToken(); + if (session.isActive()) + return; + + request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName()); + request.setUserPrincipal(null); + request.setAuthType(null); + request.getSessionInternal().setPrincipal(null); + request.getSessionInternal().setAuthType(null); + } + + public void keycloakSaveRequest(Request request) throws IOException { + saveRequest(request, request.getSessionInternal(true)); + } + + public boolean keycloakRestoreRequest(Request request) { + try { + return restoreRequest(request, request.getSessionInternal()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimpleGroup.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimpleGroup.java new file mode 100644 index 00000000000..b583d3b0e30 --- /dev/null +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimpleGroup.java @@ -0,0 +1,42 @@ +package org.keycloak.adapters.tomcat7; + +import java.security.Principal; +import java.security.acl.Group; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +public class SimpleGroup extends SimplePrincipal implements Group { + private static final long serialVersionUID = 3273437693505893786L; + private final Set members = new HashSet(); + + /** + * Creates a new group with the given name. + * @param name Group name. + */ + public SimpleGroup(final String name) { + super(name); + } + + public boolean addMember(final Principal user) { + return this.members.add(user); + } + + public boolean isMember(final Principal member) { + return this.members.contains(member); + } + + public Enumeration members() { + return Collections.enumeration(this.members); + } + + public boolean removeMember(final Principal user) { + return this.members.remove(user); + } + + public String toString() { + return super.toString() + ": " + members.toString(); + } + +} diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimplePrincipal.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimplePrincipal.java new file mode 100644 index 00000000000..e3d6507b713 --- /dev/null +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimplePrincipal.java @@ -0,0 +1,51 @@ +package org.keycloak.adapters.tomcat7; + +import java.io.Serializable; +import java.security.Principal; + +/** + * Simple security principal implementation. + * + * @author Marvin S. Addison + * @version $Revision: 22071 $ + * @since 3.1.11 + * + */ +public class SimplePrincipal implements Principal, Serializable { + + /** SimplePrincipal.java */ + private static final long serialVersionUID = -5645357206342793145L; + + /** The unique identifier for this principal. */ + private final String name; + + /** + * Creates a new principal with the given name. + * @param name Principal name. + */ + public SimplePrincipal(final String name) { + this.name = name; + } + + public final String getName() { + return this.name; + } + + public String toString() { + return getName(); + } + + public boolean equals(final Object o) { + if (o == null) { + return false; + } else if (!(o instanceof SimplePrincipal)) { + return false; + } else { + return getName().equals(((SimplePrincipal)o).getName()); + } + } + + public int hashCode() { + return 37 * getName().hashCode(); + } +} \ No newline at end of file From c03b97d2d41322e920648cfb5079e8c7c1e00963 Mon Sep 17 00:00:00 2001 From: Davide Ungari Date: Sun, 20 Apr 2014 12:06:31 +0200 Subject: [PATCH 2/2] #cfg update version --- integration/tomcat7/adapter/pom.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/integration/tomcat7/adapter/pom.xml b/integration/tomcat7/adapter/pom.xml index b3259a65823..698639b8ca0 100755 --- a/integration/tomcat7/adapter/pom.xml +++ b/integration/tomcat7/adapter/pom.xml @@ -1,9 +1,10 @@ - - + + keycloak-parent org.keycloak - 1.0-alpha-3 + 1.0-beta-1-SNAPSHOT ../../../pom.xml 4.0.0