Map Store Removal: Remove LockObjectsForModification (#25323)

Signed-off-by: vramik <vramik@redhat.com>

Closes #24793
This commit is contained in:
Vlasta Ramik 2023-12-07 07:43:43 -05:00 committed by GitHub
parent 0e535d2bbe
commit df465456b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 36 additions and 182 deletions

View file

@ -1,104 +0,0 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.utils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import java.util.HashSet;
import java.util.Set;
/**
* This flags the session that all information loaded from the stores should be locked as the service layer
* plans to modify it.
*
* This is just a hint to the underlying storage, and a store might choose to ignore it.
* The lock for any object retrieved from the session will be kept until the end of the transaction.
*
* If the store supports it, this could prevent exceptions due to optimistic locking
* problems later in the processing. If the caller retrieved objects without this wrapper, they would still be
* able to modify those objects, and those changes would be written to the store at the end of the transaction at the lastet,
* but they won't be locked.
*
*
* @author Alexander Schwartz
*/
public class LockObjectsForModification {
private static final String ATTRIBUTE = LockObjectsForModification.class.getCanonicalName();
public static boolean isEnabled(KeycloakSession session, Class<?> model) {
Set<Class<?>> lockedModels = getAttribute(session);
return lockedModels != null && lockedModels.contains(model);
}
private static Set<Class<?>> getAttribute(KeycloakSession session) {
//noinspection unchecked
return (Set<Class<?>>) session.getAttribute(ATTRIBUTE);
}
private static Set<Class<?>> getOrCreateAttribute(KeycloakSession session) {
Set<Class<?>> attribute = getAttribute(session);
if (attribute == null) {
attribute = new HashSet<>();
session.setAttribute(ATTRIBUTE, attribute);
}
return attribute;
}
public static <V> V lockUserSessionsForModification(KeycloakSession session, CallableWithoutThrowingAnException<V> callable) {
return lockObjectsForModification(session, UserSessionModel.class, callable);
}
public static <V> V lockRealmsForModification(KeycloakSession session, CallableWithoutThrowingAnException<V> callable) {
return lockObjectsForModification(session, RealmModel.class, callable);
}
private static <V> V lockObjectsForModification(KeycloakSession session, Class<?> model, CallableWithoutThrowingAnException<V> callable) {
// Only map storage supported locking objects for modification, this logic will be remove in a follow up PR
return callable.call();
}
@FunctionalInterface
public interface CallableWithoutThrowingAnException<V> {
/**
* Computes a result.
*
* @return computed result
*/
V call();
}
public static class Enabled implements AutoCloseable {
private final KeycloakSession session;
private final Class<?> model;
public Enabled(KeycloakSession session, Class<?> model) {
this.session = session;
this.model = model;
getOrCreateAttribute(session).add(model);
}
@Override
public void close() {
getOrCreateAttribute(session).remove(model);
}
}
}

View file

@ -71,7 +71,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser; import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -1064,7 +1063,7 @@ public class AuthenticationProcessor {
if (userSession == null) { // if no authenticator attached a usersession if (userSession == null) { // if no authenticator attached a usersession
userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, authSession.getParentSession().getId())); userSession = session.sessions().getUserSession(realm, authSession.getParentSession().getId());
if (userSession == null) { if (userSession == null) {
UserSessionModel.SessionPersistenceState persistenceState = UserSessionModel.SessionPersistenceState.fromString(authSession.getClientNote(AuthenticationManager.USER_SESSION_PERSISTENT_STATE)); UserSessionModel.SessionPersistenceState persistenceState = UserSessionModel.SessionPersistenceState.fromString(authSession.getClientNote(AuthenticationManager.USER_SESSION_PERSISTENT_STATE));

View file

@ -25,8 +25,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.utils.StringUtil; import org.keycloak.utils.StringUtil;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
public class UserSessionLimitsAuthenticator implements Authenticator { public class UserSessionLimitsAuthenticator implements Authenticator {
private static Logger logger = Logger.getLogger(UserSessionLimitsAuthenticator.class); private static Logger logger = Logger.getLogger(UserSessionLimitsAuthenticator.class);
@ -58,7 +56,7 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
if (context.getRealm() != null && context.getUser() != null) { if (context.getRealm() != null && context.getUser() != null) {
// Get the session count in this realm for this specific user // Get the session count in this realm for this specific user
List<UserSessionModel> userSessionsForRealm = lockUserSessionsForModification(session, () -> session.sessions().getUserSessionsStream(context.getRealm(), context.getUser()).collect(Collectors.toList())); List<UserSessionModel> userSessionsForRealm = session.sessions().getUserSessionsStream(context.getRealm(), context.getUser()).collect(Collectors.toList());
int userSessionCountForRealm = userSessionsForRealm.size(); int userSessionCountForRealm = userSessionsForRealm.size();
// Get the session count related to the current client for this user // Get the session count related to the current client for this user

View file

@ -100,8 +100,6 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.services.util.DefaultClientSessionContext; import org.keycloak.services.util.DefaultClientSessionContext;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@ -318,7 +316,7 @@ public class AuthorizationTokenService {
userSessionModel = new UserSessionManager(keycloakSession).createUserSession(KeycloakModelUtils.generateId(), realm, user, user.getUsername(), request.getClientConnection().getRemoteAddr(), userSessionModel = new UserSessionManager(keycloakSession).createUserSession(KeycloakModelUtils.generateId(), realm, user, user.getUsername(), request.getClientConnection().getRemoteAddr(),
ServiceAccountConstants.CLIENT_AUTH, false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT); ServiceAccountConstants.CLIENT_AUTH, false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
} else { } else {
userSessionModel = lockUserSessionsForModification(keycloakSession, () -> sessions.getUserSession(realm, accessToken.getSessionState())); userSessionModel = sessions.getUserSession(realm, accessToken.getSessionState());
if (userSessionModel == null) { if (userSessionModel == null) {
userSessionModel = sessions.getOfflineUserSession(realm, accessToken.getSessionState()); userSessionModel = sessions.getOfflineUserSession(realm, accessToken.getSessionState());

View file

@ -46,8 +46,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@ -128,7 +126,7 @@ public class KeycloakIdentity implements Identity {
this.accessToken = AccessToken.class.cast(token); this.accessToken = AccessToken.class.cast(token);
} else { } else {
UserSessionProvider sessions = keycloakSession.sessions(); UserSessionProvider sessions = keycloakSession.sessions();
UserSessionModel userSession = lockUserSessionsForModification(keycloakSession, () -> sessions.getUserSession(realm, token.getSessionState())); UserSessionModel userSession = sessions.getUserSession(realm, token.getSessionState());
if (userSession == null) { if (userSession == null) {
userSession = sessions.getOfflineUserSession(realm, token.getSessionState()); userSession = sessions.getOfflineUserSession(realm, token.getSessionState());
@ -297,7 +295,7 @@ public class KeycloakIdentity implements Identity {
} }
UserSessionProvider sessions = keycloakSession.sessions(); UserSessionProvider sessions = keycloakSession.sessions();
UserSessionModel userSession = lockUserSessionsForModification(keycloakSession, () -> sessions.getUserSession(realm, accessToken.getSessionState())); UserSessionModel userSession = sessions.getUserSession(realm, accessToken.getSessionState());
if (userSession == null) { if (userSession == null) {
userSession = sessions.getOfflineUserSession(realm, accessToken.getSessionState()); userSession = sessions.getOfflineUserSession(realm, accessToken.getSessionState());

View file

@ -43,8 +43,6 @@ import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
@ -97,7 +95,7 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
if (action.getKeycloakSessionIds() != null) { if (action.getKeycloakSessionIds() != null) {
for (String sessionId : action.getKeycloakSessionIds()) { for (String sessionId : action.getKeycloakSessionIds()) {
String brokerSessionId = provider.getConfig().getAlias() + "." + sessionId; String brokerSessionId = provider.getConfig().getAlias() + "." + sessionId;
UserSessionModel userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId)); UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
if (userSession != null if (userSession != null
&& userSession.getState() != UserSessionModel.State.LOGGING_OUT && userSession.getState() != UserSessionModel.State.LOGGING_OUT
&& userSession.getState() != UserSessionModel.State.LOGGED_OUT && userSession.getState() != UserSessionModel.State.LOGGED_OUT

View file

@ -82,8 +82,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author Pedro Igor * @author Pedro Igor
*/ */
@ -333,7 +331,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR); return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
} }
UserSessionModel userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, state)); UserSessionModel userSession = session.sessions().getUserSession(realm, state);
if (userSession == null) { if (userSession == null) {
logger.error("no valid user session"); logger.error("no valid user session");
EventBuilder event = new EventBuilder(realm, session, clientConnection); EventBuilder event = new EventBuilder(realm, session, clientConnection);

View file

@ -129,8 +129,6 @@ import java.util.Collections;
import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.MultivaluedMap;
import javax.xml.crypto.dsig.XMLSignature; import javax.xml.crypto.dsig.XMLSignature;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
@ -349,7 +347,7 @@ public class SAMLEndpoint {
} else { } else {
for (String sessionIndex : request.getSessionIndex()) { for (String sessionIndex : request.getSessionIndex()) {
String brokerSessionId = config.getAlias() + "." + sessionIndex; String brokerSessionId = config.getAlias() + "." + sessionIndex;
UserSessionModel userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId)); UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
if (userSession != null) { if (userSession != null) {
if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) { if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) {
continue; continue;
@ -724,7 +722,7 @@ public class SAMLEndpoint {
event.error(Errors.USER_SESSION_NOT_FOUND); event.error(Errors.USER_SESSION_NOT_FOUND);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR); return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
} }
UserSessionModel userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, relayState)); UserSessionModel userSession = session.sessions().getUserSession(realm, relayState);
if (userSession == null) { if (userSession == null) {
logger.error("no valid user session"); logger.error("no valid user session");
event.event(EventType.LOGOUT); event.event(EventType.LOGOUT);

View file

@ -116,7 +116,6 @@ import jakarta.ws.rs.core.UriInfo;
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser; import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
import static org.keycloak.representations.IDToken.NONCE; import static org.keycloak.representations.IDToken.NONCE;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* Stateless object that creates tokens and manages oauth access codes * Stateless object that creates tokens and manages oauth access codes
@ -164,7 +163,7 @@ public class TokenManager {
} }
} else { } else {
// Find userSession regularly for online tokens // Find userSession regularly for online tokens
userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, oldToken.getSessionState())); userSession = session.sessions().getUserSession(realm, oldToken.getSessionState());
if (!AuthenticationManager.isSessionValid(realm, userSession)) { if (!AuthenticationManager.isSessionValid(realm, userSession)) {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true); AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
@ -325,10 +324,10 @@ public class TokenManager {
private boolean validateTokenReuseForIntrospection(KeycloakSession session, RealmModel realm, AccessToken token) { private boolean validateTokenReuseForIntrospection(KeycloakSession session, RealmModel realm, AccessToken token) {
UserSessionModel userSession = null; UserSessionModel userSession = null;
if (token.getType().equals(TokenUtil.TOKEN_TYPE_REFRESH)) { if (token.getType().equals(TokenUtil.TOKEN_TYPE_REFRESH)) {
userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, token.getSessionState())); userSession = session.sessions().getUserSession(realm, token.getSessionState());
} else { } else {
UserSessionManager sessionManager = new UserSessionManager(session); UserSessionManager sessionManager = new UserSessionManager(session);
userSession = lockUserSessionsForModification(session, () -> sessionManager.findOfflineUserSession(realm, token.getSessionState())); userSession = sessionManager.findOfflineUserSession(realm, token.getSessionState());
} }
ClientModel client = realm.getClientByClientId(token.getIssuedFor()); ClientModel client = realm.getClientByClientId(token.getIssuedFor());

View file

@ -20,7 +20,6 @@ package org.keycloak.protocol.oidc.endpoints;
import static org.keycloak.models.UserSessionModel.State.LOGGED_OUT; import static org.keycloak.models.UserSessionModel.State.LOGGED_OUT;
import static org.keycloak.models.UserSessionModel.State.LOGGING_OUT; import static org.keycloak.models.UserSessionModel.State.LOGGING_OUT;
import static org.keycloak.services.resources.LoginActionsService.SESSION_CODE; import static org.keycloak.services.resources.LoginActionsService.SESSION_CODE;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
@ -435,7 +434,7 @@ public class LogoutEndpoint {
String idTokenIssuedAtStr = logoutSession.getAuthNote(OIDCLoginProtocol.LOGOUT_VALIDATED_ID_TOKEN_ISSUED_AT); String idTokenIssuedAtStr = logoutSession.getAuthNote(OIDCLoginProtocol.LOGOUT_VALIDATED_ID_TOKEN_ISSUED_AT);
if (userSessionIdFromIdToken != null && idTokenIssuedAtStr != null) { if (userSessionIdFromIdToken != null && idTokenIssuedAtStr != null) {
try { try {
userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, userSessionIdFromIdToken)); userSession = session.sessions().getUserSession(realm, userSessionIdFromIdToken);
if (userSession == null) { if (userSession == null) {
event.event(EventType.LOGOUT); event.event(EventType.LOGOUT);
@ -452,8 +451,7 @@ public class LogoutEndpoint {
} }
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways. // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
AuthenticationManager.AuthResult authResult = lockUserSessionsForModification(session, AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false);
() -> AuthenticationManager.authenticateIdentityCookie(session, realm, false));
if (authResult != null) { if (authResult != null) {
userSession = userSession != null ? userSession : authResult.getSession(); userSession = userSession != null ? userSession : authResult.getSession();
return initiateBrowserLogout(userSession); return initiateBrowserLogout(userSession);
@ -533,7 +531,7 @@ public class LogoutEndpoint {
userSessionModel = sessionManager.findOfflineUserSession(realm, token.getSessionState()); userSessionModel = sessionManager.findOfflineUserSession(realm, token.getSessionState());
} else { } else {
String sessionState = token.getSessionState(); String sessionState = token.getSessionState();
userSessionModel = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, sessionState)); userSessionModel = session.sessions().getUserSession(realm, sessionState);
} }
if (userSessionModel != null) { if (userSessionModel != null) {
@ -633,8 +631,7 @@ public class LogoutEndpoint {
AtomicReference<BackchannelLogoutResponse> backchannelLogoutResponse = new AtomicReference<>(new BackchannelLogoutResponse()); AtomicReference<BackchannelLogoutResponse> backchannelLogoutResponse = new AtomicReference<>(new BackchannelLogoutResponse());
backchannelLogoutResponse.get().setLocalLogoutSucceeded(true); backchannelLogoutResponse.get().setLocalLogoutSucceeded(true);
identityProviderAliases.forEach(identityProviderAlias -> { identityProviderAliases.forEach(identityProviderAlias -> {
UserSessionModel userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSessionByBrokerSessionId(realm, UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, identityProviderAlias + "." + sessionId);
identityProviderAlias + "." + sessionId));
if (logoutOfflineSessions) { if (logoutOfflineSessions) {
if (offlineSessionsLazyLoadingEnabled) { if (offlineSessionsLazyLoadingEnabled) {

View file

@ -124,8 +124,6 @@ import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
@ -574,7 +572,7 @@ public class TokenEndpoint {
res = responseBuilder.build(); res = responseBuilder.build();
if (!responseBuilder.isOfflineToken()) { if (!responseBuilder.isOfflineToken()) {
UserSessionModel userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, res.getSessionState())); UserSessionModel userSession = session.sessions().getUserSession(realm, res.getSessionState());
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId()); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
updateClientSession(clientSession); updateClientSession(clientSession);
updateUserSessionFromClientAuth(userSession); updateUserSessionFromClientAuth(userSession);

View file

@ -18,7 +18,6 @@
package org.keycloak.protocol.oidc.grants.device; package org.keycloak.protocol.oidc.grants.device;
import static org.keycloak.protocol.oidc.OIDCLoginProtocolService.tokenServiceBaseUrl; import static org.keycloak.protocol.oidc.OIDCLoginProtocolService.tokenServiceBaseUrl;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
@ -291,7 +290,7 @@ public class DeviceGrantType {
client.getId()); client.getId());
if (userSession == null) { if (userSession == null) {
userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, userSessionId)); userSession = session.sessions().getUserSession(realm, userSessionId);
if (userSession == null) { if (userSession == null) {
throw new CorsErrorResponseException(cors, OAuthErrorException.AUTHORIZATION_PENDING, throw new CorsErrorResponseException(cors, OAuthErrorException.AUTHORIZATION_PENDING,
"The authorization request is verified but can not lookup the user session yet", "The authorization request is verified but can not lookup the user session yet",

View file

@ -31,8 +31,6 @@ import org.keycloak.models.SingleUseObjectProvider;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.services.managers.UserSessionCrossDCManager; import org.keycloak.services.managers.UserSessionCrossDCManager;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@ -101,10 +99,10 @@ public class OAuth2CodeParser {
} }
// Retrieve UserSession // Retrieve UserSession
UserSessionModel userSession = lockUserSessionsForModification(session, () -> new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, userSessionId, clientUUID)); UserSessionModel userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, userSessionId, clientUUID);
if (userSession == null) { if (userSession == null) {
// Needed to track if code is invalid or was already used. // Needed to track if code is invalid or was already used.
userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, userSessionId)); userSession = session.sessions().getUserSession(realm, userSessionId);
if (userSession == null) { if (userSession == null) {
return result.illegalCode(); return result.illegalCode();
} }

View file

@ -144,8 +144,6 @@ import jakarta.ws.rs.core.MultivaluedMap;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* Resource class for the saml connect token service * Resource class for the saml connect token service
@ -1164,7 +1162,7 @@ public class SamlService extends AuthorizationEndpointBase {
return emptyArtifactResponseMessage(artifactResolveMessage, null); return emptyArtifactResponseMessage(artifactResolveMessage, null);
} }
UserSessionModel userSessionModel = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, sessionMapping.get(SamlProtocol.USER_SESSION_ID))); UserSessionModel userSessionModel = session.sessions().getUserSession(realm, sessionMapping.get(SamlProtocol.USER_SESSION_ID));
if (userSessionModel == null) { if (userSessionModel == null) {
logger.errorf("UserSession with id: %s, that corresponds to artifact: %s does not exist.", sessionMapping.get(SamlProtocol.USER_SESSION_ID), artifact); logger.errorf("UserSession with id: %s, that corresponds to artifact: %s does not exist.", sessionMapping.get(SamlProtocol.USER_SESSION_ID), artifact);
return emptyArtifactResponseMessage(artifactResolveMessage, null); return emptyArtifactResponseMessage(artifactResolveMessage, null);

View file

@ -115,7 +115,6 @@ import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser
import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID; import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow; import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow;
import static org.keycloak.services.util.CookieHelper.getCookie; import static org.keycloak.services.util.CookieHelper.getCookie;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* Stateless object that manages authentication * Stateless object that manages authentication
@ -229,7 +228,7 @@ public class AuthenticationManager {
verifier.verifierContext(signatureVerifier); verifier.verifierContext(signatureVerifier);
AccessToken token = verifier.verify().getToken(); AccessToken token = verifier.verify().getToken();
UserSessionModel cookieSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, token.getSessionState())); UserSessionModel cookieSession = session.sessions().getUserSession(realm, token.getSessionState());
if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return true; if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return true;
expireIdentityCookie(realm, uriInfo, session); expireIdentityCookie(realm, uriInfo, session);
return true; return true;
@ -319,9 +318,9 @@ public class AuthenticationManager {
// Check if "online" session still exists and remove it too // Check if "online" session still exists and remove it too
String onlineUserSessionId = userSession.getNote(CORRESPONDING_SESSION_ID); String onlineUserSessionId = userSession.getNote(CORRESPONDING_SESSION_ID);
UserSessionModel onlineUserSession = lockUserSessionsForModification(session, () -> (onlineUserSessionId != null) ? UserSessionModel onlineUserSession = onlineUserSessionId != null ?
session.sessions().getUserSession(realm, onlineUserSessionId) : session.sessions().getUserSession(realm, onlineUserSessionId) :
session.sessions().getUserSession(realm, userSession.getId())); session.sessions().getUserSession(realm, userSession.getId());
if (onlineUserSession != null) { if (onlineUserSession != null) {
session.sessions().removeUserSession(realm, onlineUserSession); session.sessions().removeUserSession(realm, onlineUserSession);
@ -950,7 +949,7 @@ public class AuthenticationManager {
if (split.length >= 3) { if (split.length >= 3) {
String oldSessionId = split[2]; String oldSessionId = split[2];
if (!oldSessionId.equals(userSession.getId())) { if (!oldSessionId.equals(userSession.getId())) {
UserSessionModel oldSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, oldSessionId)); UserSessionModel oldSession = session.sessions().getUserSession(realm, oldSessionId);
if (oldSession != null) { if (oldSession != null) {
logger.debugv("Removing old user session: session: {0}", oldSessionId); logger.debugv("Removing old user session: session: {0}", oldSessionId);
session.sessions().removeUserSession(realm, oldSession); session.sessions().removeUserSession(realm, oldSession);

View file

@ -28,23 +28,18 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.SessionExpiration; import org.keycloak.models.utils.SessionExpiration;
import org.keycloak.protocol.RestartLoginCookie; import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CookieHelper; import org.keycloak.services.util.CookieHelper;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.CommonClientSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.sessions.StickySessionEncoderProvider; import org.keycloak.sessions.StickySessionEncoderProvider;
import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.core.UriInfo;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.keycloak.authentication.AuthenticationProcessor.CURRENT_FLOW_PATH;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -108,7 +103,7 @@ public class AuthenticationSessionManager {
AuthSessionId authSessionId = decodeAuthSessionId(oldEncodedId); AuthSessionId authSessionId = decodeAuthSessionId(oldEncodedId);
String sessionId = authSessionId.getDecodedId(); String sessionId = authSessionId.getDecodedId();
UserSessionModel userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, sessionId)); UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
if (userSession != null) { if (userSession != null) {
reencodeAuthSessionCookie(oldEncodedId, authSessionId, realm); reencodeAuthSessionCookie(oldEncodedId, authSessionId, realm);
@ -284,7 +279,7 @@ public class AuthenticationSessionManager {
// Check to see if we already have authenticationSession with same ID // Check to see if we already have authenticationSession with same ID
public UserSessionModel getUserSession(AuthenticationSessionModel authSession) { public UserSessionModel getUserSession(AuthenticationSessionModel authSession) {
return lockUserSessionsForModification(session, () -> session.sessions().getUserSession(authSession.getRealm(), authSession.getParentSession().getId())); return session.sessions().getUserSession(authSession.getRealm(), authSession.getParentSession().getId());
} }

View file

@ -28,7 +28,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import static org.keycloak.services.managers.AuthenticationManager.authenticateIdentityCookie; import static org.keycloak.services.managers.AuthenticationManager.authenticateIdentityCookie;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -72,7 +71,7 @@ public class UserSessionCrossDCManager {
// in case the auth session was removed, we fall back to the identity cookie // in case the auth session was removed, we fall back to the identity cookie
// we are here doing the user session lookup twice, however the second lookup is going to make sure the // we are here doing the user session lookup twice, however the second lookup is going to make sure the
// session exists in remote caches // session exists in remote caches
AuthenticationManager.AuthResult authResult = lockUserSessionsForModification(kcSession, () -> authenticateIdentityCookie(kcSession, realm, true)); AuthenticationManager.AuthResult authResult = authenticateIdentityCookie(kcSession, realm, true);
if (authResult != null && authResult.getSession() != null) { if (authResult != null && authResult.getSession() != null) {
sessionCookies = Collections.singletonList(authResult.getSession().getId()); sessionCookies = Collections.singletonList(authResult.getSession().getId());
@ -84,9 +83,9 @@ public class UserSessionCrossDCManager {
String sessionId = authSessionId.getDecodedId(); String sessionId = authSessionId.getDecodedId();
// This will remove userSession "locally" if it doesn't exist on remoteCache // This will remove userSession "locally" if it doesn't exist on remoteCache
lockUserSessionsForModification(kcSession, () -> kcSession.sessions().getUserSessionWithPredicate(realm, sessionId, false, (UserSessionModel userSession2) -> userSession2 == null)); kcSession.sessions().getUserSessionWithPredicate(realm, sessionId, false, (UserSessionModel userSession2) -> userSession2 == null);
UserSessionModel userSession = lockUserSessionsForModification(kcSession, () -> kcSession.sessions().getUserSession(realm, sessionId)); UserSessionModel userSession = kcSession.sessions().getUserSession(realm, sessionId);
if (userSession != null) { if (userSession != null) {
asm.reencodeAuthSessionCookie(oldEncodedId, authSessionId, realm); asm.reencodeAuthSessionCookie(oldEncodedId, authSessionId, realm);

View file

@ -43,8 +43,6 @@ import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* *
* @author hmlnarik * @author hmlnarik
@ -123,7 +121,7 @@ public class LoginActionsServiceChecks {
return; return;
} }
UserSessionModel userSession = lockUserSessionsForModification(context.getSession(), () -> context.getSession().sessions().getUserSession(context.getRealm(), authSessionId)); UserSessionModel userSession = context.getSession().sessions().getUserSession(context.getRealm(), authSessionId);
boolean hasNoRequiredActions = boolean hasNoRequiredActions =
(userSession == null || userSession.getUser().getRequiredActionsStream().count() == 0) (userSession == null || userSession.getUser().getRequiredActionsStream().count() == 0)
&& &&

View file

@ -18,7 +18,6 @@
package org.keycloak.services.resources; package org.keycloak.services.resources;
import static org.keycloak.services.managers.AuthenticationManager.authenticateIdentityCookie; import static org.keycloak.services.managers.AuthenticationManager.authenticateIdentityCookie;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
import java.net.URI; import java.net.URI;
@ -40,7 +39,6 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.AuthorizationEndpointBase; import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.RestartLoginCookie; import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.services.ErrorPage; import org.keycloak.services.ErrorPage;
@ -185,7 +183,7 @@ public class SessionCodeChecks {
// if restart from cookie was not found check if the user is already authenticated // if restart from cookie was not found check if the user is already authenticated
if (response.getStatus() != Response.Status.FOUND.getStatusCode()) { if (response.getStatus() != Response.Status.FOUND.getStatusCode()) {
AuthenticationManager.AuthResult authResult = lockUserSessionsForModification(session, () -> authenticateIdentityCookie(session, realm, false)); AuthenticationManager.AuthResult authResult = authenticateIdentityCookie(session, realm, false);
if (authResult != null && authResult.getSession() != null) { if (authResult != null && authResult.getSession() != null) {
LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession) LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession)

View file

@ -32,7 +32,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.common.util.Time;
import org.keycloak.device.DeviceActivityManager; import org.keycloak.device.DeviceActivityManager;
import org.keycloak.models.AccountRoles; import org.keycloak.models.AccountRoles;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
@ -46,8 +45,6 @@ import org.keycloak.representations.account.SessionRepresentation;
import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@ -147,7 +144,7 @@ public class SessionResource {
@NoCache @NoCache
public Response logout(@PathParam("id") String id) { public Response logout(@PathParam("id") String id) {
auth.require(AccountRoles.MANAGE_ACCOUNT); auth.require(AccountRoles.MANAGE_ACCOUNT);
UserSessionModel userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, id)); UserSessionModel userSession = session.sessions().getUserSession(realm, id);
if (userSession != null && userSession.getUser().equals(user)) { if (userSession != null && userSession.getUser().equals(user)) {
AuthenticationManager.backchannelLogout(session, userSession, true); AuthenticationManager.backchannelLogout(session, userSession, true);
} }

View file

@ -16,7 +16,6 @@
*/ */
package org.keycloak.services.resources.admin; package org.keycloak.services.resources.admin;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
import static org.keycloak.util.JsonSerialization.readValue; import static org.keycloak.util.JsonSerialization.readValue;
import java.io.InputStream; import java.io.InputStream;
@ -633,7 +632,7 @@ public class RealmAdminResource {
public void deleteSession(@PathParam("session") String sessionId) { public void deleteSession(@PathParam("session") String sessionId) {
auth.users().requireManage(); auth.users().requireManage();
UserSessionModel userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, sessionId)); UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
if (userSession == null) throw new NotFoundException("Sesssion not found"); if (userSession == null) throw new NotFoundException("Sesssion not found");
AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), connection, headers, true); AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), connection, headers, true);
adminEvent.operation(OperationType.DELETE).resource(ResourceType.USER_SESSION).resourcePath(session.getContext().getUri()).success(); adminEvent.operation(OperationType.DELETE).resource(ResourceType.USER_SESSION).resourcePath(session.getContext().getUri()).success();

View file

@ -125,7 +125,6 @@ import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_ID;
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_USERNAME; import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_USERNAME;
import static org.keycloak.services.resources.admin.UserProfileResource.createUserProfileMetadata; import static org.keycloak.services.resources.admin.UserProfileResource.createUserProfileMetadata;
import static org.keycloak.userprofile.UserProfileContext.USER_API; import static org.keycloak.userprofile.UserProfileContext.USER_API;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
* Base resource for managing users * Base resource for managing users
@ -359,7 +358,7 @@ public class UserResource {
String sessionState = auth.adminAuth().getToken().getSessionState(); String sessionState = auth.adminAuth().getToken().getSessionState();
if (authenticatedRealm.getId().equals(realm.getId()) && sessionState != null) { if (authenticatedRealm.getId().equals(realm.getId()) && sessionState != null) {
sameRealm = true; sameRealm = true;
UserSessionModel userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(authenticatedRealm, sessionState)); UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, sessionState);
AuthenticationManager.expireIdentityCookie(realm, session.getContext().getUri(), session); AuthenticationManager.expireIdentityCookie(realm, session.getContext().getUri(), session);
AuthenticationManager.expireRememberMeCookie(realm, session.getContext().getUri(), session); AuthenticationManager.expireRememberMeCookie(realm, session.getContext().getUri(), session);
AuthenticationManager.expireAuthSessionCookie(realm, session.getContext().getUri(), session); AuthenticationManager.expireAuthSessionCookie(realm, session.getContext().getUri(), session);

View file

@ -37,8 +37,6 @@ import java.util.stream.IntStream;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.aMapWithSize; import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
@RequireProvider(UserSessionProvider.class) @RequireProvider(UserSessionProvider.class)
public class UserSessionConcurrencyTest extends KeycloakModelTest { public class UserSessionConcurrencyTest extends KeycloakModelTest {
@ -87,7 +85,7 @@ public class UserSessionConcurrencyTest extends KeycloakModelTest {
RealmModel realm = session.realms().getRealm(realmId); RealmModel realm = session.realms().getRealm(realmId);
ClientModel client = realm.getClientByClientId("client" + (n % CLIENTS_COUNT)); ClientModel client = realm.getClientByClientId("client" + (n % CLIENTS_COUNT));
UserSessionModel uSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, uId)); UserSessionModel uSession = session.sessions().getUserSession(realm, uId);
AuthenticatedClientSessionModel cSession = uSession.getAuthenticatedClientSessionByClient(client.getId()); AuthenticatedClientSessionModel cSession = uSession.getAuthenticatedClientSessionByClient(client.getId());
if (cSession == null) { if (cSession == null) {
wasWriting.set(true); wasWriting.set(true);