client user-session association
This commit is contained in:
parent
ae4d83107a
commit
ab8de6ba25
50 changed files with 993 additions and 264 deletions
|
@ -0,0 +1,67 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UserSessionRepresentation {
|
||||
private String id;
|
||||
private String user;
|
||||
private String ipAddress;
|
||||
private long start;
|
||||
private Set<String> applications = new HashSet<String>();
|
||||
private Map<String, String> clients = new HashMap<String, String>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(long start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public Set<String> getApplications() {
|
||||
return applications;
|
||||
}
|
||||
|
||||
public void setApplications(Set<String> applications) {
|
||||
this.applications = applications;
|
||||
}
|
||||
|
||||
public Map<String, String> getClients() {
|
||||
return clients;
|
||||
}
|
||||
|
||||
public void setClients(Map<String, String> clients) {
|
||||
this.clients = clients;
|
||||
}
|
||||
}
|
|
@ -3,5 +3,6 @@
|
|||
"resource" : "database-service",
|
||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"bearer-only" : true,
|
||||
"ssl-not-required": true,
|
||||
"enable-cors": true
|
||||
}
|
||||
|
|
|
@ -3,6 +3,5 @@
|
|||
"resource" : "database-service",
|
||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"bearer-only" : true,
|
||||
"enable-cors" : true
|
||||
|
||||
"ssl-not-required": true
|
||||
}
|
||||
|
|
|
@ -290,8 +290,8 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
user : function(UserLoader) {
|
||||
return UserLoader();
|
||||
},
|
||||
stats : function(UserSessionStatsLoader) {
|
||||
return UserSessionStatsLoader();
|
||||
sessions : function(UserSessionsLoader) {
|
||||
return UserSessionsLoader();
|
||||
}
|
||||
},
|
||||
controller : 'UserSessionsCtrl'
|
||||
|
@ -436,8 +436,8 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
application : function(ApplicationLoader) {
|
||||
return ApplicationLoader();
|
||||
},
|
||||
stats : function(ApplicationSessionStatsLoader) {
|
||||
return ApplicationSessionStatsLoader();
|
||||
sessionCount : function(ApplicationSessionCountLoader) {
|
||||
return ApplicationSessionCountLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationSessionsCtrl'
|
||||
|
@ -705,8 +705,8 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
stats : function(RealmSessionStatsLoader) {
|
||||
return RealmSessionStatsLoader();
|
||||
stats : function(RealmApplicationSessionStatsLoader) {
|
||||
return RealmApplicationSessionStatsLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmSessionStatsCtrl'
|
||||
|
|
|
@ -43,15 +43,12 @@ module.controller('ApplicationCredentialsCtrl', function($scope, $location, real
|
|||
});
|
||||
});
|
||||
|
||||
module.controller('ApplicationSessionsCtrl', function($scope, realm, stats, application,
|
||||
ApplicationLogoutUser,
|
||||
ApplicationLogoutAll,
|
||||
ApplicationSessionStats,
|
||||
ApplicationSessionStatsWithUsers,
|
||||
module.controller('ApplicationSessionsCtrl', function($scope, realm, sessionCount, application,
|
||||
ApplicationUserSessions,
|
||||
$location, Dialog, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.stats = stats;
|
||||
$scope.users = {};
|
||||
$scope.count = sessionCount.count;
|
||||
$scope.sessions = [];
|
||||
$scope.application = application;
|
||||
|
||||
$scope.toDate = function(val) {
|
||||
|
@ -59,27 +56,11 @@ module.controller('ApplicationSessionsCtrl', function($scope, realm, stats, appl
|
|||
};
|
||||
|
||||
$scope.loadUsers = function() {
|
||||
ApplicationSessionStatsWithUsers.get({ realm : realm.realm, application: $scope.application.name }, function(updated) {
|
||||
$scope.stats = updated;
|
||||
$scope.users = updated.users;
|
||||
ApplicationUserSessions.query({ realm : realm.realm, application: $scope.application.name }, function(updated) {
|
||||
$scope.count = updated.length;
|
||||
$scope.sessions = updated;
|
||||
})
|
||||
};
|
||||
|
||||
$scope.logoutAll = function() {
|
||||
ApplicationLogoutAll.save({realm : realm.realm, application: $scope.application.name}, function () {
|
||||
Notifications.success('Logged out all users');
|
||||
$scope.loadUsers();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.logoutUser = function(user) {
|
||||
console.log('Trying to logout user: ' + user);
|
||||
ApplicationLogoutUser.save({realm : realm.realm, application: $scope.application.name, user: user}, function () {
|
||||
Notifications.success('Logged out user' + user);
|
||||
$scope.loadUsers();
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.controller('ApplicationClaimsCtrl', function($scope, realm, application, claims,
|
||||
|
|
|
@ -99,32 +99,29 @@ module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, ap
|
|||
|
||||
});
|
||||
|
||||
module.controller('UserSessionsCtrl', function($scope, realm, user, stats, UserLogout, ApplicationLogoutUser, UserSessionStats, Notifications) {
|
||||
module.controller('UserSessionsCtrl', function($scope, realm, user, sessions, UserSessions, UserLogout, UserSessionLogout, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.user = user;
|
||||
$scope.stats = stats;
|
||||
$scope.sessions = sessions;
|
||||
|
||||
$scope.logoutAll = function() {
|
||||
UserLogout.save({realm : realm.realm, user: user.username}, function () {
|
||||
Notifications.success('Logged out user in all applications');
|
||||
UserSessionStats.get({realm: realm.realm, user: user.username}, function(updated) {
|
||||
$scope.stats = updated;
|
||||
UserSessions.get({realm: realm.realm, user: user.username}, function(updated) {
|
||||
$scope.sessions = updated;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
$scope.logoutApplication = function(app) {
|
||||
console.log('log user out of app: ' + app);
|
||||
ApplicationLogoutUser.save({realm : realm.realm, application: app, user: user.username}, function () {
|
||||
Notifications.success('Logged out user from application');
|
||||
UserSessionStats.get({realm: realm.realm, user: user.username}, function(updated) {
|
||||
$scope.stats = updated;
|
||||
$scope.logoutSession = function(sessionId) {
|
||||
console.log('here in logoutSession');
|
||||
UserSessionLogout.delete({realm : realm.realm, session: sessionId}, function() {
|
||||
Notifications.success('Logged out session');
|
||||
UserSessions.get({realm: realm.realm, user: user.username}, function(updated) {
|
||||
$scope.sessions = updated;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
module.controller('UserSocialCtrl', function($scope, realm, user, socialLinks) {
|
||||
|
|
|
@ -71,6 +71,14 @@ module.factory('RealmSessionStatsLoader', function(Loader, RealmSessionStats, $r
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('RealmApplicationSessionStatsLoader', function(Loader, RealmApplicationSessionStats, $route, $q) {
|
||||
return Loader.get(RealmApplicationSessionStats, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('UserLoader', function(Loader, User, $route, $q) {
|
||||
return Loader.get(User, function() {
|
||||
return {
|
||||
|
@ -89,6 +97,15 @@ module.factory('UserSessionStatsLoader', function(Loader, UserSessionStats, $rou
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('UserSessionsLoader', function(Loader, UserSessions, $route, $q) {
|
||||
return Loader.query(UserSessions, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
user : $route.current.params.user
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('UserSocialLinksLoader', function(Loader, UserSocialLinks, $route, $q) {
|
||||
return Loader.query(UserSocialLinks, function() {
|
||||
return {
|
||||
|
@ -134,6 +151,15 @@ module.factory('ApplicationSessionStatsLoader', function(Loader, ApplicationSess
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationSessionCountLoader', function(Loader, ApplicationSessionCount, $route, $q) {
|
||||
return Loader.get(ApplicationSessionCount, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
application : $route.current.params.application
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationClaimsLoader', function(Loader, ApplicationClaims, $route, $q) {
|
||||
return Loader.get(ApplicationClaims, function() {
|
||||
return {
|
||||
|
|
|
@ -183,6 +183,20 @@ module.factory('UserSessionStats', function($resource) {
|
|||
user : '@user'
|
||||
});
|
||||
});
|
||||
module.factory('UserSessions', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/users/:user/sessions', {
|
||||
realm : '@realm',
|
||||
user : '@user'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('UserSessionLogout', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/sessions/:session', {
|
||||
realm : '@realm',
|
||||
session : '@session'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('UserLogout', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/users/:user/logout', {
|
||||
realm : '@realm',
|
||||
|
@ -347,6 +361,12 @@ module.factory('RealmSessionStats', function($resource) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('RealmApplicationSessionStats', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/application-session-stats', {
|
||||
realm : '@realm'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.factory('RoleApplicationComposites', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role/composites/applications/:application', {
|
||||
|
@ -589,6 +609,20 @@ module.factory('ApplicationSessionStatsWithUsers', function($resource) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationSessionCount', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/applications/:application/session-count', {
|
||||
realm : '@realm',
|
||||
application : "@application"
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationUserSessions', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/applications/:application/user-sessions', {
|
||||
realm : '@realm',
|
||||
application : "@application"
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationLogoutAll', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/applications/:application/logout-all', {
|
||||
realm : '@realm',
|
||||
|
|
|
@ -21,38 +21,31 @@
|
|||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="activeSessions">Active Sessions</label>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" type="text" id="activeSessions" name="activeSessions" data-ng-model="stats.activeSessions" ng-disabled="true">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="activeUsers">Active Users</label>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" type="text" id="activeUsers" name="activeUsers" data-ng-model="stats.activeUsers" ng-disabled="true">
|
||||
<input class="form-control" type="text" id="activeSessions" name="activeSessions" data-ng-model="count" ng-disabled="true">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<table class="table table-striped table-bordered" data-ng-show="stats.activeSessions > 0">
|
||||
<table class="table table-striped table-bordered" data-ng-show="count > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="3">
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-primary" ng-click="logoutAll()">Invalidate All Sessions</a>
|
||||
<a class="btn btn-primary" ng-click="loadUsers()">Show Users</a>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Login Time</th>
|
||||
<th></th>
|
||||
<th>From IP</th>
|
||||
<th>Session Start</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-ng-repeat="(user, data) in users">
|
||||
<td><a href="#/realms/{{realm.realm}}/users/{{user}}">{{user}}</a></td>
|
||||
<td>{{data.whenLoggedIn | date:'medium'}}</td>
|
||||
<td><a ng-click="logoutUser(user)">invalidate session</a> </td>
|
||||
<tr data-ng-repeat="session in sessions">
|
||||
<td><a href="#/realms/{{realm.realm}}/users/{{session.user}}">{{session.user}}</a></td>
|
||||
<td>{{session.ipAddress}}</td>
|
||||
<td>{{session.start | date:'medium'}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -24,14 +24,12 @@
|
|||
<tr>
|
||||
<th>Application</th>
|
||||
<th>Active Sessions</th>
|
||||
<th>Active Users</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-ng-repeat="(application, data) in stats">
|
||||
<td><a href="#/realms/{{realm.realm}}/applications/{{application}}/sessions">{{application}}</a></td>
|
||||
<td>{{data.activeSessions}}</td>
|
||||
<td>{{data.activeUsers}}</td>
|
||||
<td>{{data}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -18,23 +18,37 @@
|
|||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="3">
|
||||
<th class="kc-table-actions" colspan="5">
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-primary" ng-click="logoutAll()">Logout</a>
|
||||
<a class="btn btn-primary" ng-click="logoutAll()">Logout All Sessions</a>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Application</th>
|
||||
<th>IP Address</th>
|
||||
<th>Login Time</th>
|
||||
<th></th>
|
||||
<th>Applications</th>
|
||||
<th>OAuth Clients</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-ng-repeat="(application, data) in stats">
|
||||
<td><a href="#/realms/{{realm.realm}}/applications/{{application}}/sessions">{{application}}</a></td>
|
||||
<td>{{data.whenLoggedIn | date:'medium'}}</td>
|
||||
<td><a ng-click="logoutApplication(application)">invalidate session</a> </td>
|
||||
<tr data-ng-repeat="session in sessions">
|
||||
<td>{{session.ipAddress}}</td>
|
||||
<td>{{session.start | date:'medium'}}</td>
|
||||
<td><ul style="list-style: none; ">
|
||||
<li data-ng-repeat="app in session.applications">
|
||||
<a href="#/realms/{{realm.realm}}/applications/{{app}}/sessions">{{app}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td><ul style="list-style: none; ">
|
||||
<li data-ng-repeat="(clientId, clientName) in session.clients">
|
||||
<a href="#/realms/{{realm.realm}}/oauth-clients/{{clientId}}">{{clientName}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td><a ng-click="logoutSession(session.id)">logout</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -120,7 +120,9 @@ public class PreAuthActionsHandler {
|
|||
String user = action.getUser();
|
||||
if (user != null) {
|
||||
log.info("logout of session for: " + user);
|
||||
userSessionManagement.logout(user);
|
||||
userSessionManagement.logoutUser(user);
|
||||
} else if (action.getSession() != null) {
|
||||
userSessionManagement.logoutKeycloakSession(action.getSession());
|
||||
} else {
|
||||
log.info("logout of all sessions");
|
||||
if (action.getNotBefore() > deployment.getNotBefore()) {
|
||||
|
|
|
@ -15,5 +15,7 @@ public interface UserSessionManagement {
|
|||
|
||||
void logoutAll();
|
||||
|
||||
void logout(String user);
|
||||
void logoutUser(String user);
|
||||
|
||||
void logoutKeycloakSession(String id);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
|
|||
session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
|
||||
String username = securityContext.getToken().getSubject();
|
||||
log.debug("userSessionManage.login: " + username);
|
||||
userSessionManagement.login(session, username);
|
||||
userSessionManagement.login(session, username, securityContext.getToken().getSessionState());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.adapters.UserSessionManagement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -23,31 +24,21 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement {
|
||||
private static final Logger log = Logger.getLogger(CatalinaUserSessionManagement.class);
|
||||
protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
|
||||
protected ConcurrentHashMap<String, UserSessions> keycloakSessionMap = new ConcurrentHashMap<String, UserSessions>();
|
||||
|
||||
public static class UserSessions {
|
||||
protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
|
||||
protected String user;
|
||||
protected long loggedIn = System.currentTimeMillis();
|
||||
|
||||
|
||||
public Map<String, Session> getSessions() {
|
||||
return sessions;
|
||||
}
|
||||
|
||||
protected Map<String, String> keycloakSessionToHttpSession = new HashMap<String, String>();
|
||||
protected Map<String, String> httpSessionToKeycloakSession = new HashMap<String, String>();
|
||||
protected Map<String, Session> sessions = new HashMap<String, Session>();
|
||||
public long getLoggedIn() {
|
||||
return loggedIn;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveSessions() {
|
||||
int active = 0;
|
||||
synchronized (userSessionMap) {
|
||||
for (UserSessions sessions : userSessionMap.values()) {
|
||||
active += sessions.getSessions().size();
|
||||
}
|
||||
|
||||
}
|
||||
return active;
|
||||
public synchronized int getActiveSessions() {
|
||||
return keycloakSessionMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,60 +47,85 @@ public class CatalinaUserSessionManagement implements SessionListener, UserSessi
|
|||
* @return null if user not logged in
|
||||
*/
|
||||
@Override
|
||||
public Long getUserLoginTime(String username) {
|
||||
public synchronized Long getUserLoginTime(String username) {
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions == null) return null;
|
||||
return sessions.getLoggedIn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getActiveUsers() {
|
||||
public synchronized Set<String> getActiveUsers() {
|
||||
HashSet<String> set = new HashSet<String>();
|
||||
set.addAll(userSessionMap.keySet());
|
||||
return set;
|
||||
}
|
||||
|
||||
|
||||
protected void login(Session session, String username) {
|
||||
synchronized (userSessionMap) {
|
||||
UserSessions userSessions = userSessionMap.get(username);
|
||||
if (userSessions == null) {
|
||||
userSessions = new UserSessions();
|
||||
userSessionMap.put(username, userSessions);
|
||||
}
|
||||
userSessions.getSessions().put(session.getId(), session);
|
||||
public synchronized void login(Session session, String username, String keycloakSessionId) {
|
||||
String sessionId = session.getId();
|
||||
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions == null) {
|
||||
sessions = new UserSessions();
|
||||
sessions.user = username;
|
||||
userSessionMap.put(username, sessions);
|
||||
}
|
||||
keycloakSessionMap.put(keycloakSessionId, sessions);
|
||||
sessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
|
||||
sessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
|
||||
session.addSessionListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logoutAll() {
|
||||
List<String> users = new ArrayList<String>();
|
||||
users.addAll(userSessionMap.keySet());
|
||||
for (String user : users) logout(user);
|
||||
for (String user : userSessionMap.keySet()) logoutUser(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(String user) {
|
||||
public void logoutUser(String user) {
|
||||
log.debug("logoutUser: " + user);
|
||||
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");
|
||||
for (Session session : sessions.getSessions().values()) {
|
||||
for (Map.Entry<String, String> entry : sessions.httpSessionToKeycloakSession.entrySet()) {
|
||||
log.debug("invalidating session for user: " + user);
|
||||
String sessionId = entry.getKey();
|
||||
String keycloakSessionId = entry.getValue();
|
||||
Session session = sessions.sessions.get(sessionId);
|
||||
session.setPrincipal(null);
|
||||
session.setAuthType(null);
|
||||
session.getSession().invalidate();
|
||||
keycloakSessionMap.remove(keycloakSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void logoutKeycloakSession(String keycloakSessionId) {
|
||||
log.debug("logoutKeycloakSession: " + keycloakSessionId);
|
||||
UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId);
|
||||
if (sessions == null) {
|
||||
log.debug("no session for keycloak session id: " + keycloakSessionId);
|
||||
return;
|
||||
}
|
||||
String sessionId = sessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
|
||||
if (sessionId == null) {
|
||||
log.debug("no session for keycloak session id: " + keycloakSessionId);
|
||||
|
||||
}
|
||||
sessions.httpSessionToKeycloakSession.remove(sessionId);
|
||||
Session session = sessions.sessions.remove(sessionId);
|
||||
session.setPrincipal(null);
|
||||
session.setAuthType(null);
|
||||
session.getSession().invalidate();
|
||||
if (sessions.keycloakSessionToHttpSession.size() == 0) {
|
||||
userSessionMap.remove(sessions.user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sessionEvent(SessionEvent event) {
|
||||
// We only care about session destroyed events
|
||||
if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
|
||||
|
@ -124,14 +140,22 @@ public class CatalinaUserSessionManagement implements SessionListener, UserSessi
|
|||
session.setAuthType(null);
|
||||
|
||||
String username = principal.getUserPrincipal().getName();
|
||||
synchronized (userSessionMap) {
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions != null) {
|
||||
sessions.getSessions().remove(session.getId());
|
||||
if (sessions.getSessions().isEmpty()) {
|
||||
UserSessions userSessions = userSessionMap.get(username);
|
||||
if (userSessions == null) {
|
||||
return;
|
||||
}
|
||||
String sessionid = session.getId();
|
||||
synchronized (this) {
|
||||
String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(sessionid);
|
||||
if (keycloakSessionId != null) {
|
||||
userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
|
||||
keycloakSessionMap.remove(keycloakSessionId);
|
||||
}
|
||||
userSessions.sessions.remove(sessionid);
|
||||
if (userSessions.httpSessionToKeycloakSession.size() == 0) {
|
||||
userSessionMap.remove(username);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
|
|||
session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
|
||||
String username = securityContext.getToken().getSubject();
|
||||
log.finer("userSessionManage.login: " + username);
|
||||
userSessionManagement.login(session, username);
|
||||
userSessionManagement.login(session, username, securityContext.getToken().getSessionState());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.adapters.tomcat7;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -23,31 +24,21 @@ import org.keycloak.adapters.UserSessionManagement;
|
|||
public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement {
|
||||
private static final Logger log = Logger.getLogger(""+CatalinaUserSessionManagement.class);
|
||||
protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
|
||||
protected ConcurrentHashMap<String, UserSessions> keycloakSessionMap = new ConcurrentHashMap<String, UserSessions>();
|
||||
|
||||
public static class UserSessions {
|
||||
protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
|
||||
protected String user;
|
||||
protected long loggedIn = System.currentTimeMillis();
|
||||
|
||||
|
||||
public Map<String, Session> getSessions() {
|
||||
return sessions;
|
||||
}
|
||||
|
||||
protected Map<String, String> keycloakSessionToHttpSession = new HashMap<String, String>();
|
||||
protected Map<String, String> httpSessionToKeycloakSession = new HashMap<String, String>();
|
||||
protected Map<String, Session> sessions = new HashMap<String, Session>();
|
||||
public long getLoggedIn() {
|
||||
return loggedIn;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveSessions() {
|
||||
int active = 0;
|
||||
synchronized (userSessionMap) {
|
||||
for (UserSessions sessions : userSessionMap.values()) {
|
||||
active += sessions.getSessions().size();
|
||||
}
|
||||
|
||||
}
|
||||
return active;
|
||||
public synchronized int getActiveSessions() {
|
||||
return keycloakSessionMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,60 +47,78 @@ public class CatalinaUserSessionManagement implements SessionListener, UserSessi
|
|||
* @return null if user not logged in
|
||||
*/
|
||||
@Override
|
||||
public Long getUserLoginTime(String username) {
|
||||
public synchronized Long getUserLoginTime(String username) {
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions == null) return null;
|
||||
return sessions.getLoggedIn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getActiveUsers() {
|
||||
public synchronized Set<String> getActiveUsers() {
|
||||
HashSet<String> set = new HashSet<String>();
|
||||
set.addAll(userSessionMap.keySet());
|
||||
return set;
|
||||
}
|
||||
|
||||
protected void login(Session session, String username) {
|
||||
synchronized (userSessionMap) {
|
||||
UserSessions userSessions = userSessionMap.get(username);
|
||||
if (userSessions == null) {
|
||||
userSessions = new UserSessions();
|
||||
userSessionMap.put(username, userSessions);
|
||||
}
|
||||
userSessions.getSessions().put(session.getId(), session);
|
||||
|
||||
public synchronized void login(Session session, String username, String keycloakSessionId) {
|
||||
String sessionId = session.getId();
|
||||
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions == null) {
|
||||
sessions = new UserSessions();
|
||||
sessions.user = username;
|
||||
userSessionMap.put(username, sessions);
|
||||
}
|
||||
keycloakSessionMap.put(keycloakSessionId, sessions);
|
||||
sessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
|
||||
sessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
|
||||
session.addSessionListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logoutAll() {
|
||||
List<String> users = new ArrayList<String>();
|
||||
users.addAll(userSessionMap.keySet());
|
||||
for (String user : users) logout(user);
|
||||
for (String user : userSessionMap.keySet()) logoutUser(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(String user) {
|
||||
log.finer("logoutUser: " + user);
|
||||
public void logoutUser(String user) {
|
||||
UserSessions sessions = null;
|
||||
synchronized (userSessionMap) {
|
||||
sessions = userSessionMap.remove(user);
|
||||
|
||||
}
|
||||
if (sessions == null) {
|
||||
log.finer("no session for user: " + user);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
log.finer("found session for user");
|
||||
for (Session session : sessions.getSessions().values()) {
|
||||
for (Map.Entry<String, String> entry : sessions.httpSessionToKeycloakSession.entrySet()) {
|
||||
String sessionId = entry.getKey();
|
||||
String keycloakSessionId = entry.getValue();
|
||||
Session session = sessions.sessions.get(sessionId);
|
||||
session.setPrincipal(null);
|
||||
session.setAuthType(null);
|
||||
session.getSession().invalidate();
|
||||
keycloakSessionMap.remove(keycloakSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void logoutKeycloakSession(String keycloakSessionId) {
|
||||
UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId);
|
||||
if (sessions == null) {
|
||||
return;
|
||||
}
|
||||
String sessionId = sessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
|
||||
if (sessionId == null) {
|
||||
|
||||
}
|
||||
sessions.httpSessionToKeycloakSession.remove(sessionId);
|
||||
Session session = sessions.sessions.remove(sessionId);
|
||||
session.setPrincipal(null);
|
||||
session.setAuthType(null);
|
||||
session.getSession().invalidate();
|
||||
if (sessions.keycloakSessionToHttpSession.size() == 0) {
|
||||
userSessionMap.remove(sessions.user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sessionEvent(SessionEvent event) {
|
||||
// We only care about session destroyed events
|
||||
if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
|
||||
|
@ -124,14 +133,22 @@ public class CatalinaUserSessionManagement implements SessionListener, UserSessi
|
|||
session.setAuthType(null);
|
||||
|
||||
String username = principal.getUserPrincipal().getName();
|
||||
synchronized (userSessionMap) {
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions != null) {
|
||||
sessions.getSessions().remove(session.getId());
|
||||
if (sessions.getSessions().isEmpty()) {
|
||||
UserSessions userSessions = userSessionMap.get(username);
|
||||
if (userSessions == null) {
|
||||
return;
|
||||
}
|
||||
String sessionid = session.getId();
|
||||
synchronized (this) {
|
||||
String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(sessionid);
|
||||
if (keycloakSessionId != null) {
|
||||
userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
|
||||
keycloakSessionMap.remove(keycloakSessionId);
|
||||
}
|
||||
userSessions.sessions.remove(sessionid);
|
||||
if (userSessions.httpSessionToKeycloakSession.size() == 0) {
|
||||
userSessionMap.remove(username);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
|
|||
HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
|
||||
HttpSession session = req.getSession(true);
|
||||
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
|
||||
userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session, account.getPrincipal().getName());
|
||||
userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session, account.getPrincipal().getName(), account.getKeycloakSecurityContext().getToken().getSessionState());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.keycloak.adapters.undertow;
|
||||
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.server.session.SessionManager;
|
||||
import org.keycloak.adapters.UserSessionManagement;
|
||||
|
||||
|
@ -41,7 +40,12 @@ public class SessionManagementBridge implements UserSessionManagement {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void logout(String user) {
|
||||
userSessionManagement.logout(sessionManager, user);
|
||||
public void logoutUser(String user) {
|
||||
userSessionManagement.logoutUser(sessionManager, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logoutKeycloakSession(String id) {
|
||||
userSessionManagement.logoutKeycloakSession(sessionManager, id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,19 +6,14 @@ import io.undertow.server.session.Session;
|
|||
import io.undertow.server.session.SessionListener;
|
||||
import io.undertow.server.session.SessionManager;
|
||||
import io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler;
|
||||
import io.undertow.util.StatusCodes;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
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;
|
||||
|
||||
|
@ -32,30 +27,22 @@ public class UndertowUserSessionManagement implements SessionListener {
|
|||
private static final Logger log = Logger.getLogger(UndertowUserSessionManagement.class);
|
||||
private static final String AUTH_SESSION_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession";
|
||||
protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
|
||||
protected ConcurrentHashMap<String, UserSessions> keycloakSessionMap = new ConcurrentHashMap<String, UserSessions>();
|
||||
protected volatile boolean registered;
|
||||
|
||||
|
||||
public static class UserSessions {
|
||||
protected Set<String> sessionIds = new HashSet<String>();
|
||||
protected String user;
|
||||
protected long loggedIn = System.currentTimeMillis();
|
||||
|
||||
public Set<String> getSessionIds() {
|
||||
return sessionIds;
|
||||
}
|
||||
|
||||
protected Map<String, String> keycloakSessionToHttpSession = new HashMap<String, String>();
|
||||
protected Map<String, String> httpSessionToKeycloakSession = new HashMap<String, String>();
|
||||
public long getLoggedIn() {
|
||||
return loggedIn;
|
||||
}
|
||||
}
|
||||
|
||||
public int getActiveSessions() {
|
||||
int active = 0;
|
||||
synchronized (userSessionMap) {
|
||||
for (UserSessions sessions : userSessionMap.values()) {
|
||||
active += sessions.getSessionIds().size();
|
||||
}
|
||||
|
||||
}
|
||||
return active;
|
||||
public synchronized int getActiveSessions() {
|
||||
return keycloakSessionMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,74 +50,88 @@ public class UndertowUserSessionManagement implements SessionListener {
|
|||
* @param username
|
||||
* @return null if user not logged in
|
||||
*/
|
||||
public Long getUserLoginTime(String username) {
|
||||
public synchronized Long getUserLoginTime(String username) {
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions == null) return null;
|
||||
return sessions.getLoggedIn();
|
||||
}
|
||||
|
||||
public Set<String> getActiveUsers() {
|
||||
public synchronized Set<String> getActiveUsers() {
|
||||
HashSet<String> set = new HashSet<String>();
|
||||
set.addAll(userSessionMap.keySet());
|
||||
return set;
|
||||
}
|
||||
|
||||
public void login(SessionManager manager, HttpSession session, String username) {
|
||||
public synchronized void login(SessionManager manager, HttpSession session, String username, String keycloakSessionId) {
|
||||
String sessionId = session.getId();
|
||||
addAuthenticatedSession(username, sessionId);
|
||||
manager.registerSessionListener(this);
|
||||
}
|
||||
|
||||
protected void addAuthenticatedSession(String username, String sessionId) {
|
||||
synchronized (userSessionMap) {
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions == null) {
|
||||
sessions = new UserSessions();
|
||||
sessions.user = username;
|
||||
userSessionMap.put(username, sessions);
|
||||
}
|
||||
sessions.getSessionIds().add(sessionId);
|
||||
sessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
|
||||
sessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
|
||||
keycloakSessionMap.put(keycloakSessionId, sessions);
|
||||
if (!registered) {
|
||||
manager.registerSessionListener(this);
|
||||
registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeAuthenticatedSession(String sessionId, String username) {
|
||||
synchronized (userSessionMap) {
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions == null) return;
|
||||
sessions.getSessionIds().remove(sessionId);
|
||||
if (sessions.getSessionIds().isEmpty()) {
|
||||
userSessionMap.remove(username);
|
||||
}
|
||||
}
|
||||
public synchronized void logoutAll(SessionManager manager) {
|
||||
for (String user : userSessionMap.keySet()) logoutUser(manager, user);
|
||||
}
|
||||
|
||||
public void logoutAll(SessionManager manager) {
|
||||
List<String> users = new ArrayList<String>();
|
||||
users.addAll(userSessionMap.keySet());
|
||||
for (String user : users) logout(manager, user);
|
||||
}
|
||||
|
||||
public void logout(SessionManager manager, String user) {
|
||||
log.info("logoutUser: " + user);
|
||||
public synchronized void logoutUser(SessionManager manager, String user) {
|
||||
log.debug("logoutUser: " + user);
|
||||
UserSessions sessions = null;
|
||||
synchronized (userSessionMap) {
|
||||
sessions = userSessionMap.remove(user);
|
||||
}
|
||||
if (sessions == null) {
|
||||
log.info("no session for user: " + user);
|
||||
log.debug("no session for user: " + user);
|
||||
return;
|
||||
}
|
||||
log.info("found session for user");
|
||||
for (String id : sessions.getSessionIds()) {
|
||||
log.debug("found session for user");
|
||||
for (Map.Entry<String, String> entry : sessions.httpSessionToKeycloakSession.entrySet()) {
|
||||
log.debug("invalidating session for user: " + user);
|
||||
Session session = manager.getSession(id);
|
||||
String sessionId = entry.getKey();
|
||||
String keycloakSessionId = entry.getValue();
|
||||
Session session = manager.getSession(sessionId);
|
||||
try {
|
||||
session.invalidate(null);
|
||||
} catch (Exception e) {
|
||||
log.warn("Session already invalidated.");
|
||||
}
|
||||
keycloakSessionMap.remove(keycloakSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void logoutKeycloakSession(SessionManager manager, String keycloakSessionId) {
|
||||
log.debug("logoutKeycloakSession: " + keycloakSessionId);
|
||||
UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId);
|
||||
if (sessions == null) {
|
||||
log.debug("no session for keycloak session id: " + keycloakSessionId);
|
||||
return;
|
||||
}
|
||||
String sessionId = sessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
|
||||
if (sessionId == null) {
|
||||
log.debug("no session for keycloak session id: " + keycloakSessionId);
|
||||
|
||||
}
|
||||
sessions.httpSessionToKeycloakSession.remove(sessionId);
|
||||
Session session = manager.getSession(sessionId);
|
||||
try {
|
||||
session.invalidate(null);
|
||||
} catch (Exception e) {
|
||||
log.warn("Session already invalidated.");
|
||||
}
|
||||
if (sessions.keycloakSessionToHttpSession.size() == 0) {
|
||||
userSessionMap.remove(sessions.user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sessionCreated(Session session, HttpServerExchange exchange) {
|
||||
}
|
||||
|
@ -141,7 +142,21 @@ public class UndertowUserSessionManagement implements SessionListener {
|
|||
String username = getUsernameFromSession(session);
|
||||
if (username == null) return;
|
||||
String sessionId = session.getId();
|
||||
removeAuthenticatedSession(sessionId, username);
|
||||
UserSessions userSessions = userSessionMap.get(username);
|
||||
if (userSessions == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(sessionId);
|
||||
if (keycloakSessionId != null) {
|
||||
userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
|
||||
keycloakSessionMap.remove(keycloakSessionId);
|
||||
}
|
||||
if (userSessions.httpSessionToKeycloakSession.size() == 0) {
|
||||
userSessionMap.remove(username);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected String getUsernameFromSession(Session session) {
|
||||
|
@ -156,8 +171,21 @@ public class UndertowUserSessionManagement implements SessionListener {
|
|||
public void sessionIdChanged(Session session, String oldSessionId) {
|
||||
String username = getUsernameFromSession(session);
|
||||
if (username == null) return;
|
||||
removeAuthenticatedSession(oldSessionId, username);
|
||||
addAuthenticatedSession(session.getId(), username);
|
||||
String sessionId = session.getId();
|
||||
|
||||
UserSessions userSessions = userSessionMap.get(username);
|
||||
if (userSessions == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(oldSessionId);
|
||||
if (keycloakSessionId != null) {
|
||||
userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
|
||||
userSessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
|
||||
userSessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -64,4 +65,7 @@ public interface ClientModel {
|
|||
|
||||
void setNotBefore(int notBefore);
|
||||
|
||||
Set<UserSessionModel> getUserSessions();
|
||||
|
||||
int getActiveUserSessions();
|
||||
}
|
||||
|
|
|
@ -263,4 +263,7 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
|
|||
|
||||
void removeExpiredUserSessions();
|
||||
|
||||
ClientModel findClientById(String id);
|
||||
|
||||
void removeUserSessions();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -25,4 +28,9 @@ public interface UserSessionModel {
|
|||
|
||||
void setLastSessionRefresh(int seconds);
|
||||
|
||||
void associateClient(ClientModel client);
|
||||
|
||||
List<ClientModel> getClientAssociations();
|
||||
|
||||
void removeAssociatedClient(ClientModel client);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
|
|||
protected ApplicationEntity applicationEntity;
|
||||
|
||||
public ApplicationAdapter(RealmModel realm, EntityManager em, ApplicationEntity applicationEntity) {
|
||||
super(realm, applicationEntity);
|
||||
super(realm, applicationEntity, em);
|
||||
this.realm = realm;
|
||||
this.em = em;
|
||||
this.applicationEntity = applicationEntity;
|
||||
|
|
|
@ -2,10 +2,16 @@ package org.keycloak.models.jpa;
|
|||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.jpa.entities.ClientEntity;
|
||||
import org.keycloak.models.jpa.entities.OAuthClientEntity;
|
||||
import org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -15,10 +21,12 @@ import java.util.Set;
|
|||
public class ClientAdapter implements ClientModel {
|
||||
protected ClientEntity entity;
|
||||
protected RealmModel realm;
|
||||
protected EntityManager em;
|
||||
|
||||
public ClientAdapter(RealmModel realm, ClientEntity entity) {
|
||||
public ClientAdapter(RealmModel realm, ClientEntity entity, EntityManager em) {
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
this.em = em;
|
||||
}
|
||||
|
||||
public ClientEntity getEntity() {
|
||||
|
@ -141,6 +149,31 @@ public class ClientAdapter implements ClientModel {
|
|||
entity.setNotBefore(notBefore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveUserSessions() {
|
||||
Query query = em.createNamedQuery("getActiveClientSessions");
|
||||
query.setParameter("clientId", getId());
|
||||
Object count = query.getSingleResult();
|
||||
return ((Number)count).intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UserSessionModel> getUserSessions() {
|
||||
Set<UserSessionModel> list = new HashSet<UserSessionModel>();
|
||||
TypedQuery<ClientUserSessionAssociationEntity> query = em.createNamedQuery("getClientUserSessionByClient", ClientUserSessionAssociationEntity.class);
|
||||
String id = getId();
|
||||
query.setParameter("clientId", id);
|
||||
List<ClientUserSessionAssociationEntity> results = query.getResultList();
|
||||
for (ClientUserSessionAssociationEntity entity : results) {
|
||||
list.add(new UserSessionAdapter(em, realm, entity.getSession()));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void deleteUserSessionAssociation() {
|
||||
em.createNamedQuery("removeClientUserSessionByClient").setParameter("clientId", getId()).executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -80,6 +80,7 @@ public class JpaKeycloakSession implements KeycloakSession {
|
|||
}
|
||||
|
||||
RealmAdapter adapter = new RealmAdapter(em, realm);
|
||||
adapter.removeUserSessions();
|
||||
for (ApplicationEntity a : new LinkedList<ApplicationEntity>(realm.getApplications())) {
|
||||
adapter.removeApplication(a.getId());
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.jpa.entities.OAuthClientEntity;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -14,8 +15,8 @@ import java.util.Set;
|
|||
*/
|
||||
public class OAuthClientAdapter extends ClientAdapter implements OAuthClientModel {
|
||||
|
||||
public OAuthClientAdapter(RealmModel realm, OAuthClientEntity entity) {
|
||||
super(realm, entity);
|
||||
public OAuthClientAdapter(RealmModel realm, OAuthClientEntity entity, EntityManager em) {
|
||||
super(realm, entity, em);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -72,6 +72,10 @@ public class RealmAdapter implements RealmModel {
|
|||
this.realm = realm;
|
||||
}
|
||||
|
||||
public RealmEntity getEntity() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return realm.getId();
|
||||
|
@ -582,6 +586,13 @@ public class RealmAdapter implements RealmModel {
|
|||
return getOAuthClient(clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel findClientById(String id) {
|
||||
ClientModel model = getApplicationById(id);
|
||||
if (model != null) return model;
|
||||
return getOAuthClientById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ApplicationModel> getApplicationNameMap() {
|
||||
Map<String, ApplicationModel> map = new HashMap<String, ApplicationModel>();
|
||||
|
@ -627,6 +638,7 @@ public class RealmAdapter implements RealmModel {
|
|||
ApplicationModel application = getApplicationById(id);
|
||||
if (application == null) return false;
|
||||
|
||||
((ApplicationAdapter)application).deleteUserSessionAssociation();
|
||||
for (RoleModel role : application.getRoles()) {
|
||||
application.removeRole(role);
|
||||
}
|
||||
|
@ -846,13 +858,14 @@ public class RealmAdapter implements RealmModel {
|
|||
data.setRealm(realm);
|
||||
em.persist(data);
|
||||
em.flush();
|
||||
return new OAuthClientAdapter(this, data);
|
||||
return new OAuthClientAdapter(this, data, em);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOAuthClient(String id) {
|
||||
OAuthClientModel oauth = getOAuthClientById(id);
|
||||
if (oauth == null) return false;
|
||||
((OAuthClientAdapter)oauth).deleteUserSessionAssociation();
|
||||
OAuthClientEntity client = (OAuthClientEntity) ((OAuthClientAdapter) oauth).getEntity();
|
||||
em.createQuery("delete from " + ScopeMappingEntity.class.getSimpleName() + " where client = :client").setParameter("client", client).executeUpdate();
|
||||
em.remove(client);
|
||||
|
@ -867,7 +880,7 @@ public class RealmAdapter implements RealmModel {
|
|||
query.setParameter("realm", realm);
|
||||
List<OAuthClientEntity> entities = query.getResultList();
|
||||
if (entities.size() == 0) return null;
|
||||
return new OAuthClientAdapter(this, entities.get(0));
|
||||
return new OAuthClientAdapter(this, entities.get(0), em);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -876,7 +889,7 @@ public class RealmAdapter implements RealmModel {
|
|||
|
||||
// Check if client belongs to this realm
|
||||
if (client == null || !this.realm.getId().equals(client.getRealm().getId())) return null;
|
||||
return new OAuthClientAdapter(this, client);
|
||||
return new OAuthClientAdapter(this, client, em);
|
||||
}
|
||||
|
||||
|
||||
|
@ -886,7 +899,7 @@ public class RealmAdapter implements RealmModel {
|
|||
query.setParameter("realm", realm);
|
||||
List<OAuthClientEntity> entities = query.getResultList();
|
||||
List<OAuthClientModel> list = new ArrayList<OAuthClientModel>();
|
||||
for (OAuthClientEntity entity : entities) list.add(new OAuthClientAdapter(this, entity));
|
||||
for (OAuthClientEntity entity : entities) list.add(new OAuthClientAdapter(this, entity, em));
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -1385,6 +1398,7 @@ public class RealmAdapter implements RealmModel {
|
|||
@Override
|
||||
public UserSessionModel createUserSession(UserModel user, String ipAddress) {
|
||||
UserSessionEntity entity = new UserSessionEntity();
|
||||
entity.setRealm(realm);
|
||||
entity.setUser(((UserAdapter) user).getUser());
|
||||
entity.setIpAddress(ipAddress);
|
||||
|
||||
|
@ -1394,20 +1408,20 @@ public class RealmAdapter implements RealmModel {
|
|||
entity.setLastSessionRefresh(currentTime);
|
||||
|
||||
em.persist(entity);
|
||||
return new UserSessionAdapter(entity);
|
||||
return new UserSessionAdapter(em, this, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getUserSession(String id) {
|
||||
UserSessionEntity entity = em.find(UserSessionEntity.class, id);
|
||||
return entity != null ? new UserSessionAdapter(entity) : null;
|
||||
return entity != null ? new UserSessionAdapter(em, this, entity) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSessionModel> getUserSessions(UserModel user) {
|
||||
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
|
||||
for (UserSessionEntity e : em.createNamedQuery("getUserSessionByUser", UserSessionEntity.class).setParameter("user", ((UserAdapter) user).getUser()).getResultList()) {
|
||||
sessions.add(new UserSessionAdapter(e));
|
||||
sessions.add(new UserSessionAdapter(em, this, e));
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
@ -1417,12 +1431,20 @@ public class RealmAdapter implements RealmModel {
|
|||
em.remove(((UserSessionAdapter) session).getEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSessions() {
|
||||
em.createNamedQuery("removeClientUserSessionByRealm").setParameter("realm", realm).executeUpdate();
|
||||
em.createNamedQuery("removeRealmUserSessions").setParameter("realm", realm).executeUpdate();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSessions(UserModel user) {
|
||||
removeUserSessions(((UserAdapter) user).getUser());
|
||||
}
|
||||
|
||||
private void removeUserSessions(UserEntity user) {
|
||||
em.createNamedQuery("removeClientUserSessionByUser").setParameter("user", user).executeUpdate();
|
||||
em.createNamedQuery("removeUserSessionByUser").setParameter("user", user).executeUpdate();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,30 @@
|
|||
package org.keycloak.models.jpa;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity;
|
||||
import org.keycloak.models.jpa.entities.UserSessionEntity;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UserSessionAdapter implements UserSessionModel {
|
||||
|
||||
private RealmModel realm;
|
||||
private UserSessionEntity entity;
|
||||
private EntityManager em;
|
||||
|
||||
public UserSessionAdapter(UserSessionEntity entity) {
|
||||
public UserSessionAdapter(EntityManager em, RealmModel realm, UserSessionEntity entity) {
|
||||
this.entity = entity;
|
||||
this.em = em;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public UserSessionEntity getEntity() {
|
||||
|
@ -68,4 +80,51 @@ public class UserSessionAdapter implements UserSessionModel {
|
|||
public void setLastSessionRefresh(int seconds) {
|
||||
entity.setLastSessionRefresh(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void associateClient(ClientModel client) {
|
||||
for (ClientUserSessionAssociationEntity ass : entity.getClients()) {
|
||||
if (ass.getClientId().equals(client.getId())) return;
|
||||
}
|
||||
ClientUserSessionAssociationEntity association = new ClientUserSessionAssociationEntity();
|
||||
association.setClientId(client.getId());
|
||||
association.setSession(entity);
|
||||
association.setUser(entity.getUser());
|
||||
association.setRealm(((RealmAdapter)realm).getEntity());
|
||||
em.persist(association);
|
||||
entity.getClients().add(association);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAssociatedClient(ClientModel client) {
|
||||
em.createNamedQuery("removeClientUserSessionByClient").setParameter("clientId", client.getId()).executeUpdate();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientModel> getClientAssociations() {
|
||||
List<ClientModel> clients = new ArrayList<ClientModel>();
|
||||
for (ClientUserSessionAssociationEntity association : entity.getClients()) {
|
||||
ClientModel client = realm.findClientById(association.getClientId());
|
||||
if (client == null) {
|
||||
throw new ModelException("couldnt find client");
|
||||
}
|
||||
clients.add(client);
|
||||
}
|
||||
return clients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
UserSessionAdapter that = (UserSessionAdapter) o;
|
||||
return that.getId().equals(this.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@Entity
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "getAllClientUserSessions", query = "select s from ClientUserSessionAssociationEntity s"),
|
||||
@NamedQuery(name = "getClientUserSessionBySession", query = "select s from ClientUserSessionAssociationEntity s where s.session = :session"),
|
||||
@NamedQuery(name = "getClientUserSessionByClient", query = "select s from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
|
||||
@NamedQuery(name = "getActiveClientSessions", query = "select COUNT(s) from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
|
||||
@NamedQuery(name = "removeClientUserSessionByClient", query = "delete from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
|
||||
@NamedQuery(name = "removeClientUserSessionByUser", query = "delete from ClientUserSessionAssociationEntity s where s.user = :user"),
|
||||
@NamedQuery(name = "removeClientUserSessionByRealm", query = "delete from ClientUserSessionAssociationEntity s where s.realm = :realm")
|
||||
})
|
||||
public class ClientUserSessionAssociationEntity {
|
||||
@Id
|
||||
@GenericGenerator(name="uuid_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
|
||||
@GeneratedValue(generator = "uuid_generator")
|
||||
private String id;
|
||||
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
private UserSessionEntity session;
|
||||
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
private UserEntity user;
|
||||
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
private RealmEntity realm;
|
||||
|
||||
private String clientId;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public UserEntity getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(UserEntity user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public UserSessionEntity getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void setSession(UserSessionEntity session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public RealmEntity getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(RealmEntity realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
}
|
|
@ -3,12 +3,17 @@ package org.keycloak.models.jpa.entities;
|
|||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.OneToMany;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -16,6 +21,7 @@ import javax.persistence.NamedQuery;
|
|||
@Entity
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "getUserSessionByUser", query = "select s from UserSessionEntity s where s.user = :user"),
|
||||
@NamedQuery(name = "removeRealmUserSessions", query = "delete from UserSessionEntity s where s.realm = :realm"),
|
||||
@NamedQuery(name = "removeUserSessionByUser", query = "delete from UserSessionEntity s where s.user = :user"),
|
||||
@NamedQuery(name = "removeUserSessionExpired", query = "delete from UserSessionEntity s where s.started < :maxTime or s.lastSessionRefresh < :idleTime")
|
||||
})
|
||||
|
@ -29,12 +35,19 @@ public class UserSessionEntity {
|
|||
@ManyToOne
|
||||
private UserEntity user;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private RealmEntity realm;
|
||||
|
||||
String ipAddress;
|
||||
|
||||
int started;
|
||||
|
||||
int lastSessionRefresh;
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="session")
|
||||
Collection<ClientUserSessionAssociationEntity> clients = new ArrayList<ClientUserSessionAssociationEntity>();
|
||||
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -74,4 +87,20 @@ public class UserSessionEntity {
|
|||
public void setLastSessionRefresh(int lastSessionRefresh) {
|
||||
this.lastSessionRefresh = lastSessionRefresh;
|
||||
}
|
||||
|
||||
public Collection<ClientUserSessionAssociationEntity> getClients() {
|
||||
return clients;
|
||||
}
|
||||
|
||||
public void setClients(Collection<ClientUserSessionAssociationEntity> clients) {
|
||||
this.clients = clients;
|
||||
}
|
||||
|
||||
public RealmEntity getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(RealmEntity realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
|
|
26
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
Normal file → Executable file
26
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
Normal file → Executable file
|
@ -5,11 +5,16 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.entities.ClientEntity;
|
||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoClientUserSessionAssociationEntity;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -164,4 +169,25 @@ public class ClientAdapter<T extends MongoIdentifiableEntity> extends AbstractMo
|
|||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UserSessionModel> getUserSessions() {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("clientId").is(getId())
|
||||
.get();
|
||||
List<MongoClientUserSessionAssociationEntity> associations = getMongoStore().loadEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
|
||||
|
||||
Set<UserSessionModel> result = new HashSet<UserSessionModel>();
|
||||
for (MongoClientUserSessionAssociationEntity association : associations) {
|
||||
UserSessionModel session = realm.getUserSession(association.getSessionId());
|
||||
result.add(session);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveUserSessions() {
|
||||
// todo, something more efficient like COUNT in JPAQL?
|
||||
return getUserSessions().size();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -661,6 +661,14 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
return getOAuthClient(clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel findClientById(String id) {
|
||||
ClientModel model = getApplicationById(id);
|
||||
if (model != null) return model;
|
||||
return getOAuthClientById(id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public ApplicationModel getApplicationById(String id) {
|
||||
|
@ -1352,6 +1360,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
@Override
|
||||
public UserSessionModel createUserSession(UserModel user, String ipAddress) {
|
||||
MongoUserSessionEntity entity = new MongoUserSessionEntity();
|
||||
entity.setRealmId(getId());
|
||||
entity.setUser(user.getId());
|
||||
entity.setIpAddress(ipAddress);
|
||||
|
||||
|
@ -1386,7 +1395,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
|
||||
@Override
|
||||
public void removeUserSession(UserSessionModel session) {
|
||||
getMongoStore().removeEntity(((UserSessionAdapter) session).getEntity(), invocationContext);
|
||||
getMongoStore().removeEntity(((UserSessionAdapter) session).getMongoEntity(), invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1395,6 +1404,12 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
getMongoStore().removeEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSessions() {
|
||||
DBObject query = new BasicDBObject("realmId", getId());
|
||||
getMongoStore().removeEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExpiredUserSessions() {
|
||||
int currentTime = Time.currentTime();
|
||||
|
|
|
@ -1,26 +1,40 @@
|
|||
package org.keycloak.models.mongo.keycloak.adapters;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoClientUserSessionAssociationEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserSessionEntity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UserSessionAdapter implements UserSessionModel {
|
||||
public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEntity> implements UserSessionModel {
|
||||
|
||||
private MongoUserSessionEntity entity;
|
||||
private RealmAdapter realm;
|
||||
private MongoStoreInvocationContext invContext;
|
||||
|
||||
public UserSessionAdapter(MongoUserSessionEntity entity, RealmAdapter realm, MongoStoreInvocationContext invContext) {
|
||||
public UserSessionAdapter(MongoUserSessionEntity entity, RealmAdapter realm, MongoStoreInvocationContext invContext)
|
||||
{
|
||||
super(invContext);
|
||||
this.entity = entity;
|
||||
this.realm = realm;
|
||||
this.invContext = invContext;
|
||||
}
|
||||
|
||||
public MongoUserSessionEntity getEntity() {
|
||||
@Override
|
||||
protected MongoUserSessionEntity getMongoEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
@ -74,4 +88,56 @@ public class UserSessionAdapter implements UserSessionModel {
|
|||
entity.setLastSessionRefresh(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void associateClient(ClientModel client) {
|
||||
List<ClientModel> clients = getClientAssociations();
|
||||
for (ClientModel ass : clients) {
|
||||
if (ass.getId().equals(client.getId())) return;
|
||||
}
|
||||
|
||||
MongoClientUserSessionAssociationEntity association = new MongoClientUserSessionAssociationEntity();
|
||||
association.setClientId(client.getId());
|
||||
association.setSessionId(getId());
|
||||
|
||||
getMongoStore().insertEntity(association, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientModel> getClientAssociations() {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("sessionId").is(getId())
|
||||
.get();
|
||||
List<MongoClientUserSessionAssociationEntity> associations = getMongoStore().loadEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
|
||||
|
||||
List<ClientModel> result = new ArrayList<ClientModel>();
|
||||
for (MongoClientUserSessionAssociationEntity association : associations) {
|
||||
ClientModel client = realm.findClientById(association.getClientId());
|
||||
result.add(client);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAssociatedClient(ClientModel client) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("sessionId").is(getId())
|
||||
.and("clientId").is(client.getId())
|
||||
.get();
|
||||
getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
|
||||
UserSessionAdapter that = (UserSessionAdapter) o;
|
||||
return getId().equals(that.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,5 +22,11 @@ public class MongoApplicationEntity extends ApplicationEntity implements MongoId
|
|||
.and("applicationId").is(getId())
|
||||
.get();
|
||||
context.getMongoStore().removeEntities(MongoRoleEntity.class, query, context);
|
||||
|
||||
query = new QueryBuilder()
|
||||
.and("clientId").is(getId())
|
||||
.get();
|
||||
context.getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, context);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package org.keycloak.models.mongo.keycloak.entities;
|
||||
|
||||
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.MongoCollection;
|
||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@MongoCollection(collectionName = "session-client-associations")
|
||||
public class MongoClientUserSessionAssociationEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
|
||||
private String clientId;
|
||||
private String sessionId;
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext invocationContext) {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package org.keycloak.models.mongo.keycloak.entities;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.models.entities.OAuthClientEntity;
|
||||
import org.keycloak.models.mongo.api.MongoCollection;
|
||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||
|
@ -15,5 +17,9 @@ public class MongoOAuthClientEntity extends OAuthClientEntity implements MongoId
|
|||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext invocationContext) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("clientId").is(getId())
|
||||
.get();
|
||||
invocationContext.getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,5 +32,8 @@ public class MongoRealmEntity extends RealmEntity implements MongoIdentifiableEn
|
|||
|
||||
// Remove all clients of this realm
|
||||
context.getMongoStore().removeEntities(MongoOAuthClientEntity.class, query, context);
|
||||
|
||||
// Remove all sessions of this realm
|
||||
context.getMongoStore().removeEntities(MongoUserSessionEntity.class, query, context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.keycloak.models.mongo.keycloak.entities;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.MongoCollection;
|
||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||
|
@ -11,6 +13,8 @@ import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
|||
@MongoCollection(collectionName = "sessions")
|
||||
public class MongoUserSessionEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
|
||||
|
||||
private String realmId;
|
||||
|
||||
private String user;
|
||||
|
||||
private String ipAddress;
|
||||
|
@ -19,6 +23,14 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
|
|||
|
||||
private int lastSessionRefresh;
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
@ -52,7 +64,12 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
|
|||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext invocationContext) {
|
||||
public void afterRemove(MongoStoreInvocationContext context) {
|
||||
// Remove all roles, which belongs to this application
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("sessionId").is(getId())
|
||||
.get();
|
||||
context.getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, context);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
|
|
|
@ -5,12 +5,14 @@ import org.keycloak.models.AuthenticationProviderModel;
|
|||
import org.keycloak.models.ClaimMask;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.OAuthClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredCredentialModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.SocialLinkModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
|
||||
import org.keycloak.representations.idm.ClaimRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
@ -19,6 +21,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
|||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.SocialLinkRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
@ -179,4 +182,20 @@ public class ModelToRepresentation {
|
|||
rep.setSocialUserId(socialLink.getSocialUserId());
|
||||
return rep;
|
||||
}
|
||||
|
||||
public static UserSessionRepresentation toRepresentation(UserSessionModel session) {
|
||||
UserSessionRepresentation rep = new UserSessionRepresentation();
|
||||
rep.setId(session.getId());
|
||||
rep.setStart(((long)session.getStarted()) * 1000);
|
||||
rep.setUser(session.getUser().getLoginName());
|
||||
rep.setIpAddress(session.getIpAddress());
|
||||
for (ClientModel client : session.getClientAssociations()) {
|
||||
if (client instanceof ApplicationModel) {
|
||||
rep.getApplications().add(client.getClientId());
|
||||
} else if (client instanceof OAuthClientModel) {
|
||||
rep.getClients().put(client.getId(), client.getClientId());
|
||||
}
|
||||
}
|
||||
return rep;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,6 +160,22 @@ public class ResourceAdminManager {
|
|||
executor.getHttpClient().getConnectionManager().shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void logoutSession(URI requestUri, RealmModel realm, String session) {
|
||||
ApacheHttpClient4Executor executor = createExecutor();
|
||||
|
||||
try {
|
||||
// don't set user notBefore as we don't want a database hit on a user driven logout
|
||||
List<ApplicationModel> resources = realm.getApplications();
|
||||
logger.debugv("logging out {0} resources ", resources.size());
|
||||
for (ApplicationModel resource : resources) {
|
||||
logoutApplication(requestUri, realm, resource, null, session, executor, 0);
|
||||
}
|
||||
} finally {
|
||||
executor.getHttpClient().getConnectionManager().shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void logoutAll(URI requestUri, RealmModel realm) {
|
||||
ApacheHttpClient4Executor executor = createExecutor();
|
||||
|
||||
|
|
|
@ -266,6 +266,7 @@ public class TokenService {
|
|||
String scope = form.getFirst(OAuth2Constants.SCOPE);
|
||||
|
||||
UserSessionModel session = realm.createUserSession(user, clientConnection.getRemoteAddr());
|
||||
session.associateClient(client);
|
||||
audit.session(session);
|
||||
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
|
||||
|
@ -648,6 +649,8 @@ public class TokenService {
|
|||
|
||||
logger.debug("accessRequest SUCCESS");
|
||||
|
||||
session.associateClient(client);
|
||||
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
|
||||
.accessToken(accessCode.getToken())
|
||||
.generateIDToken()
|
||||
|
|
|
@ -9,10 +9,12 @@ import org.keycloak.models.ModelDuplicateException;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.adapters.action.SessionStats;
|
||||
import org.keycloak.representations.adapters.action.UserStats;
|
||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||
import org.keycloak.services.managers.ApplicationManager;
|
||||
import org.keycloak.services.managers.ModelToRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
@ -36,7 +38,10 @@ import javax.ws.rs.core.MediaType;
|
|||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -230,6 +235,31 @@ public class ApplicationResource {
|
|||
return stats;
|
||||
}
|
||||
|
||||
@Path("session-count")
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Map<String, Integer> getApplicationSessionCount() {
|
||||
auth.requireView();
|
||||
Map<String, Integer> map = new HashMap<String, Integer>();
|
||||
map.put("count", application.getActiveUserSessions());
|
||||
return map;
|
||||
}
|
||||
|
||||
@Path("user-sessions")
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<UserSessionRepresentation> getUserSessions() {
|
||||
auth.requireView();
|
||||
List<UserSessionRepresentation> sessions = new ArrayList<UserSessionRepresentation>();
|
||||
for (UserSessionModel session : application.getUserSessions()) {
|
||||
UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
|
||||
sessions.add(rep);
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
@Path("logout-all")
|
||||
@POST
|
||||
public void logoutAll() {
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.keycloak.models.ApplicationModel;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
import org.keycloak.representations.adapters.action.SessionStats;
|
||||
import org.keycloak.representations.idm.RealmAuditRepresentation;
|
||||
|
@ -27,6 +28,7 @@ import javax.ws.rs.GET;
|
|||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
|
@ -156,9 +158,34 @@ public class RealmAdminResource {
|
|||
@POST
|
||||
public void logoutAll() {
|
||||
auth.requireManage();
|
||||
realm.removeUserSessions();
|
||||
new ResourceAdminManager().logoutAll(uriInfo.getRequestUri(), realm);
|
||||
}
|
||||
|
||||
@Path("sessions/{session}")
|
||||
@DELETE
|
||||
public void deleteSession(@PathParam("session") String sessionId) {
|
||||
UserSessionModel session = realm.getUserSession(sessionId);
|
||||
if (session == null) throw new NotFoundException("Sesssion not found");
|
||||
realm.removeUserSession(session);
|
||||
new ResourceAdminManager().logoutSession(uriInfo.getRequestUri(), realm, session.getId());
|
||||
}
|
||||
|
||||
@Path("application-session-stats")
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Map<String, Integer> getApplicationSessionStats() {
|
||||
auth.requireView();
|
||||
Map<String, Integer> stats = new HashMap<String, Integer>();
|
||||
for (ApplicationModel applicationModel : realm.getApplications()) {
|
||||
int size = applicationModel.getActiveUserSessions();
|
||||
if (size == 0) continue;
|
||||
stats.put(applicationModel.getName(), size);
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Path("session-stats")
|
||||
@GET
|
||||
@NoCache
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.SocialLinkModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.adapters.action.UserStats;
|
||||
import org.keycloak.representations.idm.ApplicationMappingsRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
@ -21,6 +22,7 @@ import org.keycloak.representations.idm.MappingsRepresentation;
|
|||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.SocialLinkRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||
import org.keycloak.services.email.EmailException;
|
||||
import org.keycloak.services.email.EmailSender;
|
||||
import org.keycloak.services.managers.AccessCodeEntry;
|
||||
|
@ -181,6 +183,26 @@ public class UsersResource {
|
|||
return stats;
|
||||
}
|
||||
|
||||
@Path("{username}/sessions")
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<UserSessionRepresentation> getSessions(final @PathParam("username") String username) {
|
||||
logger.info("sessions");
|
||||
auth.requireView();
|
||||
UserModel user = realm.getUser(username);
|
||||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
List<UserSessionModel> sessions = realm.getUserSessions(user);
|
||||
List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
|
||||
for (UserSessionModel session : sessions) {
|
||||
UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
|
||||
reps.add(rep);
|
||||
}
|
||||
return reps;
|
||||
}
|
||||
|
||||
@Path("{username}/social-links")
|
||||
@GET
|
||||
@NoCache
|
||||
|
@ -208,6 +230,7 @@ public class UsersResource {
|
|||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
realm.removeUserSessions(user);
|
||||
// set notBefore so that user will be forced to log in.
|
||||
user.setNotBefore(Time.currentTime());
|
||||
new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user.getId(), null);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
|
|
Loading…
Reference in a new issue