KEYCLOAK-904 Offline tokens storage changes. Added UserSessionPersisterProvider . offline sessions preloaded to cache at startup

This commit is contained in:
mposolda committed 2015-10-14 11:07:44 +02:00
1 parent 6aa029784a
commit f92fe6bea9
93 files changed
+4397 -1945

No files matched your search

@@ -32,7 +32,7 @@ import org.keycloak.representations.RefreshToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.offline.OfflineTokenUtils;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.util.TokenUtil;
import org.keycloak.util.Time;
@@ -98,7 +98,7 @@ public class TokenManager {
ClientSessionModel clientSession = null;
if (TokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) {
clientSession = OfflineTokenUtils.findOfflineClientSession(session, realm, user, oldToken.getClientSession(), oldToken.getSessionState());
clientSession = new UserSessionManager(session).findOfflineClientSession(realm, oldToken.getClientSession(), oldToken.getSessionState());
if (clientSession != null) {
userSession = clientSession.getUserSession();
}
@@ -490,14 +490,15 @@ public class TokenManager {
String scopeParam = clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM);
boolean offlineTokenRequested = TokenUtil.isOfflineTokenRequested(scopeParam);
if (offlineTokenRequested) {
if (!OfflineTokenUtils.isOfflineTokenAllowed(realm, clientSession)) {
UserSessionManager sessionManager = new UserSessionManager(session);
if (!sessionManager.isOfflineTokenAllowed(clientSession)) {
event.error(Errors.NOT_ALLOWED);
throw new ErrorResponseException("not_allowed", "Offline tokens not allowed for the user or client", Response.Status.BAD_REQUEST);
}
refreshToken = new RefreshToken(accessToken);
refreshToken.type(TokenUtil.TOKEN_TYPE_OFFLINE);
OfflineTokenUtils.persistOfflineSession(session, realm, clientSession, userSession);
sessionManager.persistOfflineSession(clientSession, userSession);
} else {
refreshToken = new RefreshToken(accessToken);
refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
@@ -11,6 +11,7 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
@@ -54,6 +55,11 @@ public class ClientManager {
sessions.onClientRemoved(realm, client);
}
UserSessionPersisterProvider sessionsPersister = realmManager.getSession().getProvider(UserSessionPersisterProvider.class);
if (sessionsPersister != null) {
sessionsPersister.onClientRemoved(realm, client);
}
UserModel serviceAccountUser = realmManager.getSession().users().getUserByServiceAccountClient(client);
if (serviceAccountUser != null) {
realmManager.getSession().users().removeUser(realm, serviceAccountUser);
@@ -19,6 +19,7 @@ package org.keycloak.services.managers;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.enums.SslRequired;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.utils.RealmImporter;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.AdminRoles;
@@ -197,6 +198,11 @@ public class RealmManager implements RealmImporter {
sessions.onRealmRemoved(realm);
}
UserSessionPersisterProvider sessionsPersister = session.getProvider(UserSessionPersisterProvider.class);
if (sessionsPersister != null) {
sessionsPersister.onRealmRemoved(realm);
}
// Remove all periodic syncs for configured federation providers
UsersSyncManager usersSyncManager = new UsersSyncManager();
for (final UserFederationProviderModel fedProvider : federationProviders) {
@@ -4,6 +4,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -21,6 +22,12 @@ public class UserManager {
if (sessions != null) {
sessions.onUserRemoved(realm, user);
}
UserSessionPersisterProvider sessionsPersister = session.getProvider(UserSessionPersisterProvider.class);
if (sessionsPersister != null) {
sessionsPersister.onUserRemoved(realm, user);
}
if (session.users().removeUser(realm, user)) {
return true;
}
@@ -0,0 +1,139 @@
package org.keycloak.services.managers;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
/**
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserSessionManager {
protected static Logger logger = Logger.getLogger(UserSessionManager.class);
private final KeycloakSession kcSession;
private final UserSessionPersisterProvider persister;
public UserSessionManager(KeycloakSession session) {
this.kcSession = session;
this.persister = session.getProvider(UserSessionPersisterProvider.class);
}
public void persistOfflineSession(ClientSessionModel clientSession, UserSessionModel userSession) {
UserModel user = userSession.getUser();
// Verify if we already have UserSession with this ID. If yes, don't create another one
UserSessionModel offlineUserSession = kcSession.sessions().getOfflineUserSession(clientSession.getRealm(), userSession.getId());
if (offlineUserSession == null) {
offlineUserSession = createOfflineUserSession(user, userSession);
}
// Create clientSession and save to DB.
createOfflineClientSession(user, clientSession, offlineUserSession);
}
// userSessionId is provided from offline token. It's used just to verify if it match the ID from clientSession representation
public ClientSessionModel findOfflineClientSession(RealmModel realm, String clientSessionId, String userSessionId) {
ClientSessionModel clientSession = kcSession.sessions().getOfflineClientSession(realm, clientSessionId);
if (clientSession == null) {
return null;
}
if (!userSessionId.equals(clientSession.getUserSession().getId())) {
throw new ModelException("User session don't match. Offline client session " + clientSession.getId() + ", It's user session " + clientSession.getUserSession().getId() +
" Wanted user session: " + userSessionId);
}
return clientSession;
}
public Set<ClientModel> findClientsWithOfflineToken(RealmModel realm, UserModel user) {
List<ClientSessionModel> clientSessions = kcSession.sessions().getOfflineClientSessions(realm, user);
Set<ClientModel> clients = new HashSet<>();
for (ClientSessionModel clientSession : clientSessions) {
clients.add(clientSession.getClient());
}
return clients;
}
public boolean revokeOfflineToken(UserModel user, ClientModel client) {
RealmModel realm = client.getRealm();
List<ClientSessionModel> clientSessions = kcSession.sessions().getOfflineClientSessions(realm, user);
boolean anyRemoved = false;
for (ClientSessionModel clientSession : clientSessions) {
if (clientSession.getClient().getId().equals(client.getId())) {
if (logger.isTraceEnabled()) {
logger.tracef("Removing existing offline token for user '%s' and client '%s' . ClientSessionID was '%s' .",
user.getUsername(), client.getClientId(), clientSession.getId());
}
kcSession.sessions().removeOfflineClientSession(realm, clientSession.getId());
persister.removeClientSession(clientSession.getId(), true);
checkOfflineUserSessionHasClientSessions(realm, user, clientSession.getUserSession().getId(), clientSessions);
anyRemoved = true;
}
}
return anyRemoved;
}
public boolean isOfflineTokenAllowed(ClientSessionModel clientSession) {
RoleModel offlineAccessRole = clientSession.getRealm().getRole(Constants.OFFLINE_ACCESS_ROLE);
if (offlineAccessRole == null) {
logger.warnf("Role '%s' not available in realm", Constants.OFFLINE_ACCESS_ROLE);
return false;
}
return clientSession.getRoles().contains(offlineAccessRole.getId());
}
private UserSessionModel createOfflineUserSession(UserModel user, UserSessionModel userSession) {
if (logger.isTraceEnabled()) {
logger.tracef("Creating new offline user session. UserSessionID: '%s' , Username: '%s'", userSession.getId(), user.getUsername());
}
UserSessionModel offlineUserSession = kcSession.sessions().createOfflineUserSession(userSession);
persister.createUserSession(userSession, true);
return offlineUserSession;
}
private void createOfflineClientSession(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());
}
ClientSessionModel offlineClientSession = kcSession.sessions().createOfflineClientSession(clientSession);
offlineClientSession.setUserSession(userSession);
persister.createClientSession(clientSession, true);
}
// Check if userSession has any offline clientSessions attached to it. Remove userSession if not
private void checkOfflineUserSessionHasClientSessions(RealmModel realm, UserModel user, String userSessionId, List<ClientSessionModel> clientSessions) {
for (ClientSessionModel clientSession : clientSessions) {
if (clientSession.getUserSession().getId().equals(userSessionId)) {
return;
}
}
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);
}
kcSession.sessions().removeOfflineUserSession(realm, userSessionId);
persister.removeUserSession(userSessionId, true);
}
}
@@ -1,300 +0,0 @@
package org.keycloak.services.offline;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.codehaus.jackson.annotate.JsonProperty;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OfflineClientSessionAdapter implements ClientSessionModel {
private final OfflineClientSessionModel model;
private final RealmModel realm;
private final ClientModel client;
private final OfflineUserSessionAdapter userSession;
private OfflineClientSessionData data;
public OfflineClientSessionAdapter(OfflineClientSessionModel model, RealmModel realm, ClientModel client, OfflineUserSessionAdapter userSession) {
this.model = model;
this.realm = realm;
this.client = client;
this.userSession = userSession;
}
// lazily init representation
private OfflineClientSessionData getData() {
if (data == null) {
try {
data = JsonSerialization.readValue(model.getData(), OfflineClientSessionData.class);
} catch (IOException ioe) {
throw new ModelException(ioe);
}
}
return data;
}
@Override
public String getId() {
return model.getClientSessionId();
}
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public ClientModel getClient() {
return client;
}
@Override
public UserSessionModel getUserSession() {
return userSession;
}
@Override
public void setUserSession(UserSessionModel userSession) {
throw new IllegalStateException("Not supported setUserSession");
}
@Override
public String getRedirectUri() {
return getData().getRedirectUri();
}
@Override
public void setRedirectUri(String uri) {
throw new IllegalStateException("Not supported setRedirectUri");
}
@Override
public int getTimestamp() {
return getData().getTimestamp();
}
@Override
public void setTimestamp(int timestamp) {
throw new IllegalStateException("Not supported setTimestamp");
}
@Override
public String getAction() {
return null;
}
@Override
public void setAction(String action) {
throw new IllegalStateException("Not supported setAction");
}
@Override
public Set<String> getRoles() {
return getData().getRoles();
}
@Override
public void setRoles(Set<String> roles) {
throw new IllegalStateException("Not supported setRoles");
}
@Override
public Set<String> getProtocolMappers() {
return getData().getProtocolMappers();
}
@Override
public void setProtocolMappers(Set<String> protocolMappers) {
throw new IllegalStateException("Not supported setProtocolMappers");
}
@Override
public Map<String, ExecutionStatus> getExecutionStatus() {
return getData().getAuthenticatorStatus();
}
@Override
public void setExecutionStatus(String authenticator, ExecutionStatus status) {
throw new IllegalStateException("Not supported setExecutionStatus");
}
@Override
public void clearExecutionStatus() {
throw new IllegalStateException("Not supported clearExecutionStatus");
}
@Override
public UserModel getAuthenticatedUser() {
return userSession.getUser();
}
@Override
public void setAuthenticatedUser(UserModel user) {
throw new IllegalStateException("Not supported setAuthenticatedUser");
}
@Override
public String getAuthMethod() {
return getData().getAuthMethod();
}
@Override
public void setAuthMethod(String method) {
throw new IllegalStateException("Not supported setAuthMethod");
}
@Override
public String getNote(String name) {
return getData().getNotes()==null ? null : getData().getNotes().get(name);
}
@Override
public void setNote(String name, String value) {
throw new IllegalStateException("Not supported setNote");
}
@Override
public void removeNote(String name) {
throw new IllegalStateException("Not supported removeNote");
}
@Override
public Map<String, String> getNotes() {
return getData().getNotes();
}
@Override
public Set<String> getRequiredActions() {
throw new IllegalStateException("Not supported getRequiredActions");
}
@Override
public void addRequiredAction(String action) {
throw new IllegalStateException("Not supported addRequiredAction");
}
@Override
public void removeRequiredAction(String action) {
throw new IllegalStateException("Not supported removeRequiredAction");
}
@Override
public void addRequiredAction(UserModel.RequiredAction action) {
throw new IllegalStateException("Not supported addRequiredAction");
}
@Override
public void removeRequiredAction(UserModel.RequiredAction action) {
throw new IllegalStateException("Not supported removeRequiredAction");
}
@Override
public void setUserSessionNote(String name, String value) {
throw new IllegalStateException("Not supported setUserSessionNote");
}
@Override
public Map<String, String> getUserSessionNotes() {
throw new IllegalStateException("Not supported getUserSessionNotes");
}
@Override
public void clearUserSessionNotes() {
throw new IllegalStateException("Not supported clearUserSessionNotes");
}
protected static class OfflineClientSessionData {
@JsonProperty("authMethod")
private String authMethod;
@JsonProperty("redirectUri")
private String redirectUri;
@JsonProperty("protocolMappers")
private Set<String> protocolMappers;
@JsonProperty("roles")
private Set<String> roles;
@JsonProperty("notes")
private Map<String, String> notes;
@JsonProperty("authenticatorStatus")
private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
@JsonProperty("timestamp")
private int timestamp;
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public Set<String> getProtocolMappers() {
return protocolMappers;
}
public void setProtocolMappers(Set<String> protocolMappers) {
this.protocolMappers = protocolMappers;
}
public Set<String> getRoles() {
return roles;
}
public void setRoles(Set<String> roles) {
this.roles = roles;
}
public Map<String, String> getNotes() {
return notes;
}
public void setNotes(Map<String, String> notes) {
this.notes = notes;
}
public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
return authenticatorStatus;
}
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
}
}
@@ -1,192 +0,0 @@
package org.keycloak.services.offline;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
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;
import org.keycloak.models.RealmModel;
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;
/**
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OfflineTokenUtils {
protected static Logger logger = Logger.getLogger(OfflineTokenUtils.class);
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 = kcSession.users().getOfflineClientSessions(realm, user);
for (OfflineClientSessionModel existing : clientSessions) {
if (existing.getClientId().equals(client.getId())) {
if (logger.isTraceEnabled()) {
logger.tracef("Removing existing offline token for user '%s' and client '%s' . ClientSessionID was '%s' . Offline token will be replaced with new one",
user.getUsername(), client.getClientId(), 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(kcSession, realm, user, existing.getUserSessionId());
}
}
}
// Verify if we already have UserSession with this ID. If yes, don't create another one
OfflineUserSessionModel userSessionRep = kcSession.users().getOfflineUserSession(realm, user, userSession.getId());
if (userSessionRep == null) {
createOfflineUserSession(kcSession, realm, user, userSession);
}
// Create clientRep and save to DB.
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(KeycloakSession kcSession, RealmModel realm, UserModel user, String clientSessionId, String userSessionId) {
OfflineClientSessionModel clientSession = kcSession.users().getOfflineClientSession(realm, user, clientSessionId);
if (clientSession == null) {
return null;
}
if (!userSessionId.equals(clientSession.getUserSessionId())) {
throw new ModelException("User session don't match. Offline client session " + clientSession.getClientSessionId() + ", It's user session " + clientSession.getUserSessionId() +
" Wanted user session: " + userSessionId);
}
OfflineUserSessionModel userSession = kcSession.users().getOfflineUserSession(realm, user, userSessionId);
if (userSession == null) {
throw new ModelException("Found clientSession " + clientSessionId + " but not userSession " + userSessionId);
}
OfflineUserSessionAdapter userSessionAdapter = new OfflineUserSessionAdapter(userSession, user);
ClientModel client = realm.getClientById(clientSession.getClientId());
return new OfflineClientSessionAdapter(clientSession, realm, client, userSessionAdapter);
}
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());
clients.add(client);
}
return clients;
}
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())) {
if (logger.isTraceEnabled()) {
logger.tracef("Removing existing offline token for user '%s' and client '%s' . ClientSessionID was '%s' .",
user.getUsername(), client.getClientId(), clientSession.getClientSessionId());
}
kcSession.users().removeOfflineClientSession(realm, user, clientSession.getClientSessionId());
checkUserSessionHasClientSessions(kcSession, realm, user, clientSession.getUserSessionId());
anyRemoved = true;
}
}
return anyRemoved;
}
public static boolean isOfflineTokenAllowed(RealmModel realm, ClientSessionModel clientSession) {
RoleModel offlineAccessRole = realm.getRole(Constants.OFFLINE_ACCESS_ROLE);
if (offlineAccessRole == null) {
logger.warnf("Role '%s' not available in realm", Constants.OFFLINE_ACCESS_ROLE);
return false;
}
return clientSession.getRoles().contains(offlineAccessRole.getId());
}
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());
}
OfflineUserSessionAdapter.OfflineUserSessionData rep = new OfflineUserSessionAdapter.OfflineUserSessionData();
rep.setBrokerUserId(userSession.getBrokerUserId());
rep.setBrokerSessionId(userSession.getBrokerSessionId());
rep.setIpAddress(userSession.getIpAddress());
rep.setAuthMethod(userSession.getAuthMethod());
rep.setRememberMe(userSession.isRememberMe());
rep.setStarted(userSession.getStarted());
rep.setNotes(userSession.getNotes());
try {
String stringRep = JsonSerialization.writeValueAsString(rep);
OfflineUserSessionModel sessionModel = new OfflineUserSessionModel();
sessionModel.setUserSessionId(userSession.getId());
sessionModel.setData(stringRep);
kcSession.users().addOfflineUserSession(realm, user, sessionModel);
} catch (IOException ioe) {
throw new ModelException(ioe);
}
}
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());
}
OfflineClientSessionAdapter.OfflineClientSessionData rep = new OfflineClientSessionAdapter.OfflineClientSessionData();
rep.setAuthMethod(clientSession.getAuthMethod());
rep.setRedirectUri(clientSession.getRedirectUri());
rep.setProtocolMappers(clientSession.getProtocolMappers());
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);
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(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)) {
return;
}
}
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);
}
kcSession.users().removeOfflineUserSession(realm, user, userSessionId);
}
}
@@ -1,215 +0,0 @@
package org.keycloak.services.offline;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.codehaus.jackson.annotate.JsonProperty;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OfflineUserSessionAdapter implements UserSessionModel {
private final OfflineUserSessionModel model;
private final UserModel user;
private OfflineUserSessionData data;
public OfflineUserSessionAdapter(OfflineUserSessionModel model, UserModel user) {
this.model = model;
this.user = user;
}
// lazily init representation
private OfflineUserSessionData getData() {
if (data == null) {
try {
data = JsonSerialization.readValue(model.getData(), OfflineUserSessionData.class);
} catch (IOException ioe) {
throw new ModelException(ioe);
}
}
return data;
}
@Override
public String getId() {
return model.getUserSessionId();
}
@Override
public String getBrokerSessionId() {
return getData().getBrokerSessionId();
}
@Override
public String getBrokerUserId() {
return getData().getBrokerUserId();
}
@Override
public UserModel getUser() {
return user;
}
@Override
public String getLoginUsername() {
return user.getUsername();
}
@Override
public String getIpAddress() {
return getData().getIpAddress();
}
@Override
public String getAuthMethod() {
return getData().getAuthMethod();
}
@Override
public boolean isRememberMe() {
return getData().isRememberMe();
}
@Override
public int getStarted() {
return getData().getStarted();
}
@Override
public int getLastSessionRefresh() {
return 0;
}
@Override
public void setLastSessionRefresh(int seconds) {
// Ignore
}
@Override
public List<ClientSessionModel> getClientSessions() {
throw new IllegalStateException("Not yet supported");
}
@Override
public String getNote(String name) {
return getData().getNotes()==null ? null : getData().getNotes().get(name);
}
@Override
public void setNote(String name, String value) {
throw new IllegalStateException("Illegal to set note offline session");
}
@Override
public void removeNote(String name) {
throw new IllegalStateException("Illegal to remove note from offline session");
}
@Override
public Map<String, String> getNotes() {
return getData().getNotes();
}
@Override
public State getState() {
return null;
}
@Override
public void setState(State state) {
throw new IllegalStateException("Illegal to set state on offline session");
}
protected static class OfflineUserSessionData {
@JsonProperty("brokerSessionId")
private String brokerSessionId;
@JsonProperty("brokerUserId")
private String brokerUserId;
@JsonProperty("ipAddress")
private String ipAddress;
@JsonProperty("authMethod")
private String authMethod;
@JsonProperty("rememberMe")
private boolean rememberMe;
@JsonProperty("started")
private int started;
@JsonProperty("notes")
private Map<String, String> notes;
public String getBrokerSessionId() {
return brokerSessionId;
}
public void setBrokerSessionId(String brokerSessionId) {
this.brokerSessionId = brokerSessionId;
}
public String getBrokerUserId() {
return brokerUserId;
}
public void setBrokerUserId(String brokerUserId) {
this.brokerUserId = brokerUserId;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.rememberMe = rememberMe;
}
public int getStarted() {
return started;
}
public void setStarted(int started) {
this.started = started;
}
public Map<String, String> getNotes() {
return notes;
}
public void setNotes(Map<String, String> notes) {
this.notes = notes;
}
}
}
@@ -56,8 +56,8 @@ import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.offline.OfflineTokenUtils;
import org.keycloak.services.util.ResolveRelative;
import org.keycloak.services.validation.Validation;
import org.keycloak.util.UriUtils;
@@ -486,7 +486,7 @@ public class AccountService extends AbstractSecuredLocalService {
// Revoke grant in UserModel
UserModel user = auth.getUser();
user.revokeConsentForClient(client.getId());
OfflineTokenUtils.revokeOfflineToken(session, realm, user, client);
new UserSessionManager(session).revokeOfflineToken(user, client);
// Logout clientSessions for this user and client
AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
@@ -7,11 +7,8 @@ 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;
@@ -27,8 +24,6 @@ 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;
@@ -413,7 +408,7 @@ public class ClientResource {
public Map<String, Integer> getOfflineSessionCount() {
auth.requireView();
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("count", session.users().getOfflineClientSessionsCount(client.getRealm(), client));
map.put("count", session.sessions().getOfflineSessionsCount(client.getRealm(), client));
return map;
}
@@ -435,19 +430,9 @@ public class ClientResource {
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());
List<UserSessionModel> userSessions = session.sessions().getOfflineUserSessions(client.getRealm(), client, firstResult, maxResults);
for (UserSessionModel userSession : userSessions) {
UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(userSession);
sessions.add(rep);
}
return sessions;
@@ -77,7 +77,7 @@ 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.managers.UserSessionManager;
import org.keycloak.services.resources.AccountService;
/**
@@ -451,7 +451,7 @@ public class UsersResource {
List<Map<String, Object>> result = new LinkedList<>();
Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(session, realm, user);
Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
for (ClientModel client : realm.getClients()) {
UserConsentModel consent = user.getConsentByClient(client.getId());
@@ -496,7 +496,7 @@ public class UsersResource {
ClientModel client = realm.getClientByClientId(clientId);
boolean revokedConsent = user.revokeConsentForClient(client.getId());
boolean revokedOfflineToken = OfflineTokenUtils.revokeOfflineToken(session, realm, user, client);
boolean revokedOfflineToken = new UserSessionManager(session).revokeOfflineToken(user, client);
if (revokedConsent) {
// Logout clientSessions for this user and client