Session cross-reference / transaction mismatch
Closes https://github.com/keycloak/keycloak/issues/20855
This commit is contained in:
parent
4b3733de2e
commit
f3fcf1f8c5
2 changed files with 80 additions and 47 deletions
|
@ -76,6 +76,8 @@ import java.util.stream.Stream;
|
||||||
import org.keycloak.models.AccountRoles;
|
import org.keycloak.models.AccountRoles;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
@ -275,14 +277,82 @@ public final class KeycloakModelUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy all the objects in the context to the session.
|
* Sets up the context for the specified session with the RealmModel.
|
||||||
* @param session The session
|
*
|
||||||
* @param context The context
|
* @param origContext The original context to propagate
|
||||||
|
* @param targetSession The new target session to propagate the context to
|
||||||
*/
|
*/
|
||||||
public static void propagateContext(KeycloakSession session, KeycloakContext context) {
|
public static void cloneContextRealmClientToSession(final KeycloakContext origContext, final KeycloakSession targetSession) {
|
||||||
session.getContext().setRealm(context.getRealm());
|
cloneContextToSession(origContext, targetSession, false);
|
||||||
session.getContext().setClient(context.getClient());
|
}
|
||||||
session.getContext().setAuthenticationSession(context.getAuthenticationSession());
|
|
||||||
|
/**
|
||||||
|
* Sets up the context for the specified session with the RealmModel, clientModel and
|
||||||
|
* AuthenticatedSessionModel.
|
||||||
|
*
|
||||||
|
* @param origContext The original context to propagate
|
||||||
|
* @param targetSession The new target session to propagate the context to
|
||||||
|
*/
|
||||||
|
public static void cloneContextRealmClientSessionToSession(final KeycloakContext origContext, final KeycloakSession targetSession) {
|
||||||
|
cloneContextToSession(origContext, targetSession, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the context for the specified session.The original realm's context is used to
|
||||||
|
* determine what models need to be re-loaded using the current session. The models
|
||||||
|
* in the context are re-read from the new session via the IDs.
|
||||||
|
*/
|
||||||
|
private static void cloneContextToSession(final KeycloakContext origContext, final KeycloakSession targetSession,
|
||||||
|
final boolean includeAuthenticatedSessionModel) {
|
||||||
|
if (origContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup realm model if necessary.
|
||||||
|
RealmModel realmModel = null;
|
||||||
|
if (origContext.getRealm() != null) {
|
||||||
|
realmModel = targetSession.realms().getRealm(origContext.getRealm().getId());
|
||||||
|
if (realmModel != null) {
|
||||||
|
targetSession.getContext().setRealm(realmModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup client model if necessary.
|
||||||
|
ClientModel clientModel = null;
|
||||||
|
if (origContext.getClient() != null) {
|
||||||
|
if (origContext.getRealm() == null || !Objects.equals(origContext.getRealm().getId(), origContext.getClient().getRealm().getId())) {
|
||||||
|
realmModel = targetSession.realms().getRealm(origContext.getClient().getRealm().getId());
|
||||||
|
}
|
||||||
|
if (realmModel != null) {
|
||||||
|
clientModel = targetSession.clients().getClientById(realmModel, origContext.getClient().getId());
|
||||||
|
if (clientModel != null) {
|
||||||
|
targetSession.getContext().setClient(clientModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup auth session model if necessary.
|
||||||
|
if (includeAuthenticatedSessionModel && origContext.getAuthenticationSession() != null) {
|
||||||
|
if (origContext.getClient() == null || !Objects.equals(origContext.getClient().getId(), origContext.getAuthenticationSession().getClient().getId())) {
|
||||||
|
realmModel = (origContext.getRealm() == null || !Objects.equals(origContext.getRealm().getId(), origContext.getAuthenticationSession().getRealm().getId()))
|
||||||
|
? targetSession.realms().getRealm(origContext.getAuthenticationSession().getRealm().getId())
|
||||||
|
: targetSession.getContext().getRealm();
|
||||||
|
clientModel = (realmModel != null)
|
||||||
|
? targetSession.clients().getClientById(realmModel, origContext.getAuthenticationSession().getClient().getId())
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
if (clientModel != null) {
|
||||||
|
RootAuthenticationSessionModel rootAuthSession = targetSession.authenticationSessions().getRootAuthenticationSession(
|
||||||
|
realmModel, origContext.getAuthenticationSession().getParentSession().getId());
|
||||||
|
if (rootAuthSession != null) {
|
||||||
|
AuthenticationSessionModel authSessionModel = rootAuthSession.getAuthenticationSession(clientModel,
|
||||||
|
origContext.getAuthenticationSession().getTabId());
|
||||||
|
if (authSessionModel != null) {
|
||||||
|
targetSession.getContext().setAuthenticationSession(authSessionModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -307,11 +377,9 @@ public final class KeycloakModelUtils {
|
||||||
public static <V> V runJobInTransactionWithResult(KeycloakSessionFactory factory, KeycloakContext context, final KeycloakSessionTaskWithResult<V> callable) {
|
public static <V> V runJobInTransactionWithResult(KeycloakSessionFactory factory, KeycloakContext context, final KeycloakSessionTaskWithResult<V> callable) {
|
||||||
V result;
|
V result;
|
||||||
try (KeycloakSession session = factory.create()) {
|
try (KeycloakSession session = factory.create()) {
|
||||||
if (context != null) {
|
|
||||||
propagateContext(session, context);
|
|
||||||
}
|
|
||||||
session.getTransactionManager().begin();
|
session.getTransactionManager().begin();
|
||||||
try {
|
try {
|
||||||
|
cloneContextRealmClientToSession(context, session);
|
||||||
result = callable.run(session);
|
result = callable.run(session);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
session.getTransactionManager().setRollbackOnly();
|
session.getTransactionManager().setRollbackOnly();
|
||||||
|
|
|
@ -19,12 +19,9 @@ package org.keycloak.common.util;
|
||||||
import jakarta.ws.rs.WebApplicationException;
|
import jakarta.ws.rs.WebApplicationException;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.KeycloakContext;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionTaskWithResult;
|
import org.keycloak.models.KeycloakSessionTaskWithResult;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link KeycloakSessionTaskWithResult} that is aimed to be used by endpoints that want to produce a {@link Response} in
|
* A {@link KeycloakSessionTaskWithResult} that is aimed to be used by endpoints that want to produce a {@link Response} in
|
||||||
|
@ -60,7 +57,7 @@ public abstract class ResponseSessionTask implements KeycloakSessionTaskWithResu
|
||||||
KeycloakSession originalContextSession = Resteasy.getContextData(KeycloakSession.class);
|
KeycloakSession originalContextSession = Resteasy.getContextData(KeycloakSession.class);
|
||||||
try {
|
try {
|
||||||
// set up the current session context based on the original session context.
|
// set up the current session context based on the original session context.
|
||||||
this.setupSessionContext(session);
|
KeycloakModelUtils.cloneContextRealmClientSessionToSession(originalSession.getContext(), session);
|
||||||
// push the current session into the resteasy context.
|
// push the current session into the resteasy context.
|
||||||
Resteasy.pushContext(KeycloakSession.class, session);
|
Resteasy.pushContext(KeycloakSession.class, session);
|
||||||
// run the actual task.
|
// run the actual task.
|
||||||
|
@ -83,38 +80,6 @@ public abstract class ResponseSessionTask implements KeycloakSessionTaskWithResu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the context for the specified session. The original realm's context is used to determine what models
|
|
||||||
* need to be re-loaded using the current session.
|
|
||||||
*
|
|
||||||
* @param session the session whose context is to be prepared.
|
|
||||||
*/
|
|
||||||
private void setupSessionContext(final KeycloakSession session) {
|
|
||||||
if (this.originalSession == null) return;
|
|
||||||
KeycloakContext context = this.originalSession.getContext();
|
|
||||||
// setup realm model if necessary.
|
|
||||||
RealmModel realmModel = null;
|
|
||||||
if (context.getRealm() != null) {
|
|
||||||
realmModel = session.realms().getRealm(context.getRealm().getId());
|
|
||||||
session.getContext().setRealm(realmModel);
|
|
||||||
}
|
|
||||||
// setup client model if necessary.
|
|
||||||
ClientModel clientModel = null;
|
|
||||||
if (context.getClient() != null) {
|
|
||||||
clientModel = session.clients().getClientById(realmModel, context.getClient().getId());
|
|
||||||
session.getContext().setClient(clientModel);
|
|
||||||
}
|
|
||||||
// setup auth session model if necessary.
|
|
||||||
if (context.getAuthenticationSession() != null) {
|
|
||||||
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realmModel,
|
|
||||||
context.getAuthenticationSession().getParentSession().getId());
|
|
||||||
if (rootAuthSession != null) {
|
|
||||||
session.getContext().setAuthenticationSession(rootAuthSession.getAuthenticationSession(clientModel,
|
|
||||||
context.getAuthenticationSession().getTabId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the response that is to be returned.
|
* Builds the response that is to be returned.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in a new issue