Fix ConcurrentLoginTest.concurrentCodeReuseShouldFail on CockroachDB
- processGrantRequest in TokenManager is now executed in a separate retriable transaction. Closes #13210
This commit is contained in:
parent
eb17157e44
commit
667f1f989f
1 changed files with 30 additions and 1 deletions
|
@ -31,6 +31,7 @@ import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.constants.ServiceAccountConstants;
|
import org.keycloak.common.constants.ServiceAccountConstants;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
import org.keycloak.common.util.Resteasy;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
@ -48,6 +49,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.TokenExchangeContext;
|
import org.keycloak.protocol.oidc.TokenExchangeContext;
|
||||||
|
@ -105,6 +107,7 @@ import javax.ws.rs.InternalServerErrorException;
|
||||||
import javax.ws.rs.OPTIONS;
|
import javax.ws.rs.OPTIONS;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.WebApplicationException;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
@ -172,6 +175,32 @@ public class TokenEndpoint {
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
@POST
|
@POST
|
||||||
public Response processGrantRequest() {
|
public Response processGrantRequest() {
|
||||||
|
// grant request needs to be run in a retriable transaction as concurrent execution of this action can lead to
|
||||||
|
// exceptions on DBs with SERIALIZABLE isolation level.
|
||||||
|
Object result = KeycloakModelUtils.runJobInRetriableTransaction(this.session.getKeycloakSessionFactory(), kcSession -> {
|
||||||
|
try {
|
||||||
|
RealmModel realmModel = kcSession.realms().getRealm(realm.getId());
|
||||||
|
kcSession.getContext().setRealm(realmModel);
|
||||||
|
// create another instance of the endpoint that will be run within the new session.
|
||||||
|
Resteasy.pushContext(KeycloakSession.class, kcSession);
|
||||||
|
TokenEndpoint other = new TokenEndpoint(new TokenManager(), realmModel, new EventBuilder(realmModel, kcSession, clientConnection));
|
||||||
|
ResteasyProviderFactory.getInstance().injectProperties(other);
|
||||||
|
return other.processGrantRequestInternal();
|
||||||
|
} catch (WebApplicationException we) {
|
||||||
|
// WebApplicationException needs to be returned and treated (rethrown) by the calling code because the new transaction
|
||||||
|
// still needs to be committed when this exception is thrown. It captures final business states that won't change when
|
||||||
|
// being retried, like an invalid code.
|
||||||
|
return we;
|
||||||
|
}
|
||||||
|
}, 10, 100);
|
||||||
|
if (WebApplicationException.class.isInstance(result)) {
|
||||||
|
throw (WebApplicationException) result;
|
||||||
|
} else {
|
||||||
|
return (Response) result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response processGrantRequestInternal() {
|
||||||
cors = Cors.add(request).auth().allowedMethods("POST").auth().exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS);
|
cors = Cors.add(request).auth().allowedMethods("POST").auth().exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS);
|
||||||
|
|
||||||
MultivaluedMap<String, String> formParameters = request.getDecodedFormParameters();
|
MultivaluedMap<String, String> formParameters = request.getDecodedFormParameters();
|
||||||
|
@ -179,7 +208,7 @@ public class TokenEndpoint {
|
||||||
if (formParameters == null) {
|
if (formParameters == null) {
|
||||||
formParameters = new MultivaluedHashMap<>();
|
formParameters = new MultivaluedHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
formParams = formParameters;
|
formParams = formParameters;
|
||||||
grantType = formParams.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM);
|
grantType = formParams.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue