Realms Map JPA implementation

Closes #9661
This commit is contained in:
Stefan Guilhen 2022-03-23 11:24:48 -03:00 committed by Hynek Mlnařík
parent 15b2f8e26d
commit ae90b232ff
20 changed files with 1890 additions and 9 deletions

View file

@ -21,5 +21,6 @@ public interface Constants {
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;
public static final Integer CURRENT_SCHEMA_VERSION_REALM = 1;
public static final Integer CURRENT_SCHEMA_VERSION_ROLE = 1;
}

View file

@ -90,6 +90,7 @@ public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E exte
CriteriaQuery<RE> query = cb.createQuery(entityType);
Root<RE> root = query.from(entityType);
query.select(selectCbConstruct(cb, root));
if (mcb.isDistinct()) query.distinct(true);
//ordering
if (!queryParameters.getOrderBy().isEmpty()) {

View file

@ -27,6 +27,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
@ -51,14 +52,34 @@ import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.dblock.DBLockProvider;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntityImpl;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntityImpl;
import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntityImpl;
import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntityImpl;
import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity;
import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntityImpl;
import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity;
import org.keycloak.models.map.realm.entity.MapIdentityProviderEntityImpl;
import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity;
import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntityImpl;
import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity;
import org.keycloak.models.map.realm.entity.MapOTPPolicyEntityImpl;
import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity;
import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntityImpl;
import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity;
import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntityImpl;
import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity;
import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntityImpl;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
@ -66,20 +87,25 @@ import org.keycloak.models.map.storage.jpa.authSession.JpaRootAuthenticationSess
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.client.entity.JpaClientEntity;
import org.keycloak.models.map.storage.jpa.clientscope.JpaClientScopeMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEntity;
import org.keycloak.models.map.storage.jpa.group.JpaGroupMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity;
import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaEntityVersionListener;
import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaOptimisticLockingListener;
import org.keycloak.models.map.storage.jpa.realm.JpaRealmMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentEntity;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity;
import org.keycloak.models.map.storage.jpa.role.JpaRoleMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
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;
import static org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider.Status.VALID;
public class JpaMapStorageProviderFactory implements
AmphibianProviderFactory<MapStorageProvider>,
MapStorageProviderFactory,
@ -103,6 +129,19 @@ public class JpaMapStorageProviderFactory implements
.constructor(JpaClientScopeEntity.class, JpaClientScopeEntity::new)
//group
.constructor(JpaGroupEntity.class, JpaGroupEntity::new)
// realm
.constructor(JpaRealmEntity.class, JpaRealmEntity::new)
.constructor(JpaComponentEntity.class, JpaComponentEntity::new)
.constructor(MapAuthenticationExecutionEntity.class, MapAuthenticationExecutionEntityImpl::new)
.constructor(MapAuthenticationFlowEntity.class, MapAuthenticationFlowEntityImpl::new)
.constructor(MapAuthenticatorConfigEntity.class, MapAuthenticatorConfigEntityImpl::new)
.constructor(MapClientInitialAccessEntity.class, MapClientInitialAccessEntityImpl::new)
.constructor(MapIdentityProviderEntity.class, MapIdentityProviderEntityImpl::new)
.constructor(MapIdentityProviderMapperEntity.class, MapIdentityProviderMapperEntityImpl::new)
.constructor(MapOTPPolicyEntity.class, MapOTPPolicyEntityImpl::new)
.constructor(MapRequiredActionProviderEntity.class, MapRequiredActionProviderEntityImpl::new)
.constructor(MapRequiredCredentialEntity.class, MapRequiredCredentialEntityImpl::new)
.constructor(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl::new)
//role
.constructor(JpaRoleEntity.class, JpaRoleEntity::new)
.build();
@ -113,6 +152,7 @@ public class JpaMapStorageProviderFactory implements
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(RealmModel.class, JpaRealmMapKeycloakTransaction::new);
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
}

View file

@ -40,6 +40,7 @@ public abstract class JpaModelCriteriaBuilder<E, M, Self extends JpaModelCriteri
private final Function<BiFunction<CriteriaBuilder, Root<E>, Predicate>, Self> instantiator;
private BiFunction<CriteriaBuilder, Root<E>, Predicate> predicateFunc = null;
private boolean isDistinct = false;
public JpaModelCriteriaBuilder(Function<BiFunction<CriteriaBuilder, Root<E>, Predicate>, Self> instantiator) {
this.instantiator = instantiator;
@ -51,6 +52,14 @@ public abstract class JpaModelCriteriaBuilder<E, M, Self extends JpaModelCriteri
this.predicateFunc = predicateFunc;
}
public JpaModelCriteriaBuilder(Function<BiFunction<CriteriaBuilder, Root<E>, Predicate>, Self> instantiator,
BiFunction<CriteriaBuilder, Root<E>, Predicate> predicateFunc,
boolean isDistinct) {
this.instantiator = instantiator;
this.predicateFunc = predicateFunc;
this.isDistinct = isDistinct;
}
protected void validateValue(Object[] value, SearchableModelField<? super M> field, ModelCriteriaBuilder.Operator op, Class<?>... expectedTypes) {
if (value == null || expectedTypes == null || value.length != expectedTypes.length) {
throw new CriterionNotSupportedException(field, op, "Invalid argument: " + Arrays.toString(value));
@ -91,4 +100,8 @@ public abstract class JpaModelCriteriaBuilder<E, M, Self extends JpaModelCriteri
public BiFunction<CriteriaBuilder, Root<E>, Predicate> getPredicateFunc() {
return predicateFunc;
}
public boolean isDistinct() {
return this.isDistinct;
}
}

View file

@ -22,11 +22,6 @@ 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;
@ -35,12 +30,21 @@ 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.JpaComponentMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaGroupMigration;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRealmMigration;
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.realm.entity.JpaComponentMetadata;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmMetadata;
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata;
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_REALM;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
public class JpaEntityMigration {
@ -50,7 +54,9 @@ public class JpaEntityMigration {
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(JpaComponentMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaComponentMigration.MIGRATORS));
MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS));
MIGRATIONS.put(JpaRealmMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaRealmMigration.MIGRATORS));
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
}

View file

@ -56,6 +56,27 @@ import org.keycloak.models.map.common.EntityWithAttributes;
import org.keycloak.models.map.common.Serialization.IgnoreUpdatedMixIn;
import org.keycloak.models.map.common.Serialization.IgnoredTypeMixIn;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntityImpl;
import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntityImpl;
import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntityImpl;
import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity;
import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntityImpl;
import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity;
import org.keycloak.models.map.realm.entity.MapIdentityProviderEntityImpl;
import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity;
import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntityImpl;
import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity;
import org.keycloak.models.map.realm.entity.MapOTPPolicyEntityImpl;
import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity;
import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntityImpl;
import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity;
import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntityImpl;
import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity;
import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntityImpl;
import static org.keycloak.models.map.storage.jpa.hibernate.jsonb.JpaEntityMigration.MIGRATIONS;
public class JsonbType extends AbstractSingleColumnStandardBasicType<Object> implements DynamicParameterizedType {
@ -68,7 +89,18 @@ public class JsonbType extends AbstractSingleColumnStandardBasicType<Object> imp
.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.activateDefaultTyping(new LaissezFaireSubTypeValidator(), ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT, JsonTypeInfo.As.PROPERTY)
.registerModule(new SimpleModule().addAbstractTypeMapping(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl.class))
.registerModule(new SimpleModule().addAbstractTypeMapping(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl.class)
// realm abstract type mappings
.addAbstractTypeMapping(MapAuthenticationExecutionEntity.class, MapAuthenticationExecutionEntityImpl.class)
.addAbstractTypeMapping(MapAuthenticationFlowEntity.class, MapAuthenticationFlowEntityImpl.class)
.addAbstractTypeMapping(MapAuthenticatorConfigEntity.class, MapAuthenticatorConfigEntityImpl.class)
.addAbstractTypeMapping(MapClientInitialAccessEntity.class, MapClientInitialAccessEntityImpl.class)
.addAbstractTypeMapping(MapIdentityProviderEntity.class, MapIdentityProviderEntityImpl.class)
.addAbstractTypeMapping(MapIdentityProviderMapperEntity.class, MapIdentityProviderMapperEntityImpl.class)
.addAbstractTypeMapping(MapOTPPolicyEntity.class, MapOTPPolicyEntityImpl.class)
.addAbstractTypeMapping(MapRequiredActionProviderEntity.class, MapRequiredActionProviderEntityImpl.class)
.addAbstractTypeMapping(MapRequiredCredentialEntity.class, MapRequiredCredentialEntityImpl.class)
.addAbstractTypeMapping(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl.class))
.addMixIn(UpdatableEntity.class, IgnoreUpdatedMixIn.class)
.addMixIn(DeepCloner.class, IgnoredTypeMixIn.class)
.addMixIn(EntityWithAttributes.class, IgnoredMetadataFieldsMixIn.class);

View file

@ -0,0 +1,35 @@
/*
* 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;
/**
* Migration functions for components.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaComponentMigration {
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
o -> o // no migration yet
);
}

View file

@ -0,0 +1,35 @@
/*
* 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;
/**
* Migration functions for realms.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaRealmMigration {
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
o -> o // no migration yet
);
}

View file

@ -0,0 +1,73 @@
/*
* 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.realm;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.RealmModel;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.realm.MapRealmEntityDelegate;
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.realm.delegate.JpaRealmDelegateProvider;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM;
/**
* A {@link org.keycloak.models.map.storage.MapKeycloakTransaction} implementation for realm entities.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaRealmMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaRealmEntity, MapRealmEntity, RealmModel> {
public JpaRealmMapKeycloakTransaction(final EntityManager em) {
super(JpaRealmEntity.class, em);
}
@Override
protected Selection<? extends JpaRealmEntity> selectCbConstruct(CriteriaBuilder cb, Root<JpaRealmEntity> root) {
return cb.construct(JpaRealmEntity.class,
root.get("id"),
root.get("version"),
root.get("entityVersion"),
root.get("name"),
root.get("displayName"),
root.get("displayNameHtml"),
root.get("enabled")
);
}
@Override
protected void setEntityVersion(JpaRootEntity entity) {
entity.setEntityVersion(CURRENT_SCHEMA_VERSION_REALM);
}
@Override
protected JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() {
return new JpaRealmModelCriteriaBuilder();
}
@Override
protected MapRealmEntity mapToEntityDelegate(JpaRealmEntity original) {
return new MapRealmEntityDelegate(new JpaRealmDelegateProvider(original, em));
}
}

View file

@ -0,0 +1,82 @@
/*
* 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.realm;
import java.util.function.BiFunction;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
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.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity;
import org.keycloak.storage.SearchableModelField;
/**
* A {@link JpaModelCriteriaBuilder} implementation for realms.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaRealmModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaRealmEntity, RealmModel, JpaRealmModelCriteriaBuilder> {
public JpaRealmModelCriteriaBuilder() {
super(JpaRealmModelCriteriaBuilder::new);
}
private JpaRealmModelCriteriaBuilder(final BiFunction<CriteriaBuilder, Root<JpaRealmEntity>, Predicate> predicateFunc) {
super(JpaRealmModelCriteriaBuilder::new, predicateFunc);
}
private JpaRealmModelCriteriaBuilder(final BiFunction<CriteriaBuilder, Root<JpaRealmEntity>, Predicate> predicateFUnc,
final boolean isDistinct) {
super(JpaRealmModelCriteriaBuilder::new, predicateFUnc, isDistinct);
}
@Override
public JpaRealmModelCriteriaBuilder compare(SearchableModelField<? super RealmModel> modelField, Operator op, Object... value) {
switch(op) {
case EQ:
if (modelField.equals(RealmModel.SearchableFields.NAME)) {
validateValue(value, modelField, op, String.class);
return new JpaRealmModelCriteriaBuilder((cb, root) ->
cb.equal(root.get(modelField.getName()), value[0])
);
} else if (modelField.equals(RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE)) {
validateValue(value, modelField, op, String.class);
return new JpaRealmModelCriteriaBuilder((cb, root) ->
cb.equal(root.join("components").get("providerType"), value[0]), true);
} else {
throw new CriterionNotSupportedException(modelField, op);
}
case EXISTS:
if (modelField.equals(RealmModel.SearchableFields.CLIENT_INITIAL_ACCESS)) {
return new JpaRealmModelCriteriaBuilder((cb, root) ->
cb.isTrue(cb.function("->", JsonbType.class, root.get("metadata"),
cb.literal("fClientInitialAccesses")).isNotNull())
);
} else {
throw new CriterionNotSupportedException(modelField, op);
}
default:
throw new CriterionNotSupportedException(modelField, op);
}
}
}

View file

@ -0,0 +1,81 @@
/*
* 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.realm.delegate;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Root;
import org.keycloak.models.map.common.EntityField;
import org.keycloak.models.map.common.delegate.DelegateProvider;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.realm.MapRealmEntityFields;
import org.keycloak.models.map.storage.jpa.JpaDelegateProvider;
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity;
/**
* A {@link DelegateProvider} implementation for {@link JpaRealmEntity}.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaRealmDelegateProvider extends JpaDelegateProvider<JpaRealmEntity> implements DelegateProvider<MapRealmEntity> {
private final EntityManager em;
public JpaRealmDelegateProvider(final JpaRealmEntity delegate, final EntityManager em) {
super(delegate);
this.em = em;
}
@Override
public MapRealmEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapRealmEntity>> field, Object... parameters) {
if (getDelegate().isMetadataInitialized()) return getDelegate();
if (isRead) {
if (field instanceof MapRealmEntityFields) {
switch ((MapRealmEntityFields) field) {
case ID:
case NAME:
case DISPLAY_NAME:
case DISPLAY_NAME_HTML:
case ENABLED:
return getDelegate();
case ATTRIBUTES:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<JpaRealmEntity> query = cb.createQuery(JpaRealmEntity.class);
Root<JpaRealmEntity> root = query.from(JpaRealmEntity.class);
root.fetch("attributes", JoinType.LEFT);
query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId())));
setDelegate(em.createQuery(query).getSingleResult());
break;
default:
setDelegate(em.find(JpaRealmEntity.class, UUID.fromString(getDelegate().getId())));
}
} else {
throw new IllegalStateException("Not a valid realm field: " + field);
}
} else {
setDelegate(em.find(JpaRealmEntity.class, UUID.fromString(getDelegate().getId())));
}
return getDelegate(); }
}

View file

@ -0,0 +1,198 @@
/*
* 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.realm.entity;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import javax.persistence.Basic;
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.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.UpdatableEntity;
import org.keycloak.models.map.realm.entity.MapComponentEntity;
import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM;
/**
* JPA {@link MapComponentEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)}
* to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
@Entity
@Table(name = "kc_component")
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
public class JpaComponentEntity extends UpdatableEntity.Impl implements MapComponentEntity, JpaRootVersionedEntity {
@Id
@Column
private UUID id;
//used for implicit optimistic locking
@Version
@Column
private int version;
@Column(insertable = false, updatable = false)
@Basic(fetch = FetchType.LAZY)
private String providerType;
@Type(type = "jsonb")
@Column(columnDefinition = "jsonb")
private final JpaComponentMetadata metadata;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="fk_root")
private JpaRealmEntity root;
/**
* No-argument constructor, used by hibernate to instantiate entities.
*/
public JpaComponentEntity() {
this.metadata = new JpaComponentMetadata();
}
public JpaComponentEntity(DeepCloner cloner) {
this.metadata = new JpaComponentMetadata(cloner);
}
public void setParent(JpaRealmEntity root) {
this.root = root;
}
public boolean isMetadataInitialized() {
return this.metadata != null;
}
@Override
public String getId() {
return this.id == null ? null : this.id.toString();
}
@Override
public void setId(String id) {
this.id = id == null ? null : UUID.fromString(id);
}
@Override
public Integer getEntityVersion() {
return this.metadata.getEntityVersion();
}
@Override
public void setEntityVersion(Integer version) {
this.metadata.setEntityVersion(version);
}
@Override
public int getVersion() {
return version;
}
@Override
public Integer getCurrentSchemaVersion() {
return CURRENT_SCHEMA_VERSION_REALM;
}
@Override
public String getName() {
return this.metadata.getName();
}
@Override
public void setName(String name) {
this.metadata.setName(name);
}
@Override
public String getProviderId() {
return this.metadata.getProviderId();
}
@Override
public void setProviderId(String providerId) {
this.metadata.setProviderId(providerId);
}
@Override
public String getProviderType() {
if (this.isMetadataInitialized()) return this.metadata.getProviderType();
return this.providerType;
}
@Override
public void setProviderType(String providerType) {
this.metadata.setProviderType(providerType);
}
@Override
public String getSubType() {
return this.metadata.getSubType();
}
@Override
public void setSubType(String subType) {
this.metadata.setSubType(subType);
}
@Override
public String getParentId() {
return this.metadata.getParentId();
}
@Override
public void setParentId(String parentId) {
this.metadata.setParentId(parentId);
}
@Override
public Map<String, List<String>> getConfig() {
return this.metadata.getConfig();
}
@Override
public void setConfig(Map<String, List<String>> config) {
this.metadata.setConfig(config);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof JpaComponentEntity)) return false;
return Objects.equals(getId(), ((JpaComponentEntity) obj).getId());
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.map.storage.jpa.realm.entity;
import java.io.Serializable;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.realm.entity.MapComponentEntityImpl;
/**
* Class that contains all the component metadata that is written as JSON into the database.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaComponentMetadata extends MapComponentEntityImpl implements Serializable {
public JpaComponentMetadata(DeepCloner cloner) {
super(cloner);
}
public JpaComponentMetadata() {
super();
}
private Integer entityVersion;
public Integer getEntityVersion() {
return entityVersion;
}
public void setEntityVersion(Integer entityVersion) {
this.entityVersion = entityVersion;
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.realm.entity;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.keycloak.models.map.storage.jpa.JpaAttributeEntity;
/**
* JPA implementation for realm attributes. This entity represents a realm attribute and has a many-to-one relationship
* with the realm entity.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
@Entity
@Table(name = "kc_realm_attribute")
public class JpaRealmAttributeEntity extends JpaAttributeEntity<JpaRealmEntity> {
public JpaRealmAttributeEntity() {
}
public JpaRealmAttributeEntity(final JpaRealmEntity root, final String name, final String value) {
super(root, name, value);
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.map.storage.jpa.realm.entity;
import java.io.Serializable;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.realm.MapRealmEntityImpl;
/**
* Class that contains all the realm metadata that is written as JSON into the database.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaRealmMetadata extends MapRealmEntityImpl implements Serializable {
public JpaRealmMetadata() {
super();
}
public JpaRealmMetadata(final DeepCloner cloner) {
super(cloner);
}
private Integer entityVersion;
public Integer getEntityVersion() {
return entityVersion;
}
public void setEntityVersion(Integer entityVersion) {
this.entityVersion = entityVersion;
}
}

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-realms-changelog-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM}.xml -->
<include file="META-INF/realms/jpa-realms-changelog-1.xml"/>
</databaseChangeLog>

View file

@ -13,6 +13,10 @@
<!--groups-->
<class>org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity</class>
<class>org.keycloak.models.map.storage.jpa.group.entity.JpaGroupAttributeEntity</class>
<!-- realms -->
<class>org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentEntity</class>
<class>org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity</class>
<class>org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmAttributeEntity</class>
<!--roles-->
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity</class>
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleAttributeEntity</class>

View file

@ -0,0 +1,98 @@
<?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: realms-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM} -->
<changeSet author="keycloak" id="realms-1">
<createTable tableName="kc_realm">
<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_realm">
<ext:column name="entityversion" type="INTEGER" jsonColumn="metadata" jsonProperty="entityVersion"/>
<ext:column name="name" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fName"/>
<ext:column name="displayname" type="TEXT" jsonColumn="metadata" jsonProperty="fDisplayName"/>
<ext:column name="displaynamehtml" type="TEXT" jsonColumn="metadata" jsonProperty="fDisplayNameHtml"/>
<ext:column name="enabled" type="BOOLEAN" jsonColumn="metadata" jsonProperty="fEnabled"/>
</ext:addGeneratedColumn>
<createIndex tableName="kc_realm" indexName="realm_entityVersion">
<column name="entityversion"/>
</createIndex>
<createIndex tableName="kc_realm" indexName="realm_name" unique="true">
<column name="name"/>
</createIndex>
<ext:createJsonIndex tableName="kc_realm" indexName="realm_clientInitialAccesses">
<ext:column jsonColumn="metadata" jsonProperty="fClientInitialAccesses"/>
</ext:createJsonIndex>
<createTable tableName="kc_component">
<column name="id" type="UUID">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="version" type="INTEGER" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="fk_root" type="UUID">
<constraints foreignKeyName="component_fk_root_fkey" references="kc_realm(id)" deleteCascade="true"/>
</column>
<column name="metadata" type="json"/>
</createTable>
<ext:addGeneratedColumn tableName="kc_component">
<ext:column name="providertype" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fProviderType"/>
</ext:addGeneratedColumn>
<createIndex tableName="kc_component" indexName="component_fk_root">
<column name="fk_root"/>
</createIndex>
<createIndex tableName="kc_component" indexName="component_componentType">
<column name="providertype"/>
</createIndex>
<createTable tableName="kc_realm_attribute">
<column name="id" type="UUID">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="fk_root" type="UUID">
<constraints foreignKeyName="realm_attr_fk_root_fkey" references="kc_realm(id)" deleteCascade="true"/>
</column>
<column name="name" type="VARCHAR(255)"/>
<column name="value" type="TEXT"/>
</createTable>
<createIndex tableName="kc_realm_attribute" indexName="realm_attr_fk_root">
<column name="fk_root"/>
</createIndex>
<createIndex tableName="kc_realm_attribute" indexName="realm_attr_name_value">
<column name="name"/>
<column name="VALUE(255)" valueComputed="VALUE(255)"/>
</createIndex>
<modifySql dbms="postgresql,cockroachdb">
<replace replace="VALUE(255)" with="(value::varchar(250))"/>
</modifySql>
</changeSet>
</databaseChangeLog>

View file

@ -0,0 +1,2 @@
package org.keycloak.utils;public class UuidValidator {
}