keycloak-aplcache
Changes
forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js 33(+7 -26)
forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js 25(+11 -14)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-sessions.html 23(+8 -15)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html 4(+1 -3)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-sessions.html 30(+22 -8)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java 2(+1 -1)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java 112(+68 -44)
integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java 2(+1 -1)
integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java 113(+65 -48)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java 2(+1 -1)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java 10(+7 -3)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java 152(+90 -62)
model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java 83(+83 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java 76(+71 -5)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java 6(+6 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientUserSessionAssociationEntity.java 37(+37 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java 6(+6 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java 3(+3 -0)
Details
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserSessionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserSessionRepresentation.java
new file mode 100755
index 0000000..b3f3c5a
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/UserSessionRepresentation.java
@@ -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;
+ }
+}
diff --git a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
index c64ff41..066aec8 100755
--- a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
@@ -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
}
diff --git a/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
index 2e02cc3..adf176c 100755
--- a/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
@@ -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
}
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
index f73d947..e633dca 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
@@ -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'
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
index 9f7615d..ef54e1e 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
@@ -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,
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js
index ee05261..35e4fb0 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js
@@ -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) {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js
index 53101e4..81a77b7 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js
@@ -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 {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
index 6d33070..0294169 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
@@ -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',
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-sessions.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-sessions.html
index 1bb3682..93c388e 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-sessions.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-sessions.html
@@ -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>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html
index c50a192..bea3c02 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html
@@ -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>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-sessions.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-sessions.html
index ef90aa2..cb84ccb 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-sessions.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-sessions.html
@@ -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>
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
index 0842b8d..16203a8 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -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()) {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
index 94da30f..ca4653a 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
@@ -15,5 +15,7 @@ public interface UserSessionManagement {
void logoutAll();
- void logout(String user);
+ void logoutUser(String user);
+
+ void logoutKeycloakSession(String id);
}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
index 9e211b2..9a5ea4e 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
@@ -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
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java
index 52af993..ecca57e 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java
@@ -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);
-
- }
+ 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()) {
- userSessionMap.remove(username);
- }
+ 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);
}
+
}
}
}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
index 7ed1f1d..27a003d 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
@@ -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
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
index a84284e..8b521e8 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
@@ -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);
-
- }
+ 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()) {
- userSessionMap.remove(username);
- }
+ 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);
}
+
}
}
}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
index f9c2edf..f73227f 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
@@ -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());
}
}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
index 4a5a4c2..11682f0 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
@@ -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);
}
}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
index ff75800..1c11d37 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
@@ -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();
- userSessionMap.put(username, sessions);
- }
- sessions.getSessionIds().add(sessionId);
+ UserSessions sessions = userSessionMap.get(username);
+ if (sessions == null) {
+ sessions = new UserSessions();
+ sessions.user = username;
+ userSessionMap.put(username, sessions);
}
- }
-
- 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);
- }
+ sessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
+ sessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
+ keycloakSessionMap.put(keycloakSessionId, sessions);
+ if (!registered) {
+ manager.registerSessionListener(this);
+ registered = true;
}
}
- public void logoutAll(SessionManager manager) {
- List<String> users = new ArrayList<String>();
- users.addAll(userSessionMap.keySet());
- for (String user : users) logout(manager, user);
+ public synchronized void logoutAll(SessionManager manager) {
+ for (String user : userSessionMap.keySet()) logoutUser(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);
- }
+ 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
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index b824db6..d88812d 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -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();
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index c48613b..68a89ea 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -263,4 +263,7 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
void removeExpiredUserSessions();
+ ClientModel findClientById(String id);
+
+ void removeUserSessions();
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
index e2e16a7..ada5899 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -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);
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index d23a0f4..6616c36 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -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;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 3267344..99e5d1b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -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() {
@@ -142,6 +150,31 @@ public class ClientAdapter implements ClientModel {
}
@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;
if (!this.getClass().equals(o.getClass())) return false;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java
new file mode 100755
index 0000000..3c38142
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java
@@ -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;
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
index 62769ea..976df2f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
@@ -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;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
index 5614053..36aabd6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
@@ -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());
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/OAuthClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/OAuthClientAdapter.java
index 48d0e4f..cd78093 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/OAuthClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/OAuthClientAdapter.java
@@ -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
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 3f420c8..95e5ed0 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -72,6 +72,10 @@ public class RealmAdapter implements RealmModel {
this.realm = realm;
}
+ public RealmEntity getEntity() {
+ return realm;
+ }
+
@Override
public String getId() {
return realm.getId();
@@ -583,6 +587,13 @@ public class RealmAdapter implements RealmModel {
}
@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>();
for (ApplicationModel app : getApplications()) {
@@ -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;
}
@@ -1418,11 +1432,19 @@ public class RealmAdapter implements RealmModel {
}
@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();
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
index 0de48e5..79e1827 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
@@ -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();
+ }
}
diff --git a/model/jpa/src/test/resources/META-INF/persistence.xml b/model/jpa/src/test/resources/META-INF/persistence.xml
index 900cb5d..c79ddab 100755
--- a/model/jpa/src/test/resources/META-INF/persistence.xml
+++ b/model/jpa/src/test/resources/META-INF/persistence.xml
@@ -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>
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
old mode 100644
new mode 100755
index f651f3a..b7404fd
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -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();
+ }
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index eb61b5b..f7e82ec 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -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
@@ -1396,6 +1405,12 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
@Override
+ public void removeUserSessions() {
+ DBObject query = new BasicDBObject("realmId", getId());
+ getMongoStore().removeEntities(MongoUserSessionEntity.class, query, invocationContext);
+ }
+
+ @Override
public void removeExpiredUserSessions() {
int currentTime = Time.currentTime();
DBObject query = new QueryBuilder()
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java
index 675e4ba..78dfcb8 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java
@@ -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();
+ }
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java
index c4a8d1a..bb83e0e 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java
@@ -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);
+
}
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientUserSessionAssociationEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientUserSessionAssociationEntity.java
new file mode 100755
index 0000000..d28e561
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientUserSessionAssociationEntity.java
@@ -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) {
+ }
+
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java
index f9b6959..a499e51 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java
@@ -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);
}
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
index 1bd2f90..d091619 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
@@ -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);
}
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
index 2d98d19..2389840 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
@@ -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);
}
}
diff --git a/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml b/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml
index 616f8e8..a75390a 100755
--- a/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml
+++ b/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml
@@ -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>
diff --git a/server/src/main/resources/META-INF/persistence.xml b/server/src/main/resources/META-INF/persistence.xml
index 616f8e8..a75390a 100755
--- a/server/src/main/resources/META-INF/persistence.xml
+++ b/server/src/main/resources/META-INF/persistence.xml
@@ -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>
diff --git a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
index 91c7686..c2d4ea6 100755
--- a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
+++ b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
@@ -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;
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index ce06542..2498eda 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -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();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
index d1c3706..7927fad 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
@@ -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;
/**
@@ -228,7 +233,32 @@ public class ApplicationResource {
logger.info("activeSessions: " + stats.getActiveSessions());
}
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
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 862aeb2..7a089d8 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -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
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 1002fc2..feba6f9 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -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);
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index e9c2c80..d1e51ac 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -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()
diff --git a/testsuite/integration/src/main/resources/META-INF/persistence.xml b/testsuite/integration/src/main/resources/META-INF/persistence.xml
index a5a7849..6e68551 100755
--- a/testsuite/integration/src/main/resources/META-INF/persistence.xml
+++ b/testsuite/integration/src/main/resources/META-INF/persistence.xml
@@ -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>
diff --git a/testsuite/performance/src/test/resources/META-INF/persistence.xml b/testsuite/performance/src/test/resources/META-INF/persistence.xml
index e5ae89a..13695fa 100755
--- a/testsuite/performance/src/test/resources/META-INF/persistence.xml
+++ b/testsuite/performance/src/test/resources/META-INF/persistence.xml
@@ -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>