keycloak-aplcache
Changes
integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java 108(+108 -0)
integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java 227(+227 -0)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java 76(+3 -73)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java 46(+46 -0)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java 4(+2 -2)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java 11(+8 -3)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java 169(+4 -165)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/AuthenticatedActionsHandler.java 132(+0 -132)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java 9(+4 -5)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java 2(+1 -1)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/PreflightCorsHandler.java 82(+0 -82)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java 198(+0 -198)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java 4(+2 -2)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java 54(+54 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java 8(+4 -4)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java 47(+47 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java 46(+46 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java 49(+49 -0)
Details
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java
new file mode 100755
index 0000000..f04f144
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java
@@ -0,0 +1,108 @@
+package org.keycloak.adapters;
+
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.representations.AccessToken;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * 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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class AuthenticatedActionsHandler {
+ private static final Logger log = Logger.getLogger(AuthenticatedActionsHandler.class);
+ protected KeycloakDeployment deployment;
+ protected HttpFacade facade;
+
+ public AuthenticatedActionsHandler(KeycloakDeployment deployment, HttpFacade facade) {
+ this.deployment = deployment;
+ this.facade = facade;
+ }
+
+ public boolean handledRequest() {
+ log.debugv("AuthenticatedActionsValve.invoke {0}", facade.getRequest().getURI());
+ if (corsRequest()) return true;
+ String requestUri = facade.getRequest().getURI();
+ if (requestUri.endsWith(AdapterConstants.K_QUERY_BEARER_TOKEN)) {
+ queryBearerToken();
+ return true;
+ }
+ return false;
+ }
+
+ protected void queryBearerToken() {
+ log.debugv("queryBearerToken {0}",facade.getRequest().getURI());
+ if (abortTokenResponse()) return;
+ facade.getResponse().setStatus(200);
+ facade.getResponse().setHeader("Content-Type", "text/plain");
+ try {
+ facade.getResponse().getOutputStream().write(facade.getSecurityContext().getTokenString().getBytes());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ facade.getResponse().end();
+ }
+
+ protected boolean abortTokenResponse() {
+ if (facade.getSecurityContext() == null) {
+ log.debugv("Not logged in, sending back 401: {0}",facade.getRequest().getURI());
+ facade.getResponse().setStatus(401);
+ facade.getResponse().end();
+ return true;
+ }
+ if (!deployment.isExposeToken()) {
+ facade.getResponse().setStatus(200);
+ facade.getResponse().end();
+ return true;
+ }
+ // Don't allow a CORS request if we're not validating CORS requests.
+ if (!deployment.isCors() && facade.getRequest().getHeader(CorsHeaders.ORIGIN) != null) {
+ facade.getResponse().setStatus(200);
+ facade.getResponse().end();
+ return true;
+ }
+ return false;
+ }
+
+ protected boolean corsRequest() {
+ if (!deployment.isCors()) return false;
+ KeycloakSecurityContext securityContext = facade.getSecurityContext();
+ String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
+ log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
+ if (securityContext != null && origin != null) {
+ AccessToken token = securityContext.getToken();
+ Set<String> allowedOrigins = token.getAllowedOrigins();
+ if (log.isDebugEnabled()) {
+ for (String a : allowedOrigins) log.debug(" " + a);
+ }
+ if (allowedOrigins == null || (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin))) {
+ if (allowedOrigins == null) {
+ log.debugv("allowedOrigins was null in token");
+ }
+ if (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin)) {
+ log.debugv("allowedOrigins did not contain origin");
+
+ }
+ facade.getResponse().setStatus(403);
+ facade.getResponse().end();
+ return true;
+ }
+ log.debugv("returning origin: {0}", origin);
+ facade.getResponse().setStatus(200);
+ facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+ facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+ } else {
+ log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
+ }
+ return false;
+ }
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/CorsHeaders.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/CorsHeaders.java
new file mode 100755
index 0000000..21f2b94
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/CorsHeaders.java
@@ -0,0 +1,16 @@
+package org.keycloak.adapters;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CorsHeaders {
+ String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
+ String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
+ String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
+ String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
+ String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
+ String ORIGIN = "Origin";
+ String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
+ String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java
index 25b9a7f..a6eaff2 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java
@@ -1,6 +1,10 @@
package org.keycloak.adapters;
+import org.keycloak.KeycloakSecurityContext;
+
import javax.security.cert.X509Certificate;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.List;
/**
@@ -48,6 +52,8 @@ public interface HttpFacade {
}
interface Request {
+
+ String getMethod();
/**
* Full request URI with query params
*
@@ -64,7 +70,9 @@ public interface HttpFacade {
String getQueryParamValue(String param);
Cookie getCookie(String cookieName);
+ String getHeader(String name);
List<String> getHeaders(String name);
+ InputStream getInputStream();
}
interface Response {
@@ -73,6 +81,8 @@ public interface HttpFacade {
void setHeader(String name, String value);
void resetCookie(String name, String path);
void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly);
+ OutputStream getOutputStream();
+ void sendError(int code, String message);
/**
* If the response is finished, end it.
@@ -81,6 +91,7 @@ public interface HttpFacade {
void end();
}
+ KeycloakSecurityContext getSecurityContext();
Request getRequest();
Response getResponse();
X509Certificate[] getCertificateChain();
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
new file mode 100755
index 0000000..6cf53a6
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -0,0 +1,227 @@
+package org.keycloak.adapters;
+
+import org.jboss.logging.Logger;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.representations.adapters.action.AdminAction;
+import org.keycloak.representations.adapters.action.LogoutAction;
+import org.keycloak.representations.adapters.action.PushNotBeforeAction;
+import org.keycloak.representations.adapters.action.SessionStats;
+import org.keycloak.representations.adapters.action.SessionStatsAction;
+import org.keycloak.representations.adapters.action.UserStats;
+import org.keycloak.representations.adapters.action.UserStatsAction;
+import org.keycloak.util.JsonSerialization;
+import org.keycloak.util.StreamUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class PreAuthActionsHandler {
+
+ private static final Logger log = Logger.getLogger(PreAuthActionsHandler.class);
+
+ protected UserSessionManagement userSessionManagement;
+ protected KeycloakDeployment deployment;
+ protected HttpFacade facade;
+
+ public PreAuthActionsHandler(UserSessionManagement userSessionManagement, KeycloakDeployment deployment, HttpFacade facade) {
+ this.userSessionManagement = userSessionManagement;
+ this.deployment = deployment;
+ this.facade = facade;
+ }
+
+ public boolean handleRequest() {
+ String requestUri = facade.getRequest().getURI();
+ log.debugv("adminRequest {0}", requestUri);
+ if (preflightCors()) {
+ return true;
+ }
+ if (requestUri.endsWith(AdapterConstants.K_LOGOUT)) {
+ handleLogout();
+ return true;
+ } else if (requestUri.endsWith(AdapterConstants.K_PUSH_NOT_BEFORE)) {
+ handlePushNotBefore();
+ return true;
+ } else if (requestUri.endsWith(AdapterConstants.K_GET_SESSION_STATS)) {
+ handleGetSessionStats();
+ return true;
+ }else if (requestUri.endsWith(AdapterConstants.K_GET_USER_STATS)) {
+ handleGetUserStats();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean preflightCors() {
+ log.debugv("checkCorsPreflight {0}", facade.getRequest().getURI());
+ if (!facade.getRequest().getMethod().equalsIgnoreCase("OPTIONS")) {
+ return false;
+ }
+ if (facade.getRequest().getHeader(CorsHeaders.ORIGIN) == null) {
+ log.debug("checkCorsPreflight: no origin header");
+ return false;
+ }
+ log.debug("Preflight request returning");
+ facade.getResponse().setStatus(200);
+ String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
+ facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+ facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+ String requestMethods = facade.getRequest().getHeader(CorsHeaders.ACCESS_CONTROL_REQUEST_METHOD);
+ if (requestMethods != null) {
+ if (deployment.getCorsAllowedMethods() != null) {
+ requestMethods = deployment.getCorsAllowedMethods();
+ }
+ facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethods);
+ }
+ String allowHeaders = facade.getRequest().getHeader(CorsHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
+ if (allowHeaders != null) {
+ if (deployment.getCorsAllowedHeaders() != null) {
+ allowHeaders = deployment.getCorsAllowedHeaders();
+ }
+ facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders);
+ }
+ if (deployment.getCorsMaxAge() > -1) {
+ facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_MAX_AGE, Integer.toString(deployment.getCorsMaxAge()));
+ }
+ return true;
+ }
+
+ protected void handleLogout() {
+ log.info("K_LOGOUT sent");
+ try {
+ JWSInput token = verifyAdminRequest();
+ if (token == null) {
+ return;
+ }
+ LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
+ if (!validateAction(action)) return;
+ String user = action.getUser();
+ if (user != null) {
+ log.info("logout of session for: " + user);
+ userSessionManagement.logout(user);
+ } else {
+ log.info("logout of all sessions");
+ userSessionManagement.logoutAll();
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+
+ protected void handlePushNotBefore() {
+ log.info("K_PUSH_NOT_BEFORE sent");
+ try {
+ JWSInput token = verifyAdminRequest();
+ if (token == null) {
+ return;
+ }
+ PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
+ if (!validateAction(action)) return;
+ deployment.setNotBefore(action.getNotBefore());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected JWSInput verifyAdminRequest() throws Exception {
+ String token = StreamUtil.readString(facade.getRequest().getInputStream());
+ if (token == null) {
+ log.warn("admin request failed, no token");
+ facade.getResponse().sendError(403, "no token");
+ return null;
+ }
+
+ JWSInput input = new JWSInput(token);
+ boolean verified = false;
+ try {
+ verified = RSAProvider.verify(input, deployment.getRealmKey());
+ } catch (Exception ignore) {
+ }
+ if (!verified) {
+ log.warn("admin request failed, unable to verify token");
+ facade.getResponse().sendError(403, "no token");
+ return null;
+ }
+ return input;
+ }
+
+
+ protected boolean validateAction(AdminAction action) {
+ if (!action.validate()) {
+ log.warn("admin request failed, not validated" + action.getAction());
+ facade.getResponse().sendError(400, "Not validated");
+ return false;
+ }
+ if (action.isExpired()) {
+ log.warn("admin request failed, expired token");
+ facade.getResponse().sendError(400, "Expired token");
+ return false;
+ }
+ if (!deployment.getResourceName().equals(action.getResource())) {
+ log.warn("Resource name does not match");
+ facade.getResponse().sendError(400, "Resource name does not match");
+ return false;
+
+ }
+ return true;
+ }
+
+ protected void handleGetSessionStats() {
+ log.info("K_GET_SESSION_STATS sent");
+ try {
+ JWSInput token = verifyAdminRequest();
+ if (token == null) return;
+ SessionStatsAction action = JsonSerialization.readValue(token.getContent(), SessionStatsAction.class);
+ if (!validateAction(action)) return;
+ SessionStats stats = new SessionStats();
+ stats.setActiveSessions(userSessionManagement.getActiveSessions());
+ stats.setActiveUsers(userSessionManagement.getActiveUsers().size());
+ if (action.isListUsers() && userSessionManagement.getActiveSessions() > 0) {
+ Map<String, UserStats> list = new HashMap<String, UserStats>();
+ for (String user : userSessionManagement.getActiveUsers()) {
+ list.put(user, getUserStats(user));
+ }
+ stats.setUsers(list);
+ }
+ facade.getResponse().setStatus(200);
+ facade.getResponse().setHeader("Content-Type", "application/json");
+ JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), stats);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ protected void handleGetUserStats() {
+ log.info("K_GET_USER_STATS sent");
+ try {
+ JWSInput token = verifyAdminRequest();
+ if (token == null) return;
+ UserStatsAction action = JsonSerialization.readValue(token.getContent(), UserStatsAction.class);
+ if (!validateAction(action)) return;
+ String user = action.getUser();
+ UserStats stats = getUserStats(user);
+ facade.getResponse().setStatus(200);
+ facade.getResponse().setHeader("Content-Type", "application/json");
+ JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), stats);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected UserStats getUserStats(String user) {
+ UserStats stats = new UserStats();
+ Long loginTime = userSessionManagement.getUserLoginTime(user);
+ if (loginTime != null) {
+ stats.setLoggedIn(true);
+ stats.setWhenLoggedIn(loginTime);
+ } else {
+ stats.setLoggedIn(false);
+ }
+ return stats;
+ }
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
new file mode 100755
index 0000000..94da30f
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
@@ -0,0 +1,19 @@
+package org.keycloak.adapters;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserSessionManagement {
+ int getActiveSessions();
+
+ Long getUserLoginTime(String username);
+
+ Set<String> getActiveUsers();
+
+ void logoutAll();
+
+ void logout(String user);
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java
index 229858c..84f5ab6 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java
@@ -9,6 +9,7 @@ import org.apache.catalina.valves.ValveBase;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.AdapterConfig;
@@ -46,81 +47,10 @@ public class AuthenticatedActionsValve extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
log.debugv("AuthenticatedActionsValve.invoke {0}", request.getRequestURI());
- KeycloakSecurityContext session = getSkeletonKeySession(request);
- if (corsRequest(request, response, session)) return;
- String requestUri = request.getRequestURI();
- if (requestUri.endsWith(AdapterConstants.K_QUERY_BEARER_TOKEN)) {
- queryBearerToken(request, response, session);
+ AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new CatalinaHttpFacade(request, response));
+ if (handler.handledRequest()) {
return;
}
getNext().invoke(request, response);
}
-
- public KeycloakSecurityContext getSkeletonKeySession(Request request) {
- KeycloakSecurityContext skSession = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
- if (skSession != null) return skSession;
- Session session = request.getSessionInternal();
- if (session != null) {
- return (KeycloakSecurityContext) session.getNote(KeycloakSecurityContext.class.getName());
- }
- return null;
- }
-
- protected void queryBearerToken(Request request, Response response, KeycloakSecurityContext session) throws IOException, ServletException {
- log.debugv("queryBearerToken {0}", request.getRequestURI());
- if (abortTokenResponse(request, response, session)) return;
- response.setStatus(HttpServletResponse.SC_OK);
- response.setContentType("text/plain");
- response.getOutputStream().write(session.getTokenString().getBytes());
- response.getOutputStream().flush();
-
- }
-
- protected boolean abortTokenResponse(Request request, Response response, KeycloakSecurityContext session) throws IOException {
- if (session == null) {
- log.debugv("session was null, sending back 401: {0}", request.getRequestURI());
- response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
- return true;
- }
- if (!deployment.isExposeToken()) {
- response.setStatus(HttpServletResponse.SC_OK);
- return true;
- }
- if (!deployment.isCors() && request.getHeader("Origin") != null) {
- response.setStatus(HttpServletResponse.SC_OK);
- return true;
- }
- return false;
- }
-
- protected boolean corsRequest(Request request, Response response, KeycloakSecurityContext session) throws IOException {
- if (!deployment.isCors()) return false;
- log.debugv("CORS enabled + request.getRequestURI()");
- String origin = request.getHeader("Origin");
- log.debugv("Origin: {0} uri: {1}", origin, request.getRequestURI());
- if (session != null && origin != null) {
- AccessToken token = session.getToken();
- Set<String> allowedOrigins = token.getAllowedOrigins();
- if (log.isDebugEnabled()) {
- for (String a : allowedOrigins) log.debug(" " + a);
- }
- if (allowedOrigins == null || (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin))) {
- if (allowedOrigins == null) {
- log.debugv("allowedOrigins was null in token");
- }
- if (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin)) {
- log.debugv("allowedOrigins did not contain origin");
-
- }
- response.sendError(HttpServletResponse.SC_FORBIDDEN);
- return true;
- }
- log.debugv("returning origin: {0}", origin);
- response.setHeader("Access-Control-Allow-Origin", origin);
- response.setHeader("Access-Control-Allow-Credentials", "true");
- } else {
- log.debugv("letting through. This is an unathenticated session or origin header was null: {0}", request.getRequestURI());
- }
- return false;
- }
}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java
index 62adcb6..6c47500 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java
@@ -2,10 +2,14 @@ package org.keycloak.adapters.as7;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
+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;
@@ -64,6 +68,25 @@ public class CatalinaHttpFacade implements HttpFacade {
}
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 {
@@ -101,6 +124,24 @@ public class CatalinaHttpFacade implements HttpFacade {
}
@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;
}
@@ -126,6 +167,11 @@ public class CatalinaHttpFacade implements HttpFacade {
}
@Override
+ public KeycloakSecurityContext getSecurityContext() {
+ return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+ }
+
+ @Override
public X509Certificate[] getCertificateChain() {
throw new IllegalStateException("Not supported yet");
}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
index 143df34..1188e6d 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
@@ -23,11 +23,11 @@ import java.util.Set;
*/
public class CatalinaRequestAuthenticator extends RequestAuthenticator {
protected KeycloakAuthenticatorValve valve;
- protected UserSessionManagement userSessionManagement;
+ protected CatalinaUserSessionManagement userSessionManagement;
protected Request request;
public CatalinaRequestAuthenticator(KeycloakDeployment deployment,
- KeycloakAuthenticatorValve valve, UserSessionManagement userSessionManagement,
+ KeycloakAuthenticatorValve valve, CatalinaUserSessionManagement userSessionManagement,
CatalinaHttpFacade facade,
Request request) {
super(facade, deployment, request.getConnector().getRedirectPort());
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
index 2012c66..74050be 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
@@ -5,24 +5,20 @@ import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
-import org.apache.catalina.Session;
-import org.apache.catalina.authenticator.Constants;
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.apache.catalina.realm.GenericPrincipal;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
-import org.keycloak.KeycloakPrincipal;
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;
-import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.action.AdminAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
import org.keycloak.representations.adapters.action.SessionStats;
@@ -35,7 +31,6 @@ import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.StreamUtil;
-import javax.security.auth.login.LoginException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -46,9 +41,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
-import java.util.Set;
/**
* Web deployment whose security is managed by a remote OAuth Skeleton Key authentication server
@@ -61,7 +54,7 @@ import java.util.Set;
*/
public class KeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
private static final Logger log = Logger.getLogger(KeycloakAuthenticatorValve.class);
- protected UserSessionManagement userSessionManagement = new UserSessionManagement();
+ protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
protected KeycloakDeployment deployment;
@@ -115,37 +108,8 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
try {
- if (deployment.isCors() && new CorsPreflightChecker(deployment).checkCorsPreflight(request, response)) {
- return;
- }
- String requestURI = request.getDecodedRequestURI();
- if (requestURI.endsWith(AdapterConstants.K_LOGOUT)) {
- JWSInput input = verifyAdminRequest(request, response);
- if (input == null) {
- return; // we failed to verify the request
- }
- remoteLogout(input, response);
- return;
- } else if (requestURI.endsWith(AdapterConstants.K_PUSH_NOT_BEFORE)) {
- JWSInput input = verifyAdminRequest(request, response);
- if (input == null) {
- return; // we failed to verify the request
- }
- pushNotBefore(input, response);
- return;
- } else if (requestURI.endsWith(AdapterConstants.K_GET_SESSION_STATS)) {
- JWSInput input = verifyAdminRequest(request, response);
- if (input == null) {
- return; // we failed to verify the request
- }
- getSessionStats(input, response);
- return;
- } else if (requestURI.endsWith(AdapterConstants.K_GET_USER_STATS)) {
- JWSInput input = verifyAdminRequest(request, response);
- if (input == null) {
- return; // we failed to verify the request
- }
- getUserStats(input, response);
+ PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deployment, new CatalinaHttpFacade(request, response));
+ if (handler.handleRequest()) {
return;
}
checkKeycloakSession(request);
@@ -172,131 +136,6 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
return false;
}
- protected JWSInput verifyAdminRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
- String token = StreamUtil.readString(request.getInputStream());
- if (token == null) {
- log.warn("admin request failed, no token");
- response.sendError(403, "no token");
- return null;
- }
-
- JWSInput input = new JWSInput(token);
- boolean verified = false;
- try {
- verified = RSAProvider.verify(input, deployment.getRealmKey());
- } catch (Exception ignore) {
- }
- if (!verified) {
- log.warn("admin request failed, unable to verify token");
- response.sendError(403, "verification failed");
- return null;
- }
- return input;
- }
-
-
- protected boolean validateAction(HttpServletResponse response, AdminAction action) throws IOException {
- if (!action.validate()) {
- log.warn("admin request failed, not validated" + action.getAction());
- response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Not validated");
- return false;
- }
- if (action.isExpired()) {
- log.warn("admin request failed, expired token");
- response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Expired token");
- return false;
- }
- if (!deployment.getResourceName().equals(action.getResource())) {
- log.warn("Resource name does not match");
- response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Resource name does not match");
- return false;
-
- }
- return true;
- }
-
- protected void pushNotBefore(JWSInput token, HttpServletResponse response) throws IOException {
- log.info("->> pushNotBefore: ");
- PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
- if (!validateAction(response, action)) {
- return;
- }
- deployment.setNotBefore(action.getNotBefore());
- response.setStatus(HttpServletResponse.SC_NO_CONTENT);
-
- }
-
- protected UserStats getUserStats(String user) {
- UserStats stats = new UserStats();
- Long loginTime = userSessionManagement.getUserLoginTime(user);
- if (loginTime != null) {
- stats.setLoggedIn(true);
- stats.setWhenLoggedIn(loginTime);
- } else {
- stats.setLoggedIn(false);
- }
- return stats;
- }
-
-
- protected void getSessionStats(JWSInput token, HttpServletResponse response) throws IOException {
- log.info("->> getSessionStats: ");
- SessionStatsAction action = JsonSerialization.readValue(token.getContent(), SessionStatsAction.class);
- if (!validateAction(response, action)) {
- return;
- }
- SessionStats stats = new SessionStats();
- stats.setActiveSessions(userSessionManagement.getActiveSessions());
- stats.setActiveUsers(userSessionManagement.getActiveUsers().size());
- if (action.isListUsers() && userSessionManagement.getActiveSessions() > 0) {
- Map<String, UserStats> list = new HashMap<String, UserStats>();
- for (String user : userSessionManagement.getActiveUsers()) {
- list.put(user, getUserStats(user));
- }
- stats.setUsers(list);
- }
- response.setStatus(200);
- response.setContentType("application/json");
- JsonSerialization.writeValueToStream(response.getOutputStream(), stats);
-
- }
-
- protected void getUserStats(JWSInput token, HttpServletResponse response) throws IOException {
- log.info("->> getUserStats: ");
- UserStatsAction action = JsonSerialization.readValue(token.getContent(), UserStatsAction.class);
- if (!validateAction(response, action)) {
- return;
- }
- String user = action.getUser();
- UserStats stats = getUserStats(user);
- response.setStatus(200);
- response.setContentType("application/json");
- JsonSerialization.writeValueToStream(response.getOutputStream(), stats);
- }
-
-
- protected void remoteLogout(JWSInput token, HttpServletResponse response) throws IOException {
- try {
- log.debug("->> remoteLogout: ");
- LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
- if (!validateAction(response, action)) {
- return;
- }
- String user = action.getUser();
- if (user != null) {
- log.debug("logout of session for: " + user);
- userSessionManagement.logout(user);
- } else {
- log.debug("logout of all sessions");
- userSessionManagement.logoutAll();
- }
- } catch (Exception e) {
- log.warn("failed to logout", e);
- response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to logout");
- }
- response.setStatus(HttpServletResponse.SC_NO_CONTENT);
- }
-
/**
* Checks that access token is still valid. Will attempt refresh of token if it is not.
*
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
index 9b0a911..50895c6 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
@@ -14,6 +14,7 @@ import io.undertow.servlet.api.ServletSessionConfig;
import java.io.ByteArrayInputStream;
import org.jboss.logging.Logger;
import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
@@ -63,16 +64,14 @@ public class KeycloakServletExtension implements ServletExtension {
}
if (is == null) throw new RuntimeException("Unable to find realm config in /WEB-INF/keycloak.json or in keycloak subsystem.");
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(is);
- PreflightCorsHandler.Wrapper preflight = new PreflightCorsHandler.Wrapper(deployment);
- UserSessionManagement userSessionManagement = new UserSessionManagement(deployment);
+ UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement(deployment);
final ServletKeycloakAuthMech mech = new ServletKeycloakAuthMech(deployment, userSessionManagement, deploymentInfo.getConfidentialPortManager());
- AuthenticatedActionsHandler.Wrapper actions = new AuthenticatedActionsHandler.Wrapper(deployment);
+ UndertowAuthenticatedActionsHandler.Wrapper actions = new UndertowAuthenticatedActionsHandler.Wrapper(deployment);
// setup handlers
- deploymentInfo.addInitialHandlerChainWrapper(preflight); // cors preflight
- deploymentInfo.addOuterHandlerChainWrapper(new ServletAdminActionsHandler.Wrapper(deployment, userSessionManagement));
+ deploymentInfo.addOuterHandlerChainWrapper(new ServletPreAuthActionsHandler.Wrapper(deployment, userSessionManagement));
deploymentInfo.addAuthenticationMechanism("KEYCLOAK", new AuthenticationMechanismFactory() {
@Override
public AuthenticationMechanism create(String s, FormParserFactory formParserFactory, Map<String, String> stringStringMap) {
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
index f66cd01..310958a 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
@@ -59,7 +59,7 @@ public class KeycloakUndertowAccount implements Account, Serializable {
return session.getTokenString();
}
- public RefreshableKeycloakSecurityContext getSession() {
+ public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
return session;
}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
index 8160592..2ce8f06 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
@@ -17,10 +17,10 @@ public class ServletKeycloakAuthMech implements AuthenticationMechanism {
public static final AttachmentKey<AuthChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
protected KeycloakDeployment deployment;
- protected UserSessionManagement userSessionManagement;
+ protected UndertowUserSessionManagement userSessionManagement;
protected ConfidentialPortManager portManager;
- public ServletKeycloakAuthMech(KeycloakDeployment deployment, UserSessionManagement userSessionManagement, ConfidentialPortManager portManager) {
+ public ServletKeycloakAuthMech(KeycloakDeployment deployment, UndertowUserSessionManagement userSessionManagement, ConfidentialPortManager portManager) {
this.deployment = deployment;
this.userSessionManagement = userSessionManagement;
this.portManager = portManager;
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
new file mode 100755
index 0000000..30aa6c1
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
@@ -0,0 +1,54 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.PreAuthActionsHandler;
+import org.keycloak.adapters.KeycloakDeployment;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletPreAuthActionsHandler implements HttpHandler {
+
+ private static final Logger log = Logger.getLogger(ServletPreAuthActionsHandler.class);
+ protected HttpHandler next;
+ protected UndertowUserSessionManagement userSessionManagement;
+ protected KeycloakDeployment deployment;
+
+ public static class Wrapper implements HandlerWrapper {
+ protected KeycloakDeployment deployment;
+ protected UndertowUserSessionManagement userSessionManagement;
+
+
+ public Wrapper(KeycloakDeployment deployment, UndertowUserSessionManagement userSessionManagement) {
+ this.deployment = deployment;
+ this.userSessionManagement = userSessionManagement;
+ }
+
+ @Override
+ public HttpHandler wrap(HttpHandler handler) {
+ return new ServletPreAuthActionsHandler(deployment, userSessionManagement, handler);
+ }
+ }
+
+ protected ServletPreAuthActionsHandler(KeycloakDeployment deployment,
+ UndertowUserSessionManagement userSessionManagement,
+ HttpHandler next) {
+ this.next = next;
+ this.deployment = deployment;
+ this.userSessionManagement = userSessionManagement;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager());
+ PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deployment, new UndertowHttpFacade(exchange));
+ if (handler.handleRequest()) return;
+ next.handleRequest(exchange);
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
index d0111bf..f294eb7 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
@@ -16,11 +16,11 @@ import javax.servlet.http.HttpSession;
*/
public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
- protected UserSessionManagement userSessionManagement;
+ protected UndertowUserSessionManagement userSessionManagement;
public ServletRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
SecurityContext securityContext, HttpServerExchange exchange,
- UserSessionManagement userSessionManagement) {
+ UndertowUserSessionManagement userSessionManagement) {
super(facade, deployment, sslRedirectPort, securityContext, exchange);
this.userSessionManagement = userSessionManagement;
}
@@ -52,16 +52,16 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
@Override
protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
+ super.propagateKeycloakContext(account);
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
- req.setAttribute(KeycloakSecurityContext.class.getName(), account.getSession());
+ req.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
}
@Override
protected void login(KeycloakUndertowAccount account) {
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
- req.setAttribute(KeycloakSecurityContext.class.getName(), account.getSession());
HttpSession session = req.getSession(true);
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session, account.getPrincipal().getName());
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
new file mode 100755
index 0000000..4a5a4c2
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
@@ -0,0 +1,47 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.SessionManager;
+import org.keycloak.adapters.UserSessionManagement;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SessionManagementBridge implements UserSessionManagement {
+
+ protected UndertowUserSessionManagement userSessionManagement;
+ protected SessionManager sessionManager;
+
+ public SessionManagementBridge(UndertowUserSessionManagement userSessionManagement, SessionManager sessionManager) {
+ this.userSessionManagement = userSessionManagement;
+ this.sessionManager = sessionManager;
+ }
+
+ @Override
+ public int getActiveSessions() {
+ return userSessionManagement.getActiveSessions();
+ }
+
+ @Override
+ public Long getUserLoginTime(String username) {
+ return userSessionManagement.getUserLoginTime(username);
+ }
+
+ @Override
+ public Set<String> getActiveUsers() {
+ return userSessionManagement.getActiveUsers();
+ }
+
+ @Override
+ public void logoutAll() {
+ userSessionManagement.logoutAll(sessionManager);
+ }
+
+ @Override
+ public void logout(String user) {
+ userSessionManagement.logout(sessionManager, user);
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java
new file mode 100755
index 0000000..8d641c0
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java
@@ -0,0 +1,46 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
+import org.keycloak.adapters.KeycloakDeployment;
+
+/**
+ * Bridge for authenticated Keycloak adapter actions
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowAuthenticatedActionsHandler implements HttpHandler {
+ private static final Logger log = Logger.getLogger(UndertowAuthenticatedActionsHandler.class);
+ protected KeycloakDeployment deployment;
+ protected HttpHandler next;
+
+ public static class Wrapper implements HandlerWrapper {
+ protected KeycloakDeployment deployment;
+
+ public Wrapper(KeycloakDeployment deployment) {
+ this.deployment = deployment;
+ }
+
+ @Override
+ public HttpHandler wrap(HttpHandler handler) {
+ return new UndertowAuthenticatedActionsHandler(deployment, handler);
+ }
+ }
+
+
+ protected UndertowAuthenticatedActionsHandler(KeycloakDeployment deployment, HttpHandler next) {
+ this.deployment = deployment;
+ this.next = next;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new UndertowHttpFacade(exchange));
+ if (handler.handledRequest()) return;
+ next.handleRequest(exchange);
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
index d08864d..f610443 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -3,11 +3,18 @@ package org.keycloak.adapters.undertow;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.Cookie;
import io.undertow.server.handlers.CookieImpl;
+import io.undertow.util.AttachmentKey;
+import io.undertow.util.Headers;
import io.undertow.util.HttpString;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.HttpFacade;
import org.keycloak.util.KeycloakUriBuilder;
import javax.security.cert.X509Certificate;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.Deque;
import java.util.List;
import java.util.Map;
@@ -17,6 +24,8 @@ import java.util.Map;
* @version $Revision: 1 $
*/
public class UndertowHttpFacade implements HttpFacade {
+ public static final AttachmentKey<KeycloakSecurityContext> KEYCLOAK_SECURITY_CONTEXT_KEY = AttachmentKey.create(KeycloakSecurityContext.class);
+
protected HttpServerExchange exchange;
protected RequestFacade requestFacade = new RequestFacade();
protected ResponseFacade responseFacade = new ResponseFacade();
@@ -57,6 +66,23 @@ public class UndertowHttpFacade implements HttpFacade {
public List<String> getHeaders(String name) {
return exchange.getRequestHeaders().get(name);
}
+
+ @Override
+ public String getMethod() {
+ return exchange.getRequestMethod().toString();
+ }
+
+
+
+ @Override
+ public String getHeader(String name) {
+ return exchange.getRequestHeaders().getFirst(name);
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return exchange.getInputStream();
+ }
}
protected class ResponseFacade implements Response {
@@ -95,6 +121,24 @@ public class UndertowHttpFacade implements HttpFacade {
}
@Override
+ public OutputStream getOutputStream() {
+ return exchange.getOutputStream();
+ }
+
+ @Override
+ public void sendError(int code, String message) {
+ exchange.setResponseCode(code);
+ exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html");
+ try {
+ exchange.getOutputStream().write(message.getBytes());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ exchange.endExchange();
+ }
+
+
+ @Override
public void end() {
exchange.endExchange();
}
@@ -115,6 +159,11 @@ public class UndertowHttpFacade implements HttpFacade {
}
@Override
+ public KeycloakSecurityContext getSecurityContext() {
+ return exchange.getAttachment(KEYCLOAK_SECURITY_CONTEXT_KEY);
+ }
+
+ @Override
public X509Certificate[] getCertificateChain() {
X509Certificate[] chain = new X509Certificate[0];
try {
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
index 14b4f72..89b2e11 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
@@ -25,6 +25,7 @@ public class UndertowRequestAuthenticator extends RequestAuthenticator {
}
protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
+ exchange.putAttachment(UndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY, account.getKeycloakSecurityContext());
}
@Override
@@ -41,6 +42,7 @@ public class UndertowRequestAuthenticator extends RequestAuthenticator {
protected void completeOAuthAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session) {
KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, session, deployment);
securityContext.authenticationComplete(account, "KEYCLOAK", false);
+ propagateKeycloakContext(account);
login(account);
}