From 633ddb70e7eb58dd345a834fd8eae5f666331b9e Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 5 Mar 2014 15:00:21 -0500 Subject: [PATCH] session mgmt as7 --- .../as7/KeycloakAuthenticatorValve.java | 131 ++++++++++++++---- .../adapters/as7/UserSessionManagement.java | 111 ++++++++++----- .../undertow/ServletAdminActionsHandler.java | 44 +++--- 3 files changed, 199 insertions(+), 87 deletions(-) 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 c9470db179..90c8d9f85f 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 @@ -20,7 +20,12 @@ import org.keycloak.adapters.RefreshableKeycloakSession; import org.keycloak.adapters.ResourceMetadata; import org.keycloak.adapters.as7.config.CatalinaAdapterConfigLoader; 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; +import org.keycloak.representations.adapters.action.SessionStatsAction; +import org.keycloak.representations.adapters.action.UserStats; +import org.keycloak.representations.adapters.action.UserStatsAction; import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.adapters.config.RealmConfiguration; import org.keycloak.adapters.config.RealmConfigurationLoader; @@ -35,7 +40,9 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; /** @@ -99,6 +106,21 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif 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); + return; } checkKeycloakSession(request); super.invoke(request, response); @@ -136,63 +158,112 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif String token = StreamUtil.readString(request.getInputStream()); if (token == null) { log.warn("admin request failed, no token"); - response.sendError(HttpServletResponse.SC_FORBIDDEN, "no token"); + response.sendError(403, "no token"); return null; } JWSInput input = new JWSInput(token); boolean verified = false; try { - verified = RSAProvider.verify(input, resourceMetadata.getRealmKey()); + verified = RSAProvider.verify(input, realmConfiguration.getMetadata().getRealmKey()); } catch (Exception ignore) { } if (!verified) { log.warn("admin request failed, unable to verify token"); - response.sendError(HttpServletResponse.SC_FORBIDDEN, "verification failed"); + response.sendError(403, "verification failed"); return null; } return input; } - protected void pushNotBefore(JWSInput token, HttpServletResponse response) throws IOException { - try { - log.info("->> pushNotBefore: "); - PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class); - if (action.isExpired()) { - log.warn("admin request failed, expired token"); - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Expired token"); - return; - } - if (!resourceMetadata.getResourceName().equals(action.getResource())) { - log.warn("Resource name does not match"); - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Resource name does not match"); - return; - } - realmConfiguration.setNotBefore(action.getNotBefore()); - } catch (Exception e) { - log.warn("failed to logout", e); - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to logout"); + 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 (!resourceMetadata.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; + } + realmConfiguration.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 list = new HashMap(); + 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 (action.isExpired()) { - log.warn("admin request failed, expired token"); - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Expired token"); + if (!validateAction(response, action)) { return; } - if (!resourceMetadata.getResourceName().equals(action.getResource())) { - log.warn("Resource name does not match"); - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Resource name does not match"); - return; - - } String user = action.getUser(); if (user != null) { log.debug("logout of session for: " + user); diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java index 0d18f1ac35..5e829bf2ed 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java @@ -8,8 +8,10 @@ import org.jboss.logging.Logger; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -20,19 +22,63 @@ import java.util.concurrent.ConcurrentHashMap; */ public class UserSessionManagement implements SessionListener { private static final Logger log = Logger.getLogger(UserSessionManagement.class); - protected ConcurrentHashMap> userSessionMap = new ConcurrentHashMap>(); + 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; + } + } + + public int getNumUserLogins() { + return userSessionMap.size(); + } + + 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 + */ + public Long getUserLoginTime(String username) { + UserSessions sessions = userSessionMap.get(username); + if (sessions == null) return null; + return sessions.getLoggedIn(); + } + + public Set getActiveUsers() { + HashSet set = new HashSet(); + set.addAll(userSessionMap.keySet()); + return set; + } + protected void login(Session session, String username) { - Map map = userSessionMap.get(username); - if (map == null) { - final Map value = new HashMap(); - map = userSessionMap.putIfAbsent(username, value); - if (map == null) { - map = value; + synchronized (userSessionMap) { + UserSessions userSessions = userSessionMap.get(username); + if (userSessions == null) { + userSessions = new UserSessions(); + userSessionMap.put(username, userSessions); } - } - synchronized (map) { - map.put(session.getId(), session); + userSessions.getSessions().put(session.getId(), session); } session.addSessionListener(this); } @@ -43,32 +89,24 @@ public class UserSessionManagement implements SessionListener { for (String user : users) logout(user); } - public void logoutAllBut(String but) { - List users = new ArrayList(); - users.addAll(userSessionMap.keySet()); - for (String user : users) { - if (!but.equals(user)) logout(user); - } - } - - public void logout(String user) { log.debug("logoutUser: " + user); - Map map = userSessionMap.remove(user); - if (map == null) { + UserSessions sessions = null; + synchronized (userSessionMap) { + sessions = userSessionMap.remove(user); + + } + if (sessions == null) { log.debug("no session for user: " + user); return; + } log.debug("found session for user"); - synchronized (map) { - for (Session session : map.values()) { - log.debug("invalidating session for user: " + user); - session.setPrincipal(null); - session.setAuthType(null); - session.getSession().invalidate(); - } + for (Session session : sessions.getSessions().values()) { + session.setPrincipal(null); + session.setAuthType(null); + session.getSession().invalidate(); } - } public void sessionEvent(SessionEvent event) { @@ -85,13 +123,14 @@ public class UserSessionManagement implements SessionListener { session.setAuthType(null); String username = principal.getUserPrincipal().getName(); - Map map = userSessionMap.get(username); - if (map == null) return; - synchronized (map) { - map.remove(session.getId()); - if (map.isEmpty()) userSessionMap.remove(username); + 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/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java index 092b99bbb5..0e329b45ac 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java @@ -69,27 +69,6 @@ public class ServletAdminActionsHandler implements HttpHandler { this.realmConfig = realmConfig; } - protected JWSInput verifyAdminRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { - String token = StreamUtil.readString(request.getInputStream()); - if (token == null) { - log.warn("admin request failed, no token"); - response.sendError(StatusCodes.FORBIDDEN, "no token"); - return null; - } - - JWSInput input = new JWSInput(token); - boolean verified = false; - try { - verified = RSAProvider.verify(input, realmConfig.getMetadata().getRealmKey()); - } catch (Exception ignore) { - } - if (!verified) { - log.warn("admin request failed, unable to verify token"); - response.sendError(StatusCodes.FORBIDDEN, "verification failed"); - return null; - } - return input; - } @@ -135,6 +114,29 @@ public class ServletAdminActionsHandler implements HttpHandler { return; } + protected JWSInput verifyAdminRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { + String token = StreamUtil.readString(request.getInputStream()); + if (token == null) { + log.warn("admin request failed, no token"); + response.sendError(StatusCodes.FORBIDDEN, "no token"); + return null; + } + + JWSInput input = new JWSInput(token); + boolean verified = false; + try { + verified = RSAProvider.verify(input, realmConfig.getMetadata().getRealmKey()); + } catch (Exception ignore) { + } + if (!verified) { + log.warn("admin request failed, unable to verify token"); + response.sendError(StatusCodes.FORBIDDEN, "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());