JPA map storage: Authentication session no-downtime store

Closes #9665
This commit is contained in:
vramik 2022-03-21 09:44:22 +01:00 committed by Hynek Mlnařík
parent 22a16ee899
commit 8ff768b33b
24 changed files with 1076 additions and 45 deletions

View file

@ -17,6 +17,7 @@
package org.keycloak.models.map.storage.jpa;
public interface Constants {
public static final Integer CURRENT_SCHEMA_VERSION_AUTH_SESSION = 1;
public static final Integer CURRENT_SCHEMA_VERSION_CLIENT = 1;
public static final Integer CURRENT_SCHEMA_VERSION_CLIENT_SCOPE = 1;
public static final Integer CURRENT_SCHEMA_VERSION_GROUP = 1;

View file

@ -44,8 +44,10 @@ public abstract class JpaDelegateProvider<T extends JpaRootEntity & AbstractEnti
if (newDelegate == null) {
throw new ModelIllegalStateException("Unable to retrieve entity: " + delegate.getClass().getName() + "#" + delegate.getId());
}
if (newDelegate.getVersion() != delegate.getVersion()) {
throw new ModelIllegalStateException("Version of entity changed between two loads: " + delegate.getClass().getName() + "#" + delegate.getId());
if (newDelegate instanceof JpaRootVersionedEntity && delegate instanceof JpaRootVersionedEntity) {
if (((JpaRootVersionedEntity) newDelegate).getVersion() != ((JpaRootVersionedEntity) delegate).getVersion()) {
throw new ModelIllegalStateException("Version of entity changed between two loads: " + delegate.getClass().getName() + "#" + delegate.getId());
}
}
this.delegate = newDelegate;
}

View file

@ -22,6 +22,7 @@ import java.util.UUID;
import java.util.function.BiFunction;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
@ -29,7 +30,8 @@ import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.connections.jpa.JpaKeycloakTransaction;
import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.PersistenceExceptionConverter;
import static org.keycloak.models.jpa.PaginationUtils.paginateQuery;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.StringKeyConverter;
@ -39,13 +41,15 @@ import org.keycloak.models.map.storage.QueryParameters;
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
import static org.keycloak.utils.StreamsUtil.closing;
public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E extends AbstractEntity, M> extends JpaKeycloakTransaction implements MapKeycloakTransaction<E, M> {
public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E extends AbstractEntity, M> implements MapKeycloakTransaction<E, M> {
private static final Logger logger = Logger.getLogger(JpaMapKeycloakTransaction.class);
private final Class<RE> entityType;
protected EntityManager em;
@SuppressWarnings("unchecked")
public JpaMapKeycloakTransaction(Class<RE> entityType, EntityManager em) {
super(em);
this.em = em;
this.entityType = entityType;
}
@ -58,10 +62,10 @@ public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E exte
@SuppressWarnings("unchecked")
public E create(E mapEntity) {
JpaRootEntity jpaEntity = entityType.cast(CLONER.from(mapEntity));
CLONER.from(mapEntity);
if (mapEntity.getId() == null) {
jpaEntity.setId(StringKeyConverter.UUIDKey.INSTANCE.yieldNewUniqueKey().toString());
}
logger.tracef("tx %d: create entity %s", hashCode(), jpaEntity.getId());
setEntityVersion(jpaEntity);
em.persist(jpaEntity);
return (E) jpaEntity;
@ -137,6 +141,7 @@ public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E exte
UUID uuid = UUIDKey.INSTANCE.fromStringSafe(key);
if (uuid == null) return false;
em.remove(em.getReference(entityType, uuid));
logger.tracef("tx %d: delete entity %s", hashCode(), key);
return true;
}
@ -155,14 +160,43 @@ public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E exte
BiFunction<CriteriaBuilder, Root<RE>, Predicate> predicateFunc = mcb.getPredicateFunc();
if (predicateFunc != null) deleteQuery.where(predicateFunc.apply(cb, root));
// TODO find out if the flush and clear are needed here or not, since delete(QueryParameters)
// is not used yet from the code it's difficult to investigate its potential purpose here
// according to https://thorben-janssen.com/5-common-hibernate-mistakes-that-cause-dozens-of-unexpected-queries/#Remove_Child_Entities_With_a_Bulk_Operation
// it seems it is necessary unless it is sure that any of removed entities wasn't fetched
// Once KEYCLOAK-19697 is done we could test our scenarios and see if we need the flush and clear
// em.flush();
// em.clear();
return em.createQuery(deleteQuery).executeUpdate();
}
@Override
public void begin() {
logger.tracef("tx %d: begin", hashCode());
em.getTransaction().begin();
}
@Override
public void commit() {
try {
logger.tracef("tx %d: commit", hashCode());
em.getTransaction().commit();
} catch (PersistenceException e) {
throw PersistenceExceptionConverter.convert(e.getCause() != null ? e.getCause() : e);
}
}
@Override
public void rollback() {
logger.tracef("tx %d: rollback", hashCode());
em.getTransaction().rollback();
}
@Override
public void setRollbackOnly() {
em.getTransaction().setRollbackOnly();
}
@Override
public boolean getRollbackOnly() {
return em.getTransaction().getRollbackOnly();
}
@Override
public boolean isActive() {
return em.getTransaction().isActive();
}
}

View file

@ -62,6 +62,9 @@ import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.storage.jpa.authSession.JpaRootAuthenticationSessionMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaAuthenticationSessionEntity;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity;
import org.keycloak.models.map.storage.jpa.client.JpaClientMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.clientscope.JpaClientScopeMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEntity;
@ -75,6 +78,7 @@ import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider;
import static org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider.Status.VALID;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.sessions.RootAuthenticationSessionModel;
public class JpaMapStorageProviderFactory implements
AmphibianProviderFactory<MapStorageProvider>,
@ -89,23 +93,27 @@ public class JpaMapStorageProviderFactory implements
private Config.Scope config;
public final static DeepCloner CLONER = new DeepCloner.Builder()
//client
.constructor(JpaClientEntity.class, JpaClientEntity::new)
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
//client-scope
.constructor(JpaClientScopeEntity.class, JpaClientScopeEntity::new)
//group
.constructor(JpaGroupEntity.class, JpaGroupEntity::new)
//role
.constructor(JpaRoleEntity.class, JpaRoleEntity::new)
.build();
//auth-session
.constructor(JpaRootAuthenticationSessionEntity.class, JpaRootAuthenticationSessionEntity::new)
.constructor(JpaAuthenticationSessionEntity.class, JpaAuthenticationSessionEntity::new)
//client
.constructor(JpaClientEntity.class, JpaClientEntity::new)
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
//client-scope
.constructor(JpaClientScopeEntity.class, JpaClientScopeEntity::new)
//group
.constructor(JpaGroupEntity.class, JpaGroupEntity::new)
//role
.constructor(JpaRoleEntity.class, JpaRoleEntity::new)
.build();
private static final Map<Class<?>, Function<EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>();
static {
MODEL_TO_TX.put(ClientScopeModel.class, JpaClientScopeMapKeycloakTransaction::new);
MODEL_TO_TX.put(ClientModel.class, JpaClientMapKeycloakTransaction::new);
MODEL_TO_TX.put(GroupModel.class, JpaGroupMapKeycloakTransaction::new);
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
MODEL_TO_TX.put(RootAuthenticationSessionModel.class, JpaRootAuthenticationSessionMapKeycloakTransaction::new);
MODEL_TO_TX.put(ClientScopeModel.class, JpaClientScopeMapKeycloakTransaction::new);
MODEL_TO_TX.put(ClientModel.class, JpaClientMapKeycloakTransaction::new);
MODEL_TO_TX.put(GroupModel.class, JpaGroupMapKeycloakTransaction::new);
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
}
public MapKeycloakTransaction createTransaction(Class<?> modelType, EntityManager em) {

View file

@ -26,11 +26,6 @@ import org.keycloak.models.map.common.AbstractEntity;
*/
public interface JpaRootEntity extends AbstractEntity, Serializable {
/**
* Version of the JPA entity used for optimistic locking
*/
int getVersion();
/**
* @return current supported version of the JPA entity used for schema versioning.
*/

View file

@ -0,0 +1,29 @@
/*
* 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;
/**
* Interface for all root entities which implements optimistic locking.
*/
public interface JpaRootVersionedEntity extends JpaRootEntity {
/**
* Version of the JPA entity used for optimistic locking
*/
int getVersion();
}

View file

@ -0,0 +1,66 @@
/*
* 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.authSession;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
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;
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.authSession.delegate.JpaRootAuthenticationSessionDelegateProvider;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity;
import org.keycloak.sessions.RootAuthenticationSessionModel;
public class JpaRootAuthenticationSessionMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaRootAuthenticationSessionEntity, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> {
@SuppressWarnings("unchecked")
public JpaRootAuthenticationSessionMapKeycloakTransaction(EntityManager em) {
super(JpaRootAuthenticationSessionEntity.class, em);
}
@Override
public Selection<JpaRootAuthenticationSessionEntity> selectCbConstruct(CriteriaBuilder cb, Root<JpaRootAuthenticationSessionEntity> root) {
return cb.construct(JpaRootAuthenticationSessionEntity.class,
root.get("id"),
root.get("version"),
root.get("entityVersion"),
root.get("realmId"),
root.get("timestamp"),
root.get("expiration")
);
}
@Override
public void setEntityVersion(JpaRootEntity entity) {
entity.setEntityVersion(CURRENT_SCHEMA_VERSION_AUTH_SESSION);
}
@Override
public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() {
return new JpaRootAuthenticationSessionModelCriteriaBuilder();
}
@Override
protected MapRootAuthenticationSessionEntity mapToEntityDelegate(JpaRootAuthenticationSessionEntity original) {
return new MapRootAuthenticationSessionEntityDelegate(new JpaRootAuthenticationSessionDelegateProvider(original, em));
}
}

View file

@ -0,0 +1,70 @@
/*
* 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.authSession;
import java.util.function.BiFunction;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel.SearchableFields;
import org.keycloak.storage.SearchableModelField;
public class JpaRootAuthenticationSessionModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaRootAuthenticationSessionEntity, RootAuthenticationSessionModel, JpaRootAuthenticationSessionModelCriteriaBuilder> {
public JpaRootAuthenticationSessionModelCriteriaBuilder() {
super(JpaRootAuthenticationSessionModelCriteriaBuilder::new);
}
private JpaRootAuthenticationSessionModelCriteriaBuilder(BiFunction<CriteriaBuilder, Root<JpaRootAuthenticationSessionEntity>, Predicate> predicateFunc) {
super(JpaRootAuthenticationSessionModelCriteriaBuilder::new, predicateFunc);
}
@Override
public JpaRootAuthenticationSessionModelCriteriaBuilder compare(SearchableModelField<? super RootAuthenticationSessionModel> modelField, Operator op, Object... value) {
switch (op) {
case EQ:
if (modelField.equals(SearchableFields.REALM_ID)) {
validateValue(value, modelField, op, String.class);
return new JpaRootAuthenticationSessionModelCriteriaBuilder((cb, root) ->
cb.equal(root.get(modelField.getName()), value[0])
);
} else {
throw new CriterionNotSupportedException(modelField, op);
}
case LT:
if (modelField.equals(SearchableFields.EXPIRATION)) {
validateValue(value, modelField, op, Number.class);
Number expiration = (Number) value[0];
return new JpaRootAuthenticationSessionModelCriteriaBuilder((cb, root) ->
cb.lt(root.get(modelField.getName()), expiration)
);
} else {
throw new CriterionNotSupportedException(modelField, op);
}
default:
throw new CriterionNotSupportedException(modelField, op);
}
}
}

View file

@ -0,0 +1,61 @@
/*
* 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.authSession.delegate;
import java.util.UUID;
import javax.persistence.EntityManager;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntityFields;
import org.keycloak.models.map.common.EntityField;
import org.keycloak.models.map.common.delegate.DelegateProvider;
import org.keycloak.models.map.storage.jpa.JpaDelegateProvider;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity;
public class JpaRootAuthenticationSessionDelegateProvider extends JpaDelegateProvider<JpaRootAuthenticationSessionEntity> implements DelegateProvider<MapRootAuthenticationSessionEntity> {
private final EntityManager em;
public JpaRootAuthenticationSessionDelegateProvider(JpaRootAuthenticationSessionEntity delegate, EntityManager em) {
super(delegate);
this.em = em;
}
@Override
public MapRootAuthenticationSessionEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapRootAuthenticationSessionEntity>> field, Object... parameters) {
if (getDelegate().isMetadataInitialized()) return getDelegate();
if (isRead) {
if (field instanceof MapRootAuthenticationSessionEntityFields) {
switch ((MapRootAuthenticationSessionEntityFields) field) {
case ID:
case REALM_ID:
case TIMESTAMP:
case EXPIRATION:
return getDelegate();
default:
setDelegate(em.find(JpaRootAuthenticationSessionEntity.class, UUID.fromString(getDelegate().getId())));
}
} else {
throw new IllegalStateException("Not a valid client field: " + field);
}
} else {
setDelegate(em.find(JpaRootAuthenticationSessionEntity.class, UUID.fromString(getDelegate().getId())));
}
return getDelegate();
}
}

View file

@ -0,0 +1,292 @@
/*
* 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.authSession.entity;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
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.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.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")
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
public class JpaAuthenticationSessionEntity extends UpdatableEntity.Impl implements MapAuthenticationSessionEntity, JpaRootEntity {
@Id
@Column
@GeneratedValue
private UUID id;
@Type(type = "jsonb")
@Column(columnDefinition = "jsonb")
private final JpaAuthenticationSessionMetadata metadata;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="fk_root")
private JpaRootAuthenticationSessionEntity root;
/**
* No-argument constructor, used by hibernate to instantiate entities.
*/
public JpaAuthenticationSessionEntity() {
this.metadata = new JpaAuthenticationSessionMetadata();
}
public JpaAuthenticationSessionEntity(DeepCloner cloner) {
this.metadata = new JpaAuthenticationSessionMetadata(cloner);
}
public void setParent(JpaRootAuthenticationSessionEntity root) {
this.root = root;
}
@Override
public String getId() {
return id == null ? null : id.toString();
}
@Override
public void setId(String id) {
this.id = id == null ? null : UUID.fromString(id);
}
@Override
public Integer getEntityVersion() {
return metadata.getEntityVersion();
}
@Override
public void setEntityVersion(Integer version) {
metadata.setEntityVersion(version);
}
@Override
public Integer getCurrentSchemaVersion() {
return CURRENT_SCHEMA_VERSION_AUTH_SESSION;
}
@Override
public String getTabId() {
return metadata.getTabId();
}
@Override
public void setTabId(String tabId) {
metadata.setTabId(tabId);
}
@Override
public Map<String, String> getUserSessionNotes() {
return metadata.getUserSessionNotes();
}
@Override
public void setUserSessionNotes(Map<String, String> userSessionNotes) {
metadata.setUserSessionNotes(userSessionNotes);
}
@Override
public void setUserSessionNote(String name, String value) {
metadata.setUserSessionNote(name, value);
}
@Override
public String getClientUUID() {
return metadata.getClientUUID();
}
@Override
public void setClientUUID(String clientUUID) {
metadata.setClientUUID(clientUUID);
}
@Override
public String getAuthUserId() {
return metadata.getAuthUserId();
}
@Override
public void setAuthUserId(String authUserId) {
metadata.setAuthUserId(authUserId);
}
@Override
public Integer getTimestamp() {
return metadata.getTimestamp();
}
@Override
public void setTimestamp(Integer timestamp) {
metadata.setTimestamp(timestamp);
}
@Override
public String getRedirectUri() {
return metadata.getRedirectUri();
}
@Override
public void setRedirectUri(String redirectUri) {
metadata.setRedirectUri(redirectUri);
}
@Override
public String getAction() {
return metadata.getAction();
}
@Override
public void setAction(String action) {
metadata.setAction(action);
}
@Override
public Set<String> getClientScopes() {
return metadata.getClientScopes();
}
@Override
public void setClientScopes(Set<String> clientScopes) {
metadata.setClientScopes(clientScopes);
}
@Override
public Set<String> getRequiredActions() {
return metadata.getRequiredActions();
}
@Override
public void setRequiredActions(Set<String> requiredActions) {
metadata.setRequiredActions(requiredActions);
}
@Override
public void addRequiredAction(String requiredAction) {
metadata.addRequiredAction(requiredAction);
}
@Override
public void removeRequiredAction(String action) {
metadata.removeRequiredAction(action);
}
@Override
public String getProtocol() {
return metadata.getProtocol();
}
@Override
public void setProtocol(String protocol) {
metadata.setProtocol(protocol);
}
@Override
public Map<String, String> getClientNotes() {
return metadata.getClientNotes();
}
@Override
public void setClientNotes(Map<String, String> clientNotes) {
metadata.setClientNotes(clientNotes);
}
@Override
public void setClientNote(String name, String value) {
metadata.setClientNote(name, value);
}
@Override
public void removeClientNote(String name) {
metadata.removeClientNote(name);
}
@Override
public Map<String, String> getAuthNotes() {
return metadata.getAuthNotes();
}
@Override
public void setAuthNotes(Map<String, String> authNotes) {
metadata.setAuthNotes(authNotes);
}
@Override
public void setAuthNote(String name, String value) {
metadata.setAuthNote(name, value);
}
@Override
public void removeAuthNote(String name) {
metadata.removeAuthNote(name);
}
@Override
public Map<String, CommonClientSessionModel.ExecutionStatus> getExecutionStatuses() {
return metadata.getExecutionStatuses();
}
@Override
public void setExecutionStatuses(Map<String, CommonClientSessionModel.ExecutionStatus> executionStatus) {
metadata.setExecutionStatuses(executionStatus);
}
@Override
public void setExecutionStatus(String authenticator, CommonClientSessionModel.ExecutionStatus status) {
metadata.setExecutionStatus(authenticator, status);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof JpaAuthenticationSessionEntity)) return false;
return Objects.equals(getId(), ((JpaAuthenticationSessionEntity) obj).getId()) &&
Objects.equals(getTabId(), ((JpaAuthenticationSessionEntity) obj).getTabId());
}
}

View file

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

View file

@ -0,0 +1,226 @@
/*
* 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.authSession.entity;
import java.util.HashSet;
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.map.authSession.MapAuthenticationSessionEntity;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity.AbstractRootAuthenticationSessionEntity;
import org.keycloak.models.map.common.DeepCloner;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION;
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
/**
* Entity represents root authentication session.
*
* 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_auth_root_session")
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
public class JpaRootAuthenticationSessionEntity extends AbstractRootAuthenticationSessionEntity 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 JpaRootAuthenticationSessionMetadata 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 Integer timestamp;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private Long expiration;
@OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true)
private final Set<JpaAuthenticationSessionEntity> authSessions = new HashSet<>();
/**
* No-argument constructor, used by hibernate to instantiate entities.
*/
public JpaRootAuthenticationSessionEntity() {
this.metadata = new JpaRootAuthenticationSessionMetadata();
}
public JpaRootAuthenticationSessionEntity(DeepCloner cloner) {
this.metadata = new JpaRootAuthenticationSessionMetadata(cloner);
}
/**
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
* It is used to select root auth session without metadata(json) field.
*/
public JpaRootAuthenticationSessionEntity(UUID id, Integer entityVersion, String realmId, Integer timestamp, Long expiration) {
this.id = id;
this.entityVersion = entityVersion;
this.realmId = realmId;
this.timestamp = timestamp;
this.expiration = expiration;
this.metadata = null;
}
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 CURRENT_SCHEMA_VERSION_AUTH_SESSION;
}
@Override
public int getVersion() {
return version;
}
@Override
public String getId() {
return id == null ? null : id.toString();
}
@Override
public void setId(String id) {
this.id = id == null ? null : UUID.fromString(id);
}
@Override
public String getRealmId() {
if (isMetadataInitialized()) return metadata.getRealmId();
return realmId;
}
@Override
public void setRealmId(String realmId) {
metadata.setRealmId(realmId);
}
@Override
public Integer getTimestamp() {
if (isMetadataInitialized()) return metadata.getTimestamp();
return timestamp;
}
@Override
public void setTimestamp(Integer timestamp) {
metadata.setTimestamp(timestamp);
}
@Override
public Long getExpiration() {
if (isMetadataInitialized()) return metadata.getExpiration();
return expiration;
}
@Override
public void setExpiration(Long expiration) {
metadata.setExpiration(expiration);
}
@Override
public Set<MapAuthenticationSessionEntity> getAuthenticationSessions() {
return authSessions.stream().map(MapAuthenticationSessionEntity.class::cast).collect(Collectors.toSet());
}
@Override
public void setAuthenticationSessions(Set<MapAuthenticationSessionEntity> authenticationSessions) {
authSessions.clear();
if (authenticationSessions != null) {
for (MapAuthenticationSessionEntity authenticationSession : authenticationSessions) {
addAuthenticationSession(authenticationSession);
}
}
}
@Override
public void addAuthenticationSession(MapAuthenticationSessionEntity authenticationSession) {
JpaAuthenticationSessionEntity jpaAuthSession = JpaAuthenticationSessionEntity.class.cast(CLONER.from(authenticationSession));
jpaAuthSession.setParent(this);
jpaAuthSession.setEntityVersion(this.getEntityVersion());
authSessions.add(jpaAuthSession);
}
@Override
public Optional<MapAuthenticationSessionEntity> getAuthenticationSession(String tabId) {
return authSessions.stream().filter(as -> Objects.equals(as.getTabId(), tabId)).findFirst().map(MapAuthenticationSessionEntity.class::cast);
}
@Override
public Boolean removeAuthenticationSession(String tabId) {
return authSessions.removeIf(as -> Objects.equals(as.getTabId(), tabId));
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof JpaRootAuthenticationSessionEntity)) return false;
return Objects.equals(getId(), ((JpaRootAuthenticationSessionEntity) obj).getId());
}
}

View file

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

View file

@ -43,7 +43,7 @@ import org.keycloak.models.map.client.MapClientEntity.AbstractClientEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.common.DeepCloner;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT;
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;
/**
@ -59,7 +59,7 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
)
})
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
public class JpaClientEntity extends AbstractClientEntity implements JpaRootEntity {
public class JpaClientEntity extends AbstractClientEntity implements JpaRootVersionedEntity {
@Id
@Column

View file

@ -43,7 +43,7 @@ import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.clientscope.MapClientScopeEntity.AbstractClientScopeEntity;
import org.keycloak.models.map.common.DeepCloner;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SCOPE;
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;
/**
@ -54,7 +54,7 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
@Entity
@Table(name = "kc_client_scope", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "name"})})
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
public class JpaClientScopeEntity extends AbstractClientScopeEntity implements JpaRootEntity {
public class JpaClientScopeEntity extends AbstractClientScopeEntity implements JpaRootVersionedEntity {
@Id
@Column

View file

@ -41,7 +41,7 @@ import org.hibernate.annotations.TypeDefs;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.group.MapGroupEntity.AbstractGroupEntity;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP;
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;
/**
@ -52,7 +52,7 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
@Entity
@Table(name = "kc_group", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "name", "parentId"})})
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
public class JpaGroupEntity extends AbstractGroupEntity implements JpaRootEntity {
public class JpaGroupEntity extends AbstractGroupEntity implements JpaRootVersionedEntity {
@Id
@Column

View file

@ -22,17 +22,22 @@ import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
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_GROUP;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaAuthenticationSessionMetadata;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionMetadata;
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientMetadata;
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeMetadata;
import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupMetadata;
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.JpaClientScopeMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaGroupMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRoleMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRootAuthenticationSessionMigration;
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata;
@ -41,10 +46,12 @@ public class JpaEntityMigration {
static final Map<Class<?>, BiFunction<ObjectNode, Integer, ObjectNode>> MIGRATIONS = new HashMap<>();
static {
MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS));
MIGRATIONS.put(JpaClientScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SCOPE, tree, JpaClientScopeMigration.MIGRATORS));
MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS));
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
MIGRATIONS.put(JpaAuthenticationSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_SESSION, tree, JpaAuthenticationSessionMigration.MIGRATORS));
MIGRATIONS.put(JpaRootAuthenticationSessionMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_AUTH_SESSION, tree, JpaRootAuthenticationSessionMigration.MIGRATORS));
MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS));
MIGRATIONS.put(JpaClientScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SCOPE, tree, JpaClientScopeMigration.MIGRATORS));
MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS));
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
}
private static ObjectNode migrateTreeTo(int entityVersion, Integer supportedVersion, ObjectNode node, List<Function<ObjectNode, ObjectNode>> migrators) {

View file

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

View file

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

View file

@ -41,7 +41,7 @@ import org.hibernate.annotations.TypeDefs;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.role.MapRoleEntity.AbstractRoleEntity;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
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;
/**
@ -52,7 +52,7 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
@Entity
@Table(name = "kc_role", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "clientId", "name"})})
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
public class JpaRoleEntity extends AbstractRoleEntity implements JpaRootEntity {
public class JpaRoleEntity extends AbstractRoleEntity implements JpaRootVersionedEntity {
@Id
@Column

View file

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2022 Red Hat, Inc. and/or its affiliates
and other contributors as indicated by the @author tags.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!-- format of id of changeSet: auth-sessions-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION} -->
<changeSet author="keycloak" id="auth-sessions-1">
<createTable tableName="kc_auth_root_session">
<column name="id" type="UUID">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="version" type="INTEGER" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="metadata" type="json"/>
</createTable>
<ext:addGeneratedColumn tableName="kc_auth_root_session">
<ext:column name="entityversion" type="INTEGER" jsonColumn="metadata" jsonProperty="entityVersion"/>
<ext:column name="realmid" type="VARCHAR(36)" jsonColumn="metadata" jsonProperty="fRealmId"/>
<ext:column name="timestamp" type="INTEGER" jsonColumn="metadata" jsonProperty="fTimestamp"/>
<ext:column name="expiration" type="BIGINT" jsonColumn="metadata" jsonProperty="fExpiration"/>
</ext:addGeneratedColumn>
<createIndex tableName="kc_auth_root_session" indexName="auth_root_session_entityVersion">
<column name="entityversion"/>
</createIndex>
<createIndex tableName="kc_auth_root_session" indexName="auth_root_session_realmId_expiration">
<column name="realmid"/>
<column name="expiration"/>
</createIndex>
<createTable tableName="kc_auth_session">
<column name="id" type="UUID">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="fk_root" type="UUID">
<constraints foreignKeyName="auth_session_fk_root_fkey" references="kc_auth_root_session(id)" deleteCascade="true"/>
</column>
<column name="metadata" type="json"/>
</createTable>
<createIndex tableName="kc_auth_session" indexName="auth_session_fk_root">
<column name="fk_root"/>
</createIndex>
<ext:createJsonIndex tableName="kc_auth_session" indexName="auth_session_tabId">
<ext:column jsonColumn="metadata" jsonProperty="fTabId"/>
</ext:createJsonIndex>
</changeSet>
</databaseChangeLog>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2022 Red Hat, Inc. and/or its affiliates
and other contributors as indicated by the @author tags.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<!-- format of id of changelog file names: jpa-auth-sessions-changelog-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION}.xml -->
<include file="META-INF/auth-sessions/jpa-auth-sessions-changelog-1.xml"/>
</databaseChangeLog>

View file

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="keycloak-jpa-default">
<!--auth-sessions-->
<class>org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity</class>
<class>org.keycloak.models.map.storage.jpa.authSession.entity.JpaAuthenticationSessionEntity</class>
<!--client-scopes-->
<class>org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEntity</class>
<class>org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeAttributeEntity</class>

View file

@ -118,3 +118,7 @@ log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error
#log4j.logger.org.keycloak.services.clientpolicy=trace
#log4j.logger.org.keycloak.STACK_TRACE=trace
# Enable logs the SQL statements
#log4j.logger.org.hibernate.SQL=debug
#log4j.logger.org.hibernate.type=trace