keycloak-aplcache
Changes
core/src/main/java/org/keycloak/representations/idm/OfflineClientSessionRepresentation.java 35(+35 -0)
core/src/main/java/org/keycloak/representations/idm/OfflineUserSessionRepresentation.java 37(+37 -0)
export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java 25(+25 -0)
forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java 2(+1 -1)
forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java 5(+3 -2)
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js 48(+48 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html 57(+57 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html 6(+6 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html 7(+7 -0)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java 105(+98 -7)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java 48(+0 -48)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java 7(+4 -3)
Details
diff --git a/core/src/main/java/org/keycloak/representations/idm/OfflineClientSessionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/OfflineClientSessionRepresentation.java
new file mode 100644
index 0000000..b2374bc
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/OfflineClientSessionRepresentation.java
@@ -0,0 +1,35 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OfflineClientSessionRepresentation {
+
+ private String clientSessionId;
+ private String client; // clientId (not DB ID)
+ private String data;
+
+ public String getClientSessionId() {
+ return clientSessionId;
+ }
+
+ public void setClientSessionId(String clientSessionId) {
+ this.clientSessionId = clientSessionId;
+ }
+
+ public String getClient() {
+ return client;
+ }
+
+ public void setClient(String client) {
+ this.client = client;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/OfflineUserSessionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/OfflineUserSessionRepresentation.java
new file mode 100644
index 0000000..e877c3e
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/OfflineUserSessionRepresentation.java
@@ -0,0 +1,37 @@
+package org.keycloak.representations.idm;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OfflineUserSessionRepresentation {
+
+ private String userSessionId;
+ private String data;
+ private List<OfflineClientSessionRepresentation> offlineClientSessions;
+
+ public String getUserSessionId() {
+ return userSessionId;
+ }
+
+ public void setUserSessionId(String userSessionId) {
+ this.userSessionId = userSessionId;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+
+ public List<OfflineClientSessionRepresentation> getOfflineClientSessions() {
+ return offlineClientSessions;
+ }
+
+ public void setOfflineClientSessions(List<OfflineClientSessionRepresentation> offlineClientSessions) {
+ this.offlineClientSessions = offlineClientSessions;
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index d724fd7..99f8f3c 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -36,6 +36,7 @@ public class UserRepresentation {
protected List<String> realmRoles;
protected Map<String, List<String>> clientRoles;
protected List<UserConsentRepresentation> clientConsents;
+ protected List<OfflineUserSessionRepresentation> offlineUserSessions;
@Deprecated
protected Map<String, List<String>> applicationRoles;
@@ -218,4 +219,12 @@ public class UserRepresentation {
public void setServiceAccountClientId(String serviceAccountClientId) {
this.serviceAccountClientId = serviceAccountClientId;
}
+
+ public List<OfflineUserSessionRepresentation> getOfflineUserSessions() {
+ return offlineUserSessions;
+ }
+
+ public void setOfflineUserSessions(List<OfflineUserSessionRepresentation> offlineUserSessions) {
+ this.offlineUserSessions = offlineUserSessions;
+ }
}
diff --git a/docbook/reference/en/en-US/modules/timeouts.xml b/docbook/reference/en/en-US/modules/timeouts.xml
index 08e0450..78418cb 100755
--- a/docbook/reference/en/en-US/modules/timeouts.xml
+++ b/docbook/reference/en/en-US/modules/timeouts.xml
@@ -1,4 +1,4 @@
- <chapter id="timeouts">
+<chapter id="timeouts">
<title>Cookie settings, Session Timeouts, and Token Lifespans</title>
<para>
Keycloak has a bunch of fine-grain settings to manage browser cookies, user login sessions, and token lifespans.
@@ -52,4 +52,38 @@
back to your application as an authentnicated user. This value is relatively short and is usually measured in minutes.
</para>
</section>
+ <section id="offline-access">
+ <title>Offline Access</title>
+ <para>
+ The Offline access is the feature described in <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess">OpenID Connect specification</ulink> .
+ The idea is that during login, your client application will request Offline token instead of classic Refresh token.
+ Then the application can save this offline token in the database and can use it anytime later even if user is logged out.
+ This is useful for example if your application needs to do some "offline" actions on behalf of user even if user is not online. For example
+ periodic backup of some data every night etc.
+ </para>
+ <para>
+ Your application is responsible for persist the offline token in some storage (usually database) and then use it to
+ manually retrieve new access token from Keycloak server.
+ </para>
+ <para>
+ The difference between classic Refresh token and Offline token is, that offline token will never expire and is not subject of <literal>SSO Session Idle timeout</literal> .
+ The offline token is valid even after user logout or server restart. User can revoke the offline tokens in Account management UI. The admin
+ user can revoke offline tokens for individual users in admin console (The <literal>Consent</literal> tab of particular user) and he can
+ see all the offline tokens of all users for particular client application in the settings of the client. Revoking of all offline tokens for particular
+ client is possible by set <literal>notBefore</literal> policy for the client.
+ </para>
+ <para>
+ For requesting the offline token, user needs to be in realm role <literal>offline_access</literal> and client needs to have
+ scope for this role. If client has <literal>Full scope allowed</literal>, the scope is granted by default. Also users are automatically
+ members of the role as it's the default role.
+ </para>
+ <para>
+ The client can request offline token by adding parameter <literal>scope=offline_access</literal>
+ when sending authorization request to Keycloak. The adapter automatically adds this parameter when you use it to access secured
+ URL of your application (ie. http://localhost:8080/customer-portal/secured?scope=offline_access ).
+ The <link linkend='direct-access-grants'>Direct Access Grant</link> or <link linkend="service-accounts">Service account</link> flows also support
+ offline tokens if you include <literal>scope=offline_access</literal> in the body of the authentication request. For more details,
+ see the <literal>offline-access-app</literal> example from Keycloak demo.
+ </para>
+ </section>
</chapter>
\ No newline at end of file
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index 5137491..88471d0 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -1,5 +1,8 @@
package org.keycloak.exportimport.util;
+import org.keycloak.models.OfflineClientSessionModel;
+import org.keycloak.models.OfflineUserSessionModel;
+import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
import org.keycloak.util.Base64;
import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonFactory;
@@ -295,6 +298,28 @@ public class ExportUtils {
}
}
+ // Offline sessions
+ List<OfflineUserSessionRepresentation> offlineSessionReps = new LinkedList<>();
+ Collection<OfflineUserSessionModel> offlineSessions = session.users().getOfflineUserSessions(realm, user);
+ Collection<OfflineClientSessionModel> offlineClientSessions = session.users().getOfflineClientSessions(realm, user);
+
+ Map<String, List<OfflineClientSessionModel>> processed = new HashMap<>();
+ for (OfflineClientSessionModel clsm : offlineClientSessions) {
+ String userSessionId = clsm.getUserSessionId();
+ List<OfflineClientSessionModel> current = processed.get(userSessionId);
+ if (current == null) {
+ current = new LinkedList<>();
+ processed.put(userSessionId, current);
+ }
+ current.add(clsm);
+ }
+
+ for (OfflineUserSessionModel userSession : offlineSessions) {
+ OfflineUserSessionRepresentation sessionRep = ModelToRepresentation.toRepresentation(realm, userSession, processed.get(userSession.getUserSessionId()));
+ offlineSessionReps.add(sessionRep);
+ }
+ userRep.setOfflineUserSessions(offlineSessionReps);
+
return userRep;
}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
index a4b3771..f782543 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
@@ -202,7 +202,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
attributes.put("sessions", new SessionsBean(realm, sessions));
break;
case APPLICATIONS:
- attributes.put("applications", new ApplicationsBean(realm, user));
+ attributes.put("applications", new ApplicationsBean(session, realm, user));
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
break;
case PASSWORD:
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java
index 371b5d0..71bf200 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java
@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Set;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
@@ -22,9 +23,9 @@ public class ApplicationsBean {
private List<ApplicationEntry> applications = new LinkedList<ApplicationEntry>();
- public ApplicationsBean(RealmModel realm, UserModel user) {
+ public ApplicationsBean(KeycloakSession session, RealmModel realm, UserModel user) {
- Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(realm, user);
+ Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(session, realm, user);
List<ClientModel> realmClients = realm.getClients();
for (ClientModel client : realmClients) {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index 350f0d0..a965325 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -637,6 +637,21 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ClientSessionsCtrl'
})
+ .when('/realms/:realm/clients/:client/offline-access', {
+ templateUrl : resourceUrl + '/partials/client-offline-sessions.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ client : function(ClientLoader) {
+ return ClientLoader();
+ },
+ offlineSessionCount : function(ClientOfflineSessionCountLoader) {
+ return ClientOfflineSessionCountLoader();
+ }
+ },
+ controller : 'ClientOfflineSessionsCtrl'
+ })
.when('/realms/:realm/clients/:client/credentials', {
templateUrl : resourceUrl + '/partials/client-credentials.html',
resolve : {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 3cb3e60..27a9794 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -507,6 +507,54 @@ module.controller('ClientSessionsCtrl', function($scope, realm, sessionCount, cl
};
});
+module.controller('ClientOfflineSessionsCtrl', function($scope, realm, offlineSessionCount, client,
+ ClientOfflineSessions) {
+ $scope.realm = realm;
+ $scope.count = offlineSessionCount.count;
+ $scope.sessions = [];
+ $scope.client = client;
+
+ $scope.page = 0;
+
+ $scope.query = {
+ realm : realm.realm,
+ client: $scope.client.id,
+ max : 5,
+ first : 0
+ }
+
+ $scope.firstPage = function() {
+ $scope.query.first = 0;
+ if ($scope.query.first < 0) {
+ $scope.query.first = 0;
+ }
+ $scope.loadUsers();
+ }
+
+ $scope.previousPage = function() {
+ $scope.query.first -= parseInt($scope.query.max);
+ if ($scope.query.first < 0) {
+ $scope.query.first = 0;
+ }
+ $scope.loadUsers();
+ }
+
+ $scope.nextPage = function() {
+ $scope.query.first += parseInt($scope.query.max);
+ $scope.loadUsers();
+ }
+
+ $scope.toDate = function(val) {
+ return new Date(val);
+ };
+
+ $scope.loadUsers = function() {
+ ClientOfflineSessions.query($scope.query, function(updated) {
+ $scope.sessions = updated;
+ })
+ };
+});
+
module.controller('ClientRoleDetailCtrl', function($scope, realm, client, role, roles, clients,
Role, ClientRole, RoleById, RoleRealmComposites, RoleClientComposites,
$http, $location, Dialog, Notifications) {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index b2a4870..3746740 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -208,9 +208,9 @@ module.controller('UserConsentsCtrl', function($scope, realm, user, userConsents
UserConsents.query({realm: realm.realm, user: user.id}, function(updated) {
$scope.userConsents = updated;
})
- Notifications.success('Consent revoked successfully');
+ Notifications.success('Grant revoked successfully');
}, function() {
- Notifications.error("Consent couldn't be revoked");
+ Notifications.error("Grant couldn't be revoked");
});
console.log("Revoke consent " + clientId);
}
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
index 6b09bc1..9c05940 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
@@ -246,6 +246,15 @@ module.factory('ClientSessionCountLoader', function(Loader, ClientSessionCount,
});
});
+module.factory('ClientOfflineSessionCountLoader', function(Loader, ClientOfflineSessionCount, $route, $q) {
+ return Loader.get(ClientOfflineSessionCount, function() {
+ return {
+ realm : $route.current.params.realm,
+ client : $route.current.params.client
+ }
+ });
+});
+
module.factory('ClientClaimsLoader', function(Loader, ClientClaims, $route, $q) {
return Loader.get(ClientClaims, function() {
return {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index 8d56c90..03f4b99 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -841,6 +841,20 @@ module.factory('ClientUserSessions', function($resource) {
});
});
+module.factory('ClientOfflineSessionCount', function($resource) {
+ return $resource(authUrl + '/admin/realms/:realm/clients/:client/offline-session-count', {
+ realm : '@realm',
+ client : "@client"
+ });
+});
+
+module.factory('ClientOfflineSessions', function($resource) {
+ return $resource(authUrl + '/admin/realms/:realm/clients/:client/offline-sessions', {
+ realm : '@realm',
+ client : "@client"
+ });
+});
+
module.factory('ClientLogoutAll', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/clients/:client/logout-all', {
realm : '@realm',
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html
new file mode 100644
index 0000000..c8afb02
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html
@@ -0,0 +1,57 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+ <ol class="breadcrumb">
+ <li><a href="#/realms/{{realm.realm}}/clients">Clients</a></li>
+ <li>{{client.clientId}}</li>
+ </ol>
+
+ <kc-tabs-client></kc-tabs-client>
+
+ <form class="form-horizontal" name="sessionStats">
+ <fieldset class="border-top">
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="activeSessions">Offline Tokens</label>
+ <div class="col-md-6">
+ <input class="form-control" type="text" id="activeSessions" name="activeSessions" data-ng-model="count" ng-disabled="true">
+ </div>
+ <kc-tooltip>Total number of active offline tokens for this client.</kc-tooltip>
+ </div>
+ </fieldset>
+ </form>
+ <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-default" ng-click="loadUsers()" tooltip-placement="left" tooltip-trigger="mouseover mouseout" tooltip="Warning, this is a potentially expensive operation depending on number of offline tokens.">Show Offline Tokens</a>
+ </div>
+ </th>
+ </tr>
+ <tr data-ng-show="sessions">
+ <th>User</th>
+ <th>From IP</th>
+ <th>Token Issued</th>
+ </tr>
+ </thead>
+ <tfoot data-ng-show="sessions && (sessions.length >= 5 || query.first != 0)">
+ <tr>
+ <td colspan="7">
+ <div class="table-nav">
+ <button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">First page</button>
+ <button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">Previous page</button>
+ <button data-ng-click="nextPage()" class="next" ng-disabled="sessions.length < query.max">Next page</button>
+ </div>
+ </td>
+ </tr>
+ </tfoot>
+ <tbody>
+ <tr data-ng-repeat="session in sessions">
+ <td><a href="#/realms/{{realm.realm}}/users/{{session.userId}}">{{session.username}}</a></td>
+ <td>{{session.ipAddress}}</td>
+ <td>{{session.start | date:'medium'}}</td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html
index 22db348..cf6db99 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html
@@ -12,6 +12,7 @@
<th>Client</th>
<th>Granted Roles</th>
<th>Granted Protocol Mappers</th>
+ <th>Additional Grants</th>
<th>Action</th>
</tr>
</thead>
@@ -35,6 +36,11 @@
</span>
</span>
</td>
+ <td>
+ <span data-ng-repeat="additionalGrant in consent.additionalGrants">
+ <span ng-if="!$first">, </span>{{additionalGrant}}
+ </span>
+ </td>
<td class="kc-action-cell">
<button class="btn btn-default btn-block btn-sm" ng-click="revokeConsent(consent.clientId)">
<i class="pficon pficon-delete"></i> Revoke
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
index 2aad2d5..f033aac 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
@@ -26,6 +26,13 @@
<kc-tooltip>View active sessions for this client. Allows you to see which users are active and when they logged in.</kc-tooltip>
</li>
+ <li ng-class="{active: path[4] == 'offline-access'}" data-ng-show="!client.bearerOnly">
+ <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/offline-access">Offline Access</a>
+ <kc-tooltip>View offline sessions for this client. Allows you to see which users retrieve offline token and when they retrieve it.
+ To revoke all tokens for the client, go to Revocation tab and set new not before value.
+ </kc-tooltip>
+ </li>
+
<li ng-class="{active: path[4] == 'clustering'}" data-ng-show="!client.publicClient"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/clustering">Clustering</a></li>
<li ng-class="{active: path[4] == 'installation'}" data-ng-show="client.protocol != 'saml'">
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
index 8010586..e2db902 100644
--- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
@@ -3,8 +3,10 @@ package org.keycloak.migration.migrators;
import java.util.List;
import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
@@ -17,6 +19,16 @@ public class MigrateTo1_6_0 {
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {
+
+ for (RoleModel realmRole : realm.getRoles()) {
+ realmRole.setScopeParamRequired(false);
+ }
+ for (ClientModel client : realm.getClients()) {
+ for (RoleModel clientRole : client.getRoles()) {
+ clientRole.setScopeParamRequired(false);
+ }
+ }
+
KeycloakModelUtils.setupOfflineTokens(realm);
}
diff --git a/model/api/src/main/java/org/keycloak/models/OfflineClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/OfflineClientSessionModel.java
index 47ae23a..59e325d 100644
--- a/model/api/src/main/java/org/keycloak/models/OfflineClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/OfflineClientSessionModel.java
@@ -8,6 +8,7 @@ public class OfflineClientSessionModel {
private String clientSessionId;
private String userSessionId;
private String clientId;
+ private String userId;
private String data;
public String getClientSessionId() {
@@ -34,6 +35,14 @@ public class OfflineClientSessionModel {
this.clientId = clientId;
}
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
public String getData() {
return data;
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index 13c05f8..0500308 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -475,6 +476,70 @@ public class UserFederationManager implements UserProvider {
}
@Override
+ public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
+ validateUser(realm, user);
+ if (user == null) throw new IllegalStateException("Federated user no longer valid");
+ session.userStorage().addOfflineUserSession(realm, user, offlineUserSession);
+ }
+
+ @Override
+ public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
+ validateUser(realm, user);
+ if (user == null) throw new IllegalStateException("Federated user no longer valid");
+ return session.userStorage().getOfflineUserSession(realm, user, userSessionId);
+ }
+
+ @Override
+ public Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user) {
+ validateUser(realm, user);
+ if (user == null) throw new IllegalStateException("Federated user no longer valid");
+ return session.userStorage().getOfflineUserSessions(realm, user);
+ }
+
+ @Override
+ public boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
+ validateUser(realm, user);
+ if (user == null) throw new IllegalStateException("Federated user no longer valid");
+ return session.userStorage().removeOfflineUserSession(realm, user, userSessionId);
+ }
+
+ @Override
+ public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession) {
+ session.userStorage().addOfflineClientSession(realm, offlineClientSession);
+ }
+
+ @Override
+ public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
+ validateUser(realm, user);
+ if (user == null) throw new IllegalStateException("Federated user no longer valid");
+ return session.userStorage().getOfflineClientSession(realm, user, clientSessionId);
+ }
+
+ @Override
+ public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
+ validateUser(realm, user);
+ if (user == null) throw new IllegalStateException("Federated user no longer valid");
+ return session.userStorage().getOfflineClientSessions(realm, user);
+ }
+
+ @Override
+ public boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
+ validateUser(realm, user);
+ if (user == null) throw new IllegalStateException("Federated user no longer valid");
+ return session.userStorage().removeOfflineClientSession(realm, user, clientSessionId);
+ }
+
+ @Override
+ public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
+ return session.userStorage().getOfflineClientSessionsCount(realm, client);
+ }
+
+ @Override
+ public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
+ return session.userStorage().getOfflineClientSessions(realm, client, firstResult, maxResults);
+ }
+
+ @Override
public void close() {
}
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 51e8fc4..f57bb04 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -114,15 +114,6 @@ public interface UserModel {
void updateConsent(UserConsentModel consent);
boolean revokeConsentForClient(String clientInternalId);
- void addOfflineUserSession(OfflineUserSessionModel offlineUserSession);
- OfflineUserSessionModel getOfflineUserSession(String userSessionId);
- Collection<OfflineUserSessionModel> getOfflineUserSessions();
- boolean removeOfflineUserSession(String userSessionId);
- void addOfflineClientSession(OfflineClientSessionModel offlineClientSession);
- OfflineClientSessionModel getOfflineClientSession(String clientSessionId);
- Collection<OfflineClientSessionModel> getOfflineClientSessions();
- boolean removeOfflineClientSession(String clientSessionId);
-
public static enum RequiredAction {
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserProvider.java b/model/api/src/main/java/org/keycloak/models/UserProvider.java
index 8712f2b..0012b24 100755
--- a/model/api/src/main/java/org/keycloak/models/UserProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserProvider.java
@@ -2,6 +2,7 @@ package org.keycloak.models;
import org.keycloak.provider.Provider;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -55,5 +56,17 @@ public interface UserProvider extends Provider {
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input);
+ void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession);
+ OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId);
+ Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user);
+ boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId);
+ void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession);
+ OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId);
+ Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user);
+ boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId);
+
+ int getOfflineClientSessionsCount(RealmModel realm, ClientModel client);
+ Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int first, int max);
+
void close();
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 9906ab2..6b19606 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -10,6 +10,8 @@ import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.OfflineClientSessionModel;
+import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
@@ -30,6 +32,8 @@ import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.OfflineClientSessionRepresentation;
+import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -43,6 +47,7 @@ import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.util.Time;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@@ -506,7 +511,31 @@ public class ModelToRepresentation {
return rep;
}
+ public static OfflineUserSessionRepresentation toRepresentation(RealmModel realm, OfflineUserSessionModel model, Collection<OfflineClientSessionModel> clientSessions) {
+ OfflineUserSessionRepresentation rep = new OfflineUserSessionRepresentation();
+ rep.setData(model.getData());
+ rep.setUserSessionId(model.getUserSessionId());
+ List<OfflineClientSessionRepresentation> clientSessionReps = new LinkedList<>();
+ for (OfflineClientSessionModel clsm : clientSessions) {
+ OfflineClientSessionRepresentation clrep = toRepresentation(realm, clsm);
+ clientSessionReps.add(clrep);
+ }
+ rep.setOfflineClientSessions(clientSessionReps);
+ return rep;
+ }
+
+ public static OfflineClientSessionRepresentation toRepresentation(RealmModel realm, OfflineClientSessionModel model) {
+ OfflineClientSessionRepresentation rep = new OfflineClientSessionRepresentation();
+
+ String clientInternalId = model.getClientId();
+ ClientModel client = realm.getClientById(clientInternalId);
+ rep.setClient(client.getClientId());
+
+ rep.setClientSessionId(model.getClientSessionId());
+ rep.setData(model.getData());
+ return rep;
+ }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index ffb9673..4829182 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -1,5 +1,9 @@
package org.keycloak.models.utils;
+import org.keycloak.models.OfflineClientSessionModel;
+import org.keycloak.models.OfflineUserSessionModel;
+import org.keycloak.representations.idm.OfflineClientSessionRepresentation;
+import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
import org.keycloak.util.Base64;
import org.jboss.logging.Logger;
import org.keycloak.enums.SslRequired;
@@ -981,6 +985,11 @@ public class RepresentationToModel {
user.addConsent(consentModel);
}
}
+ if (userRep.getOfflineUserSessions() != null) {
+ for (OfflineUserSessionRepresentation sessionRep : userRep.getOfflineUserSessions()) {
+ importOfflineSession(session, newRealm, user, sessionRep);
+ }
+ }
if (userRep.getServiceAccountClientId() != null) {
String clientId = userRep.getServiceAccountClientId();
ClientModel client = clientMap.get(clientId);
@@ -1151,6 +1160,29 @@ public class RepresentationToModel {
return consentModel;
}
+ public static void importOfflineSession(KeycloakSession session, RealmModel newRealm, UserModel user, OfflineUserSessionRepresentation sessionRep) {
+ OfflineUserSessionModel model = new OfflineUserSessionModel();
+ model.setUserSessionId(sessionRep.getUserSessionId());
+ model.setData(sessionRep.getData());
+ session.users().addOfflineUserSession(newRealm, user, model);
+
+ for (OfflineClientSessionRepresentation csRep : sessionRep.getOfflineClientSessions()) {
+ OfflineClientSessionModel csModel = new OfflineClientSessionModel();
+ String clientId = csRep.getClient();
+ ClientModel client = newRealm.getClientByClientId(clientId);
+ if (client == null) {
+ throw new RuntimeException("Unable to find client " + clientId + " referenced from offlineClientSession of user " + user.getUsername());
+ }
+ csModel.setClientId(client.getId());
+ csModel.setUserId(user.getId());
+ csModel.setClientSessionId(csRep.getClientSessionId());
+ csModel.setUserSessionId(sessionRep.getUserSessionId());
+ csModel.setData(csRep.getData());
+
+ session.users().addOfflineClientSession(newRealm, csModel);
+ }
+ }
+
public static AuthenticationFlowModel toModel(AuthenticationFlowRepresentation rep) {
AuthenticationFlowModel model = new AuthenticationFlowModel();
model.setBuiltIn(rep.isBuiltIn());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index f727c75..5f73031 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -258,44 +258,4 @@ public class UserModelDelegate implements UserModel {
public void setCreatedTimestamp(Long timestamp){
delegate.setCreatedTimestamp(timestamp);
}
-
- @Override
- public void addOfflineUserSession(OfflineUserSessionModel userSession) {
- delegate.addOfflineUserSession(userSession);
- }
-
- @Override
- public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
- return delegate.getOfflineUserSession(userSessionId);
- }
-
- @Override
- public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
- return delegate.getOfflineUserSessions();
- }
-
- @Override
- public boolean removeOfflineUserSession(String userSessionId) {
- return delegate.removeOfflineUserSession(userSessionId);
- }
-
- @Override
- public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
- delegate.addOfflineClientSession(clientSession);
- }
-
- @Override
- public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
- return delegate.getOfflineClientSession(clientSessionId);
- }
-
- @Override
- public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
- return delegate.getOfflineClientSessions();
- }
-
- @Override
- public boolean removeOfflineClientSession(String clientSessionId) {
- return delegate.removeOfflineClientSession(clientSessionId);
- }
}
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
index d89ce63..2c233d1 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
@@ -574,141 +574,6 @@ public class UserAdapter implements UserModel, Comparable {
return false;
}
- @Override
- public void addOfflineUserSession(OfflineUserSessionModel userSession) {
- if (user.getOfflineUserSessions() == null) {
- user.setOfflineUserSessions(new ArrayList<OfflineUserSessionEntity>());
- }
-
- if (getUserSessionEntityById(userSession.getUserSessionId()) != null) {
- throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + user.getUsername());
- }
-
- OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
- entity.setUserSessionId(userSession.getUserSessionId());
- entity.setData(userSession.getData());
- entity.setOfflineClientSessions(new ArrayList<OfflineClientSessionEntity>());
- user.getOfflineUserSessions().add(entity);
- }
-
- @Override
- public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
- OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
- return entity==null ? null : toModel(entity);
- }
-
- @Override
- public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
- if (user.getOfflineUserSessions()==null) {
- return Collections.emptyList();
- } else {
- List<OfflineUserSessionModel> result = new ArrayList<>();
- for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
- result.add(toModel(entity));
- }
- return result;
- }
- }
-
- private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
- OfflineUserSessionModel model = new OfflineUserSessionModel();
- model.setUserSessionId(entity.getUserSessionId());
- model.setData(entity.getData());
- return model;
- }
-
- @Override
- public boolean removeOfflineUserSession(String userSessionId) {
- OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
- if (entity != null) {
- user.getOfflineUserSessions().remove(entity);
- return true;
- } else {
- return false;
- }
- }
-
- private OfflineUserSessionEntity getUserSessionEntityById(String userSessionId) {
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
- if (entity.getUserSessionId().equals(userSessionId)) {
- return entity;
- }
- }
- }
- return null;
- }
-
- @Override
- public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
- OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(clientSession.getUserSessionId());
- if (userSessionEntity == null) {
- throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + user.getUsername());
- }
-
- OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
- clEntity.setClientSessionId(clientSession.getClientSessionId());
- clEntity.setClientId(clientSession.getClientId());
- clEntity.setData(clientSession.getData());
-
- userSessionEntity.getOfflineClientSessions().add(clEntity);
- }
-
- @Override
- public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- if (clSession.getClientSessionId().equals(clientSessionId)) {
- return toModel(clSession, userSession.getUserSessionId());
- }
- }
- }
- }
-
- return null;
- }
-
- private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userSessionId) {
- OfflineClientSessionModel model = new OfflineClientSessionModel();
- model.setClientSessionId(cls.getClientSessionId());
- model.setClientId(cls.getClientId());
- model.setData(cls.getData());
- model.setUserSessionId(userSessionId);
- return model;
- }
-
- @Override
- public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
- List<OfflineClientSessionModel> result = new ArrayList<>();
-
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- result.add(toModel(clSession, userSession.getUserSessionId()));
- }
- }
- }
-
- return result;
- }
-
- @Override
- public boolean removeOfflineClientSession(String clientSessionId) {
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- if (clSession.getClientSessionId().equals(clientSessionId)) {
- userSession.getOfflineClientSessions().remove(clSession);
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
@Override
public boolean equals(Object o) {
diff --git a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
index 40547a8..9f4080a 100755
--- a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
+++ b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
@@ -23,6 +23,9 @@ import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.OfflineClientSessionModel;
+import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
@@ -32,6 +35,8 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.entities.FederatedIdentityEntity;
+import org.keycloak.models.entities.OfflineClientSessionEntity;
+import org.keycloak.models.entities.OfflineUserSessionEntity;
import org.keycloak.models.entities.UserEntity;
import org.keycloak.models.file.adapter.UserAdapter;
import org.keycloak.models.utils.CredentialValidation;
@@ -41,6 +46,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -489,4 +495,187 @@ public class FileUserProvider implements UserProvider {
return null; // not supported yet
}
+ @Override
+ public void addOfflineUserSession(RealmModel realm, UserModel userModel, OfflineUserSessionModel userSession) {
+ userModel = getUserById(userModel.getId(), realm);
+ UserEntity userEntity = ((UserAdapter) userModel).getUserEntity();
+
+ if (userEntity.getOfflineUserSessions() == null) {
+ userEntity.setOfflineUserSessions(new ArrayList<OfflineUserSessionEntity>());
+ }
+
+ if (getUserSessionEntityById(userEntity, userSession.getUserSessionId()) != null) {
+ throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + userEntity.getUsername());
+ }
+
+ OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
+ entity.setUserSessionId(userSession.getUserSessionId());
+ entity.setData(userSession.getData());
+ entity.setOfflineClientSessions(new ArrayList<OfflineClientSessionEntity>());
+ userEntity.getOfflineUserSessions().add(entity);
+ }
+
+ @Override
+ public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
+ userModel = getUserById(userModel.getId(), realm);
+ UserEntity userEntity = ((UserAdapter) userModel).getUserEntity();
+
+ OfflineUserSessionEntity entity = getUserSessionEntityById(userEntity, userSessionId);
+ return entity==null ? null : toModel(entity);
+ }
+
+ @Override
+ public Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel userModel) {
+ userModel = getUserById(userModel.getId(), realm);
+ UserEntity user = ((UserAdapter) userModel).getUserEntity();
+
+ if (user.getOfflineUserSessions()==null) {
+ return Collections.emptyList();
+ } else {
+ List<OfflineUserSessionModel> result = new ArrayList<>();
+ for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
+ result.add(toModel(entity));
+ }
+ return result;
+ }
+ }
+
+ private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
+ OfflineUserSessionModel model = new OfflineUserSessionModel();
+ model.setUserSessionId(entity.getUserSessionId());
+ model.setData(entity.getData());
+ return model;
+ }
+
+ @Override
+ public boolean removeOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
+ userModel = getUserById(userModel.getId(), realm);
+ UserEntity user = ((UserAdapter) userModel).getUserEntity();
+
+ OfflineUserSessionEntity entity = getUserSessionEntityById(user, userSessionId);
+ if (entity != null) {
+ user.getOfflineUserSessions().remove(entity);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private OfflineUserSessionEntity getUserSessionEntityById(UserEntity user, String userSessionId) {
+ if (user.getOfflineUserSessions() != null) {
+ for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
+ if (entity.getUserSessionId().equals(userSessionId)) {
+ return entity;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel clientSession) {
+ UserModel userModel = getUserById(clientSession.getUserId(), realm);
+ UserEntity user = ((UserAdapter) userModel).getUserEntity();
+
+ OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(user, clientSession.getUserSessionId());
+ if (userSessionEntity == null) {
+ throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + user.getUsername());
+ }
+
+ OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
+ clEntity.setClientSessionId(clientSession.getClientSessionId());
+ clEntity.setClientId(clientSession.getClientId());
+ clEntity.setData(clientSession.getData());
+
+ userSessionEntity.getOfflineClientSessions().add(clEntity);
+ }
+
+ @Override
+ public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
+ userModel = getUserById(userModel.getId(), realm);
+ UserEntity user = ((UserAdapter) userModel).getUserEntity();
+
+ if (user.getOfflineUserSessions() != null) {
+ for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
+ for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
+ if (clSession.getClientSessionId().equals(clientSessionId)) {
+ return toModel(clSession, userSession.getUserSessionId());
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userSessionId) {
+ OfflineClientSessionModel model = new OfflineClientSessionModel();
+ model.setClientSessionId(cls.getClientSessionId());
+ model.setClientId(cls.getClientId());
+ model.setData(cls.getData());
+ model.setUserSessionId(userSessionId);
+ return model;
+ }
+
+ @Override
+ public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel userModel) {
+ userModel = getUserById(userModel.getId(), realm);
+ UserEntity user = ((UserAdapter) userModel).getUserEntity();
+
+ List<OfflineClientSessionModel> result = new ArrayList<>();
+
+ if (user.getOfflineUserSessions() != null) {
+ for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
+ for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
+ result.add(toModel(clSession, userSession.getUserSessionId()));
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean removeOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
+ userModel = getUserById(userModel.getId(), realm);
+ UserEntity user = ((UserAdapter) userModel).getUserEntity();
+
+ if (user.getOfflineUserSessions() != null) {
+ for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
+ for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
+ if (clSession.getClientSessionId().equals(clientSessionId)) {
+ userSession.getOfflineClientSessions().remove(clSession);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
+ return getOfflineClientSessions(realm, client, -1, -1).size();
+ }
+
+ @Override
+ public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
+ List<OfflineClientSessionModel> result = new LinkedList<>();
+
+ List<UserModel> users = new ArrayList<>(inMemoryModel.getUsers(realm.getId()));
+ users = sortedSubList(users, firstResult, maxResults);
+
+ for (UserModel userModel : users) {
+ UserEntity user = ((UserAdapter) userModel).getUserEntity();
+ for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
+ for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
+ if (clSession.getClientId().equals(client.getId())) {
+ result.add(toModel(clSession, userSession.getUserSessionId()));
+ }
+ }
+ }
+ }
+ return result;
+ }
}
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
index df7c144..584e91f 100644
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
@@ -104,13 +104,14 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
};
}
+ private boolean isRegisteredForInvalidation(RealmModel realm, String userId) {
+ return realmInvalidations.contains(realm.getId()) || userInvalidations.containsKey(userId);
+ }
+
@Override
public UserModel getUserById(String id, RealmModel realm) {
if (!cache.isEnabled()) return getDelegate().getUserById(id, realm);
- if (realmInvalidations.contains(realm.getId())) {
- return getDelegate().getUserById(id, realm);
- }
- if (userInvalidations.containsKey(id)) {
+ if (isRegisteredForInvalidation(realm, id)) {
return getDelegate().getUserById(id, realm);
}
@@ -120,7 +121,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
if (model == null) return null;
if (managedUsers.containsKey(id)) return managedUsers.get(id);
if (userInvalidations.containsKey(id)) return model;
- cached = new CachedUser(realm, model);
+ cached = new CachedUser(this, realm, model);
cache.addCachedUser(realm.getId(), cached);
} else if (managedUsers.containsKey(id)) {
return managedUsers.get(id);
@@ -145,7 +146,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
if (model == null) return null;
if (managedUsers.containsKey(model.getId())) return managedUsers.get(model.getId());
if (userInvalidations.containsKey(model.getId())) return model;
- cached = new CachedUser(realm, model);
+ cached = new CachedUser(this, realm, model);
cache.addCachedUser(realm.getId(), cached);
} else if (userInvalidations.containsKey(cached.getId())) {
return getDelegate().getUserById(cached.getId(), realm);
@@ -172,7 +173,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
UserModel model = getDelegate().getUserByEmail(email, realm);
if (model == null) return null;
if (userInvalidations.containsKey(model.getId())) return model;
- cached = new CachedUser(realm, model);
+ cached = new CachedUser(this, realm, model);
cache.addCachedUser(realm.getId(), cached);
} else if (userInvalidations.containsKey(cached.getId())) {
return getDelegate().getUserByEmail(email, realm);
@@ -327,4 +328,94 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
getDelegate().preRemove(client, protocolMapper);
}
+
+ @Override
+ public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
+ registerUserInvalidation(realm, user.getId());
+ getDelegate().addOfflineUserSession(realm, user, offlineUserSession);
+ }
+
+ @Override
+ public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
+ if (isRegisteredForInvalidation(realm, user.getId())) {
+ return getDelegate().getOfflineUserSession(realm, user, userSessionId);
+ }
+
+ CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
+ if (cachedUser == null) {
+ return getDelegate().getOfflineUserSession(realm, user, userSessionId);
+ } else {
+ return cachedUser.getOfflineUserSessions().get(userSessionId);
+ }
+ }
+
+ @Override
+ public Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user) {
+ if (isRegisteredForInvalidation(realm, user.getId())) {
+ return getDelegate().getOfflineUserSessions(realm, user);
+ }
+
+ CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
+ if (cachedUser == null) {
+ return getDelegate().getOfflineUserSessions(realm, user);
+ } else {
+ return cachedUser.getOfflineUserSessions().values();
+ }
+ }
+
+ @Override
+ public boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
+ registerUserInvalidation(realm, user.getId());
+ return getDelegate().removeOfflineUserSession(realm, user, userSessionId);
+ }
+
+ @Override
+ public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession) {
+ registerUserInvalidation(realm, offlineClientSession.getUserId());
+ getDelegate().addOfflineClientSession(realm, offlineClientSession);
+ }
+
+ @Override
+ public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
+ if (isRegisteredForInvalidation(realm, user.getId())) {
+ return getDelegate().getOfflineClientSession(realm, user, clientSessionId);
+ }
+
+ CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
+ if (cachedUser == null) {
+ return getDelegate().getOfflineClientSession(realm, user, clientSessionId);
+ } else {
+ return cachedUser.getOfflineClientSessions().get(clientSessionId);
+ }
+ }
+
+ @Override
+ public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
+ if (isRegisteredForInvalidation(realm, user.getId())) {
+ return getDelegate().getOfflineClientSessions(realm, user);
+ }
+
+ CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
+ if (cachedUser == null) {
+ return getDelegate().getOfflineClientSessions(realm, user);
+ } else {
+ return cachedUser.getOfflineClientSessions().values();
+ }
+ }
+
+ @Override
+ public boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
+ registerUserInvalidation(realm, user.getId());
+ return getDelegate().removeOfflineClientSession(realm, user, clientSessionId);
+ }
+
+ @Override
+ public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
+ return getDelegate().getOfflineClientSessionsCount(realm, client);
+ }
+
+ @Override
+ public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
+ return getDelegate().getOfflineClientSessions(realm, client, firstResult, maxResults);
+ }
}
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index 769f1b4..5a74b01 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -348,52 +348,4 @@ public class UserAdapter implements UserModel {
getDelegateForUpdate();
return updated.revokeConsentForClient(clientId);
}
-
- @Override
- public void addOfflineUserSession(OfflineUserSessionModel userSession) {
- getDelegateForUpdate();
- updated.addOfflineUserSession(userSession);
- }
-
- @Override
- public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
- if (updated != null) return updated.getOfflineUserSession(userSessionId);
- return cached.getOfflineUserSessions().get(userSessionId);
- }
-
- @Override
- public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
- if (updated != null) return updated.getOfflineUserSessions();
- return cached.getOfflineUserSessions().values();
- }
-
- @Override
- public boolean removeOfflineUserSession(String userSessionId) {
- getDelegateForUpdate();
- return updated.removeOfflineUserSession(userSessionId);
- }
-
- @Override
- public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
- getDelegateForUpdate();
- updated.addOfflineClientSession(clientSession);
- }
-
- @Override
- public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
- if (updated != null) return updated.getOfflineClientSession(clientSessionId);
- return cached.getOfflineClientSessions().get(clientSessionId);
- }
-
- @Override
- public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
- if (updated != null) return updated.getOfflineClientSessions();
- return cached.getOfflineClientSessions().values();
- }
-
- @Override
- public boolean removeOfflineClientSession(String clientSessionId) {
- getDelegateForUpdate();
- return updated.removeOfflineClientSession(clientSessionId);
- }
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
index d38b6f9..24e866a 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
@@ -6,6 +6,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.util.MultivaluedHashMap;
import java.io.Serializable;
@@ -40,7 +41,7 @@ public class CachedUser implements Serializable {
private Map<String, OfflineUserSessionModel> offlineUserSessions = new HashMap<>();
private Map<String, OfflineClientSessionModel> offlineClientSessions = new HashMap<>();
- public CachedUser(RealmModel realm, UserModel user) {
+ public CachedUser(CacheUserProvider cacheUserProvider, RealmModel realm, UserModel user) {
this.id = user.getId();
this.realm = realm.getId();
this.username = user.getUsername();
@@ -59,10 +60,10 @@ public class CachedUser implements Serializable {
for (RoleModel role : user.getRoleMappings()) {
roleMappings.add(role.getId());
}
- for (OfflineUserSessionModel offlineSession : user.getOfflineUserSessions()) {
+ for (OfflineUserSessionModel offlineSession : cacheUserProvider.getDelegate().getOfflineUserSessions(realm, user)) {
offlineUserSessions.put(offlineSession.getUserSessionId(), offlineSession);
}
- for (OfflineClientSessionModel offlineSession : user.getOfflineClientSessions()) {
+ for (OfflineClientSessionModel offlineSession : cacheUserProvider.getDelegate().getOfflineClientSessions(realm, user)) {
offlineClientSessions.put(offlineSession.getClientSessionId(), offlineSession);
}
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/OfflineClientSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/OfflineClientSessionEntity.java
index 23081c4..e1c636a 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/OfflineClientSessionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/OfflineClientSessionEntity.java
@@ -16,7 +16,9 @@ import javax.persistence.Table;
@NamedQueries({
@NamedQuery(name="deleteOfflineClientSessionsByRealm", query="delete from OfflineClientSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteOfflineClientSessionsByRealmAndLink", query="delete from OfflineClientSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
- @NamedQuery(name="deleteOfflineClientSessionsByClient", query="delete from OfflineClientSessionEntity sess where sess.clientId=:clientId")
+ @NamedQuery(name="deleteOfflineClientSessionsByClient", query="delete from OfflineClientSessionEntity sess where sess.clientId=:clientId"),
+ @NamedQuery(name="findOfflineClientSessionsCountByClient", query="select count(sess) from OfflineClientSessionEntity sess where sess.clientId=:clientId"),
+ @NamedQuery(name="findOfflineClientSessionsByClient", query="select sess from OfflineClientSessionEntity sess where sess.clientId=:clientId order by sess.user.username")
})
@Table(name="OFFLINE_CLIENT_SESSION")
@Entity
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 8cee4ea..563e898 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -4,6 +4,8 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OfflineClientSessionModel;
+import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
@@ -13,6 +15,8 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
+import org.keycloak.models.jpa.entities.OfflineClientSessionEntity;
+import org.keycloak.models.jpa.entities.OfflineUserSessionEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.utils.CredentialValidation;
@@ -22,8 +26,10 @@ import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -473,4 +479,167 @@ public class JpaUserProvider implements UserProvider {
// Not supported yet
return null;
}
+
+ @Override
+ public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+
+ OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
+ entity.setUser(userEntity);
+ entity.setUserSessionId(offlineUserSession.getUserSessionId());
+ entity.setData(offlineUserSession.getData());
+ em.persist(entity);
+ userEntity.getOfflineUserSessions().add(entity);
+ em.flush();
+ }
+
+ @Override
+ public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+
+ for (OfflineUserSessionEntity entity : userEntity.getOfflineUserSessions()) {
+ if (entity.getUserSessionId().equals(userSessionId)) {
+ return toModel(entity);
+ }
+ }
+ return null;
+ }
+
+ private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
+ OfflineUserSessionModel model = new OfflineUserSessionModel();
+ model.setUserSessionId(entity.getUserSessionId());
+ model.setData(entity.getData());
+ return model;
+ }
+
+ @Override
+ public Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+
+ List<OfflineUserSessionModel> result = new LinkedList<>();
+ for (OfflineUserSessionEntity entity : userEntity.getOfflineUserSessions()) {
+ result.add(toModel(entity));
+ }
+ return result;
+ }
+
+ @Override
+ public boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+
+ OfflineUserSessionEntity found = null;
+ for (OfflineUserSessionEntity session : userEntity.getOfflineUserSessions()) {
+ if (session.getUserSessionId().equals(userSessionId)) {
+ found = session;
+ break;
+ }
+ }
+
+ if (found == null) {
+ return false;
+ } else {
+ userEntity.getOfflineUserSessions().remove(found);
+ em.remove(found);
+ em.flush();
+ return true;
+ }
+ }
+
+ @Override
+ public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession) {
+ UserEntity userEntity = em.getReference(UserEntity.class, offlineClientSession.getUserId());
+
+ OfflineClientSessionEntity entity = new OfflineClientSessionEntity();
+ entity.setUser(userEntity);
+ entity.setClientSessionId(offlineClientSession.getClientSessionId());
+ entity.setUserSessionId(offlineClientSession.getUserSessionId());
+ entity.setClientId(offlineClientSession.getClientId());
+ entity.setData(offlineClientSession.getData());
+ em.persist(entity);
+ userEntity.getOfflineClientSessions().add(entity);
+ em.flush();
+ }
+
+ @Override
+ public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+
+ for (OfflineClientSessionEntity entity : userEntity.getOfflineClientSessions()) {
+ if (entity.getClientSessionId().equals(clientSessionId)) {
+ return toModel(entity);
+ }
+ }
+ return null;
+ }
+
+ private OfflineClientSessionModel toModel(OfflineClientSessionEntity entity) {
+ OfflineClientSessionModel model = new OfflineClientSessionModel();
+ model.setClientSessionId(entity.getClientSessionId());
+ model.setClientId(entity.getClientId());
+ model.setUserId(entity.getUser().getId());
+ model.setUserSessionId(entity.getUserSessionId());
+ model.setData(entity.getData());
+ return model;
+ }
+
+ @Override
+ public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+
+ List<OfflineClientSessionModel> result = new LinkedList<>();
+ for (OfflineClientSessionEntity entity : userEntity.getOfflineClientSessions()) {
+ result.add(toModel(entity));
+ }
+ return result;
+ }
+
+ @Override
+ public boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+
+ OfflineClientSessionEntity found = null;
+ for (OfflineClientSessionEntity session : userEntity.getOfflineClientSessions()) {
+ if (session.getClientSessionId().equals(clientSessionId)) {
+ found = session;
+ break;
+ }
+ }
+
+ if (found == null) {
+ return false;
+ } else {
+ userEntity.getOfflineClientSessions().remove(found);
+ em.remove(found);
+ em.flush();
+ return true;
+ }
+ }
+
+ @Override
+ public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
+ Query query = em.createNamedQuery("findOfflineClientSessionsCountByClient");
+ query.setParameter("clientId", client.getId());
+ Number n = (Number) query.getSingleResult();
+ return n.intValue();
+ }
+
+ @Override
+ public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
+ TypedQuery<OfflineClientSessionEntity> query = em.createNamedQuery("findOfflineClientSessionsByClient", OfflineClientSessionEntity.class);
+ query.setParameter("clientId", client.getId());
+
+ if (firstResult != -1) {
+ query.setFirstResult(firstResult);
+ }
+ if (maxResults != -1) {
+ query.setMaxResults(maxResults);
+ }
+
+ List<OfflineClientSessionEntity> results = query.getResultList();
+ Set<OfflineClientSessionModel> set = new HashSet<>();
+ for (OfflineClientSessionEntity entity : results) {
+ set.add(toModel(entity));
+ }
+ return set;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index bdcf1f1..9c75057 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -756,124 +756,6 @@ public class UserAdapter implements UserModel {
}
@Override
- public void addOfflineUserSession(OfflineUserSessionModel offlineSession) {
- OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
- entity.setUser(user);
- entity.setUserSessionId(offlineSession.getUserSessionId());
- entity.setData(offlineSession.getData());
- em.persist(entity);
- user.getOfflineUserSessions().add(entity);
- em.flush();
- }
-
- @Override
- public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
- for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
- if (entity.getUserSessionId().equals(userSessionId)) {
- return toModel(entity);
- }
- }
- return null;
- }
-
- private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
- OfflineUserSessionModel model = new OfflineUserSessionModel();
- model.setUserSessionId(entity.getUserSessionId());
- model.setData(entity.getData());
- return model;
- }
-
- @Override
- public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
- List<OfflineUserSessionModel> result = new LinkedList<>();
- for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
- result.add(toModel(entity));
- }
- return result;
- }
-
- @Override
- public boolean removeOfflineUserSession(String userSessionId) {
- OfflineUserSessionEntity found = null;
- for (OfflineUserSessionEntity session : user.getOfflineUserSessions()) {
- if (session.getUserSessionId().equals(userSessionId)) {
- found = session;
- break;
- }
- }
-
- if (found == null) {
- return false;
- } else {
- user.getOfflineUserSessions().remove(found);
- em.remove(found);
- em.flush();
- return true;
- }
- }
-
- @Override
- public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
- OfflineClientSessionEntity entity = new OfflineClientSessionEntity();
- entity.setUser(user);
- entity.setClientSessionId(clientSession.getClientSessionId());
- entity.setUserSessionId(clientSession.getUserSessionId());
- entity.setClientId(clientSession.getClientId());
- entity.setData(clientSession.getData());
- em.persist(entity);
- user.getOfflineClientSessions().add(entity);
- em.flush();
- }
-
- @Override
- public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
- for (OfflineClientSessionEntity entity : user.getOfflineClientSessions()) {
- if (entity.getClientSessionId().equals(clientSessionId)) {
- return toModel(entity);
- }
- }
- return null;
- }
-
- private OfflineClientSessionModel toModel(OfflineClientSessionEntity entity) {
- OfflineClientSessionModel model = new OfflineClientSessionModel();
- model.setClientSessionId(entity.getClientSessionId());
- model.setClientId(entity.getClientId());
- model.setUserSessionId(entity.getUserSessionId());
- model.setData(entity.getData());
- return model;
- }
-
- @Override
- public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
- List<OfflineClientSessionModel> result = new LinkedList<>();
- for (OfflineClientSessionEntity entity : user.getOfflineClientSessions()) {
- result.add(toModel(entity));
- }
- return result;
- }
-
- @Override
- public boolean removeOfflineClientSession(String clientSessionId) {
- OfflineClientSessionEntity found = null;
- for (OfflineClientSessionEntity session : user.getOfflineClientSessions()) {
- if (session.getClientSessionId().equals(clientSessionId)) {
- found = session;
- break;
- }
- }
-
- if (found == null) {
- return false;
- } else {
- user.getOfflineClientSessions().remove(found);
- em.remove(found);
- em.flush();
- return true;
- }
- }
-
- @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof UserModel)) return false;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 14081c1..214733a 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -10,6 +10,10 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.OfflineClientSessionModel;
+import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
@@ -26,8 +30,10 @@ import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import org.keycloak.models.utils.CredentialValidation;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -51,7 +57,7 @@ public class MongoUserProvider implements UserProvider {
}
@Override
- public UserModel getUserById(String id, RealmModel realm) {
+ public UserAdapter getUserById(String id, RealmModel realm) {
MongoUserEntity user = getMongoStore().loadEntity(MongoUserEntity.class, id, invocationContext);
// Check that it's user from this realm
@@ -244,8 +250,8 @@ public class MongoUserProvider implements UserProvider {
@Override
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel userModel, RealmModel realm) {
- UserModel user = getUserById(userModel.getId(), realm);
- MongoUserEntity userEntity = ((UserAdapter) user).getUser();
+ UserAdapter user = getUserById(userModel.getId(), realm);
+ MongoUserEntity userEntity = user.getUser();
List<FederatedIdentityEntity> linkEntities = userEntity.getFederatedIdentities();
if (linkEntities == null) {
@@ -263,8 +269,8 @@ public class MongoUserProvider implements UserProvider {
@Override
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
- user = getUserById(user.getId(), realm);
- MongoUserEntity userEntity = ((UserAdapter) user).getUser();
+ UserAdapter mongoUser = getUserById(user.getId(), realm);
+ MongoUserEntity userEntity = mongoUser.getUser();
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, socialProvider);
return federatedIdentityEntity != null ? new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(), federatedIdentityEntity.getUserId(),
@@ -320,8 +326,8 @@ public class MongoUserProvider implements UserProvider {
@Override
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel identity) {
- user = getUserById(user.getId(), realm);
- MongoUserEntity userEntity = ((UserAdapter) user).getUser();
+ UserAdapter mongoUser = getUserById(user.getId(), realm);
+ MongoUserEntity userEntity = mongoUser.getUser();
FederatedIdentityEntity federatedIdentityEntity = new FederatedIdentityEntity();
federatedIdentityEntity.setIdentityProvider(identity.getIdentityProvider());
federatedIdentityEntity.setUserId(identity.getUserId());
@@ -333,8 +339,8 @@ public class MongoUserProvider implements UserProvider {
@Override
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
- federatedUser = getUserById(federatedUser.getId(), realm);
- MongoUserEntity userEntity = ((UserAdapter) federatedUser).getUser();
+ UserAdapter mongoUser = getUserById(federatedUser.getId(), realm);
+ MongoUserEntity userEntity = mongoUser.getUser();
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, federatedIdentityModel.getIdentityProvider());
federatedIdentityEntity.setToken(federatedIdentityModel.getToken());
@@ -342,8 +348,8 @@ public class MongoUserProvider implements UserProvider {
@Override
public boolean removeFederatedIdentity(RealmModel realm, UserModel userModel, String socialProvider) {
- UserModel user = getUserById(userModel.getId(), realm);
- MongoUserEntity userEntity = ((UserAdapter) user).getUser();
+ UserAdapter user = getUserById(userModel.getId(), realm);
+ MongoUserEntity userEntity = user.getUser();
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, socialProvider);
if (federatedIdentityEntity == null) {
return false;
@@ -476,4 +482,205 @@ public class MongoUserProvider implements UserProvider {
// Not supported yet
return null;
}
+
+ @Override
+ public void addOfflineUserSession(RealmModel realm, UserModel userModel, OfflineUserSessionModel userSession) {
+ MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
+
+ if (user.getOfflineUserSessions() == null) {
+ user.setOfflineUserSessions(new ArrayList<OfflineUserSessionEntity>());
+ }
+
+ if (getUserSessionEntityById(user, userSession.getUserSessionId()) != null) {
+ throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + user.getUsername());
+ }
+
+ OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
+ entity.setUserSessionId(userSession.getUserSessionId());
+ entity.setData(userSession.getData());
+ entity.setOfflineClientSessions(new ArrayList<OfflineClientSessionEntity>());
+ user.getOfflineUserSessions().add(entity);
+
+ getMongoStore().updateEntity(user, invocationContext);
+ }
+
+ private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
+ OfflineUserSessionModel model = new OfflineUserSessionModel();
+ model.setUserSessionId(entity.getUserSessionId());
+ model.setData(entity.getData());
+ return model;
+ }
+
+ private OfflineUserSessionEntity getUserSessionEntityById(MongoUserEntity user, String userSessionId) {
+ if (user.getOfflineUserSessions() != null) {
+ for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
+ if (entity.getUserSessionId().equals(userSessionId)) {
+ return entity;
+ }
+ }
+ }
+ return null;
+ }
+
+
+ @Override
+ public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
+ MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
+
+ OfflineUserSessionEntity entity = getUserSessionEntityById(user, userSessionId);
+ return entity==null ? null : toModel(entity);
+ }
+
+ @Override
+ public Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel userModel) {
+ MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
+
+ if (user.getOfflineUserSessions()==null) {
+ return Collections.emptyList();
+ } else {
+ List<OfflineUserSessionModel> result = new ArrayList<>();
+ for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
+ result.add(toModel(entity));
+ }
+ return result;
+ }
+ }
+
+ @Override
+ public boolean removeOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
+ MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
+
+ OfflineUserSessionEntity entity = getUserSessionEntityById(user, userSessionId);
+ if (entity != null) {
+ user.getOfflineUserSessions().remove(entity);
+ getMongoStore().updateEntity(user, invocationContext);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel clientSession) {
+ MongoUserEntity user = getUserById(clientSession.getUserId(), realm).getUser();
+
+ OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(user, clientSession.getUserSessionId());
+ if (userSessionEntity == null) {
+ throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + user.getUsername());
+ }
+
+ OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
+ clEntity.setClientSessionId(clientSession.getClientSessionId());
+ clEntity.setClientId(clientSession.getClientId());
+ clEntity.setData(clientSession.getData());
+
+ userSessionEntity.getOfflineClientSessions().add(clEntity);
+ getMongoStore().updateEntity(user, invocationContext);
+ }
+
+ @Override
+ public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
+ MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
+
+ if (user.getOfflineUserSessions() != null) {
+ for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
+ for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
+ if (clSession.getClientSessionId().equals(clientSessionId)) {
+ return toModel(clSession, user.getId(), userSession.getUserSessionId());
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userId, String userSessionId) {
+ OfflineClientSessionModel model = new OfflineClientSessionModel();
+ model.setClientSessionId(cls.getClientSessionId());
+ model.setClientId(cls.getClientId());
+ model.setUserId(userId);
+ model.setData(cls.getData());
+ model.setUserSessionId(userSessionId);
+ return model;
+ }
+
+ @Override
+ public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel userModel) {
+ MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
+
+ List<OfflineClientSessionModel> result = new ArrayList<>();
+
+ if (user.getOfflineUserSessions() != null) {
+ for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
+ for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
+ result.add(toModel(clSession, user.getId(), userSession.getUserSessionId()));
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean removeOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
+ MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
+ boolean updated = false;
+
+ if (user.getOfflineUserSessions() != null) {
+ for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
+ for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
+ if (clSession.getClientSessionId().equals(clientSessionId)) {
+ userSession.getOfflineClientSessions().remove(clSession);
+ updated = true;
+ break;
+ }
+ }
+
+ if (updated && userSession.getOfflineClientSessions().isEmpty()) {
+ user.getOfflineUserSessions().remove(userSession);
+ }
+
+ if (updated) {
+ getMongoStore().updateEntity(user, invocationContext);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
+ DBObject query = new QueryBuilder()
+ .and("realmId").is(realm.getId())
+ .and("offlineUserSessions.offlineClientSessions.clientId").is(client.getId())
+ .get();
+ return getMongoStore().countEntities(MongoUserEntity.class, query, invocationContext);
+ }
+
+ @Override
+ public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
+ DBObject query = new QueryBuilder()
+ .and("realmId").is(realm.getId())
+ .and("offlineUserSessions.offlineClientSessions.clientId").is(client.getId())
+ .get();
+ DBObject sort = new BasicDBObject("username", 1);
+ List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, query, sort, firstResult, maxResults, invocationContext);
+
+ List<OfflineClientSessionModel> result = new LinkedList<>();
+ for (MongoUserEntity user : users) {
+ for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
+ for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
+ if (clSession.getClientId().equals(client.getId())) {
+ OfflineClientSessionModel model = toModel(clSession, user.getId(), userSession.getUserSessionId());
+ result.add(model);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index a475bb6..9f13e63 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -633,145 +633,6 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
}
@Override
- public void addOfflineUserSession(OfflineUserSessionModel userSession) {
- if (user.getOfflineUserSessions() == null) {
- user.setOfflineUserSessions(new ArrayList<OfflineUserSessionEntity>());
- }
-
- if (getUserSessionEntityById(userSession.getUserSessionId()) != null) {
- throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + getMongoEntity().getUsername());
- }
-
- OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
- entity.setUserSessionId(userSession.getUserSessionId());
- entity.setData(userSession.getData());
- entity.setOfflineClientSessions(new ArrayList<OfflineClientSessionEntity>());
- user.getOfflineUserSessions().add(entity);
- updateUser();
- }
-
- @Override
- public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
- OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
- return entity==null ? null : toModel(entity);
- }
-
- @Override
- public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
- if (user.getOfflineUserSessions()==null) {
- return Collections.emptyList();
- } else {
- List<OfflineUserSessionModel> result = new ArrayList<>();
- for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
- result.add(toModel(entity));
- }
- return result;
- }
- }
-
- private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
- OfflineUserSessionModel model = new OfflineUserSessionModel();
- model.setUserSessionId(entity.getUserSessionId());
- model.setData(entity.getData());
- return model;
- }
-
- @Override
- public boolean removeOfflineUserSession(String userSessionId) {
- OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
- if (entity != null) {
- user.getOfflineUserSessions().remove(entity);
- updateUser();
- return true;
- } else {
- return false;
- }
- }
-
- private OfflineUserSessionEntity getUserSessionEntityById(String userSessionId) {
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
- if (entity.getUserSessionId().equals(userSessionId)) {
- return entity;
- }
- }
- }
- return null;
- }
-
- @Override
- public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
- OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(clientSession.getUserSessionId());
- if (userSessionEntity == null) {
- throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + getMongoEntity().getUsername());
- }
-
- OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
- clEntity.setClientSessionId(clientSession.getClientSessionId());
- clEntity.setClientId(clientSession.getClientId());
- clEntity.setData(clientSession.getData());
-
- userSessionEntity.getOfflineClientSessions().add(clEntity);
- updateUser();
- }
-
- @Override
- public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- if (clSession.getClientSessionId().equals(clientSessionId)) {
- return toModel(clSession, userSession.getUserSessionId());
- }
- }
- }
- }
-
- return null;
- }
-
- private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userSessionId) {
- OfflineClientSessionModel model = new OfflineClientSessionModel();
- model.setClientSessionId(cls.getClientSessionId());
- model.setClientId(cls.getClientId());
- model.setData(cls.getData());
- model.setUserSessionId(userSessionId);
- return model;
- }
-
- @Override
- public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
- List<OfflineClientSessionModel> result = new ArrayList<>();
-
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- result.add(toModel(clSession, userSession.getUserSessionId()));
- }
- }
- }
-
- return result;
- }
-
- @Override
- public boolean removeOfflineClientSession(String clientSessionId) {
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- if (clSession.getClientSessionId().equals(clientSessionId)) {
- userSession.getOfflineClientSessions().remove(clSession);
- updateUser();
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof UserModel)) return false;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index c166565..ee8e9e8 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -98,7 +98,7 @@ public class TokenManager {
ClientSessionModel clientSession = null;
if (RefreshTokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) {
- clientSession = OfflineTokenUtils.findOfflineClientSession(realm, user, oldToken.getClientSession(), oldToken.getSessionState());
+ clientSession = OfflineTokenUtils.findOfflineClientSession(session, realm, user, oldToken.getClientSession(), oldToken.getSessionState());
if (clientSession != null) {
userSession = clientSession.getUserSession();
}
@@ -496,7 +496,7 @@ public class TokenManager {
refreshToken = new RefreshToken(accessToken);
refreshToken.type(RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
- OfflineTokenUtils.persistOfflineSession(clientSession, userSession);
+ OfflineTokenUtils.persistOfflineSession(session, realm, clientSession, userSession);
} else {
refreshToken = new RefreshToken(accessToken);
refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
diff --git a/services/src/main/java/org/keycloak/services/offline/OfflineClientSessionAdapter.java b/services/src/main/java/org/keycloak/services/offline/OfflineClientSessionAdapter.java
index 50abfd4..519ae25 100644
--- a/services/src/main/java/org/keycloak/services/offline/OfflineClientSessionAdapter.java
+++ b/services/src/main/java/org/keycloak/services/offline/OfflineClientSessionAdapter.java
@@ -75,7 +75,7 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
@Override
public String getRedirectUri() {
- return data.getRedirectUri();
+ return getData().getRedirectUri();
}
@Override
@@ -85,7 +85,7 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
@Override
public int getTimestamp() {
- return 0;
+ return getData().getTimestamp();
}
@Override
@@ -238,6 +238,9 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
@JsonProperty("authenticatorStatus")
private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
+ @JsonProperty("timestamp")
+ private int timestamp;
+
public String getAuthMethod() {
return authMethod;
}
@@ -285,5 +288,13 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus;
}
+
+ public int getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(int timestamp) {
+ this.timestamp = timestamp;
+ }
}
}
diff --git a/services/src/main/java/org/keycloak/services/offline/OfflineTokenUtils.java b/services/src/main/java/org/keycloak/services/offline/OfflineTokenUtils.java
index b4900c1..13ff55b 100644
--- a/services/src/main/java/org/keycloak/services/offline/OfflineTokenUtils.java
+++ b/services/src/main/java/org/keycloak/services/offline/OfflineTokenUtils.java
@@ -9,6 +9,7 @@ import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
@@ -17,9 +18,9 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.util.JsonSerialization;
+import org.keycloak.util.Time;
/**
- * TODO: Change to utils?
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@@ -27,12 +28,12 @@ public class OfflineTokenUtils {
protected static Logger logger = Logger.getLogger(OfflineTokenUtils.class);
- public static void persistOfflineSession(ClientSessionModel clientSession, UserSessionModel userSession) {
+ public static void persistOfflineSession(KeycloakSession kcSession, RealmModel realm, ClientSessionModel clientSession, UserSessionModel userSession) {
UserModel user = userSession.getUser();
ClientModel client = clientSession.getClient();
// First verify if we already have offlineToken for this user+client . If yes, then invalidate it (This is to avoid leaks)
- Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
+ Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
for (OfflineClientSessionModel existing : clientSessions) {
if (existing.getClientId().equals(client.getId())) {
if (logger.isTraceEnabled()) {
@@ -40,28 +41,28 @@ public class OfflineTokenUtils {
user.getUsername(), client.getClientId(), existing.getClientSessionId());
}
- user.removeOfflineClientSession(existing.getClientSessionId());
+ kcSession.users().removeOfflineClientSession(realm, user, existing.getClientSessionId());
// Check if userSession is ours. If not, then check if it has other clientSessions and remove it otherwise
if (!existing.getUserSessionId().equals(userSession.getId())) {
- checkUserSessionHasClientSessions(user, existing.getUserSessionId());
+ checkUserSessionHasClientSessions(kcSession, realm, user, existing.getUserSessionId());
}
}
}
// Verify if we already have UserSession with this ID. If yes, don't create another one
- OfflineUserSessionModel userSessionRep = user.getOfflineUserSession(userSession.getId());
+ OfflineUserSessionModel userSessionRep = kcSession.users().getOfflineUserSession(realm, user, userSession.getId());
if (userSessionRep == null) {
- createOfflineUserSession(user, userSession);
+ createOfflineUserSession(kcSession, realm, user, userSession);
}
// Create clientRep and save to DB.
- createOfflineClientSession(user, clientSession, userSession);
+ createOfflineClientSession(kcSession, realm, user, clientSession, userSession);
}
// userSessionId is provided from offline token. It's used just to verify if it match the ID from clientSession representation
- public static ClientSessionModel findOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId, String userSessionId) {
- OfflineClientSessionModel clientSession = user.getOfflineClientSession(clientSessionId);
+ public static ClientSessionModel findOfflineClientSession(KeycloakSession kcSession, RealmModel realm, UserModel user, String clientSessionId, String userSessionId) {
+ OfflineClientSessionModel clientSession = kcSession.users().getOfflineClientSession(realm, user, clientSessionId);
if (clientSession == null) {
return null;
}
@@ -71,7 +72,7 @@ public class OfflineTokenUtils {
" Wanted user session: " + userSessionId);
}
- OfflineUserSessionModel userSession = user.getOfflineUserSession(userSessionId);
+ OfflineUserSessionModel userSession = kcSession.users().getOfflineUserSession(realm, user, userSessionId);
if (userSession == null) {
throw new ModelException("Found clientSession " + clientSessionId + " but not userSession " + userSessionId);
}
@@ -79,13 +80,11 @@ public class OfflineTokenUtils {
OfflineUserSessionAdapter userSessionAdapter = new OfflineUserSessionAdapter(userSession, user);
ClientModel client = realm.getClientById(clientSession.getClientId());
- OfflineClientSessionAdapter clientSessionAdapter = new OfflineClientSessionAdapter(clientSession, realm, client, userSessionAdapter);
-
- return clientSessionAdapter;
+ return new OfflineClientSessionAdapter(clientSession, realm, client, userSessionAdapter);
}
- public static Set<ClientModel> findClientsWithOfflineToken(RealmModel realm, UserModel user) {
- Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
+ public static Set<ClientModel> findClientsWithOfflineToken(KeycloakSession kcSession, RealmModel realm, UserModel user) {
+ Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
Set<ClientModel> clients = new HashSet<>();
for (OfflineClientSessionModel clientSession : clientSessions) {
ClientModel client = realm.getClientById(clientSession.getClientId());
@@ -94,8 +93,8 @@ public class OfflineTokenUtils {
return clients;
}
- public static boolean revokeOfflineToken(UserModel user, ClientModel client) {
- Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
+ public static boolean revokeOfflineToken(KeycloakSession kcSession, RealmModel realm, UserModel user, ClientModel client) {
+ Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
boolean anyRemoved = false;
for (OfflineClientSessionModel clientSession : clientSessions) {
if (clientSession.getClientId().equals(client.getId())) {
@@ -104,8 +103,8 @@ public class OfflineTokenUtils {
user.getUsername(), client.getClientId(), clientSession.getClientSessionId());
}
- user.removeOfflineClientSession(clientSession.getClientSessionId());
- checkUserSessionHasClientSessions(user, clientSession.getUserSessionId());
+ kcSession.users().removeOfflineClientSession(realm, user, clientSession.getClientSessionId());
+ checkUserSessionHasClientSessions(kcSession, realm, user, clientSession.getUserSessionId());
anyRemoved = true;
}
}
@@ -123,7 +122,7 @@ public class OfflineTokenUtils {
return clientSession.getRoles().contains(offlineAccessRole.getId());
}
- private static void createOfflineUserSession(UserModel user, UserSessionModel userSession) {
+ private static void createOfflineUserSession(KeycloakSession kcSession, RealmModel realm, UserModel user, UserSessionModel userSession) {
if (logger.isTraceEnabled()) {
logger.tracef("Creating new offline user session. UserSessionID: '%s' , Username: '%s'", userSession.getId(), user.getUsername());
}
@@ -141,13 +140,13 @@ public class OfflineTokenUtils {
OfflineUserSessionModel sessionModel = new OfflineUserSessionModel();
sessionModel.setUserSessionId(userSession.getId());
sessionModel.setData(stringRep);
- user.addOfflineUserSession(sessionModel);
+ kcSession.users().addOfflineUserSession(realm, user, sessionModel);
} catch (IOException ioe) {
throw new ModelException(ioe);
}
}
- private static void createOfflineClientSession(UserModel user, ClientSessionModel clientSession, UserSessionModel userSession) {
+ private static void createOfflineClientSession(KeycloakSession kcSession, RealmModel realm, UserModel user, ClientSessionModel clientSession, UserSessionModel userSession) {
if (logger.isTraceEnabled()) {
logger.tracef("Creating new offline token client session. ClientSessionId: '%s', UserSessionID: '%s' , Username: '%s', Client: '%s'" ,
clientSession.getId(), userSession.getId(), user.getUsername(), clientSession.getClient().getClientId());
@@ -159,23 +158,25 @@ public class OfflineTokenUtils {
rep.setRoles(clientSession.getRoles());
rep.setNotes(clientSession.getNotes());
rep.setAuthenticatorStatus(clientSession.getExecutionStatus());
+ rep.setTimestamp(Time.currentTime());
try {
String stringRep = JsonSerialization.writeValueAsString(rep);
OfflineClientSessionModel clsModel = new OfflineClientSessionModel();
clsModel.setClientSessionId(clientSession.getId());
clsModel.setClientId(clientSession.getClient().getId());
+ clsModel.setUserId(user.getId());
clsModel.setUserSessionId(userSession.getId());
clsModel.setData(stringRep);
- user.addOfflineClientSession(clsModel);
+ kcSession.users().addOfflineClientSession(realm, clsModel);
} catch (IOException ioe) {
throw new ModelException(ioe);
}
}
// Check if userSession has any offline clientSessions attached to it. Remove userSession if not
- private static void checkUserSessionHasClientSessions(UserModel user, String userSessionId) {
- Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
+ private static void checkUserSessionHasClientSessions(KeycloakSession kcSession, RealmModel realm, UserModel user, String userSessionId) {
+ Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
for (OfflineClientSessionModel clientSession : clientSessions) {
if (clientSession.getUserSessionId().equals(userSessionId)) {
@@ -186,6 +187,6 @@ public class OfflineTokenUtils {
if (logger.isTraceEnabled()) {
logger.tracef("Removing offline userSession for user %s as it doesn't have any client sessions attached. UserSessionID: %s", user.getUsername(), userSessionId);
}
- user.removeOfflineUserSession(userSessionId);
+ kcSession.users().removeOfflineUserSession(realm, user, userSessionId);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 20e7b6e..17de6a2 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -486,7 +486,7 @@ public class AccountService extends AbstractSecuredLocalService {
// Revoke grant in UserModel
UserModel user = auth.getUser();
user.revokeConsentForClient(client.getId());
- OfflineTokenUtils.revokeOfflineToken(user, client);
+ OfflineTokenUtils.revokeOfflineToken(session, realm, user, client);
// Logout clientSessions for this user and client
AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 2198333..71d8ac0 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -7,8 +7,11 @@ import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.OfflineClientSessionModel;
+import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
@@ -24,6 +27,8 @@ import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
+import org.keycloak.services.offline.OfflineClientSessionAdapter;
+import org.keycloak.services.offline.OfflineUserSessionAdapter;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.ErrorResponse;
import org.keycloak.util.JsonSerialization;
@@ -391,6 +396,65 @@ public class ClientResource {
}
/**
+ * Get application offline session count
+ *
+ * Returns a number of offline user sessions associated with this client
+ *
+ * {
+ * "count": number
+ * }
+ *
+ * @return
+ */
+ @Path("offline-session-count")
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public Map<String, Integer> getOfflineSessionCount() {
+ auth.requireView();
+ Map<String, Integer> map = new HashMap<String, Integer>();
+ map.put("count", session.users().getOfflineClientSessionsCount(client.getRealm(), client));
+ return map;
+ }
+
+ /**
+ * Get offline sessions for client
+ *
+ * Returns a list of offline user sessions associated with this client
+ *
+ * @param firstResult Paging offset
+ * @param maxResults Paging size
+ * @return
+ */
+ @Path("offline-sessions")
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<UserSessionRepresentation> getOfflineUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
+ auth.requireView();
+ firstResult = firstResult != null ? firstResult : -1;
+ maxResults = maxResults != null ? maxResults : -1;
+ List<UserSessionRepresentation> sessions = new ArrayList<UserSessionRepresentation>();
+ for (OfflineClientSessionModel offlineClientSession : session.users().getOfflineClientSessions(client.getRealm(), client, firstResult, maxResults)) {
+ UserModel user = session.users().getUserById(offlineClientSession.getUserId(), client.getRealm());
+ OfflineUserSessionModel offlineUserSession = session.users().getOfflineUserSession(client.getRealm(), user, offlineClientSession.getUserSessionId());
+ OfflineUserSessionAdapter sessionAdapter = new OfflineUserSessionAdapter(offlineUserSession, user);
+ OfflineClientSessionAdapter clientSessionAdapter = new OfflineClientSessionAdapter(offlineClientSession, client.getRealm(), client, sessionAdapter);
+
+ UserSessionRepresentation rep = new UserSessionRepresentation();
+ rep.setId(sessionAdapter.getId());
+ rep.setStart(Time.toMillis(clientSessionAdapter.getTimestamp()));
+ rep.setUsername(user.getUsername());
+ rep.setUserId(user.getId());
+ rep.setIpAddress(sessionAdapter.getIpAddress());
+
+ sessions.add(rep);
+ }
+ return sessions;
+ }
+
+
+ /**
* Logout all sessions
*
* If the client has an admin URL, invalidate all sessions associated with that client directly.
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 e33e50e..bf535d9 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
@@ -66,15 +66,18 @@ import javax.ws.rs.WebApplicationException;
import java.net.URI;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.keycloak.models.UsernameLoginFailureModel;
import org.keycloak.services.managers.BruteForceProtector;
+import org.keycloak.services.offline.OfflineTokenUtils;
import org.keycloak.services.resources.AccountService;
/**
@@ -439,25 +442,44 @@ public class UsersResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public List<UserConsentRepresentation> getConsents(final @PathParam("id") String id) {
+ public List<Map<String, Object>> getConsents(final @PathParam("id") String id) {
auth.requireView();
UserModel user = session.users().getUserById(id, realm);
if (user == null) {
throw new NotFoundException("User not found");
}
- List<UserConsentModel> consents = user.getConsents();
- List<UserConsentRepresentation> result = new ArrayList<UserConsentRepresentation>();
+ List<Map<String, Object>> result = new LinkedList<>();
- for (UserConsentModel consent : consents) {
- UserConsentRepresentation rep = ModelToRepresentation.toRepresentation(consent);
- result.add(rep);
+ Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(session, realm, user);
+
+ for (ClientModel client : realm.getClients()) {
+ UserConsentModel consent = user.getConsentByClient(client.getId());
+ boolean hasOfflineToken = offlineClients.contains(client);
+
+ if (consent == null && !hasOfflineToken) {
+ continue;
+ }
+
+ UserConsentRepresentation rep = (consent == null) ? null : ModelToRepresentation.toRepresentation(consent);
+
+ Map<String, Object> currentRep = new HashMap<>();
+ currentRep.put("clientId", client.getClientId());
+ currentRep.put("grantedProtocolMappers", (rep==null ? Collections.emptyMap() : rep.getGrantedProtocolMappers()));
+ currentRep.put("grantedRealmRoles", (rep==null ? Collections.emptyList() : rep.getGrantedRealmRoles()));
+ currentRep.put("grantedClientRoles", (rep==null ? Collections.emptyMap() : rep.getGrantedClientRoles()));
+
+ List<String> additionalGrants = hasOfflineToken ? Arrays.asList("Offline Token") : Collections.<String>emptyList();
+ currentRep.put("additionalGrants", additionalGrants);
+
+ result.add(currentRep);
}
+
return result;
}
/**
- * Revoke consent for particular client from user
+ * Revoke consent and offline tokens for particular client from user
*
* @param id User id
* @param clientId Client id
@@ -473,12 +495,16 @@ public class UsersResource {
}
ClientModel client = realm.getClientByClientId(clientId);
- boolean revoked = user.revokeConsentForClient(client.getId());
- if (revoked) {
+ boolean revokedConsent = user.revokeConsentForClient(client.getId());
+ boolean revokedOfflineToken = OfflineTokenUtils.revokeOfflineToken(session, realm, user, client);
+
+ if (revokedConsent) {
// Logout clientSessions for this user and client
AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
- } else {
- throw new NotFoundException("Consent not found");
+ }
+
+ if (!revokedConsent && !revokedOfflineToken) {
+ throw new NotFoundException("Consent nor offline token not found");
}
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index 6f42fd5..6040c72 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -15,6 +15,8 @@ import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.OfflineClientSessionModel;
+import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
@@ -32,6 +34,7 @@ import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -327,6 +330,21 @@ public class ImportTest extends AbstractModelTest {
Assert.assertFalse(otherAppAdminConsent.isRoleGranted(application.getRole("app-admin")));
Assert.assertTrue(otherAppAdminConsent.isProtocolMapperGranted(gssCredentialMapper));
+ // Test offline sessions
+ Collection<OfflineUserSessionModel> offlineUserSessions = session.users().getOfflineUserSessions(realm, admin);
+ Collection<OfflineClientSessionModel> offlineClientSessions = session.users().getOfflineClientSessions(realm, admin);
+ Assert.assertEquals(offlineUserSessions.size(), 1);
+ Assert.assertEquals(offlineClientSessions.size(), 1);
+ OfflineUserSessionModel offlineSession = offlineUserSessions.iterator().next();
+ OfflineClientSessionModel offlineClSession = offlineClientSessions.iterator().next();
+ Assert.assertEquals(offlineSession.getData(), "something1");
+ Assert.assertEquals(offlineSession.getUserSessionId(), "123");
+ Assert.assertEquals(offlineClSession.getClientId(), otherApp.getId());
+ Assert.assertEquals(offlineClSession.getUserSessionId(), "123");
+ Assert.assertEquals(offlineClSession.getUserId(), admin.getId());
+ Assert.assertEquals(offlineClSession.getData(), "something2");
+
+
// Test service accounts
Assert.assertFalse(application.isServiceAccountsEnabled());
Assert.assertTrue(otherApp.isServiceAccountsEnabled());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
index f0118c2..68fa2f8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
@@ -15,6 +15,7 @@ import static org.junit.Assert.assertNotNull;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -292,12 +293,35 @@ public class UserModelTest extends AbstractModelTest {
ClientModel barClient = realm.addClient("bar");
UserModel user1 = session.users().addUser(realm, "user1");
- addOfflineUserSession(user1, "123", "something1");
- addOfflineClientSession(user1, "456", "123", fooClient.getId(), "something2");
- addOfflineClientSession(user1, "789", "123", barClient.getId(), "something3");
+ UserModel user2 = session.users().addUser(realm, "user2");
+
+ addOfflineUserSession(realm, user1, "123", "something1");
+ addOfflineClientSession(realm, user1, "456", "123", fooClient.getId(), "something2");
+ addOfflineClientSession(realm, user1, "789", "123", barClient.getId(), "something3");
+
+ addOfflineUserSession(realm, user2, "2123", "something4");
+ addOfflineClientSession(realm, user2, "2456", "2123", fooClient.getId(), "something5");
commit();
+ // Searching by clients
+ Assert.assertEquals(2, session.users().getOfflineClientSessionsCount(realm, fooClient));
+ Assert.assertEquals(1, session.users().getOfflineClientSessionsCount(realm, barClient));
+
+ Collection<OfflineClientSessionModel> clientSessions = session.users().getOfflineClientSessions(realm, fooClient, 0, 10);
+ Assert.assertEquals(2, clientSessions.size());
+ clientSessions = session.users().getOfflineClientSessions(realm, fooClient, 0, 1);
+ OfflineClientSessionModel cls = clientSessions.iterator().next();
+ assertSessionEquals(cls, "456", "123", fooClient.getId(), user1.getId(), "something2");
+ clientSessions = session.users().getOfflineClientSessions(realm, fooClient, 1, 1);
+ cls = clientSessions.iterator().next();
+ assertSessionEquals(cls, "2456", "2123", fooClient.getId(), user2.getId(), "something5");
+
+ clientSessions = session.users().getOfflineClientSessions(realm, barClient, 0, 10);
+ Assert.assertEquals(1, clientSessions.size());
+ cls = clientSessions.iterator().next();
+ assertSessionEquals(cls, "789", "123", barClient.getId(), user1.getId(), "something3");
+
realm = realmManager.getRealmByName("original");
realm.removeClient(barClient.getId());
@@ -305,9 +329,9 @@ public class UserModelTest extends AbstractModelTest {
realm = realmManager.getRealmByName("original");
user1 = session.users().getUserByUsername("user1", realm);
- Assert.assertEquals("something1", user1.getOfflineUserSession("123").getData());
- Assert.assertEquals("something2", user1.getOfflineClientSession("456").getData());
- Assert.assertNull(user1.getOfflineClientSession("789"));
+ Assert.assertEquals("something1", session.users().getOfflineUserSession(realm, user1, "123").getData());
+ Assert.assertEquals("something2", session.users().getOfflineClientSession(realm, user1, "456").getData());
+ Assert.assertNull(session.users().getOfflineClientSession(realm, user1, "789"));
realm.removeClient(fooClient.getId());
@@ -315,27 +339,28 @@ public class UserModelTest extends AbstractModelTest {
realm = realmManager.getRealmByName("original");
user1 = session.users().getUserByUsername("user1", realm);
- Assert.assertNull(user1.getOfflineClientSession("456"));
- Assert.assertNull(user1.getOfflineClientSession("789"));
- Assert.assertNull(user1.getOfflineUserSession("123"));
- Assert.assertEquals(0, user1.getOfflineUserSessions().size());
- Assert.assertEquals(0, user1.getOfflineClientSessions().size());
+ Assert.assertNull(session.users().getOfflineClientSession(realm, user1, "456"));
+ Assert.assertNull(session.users().getOfflineClientSession(realm, user1, "789"));
+ Assert.assertNull(session.users().getOfflineUserSession(realm, user1, "123"));
+ Assert.assertEquals(0, session.users().getOfflineUserSessions(realm, user1).size());
+ Assert.assertEquals(0, session.users().getOfflineClientSessions(realm, user1).size());
}
- private void addOfflineUserSession(UserModel user, String userSessionId, String data) {
+ private void addOfflineUserSession(RealmModel realm, UserModel user, String userSessionId, String data) {
OfflineUserSessionModel model = new OfflineUserSessionModel();
model.setUserSessionId(userSessionId);
model.setData(data);
- user.addOfflineUserSession(model);
+ session.users().addOfflineUserSession(realm, user, model);
}
- private void addOfflineClientSession(UserModel user, String clientSessionId, String userSessionId, String clientId, String data) {
+ private void addOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId, String userSessionId, String clientId, String data) {
OfflineClientSessionModel model = new OfflineClientSessionModel();
model.setClientSessionId(clientSessionId);
model.setUserSessionId(userSessionId);
+ model.setUserId(user.getId());
model.setClientId(clientId);
model.setData(data);
- user.addOfflineClientSession(model);
+ session.users().addOfflineClientSession(realm, model);
}
public static void assertEquals(UserModel expected, UserModel actual) {
@@ -352,5 +377,14 @@ public class UserModelTest extends AbstractModelTest {
Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions);
}
+ private static void assertSessionEquals(OfflineClientSessionModel cls, String expectedClientSessionId, String expectedUserSessionId,
+ String expectedClientId, String expectedUserId, String expectedData) {
+ Assert.assertEquals(cls.getData(), expectedData);
+ Assert.assertEquals(cls.getClientSessionId(), expectedClientSessionId);
+ Assert.assertEquals(cls.getUserSessionId(), expectedUserSessionId);
+ Assert.assertEquals(cls.getUserId(), expectedUserId);
+ Assert.assertEquals(cls.getClientId(), expectedClientId);
+ }
+
}
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index f7c8cdd..521ef83 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -119,6 +119,19 @@
"openid-connect": [ "gss delegation credential" ]
}
}
+ ],
+ "offlineUserSessions": [
+ {
+ "userSessionId": "123",
+ "data": "something1",
+ "offlineClientSessions": [
+ {
+ "clientSessionId": "456",
+ "client": "OtherApp",
+ "data": "something2"
+ }
+ ]
+ }
]
},
{