JPA map storage: User / client session no-downtime store (#12241)

Co-authored-by: Alexander Schwartz <alexander.schwartz@gmx.net>

Closes #9666
This commit is contained in:
Vlasta Ramik 2022-07-14 17:07:02 +02:00 committed by GitHub
parent 84ac2a2ba4
commit ec853a6b83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 1519 additions and 144 deletions

View file

@ -16,8 +16,6 @@
*/
package org.keycloak.models.map.storage.jpa;
import javax.persistence.criteria.CriteriaBuilder;
public interface Constants {
public static final Integer CURRENT_SCHEMA_VERSION_ADMIN_EVENT = 1;
public static final Integer CURRENT_SCHEMA_VERSION_AUTH_EVENT = 1;
@ -28,6 +26,7 @@ public interface Constants {
public static final Integer CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE_SERVER = 1;
public static final Integer CURRENT_SCHEMA_VERSION_AUTHZ_SCOPE = 1;
public static final Integer CURRENT_SCHEMA_VERSION_CLIENT = 1;
public static final Integer CURRENT_SCHEMA_VERSION_CLIENT_SESSION = 1;
public static final Integer CURRENT_SCHEMA_VERSION_CLIENT_SCOPE = 1;
public static final Integer CURRENT_SCHEMA_VERSION_COMPONENT = 1;
public static final Integer CURRENT_SCHEMA_VERSION_GROUP = 1;
@ -39,5 +38,5 @@ public interface Constants {
public static final Integer CURRENT_SCHEMA_VERSION_USER = 1;
public static final Integer CURRENT_SCHEMA_VERSION_USER_CONSENT = 1;
public static final Integer CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY = 1;
public static final Integer CURRENT_SCHEMA_VERSION_USER_SESSION = 1;
}

View file

@ -22,6 +22,8 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
@ -32,6 +34,7 @@ import javax.persistence.criteria.Selection;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.ExpirableEntity;
import org.keycloak.models.map.common.StringKeyConverter;
@ -40,7 +43,7 @@ import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.utils.LockObjectsForModification;
import static org.keycloak.models.map.common.ExpirationUtils.isExpired;
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
@ -50,12 +53,14 @@ import static org.keycloak.utils.StreamsUtil.closing;
public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E extends AbstractEntity, M> implements MapKeycloakTransaction<E, M> {
private static final Logger logger = Logger.getLogger(JpaMapKeycloakTransaction.class);
private final KeycloakSession session;
private final Class<RE> entityType;
private final Class<M> modelType;
private final boolean isExpirableEntity;
protected EntityManager em;
public JpaMapKeycloakTransaction(Class<RE> entityType, Class<M> modelType, EntityManager em) {
public JpaMapKeycloakTransaction(KeycloakSession session, Class<RE> entityType, Class<M> modelType, EntityManager em) {
this.session = session;
this.em = em;
this.entityType = entityType;
this.modelType = modelType;
@ -67,6 +72,15 @@ public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E exte
protected abstract JpaModelCriteriaBuilder createJpaModelCriteriaBuilder();
protected abstract E mapToEntityDelegate(RE original);
/**
* Indicates of pessimistic locking should be allowed for this entity. This should be enabled only for those entities
* where there is no expected contention from different callers. For UserSessions and ClientSessions this should be possible.
* The locking on Clients would be problematic as such a lock affects multiple callers.
*/
protected boolean lockingSupportedForEntity() {
return false;
}
private final HashMap<String, E> cacheWithinSession = new HashMap<>();
/**
@ -101,7 +115,11 @@ public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E exte
if (key == null) return null;
UUID uuid = StringKeyConverter.UUIDKey.INSTANCE.fromStringSafe(key);
if (uuid == null) return null;
E e = mapToEntityDelegateUnique(em.find(entityType, uuid));
E e = mapToEntityDelegateUnique(
lockingSupportedForEntity() && LockObjectsForModification.isEnabled(session) ?
em.find(entityType, uuid, LockModeType.PESSIMISTIC_WRITE) :
em.find(entityType, uuid)
);
return e != null && isExpirableEntity && isExpired((ExpirableEntity) e, true) ? null : e;
}
@ -142,7 +160,11 @@ public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E exte
}
if (predicateFunc != null) query.where(predicateFunc.apply(cb, query::subquery, root));
return closing(paginateQuery(em.createQuery(query), queryParameters.getOffset(), queryParameters.getLimit()).getResultStream())
TypedQuery<RE> emQuery = em.createQuery(query);
if (lockingSupportedForEntity() && LockObjectsForModification.isEnabled(session)) {
emQuery = emQuery.setLockMode(LockModeType.PESSIMISTIC_WRITE);
}
return closing(paginateQuery(emQuery, queryParameters.getOffset(), queryParameters.getLimit()).getResultStream())
.map(this::mapToEntityDelegateUnique);
}

View file

@ -59,7 +59,7 @@ public class JpaMapStorageProvider implements MapStorageProvider {
return new MapStorage<V, M>() {
@Override
public MapKeycloakTransaction<V, M> createTransaction(KeycloakSession session) {
return factory.createTransaction(modelType, em);
return factory.createTransaction(session, modelType, em);
}
};
}

View file

@ -29,7 +29,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.BiFunction;
import javax.naming.InitialContext;
import javax.naming.NamingException;
@ -59,6 +59,7 @@ import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
@ -68,6 +69,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.dblock.DBLockProvider;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntityImpl;
@ -131,6 +133,9 @@ import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
import org.keycloak.models.map.storage.jpa.singleUseObject.JpaSingleUseObjectMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity;
import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider;
import org.keycloak.models.map.storage.jpa.userSession.JpaUserSessionMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionEntity;
import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionEntity;
import org.keycloak.models.map.storage.jpa.user.JpaUserMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentEntity;
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity;
@ -203,9 +208,12 @@ public class JpaMapStorageProviderFactory implements
.constructor(JpaUserConsentEntity.class, JpaUserConsentEntity::new)
.constructor(JpaUserFederatedIdentityEntity.class, JpaUserFederatedIdentityEntity::new)
.constructor(MapUserCredentialEntity.class, MapUserCredentialEntityImpl::new)
//user/client session
.constructor(JpaClientSessionEntity.class, JpaClientSessionEntity::new)
.constructor(JpaUserSessionEntity.class, JpaUserSessionEntity::new)
.build();
private static final Map<Class<?>, Function<EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>();
private static final Map<Class<?>, BiFunction<KeycloakSession, EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>();
static {
//auth-sessions
MODEL_TO_TX.put(RootAuthenticationSessionModel.class, JpaRootAuthenticationSessionMapKeycloakTransaction::new);
@ -234,6 +242,8 @@ public class JpaMapStorageProviderFactory implements
MODEL_TO_TX.put(UserLoginFailureModel.class, JpaUserLoginFailureMapKeycloakTransaction::new);
//users
MODEL_TO_TX.put(UserModel.class, JpaUserMapKeycloakTransaction::new);
//sessions
MODEL_TO_TX.put(UserSessionModel.class, JpaUserSessionMapKeycloakTransaction::new);
}
public JpaMapStorageProviderFactory() {
@ -242,8 +252,8 @@ public class JpaMapStorageProviderFactory implements
this.sessionTxKey = SESSION_TX_PREFIX + index;
}
public MapKeycloakTransaction createTransaction(Class<?> modelType, EntityManager em) {
return MODEL_TO_TX.get(modelType).apply(em);
public MapKeycloakTransaction createTransaction(KeycloakSession session, Class<?> modelType, EntityManager em) {
return MODEL_TO_TX.get(modelType).apply(session, em);
}
@Override
@ -358,17 +368,6 @@ public class JpaMapStorageProviderFactory implements
)
);
Integer isolation = config.getInt("isolation");
if (isolation != null) {
if (isolation < Connection.TRANSACTION_REPEATABLE_READ) {
logger.warn("Concurrent requests may not be reliable with transaction level lower than TRANSACTION_REPEATABLE_READ.");
}
properties.put(AvailableSettings.ISOLATION, String.valueOf(isolation));
} else {
// default value is TRANSACTION_READ_COMMITTED
logger.warn("Concurrent requests may not be reliable with transaction level lower than TRANSACTION_REPEATABLE_READ.");
}
logger.trace("Creating EntityManagerFactory");
this.emf = Persistence.createEntityManagerFactory(unitName, properties);
logger.trace("EntityManagerFactory created");

View file

@ -35,7 +35,6 @@ import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
/**

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.models.map.storage.jpa.role;
package org.keycloak.models.map.storage.jpa;
import org.keycloak.models.map.storage.jpa.JpaSubqueryProvider;

View file

@ -20,7 +20,6 @@ import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;
import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.PersistenceExceptionConverter;
import org.keycloak.models.KeycloakTransaction;
/**

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.connections.jpa;
package org.keycloak.models.map.storage.jpa;
import org.hibernate.exception.ConstraintViolationException;
import org.keycloak.models.Constants;

View file

@ -20,6 +20,8 @@ import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntityDelegate;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION;
@ -33,8 +35,8 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
public class JpaRootAuthenticationSessionMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaRootAuthenticationSessionEntity, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> {
@SuppressWarnings("unchecked")
public JpaRootAuthenticationSessionMapKeycloakTransaction(EntityManager em) {
super(JpaRootAuthenticationSessionEntity.class, RootAuthenticationSessionModel.class, em);
public JpaRootAuthenticationSessionMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaRootAuthenticationSessionEntity.class, RootAuthenticationSessionModel.class, em);
}
@Override

View file

@ -18,8 +18,8 @@ package org.keycloak.models.map.storage.jpa.authSession;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel.SearchableFields;
import org.keycloak.storage.SearchableModelField;

View file

@ -36,19 +36,12 @@ import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.UpdatableEntity;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION;
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.sessions.CommonClientSessionModel;
/**
* Entity represents individual authentication session.
*
* It implements {@link JpaRootEntity} as it contains json field.
*
* Authentication session is modified from multiple transactions within one request,
* via {@code KeycloakModelUtils.runJobInTransaction}. Therefore it doesn't
* implement {@code JpaRootVersionedEntity} nor {@code JpaChildEntity}.
*/
@Entity
@Table(name = "kc_auth_session")

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntityDelegate;
import org.keycloak.models.map.storage.jpa.Constants;
@ -33,8 +34,8 @@ import org.keycloak.models.map.storage.jpa.authorization.permission.entity.JpaPe
public class JpaPermissionMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaPermissionEntity, MapPermissionTicketEntity, PermissionTicket> {
@SuppressWarnings("unchecked")
public JpaPermissionMapKeycloakTransaction(EntityManager em) {
super(JpaPermissionEntity.class, PermissionTicket.class, em);
public JpaPermissionMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaPermissionEntity.class, PermissionTicket.class, em);
}
@Override

View file

@ -27,9 +27,9 @@ import org.keycloak.models.map.common.StringKeyConverter.UUIDKey;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.authorization.permission.entity.JpaPermissionEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
public class JpaPermissionModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaPermissionEntity, PermissionTicket, JpaPermissionModelCriteriaBuilder> {

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.authorization.model.Policy;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntityDelegate;
import org.keycloak.models.map.storage.jpa.Constants;
@ -33,8 +34,8 @@ import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicy
public class JpaPolicyMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaPolicyEntity, MapPolicyEntity, Policy> {
@SuppressWarnings("unchecked")
public JpaPolicyMapKeycloakTransaction(EntityManager em) {
super(JpaPolicyEntity.class, Policy.class, em);
public JpaPolicyMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaPolicyEntity.class, Policy.class, em);
}
@Override

View file

@ -30,10 +30,10 @@ import org.keycloak.models.map.common.StringKeyConverter.UUIDKey;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyConfigEntity;
import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
public class JpaPolicyModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaPolicyEntity, Policy, JpaPolicyModelCriteriaBuilder> {

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.authorization.model.Resource;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntityDelegate;
import org.keycloak.models.map.storage.jpa.Constants;
@ -33,8 +34,8 @@ import org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaReso
public class JpaResourceMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaResourceEntity, MapResourceEntity, Resource> {
@SuppressWarnings("unchecked")
public JpaResourceMapKeycloakTransaction(EntityManager em) {
super(JpaResourceEntity.class, Resource.class, em);
public JpaResourceMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaResourceEntity.class, Resource.class, em);
}
@Override

View file

@ -29,8 +29,8 @@ import org.keycloak.models.map.common.StringKeyConverter.UUIDKey;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaResourceEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
public class JpaResourceModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaResourceEntity, Resource, JpaResourceModelCriteriaBuilder> {

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntityDelegate;
import org.keycloak.models.map.storage.jpa.Constants;
@ -33,8 +34,8 @@ import org.keycloak.models.map.storage.jpa.authorization.resourceServer.entity.J
public class JpaResourceServerMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaResourceServerEntity, MapResourceServerEntity, ResourceServer> {
@SuppressWarnings("unchecked")
public JpaResourceServerMapKeycloakTransaction(EntityManager em) {
super(JpaResourceServerEntity.class, ResourceServer.class, em);
public JpaResourceServerMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaResourceServerEntity.class, ResourceServer.class, em);
}
@Override

View file

@ -20,8 +20,8 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.ResourceServer.SearchableFields;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.authorization.resourceServer.entity.JpaResourceServerEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
public class JpaResourceServerModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaResourceServerEntity, ResourceServer, JpaResourceServerModelCriteriaBuilder> {

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.authorization.model.Scope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
import org.keycloak.models.map.authorization.entity.MapScopeEntityDelegate;
import org.keycloak.models.map.storage.jpa.Constants;
@ -33,8 +34,8 @@ import org.keycloak.models.map.storage.jpa.authorization.scope.entity.JpaScopeEn
public class JpaScopeMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaScopeEntity, MapScopeEntity, Scope> {
@SuppressWarnings("unchecked")
public JpaScopeMapKeycloakTransaction(EntityManager em) {
super(JpaScopeEntity.class, Scope.class, em);
public JpaScopeMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaScopeEntity.class, Scope.class, em);
}
@Override

View file

@ -27,8 +27,8 @@ import org.keycloak.models.map.common.StringKeyConverter.UUIDKey;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.authorization.scope.entity.JpaScopeEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
public class JpaScopeModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaScopeEntity, Scope, JpaScopeModelCriteriaBuilder> {

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.client.MapClientEntity;
import org.keycloak.models.map.client.MapClientEntityDelegate;
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
@ -33,8 +34,8 @@ import org.keycloak.models.map.storage.jpa.client.delegate.JpaClientDelegateProv
public class JpaClientMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaClientEntity, MapClientEntity, ClientModel> {
@SuppressWarnings("unchecked")
public JpaClientMapKeycloakTransaction(EntityManager em) {
super(JpaClientEntity.class, ClientModel.class, em);
public JpaClientMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaClientEntity.class, ClientModel.class, em);
}
@Override

View file

@ -23,10 +23,10 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientModel.SearchableFields;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientAttributeEntity;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientAttributeEntity;
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
import org.keycloak.storage.SearchableModelField;
public class JpaClientModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaClientEntity, ClientModel, JpaClientModelCriteriaBuilder> {

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
import org.keycloak.models.map.clientscope.MapClientScopeEntityDelegate;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SCOPE;
@ -33,8 +34,8 @@ import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEnti
public class JpaClientScopeMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaClientScopeEntity, MapClientScopeEntity, ClientScopeModel> {
@SuppressWarnings("unchecked")
public JpaClientScopeMapKeycloakTransaction(EntityManager em) {
super(JpaClientScopeEntity.class, ClientScopeModel.class, em);
public JpaClientScopeMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaClientScopeEntity.class, ClientScopeModel.class, em);
}
@Override

View file

@ -20,8 +20,8 @@ import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.ClientScopeModel.SearchableFields;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
public class JpaClientScopeModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaClientScopeEntity, ClientScopeModel, JpaClientScopeModelCriteriaBuilder> {

View file

@ -22,6 +22,7 @@ import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.events.MapAdminEventEntity;
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
@ -37,8 +38,8 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
*/
public class JpaAdminEventMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaAdminEventEntity, MapAdminEventEntity, AdminEvent> {
public JpaAdminEventMapKeycloakTransaction(final EntityManager em) {
super(JpaAdminEventEntity.class, AdminEvent.class, em);
public JpaAdminEventMapKeycloakTransaction(KeycloakSession session, final EntityManager em) {
super(session, JpaAdminEventEntity.class, AdminEvent.class, em);
}
@Override

View file

@ -26,8 +26,8 @@ import javax.persistence.criteria.CriteriaBuilder;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.event.admin.entity.JpaAdminEventEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
import org.keycloak.util.EnumWithStableIndex;

View file

@ -22,6 +22,7 @@ import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.events.Event;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.events.MapAuthEventEntity;
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
@ -37,8 +38,8 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
*/
public class JpaAuthEventMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaAuthEventEntity, MapAuthEventEntity, Event> {
public JpaAuthEventMapKeycloakTransaction(final EntityManager em) {
super(JpaAuthEventEntity.class, Event.class, em);
public JpaAuthEventMapKeycloakTransaction(KeycloakSession session, final EntityManager em) {
super(session, JpaAuthEventEntity.class, Event.class, em);
}
@Override

View file

@ -26,8 +26,8 @@ import javax.persistence.criteria.CriteriaBuilder;
import org.keycloak.events.Event;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.event.auth.entity.JpaAuthEventEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
import org.keycloak.util.EnumWithStableIndex;

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.group.MapGroupEntity;
import org.keycloak.models.map.group.MapGroupEntityDelegate;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP;
@ -33,8 +34,8 @@ import org.keycloak.models.map.storage.jpa.JpaRootEntity;
public class JpaGroupMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaGroupEntity, MapGroupEntity, GroupModel> {
@SuppressWarnings("unchecked")
public JpaGroupMapKeycloakTransaction(EntityManager em) {
super(JpaGroupEntity.class, GroupModel.class, em);
public JpaGroupMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaGroupEntity.class, GroupModel.class, em);
}
@Override

View file

@ -25,8 +25,8 @@ import org.keycloak.models.GroupModel;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
public class JpaGroupModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaGroupEntity, GroupModel, JpaGroupModelCriteriaBuilder> {

View file

@ -39,6 +39,7 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaAdminEve
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaAuthEventMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaAuthenticationSessionMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientSessionMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientScopeMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaComponentMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaGroupMigration;
@ -55,6 +56,7 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserCons
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserFederatedIdentityMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserLoginFailureMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserSessionMigration;
import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureMetadata;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentMetadata;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmMetadata;
@ -63,6 +65,8 @@ import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseOb
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentMetadata;
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserFederatedIdentityMetadata;
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserMetadata;
import org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionMetadata;
import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionMetadata;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ADMIN_EVENT;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTHZ_PERMISSION;
@ -74,6 +78,7 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SCOPE;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SESSION;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
@ -82,6 +87,7 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_CONSENT;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_SESSION;
public class JpaEntityMigration {
@ -101,8 +107,8 @@ public class JpaEntityMigration {
//client-scopes
MIGRATIONS.put(JpaClientScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SCOPE, tree, JpaClientScopeMigration.MIGRATORS));
//events
MIGRATIONS.put(JpaAdminEventMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ADMIN_EVENT, tree, JpaAdminEventMigration.MIGRATORS));
MIGRATIONS.put(JpaAuthEventMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_EVENT, tree, JpaAuthEventMigration.MIGRATORS));
MIGRATIONS.put(JpaAdminEventMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ADMIN_EVENT, tree, JpaAdminEventMigration.MIGRATORS));
MIGRATIONS.put(JpaAuthEventMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_EVENT, tree, JpaAuthEventMigration.MIGRATORS));
//groups
MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS));
//realms
@ -110,13 +116,16 @@ public class JpaEntityMigration {
MIGRATIONS.put(JpaRealmMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaRealmMigration.MIGRATORS));
//roles
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
//sessions
MIGRATIONS.put(JpaClientSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SESSION, tree, JpaClientSessionMigration.MIGRATORS));
MIGRATIONS.put(JpaUserSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_SESSION, tree, JpaUserSessionMigration.MIGRATORS));
//single-use-objects
MIGRATIONS.put(JpaSingleUseObjectMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT, tree, JpaSingleUseObjectMigration.MIGRATORS));
//user-login-failures
MIGRATIONS.put(JpaUserLoginFailureMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE,tree, JpaUserLoginFailureMigration.MIGRATORS));
MIGRATIONS.put(JpaUserLoginFailureMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE, tree, JpaUserLoginFailureMigration.MIGRATORS));
//users
MIGRATIONS.put(JpaUserMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER, tree, JpaUserMigration.MIGRATORS));
MIGRATIONS.put(JpaUserConsentMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_CONSENT, tree, JpaUserConsentMigration.MIGRATORS));
MIGRATIONS.put(JpaUserMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER, tree, JpaUserMigration.MIGRATORS));
MIGRATIONS.put(JpaUserConsentMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_CONSENT, tree, JpaUserConsentMigration.MIGRATORS));
MIGRATIONS.put(JpaUserFederatedIdentityMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY, tree, JpaUserFederatedIdentityMigration.MIGRATORS));
}

View file

@ -0,0 +1,30 @@
/*
* 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.models.map.storage.jpa.hibernate.jsonb.migration;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class JpaClientSessionMigration {
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
o -> o // no migration yet
);
}

View file

@ -0,0 +1,30 @@
/*
* 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.models.map.storage.jpa.hibernate.jsonb.migration;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class JpaUserSessionMigration {
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
o -> o // no migration yet
);
}

View file

@ -26,6 +26,7 @@ import org.hibernate.event.spi.PreInsertEventListener;
import org.hibernate.event.spi.PreUpdateEvent;
import org.hibernate.event.spi.PreUpdateEventListener;
import org.keycloak.models.map.storage.jpa.JpaChildEntity;
import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity;
import javax.persistence.LockModeType;
import java.util.Objects;
@ -50,6 +51,9 @@ public class JpaOptimisticLockingListener implements PreInsertEventListener, Pre
Objects.requireNonNull(root, "children must always return their parent, never null");
}
// do not lock if root doesn't implement implicit optimistic locking mechanism
if (! (root instanceof JpaRootVersionedEntity)) return;
// a session would not contain the entity if it has been deleted
// if the entity has been deleted JPA would throw an IllegalArgumentException with the message
// "entity not in the persistence context".

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntityDelegate;
@ -40,8 +41,8 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
public class JpaUserLoginFailureMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaUserLoginFailureEntity, MapUserLoginFailureEntity, UserLoginFailureModel> {
@SuppressWarnings("unchecked")
public JpaUserLoginFailureMapKeycloakTransaction(EntityManager em) {
super(JpaUserLoginFailureEntity.class, UserLoginFailureModel.class, em);
public JpaUserLoginFailureMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaUserLoginFailureEntity.class, UserLoginFailureModel.class, em);
}
@Override

View file

@ -19,8 +19,8 @@ package org.keycloak.models.map.storage.jpa.loginFailure;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;
/**

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.realm.MapRealmEntityDelegate;
@ -39,8 +40,8 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
*/
public class JpaRealmMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaRealmEntity, MapRealmEntity, RealmModel> {
public JpaRealmMapKeycloakTransaction(final EntityManager em) {
super(JpaRealmEntity.class, RealmModel.class, em);
public JpaRealmMapKeycloakTransaction(KeycloakSession session, final EntityManager em) {
super(session, JpaRealmEntity.class, RealmModel.class, em);
}
@Override

View file

@ -21,9 +21,9 @@ import javax.persistence.criteria.JoinType;
import org.keycloak.models.RealmModel;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;

View file

@ -20,6 +20,8 @@ import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RoleModel;
import org.keycloak.models.map.role.MapRoleEntity;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
@ -31,8 +33,9 @@ import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
public class JpaRoleMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaRoleEntity, MapRoleEntity, RoleModel> {
public JpaRoleMapKeycloakTransaction(EntityManager em) {
super(JpaRoleEntity.class, RoleModel.class, em);
@SuppressWarnings("unchecked")
public JpaRoleMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaRoleEntity.class, RoleModel.class, em);
}
@Override

View file

@ -27,6 +27,7 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.RoleModel.SearchableFields;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleCompositeEntity;
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
import org.keycloak.storage.SearchableModelField;

View file

@ -22,6 +22,7 @@ import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
@ -37,8 +38,8 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
*/
public class JpaSingleUseObjectMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaSingleUseObjectEntity, MapSingleUseObjectEntity, ActionTokenValueModel> {
public JpaSingleUseObjectMapKeycloakTransaction(final EntityManager em) {
super(JpaSingleUseObjectEntity.class, ActionTokenValueModel.class, em);
public JpaSingleUseObjectMapKeycloakTransaction(KeycloakSession session, final EntityManager em) {
super(session, JpaSingleUseObjectEntity.class, ActionTokenValueModel.class, em);
}
@Override

View file

@ -18,16 +18,11 @@ package org.keycloak.models.map.storage.jpa.singleUseObject;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity;
import org.keycloak.storage.SearchableModelField;

View file

@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel;
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
@ -39,8 +40,8 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
*/
public class JpaUserMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaUserEntity, MapUserEntity, UserModel> {
public JpaUserMapKeycloakTransaction(final EntityManager em) {
super(JpaUserEntity.class, UserModel.class, em);
public JpaUserMapKeycloakTransaction(KeycloakSession session,final EntityManager em) {
super(session, JpaUserEntity.class, UserModel.class, em);
}
@Override

View file

@ -26,8 +26,8 @@ import javax.persistence.criteria.JoinType;
import org.keycloak.models.UserModel;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserAttributeEntity;
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentEntity;
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity;

View file

@ -0,0 +1,65 @@
/*
* 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.models.map.storage.jpa.userSession;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_SESSION;
public class JpaUserSessionMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaUserSessionEntity, MapUserSessionEntity, UserSessionModel> {
public JpaUserSessionMapKeycloakTransaction(KeycloakSession session, final EntityManager em) {
super(session, JpaUserSessionEntity.class, UserSessionModel.class, em);
}
@Override
protected Selection<? extends JpaUserSessionEntity> selectCbConstruct(CriteriaBuilder cb, Root<JpaUserSessionEntity> root) {
return root;
}
@Override
protected void setEntityVersion(JpaRootEntity entity) {
entity.setEntityVersion(CURRENT_SCHEMA_VERSION_USER_SESSION);
}
@Override
protected JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() {
return new JpaUserSessionModelCriteriaBuilder();
}
@Override
protected MapUserSessionEntity mapToEntityDelegate(JpaUserSessionEntity original) {
return original;
}
@Override
protected boolean lockingSupportedForEntity() {
return true;
}
}

View file

@ -0,0 +1,100 @@
/*
* 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.models.map.storage.jpa.userSession;
import java.util.Objects;
import java.util.UUID;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionModel.SearchableFields;
import org.keycloak.models.map.common.StringKeyConverter.UUIDKey;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionEntity;
import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionNoteEntity;
import org.keycloak.storage.SearchableModelField;
public class JpaUserSessionModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaUserSessionEntity, UserSessionModel, JpaUserSessionModelCriteriaBuilder> {
public JpaUserSessionModelCriteriaBuilder() {
super(JpaUserSessionModelCriteriaBuilder::new);
}
private JpaUserSessionModelCriteriaBuilder(JpaPredicateFunction<JpaUserSessionEntity> predicateFunc) {
super(JpaUserSessionModelCriteriaBuilder::new, predicateFunc);
}
@Override
public JpaUserSessionModelCriteriaBuilder compare(SearchableModelField<? super UserSessionModel> modelField, Operator op, Object... value) {
switch(op) {
case EQ:
if (modelField == SearchableFields.ID) {
validateValue(value, modelField, op, String.class);
return new JpaUserSessionModelCriteriaBuilder((cb, query, root) -> {
UUID uuid = UUIDKey.INSTANCE.fromStringSafe(Objects.toString(value[0], null));
if (uuid == null) return cb.or();
return cb.equal(root.get(modelField.getName()), uuid);
});
} else if (modelField == SearchableFields.REALM_ID ||
modelField == SearchableFields.USER_ID ||
modelField == SearchableFields.BROKER_USER_ID ||
modelField == SearchableFields.BROKER_SESSION_ID) {
validateValue(value, modelField, op, String.class);
return new JpaUserSessionModelCriteriaBuilder((cb, query, root) ->
cb.equal(root.get(modelField.getName()), value[0])
);
} else if (modelField == SearchableFields.IS_OFFLINE) {
validateValue(value, modelField, op, Boolean.class);
return new JpaUserSessionModelCriteriaBuilder((cb, query, root) ->
cb.equal(root.get("offline"), value[0])
);
} else if (modelField == SearchableFields.CORRESPONDING_SESSION_ID) {
validateValue(value, modelField, op, String.class);
return new JpaUserSessionModelCriteriaBuilder((cb, query, root) -> {
Join<JpaUserSessionEntity, JpaUserSessionNoteEntity> join = root.join("notes", JoinType.LEFT);
return cb.and(
cb.equal(join.get("name"), UserSessionModel.CORRESPONDING_SESSION_ID),
cb.equal(join.get("value"), value[0])
);
});
} else if (modelField == SearchableFields.CLIENT_ID) {
validateValue(value, modelField, op, String.class);
return new JpaUserSessionModelCriteriaBuilder((cb, query, root) -> {
return cb.equal(root.join("clientSessions", JoinType.LEFT).get("clientId"), value[0]);
});
} else {
throw new CriterionNotSupportedException(modelField, op);
}
default:
throw new CriterionNotSupportedException(modelField, op);
}
}
}

View file

@ -0,0 +1,274 @@
/*
* 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.models.map.storage.jpa.userSession.entity;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Version;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.UuidValidator;
import org.keycloak.models.map.storage.jpa.Constants;
import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity.AbstractAuthenticatedClientSessionEntity;
/**
* Entity represents authenticated client session.
*/
@Entity
@Table(name = "kc_client_session")
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
public class JpaClientSessionEntity extends AbstractAuthenticatedClientSessionEntity implements JpaRootVersionedEntity {
@Id
@Column
private UUID id;
//used for implicit optimistic locking
@Version
@Column
private int version;
@Type(type = "jsonb")
@Column(columnDefinition = "jsonb")
private final JpaClientSessionMetadata metadata;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private Integer entityVersion;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private String clientId;
@OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true)
private final Set<JpaClientSessionNoteEntity> notes = new HashSet<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fk_root")
private JpaUserSessionEntity root;
/**
* No-argument constructor, used by hibernate to instantiate entities.
*/
public JpaClientSessionEntity() {
this.metadata = new JpaClientSessionMetadata();
}
public JpaClientSessionEntity(DeepCloner cloner) {
this.metadata = new JpaClientSessionMetadata(cloner);
}
public void setParent(JpaUserSessionEntity root) {
this.root = root;
}
@Override
public Integer getEntityVersion() {
return metadata.getEntityVersion();
}
@Override
public void setEntityVersion(Integer entityVersion) {
metadata.setEntityVersion(entityVersion);
}
@Override
public Integer getCurrentSchemaVersion() {
return Constants.CURRENT_SCHEMA_VERSION_CLIENT_SESSION;
}
@Override
public int getVersion() {
return version;
}
@Override
public String getId() {
return id == null ? null : id.toString();
}
@Override
public void setId(String id) {
String validatedId = UuidValidator.validateAndConvert(id);
this.id = UUID.fromString(validatedId);
}
@Override
public String getRealmId() {
return metadata.getRealmId();
}
@Override
public void setRealmId(String realmId) {
metadata.setRealmId(realmId);
}
@Override
public String getClientId() {
return metadata.getClientId();
}
@Override
public void setClientId(String clientId) {
metadata.setClientId(clientId);
}
@Override
public String getAuthMethod() {
return metadata.getAuthMethod();
}
@Override
public void setAuthMethod(String authMethod) {
metadata.setAuthMethod(authMethod);
}
@Override
public String getRedirectUri() {
return metadata.getRedirectUri();
}
@Override
public void setRedirectUri(String redirectUri) {
metadata.setRedirectUri(redirectUri);
}
@Override
public Long getTimestamp() {
return metadata.getTimestamp();
}
@Override
public void setTimestamp(Long timestamp) {
metadata.setTimestamp(timestamp);
}
@Override
public Long getExpiration() {
return metadata.getExpiration();
}
@Override
public void setExpiration(Long expiration) {
metadata.setExpiration(expiration);
}
@Override
public String getAction() {
return metadata.getAction();
}
@Override
public void setAction(String action) {
metadata.setAction(action);
}
@Override
public String getCurrentRefreshToken() {
return metadata.getCurrentRefreshToken();
}
@Override
public void setCurrentRefreshToken(String currentRefreshToken) {
metadata.setCurrentRefreshToken(currentRefreshToken);
}
@Override
public Integer getCurrentRefreshTokenUseCount() {
return metadata.getCurrentRefreshTokenUseCount();
}
@Override
public void setCurrentRefreshTokenUseCount(Integer currentRefreshTokenUseCount) {
metadata.setCurrentRefreshTokenUseCount(currentRefreshTokenUseCount);
}
@Override
public Boolean isOffline() {
return metadata.isOffline();
}
@Override
public void setOffline(Boolean offline) {
metadata.setOffline(offline);
}
@Override
public Map<String, String> getNotes() {
return notes.stream().collect(Collectors.toMap(JpaClientSessionNoteEntity::getName, JpaClientSessionNoteEntity::getValue));
}
@Override
public void setNotes(Map<String, String> notes) {
this.notes.clear();
if (notes == null) return;
for (Map.Entry<String, String> entry : notes.entrySet()) {
setNote(entry.getKey(), entry.getValue());
}
}
@Override
public String getNote(String name) {
return notes.stream()
.filter(obj -> Objects.equals(obj.getName(), name))
.findFirst()
.map(JpaClientSessionNoteEntity::getValue)
.orElse(null);
}
@Override
public Boolean removeNote(String name) {
return notes.removeIf(obj -> Objects.equals(obj.getName(), name));
}
@Override
public void setNote(String name, String value) {
removeNote(name);
if (name == null || value == null || value.trim().isEmpty()) return;
notes.add(new JpaClientSessionNoteEntity(this, name, value));
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof JpaClientSessionEntity)) return false;
return Objects.equals(getId(), ((JpaClientSessionEntity) obj).getId());
}
}

View file

@ -0,0 +1,43 @@
/*
* 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.models.map.storage.jpa.userSession.entity;
import java.io.Serializable;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntityImpl;
public class JpaClientSessionMetadata extends MapAuthenticatedClientSessionEntityImpl implements Serializable {
public JpaClientSessionMetadata(DeepCloner cloner) {
super(cloner);
}
public JpaClientSessionMetadata() {
super();
}
private Integer entityVersion;
public Integer getEntityVersion() {
return entityVersion;
}
public void setEntityVersion(Integer entityVersion) {
this.entityVersion = entityVersion;
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.models.map.storage.jpa.userSession.entity;
import java.util.Objects;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.keycloak.models.map.storage.jpa.JpaAttributeEntity;
@Entity
@Table(name = "kc_client_session_note")
public class JpaClientSessionNoteEntity extends JpaAttributeEntity<JpaClientSessionEntity> {
public JpaClientSessionNoteEntity() {
}
public JpaClientSessionNoteEntity(JpaClientSessionEntity root, String name, String value) {
super(root, name, value);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof JpaClientSessionNoteEntity)) return false;
JpaClientSessionNoteEntity that = (JpaClientSessionNoteEntity) obj;
return Objects.equals(getParent(), that.getParent()) &&
Objects.equals(getName(), that.getName());
}
}

View file

@ -0,0 +1,380 @@
/*
* 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.models.map.storage.jpa.userSession.entity;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Version;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.UuidValidator;
import org.keycloak.models.map.storage.jpa.Constants;
import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity.AbstractUserSessionEntity;
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
/**
* There are some fields marked by {@code @Column(insertable = false, updatable = false)}.
* Those fields are automatically generated by database from json field,
* therefore marked as non-insertable and non-updatable to instruct hibernate.
*/
@Entity
@Table(name = "kc_user_session")
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
public class JpaUserSessionEntity extends AbstractUserSessionEntity implements JpaRootVersionedEntity {
@Id
@Column
private UUID id;
//used for implicit optimistic locking
@Version
@Column
private int version;
@Type(type = "jsonb")
@Column(columnDefinition = "jsonb")
private final JpaUserSessionMetadata metadata;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private Integer entityVersion;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private String realmId;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private String userId;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private String brokerSessionId;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private String brokerUserId;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private Boolean offline;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private Long lastSessionRefresh;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private Long expiration;
@OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true)
private final Set<JpaUserSessionNoteEntity> notes = new HashSet<>();
@OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true)
private final Set<JpaClientSessionEntity> clientSessions = new HashSet<>();
/**
* No-argument constructor, used by hibernate to instantiate entities.
*/
public JpaUserSessionEntity() {
this.metadata = new JpaUserSessionMetadata();
}
public JpaUserSessionEntity(DeepCloner cloner) {
this.metadata = new JpaUserSessionMetadata(cloner);
}
public boolean isMetadataInitialized() {
return metadata != null;
}
@Override
public Integer getEntityVersion() {
if (isMetadataInitialized()) return metadata.getEntityVersion();
return entityVersion;
}
@Override
public void setEntityVersion(Integer entityVersion) {
metadata.setEntityVersion(entityVersion);
}
@Override
public Integer getCurrentSchemaVersion() {
return Constants.CURRENT_SCHEMA_VERSION_USER_SESSION;
}
@Override
public int getVersion() {
return version;
}
@Override
public String getId() {
return id == null ? null : id.toString();
}
@Override
public void setId(String id) {
String validatedId = UuidValidator.validateAndConvert(id);
this.id = UUID.fromString(validatedId);
}
@Override
public String getRealmId() {
if (isMetadataInitialized()) return metadata.getRealmId();
return realmId;
}
@Override
public void setRealmId(String realmId) {
metadata.setRealmId(realmId);
}
@Override
public String getUserId() {
if (isMetadataInitialized()) return metadata.getUserId();
return userId;
}
@Override
public void setUserId(String userId) {
metadata.setUserId(userId);
}
@Override
public String getLoginUsername() {
return metadata.getLoginUsername();
}
@Override
public void setLoginUsername(String loginUsername) {
metadata.setLoginUsername(loginUsername);
}
@Override
public String getIpAddress() {
return metadata.getIpAddress();
}
@Override
public void setIpAddress(String ipAddress) {
metadata.setIpAddress(ipAddress);
}
@Override
public String getAuthMethod() {
return metadata.getAuthMethod();
}
@Override
public void setAuthMethod(String authMethod) {
metadata.setAuthMethod(authMethod);
}
@Override
public Boolean isOffline() {
if (isMetadataInitialized()) return metadata.isOffline();
return offline;
}
@Override
public void setOffline(Boolean offline) {
metadata.setOffline(offline);
}
@Override
public Boolean isRememberMe() {
return metadata.isRememberMe();
}
@Override
public void setRememberMe(Boolean rememberMe) {
metadata.setRememberMe(rememberMe);
}
@Override
public Long getTimestamp() {
return metadata.getTimestamp();
}
@Override
public void setTimestamp(Long timestamp) {
metadata.setTimestamp(timestamp);
}
@Override
public Long getLastSessionRefresh() {
if (isMetadataInitialized()) return metadata.getLastSessionRefresh();
return lastSessionRefresh;
}
@Override
public void setLastSessionRefresh(Long lastSessionRefresh) {
metadata.setLastSessionRefresh(lastSessionRefresh);
}
@Override
public Long getExpiration() {
if (isMetadataInitialized()) return metadata.getExpiration();
return expiration;
}
@Override
public void setExpiration(Long expiration) {
metadata.setExpiration(expiration);
}
@Override
public UserSessionModel.State getState() {
return metadata.getState();
}
@Override
public void setState(UserSessionModel.State state) {
metadata.setState(state);
}
@Override
public UserSessionModel.SessionPersistenceState getPersistenceState() {
return UserSessionModel.SessionPersistenceState.PERSISTENT;
}
@Override
public void setPersistenceState(UserSessionModel.SessionPersistenceState persistenceState) {
// no-op: each non-transient user session (stored in the db) has PERSISTENT state
}
@Override
public Map<String, String> getNotes() {
return Collections.unmodifiableMap(notes.stream().collect(Collectors.toMap(JpaUserSessionNoteEntity::getName, JpaUserSessionNoteEntity::getValue)));
}
@Override
public String getNote(String name) {
return notes.stream()
.filter(obj -> Objects.equals(obj.getName(), name))
.findFirst()
.map(JpaUserSessionNoteEntity::getValue)
.orElse(null);
}
@Override
public void setNotes(Map<String, String> notes) {
this.notes.clear();
if (notes == null) return;
for (Map.Entry<String, String> entry : notes.entrySet()) {
setNote(entry.getKey(), entry.getValue());
}
}
@Override
public Boolean removeNote(String name) {
return notes.removeIf(obj -> Objects.equals(obj.getName(), name));
}
@Override
public void setNote(String name, String value) {
removeNote(name);
if (name == null || value == null || value.trim().isEmpty()) return;
notes.add(new JpaUserSessionNoteEntity(this, name, value));
}
@Override
public String getBrokerSessionId() {
if (isMetadataInitialized()) return metadata.getBrokerSessionId();
return brokerSessionId;
}
@Override
public void setBrokerSessionId(String brokerSessionId) {
metadata.setBrokerSessionId(brokerSessionId);
}
@Override
public String getBrokerUserId() {
if (isMetadataInitialized()) return metadata.getBrokerUserId();
return brokerUserId;
}
@Override
public void setBrokerUserId(String brokerUserId) {
metadata.setBrokerUserId(brokerUserId);
}
@Override
public Set<MapAuthenticatedClientSessionEntity> getAuthenticatedClientSessions() {
return clientSessions.stream().map(MapAuthenticatedClientSessionEntity.class::cast).collect(Collectors.toSet());
}
@Override
public void addAuthenticatedClientSession(MapAuthenticatedClientSessionEntity clientSession) {
JpaClientSessionEntity jpaClientSession = JpaClientSessionEntity.class.cast(CLONER.from(clientSession));
jpaClientSession.setParent(this);
jpaClientSession.setEntityVersion(this.getEntityVersion());
clientSessions.add(jpaClientSession);
}
@Override
public Optional<MapAuthenticatedClientSessionEntity> getAuthenticatedClientSession(String clientUUID) {
return clientSessions.stream().filter(cs -> Objects.equals(cs.getClientId(), clientUUID)).findFirst().map(MapAuthenticatedClientSessionEntity.class::cast);
}
@Override
public Boolean removeAuthenticatedClientSession(String clientUUID) {
return clientSessions.removeIf(cs -> Objects.equals(cs.getClientId(), clientUUID));
}
@Override
public void clearAuthenticatedClientSessions() {
clientSessions.clear();
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof JpaUserSessionEntity)) return false;
return Objects.equals(getId(), ((JpaUserSessionEntity) obj).getId());
}
}

View file

@ -0,0 +1,43 @@
/*
* 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.models.map.storage.jpa.userSession.entity;
import java.io.Serializable;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.userSession.MapUserSessionEntityImpl;
public class JpaUserSessionMetadata extends MapUserSessionEntityImpl implements Serializable {
public JpaUserSessionMetadata(DeepCloner cloner) {
super(cloner);
}
public JpaUserSessionMetadata() {
super();
}
private Integer entityVersion;
public Integer getEntityVersion() {
return entityVersion;
}
public void setEntityVersion(Integer entityVersion) {
this.entityVersion = entityVersion;
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.models.map.storage.jpa.userSession.entity;
import java.util.Objects;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.keycloak.models.map.storage.jpa.JpaAttributeEntity;
@Entity
@Table(name = "kc_user_session_note")
public class JpaUserSessionNoteEntity extends JpaAttributeEntity<JpaUserSessionEntity> {
public JpaUserSessionNoteEntity() {
}
public JpaUserSessionNoteEntity(JpaUserSessionEntity root, String name, String value) {
super(root, name, value);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof JpaUserSessionNoteEntity)) return false;
JpaUserSessionNoteEntity that = (JpaUserSessionNoteEntity) obj;
return Objects.equals(getParent(), that.getParent()) &&
Objects.equals(getName(), that.getName());
}
}

View file

@ -29,5 +29,6 @@ limitations under the License.
<include file="META-INF/jpa-roles-changelog.xml"/>
<include file="META-INF/jpa-single-use-objects-changelog.xml"/>
<include file="META-INF/jpa-user-login-failures-changelog.xml"/>
<include file="META-INF/jpa-user-sessions-changelog.xml"/>
<include file="META-INF/jpa-users-changelog.xml"/>
</databaseChangeLog>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<include file="META-INF/user-sessions/jpa-user-sessions-changelog-1.xml"/>
</databaseChangeLog>

View file

@ -38,6 +38,11 @@
<class>org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectNoteEntity</class>
<!--user-login-failures-->
<class>org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity</class>
<!--user-sessions-->
<class>org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionEntity</class>
<class>org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionNoteEntity</class>
<class>org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionEntity</class>
<class>org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionNoteEntity</class>
<!--users-->
<class>org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity</class>
<class>org.keycloak.models.map.storage.jpa.user.entity.JpaUserAttributeEntity</class>

View file

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet author="keycloak" id="user-sessions-1">
<createTable tableName="kc_user_session">
<column name="id" type="UUID">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="version" type="INTEGER" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="metadata" type="json"/>
</createTable>
<ext:addGeneratedColumn tableName="kc_user_session">
<ext:column name="entityversion" type="INTEGER" jsonColumn="metadata" jsonProperty="entityVersion"/>
<ext:column name="offline" type="BOOLEAN" jsonColumn="metadata" jsonProperty="fOffline"/>
<ext:column name="realmid" type="KC_KEY" jsonColumn="metadata" jsonProperty="fRealmId"/>
<ext:column name="userid" type="KC_KEY" jsonColumn="metadata" jsonProperty="fUserId"/>
<ext:column name="brokersessionid" type="KC_KEY" jsonColumn="metadata" jsonProperty="fBrokerSessionId"/>
<ext:column name="brokeruserid" type="KC_KEY" jsonColumn="metadata" jsonProperty="fBrokerUserId"/>
<ext:column name="lastsessionrefresh" type="BIGINT" jsonColumn="metadata" jsonProperty="fLastSessionRefresh"/>
<ext:column name="expiration" type="BIGINT" jsonColumn="metadata" jsonProperty="fExpiration"/>
</ext:addGeneratedColumn>
<createIndex tableName="kc_user_session" indexName="user_session_entityVersion">
<column name="entityversion"/>
</createIndex>
<createIndex tableName="kc_user_session" indexName="user_session_realm_offline_user">
<column name="realmid"/>
<column name="offline"/>
<column name="userid"/>
</createIndex>
<createTable tableName="kc_user_session_note">
<column name="id" type="UUID">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="fk_root" type="UUID">
<constraints foreignKeyName="user_session_note_fk_root_fkey" references="kc_user_session(id)" deleteCascade="true"/>
</column>
<column name="name" type="VARCHAR(255)"/>
<column name="value" type="TEXT"/>
</createTable>
<createIndex tableName="kc_user_session_note" indexName="kc_user_session_note_fk_root_name_key">
<column name="fk_root"/>
<column name="name"/>
</createIndex>
</changeSet>
<changeSet author="keycloak" id="user-sessions-2" dbms="postgresql">
<!-- this is deferrable and initiallyDeferred as hibernate will first insert new entries and then delete the old by default -->
<!-- this will not work on cockroachdb as deferred indexes are not supported in version 22.1 yet, therefore, only run it on postgresql -->
<!-- see https://go.crdb.dev/issue-v/31632/v21.2 for the current status of the implementation -->
<addUniqueConstraint tableName="kc_user_session_note" columnNames="fk_root, name" deferrable="true" initiallyDeferred="true" />
</changeSet>
<changeSet author="keycloak" id="client-sessions-1">
<createTable tableName="kc_client_session">
<column name="id" type="UUID">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="version" type="INTEGER" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="fk_root" type="UUID">
<constraints foreignKeyName="client_session_fk_root_fkey" references="kc_user_session(id)" deleteCascade="true"/>
</column>
<column name="metadata" type="json"/>
</createTable>
<ext:addGeneratedColumn tableName="kc_client_session">
<ext:column name="entityversion" type="INTEGER" jsonColumn="metadata" jsonProperty="entityVersion"/>
<ext:column name="clientid" type="KC_KEY" jsonColumn="metadata" jsonProperty="fClientId"/>
</ext:addGeneratedColumn>
<createIndex tableName="kc_client_session" indexName="client_session_entityVersion">
<column name="entityversion"/>
</createIndex>
<createIndex tableName="kc_client_session" indexName="client_session_fk_root">
<column name="fk_root"/>
</createIndex>
<createTable tableName="kc_client_session_note">
<column name="id" type="UUID">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="fk_root" type="UUID">
<constraints foreignKeyName="client_session_fk_root_fkey" references="kc_client_session(id)" deleteCascade="true"/>
</column>
<column name="name" type="VARCHAR(255)"/>
<column name="value" type="TEXT"/>
</createTable>
<createIndex tableName="kc_client_session_note" indexName="kc_client_session_note_fk_root_name_key">
<column name="fk_root"/>
<column name="name"/>
</createIndex>
</changeSet>
<changeSet author="keycloak" id="client-sessions-2" dbms="postgresql">
<!-- this is deferrable and initiallyDeferred as hibernate will first insert new entries and then delete the old by default -->
<!-- this will not work on cockroachdb as deferred indexes are not supported in version 22.1 yet, therefore, only run it on postgresql -->
<!-- see https://go.crdb.dev/issue-v/31632/v21.2 for the current status of the implementation -->
<addUniqueConstraint tableName="kc_client_session_note" columnNames="fk_root, name" deferrable="true" initiallyDeferred="true" />
</changeSet>
</databaseChangeLog>

View file

@ -23,7 +23,6 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
@ -49,7 +48,6 @@ import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.role.MapRoleEntity;
import org.keycloak.models.map.user.MapUserEntity;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import java.util.HashMap;
@ -67,7 +65,6 @@ public class ModelEntityUtil {
private static final Map<Class<?>, String> MODEL_TO_NAME = new HashMap<>();
static {
MODEL_TO_NAME.put(ActionTokenValueModel.class, "single-use-objects");
MODEL_TO_NAME.put(AuthenticatedClientSessionModel.class, "client-sessions");
MODEL_TO_NAME.put(ClientScopeModel.class, "client-scopes");
MODEL_TO_NAME.put(ClientModel.class, "clients");
MODEL_TO_NAME.put(GroupModel.class, "groups");
@ -94,7 +91,6 @@ public class ModelEntityUtil {
private static final Map<Class<?>, Class<? extends AbstractEntity>> MODEL_TO_ENTITY_TYPE = new HashMap<>();
static {
MODEL_TO_ENTITY_TYPE.put(ActionTokenValueModel.class, MapSingleUseObjectEntity.class);
MODEL_TO_ENTITY_TYPE.put(AuthenticatedClientSessionModel.class, MapAuthenticatedClientSessionEntity.class);
MODEL_TO_ENTITY_TYPE.put(ClientScopeModel.class, MapClientScopeEntity.class);
MODEL_TO_ENTITY_TYPE.put(ClientModel.class, MapClientEntity.class);
MODEL_TO_ENTITY_TYPE.put(GroupModel.class, MapGroupEntity.class);

View file

@ -181,6 +181,8 @@ public class MapUserSessionProvider implements UserSessionProvider {
LOG.tracef("getUserSession(%s, %s)%s", realm, id, getShortStackTrace());
if (id == null) return null;
MapUserSessionEntity userSessionEntity = transientUserSessions.get(id);
if (userSessionEntity != null) {
return userEntityToAdapterFunc(realm).apply(userSessionEntity);
@ -565,6 +567,8 @@ public class MapUserSessionProvider implements UserSessionProvider {
}
private MapUserSessionEntity getUserSessionById(String id) {
if (id == null) return null;
MapUserSessionEntity userSessionEntity = transientUserSessions.get(id);
if (userSessionEntity == null) {

View file

@ -0,0 +1,84 @@
/*
* 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;
/**
* 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 LockObjectsForModification.Enabled enable(KeycloakSession session) {
return new Enabled(session);
}
public static boolean isEnabled(KeycloakSession session) {
return session.getAttribute(ATTRIBUTE) != null;
}
public static <V> V lockObjectsForModification(KeycloakSession session, CallableWithoutThrowingAnException<V> callable) {
if (LockObjectsForModification.isEnabled(session)) {
// If someone nests the call, and it would already be locked, don't try to lock it a second time.
// Otherwise, the inner unlocking might also unlock the outer lock.
return callable.call();
}
try (LockObjectsForModification.Enabled ignored = LockObjectsForModification.enable(session)) {
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;
public Enabled(KeycloakSession session) {
this.session = session;
session.setAttribute(ATTRIBUTE, Boolean.TRUE);
}
@Override
public void close() {
session.removeAttribute(ATTRIBUTE);
}
}
}

View file

@ -21,7 +21,6 @@ package org.keycloak.models;
import java.util.Map;
import org.keycloak.sessions.CommonClientSessionModel;
import org.keycloak.storage.SearchableModelField;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>

View file

@ -67,6 +67,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.keycloak.utils.LockObjectsForModification.lockObjectsForModification;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@ -1066,7 +1068,7 @@ public class AuthenticationProcessor {
if (userSession == null) { // if no authenticator attached a usersession
userSession = session.sessions().getUserSession(realm, authSession.getParentSession().getId());
userSession = lockObjectsForModification(session, () -> session.sessions().getUserSession(realm, authSession.getParentSession().getId()));
if (userSession == null) {
UserSessionModel.SessionPersistenceState persistenceState = UserSessionModel.SessionPersistenceState.fromString(authSession.getClientNote(AuthenticationManager.USER_SESSION_PERSISTENT_STATE));

View file

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

View file

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

View file

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

View file

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

View file

@ -72,6 +72,8 @@ import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.keycloak.utils.LockObjectsForModification.lockObjectsForModification;
/**
* @author Pedro Igor
*/
@ -336,7 +338,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
UserSessionModel userSession = session.sessions().getUserSession(realm, state);
UserSessionModel userSession = lockObjectsForModification(session, () -> session.sessions().getUserSession(realm, state));
if (userSession == null) {
logger.error("no valid user session");
EventBuilder event = new EventBuilder(realm, session, clientConnection);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -145,6 +145,7 @@ import javax.ws.rs.core.MultivaluedMap;
import javax.xml.parsers.ParserConfigurationException;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.utils.LockObjectsForModification.lockObjectsForModification;
/**
@ -1143,7 +1144,7 @@ public class SamlService extends AuthorizationEndpointBase {
return emptyArtifactResponseMessage(artifactResolveMessage, null);
}
UserSessionModel userSessionModel = session.sessions().getUserSession(realm, sessionMapping.get(SamlProtocol.USER_SESSION_ID));
UserSessionModel userSessionModel = lockObjectsForModification(session, () -> session.sessions().getUserSession(realm, sessionMapping.get(SamlProtocol.USER_SESSION_ID)));
if (userSessionModel == null) {
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);

View file

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

View file

@ -36,6 +36,8 @@ import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static org.keycloak.utils.LockObjectsForModification.lockObjectsForModification;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@ -98,7 +100,7 @@ public class AuthenticationSessionManager {
AuthSessionId authSessionId = decodeAuthSessionId(oldEncodedId);
String sessionId = authSessionId.getDecodedId();
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
UserSessionModel userSession = lockObjectsForModification(session, () -> session.sessions().getUserSession(realm, sessionId));
if (userSession != null) {
reencodeAuthSessionCookie(oldEncodedId, authSessionId, realm);
@ -215,7 +217,7 @@ public class AuthenticationSessionManager {
// Check to see if we already have authenticationSession with same ID
public UserSessionModel getUserSession(AuthenticationSessionModel authSession) {
return session.sessions().getUserSession(authSession.getRealm(), authSession.getParentSession().getId());
return lockObjectsForModification(session, () -> session.sessions().getUserSession(authSession.getRealm(), authSession.getParentSession().getId()));
}

View file

@ -27,6 +27,8 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import static org.keycloak.utils.LockObjectsForModification.lockObjectsForModification;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@ -69,9 +71,9 @@ public class UserSessionCrossDCManager {
String sessionId = authSessionId.getDecodedId();
// This will remove userSession "locally" if it doesn't exist on remoteCache
kcSession.sessions().getUserSessionWithPredicate(realm, sessionId, false, (UserSessionModel userSession2) -> userSession2 == null);
lockObjectsForModification(kcSession, () -> kcSession.sessions().getUserSessionWithPredicate(realm, sessionId, false, (UserSessionModel userSession2) -> userSession2 == null));
UserSessionModel userSession = kcSession.sessions().getUserSession(realm, sessionId);
UserSessionModel userSession = lockObjectsForModification(kcSession, () -> kcSession.sessions().getUserSession(realm, sessionId));
if (userSession != null) {
asm.reencodeAuthSessionCookie(oldEncodedId, authSessionId, realm);

View file

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

View file

@ -47,6 +47,8 @@ import org.keycloak.representations.account.SessionRepresentation;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager;
import static org.keycloak.utils.LockObjectsForModification.lockObjectsForModification;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@ -148,7 +150,7 @@ public class SessionResource {
@NoCache
public Response logout(@PathParam("id") String id) {
auth.require(AccountRoles.MANAGE_ACCOUNT);
UserSessionModel userSession = session.sessions().getUserSession(realm, id);
UserSessionModel userSession = lockObjectsForModification(session, () -> session.sessions().getUserSession(realm, id));
if (userSession != null && userSession.getUser().equals(user)) {
AuthenticationManager.backchannelLogout(session, userSession, true);
}

View file

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

View file

@ -116,6 +116,7 @@ import java.util.stream.Stream;
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_ID;
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_USERNAME;
import static org.keycloak.userprofile.UserProfileContext.USER_API;
import static org.keycloak.utils.LockObjectsForModification.lockObjectsForModification;
/**
* Base resource for managing users
@ -313,7 +314,7 @@ public class UserResource {
String sessionState = auth.adminAuth().getToken().getSessionState();
if (authenticatedRealm.getId().equals(realm.getId()) && sessionState != null) {
sameRealm = true;
UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, sessionState);
UserSessionModel userSession = lockObjectsForModification(session, () -> session.sessions().getUserSession(authenticatedRealm, sessionState));
AuthenticationManager.expireIdentityCookie(realm, session.getContext().getUri(), clientConnection);
AuthenticationManager.expireRememberMeCookie(realm, session.getContext().getUri(), clientConnection);
AuthenticationManager.backchannelLogout(session, authenticatedRealm, userSession, session.getContext().getUri(), clientConnection, headers, true);

View file

@ -902,6 +902,7 @@
<keycloak.role.map.storage.provider>jpa</keycloak.role.map.storage.provider>
<keycloak.singleUseObject.map.storage.provider>jpa</keycloak.singleUseObject.map.storage.provider>
<keycloak.user.map.storage.provider>jpa</keycloak.user.map.storage.provider>
<keycloak.userSession.map.storage.provider>jpa</keycloak.userSession.map.storage.provider>
</systemPropertyVariables>
</configuration>
</plugin>

View file

@ -32,6 +32,7 @@ import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory;
import org.keycloak.models.map.client.MapClientProviderFactory;
import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
import org.keycloak.models.map.deploymentState.MapDeploymentStateProviderFactory;
import org.keycloak.models.map.events.MapEventStoreProviderFactory;
import org.keycloak.models.map.group.MapGroupProviderFactory;
import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory;
@ -88,21 +89,21 @@ public class JpaMapStorage extends KeycloakModelParameters {
.config("driverDialect", "org.keycloak.models.map.storage.jpa.hibernate.dialect.JsonbPostgreSQL95Dialect");
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("group").provider(MapGroupProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("realm").provider(MapRealmProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("role").provider(MapRoleProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("group").provider(MapGroupProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("realm").provider(MapRealmProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("role").provider(MapRoleProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi(DeploymentStateSpi.NAME).provider(MapDeploymentStateProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(StoreFactorySpi.NAME).provider(MapAuthorizationStoreFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("user").provider(MapUserProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("user").provider(MapUserProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(EventStoreSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config("storage-admin-events.provider", JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi(EventStoreSpi.NAME).provider(MapEventStoreProviderFactory.PROVIDER_ID) .config("storage-admin-events.provider", JpaMapStorageProviderFactory.PROVIDER_ID)
.config("storage-auth-events.provider", JpaMapStorageProviderFactory.PROVIDER_ID);
}
}

View file

@ -54,7 +54,6 @@ import static org.keycloak.testsuite.model.session.UserSessionPersisterProviderT
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
@RequireProvider(UserSessionPersisterProvider.class)
@RequireProvider(UserSessionProvider.class)
@RequireProvider(UserProvider.class)
@RequireProvider(RealmProvider.class)