diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/JpaChangesPerformer.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/JpaChangesPerformer.java index 767fecf79df..6ef3e8f5b00 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/JpaChangesPerformer.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/JpaChangesPerformer.java @@ -22,6 +22,7 @@ import org.jboss.logging.Logger; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelIllegalStateException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; @@ -125,576 +126,618 @@ public class JpaChangesPerformer implements SessionC } } - private void processClientSessionUpdate(KeycloakSession innerSession, Map.Entry> entry, MergedUpdate merged) { + private void processClientSessionUpdate(KeycloakSession session, Map.Entry> entry, MergedUpdate merged) { SessionUpdatesList sessionUpdates = entry.getValue(); SessionEntityWrapper sessionWrapper = sessionUpdates.getEntityWrapper(); RealmModel realm = sessionUpdates.getRealm(); - UserSessionPersisterProvider userSessionPersister = innerSession.getProvider(UserSessionPersisterProvider.class); + UserSessionPersisterProvider userSessionPersister = session.getProvider(UserSessionPersisterProvider.class); - if (merged.getOperation() == SessionUpdateTask.CacheOperation.REMOVE) { - AuthenticatedClientSessionEntity entity = (AuthenticatedClientSessionEntity) sessionWrapper.getEntity(); - userSessionPersister.removeClientSession(entity.getUserSessionId(), entity.getClientId(), entity.isOffline()); - } else if (merged.getOperation() == SessionUpdateTask.CacheOperation.ADD || merged.getOperation() == SessionUpdateTask.CacheOperation.ADD_IF_ABSENT){ - AuthenticatedClientSessionEntity entity = (AuthenticatedClientSessionEntity) sessionWrapper.getEntity(); - userSessionPersister.createClientSession(new AuthenticatedClientSessionModel() { + switch (merged.getOperation()) { + case REMOVE -> { + AuthenticatedClientSessionEntity entity = (AuthenticatedClientSessionEntity) sessionWrapper.getEntity(); + userSessionPersister.removeClientSession(entity.getUserSessionId(), entity.getClientId(), entity.isOffline()); + } + case ADD, ADD_IF_ABSENT -> createClientSession(session, sessionWrapper, userSessionPersister); + case REPLACE -> mergeClientSession(sessionWrapper, userSessionPersister, realm, sessionUpdates); + } + + } + + private void mergeClientSession(SessionEntityWrapper sessionWrapper, UserSessionPersisterProvider userSessionPersister, RealmModel realm, SessionUpdatesList sessionUpdates) { + AuthenticatedClientSessionEntity entity = (AuthenticatedClientSessionEntity) sessionWrapper.getEntity(); + ClientModel client = new ClientModelLazyDelegate(null) { + @Override + public String getId() { + return Objects.requireNonNull(entity.getClientId()); + } + }; + UserSessionModel userSession = new UserSessionModelDelegate(null) { + @Override + public String getId() { + return Objects.requireNonNull(entity.getUserSessionId()); + } + }; + PersistentAuthenticatedClientSessionAdapter clientSessionModel = (PersistentAuthenticatedClientSessionAdapter) userSessionPersister.loadClientSession(realm, client, userSession, entity.isOffline()); + if (clientSessionModel != null) { + AuthenticatedClientSessionEntity authenticatedClientSessionEntity = new AuthenticatedClientSessionEntity(entity.getId()) { @Override - public int getStarted() { - return entity.getStarted(); + public Map getNotes() { + return new HashMap<>() { + @Override + public String get(Object key) { + return clientSessionModel.getNotes().get(key); + } + + @Override + public String put(String key, String value) { + String oldValue = clientSessionModel.getNotes().get(key); + clientSessionModel.setNote(key, value); + return oldValue; + } + }; } @Override - public int getUserSessionStarted() { - return entity.getUserSessionStarted(); - } - - @Override - public boolean isUserSessionRememberMe() { - return entity.isUserSessionRememberMe(); - } - - @Override - public String getId() { - return entity.getId().toString(); - } - - @Override - public int getTimestamp() { - return entity.getTimestamp(); + public void setRedirectUri(String redirectUri) { + clientSessionModel.setRedirectUri(redirectUri); } @Override public void setTimestamp(int timestamp) { - throw new IllegalStateException("not implemented"); - } - - @Override - public void detachFromUserSession() { - throw new IllegalStateException("not implemented"); - } - - @Override - public UserSessionModel getUserSession() { - return new UserSessionModelDelegate(null) { - @Override - public String getId() { - return entity.getUserSessionId(); - } - }; - } - - @Override - public String getNote(String name) { - return entity.getNotes().get(name); - } - - @Override - public void setNote(String name, String value) { - throw new IllegalStateException("not implemented"); - } - - @Override - public void removeNote(String name) { - throw new IllegalStateException("not implemented"); - } - - @Override - public Map getNotes() { - return entity.getNotes(); - } - - @Override - public String getRedirectUri() { - return entity.getRedirectUri(); - } - - @Override - public void setRedirectUri(String uri) { - throw new IllegalStateException("not implemented"); - } - - @Override - public RealmModel getRealm() { - return innerSession.realms().getRealm(entity.getRealmId()); - } - - @Override - public ClientModel getClient() { - return new ClientModelLazyDelegate(() -> null) { - @Override - public String getId() { - return entity.getClientId(); - } - }; - } - - @Override - public String getAction() { - return entity.getAction(); + clientSessionModel.setTimestamp(timestamp); } @Override public void setAction(String action) { - throw new IllegalStateException("not implemented"); + clientSessionModel.setAction(action); } @Override - public String getProtocol() { - return entity.getAuthMethod(); - } - - @Override - public void setProtocol(String method) { - throw new IllegalStateException("not implemented"); - } - }, entity.isOffline()); - } else { - AuthenticatedClientSessionEntity entity = (AuthenticatedClientSessionEntity) sessionWrapper.getEntity(); - ClientModel client = new ClientModelLazyDelegate(null) { - @Override - public String getId() { - return Objects.requireNonNull(entity.getClientId()); - } - }; - UserSessionModel userSession = new UserSessionModelDelegate(null) { - @Override - public String getId() { - return Objects.requireNonNull(entity.getUserSessionId()); - } - }; - PersistentAuthenticatedClientSessionAdapter clientSessionModel = (PersistentAuthenticatedClientSessionAdapter) userSessionPersister.loadClientSession(realm, client, userSession, entity.isOffline()); - if (clientSessionModel != null) { - AuthenticatedClientSessionEntity authenticatedClientSessionEntity = new AuthenticatedClientSessionEntity(entity.getId()) { - @Override - public Map getNotes() { - return new HashMap<>() { - @Override - public String get(Object key) { - return clientSessionModel.getNotes().get(key); - } - - @Override - public String put(String key, String value) { - String oldValue = clientSessionModel.getNotes().get(key); - clientSessionModel.setNote(key, value); - return oldValue; - } - }; - } - - @Override - public void setRedirectUri(String redirectUri) { - clientSessionModel.setRedirectUri(redirectUri); - } - - @Override - public void setTimestamp(int timestamp) { - clientSessionModel.setTimestamp(timestamp); - } - - @Override - public void setAction(String action) { - clientSessionModel.setAction(action); - } - - @Override - public void setAuthMethod(String authMethod) { - clientSessionModel.setProtocol(authMethod); - } - - @Override - public String getAuthMethod() { - throw new IllegalStateException("not implemented"); - } - - @Override - public String getRedirectUri() { - return clientSessionModel.getRedirectUri(); - } - - @Override - public int getTimestamp() { - return clientSessionModel.getTimestamp(); - } - - @Override - public int getUserSessionStarted() { - return clientSessionModel.getUserSessionStarted(); - } - - @Override - public int getStarted() { - return clientSessionModel.getStarted(); - } - - @Override - public boolean isUserSessionRememberMe() { - return clientSessionModel.isUserSessionRememberMe(); - } - - @Override - public String getClientId() { - return clientSessionModel.getClient().getClientId(); - } - - @Override - public void setClientId(String clientId) { - throw new IllegalStateException("not implemented"); - } - - @Override - public String getAction() { - return clientSessionModel.getAction(); - } - - @Override - public void setNotes(Map notes) { - clientSessionModel.getNotes().keySet().forEach(clientSessionModel::removeNote); - notes.forEach((k, v) -> clientSessionModel.setNote(k, v)); - } - - @Override - public UUID getId() { - return UUID.fromString(clientSessionModel.getId()); - } - - @Override - public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) { - throw new IllegalStateException("not implemented"); - } - - @Override - public String getUserSessionId() { - return clientSessionModel.getUserSession().getId(); - } - - @Override - public void setUserSessionId(String userSessionId) { - throw new IllegalStateException("not implemented"); - } - }; - sessionUpdates.getUpdateTasks().forEach(vSessionUpdateTask -> { - vSessionUpdateTask.runUpdate((V) authenticatedClientSessionEntity); - if (vSessionUpdateTask.getOperation() == SessionUpdateTask.CacheOperation.REMOVE) { - userSessionPersister.removeClientSession(entity.getUserSessionId(), entity.getClientId(), entity.isOffline()); - } - }); - clientSessionModel.getUpdatedModel(); - } - } - - } - - private void processUserSessionUpdate(KeycloakSession innerSession, Map.Entry> entry, MergedUpdate merged) { - SessionUpdatesList sessionUpdates = entry.getValue(); - SessionEntityWrapper sessionWrapper = sessionUpdates.getEntityWrapper(); - RealmModel realm = sessionUpdates.getRealm(); - UserSessionPersisterProvider userSessionPersister = innerSession.getProvider(UserSessionPersisterProvider.class); - UserSessionEntity entity = (UserSessionEntity) sessionWrapper.getEntity(); - - if (merged.getOperation() == SessionUpdateTask.CacheOperation.REMOVE) { - userSessionPersister.removeUserSession(entry.getKey().toString(), entity.isOffline()); - } else if (merged.getOperation() == SessionUpdateTask.CacheOperation.ADD || merged.getOperation() == SessionUpdateTask.CacheOperation.ADD_IF_ABSENT){ - userSessionPersister.createUserSession(new UserSessionModel() { - @Override - public String getId() { - return entity.getId(); - } - - @Override - public RealmModel getRealm() { - return new RealmModelDelegate(null) { - @Override - public String getId() { - return entity.getRealmId(); - } - }; - } - - @Override - public String getBrokerSessionId() { - return entity.getBrokerSessionId(); - } - - @Override - public String getBrokerUserId() { - return entity.getBrokerUserId(); - } - - @Override - public UserModel getUser() { - return new UserModelDelegate(null) { - @Override - public String getId() { - return entity.getUser(); - } - }; - } - - @Override - public String getLoginUsername() { - return entity.getLoginUsername(); - } - - @Override - public String getIpAddress() { - return entity.getIpAddress(); + public void setAuthMethod(String authMethod) { + clientSessionModel.setProtocol(authMethod); } @Override public String getAuthMethod() { - return entity.getAuthMethod(); + throw new IllegalStateException("not implemented"); } @Override - public boolean isRememberMe() { - return entity.isRememberMe(); + public String getRedirectUri() { + return clientSessionModel.getRedirectUri(); + } + + @Override + public int getTimestamp() { + return clientSessionModel.getTimestamp(); + } + + @Override + public int getUserSessionStarted() { + return clientSessionModel.getUserSessionStarted(); } @Override public int getStarted() { - return entity.getStarted(); + return clientSessionModel.getStarted(); } @Override - public int getLastSessionRefresh() { - return entity.getLastSessionRefresh(); + public boolean isUserSessionRememberMe() { + return clientSessionModel.isUserSessionRememberMe(); } @Override - public void setLastSessionRefresh(int seconds) { + public String getClientId() { + return clientSessionModel.getClient().getClientId(); + } + + @Override + public void setClientId(String clientId) { throw new IllegalStateException("not implemented"); } @Override - public boolean isOffline() { - return entity.isOffline(); + public String getAction() { + return clientSessionModel.getAction(); } @Override - public Map getAuthenticatedClientSessions() { - // This is not used when saving this to the database. - return Collections.emptyMap(); + public void setNotes(Map notes) { + clientSessionModel.getNotes().keySet().forEach(clientSessionModel::removeNote); + notes.forEach((k, v) -> clientSessionModel.setNote(k, v)); } @Override - public void removeAuthenticatedClientSessions(Collection removedClientUUIDS) { + public UUID getId() { + return UUID.fromString(clientSessionModel.getId()); + } + + @Override + public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) { throw new IllegalStateException("not implemented"); } @Override - public String getNote(String name) { - return entity.getNotes().get(name); + public String getUserSessionId() { + return clientSessionModel.getUserSession().getId(); } @Override - public void setNote(String name, String value) { + public void setUserSessionId(String userSessionId) { throw new IllegalStateException("not implemented"); } - - @Override - public void removeNote(String name) { - throw new IllegalStateException("not implemented"); + }; + sessionUpdates.getUpdateTasks().forEach(vSessionUpdateTask -> { + vSessionUpdateTask.runUpdate((V) authenticatedClientSessionEntity); + if (vSessionUpdateTask.getOperation() == SessionUpdateTask.CacheOperation.REMOVE) { + userSessionPersister.removeClientSession(entity.getUserSessionId(), entity.getClientId(), entity.isOffline()); } - - @Override - public Map getNotes() { - return entity.getNotes(); - } - - @Override - public State getState() { - return entity.getState(); - } - - @Override - public void setState(State state) { - throw new IllegalStateException("not implemented"); - } - - @Override - public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) { - throw new IllegalStateException("not implemented"); - } - }, entity.isOffline()); + }); + clientSessionModel.getUpdatedModel(); } else { - PersistentUserSessionAdapter userSessionModel = (PersistentUserSessionAdapter) userSessionPersister.loadUserSession(realm, entry.getKey().toString(), entity.isOffline()); - if (userSessionModel != null) { - UserSessionEntity userSessionEntity = new UserSessionEntity(userSessionModel.getId()) { - @Override - public Map getNotes() { - return new HashMap<>() { - - @Override - public String get(Object key) { - return userSessionModel.getNotes().get(key); - } - - @Override - public String put(String key, String value) { - String oldValue = userSessionModel.getNotes().get(key); - userSessionModel.setNote(key, value); - return oldValue; - } - - @Override - public String remove(Object key) { - String oldValue = userSessionModel.getNotes().get(key); - userSessionModel.removeNote(key.toString()); - return oldValue; - } - - @Override - public void clear() { - userSessionModel.getNotes().clear(); - } - }; - } - - @Override - public void setLastSessionRefresh(int lastSessionRefresh) { - userSessionModel.setLastSessionRefresh(lastSessionRefresh); - } - - @Override - public void setState(UserSessionModel.State state) { - userSessionModel.setState(state); - } - - @Override - public AuthenticatedClientSessionStore getAuthenticatedClientSessions() { - return new AuthenticatedClientSessionStore() { - @Override - public void clear() { - userSessionModel.getAuthenticatedClientSessions().clear(); - } - }; - } - - @Override - public String getRealmId() { - return userSessionModel.getRealm().getId(); - } - - @Override - public void setRealmId(String realmId) { - userSessionModel.setRealm(innerSession.realms().getRealm(realmId)); - } - - @Override - public String getUser() { - return userSessionModel.getUser().getId(); - } - - @Override - public void setUser(String userId) { - userSessionModel.setUser(innerSession.users().getUserById(realm, userId)); - } - - @Override - public String getLoginUsername() { - return userSessionModel.getLoginUsername(); - } - - @Override - public void setLoginUsername(String loginUsername) { - userSessionModel.setLoginUsername(loginUsername); - } - - @Override - public String getIpAddress() { - return userSessionModel.getIpAddress(); - } - - @Override - public void setIpAddress(String ipAddress) { - userSessionModel.setIpAddress(ipAddress); - } - - @Override - public String getAuthMethod() { - return userSessionModel.getAuthMethod(); - } - - @Override - public void setAuthMethod(String authMethod) { - userSessionModel.setAuthMethod(authMethod); - } - - @Override - public boolean isRememberMe() { - return userSessionModel.isRememberMe(); - } - - @Override - public void setRememberMe(boolean rememberMe) { - userSessionModel.setRememberMe(rememberMe); - } - - @Override - public int getStarted() { - return userSessionModel.getStarted(); - } - - @Override - public void setStarted(int started) { - userSessionModel.setStarted(started); - } - - @Override - public int getLastSessionRefresh() { - return userSessionModel.getLastSessionRefresh(); - } - - @Override - public void setNotes(Map notes) { - userSessionModel.getNotes().keySet().forEach(userSessionModel::removeNote); - notes.forEach((k, v) -> userSessionModel.setNote(k, v)); - } - - @Override - public void setAuthenticatedClientSessions(AuthenticatedClientSessionStore authenticatedClientSessions) { - throw new IllegalStateException("not supported"); - } - - @Override - public UserSessionModel.State getState() { - return userSessionModel.getState(); - } - - @Override - public String getBrokerSessionId() { - return userSessionModel.getBrokerSessionId(); - } - - @Override - public void setBrokerSessionId(String brokerSessionId) { - userSessionModel.setBrokerSessionId(brokerSessionId); - } - - @Override - public String getBrokerUserId() { - return userSessionModel.getBrokerUserId(); - } - - @Override - public void setBrokerUserId(String brokerUserId) { - userSessionModel.setBrokerUserId(brokerUserId); - } - - @Override - public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) { - throw new IllegalStateException("not supported"); - } - }; - sessionUpdates.getUpdateTasks().forEach(vSessionUpdateTask -> { - vSessionUpdateTask.runUpdate((V) userSessionEntity); - if (vSessionUpdateTask.getOperation() == SessionUpdateTask.CacheOperation.REMOVE) { - userSessionPersister.removeUserSession(entry.getKey().toString(), entity.isOffline()); - } - }); - userSessionModel.getUpdatedModel(); - } - + LOG.debugf("No client session found for %s/%s", entity.getUserSessionId(), entity.getClientId()); } } + + private static void createClientSession(KeycloakSession session, SessionEntityWrapper sessionWrapper, UserSessionPersisterProvider userSessionPersister) { + AuthenticatedClientSessionEntity entity = (AuthenticatedClientSessionEntity) sessionWrapper.getEntity(); + userSessionPersister.createClientSession(new AuthenticatedClientSessionModel() { + @Override + public int getStarted() { + return entity.getStarted(); + } + + @Override + public int getUserSessionStarted() { + return entity.getUserSessionStarted(); + } + + @Override + public boolean isUserSessionRememberMe() { + return entity.isUserSessionRememberMe(); + } + + @Override + public String getId() { + return entity.getId().toString(); + } + + @Override + public int getTimestamp() { + return entity.getTimestamp(); + } + + @Override + public void setTimestamp(int timestamp) { + throw new IllegalStateException("not implemented"); + } + + @Override + public void detachFromUserSession() { + throw new IllegalStateException("not implemented"); + } + + @Override + public UserSessionModel getUserSession() { + return new UserSessionModelDelegate(null) { + @Override + public String getId() { + return entity.getUserSessionId(); + } + }; + } + + @Override + public String getNote(String name) { + return entity.getNotes().get(name); + } + + @Override + public void setNote(String name, String value) { + throw new IllegalStateException("not implemented"); + } + + @Override + public void removeNote(String name) { + throw new IllegalStateException("not implemented"); + } + + @Override + public Map getNotes() { + return entity.getNotes(); + } + + @Override + public String getRedirectUri() { + return entity.getRedirectUri(); + } + + @Override + public void setRedirectUri(String uri) { + throw new IllegalStateException("not implemented"); + } + + @Override + public RealmModel getRealm() { + return session.realms().getRealm(entity.getRealmId()); + } + + @Override + public ClientModel getClient() { + return new ClientModelLazyDelegate(() -> null) { + @Override + public String getId() { + return entity.getClientId(); + } + }; + } + + @Override + public String getAction() { + return entity.getAction(); + } + + @Override + public void setAction(String action) { + throw new IllegalStateException("not implemented"); + } + + @Override + public String getProtocol() { + return entity.getAuthMethod(); + } + + @Override + public void setProtocol(String method) { + throw new IllegalStateException("not implemented"); + } + }, entity.isOffline()); + } + + private void processUserSessionUpdate(KeycloakSession session, Map.Entry> entry, MergedUpdate merged) { + SessionUpdatesList sessionUpdates = entry.getValue(); + SessionEntityWrapper sessionWrapper = sessionUpdates.getEntityWrapper(); + RealmModel realm = sessionUpdates.getRealm(); + UserSessionPersisterProvider userSessionPersister = session.getProvider(UserSessionPersisterProvider.class); + UserSessionEntity entity = (UserSessionEntity) sessionWrapper.getEntity(); + + switch (merged.getOperation()) { + case REMOVE -> userSessionPersister.removeUserSession(entry.getKey().toString(), entity.isOffline()); + case ADD -> throw new ModelIllegalStateException("Operation ADD is not implemented to overwrite an existing user session"); + case ADD_IF_ABSENT -> { + PersistentUserSessionAdapter userSessionModel = (PersistentUserSessionAdapter) userSessionPersister.loadUserSession(realm, entry.getKey().toString(), entity.isOffline()); + if (userSessionModel != null) { + // This might happen if the user logs in via multiple tabs at the same time from an external broker, and the same authentication session creates + // multiple user sessions concurrently. + if (!Objects.equals(userSessionModel.getUserId(), entity.getUser())) { + // This should never happen, and if it does, it shows a bug somewhere else where a wrong ID was used. + // Still, this check would help us to identify if such a problem exists, so this is why we keep it here. + throw new ModelIllegalStateException("User ID of the session does not match, the user ID should not change"); + } + if (Math.abs(userSessionModel.getStarted() - entity.getStarted()) > 10) { + // The only valid situation where a session is created with an already existing ID is that there are concurrent requests. + // For example, an authentication flow is triggered in two different tabs of a browser, and processed concurrently. + // In such a case, it is valid and this code will then update the first one created. + // In all other cases, if such a request for an authentication session would come in later, this should be handled in other places. + // Due to this, this is limited to the first 10 seconds of a user session to handle only the current login case. + throw new ModelIllegalStateException("Session has already aged, concurrent requests to create it should not happen"); + } + mergeUserSession(session, entry, userSessionModel, realm, sessionUpdates, userSessionPersister, entity); + } else { + createUserSession(userSessionPersister, entity); + } + } + case REPLACE -> { + PersistentUserSessionAdapter userSessionModel = (PersistentUserSessionAdapter) userSessionPersister.loadUserSession(realm, entry.getKey().toString(), entity.isOffline()); + if (userSessionModel != null) { + mergeUserSession(session, entry, userSessionModel, realm, sessionUpdates, userSessionPersister, entity); + } else { + LOG.debugf("No user session found for %s", entry.getKey()); + } + } + } + } + + private static void createUserSession(UserSessionPersisterProvider userSessionPersister, UserSessionEntity entity) { + userSessionPersister.createUserSession(new UserSessionModel() { + @Override + public String getId() { + return entity.getId(); + } + + @Override + public RealmModel getRealm() { + return new RealmModelDelegate(null) { + @Override + public String getId() { + return entity.getRealmId(); + } + }; + } + + @Override + public String getBrokerSessionId() { + return entity.getBrokerSessionId(); + } + + @Override + public String getBrokerUserId() { + return entity.getBrokerUserId(); + } + + @Override + public UserModel getUser() { + return new UserModelDelegate(null) { + @Override + public String getId() { + return entity.getUser(); + } + }; + } + + @Override + public String getLoginUsername() { + return entity.getLoginUsername(); + } + + @Override + public String getIpAddress() { + return entity.getIpAddress(); + } + + @Override + public String getAuthMethod() { + return entity.getAuthMethod(); + } + + @Override + public boolean isRememberMe() { + return entity.isRememberMe(); + } + + @Override + public int getStarted() { + return entity.getStarted(); + } + + @Override + public int getLastSessionRefresh() { + return entity.getLastSessionRefresh(); + } + + @Override + public void setLastSessionRefresh(int seconds) { + throw new IllegalStateException("not implemented"); + } + + @Override + public boolean isOffline() { + return entity.isOffline(); + } + + @Override + public Map getAuthenticatedClientSessions() { + // This is not used when saving this to the database. + return Collections.emptyMap(); + } + + @Override + public void removeAuthenticatedClientSessions(Collection removedClientUUIDS) { + throw new IllegalStateException("not implemented"); + } + + @Override + public String getNote(String name) { + return entity.getNotes().get(name); + } + + @Override + public void setNote(String name, String value) { + throw new IllegalStateException("not implemented"); + } + + @Override + public void removeNote(String name) { + throw new IllegalStateException("not implemented"); + } + + @Override + public Map getNotes() { + return entity.getNotes(); + } + + @Override + public State getState() { + return entity.getState(); + } + + @Override + public void setState(State state) { + throw new IllegalStateException("not implemented"); + } + + @Override + public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) { + throw new IllegalStateException("not implemented"); + } + }, entity.isOffline()); + } + + private void mergeUserSession(KeycloakSession innerSession, Map.Entry> entry, PersistentUserSessionAdapter userSessionModel, RealmModel realm, SessionUpdatesList sessionUpdates, UserSessionPersisterProvider userSessionPersister, UserSessionEntity entity) { + UserSessionEntity userSessionEntity = new UserSessionEntity(userSessionModel.getId()) { + @Override + public Map getNotes() { + return new HashMap<>() { + + @Override + public String get(Object key) { + return userSessionModel.getNotes().get(key); + } + + @Override + public String put(String key, String value) { + String oldValue = userSessionModel.getNotes().get(key); + userSessionModel.setNote(key, value); + return oldValue; + } + + @Override + public String remove(Object key) { + String oldValue = userSessionModel.getNotes().get(key); + userSessionModel.removeNote(key.toString()); + return oldValue; + } + + @Override + public void clear() { + userSessionModel.getNotes().clear(); + } + }; + } + + @Override + public void setLastSessionRefresh(int lastSessionRefresh) { + userSessionModel.setLastSessionRefresh(lastSessionRefresh); + } + + @Override + public void setState(UserSessionModel.State state) { + userSessionModel.setState(state); + } + + @Override + public AuthenticatedClientSessionStore getAuthenticatedClientSessions() { + return new AuthenticatedClientSessionStore() { + @Override + public void clear() { + userSessionModel.getAuthenticatedClientSessions().clear(); + } + }; + } + + @Override + public String getRealmId() { + return userSessionModel.getRealm().getId(); + } + + @Override + public void setRealmId(String realmId) { + userSessionModel.setRealm(innerSession.realms().getRealm(realmId)); + } + + @Override + public String getUser() { + return userSessionModel.getUser().getId(); + } + + @Override + public void setUser(String userId) { + userSessionModel.setUser(innerSession.users().getUserById(realm, userId)); + } + + @Override + public String getLoginUsername() { + return userSessionModel.getLoginUsername(); + } + + @Override + public void setLoginUsername(String loginUsername) { + userSessionModel.setLoginUsername(loginUsername); + } + + @Override + public String getIpAddress() { + return userSessionModel.getIpAddress(); + } + + @Override + public void setIpAddress(String ipAddress) { + userSessionModel.setIpAddress(ipAddress); + } + + @Override + public String getAuthMethod() { + return userSessionModel.getAuthMethod(); + } + + @Override + public void setAuthMethod(String authMethod) { + userSessionModel.setAuthMethod(authMethod); + } + + @Override + public boolean isRememberMe() { + return userSessionModel.isRememberMe(); + } + + @Override + public void setRememberMe(boolean rememberMe) { + userSessionModel.setRememberMe(rememberMe); + } + + @Override + public int getStarted() { + return userSessionModel.getStarted(); + } + + @Override + public void setStarted(int started) { + userSessionModel.setStarted(started); + } + + @Override + public int getLastSessionRefresh() { + return userSessionModel.getLastSessionRefresh(); + } + + @Override + public void setNotes(Map notes) { + userSessionModel.getNotes().keySet().forEach(userSessionModel::removeNote); + notes.forEach((k, v) -> userSessionModel.setNote(k, v)); + } + + @Override + public void setAuthenticatedClientSessions(AuthenticatedClientSessionStore authenticatedClientSessions) { + throw new IllegalStateException("not supported"); + } + + @Override + public UserSessionModel.State getState() { + return userSessionModel.getState(); + } + + @Override + public String getBrokerSessionId() { + return userSessionModel.getBrokerSessionId(); + } + + @Override + public void setBrokerSessionId(String brokerSessionId) { + userSessionModel.setBrokerSessionId(brokerSessionId); + } + + @Override + public String getBrokerUserId() { + return userSessionModel.getBrokerUserId(); + } + + @Override + public void setBrokerUserId(String brokerUserId) { + userSessionModel.setBrokerUserId(brokerUserId); + } + + @Override + public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) { + throw new IllegalStateException("not supported"); + } + }; + sessionUpdates.getUpdateTasks().forEach(vSessionUpdateTask -> { + vSessionUpdateTask.runUpdate((V) userSessionEntity); + if (vSessionUpdateTask.getOperation() == SessionUpdateTask.CacheOperation.REMOVE) { + userSessionPersister.removeUserSession(entry.getKey().toString(), entity.isOffline()); + } + }); + userSessionModel.getUpdatedModel(); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java index e78c2289589..adcbd478e61 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java @@ -45,7 +45,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -55,10 +54,6 @@ import static org.junit.Assert.assertTrue; */ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTest { - private static KeycloakSession currentSession; - private static RealmModel realm; - private static UserSessionManager sessionManager; - @Before public void before() { testingClient.server("test").run(session -> @@ -89,28 +84,25 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes public void testOfflineSessionsCrud(KeycloakSession session) { Map> offlineSessions = new HashMap<>(); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), (KeycloakSession sessionCrud) -> { + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), currentSession -> { // Create some online sessions in infinispan - reloadState(sessionCrud); - createSessions(sessionCrud); + reloadState(currentSession); + createSessions(currentSession); }); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), (KeycloakSession sessionCrud2) -> { - currentSession = sessionCrud2; - realm = currentSession.realms().getRealmByName("test"); - sessionManager = new UserSessionManager(currentSession); + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), currentSession -> { + RealmModel realm = currentSession.realms().getRealmByName("test"); // Key is userSession ID, values are client UUIDS // Persist 3 created userSessions and clientSessions as offline ClientModel testApp = realm.getClientByClientId("test-app"); - currentSession.sessions().getUserSessionsStream(realm, testApp).collect(Collectors.toList()) + currentSession.sessions().getUserSessionsStream(realm, testApp).toList() .forEach(userSession -> offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(currentSession, userSession))); }); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), (KeycloakSession sessionCrud3) -> { - currentSession = sessionCrud3; - realm = currentSession.realms().getRealmByName("test"); - sessionManager = new UserSessionManager(currentSession); + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), currentSession -> { + RealmModel realm = currentSession.realms().getRealmByName("test"); + UserSessionManager sessionManager = new UserSessionManager(currentSession); // Assert all previously saved offline sessions found for (Map.Entry> entry : offlineSessions.entrySet()) { @@ -123,7 +115,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes UserModel user1 = currentSession.users().getUserByUsername(realm, "user1"); Set clients = sessionManager.findClientsWithOfflineToken(realm, user1); - Assert.assertEquals(clients.size(), 2); + Assert.assertEquals(2, clients.size()); for (ClientModel client : clients) { Assert.assertTrue(client.getClientId().equals("test-app") || client.getClientId().equals("third-party")); } @@ -131,7 +123,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes UserModel user2 = currentSession.users().getUserByUsername(realm, "user2"); clients = sessionManager.findClientsWithOfflineToken(realm, user2); - Assert.assertEquals(clients.size(), 1); + Assert.assertEquals(1, clients.size()); Assert.assertEquals("test-app", clients.iterator().next().getClientId()); // Test count @@ -143,16 +135,15 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes sessionManager.revokeOfflineToken(user1, testApp); }); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), (KeycloakSession sessionCrud4) -> { - currentSession = sessionCrud4; - realm = currentSession.realms().getRealmByName("test"); - sessionManager = new UserSessionManager(currentSession); + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), currentSession -> { + RealmModel realm = currentSession.realms().getRealmByName("test"); + UserSessionManager sessionManager = new UserSessionManager(currentSession); // Assert userSession revoked ClientModel thirdparty = realm.getClientByClientId("third-party"); List thirdpartySessions = currentSession.sessions().getOfflineUserSessionsStream(realm, thirdparty, 0, 10) - .collect(Collectors.toList()); + .toList(); Assert.assertEquals(1, thirdpartySessions.size()); Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress()); Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername()); @@ -172,10 +163,9 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes }); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), (KeycloakSession sessionCrud5) -> { - currentSession = sessionCrud5; - realm = currentSession.realms().getRealmByName("test"); - sessionManager = new UserSessionManager(currentSession); + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), currentSession -> { + RealmModel realm = currentSession.realms().getRealmByName("test"); + UserSessionManager sessionManager = new UserSessionManager(currentSession); ClientModel testApp = realm.getClientByClientId("test-app"); ClientModel thirdparty = realm.getClientByClientId("third-party"); @@ -185,7 +175,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes Assert.assertEquals(0, currentSession.sessions().getOfflineSessionsCount(realm, thirdparty)); List testAppSessions = currentSession.sessions().getOfflineUserSessionsStream(realm, testApp, 0, 10) - .collect(Collectors.toList()); + .toList(); Assert.assertEquals(1, testAppSessions.size()); Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress()); @@ -203,8 +193,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes public void testOnRealmRemoved(KeycloakSession session) { AtomicReference userSessionID = new AtomicReference<>(); - String realmId = KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR1) -> { - currentSession = sessionRR1; + String realmId = KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), currentSession -> { RealmModel fooRealm = currentSession.realms().createRealm("foo"); currentSession.getContext().setRealm(fooRealm); fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName())); @@ -223,9 +212,8 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes return fooRealm.getId(); }); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR2) -> { - currentSession = sessionRR2; - sessionManager = new UserSessionManager(currentSession); + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { + UserSessionManager sessionManager = new UserSessionManager(currentSession); // Persist offline session RealmModel fooRealm = currentSession.realms().getRealm(realmId); @@ -234,18 +222,20 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes createOfflineSessionIncludeClientSessions(currentSession, userSession); UserSessionModel offlineUserSession = sessionManager.findOfflineUserSession(fooRealm, userSession.getId()); - Assert.assertEquals(offlineUserSession.getAuthenticatedClientSessions().size(), 1); + Assert.assertEquals(1, offlineUserSession.getAuthenticatedClientSessions().size()); AuthenticatedClientSessionModel offlineClientSession = offlineUserSession.getAuthenticatedClientSessions().values().iterator().next(); Assert.assertEquals("foo-app", offlineClientSession.getClient().getClientId()); Assert.assertEquals("user3", offlineClientSession.getUserSession().getUser().getUsername()); + }); - // Remove realm + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { + RealmModel fooRealm = currentSession.realms().getRealm(realmId); + currentSession.getContext().setRealm(fooRealm); RealmManager realmMgr = new RealmManager(currentSession); realmMgr.removeRealm(realmMgr.getRealm(realmId)); }); - - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR3) -> { - currentSession = sessionRR3; + + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { RealmModel fooRealm = currentSession.realms().createRealm(realmId, "foo"); currentSession.getContext().setRealm(fooRealm); fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName())); @@ -254,8 +244,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes currentSession.users().addUser(fooRealm, "user3"); }); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR4) -> { - currentSession = sessionRR4; + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { RealmModel fooRealm = currentSession.realms().getRealm(realmId); currentSession.getContext().setRealm(fooRealm); Assert.assertEquals(0, currentSession.sessions().getOfflineSessionsCount(fooRealm, fooRealm.getClientByClientId("foo-app"))); @@ -270,9 +259,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes @ModelTest public void testOnClientRemoved(KeycloakSession session) { AtomicReference userSessionID = new AtomicReference<>(); - String realmId = KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR1) -> { - currentSession = sessionCR1; - sessionManager = new UserSessionManager(currentSession); + String realmId = KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), currentSession -> { RealmModel fooRealm = currentSession.realms().createRealm("foo"); currentSession.getContext().setRealm(fooRealm); fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName())); @@ -297,8 +284,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes try { int started = Time.currentTime(); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR2) -> { - currentSession = sessionCR2; + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { // Create offline currentSession RealmModel fooRealm = currentSession.realms().getRealm(realmId); currentSession.getContext().setRealm(fooRealm); @@ -306,8 +292,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes createOfflineSessionIncludeClientSessions(currentSession, userSession); }); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR3) -> { - currentSession = sessionCR3; + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { RealmManager realmMgr = new RealmManager(currentSession); ClientManager clientMgr = new ClientManager(realmMgr); RealmModel fooRealm = realmMgr.getRealm(realmId); @@ -315,15 +300,14 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes // Assert currentSession was persisted with both clientSessions UserSessionModel offlineSession = currentSession.sessions().getOfflineUserSession(fooRealm, userSessionID.get()); - assertSession(offlineSession, currentSession.users().getUserByUsername(fooRealm, "user3"), "127.0.0.1", started, started, "foo-app", "bar-app"); + assertSession(offlineSession, currentSession.users().getUserByUsername(fooRealm, "user3"), "127.0.0.1", started, started); // Remove foo-app client ClientModel client = fooRealm.getClientByClientId("foo-app"); clientMgr.removeClient(fooRealm, client); }); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR4) -> { - currentSession = sessionCR4; + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { RealmManager realmMgr = new RealmManager(currentSession); ClientManager clientMgr = new ClientManager(realmMgr); RealmModel fooRealm = realmMgr.getRealm(realmId); @@ -339,8 +323,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes clientMgr.removeClient(fooRealm, client); }); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR5) -> { - currentSession = sessionCR5; + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { // Assert nothing loaded - userSession was removed as well because it was last userSession RealmManager realmMgr = new RealmManager(currentSession); RealmModel fooRealm = realmMgr.getRealm(realmId); @@ -352,8 +335,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes } catch (Exception e) { throw new RuntimeException(e); } finally { - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionTearDown) -> { - currentSession = sessionTearDown; + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { RealmManager realmMgr = new RealmManager(currentSession); RealmModel fooRealm = realmMgr.getRealm(realmId); currentSession.getContext().setRealm(fooRealm); @@ -373,8 +355,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes @ModelTest public void testOnUserRemoved(KeycloakSession session) { AtomicReference userSessionID = new AtomicReference<>(); - String realmId = KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), (KeycloakSession sessionUR1) -> { - currentSession = sessionUR1; + String realmId = KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), currentSession -> { RealmModel fooRealm = currentSession.realms().createRealm("foo"); currentSession.getContext().setRealm(fooRealm); fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName())); @@ -396,9 +377,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes int started = Time.currentTime(); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionUR2) -> { - currentSession = sessionUR2; - + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { // Create offline session RealmModel fooRealm = currentSession.realms().getRealm(realmId); currentSession.getContext().setRealm(fooRealm); @@ -406,9 +385,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes createOfflineSessionIncludeClientSessions(currentSession, userSession); }); - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionUR3) -> { - currentSession = sessionUR3; - + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { RealmManager realmMgr = new RealmManager(currentSession); RealmModel fooRealm = realmMgr.getRealm(realmId); currentSession.getContext().setRealm(fooRealm); @@ -416,15 +393,13 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes // Assert session was persisted with both clientSessions UserSessionModel offlineSession = currentSession.sessions().getOfflineUserSession(fooRealm, userSessionID.get()); - assertSession(offlineSession, user3, "127.0.0.1", started, started, "foo-app"); + assertSession(offlineSession, user3, "127.0.0.1", started, started); }); } catch (Exception e) { throw new RuntimeException(e); } finally { - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionTearDown) -> { - currentSession = sessionTearDown; - + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), currentSession -> { RealmManager realmMgr = new RealmManager(currentSession); RealmModel fooRealm = realmMgr.getRealm(realmId); currentSession.getContext().setRealm(fooRealm); @@ -453,7 +428,7 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes } public static void assertSession(UserSessionModel session, UserModel user, String ipAddress, int started, - int lastRefresh, String... clients) { + int lastRefresh) { assertEquals(user.getId(), session.getUser().getId()); assertEquals(ipAddress, session.getIpAddress()); assertEquals(user.getUsername(), session.getLoginUsername()); @@ -462,14 +437,10 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes assertTrue((session.getStarted() >= started - 1) && (session.getStarted() <= started + 1)); assertTrue((session.getLastSessionRefresh() >= lastRefresh - 1) && (session.getLastSessionRefresh() <= lastRefresh + 1)); - String[] actualClients = new String[session.getAuthenticatedClientSessions().size()]; - int i = 0; for (Map.Entry entry : session.getAuthenticatedClientSessions().entrySet()) { String clientUUID = entry.getKey(); AuthenticatedClientSessionModel clientSession = entry.getValue(); Assert.assertEquals(clientUUID, clientSession.getClient().getId()); - actualClients[i] = clientSession.getClient().getClientId(); - i++; } } @@ -481,26 +452,19 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes return clientSession; } - private static UserSessionModel[] createSessions(KeycloakSession session) { + private static UserSessionModel[] createSessions(KeycloakSession currentSession) { UserSessionModel[] sessions = new UserSessionModel[3]; - sessions[0] = session.sessions().createUserSession(null, realm, currentSession.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); + RealmModel realm = currentSession.getContext().getRealm(); + sessions[0] = currentSession.sessions().createUserSession(null, realm, currentSession.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); - Set roles = new HashSet(); - roles.add("one"); - roles.add("two"); + createClientSession(currentSession, realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state"); + createClientSession(currentSession, realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state"); - Set protocolMappers = new HashSet(); - protocolMappers.add("mapper-one"); - protocolMappers.add("mapper-two"); + sessions[1] = currentSession.sessions().createUserSession(null, realm, currentSession.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.2", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); + createClientSession(currentSession, realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state"); - createClientSession(session, realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state"); - createClientSession(session, realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state"); - - sessions[1] = session.sessions().createUserSession(null, realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.2", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); - createClientSession(session, realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state"); - - sessions[2] = session.sessions().createUserSession(null, realm, session.users().getUserByUsername(realm, "user2"), "user2", "127.0.0.3", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); - createClientSession(session, realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state"); + sessions[2] = currentSession.sessions().createUserSession(null, realm, currentSession.users().getUserByUsername(realm, "user2"), "user2", "127.0.0.3", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); + createClientSession(currentSession, realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state"); return sessions; } @@ -509,14 +473,12 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes reloadState(session, false); } - public static void reloadState(KeycloakSession session, Boolean initialConfig) { - currentSession = session; - realm = currentSession.realms().getRealmByName("test"); + public static void reloadState(KeycloakSession currentSession, Boolean initialConfig) { + RealmModel realm = currentSession.realms().getRealmByName("test"); if (initialConfig) { currentSession.users().addUser(realm, "user1").setEmail("user1@localhost"); currentSession.users().addUser(realm, "user2").setEmail("user2@localhost"); } - sessionManager = new UserSessionManager(currentSession); } @Override diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionPersisterProviderTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionPersisterProviderTest.java index c42cdada7d1..78c69dcc53c 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionPersisterProviderTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionPersisterProviderTest.java @@ -553,6 +553,10 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest { setTimeOffset(i); UserSessionModel userSession = session.sessions().createUserSession(null, realm, user, "user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); + if (userSessionsInner.contains(userSession.getId())) { + Assert.fail("Duplicate session id generated: " + userSession.getId()); + } + createClientSession(session, realmId, realm.getClientByClientId("test-app"), userSession, "http://redirect", "state"); userSessionsInner.add(userSession.getId()); } @@ -585,6 +589,48 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest { } + @Test + public void testConcurrentSessionCreation() { + String userSessionId = withRealm(realmId, (session, realm) -> { + UserModel user = session.users().getUserByUsername(realm, "user1"); + UserSessionModel userSession = session.sessions().createUserSession(null, realm, user, "user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); + userSession.setNote("ITERATION1", "true"); + return userSession.getId(); + }); + + // Simulate a concurrently created session + withRealm(realmId, (session, realm) -> { + UserModel user = session.users().getUserByUsername(realm, "user1"); + UserSessionModel userSession = session.sessions().createUserSession(userSessionId, realm, user, "user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); + userSession.setNote("ITERATION2", "true"); + return null; + }); + + withRealm(realmId, (session, realm) -> { + UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId); + assertThat(userSession.getNote("ITERATION1"), Matchers.equalTo("true")); + assertThat(userSession.getNote("ITERATION2"), Matchers.equalTo("true")); + return null; + }); + + if (MultiSiteUtils.isPersistentSessionsEnabled()) { + try { + // Simulate a concurrently created session with a different user + withRealm(realmId, (session, realm) -> { + UserModel user = session.users().getUserByUsername(realm, "user2"); + UserSessionModel userSession = session.sessions().createUserSession(userSessionId, realm, user, "user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); + userSession.setNote("ITERATION2", "true"); + return null; + }); + Assert.fail("Exception expected"); + } catch (RuntimeException e) { + assertThat(e.getMessage(), Matchers.containsString("unable to complete the session updates")); + assertThat(e.getSuppressed()[0].getMessage(), Matchers.containsString("User ID of the session does not match")); + } + } + + } + @Test public void testExpiredSessions() { int started = Time.currentTime();