keycloak-memoizeit
Changes
integration/pom.xml 2(+1 -1)
integration/tomcat7/adapter/pom.xml 89(+89 -0)
integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/AuthenticatedActionsValve.java 49(+49 -0)
integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaHttpFacade.java 181(+181 -0)
integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java 123(+123 -0)
integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSecurityContextHelper.java 131(+131 -0)
integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java 137(+137 -0)
integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CorsPreflightChecker.java 59(+59 -0)
Details
integration/pom.xml 2(+1 -1)
diff --git a/integration/pom.xml b/integration/pom.xml
index 366904c..15a2196 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -19,12 +19,12 @@
<module>servlet-oauth-client</module>
<module>jboss-adapter-core</module>
<module>as7-eap6/adapter</module>
+ <module>tomcat7/adapter</module>
<module>undertow</module>
<module>wildfly-adapter</module>
<module>wildfly-subsystem</module>
<module>as7-eap-subsystem</module>
<module>js</module>
<module>installed</module>
- <!-- <module>as7-eap6/jboss-modules</module> -->
</modules>
</project>
integration/tomcat7/adapter/pom.xml 89(+89 -0)
diff --git a/integration/tomcat7/adapter/pom.xml b/integration/tomcat7/adapter/pom.xml
new file mode 100755
index 0000000..698639b
--- /dev/null
+++ b/integration/tomcat7/adapter/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-tomcat7-adapter</artifactId>
+ <name>Keycloak Tomcat7 Integration</name>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jboss-adapter-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>${keycloak.apache.httpcomponents.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.iharder</groupId>
+ <artifactId>base64</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk16</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-core-asl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-xc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ <version>7.0.52</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ <version>7.0.52</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
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 0000000..7583061
--- /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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @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 0000000..9088533
--- /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
+ * <p/>
+ * Actions include:
+ * <p/>
+ * CORS Origin Check and Response headers
+ * k_query_bearer_token: Get bearer token from server for Javascripts CORS requests
+ *
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @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 0000000..8c4618f
--- /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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @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<String> getHeaders(String name) {
+ Enumeration<String> headers = request.getHeaders(name);
+ if (headers == null) return null;
+ List<String> list = new ArrayList<String>();
+ 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 0000000..7ed1f1d
--- /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 <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @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<String> 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<String> 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<String> getRolesFromToken(RefreshableKeycloakSecurityContext session) {
+ Set<String> 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 0000000..b4e9a59
--- /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 <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaSecurityContextHelper {
+ public GenericPrincipal createPrincipal(Realm realm, final Principal identity, final Set<String> roleSet, final KeycloakSecurityContext securityContext) {
+// KeycloakAccount account = new KeycloakAccount() {
+// @Override
+// public Principal getPrincipal() {
+// return identity;
+// }
+//
+// @Override
+// public Set<String> getRoles() {
+// return roleSet;
+// }
+//
+// @Override
+// public KeycloakSecurityContext getKeycloakSecurityContext() {
+// return securityContext;
+// }
+// };
+ Subject subject = new Subject();
+ Set<Principal> 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<? extends Principal> members = group.members();
+ while (members.hasMoreElements()) {
+ Principal role = (Principal) members.nextElement();
+ subjectGroup.addMember(role);
+ }
+ }
+ Principal userPrincipal = getPrincipal(subject);
+ List<String> rolesAsStringList = new ArrayList<String>();
+ 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<Principal> 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<? extends Principal> e = g.members();
+// if (e.hasMoreElements())
+// callerPrincipal = e.nextElement();
+// }
+// }
+ }
+ }
+ }
+ return callerPrincipal == null ? principal : callerPrincipal;
+ }
+
+ protected Group createGroup(String name, Set<Principal> principals) {
+ Group roles = null;
+ Iterator<Principal> 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<String> 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 0000000..a84284e
--- /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 <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement {
+ private static final Logger log = Logger.getLogger(""+CatalinaUserSessionManagement.class);
+ protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
+
+ public static class UserSessions {
+ protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
+ protected long loggedIn = System.currentTimeMillis();
+
+
+ public Map<String, Session> 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<String> getActiveUsers() {
+ HashSet<String> set = new HashSet<String>();
+ 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<String> users = new ArrayList<String>();
+ 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 0000000..d614872
--- /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 <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @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 0000000..2dd4e97
--- /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
+ * <p/>
+ * Redirects browser to remote authentication server if not logged in. Also
+ * allows OAuth Bearer Token requests that contain a Skeleton Key bearer tokens.
+ *
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @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 0000000..b583d3b
--- /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<Principal> members = new HashSet<Principal>();
+
+ /**
+ * 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<? extends Principal> 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 0000000..e3d6507
--- /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