session stats

This commit is contained in:
Bill Burke 2014-03-04 15:52:27 -05:00
parent 06288fa07b
commit 2d86b29b6c
20 changed files with 464 additions and 58 deletions

View file

@ -147,14 +147,21 @@ module.factory('ServerInfo', function($resource) {
});
module.factory('User', function($resource) {
return $resource('/auth/rest/admin/realms/:realm/users/:userId', {
realm : '@realm',
userId : '@userId'
}, {
return $resource('/auth/rest/admin/realms/:realm/users/:userId', {
realm : '@realm',
userId : '@userId'
}, {
update : {
method : 'PUT'
}
});
});
});
module.factory('UserSessionStats', function($resource) {
return $resource('/auth/rest/admin/realms/:realm/users/:userId/session-stats', {
realm : '@realm',
userId : '@userId'
});
});
module.factory('UserCredentials', function($resource) {
@ -241,6 +248,13 @@ module.factory('RealmPushRevocation', function($resource) {
});
});
module.factory('RealmSessionStats', function($resource) {
return $resource('/auth/rest/admin/realms/:realm/session-stats', {
realm : '@realm'
});
});
module.factory('RoleApplicationComposites', function($resource) {
return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role/composites/applications/:application', {
realm : '@realm',
@ -456,6 +470,7 @@ module.factory('ApplicationRole', function($resource) {
}
});
});
module.factory('ApplicationClaims', function($resource) {
return $resource('/auth/rest/admin/realms/:realm/applications/:application/claims', {
realm : '@realm',
@ -467,6 +482,13 @@ module.factory('ApplicationClaims', function($resource) {
});
});
module.factory('ApplicationSessionStats', function($resource) {
return $resource('/auth/rest/admin/realms/:realm/applications/:application/session-stats', {
realm : '@realm',
application : "@application"
});
});
module.factory('ApplicationPushRevocation', function($resource) {
return $resource('/auth/rest/admin/realms/:realm/applications/:application/push-revocation', {
realm : '@realm',

View file

@ -9,6 +9,8 @@ public interface AdapterConstants {
// URL endpoints
public static final String K_LOGOUT = "k_logout";
public static final String K_PUSH_NOT_BEFORE = "k_push_not_before";
public static final String K_GET_USER_STATS = "k_get_user_stats";
public static final String K_GET_SESSION_STATS = "k_get_session_stats";
public static final String K_QUERY_BEARER_TOKEN = "k_query_bearer_token";
// This param name is defined again in Keycloak Subsystem class

View file

@ -8,18 +8,20 @@ import org.codehaus.jackson.annotate.JsonIgnore;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AdminAction {
public abstract class AdminAction {
protected String id;
protected int expiration;
protected String resource;
protected String action;
public AdminAction() {
}
public AdminAction(String id, int expiration, String resource) {
public AdminAction(String id, int expiration, String resource, String action) {
this.id = id;
this.expiration = expiration;
this.resource = resource;
this.action = action;
}
public String getId() {
@ -56,4 +58,6 @@ public class AdminAction {
public void setResource(String resource) {
this.resource = resource;
}
public abstract boolean validate();
}

View file

@ -5,13 +5,14 @@ package org.keycloak.representations.adapters.action;
* @version $Revision: 1 $
*/
public class LogoutAction extends AdminAction {
public static final String LOGOUT = "LOGOUT";
protected String user;
public LogoutAction() {
}
public LogoutAction(String id, int expiration, String resource, String user) {
super(id, expiration, resource);
super(id, expiration, resource, LOGOUT);
this.user = user;
}
@ -22,4 +23,9 @@ public class LogoutAction extends AdminAction {
public void setUser(String user) {
this.user = user;
}
@Override
public boolean validate() {
return LOGOUT.equals(action);
}
}

View file

@ -6,13 +6,14 @@ package org.keycloak.representations.adapters.action;
*/
public class PushNotBeforeAction extends AdminAction {
public static final String PUSH_NOT_BEFORE = "PUSH_NOT_BEFORE";
protected int notBefore;
public PushNotBeforeAction() {
}
public PushNotBeforeAction(String id, int expiration, String resource, int notBefore) {
super(id, expiration, resource);
super(id, expiration, resource, PUSH_NOT_BEFORE);
this.notBefore = notBefore;
}
@ -23,4 +24,10 @@ public class PushNotBeforeAction extends AdminAction {
public void setNotBefore(int notBefore) {
this.notBefore = notBefore;
}
@Override
public boolean validate() {
return PUSH_NOT_BEFORE.equals(action);
}
}

View file

@ -0,0 +1,38 @@
package org.keycloak.representations.adapters.action;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SessionStats {
protected int activeSessions;
protected int activeUsers;
protected Set<String> users;
public int getActiveSessions() {
return activeSessions;
}
public void setActiveSessions(int activeSessions) {
this.activeSessions = activeSessions;
}
public int getActiveUsers() {
return activeUsers;
}
public void setActiveUsers(int activeUsers) {
this.activeUsers = activeUsers;
}
public Set<String> getUsers() {
return users;
}
public void setUsers(Set<String> users) {
this.users = users;
}
}

View file

@ -0,0 +1,35 @@
package org.keycloak.representations.adapters.action;
/**
* Query session stats.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SessionStatsAction extends AdminAction {
public static final String SESSION_STATS = "SESSION_STATS";
protected boolean listUsers;
public SessionStatsAction() {
}
public SessionStatsAction(String id, int expiration, String resource) {
super(id, expiration, resource, SESSION_STATS);
}
public boolean isListUsers() {
return listUsers;
}
public void setListUsers(boolean listUsers) {
this.listUsers = listUsers;
}
@Override
public boolean validate() {
return SESSION_STATS.equals(action);
}
}

View file

@ -0,0 +1,26 @@
package org.keycloak.representations.adapters.action;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserStats {
protected boolean loggedIn;
protected long whenLoggedIn;
public boolean isLoggedIn() {
return loggedIn;
}
public void setLoggedIn(boolean loggedIn) {
this.loggedIn = loggedIn;
}
public long getWhenLoggedIn() {
return whenLoggedIn;
}
public void setWhenLoggedIn(long whenLoggedIn) {
this.whenLoggedIn = whenLoggedIn;
}
}

View file

@ -0,0 +1,31 @@
package org.keycloak.representations.adapters.action;
/**
* Query session stats.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserStatsAction extends AdminAction {
public static final String USER_STATS = "USER_STATS";
protected String user;
public UserStatsAction() {
}
public UserStatsAction(String id, int expiration, String resource, String user) {
super(id, expiration, resource, USER_STATS);
this.user = user;
}
public String getUser() {
return user;
}
@Override
public boolean validate() {
return USER_STATS.equals(action);
}
}

View file

@ -6,6 +6,7 @@ import org.codehaus.jackson.map.annotate.JsonSerialize;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Utility class to handle simple JSON serializable for Keycloak.
@ -25,6 +26,11 @@ public class JsonSerialization {
prettyMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
}
public static void writeValueToStream(OutputStream os, Object obj) throws IOException {
mapper.writeValue(os, obj);
}
public static String writeValueAsString(Object obj) throws IOException {
return mapper.writeValueAsString(obj);
}

View file

@ -52,6 +52,7 @@ public class RefreshableKeycloakSession extends KeycloakSecurityContext {
}
public void refreshExpiredToken() {
log.info("checking whether to refresh.");
if (isActive()) return;
if (this.realmConfiguration == null || refreshToken == null) return; // Might be serialized in HttpSession?
@ -75,6 +76,10 @@ public class RefreshableKeycloakSession extends KeycloakSecurityContext {
} catch (VerificationException e) {
log.error("failed verification of token");
}
if (response.getNotBeforePolicy() > realmConfiguration.getNotBefore()) {
realmConfiguration.setNotBefore(response.getNotBeforePolicy());
}
this.token = token;
this.refreshToken = response.getRefreshToken();
this.tokenString = tokenString;

View file

@ -156,7 +156,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
protected void pushNotBefore(JWSInput token, HttpServletResponse response) throws IOException {
try {
log.debug("->> pushNotBefore: ");
log.info("->> pushNotBefore: ");
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
if (action.isExpired()) {
log.warn("admin request failed, expired token");

View file

@ -12,12 +12,18 @@ import org.keycloak.adapters.ResourceMetadata;
import org.keycloak.adapters.config.RealmConfiguration;
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.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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -101,6 +107,12 @@ public class ServletAdminActionsHandler implements HttpHandler {
} else if (requestUri.endsWith(AdapterConstants.K_PUSH_NOT_BEFORE)) {
handlePushNotBefore(request, response);
return;
} else if (requestUri.endsWith(AdapterConstants.K_GET_SESSION_STATS)) {
handleGetSessionStats(request, response);
return;
}else if (requestUri.endsWith(AdapterConstants.K_GET_USER_STATS)) {
handleGetUserStats(request, response);
return;
} else {
next.handleRequest(exchange);
return;
@ -110,20 +122,65 @@ public class ServletAdminActionsHandler implements HttpHandler {
protected void handlePushNotBefore(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("K_PUSH_NOT_BEFORE sent");
JWSInput token = verifyAdminRequest(request, response);
if (token == null) return;
if (token == null) {
return;
}
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
if (!validateAction(response, action)) return;
realmConfig.setNotBefore(action.getNotBefore());
return;
}
protected boolean validateAction(HttpServletResponse response, AdminAction action) throws IOException {
if (!action.validate()) {
log.warn("admin request failed, not validated");
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;
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;
return false;
}
realmConfig.setNotBefore(action.getNotBefore());
return true;
}
protected void handleGetSessionStats(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("K_GET_SESSION_STATS sent");
JWSInput token = verifyAdminRequest(request, response);
if (token == null) return;
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()) stats.setUsers(userSessionManagement.getActiveUsers());
response.setStatus(200);
JsonSerialization.writeValueToStream(response.getOutputStream(), stats);
return;
}
protected void handleGetUserStats(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("K_GET_USER_STATS sent");
JWSInput token = verifyAdminRequest(request, response);
if (token == null) return;
UserStatsAction action = JsonSerialization.readValue(token.getContent(), UserStatsAction.class);
if (!validateAction(response, action)) return;
UserStats stats = new UserStats();
Long loginTime = userSessionManagement.getUserLoginTime(action.getUser());
if (loginTime != null) {
stats.setLoggedIn(true);
stats.setWhenLoggedIn(loginTime);
} else {
stats.setLoggedIn(false);
}
response.setStatus(200);
JsonSerialization.writeValueToStream(response.getOutputStream(), stats);
return;
}
}

View file

@ -31,7 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class UserSessionManagement implements SessionListener {
private static final Logger log = Logger.getLogger(UserSessionManagement.class);
private static final String AUTH_SESSION_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession";
protected ConcurrentHashMap<String, Set<String>> userSessionMap = new ConcurrentHashMap<String, Set<String>>();
protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
protected RealmConfiguration realmInfo;
@ -39,6 +39,51 @@ public class UserSessionManagement implements SessionListener {
this.realmInfo = realmInfo;
}
public static class UserSessions {
protected Set<String> sessionIds = new HashSet<String>();
protected long loggedIn = System.currentTimeMillis();
public Set<String> getSessionIds() {
return sessionIds;
}
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.getSessionIds().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<String> getActiveUsers() {
HashSet<String> set = new HashSet<String>();
set.addAll(userSessionMap.keySet());
return set;
}
public void remoteLogout(JWSInput token, SessionManager manager, HttpServletResponse response) throws IOException {
try {
log.info("->> remoteLogout: ");
@ -77,28 +122,22 @@ public class UserSessionManagement implements SessionListener {
protected void addAuthenticatedSession(String username, String sessionId) {
synchronized (userSessionMap) {
Set<String> map = userSessionMap.get(username);
if (map == null) {
final Set<String> value = new HashSet<String>();
map = userSessionMap.putIfAbsent(username, value);
if (map == null) {
map = value;
}
UserSessions sessions = userSessionMap.get(username);
if (sessions == null) {
UserSessions session = new UserSessions();
userSessionMap.put(username, session);
}
synchronized (map) {
map.add(sessionId);
}
sessions.getSessionIds().add(sessionId);
}
}
protected void removeAuthenticatedSession(String sessionId, String username) {
synchronized (userSessionMap) {
Set<String> map = userSessionMap.get(username);
if (map == null) return;
synchronized (map) {
map.remove(sessionId);
if (map.isEmpty()) userSessionMap.remove(username);
UserSessions sessions = userSessionMap.get(username);
if (sessions == null) return;
sessions.getSessionIds().remove(sessionId);
if (sessions.getSessionIds().isEmpty()) {
userSessionMap.remove(username);
}
}
}
@ -119,24 +158,24 @@ public class UserSessionManagement implements SessionListener {
public void logout(SessionManager manager, String user) {
log.info("logoutUser: " + user);
Set<String> map = userSessionMap.remove(user);
if (map == null) {
UserSessions sessions = null;
synchronized (userSessionMap) {
sessions = userSessionMap.remove(user);
}
if (sessions == null) {
log.info("no session for user: " + user);
return;
}
log.info("found session for user");
synchronized (map) {
for (String id : map) {
log.debug("invalidating session for user: " + user);
Session session = manager.getSession(id);
try {
session.invalidate(null);
} catch (Exception e) {
log.warn("Session already invalidated.");
}
for (String id : sessions.getSessionIds()) {
log.debug("invalidating session for user: " + user);
Session session = manager.getSession(id);
try {
session.invalidate(null);
} catch (Exception e) {
log.warn("Session already invalidated.");
}
}
}
@Override

View file

@ -7,8 +7,13 @@ import org.keycloak.TokenIdGenerator;
import org.keycloak.adapters.AdapterConstants;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
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 javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;
@ -21,6 +26,74 @@ import java.util.List;
public class ResourceAdminManager {
protected static Logger logger = Logger.getLogger(ResourceAdminManager.class);
public SessionStats getSessionStats(RealmModel realm, ApplicationModel application, boolean users) {
ResteasyClient client = new ResteasyClientBuilder()
.disableTrustManager() // todo fix this, should have a trust manager or a good default
.build();
try {
return getSessionStats(realm, application, users, client);
} finally {
client.close();
}
}
public SessionStats getSessionStats(RealmModel realm, ApplicationModel application, boolean users, ResteasyClient client) {
String managementUrl = application.getManagementUrl();
if (managementUrl != null) {
SessionStatsAction adminAction = new SessionStatsAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, application.getName());
adminAction.setListUsers(users);
String token = new TokenManager().encodeToken(realm, adminAction);
logger.info("session stats for application: {0} url: {1}", application.getName(), managementUrl);
Response response = client.target(managementUrl).path(AdapterConstants.K_GET_SESSION_STATS).request().post(Entity.text(token));
if (response.getStatus() != 200) {
logger.warn("Failed to get stats: " + response.getStatus());
return null;
}
SessionStats stats = response.readEntity(SessionStats.class);
return stats;
} else {
logger.info("no management url.");
return null;
}
}
public UserStats getUserStats(RealmModel realm, ApplicationModel application, UserModel user) {
ResteasyClient client = new ResteasyClientBuilder()
.disableTrustManager() // todo fix this, should have a trust manager or a good default
.build();
try {
return getUserStats(realm, application, user, client);
} finally {
client.close();
}
}
public UserStats getUserStats(RealmModel realm, ApplicationModel application, UserModel user, ResteasyClient client) {
String managementUrl = application.getManagementUrl();
if (managementUrl != null) {
UserStatsAction adminAction = new UserStatsAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, application.getName(), user.getId());
String token = new TokenManager().encodeToken(realm, adminAction);
logger.info("session stats for application: {0} url: {1}", application.getName(), managementUrl);
Response response = client.target(managementUrl).path(AdapterConstants.K_GET_USER_STATS).request().post(Entity.text(token));
if (response.getStatus() != 200) {
logger.warn("Failed to get stats: " + response.getStatus());
return null;
}
UserStats stats = response.readEntity(UserStats.class);
return stats;
} else {
logger.info("no management url.");
return null;
}
}
public void singleLogOut(RealmModel realm, String user) {
ResteasyClient client = new ResteasyClientBuilder()
.disableTrustManager() // todo fix this, should have a trust manager or a good default

View file

@ -366,18 +366,20 @@ public class TokenManager {
return encodedToken;
}
public AccessTokenResponseBuilder responseBuilder(RealmModel realm) {
return new AccessTokenResponseBuilder(realm);
public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client) {
return new AccessTokenResponseBuilder(realm, client);
}
public class AccessTokenResponseBuilder {
RealmModel realm;
ClientModel client;
AccessToken accessToken;
RefreshToken refreshToken;
IDToken idToken;
public AccessTokenResponseBuilder(RealmModel realm) {
public AccessTokenResponseBuilder(RealmModel realm, ClientModel client) {
this.realm = realm;
this.client = client;
}
public AccessTokenResponseBuilder accessToken(AccessToken accessToken) {
@ -465,7 +467,9 @@ public class TokenManager {
String encodedToken = new JWSBuilder().jsonContent(refreshToken).rsa256(realm.getPrivateKey());
res.setRefreshToken(encodedToken);
}
res.setNotBeforePolicy(realm.getNotBefore());
int notBefore = realm.getNotBefore();
if (client.getNotBefore() > notBefore) notBefore = client.getNotBefore();
res.setNotBeforePolicy(notBefore);
return res;
}
}

View file

@ -160,7 +160,7 @@ public class TokenService {
throw new NotAuthorizedException("Auth failed");
}
String scope = form.getFirst("scope");
AccessTokenResponse res = tokenManager.responseBuilder(realm)
AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
.generateAccessToken(scope, client, user)
.generateIDToken()
.build();
@ -190,7 +190,7 @@ public class TokenService {
throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(), e);
}
AccessTokenResponse res = tokenManager.responseBuilder(realm)
AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
.accessToken(accessToken)
.generateIDToken()
.generateRefreshToken().build();
@ -413,7 +413,7 @@ public class TokenService {
.build();
}
logger.debug("accessRequest SUCCESS");
AccessTokenResponse res = tokenManager.responseBuilder(realm)
AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
.accessToken(accessCode.getToken())
.generateIDToken()
.generateRefreshToken().build();

View file

@ -6,6 +6,7 @@ import org.keycloak.models.ApplicationModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.representations.adapters.action.SessionStats;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.ApplicationManager;
@ -28,6 +29,8 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
@ -193,6 +196,15 @@ public class ApplicationResource {
new ResourceAdminManager().pushApplicationRevocationPolicy(realm, application);
}
@Path("session-stats")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public SessionStats getSessionStats() {
return new ResourceAdminManager().getSessionStats(realm, application, true);
}

View file

@ -2,8 +2,10 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.adapters.action.SessionStats;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.ModelToRepresentation;
import org.keycloak.services.managers.RealmManager;
@ -13,6 +15,10 @@ import org.keycloak.services.managers.TokenManager;
import javax.ws.rs.*;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -112,4 +118,19 @@ public class RealmAdminResource {
new ResourceAdminManager().pushRealmRevocationPolicy(realm);
}
@Path("session-stats")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public Map<String,SessionStats> getSessionStats() {
Map<String, SessionStats> stats = new HashMap<String, SessionStats>();
for (ApplicationModel applicationModel : realm.getApplications()) {
if (applicationModel.getManagementUrl() == null) continue;
SessionStats appStats = new ResourceAdminManager().getSessionStats(realm, applicationModel, false);
stats.put(applicationModel.getName(), appStats);
}
return stats;
}
}

View file

@ -10,6 +10,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.adapters.action.SessionStats;
import org.keycloak.representations.adapters.action.UserStats;
import org.keycloak.representations.idm.*;
import org.keycloak.services.email.EmailException;
import org.keycloak.services.email.EmailSender;
@ -17,6 +19,7 @@ import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.ModelToRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.Urls;
@ -34,6 +37,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.ArrayList;
@ -138,12 +142,32 @@ public class UsersResource {
auth.requireView();
UserModel user = realm.getUser(username);
if (user == null || !isUser(user)) {
if (user == null) {
throw new NotFoundException();
}
return ModelToRepresentation.toRepresentation(user);
}
@Path("{username}/session-stats")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public Map<String,UserStats> getSessionStats(final @PathParam("username") String username) {
auth.requireView();
UserModel user = realm.getUser(username);
if (user == null) {
throw new NotFoundException();
}
Map<String, UserStats> stats = new HashMap<String, UserStats>();
for (ApplicationModel applicationModel : realm.getApplications()) {
if (applicationModel.getManagementUrl() == null) continue;
UserStats appStats = new ResourceAdminManager().getUserStats(realm, applicationModel, user);
stats.put(applicationModel.getName(), appStats);
}
return stats;
}
@Path("{username}")
@DELETE
@NoCache
@ -191,17 +215,11 @@ public class UsersResource {
}
for (UserModel user : userModels) {
if (isUser(user)) {
results.add(ModelToRepresentation.toRepresentation(user));
}
results.add(ModelToRepresentation.toRepresentation(user));
}
return results;
}
private boolean isUser(UserModel user) {
return true;
}
@Path("{username}/role-mappings")
@GET
@Produces("application/json")