diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/Constants.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/Constants.java index 53240fc692..20cbd3b4df 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/Constants.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/Constants.java @@ -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; } diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapKeycloakTransaction.java index d886d78633..2ed2b39df4 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapKeycloakTransaction.java @@ -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 implements MapKeycloakTransaction { private static final Logger logger = Logger.getLogger(JpaMapKeycloakTransaction.class); + private final KeycloakSession session; private final Class entityType; private final Class modelType; private final boolean isExpirableEntity; protected EntityManager em; - public JpaMapKeycloakTransaction(Class entityType, Class modelType, EntityManager em) { + public JpaMapKeycloakTransaction(KeycloakSession session, Class entityType, Class modelType, EntityManager em) { + this.session = session; this.em = em; this.entityType = entityType; this.modelType = modelType; @@ -67,6 +72,15 @@ public abstract class JpaMapKeycloakTransaction cacheWithinSession = new HashMap<>(); /** @@ -101,7 +115,11 @@ public abstract class JpaMapKeycloakTransaction 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); } diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProvider.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProvider.java index 13ec9889f9..a2bbdb17d6 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProvider.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProvider.java @@ -59,7 +59,7 @@ public class JpaMapStorageProvider implements MapStorageProvider { return new MapStorage() { @Override public MapKeycloakTransaction createTransaction(KeycloakSession session) { - return factory.createTransaction(modelType, em); + return factory.createTransaction(session, modelType, em); } }; } diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProviderFactory.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProviderFactory.java index 438fa71b75..dba49e987a 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProviderFactory.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaMapStorageProviderFactory.java @@ -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, Function> MODEL_TO_TX = new HashMap<>(); + private static final Map, BiFunction> 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"); diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaModelCriteriaBuilder.java index 344d8f7719..de1c06de48 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaModelCriteriaBuilder.java @@ -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; /** diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaPredicateFunction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaPredicateFunction.java similarity index 96% rename from model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaPredicateFunction.java rename to model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaPredicateFunction.java index 866c19a56b..a9aaccda59 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaPredicateFunction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaPredicateFunction.java @@ -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; diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaTransactionWrapper.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaTransactionWrapper.java index d632a9cab3..f5fddb3383 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaTransactionWrapper.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaTransactionWrapper.java @@ -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; /** diff --git a/model/map-jpa/src/main/java/org/keycloak/connections/jpa/PersistenceExceptionConverter.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/PersistenceExceptionConverter.java similarity index 98% rename from model/map-jpa/src/main/java/org/keycloak/connections/jpa/PersistenceExceptionConverter.java rename to model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/PersistenceExceptionConverter.java index 7749f26b45..0965beb62b 100644 --- a/model/map-jpa/src/main/java/org/keycloak/connections/jpa/PersistenceExceptionConverter.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/PersistenceExceptionConverter.java @@ -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; diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionMapKeycloakTransaction.java index c7043b1794..63059b5c0f 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionMapKeycloakTransaction.java @@ -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 { @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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionModelCriteriaBuilder.java index 8e9cbb3ed8..be7be64fe4 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/JpaRootAuthenticationSessionModelCriteriaBuilder.java @@ -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; diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaAuthenticationSessionEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaAuthenticationSessionEntity.java index 0401089220..1defe2e369 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaAuthenticationSessionEntity.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authSession/entity/JpaAuthenticationSessionEntity.java @@ -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") diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionMapKeycloakTransaction.java index a2b4b2885f..b63165c7fe 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionMapKeycloakTransaction.java @@ -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 { @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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionModelCriteriaBuilder.java index 5f10f2e036..b239614213 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/permission/JpaPermissionModelCriteriaBuilder.java @@ -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 { diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyMapKeycloakTransaction.java index f86d450602..eff5e26009 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyMapKeycloakTransaction.java @@ -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 { @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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyModelCriteriaBuilder.java index e50b6f5532..9062f4fdc0 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/policy/JpaPolicyModelCriteriaBuilder.java @@ -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 { diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceMapKeycloakTransaction.java index 55ff7fa14e..de3d3eb178 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceMapKeycloakTransaction.java @@ -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 { @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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceModelCriteriaBuilder.java index c719edddb9..3934156045 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resource/JpaResourceModelCriteriaBuilder.java @@ -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 { diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerMapKeycloakTransaction.java index 5882ad73a2..1c8d577deb 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerMapKeycloakTransaction.java @@ -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 { @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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerModelCriteriaBuilder.java index d415fee8bd..428764b0e7 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/resourceServer/JpaResourceServerModelCriteriaBuilder.java @@ -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 { diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeMapKeycloakTransaction.java index 2184fc4614..c400daed76 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeMapKeycloakTransaction.java @@ -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 { @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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeModelCriteriaBuilder.java index 075d3bd14b..7c0bfbd63c 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/authorization/scope/JpaScopeModelCriteriaBuilder.java @@ -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 { diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientMapKeycloakTransaction.java index a43deb6a51..3bdf887618 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientMapKeycloakTransaction.java @@ -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 { @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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientModelCriteriaBuilder.java index bc1d1808d5..7acc3def7f 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/client/JpaClientModelCriteriaBuilder.java @@ -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 { diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientscope/JpaClientScopeMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientscope/JpaClientScopeMapKeycloakTransaction.java index 5416f5ea3f..35cf1a314c 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientscope/JpaClientScopeMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientscope/JpaClientScopeMapKeycloakTransaction.java @@ -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 { @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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientscope/JpaClientScopeModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientscope/JpaClientScopeModelCriteriaBuilder.java index b0ae985f41..e9a65e3966 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientscope/JpaClientScopeModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/clientscope/JpaClientScopeModelCriteriaBuilder.java @@ -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 { diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventMapKeycloakTransaction.java index e0e684180f..80fc51338c 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventMapKeycloakTransaction.java @@ -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 { - 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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventModelCriteriaBuilder.java index cd9ba16d0a..0d2385a2b3 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/admin/JpaAdminEventModelCriteriaBuilder.java @@ -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; diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventMapKeycloakTransaction.java index be07979e2f..a014025ac0 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventMapKeycloakTransaction.java @@ -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 { - 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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventModelCriteriaBuilder.java index a7857e437c..ec3822b041 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/event/auth/JpaAuthEventModelCriteriaBuilder.java @@ -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; diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupMapKeycloakTransaction.java index 20f7db14fc..c0c6a27765 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupMapKeycloakTransaction.java @@ -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 { @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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupModelCriteriaBuilder.java index 4a5177c439..a8a7c8fcf9 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/group/JpaGroupModelCriteriaBuilder.java @@ -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 { diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaEntityMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaEntityMigration.java index 294235d9ab..4088f0ee34 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaEntityMigration.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaEntityMigration.java @@ -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)); } diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientSessionMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientSessionMigration.java new file mode 100644 index 0000000000..627f843bc2 --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientSessionMigration.java @@ -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> MIGRATORS = Arrays.asList( + o -> o // no migration yet + ); +} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserSessionMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserSessionMigration.java new file mode 100644 index 0000000000..8c856c2029 --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaUserSessionMigration.java @@ -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> MIGRATORS = Arrays.asList( + o -> o // no migration yet + ); +} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaOptimisticLockingListener.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaOptimisticLockingListener.java index 12dd680c0a..c582634051 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaOptimisticLockingListener.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/listeners/JpaOptimisticLockingListener.java @@ -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". diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureMapKeycloakTransaction.java index cde1d9e3e8..382c378787 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureMapKeycloakTransaction.java @@ -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 { @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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureModelCriteriaBuilder.java index 9675481848..40c69e1ae2 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/loginFailure/JpaUserLoginFailureModelCriteriaBuilder.java @@ -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; /** diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmMapKeycloakTransaction.java index 2cec9233a4..9c28b288cb 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmMapKeycloakTransaction.java @@ -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 { - 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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmModelCriteriaBuilder.java index 0a12a7f430..52947842c9 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/realm/JpaRealmModelCriteriaBuilder.java @@ -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; diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleMapKeycloakTransaction.java index fca5fafbc7..e59f4d41bb 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleMapKeycloakTransaction.java @@ -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 { - 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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleModelCriteriaBuilder.java index 070a6a52be..d338b459de 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/role/JpaRoleModelCriteriaBuilder.java @@ -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; diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectMapKeycloakTransaction.java index d47bfbda2b..cfe3f8959d 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectMapKeycloakTransaction.java @@ -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 { - 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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectModelCriteriaBuilder.java index 5754371569..e163e1a0c0 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/singleUseObject/JpaSingleUseObjectModelCriteriaBuilder.java @@ -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; diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserMapKeycloakTransaction.java index d20ce58f81..b8e2ae216f 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserMapKeycloakTransaction.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserMapKeycloakTransaction.java @@ -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 { - 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 diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserModelCriteriaBuilder.java index 6a457826de..b90ec56d36 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserModelCriteriaBuilder.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/user/JpaUserModelCriteriaBuilder.java @@ -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; diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionMapKeycloakTransaction.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionMapKeycloakTransaction.java new file mode 100644 index 0000000000..19f5e44d5f --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionMapKeycloakTransaction.java @@ -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 { + + public JpaUserSessionMapKeycloakTransaction(KeycloakSession session, final EntityManager em) { + super(session, JpaUserSessionEntity.class, UserSessionModel.class, em); + } + + @Override + protected Selection selectCbConstruct(CriteriaBuilder cb, Root 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; + } + +} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionModelCriteriaBuilder.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionModelCriteriaBuilder.java new file mode 100644 index 0000000000..72bc16c180 --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/JpaUserSessionModelCriteriaBuilder.java @@ -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 { + + public JpaUserSessionModelCriteriaBuilder() { + super(JpaUserSessionModelCriteriaBuilder::new); + } + + private JpaUserSessionModelCriteriaBuilder(JpaPredicateFunction predicateFunc) { + super(JpaUserSessionModelCriteriaBuilder::new, predicateFunc); + } + + @Override + public JpaUserSessionModelCriteriaBuilder compare(SearchableModelField 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 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); + } + } +} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionEntity.java new file mode 100644 index 0000000000..fffcfa19c3 --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionEntity.java @@ -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 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 getNotes() { + return notes.stream().collect(Collectors.toMap(JpaClientSessionNoteEntity::getName, JpaClientSessionNoteEntity::getValue)); + } + + @Override + public void setNotes(Map notes) { + this.notes.clear(); + if (notes == null) return; + for (Map.Entry 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()); + } +} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionMetadata.java new file mode 100644 index 0000000000..472d5af704 --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionMetadata.java @@ -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; + } + +} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionNoteEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionNoteEntity.java new file mode 100644 index 0000000000..793398bfd0 --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaClientSessionNoteEntity.java @@ -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 { + + 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()); + } +} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionEntity.java new file mode 100644 index 0000000000..c8545d424f --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionEntity.java @@ -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 notes = new HashSet<>(); + + @OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true) + private final Set 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 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 notes) { + this.notes.clear(); + if (notes == null) return; + for (Map.Entry 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 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 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()); + } +} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionMetadata.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionMetadata.java new file mode 100644 index 0000000000..efe7b619f3 --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionMetadata.java @@ -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; + } + +} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionNoteEntity.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionNoteEntity.java new file mode 100644 index 0000000000..b7e362c4e7 --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/userSession/entity/JpaUserSessionNoteEntity.java @@ -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 { + + 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()); + } +} diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-aggregate-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-aggregate-changelog.xml index 334ca36e44..6b3ce97cf5 100644 --- a/model/map-jpa/src/main/resources/META-INF/jpa-aggregate-changelog.xml +++ b/model/map-jpa/src/main/resources/META-INF/jpa-aggregate-changelog.xml @@ -29,5 +29,6 @@ limitations under the License. + - \ No newline at end of file + diff --git a/model/map-jpa/src/main/resources/META-INF/jpa-user-sessions-changelog.xml b/model/map-jpa/src/main/resources/META-INF/jpa-user-sessions-changelog.xml new file mode 100644 index 0000000000..14140aef26 --- /dev/null +++ b/model/map-jpa/src/main/resources/META-INF/jpa-user-sessions-changelog.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/model/map-jpa/src/main/resources/META-INF/persistence.xml b/model/map-jpa/src/main/resources/META-INF/persistence.xml index 68c0a0a837..fa3167d157 100644 --- a/model/map-jpa/src/main/resources/META-INF/persistence.xml +++ b/model/map-jpa/src/main/resources/META-INF/persistence.xml @@ -38,6 +38,11 @@ org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectNoteEntity org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity + + org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionEntity + org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionNoteEntity + org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionEntity + org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionNoteEntity org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity org.keycloak.models.map.storage.jpa.user.entity.JpaUserAttributeEntity diff --git a/model/map-jpa/src/main/resources/META-INF/user-sessions/jpa-user-sessions-changelog-1.xml b/model/map-jpa/src/main/resources/META-INF/user-sessions/jpa-user-sessions-changelog-1.xml new file mode 100644 index 0000000000..506ec391be --- /dev/null +++ b/model/map-jpa/src/main/resources/META-INF/user-sessions/jpa-user-sessions-changelog-1.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/ModelEntityUtil.java b/model/map/src/main/java/org/keycloak/models/map/storage/ModelEntityUtil.java index 9fec6861b6..4b03042f30 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/ModelEntityUtil.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/ModelEntityUtil.java @@ -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, 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> 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); diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java b/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java index a93400865e..67b216d029 100644 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java @@ -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) { diff --git a/server-spi-private/src/main/java/org/keycloak/utils/LockObjectsForModification.java b/server-spi-private/src/main/java/org/keycloak/utils/LockObjectsForModification.java new file mode 100644 index 0000000000..0df92e2488 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/utils/LockObjectsForModification.java @@ -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 lockObjectsForModification(KeycloakSession session, CallableWithoutThrowingAnException 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 { + /** + * 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); + } + } +} diff --git a/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java b/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java index d069f93c19..ed85e14423 100644 --- a/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java +++ b/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java @@ -21,7 +21,6 @@ package org.keycloak.models; import java.util.Map; import org.keycloak.sessions.CommonClientSessionModel; -import org.keycloak.storage.SearchableModelField; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index bf83f6ca00..f2d6845c36 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -67,6 +67,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static org.keycloak.utils.LockObjectsForModification.lockObjectsForModification; + /** * @author Bill Burke * @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)); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/sessionlimits/UserSessionLimitsAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/sessionlimits/UserSessionLimitsAuthenticator.java index 1fa125f2d5..d317797115 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/sessionlimits/UserSessionLimitsAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/sessionlimits/UserSessionLimitsAuthenticator.java @@ -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 userSessionsForRealm = session.sessions().getUserSessionsStream(context.getRealm(), context.getUser()).collect(Collectors.toList()); + List 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 diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java index dd1a6a599d..1d67228365 100644 --- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java +++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java @@ -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 Pedro Igor */ @@ -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()); diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java index 73e277f353..ed53348cab 100644 --- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java +++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java @@ -46,6 +46,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import static org.keycloak.utils.LockObjectsForModification.lockObjectsForModification; + /** * @author Pedro Igor */ @@ -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()); diff --git a/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java index 1b3428fc65..4e79ab9f6e 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java @@ -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 Bill Burke * @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 diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java index d17de8c553..cdaa1c6e46 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java @@ -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 session.sessions().getUserSession(realm, state)); if (userSession == null) { logger.error("no valid user session"); EventBuilder event = new EventBuilder(realm, session, clientConnection); diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java index 86cab91762..0df7084c26 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java @@ -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 Bill Burke * @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); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index c1d16570be..e1eb6c35f3 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -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()); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index adf6e68831..445a08c697 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -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 = 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) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index 7b0fc24baf..232c5c5873 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -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 Stian Thorgersen */ @@ -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); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java index 04a700678a..026988e01e 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java @@ -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", diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2CodeParser.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2CodeParser.java index 75a48148f2..caa51a5e67 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2CodeParser.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2CodeParser.java @@ -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 Marek Posolda */ @@ -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(); } diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java index a6fb156ac0..5543d64c86 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -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); diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index ba7fee0af3..a0b1657318 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -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); diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java index a6836ec9ae..c5398d809d 100644 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java @@ -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 Marek Posolda */ @@ -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())); } diff --git a/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java b/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java index a2f448807d..e8c670e496 100644 --- a/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java +++ b/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java @@ -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 Marek Posolda */ @@ -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); diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java index 972b999a1c..bc16c8d88a 100644 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java @@ -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) && diff --git a/services/src/main/java/org/keycloak/services/resources/account/SessionResource.java b/services/src/main/java/org/keycloak/services/resources/account/SessionResource.java index 5d816118c3..4f52e370c9 100755 --- a/services/src/main/java/org/keycloak/services/resources/account/SessionResource.java +++ b/services/src/main/java/org/keycloak/services/resources/account/SessionResource.java @@ -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 Pedro Igor */ @@ -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); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 059f7082e1..8c01ffddbc 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -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(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java index bc78006430..925aca6f0c 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java @@ -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); diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 1486247955..381c549d0e 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -902,6 +902,7 @@ jpa jpa jpa + jpa diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaMapStorage.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaMapStorage.java index 619b5b9a5d..6704843cf8 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaMapStorage.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaMapStorage.java @@ -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); } } diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderModelTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderModelTest.java index 64abe12708..14f49b21c9 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderModelTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderModelTest.java @@ -54,7 +54,6 @@ import static org.keycloak.testsuite.model.session.UserSessionPersisterProviderT /** * @author Martin Kanis */ -@RequireProvider(UserSessionPersisterProvider.class) @RequireProvider(UserSessionProvider.class) @RequireProvider(UserProvider.class) @RequireProvider(RealmProvider.class)