KEYCLOAK-18565 JPA roles no-downtime store
This commit is contained in:
parent
213b1f5042
commit
7b89d151c1
23 changed files with 1090 additions and 71 deletions
|
@ -18,4 +18,5 @@ package org.keycloak.models.map.storage.jpa;
|
||||||
|
|
||||||
public interface Constants {
|
public interface Constants {
|
||||||
public static final Integer SUPPORTED_VERSION_CLIENT = 1;
|
public static final Integer SUPPORTED_VERSION_CLIENT = 1;
|
||||||
|
public static final Integer SUPPORTED_VERSION_ROLE = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
|
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.dblock.DBLockProvider;
|
import org.keycloak.models.dblock.DBLockProvider;
|
||||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||||
import org.keycloak.models.map.client.MapProtocolMapperEntityImpl;
|
import org.keycloak.models.map.client.MapProtocolMapperEntityImpl;
|
||||||
|
@ -50,6 +51,8 @@ import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||||
import org.keycloak.models.map.storage.MapStorageProviderFactory;
|
import org.keycloak.models.map.storage.MapStorageProviderFactory;
|
||||||
import org.keycloak.models.map.storage.jpa.client.JpaClientMapKeycloakTransaction;
|
import org.keycloak.models.map.storage.jpa.client.JpaClientMapKeycloakTransaction;
|
||||||
|
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 org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider;
|
||||||
import static org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider.Status.VALID;
|
import static org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider.Status.VALID;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
@ -70,11 +73,14 @@ public class JpaMapStorageProviderFactory implements
|
||||||
//client
|
//client
|
||||||
.constructor(JpaClientEntity.class, JpaClientEntity::new)
|
.constructor(JpaClientEntity.class, JpaClientEntity::new)
|
||||||
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
|
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
|
||||||
|
//role
|
||||||
|
.constructor(JpaRoleEntity.class, JpaRoleEntity::new)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private static final Map<Class<?>, Function<EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>();
|
private static final Map<Class<?>, Function<EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>();
|
||||||
static {
|
static {
|
||||||
MODEL_TO_TX.put(ClientModel.class, JpaClientMapKeycloakTransaction::new);
|
MODEL_TO_TX.put(ClientModel.class, JpaClientMapKeycloakTransaction::new);
|
||||||
|
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MapKeycloakTransaction createTransaction(Class<?> modelType, EntityManager em) {
|
public MapKeycloakTransaction createTransaction(Class<?> modelType, EntityManager em) {
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
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.ModelCriteriaBuilder;
|
||||||
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||||
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class containing methods common to all Jpa*ModelCriteriaBuilder implementations
|
||||||
|
*
|
||||||
|
* @param <E> Entity
|
||||||
|
* @param <M> Model
|
||||||
|
* @param <Self> specific implementation of this class
|
||||||
|
*/
|
||||||
|
public abstract class JpaModelCriteriaBuilder<E, M, Self extends ModelCriteriaBuilder<M, Self>> implements ModelCriteriaBuilder<M, Self> {
|
||||||
|
|
||||||
|
private final Function<BiFunction<CriteriaBuilder, Root<E>, Predicate>, Self> instantiator;
|
||||||
|
private BiFunction<CriteriaBuilder, Root<E>, Predicate> predicateFunc = null;
|
||||||
|
|
||||||
|
public JpaModelCriteriaBuilder(Function<BiFunction<CriteriaBuilder, Root<E>, Predicate>, Self> instantiator) {
|
||||||
|
this.instantiator = instantiator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaModelCriteriaBuilder(Function<BiFunction<CriteriaBuilder, Root<E>, Predicate>, Self> instantiator,
|
||||||
|
BiFunction<CriteriaBuilder, Root<E>, Predicate> predicateFunc) {
|
||||||
|
this.instantiator = instantiator;
|
||||||
|
this.predicateFunc = predicateFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void validateValue(Object[] value, SearchableModelField 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));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < expectedTypes.length; i++) {
|
||||||
|
if (! expectedTypes[i].isInstance(value[i])) {
|
||||||
|
throw new CriterionNotSupportedException(field, op, "Expected types: " + Arrays.toString(expectedTypes) +
|
||||||
|
" but got: " + Arrays.toString(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String convertToJson(Object input) {
|
||||||
|
try {
|
||||||
|
return JsonbType.MAPPER.writeValueAsString(input);
|
||||||
|
} catch (JsonProcessingException ex) {
|
||||||
|
throw new RuntimeException("Unable to write value as String.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Self and(Self... builders) {
|
||||||
|
return instantiator.apply((cb, root) -> cb.and(Stream.of(builders).map((Self b) -> {
|
||||||
|
if ( !(b instanceof JpaModelCriteriaBuilder)) throw new IllegalStateException("Invalid type of ModelCriteriaBuilder.");
|
||||||
|
return ((JpaModelCriteriaBuilder) b).getPredicateFunc().apply(cb, root);
|
||||||
|
}).toArray(Predicate[]::new)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Self or(Self... builders) {
|
||||||
|
return instantiator.apply((cb, root) -> cb.or(Stream.of(builders).map((Self b) -> {
|
||||||
|
if ( !(b instanceof JpaModelCriteriaBuilder)) throw new IllegalStateException("Invalid type of ModelCriteriaBuilder.");
|
||||||
|
return ((JpaModelCriteriaBuilder) b).getPredicateFunc().apply(cb, root);
|
||||||
|
}).toArray(Predicate[]::new)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Self not(Self builder) {
|
||||||
|
return instantiator.apply((cb, root) -> {
|
||||||
|
if ( !(builder instanceof JpaModelCriteriaBuilder)) throw new IllegalStateException("Invalid type of ModelCriteriaBuilder.");
|
||||||
|
BiFunction<CriteriaBuilder, Root<E>, Predicate> predFunc = ((JpaModelCriteriaBuilder) builder).getPredicateFunc();
|
||||||
|
return cb.not(predFunc.apply(cb, root));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public BiFunction<CriteriaBuilder, Root<E>, Predicate> getPredicateFunc() {
|
||||||
|
return predicateFunc;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,12 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models.map.storage.jpa.client;
|
package org.keycloak.models.map.storage.jpa.client;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
|
|
||||||
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientAttributeEntity;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.stream.Stream;
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
import javax.persistence.criteria.Join;
|
import javax.persistence.criteria.Join;
|
||||||
import javax.persistence.criteria.Predicate;
|
import javax.persistence.criteria.Predicate;
|
||||||
|
@ -30,38 +25,19 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientModel.SearchableFields;
|
import org.keycloak.models.ClientModel.SearchableFields;
|
||||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||||
import org.keycloak.models.map.storage.CriterionNotSupportedException;
|
import org.keycloak.models.map.storage.CriterionNotSupportedException;
|
||||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
|
||||||
|
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientAttributeEntity;
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||||
import org.keycloak.storage.SearchableModelField;
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
public class JpaClientModelCriteriaBuilder implements ModelCriteriaBuilder<ClientModel, JpaClientModelCriteriaBuilder> {
|
public class JpaClientModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaClientEntity, ClientModel, JpaClientModelCriteriaBuilder> {
|
||||||
|
|
||||||
private BiFunction<CriteriaBuilder, Root<JpaClientEntity>, Predicate> predicateFunc = null;
|
|
||||||
|
|
||||||
public JpaClientModelCriteriaBuilder() {
|
public JpaClientModelCriteriaBuilder() {
|
||||||
|
super(JpaClientModelCriteriaBuilder::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JpaClientModelCriteriaBuilder(BiFunction<CriteriaBuilder, Root<JpaClientEntity>, Predicate> predicateFunc) {
|
private JpaClientModelCriteriaBuilder(BiFunction<CriteriaBuilder, Root<JpaClientEntity>, Predicate> predicateFunc) {
|
||||||
this.predicateFunc = predicateFunc;
|
super(JpaClientModelCriteriaBuilder::new, predicateFunc);
|
||||||
}
|
|
||||||
|
|
||||||
private void validateValue(Object[] value, SearchableModelField field, Operator op, Class<?>... expectedTypes) {
|
|
||||||
if (value == null || expectedTypes == null || value.length != expectedTypes.length) {
|
|
||||||
throw new CriterionNotSupportedException(field, op, "Invalid argument: " + Arrays.toString(value));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < expectedTypes.length; i++) {
|
|
||||||
if (! expectedTypes[i].isInstance(value[i])) {
|
|
||||||
throw new CriterionNotSupportedException(field, op, "Expected types: " + Arrays.toString(expectedTypes) +
|
|
||||||
" but got: " + Arrays.toString(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String convertToJson(Object input) {
|
|
||||||
try {
|
|
||||||
return JsonbType.MAPPER.writeValueAsString(input);
|
|
||||||
} catch (JsonProcessingException ex) {
|
|
||||||
throw new RuntimeException("Unable to write value as String.", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -125,30 +101,4 @@ public class JpaClientModelCriteriaBuilder implements ModelCriteriaBuilder<Clien
|
||||||
throw new CriterionNotSupportedException(modelField, op);
|
throw new CriterionNotSupportedException(modelField, op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public JpaClientModelCriteriaBuilder and(JpaClientModelCriteriaBuilder... builders) {
|
|
||||||
return new JpaClientModelCriteriaBuilder(
|
|
||||||
(cb, root) -> cb.and(Stream.of(builders).map(b -> b.getPredicateFunc().apply(cb, root)).toArray(Predicate[]::new))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JpaClientModelCriteriaBuilder or(JpaClientModelCriteriaBuilder... builders) {
|
|
||||||
return new JpaClientModelCriteriaBuilder(
|
|
||||||
(cb, root) -> cb.or(Stream.of(builders).map(b -> b.getPredicateFunc().apply(cb, root)).toArray(Predicate[]::new))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JpaClientModelCriteriaBuilder not(JpaClientModelCriteriaBuilder builder) {
|
|
||||||
return new JpaClientModelCriteriaBuilder(
|
|
||||||
(cb, root) -> cb.not(builder.getPredicateFunc().apply(cb, root))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
BiFunction<CriteriaBuilder, Root<JpaClientEntity>, Predicate> getPredicateFunc() {
|
|
||||||
return predicateFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,8 @@ public class JpaClientDelegateProvider implements DelegateProvider<MapClientEnti
|
||||||
private JpaClientEntity delegate;
|
private JpaClientEntity delegate;
|
||||||
private final EntityManager em;
|
private final EntityManager em;
|
||||||
|
|
||||||
public JpaClientDelegateProvider(JpaClientEntity deledate, EntityManager em) {
|
public JpaClientDelegateProvider(JpaClientEntity delegate, EntityManager em) {
|
||||||
this.delegate = deledate;
|
this.delegate = delegate;
|
||||||
this.em = em;
|
this.em = em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,11 @@ import org.keycloak.models.map.common.DeepCloner;
|
||||||
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT;
|
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT;
|
||||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
@Entity
|
||||||
@Table(name = "client")
|
@Table(name = "client")
|
||||||
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||||
|
|
|
@ -25,12 +25,16 @@ import java.util.function.Function;
|
||||||
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientMetadata;
|
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientMetadata;
|
||||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientMigration;
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientMigration;
|
||||||
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT;
|
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT;
|
||||||
|
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_ROLE;
|
||||||
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRoleMigration;
|
||||||
|
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata;
|
||||||
|
|
||||||
public class JpaEntityMigration {
|
public class JpaEntityMigration {
|
||||||
|
|
||||||
static final Map<Class<?>, BiFunction<ObjectNode, Integer, ObjectNode>> MIGRATIONS = new HashMap<>();
|
static final Map<Class<?>, BiFunction<ObjectNode, Integer, ObjectNode>> MIGRATIONS = new HashMap<>();
|
||||||
static {
|
static {
|
||||||
MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, SUPPORTED_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS));
|
MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, SUPPORTED_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS));
|
||||||
|
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, SUPPORTED_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ObjectNode migrateTreeTo(int entityVersion, Integer supportedVersion, ObjectNode node, List<Function<ObjectNode, ObjectNode>> migrators) {
|
private static ObjectNode migrateTreeTo(int entityVersion, Integer supportedVersion, ObjectNode node, List<Function<ObjectNode, ObjectNode>> migrators) {
|
||||||
|
|
|
@ -76,6 +76,9 @@ public class JsonbType extends AbstractSingleColumnStandardBasicType<Object> imp
|
||||||
abstract class IgnoredMetadataFieldsMixIn {
|
abstract class IgnoredMetadataFieldsMixIn {
|
||||||
@JsonIgnore public abstract String getId();
|
@JsonIgnore public abstract String getId();
|
||||||
@JsonIgnore public abstract Map<String, List<String>> getAttributes();
|
@JsonIgnore public abstract Map<String, List<String>> getAttributes();
|
||||||
|
|
||||||
|
// roles: assumed it's true when getClient() != null, see AbstractRoleEntity.isClientRole()
|
||||||
|
@JsonIgnore public abstract Boolean isClientRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonbType() {
|
public JsonbType() {
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class JpaRoleMigration {
|
||||||
|
|
||||||
|
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
|
||||||
|
o -> o // no migration yet
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* 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.role;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.CriteriaDelete;
|
||||||
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
|
import javax.persistence.criteria.Order;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
import org.keycloak.connections.jpa.JpaKeycloakTransaction;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import static org.keycloak.models.jpa.PaginationUtils.paginateQuery;
|
||||||
|
import org.keycloak.models.map.common.StringKeyConvertor.UUIDKey;
|
||||||
|
import org.keycloak.models.map.role.MapRoleEntity;
|
||||||
|
import org.keycloak.models.map.role.MapRoleEntityDelegate;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.QueryParameters;
|
||||||
|
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_ROLE;
|
||||||
|
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
|
||||||
|
import org.keycloak.models.map.storage.jpa.role.delegate.JpaRoleDelegateProvider;
|
||||||
|
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
||||||
|
import static org.keycloak.utils.StreamsUtil.closing;
|
||||||
|
|
||||||
|
public class JpaRoleMapKeycloakTransaction extends JpaKeycloakTransaction implements MapKeycloakTransaction<MapRoleEntity, RoleModel> {
|
||||||
|
|
||||||
|
public JpaRoleMapKeycloakTransaction(EntityManager em) {
|
||||||
|
super(em);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapRoleEntity create(MapRoleEntity mapEntity) {
|
||||||
|
JpaRoleEntity jpaEntity = (JpaRoleEntity) CLONER.from(mapEntity);
|
||||||
|
if (mapEntity.getId() == null) {
|
||||||
|
jpaEntity.setId(UUIDKey.INSTANCE.yieldNewUniqueKey().toString());
|
||||||
|
}
|
||||||
|
jpaEntity.setEntityVersion(SUPPORTED_VERSION_ROLE);
|
||||||
|
em.persist(jpaEntity);
|
||||||
|
return jpaEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapRoleEntity read(String key) {
|
||||||
|
if (key == null) return null;
|
||||||
|
UUID uuid = UUIDKey.INSTANCE.fromStringSafe(key);
|
||||||
|
if (uuid == null) return null;
|
||||||
|
|
||||||
|
return em.find(JpaRoleEntity.class, uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<MapRoleEntity> read(QueryParameters<RoleModel> queryParameters) {
|
||||||
|
JpaRoleModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||||
|
.flashToModelCriteriaBuilder(new JpaRoleModelCriteriaBuilder());
|
||||||
|
|
||||||
|
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<JpaRoleEntity> query = cb.createQuery(JpaRoleEntity.class);
|
||||||
|
Root<JpaRoleEntity> root = query.from(JpaRoleEntity.class);
|
||||||
|
query.select(cb.construct(JpaRoleEntity.class,
|
||||||
|
root.get("id"),
|
||||||
|
root.get("entityVersion"),
|
||||||
|
root.get("realmId"),
|
||||||
|
root.get("clientId"),
|
||||||
|
root.get("name"),
|
||||||
|
root.get("description")
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!queryParameters.getOrderBy().isEmpty()) {
|
||||||
|
List<Order> orderByList = new LinkedList<>();
|
||||||
|
for (QueryParameters.OrderBy<RoleModel> order : queryParameters.getOrderBy()) {
|
||||||
|
switch (order.getOrder()) {
|
||||||
|
case ASCENDING:
|
||||||
|
orderByList.add(cb.asc(root.get(order.getModelField().getName())));
|
||||||
|
break;
|
||||||
|
case DESCENDING:
|
||||||
|
orderByList.add(cb.desc(root.get(order.getModelField().getName())));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Unknown ordering.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
query.orderBy(orderByList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mcb.getPredicateFunc() != null) query.where(mcb.getPredicateFunc().apply(cb, root));
|
||||||
|
|
||||||
|
return closing(
|
||||||
|
paginateQuery(em.createQuery(query), queryParameters.getOffset(), queryParameters.getLimit())
|
||||||
|
.getResultStream())
|
||||||
|
.map(r -> new MapRoleEntityDelegate(new JpaRoleDelegateProvider(r, em)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCount(QueryParameters<RoleModel> queryParameters) {
|
||||||
|
JpaRoleModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||||
|
.flashToModelCriteriaBuilder(new JpaRoleModelCriteriaBuilder());
|
||||||
|
|
||||||
|
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||||
|
|
||||||
|
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
|
||||||
|
Root<JpaRoleEntity> root = countQuery.from(JpaRoleEntity.class);
|
||||||
|
countQuery.select(cb.count(root));
|
||||||
|
|
||||||
|
if (mcb.getPredicateFunc() != null) countQuery.where(mcb.getPredicateFunc().apply(cb, root));
|
||||||
|
|
||||||
|
return em.createQuery(countQuery).getSingleResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete(String key) {
|
||||||
|
if (key == null) return false;
|
||||||
|
UUID uuid = UUIDKey.INSTANCE.fromStringSafe(key);
|
||||||
|
if (uuid == null) return false;
|
||||||
|
em.remove(em.getReference(JpaRoleEntity.class, uuid));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long delete(QueryParameters<RoleModel> queryParameters) {
|
||||||
|
JpaRoleModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||||
|
.flashToModelCriteriaBuilder(new JpaRoleModelCriteriaBuilder());
|
||||||
|
|
||||||
|
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||||
|
|
||||||
|
CriteriaDelete<JpaRoleEntity> deleteQuery = cb.createCriteriaDelete(JpaRoleEntity.class);
|
||||||
|
|
||||||
|
Root<JpaRoleEntity> root = deleteQuery.from(JpaRoleEntity.class);
|
||||||
|
|
||||||
|
if (mcb.getPredicateFunc() != null) deleteQuery.where(mcb.getPredicateFunc().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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* 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.role;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder.In;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.RoleModel.SearchableFields;
|
||||||
|
import org.keycloak.models.map.common.StringKeyConvertor.UUIDKey;
|
||||||
|
import org.keycloak.models.map.storage.CriterionNotSupportedException;
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||||
|
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
||||||
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
|
public class JpaRoleModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaRoleEntity, RoleModel, JpaRoleModelCriteriaBuilder> {
|
||||||
|
|
||||||
|
public JpaRoleModelCriteriaBuilder() {
|
||||||
|
super(JpaRoleModelCriteriaBuilder::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JpaRoleModelCriteriaBuilder(BiFunction<CriteriaBuilder, Root<JpaRoleEntity>, Predicate> predicateFunc) {
|
||||||
|
super(JpaRoleModelCriteriaBuilder::new, predicateFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public JpaRoleModelCriteriaBuilder compare(SearchableModelField<? super RoleModel> modelField, Operator op, Object... value) {
|
||||||
|
switch (op) {
|
||||||
|
case EQ:
|
||||||
|
if (modelField.equals(SearchableFields.REALM_ID) ||
|
||||||
|
modelField.equals(SearchableFields.CLIENT_ID) ||
|
||||||
|
modelField.equals(SearchableFields.NAME)) {
|
||||||
|
validateValue(value, modelField, op, String.class);
|
||||||
|
|
||||||
|
return new JpaRoleModelCriteriaBuilder((cb, root) ->
|
||||||
|
cb.equal(root.get(modelField.getName()), value[0])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new CriterionNotSupportedException(modelField, op);
|
||||||
|
}
|
||||||
|
case NE:
|
||||||
|
if (modelField.equals(SearchableFields.IS_CLIENT_ROLE)) {
|
||||||
|
|
||||||
|
validateValue(value, modelField, op, Boolean.class);
|
||||||
|
|
||||||
|
return new JpaRoleModelCriteriaBuilder((cb, root) ->
|
||||||
|
((Boolean) value[0]) ? cb.isNull(root.get("clientId")) : cb.isNotNull(root.get("clientId"))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new CriterionNotSupportedException(modelField, op);
|
||||||
|
}
|
||||||
|
case IN:
|
||||||
|
if (modelField.equals(SearchableFields.ID)) {
|
||||||
|
if (value == null || value.length == 0) throw new CriterionNotSupportedException(modelField, op);
|
||||||
|
|
||||||
|
final Collection<?> collectionValues;
|
||||||
|
if (value.length == 1) {
|
||||||
|
|
||||||
|
if (value[0] instanceof Object[]) {
|
||||||
|
collectionValues = Arrays.asList(value[0]);
|
||||||
|
} else if (value[0] instanceof Collection) {
|
||||||
|
collectionValues = (Collection) value[0];
|
||||||
|
} else if (value[0] instanceof Stream) {
|
||||||
|
try (Stream<?> str = ((Stream) value[0])) {
|
||||||
|
collectionValues = str.collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
collectionValues = Collections.singleton(value[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
collectionValues = new HashSet(Arrays.asList(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JpaRoleModelCriteriaBuilder((cb, root) -> {
|
||||||
|
In<UUID> in = cb.in(root.get("id"));
|
||||||
|
for (Object id : collectionValues) {
|
||||||
|
try {
|
||||||
|
in.value(UUIDKey.INSTANCE.fromString(Objects.toString(id, null)));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new CriterionNotSupportedException(modelField, op, id + " id is not in uuid format.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new CriterionNotSupportedException(modelField, op);
|
||||||
|
}
|
||||||
|
case ILIKE:
|
||||||
|
if (modelField.equals(SearchableFields.NAME) ||
|
||||||
|
modelField.equals(SearchableFields.DESCRIPTION)) {
|
||||||
|
|
||||||
|
validateValue(value, modelField, op, String.class);
|
||||||
|
|
||||||
|
return new JpaRoleModelCriteriaBuilder((cb, root) ->
|
||||||
|
cb.like(cb.lower(root.get(modelField.getName())), value[0].toString().toLowerCase())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new CriterionNotSupportedException(modelField, op);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new CriterionNotSupportedException(modelField, op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* 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.role.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.role.MapRoleEntity;
|
||||||
|
import org.keycloak.models.map.role.MapRoleEntityFields;
|
||||||
|
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
||||||
|
|
||||||
|
public class JpaRoleDelegateProvider implements DelegateProvider<MapRoleEntity> {
|
||||||
|
|
||||||
|
private JpaRoleEntity delegate;
|
||||||
|
private final EntityManager em;
|
||||||
|
|
||||||
|
public JpaRoleDelegateProvider(JpaRoleEntity delegate, EntityManager em) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.em = em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JpaRoleEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapRoleEntity>> field, Object... parameters) {
|
||||||
|
if (delegate.isMetadataInitialized()) return delegate;
|
||||||
|
if (isRead) {
|
||||||
|
if (field instanceof MapRoleEntityFields) {
|
||||||
|
switch ((MapRoleEntityFields) field) {
|
||||||
|
case ID:
|
||||||
|
case REALM_ID:
|
||||||
|
case CLIENT_ID:
|
||||||
|
case NAME:
|
||||||
|
case DESCRIPTION:
|
||||||
|
return delegate;
|
||||||
|
|
||||||
|
case ATTRIBUTES:
|
||||||
|
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<JpaRoleEntity> query = cb.createQuery(JpaRoleEntity.class);
|
||||||
|
Root<JpaRoleEntity> root = query.from(JpaRoleEntity.class);
|
||||||
|
root.fetch("attributes", JoinType.INNER);
|
||||||
|
query.select(root).where(cb.equal(root.get("id"), UUID.fromString(delegate.getId())));
|
||||||
|
|
||||||
|
delegate = em.createQuery(query).getSingleResult();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
delegate = em.find(JpaRoleEntity.class, UUID.fromString(delegate.getId()));
|
||||||
|
}
|
||||||
|
} else throw new IllegalStateException("Not a valid role field: " + field);
|
||||||
|
} else {
|
||||||
|
delegate = em.find(JpaRoleEntity.class, UUID.fromString(delegate.getId()));
|
||||||
|
}
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* 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.role.entity;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
|
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 org.hibernate.annotations.Nationalized;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "role_attribute")
|
||||||
|
public class JpaRoleAttributeEntity implements Serializable {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column
|
||||||
|
@GeneratedValue
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name="fk_role")
|
||||||
|
private JpaRoleEntity role;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Nationalized
|
||||||
|
@Column
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public JpaRoleAttributeEntity() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaRoleAttributeEntity(JpaRoleEntity role, String name, String value) {
|
||||||
|
this.role = role;
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaRoleEntity getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRole(JpaRoleEntity role) {
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof JpaRoleAttributeEntity)) return false;
|
||||||
|
JpaRoleAttributeEntity that = (JpaRoleAttributeEntity) obj;
|
||||||
|
return Objects.equals(getRole(), that.getRole()) &&
|
||||||
|
Objects.equals(getName(), that.getName()) &&
|
||||||
|
Objects.equals(getValue(), that.getValue());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,301 @@
|
||||||
|
/*
|
||||||
|
* 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.role.entity;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.persistence.Basic;
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
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.role.MapRoleEntity.AbstractRoleEntity;
|
||||||
|
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_ROLE;
|
||||||
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = "role", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "clientId", "name"})})
|
||||||
|
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||||
|
public class JpaRoleEntity extends AbstractRoleEntity implements Serializable {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
//used for implicit optimistic locking
|
||||||
|
@Version
|
||||||
|
@Column
|
||||||
|
private int version;
|
||||||
|
|
||||||
|
@Type(type = "jsonb")
|
||||||
|
@Column(columnDefinition = "jsonb")
|
||||||
|
private final JpaRoleMetadata metadata;
|
||||||
|
|
||||||
|
@Column(insertable = false, updatable = false)
|
||||||
|
@Basic(fetch = FetchType.LAZY)
|
||||||
|
private Integer entityVersion;
|
||||||
|
|
||||||
|
@Column(insertable = false, updatable = false)
|
||||||
|
@Basic(fetch = FetchType.LAZY)
|
||||||
|
private String realmId;
|
||||||
|
|
||||||
|
@Column(insertable = false, updatable = false)
|
||||||
|
@Basic(fetch = FetchType.LAZY)
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
@Column(insertable = false, updatable = false)
|
||||||
|
@Basic(fetch = FetchType.LAZY)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(insertable = false, updatable = false)
|
||||||
|
@Basic(fetch = FetchType.LAZY)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "role", cascade = CascadeType.PERSIST, orphanRemoval = true)
|
||||||
|
private final Set<JpaRoleAttributeEntity> attributes = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No-argument constructor, used by hibernate to instantiate entities.
|
||||||
|
*/
|
||||||
|
public JpaRoleEntity() {
|
||||||
|
this.metadata = new JpaRoleMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaRoleEntity(DeepCloner cloner) {
|
||||||
|
this.metadata = new JpaRoleMetadata(cloner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
|
||||||
|
* It is used to select role without metadata(json) field.
|
||||||
|
*/
|
||||||
|
public JpaRoleEntity(UUID id, Integer entityVersion, String realmId, String clientId, String name, String description) {
|
||||||
|
this.id = id;
|
||||||
|
this.entityVersion = entityVersion;
|
||||||
|
this.realmId = realmId;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
this.metadata = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMetadataInitialized() {
|
||||||
|
return metadata != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In case of any update on entity, we want to update the entityVerion
|
||||||
|
* to current one.
|
||||||
|
*/
|
||||||
|
private void checkEntityVersionForUpdate() {
|
||||||
|
Integer ev = getEntityVersion();
|
||||||
|
if (ev != null && ev < SUPPORTED_VERSION_ROLE) {
|
||||||
|
setEntityVersion(SUPPORTED_VERSION_ROLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getEntityVersion() {
|
||||||
|
if (isMetadataInitialized()) return metadata.getEntityVersion();
|
||||||
|
return entityVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntityVersion(Integer entityVersion) {
|
||||||
|
metadata.setEntityVersion(entityVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 String getClientId() {
|
||||||
|
if (isMetadataInitialized()) return metadata.getClientId();
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
if (isMetadataInitialized()) return metadata.getName();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
if (isMetadataInitialized()) return metadata.getDescription();
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientRole(Boolean clientRole) {
|
||||||
|
// intentionally do nothing, assuming the role is client-role when this.getClientId() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
checkEntityVersionForUpdate();
|
||||||
|
metadata.setRealmId(realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
checkEntityVersionForUpdate();
|
||||||
|
metadata.setClientId(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
checkEntityVersionForUpdate();
|
||||||
|
metadata.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDescription(String description) {
|
||||||
|
checkEntityVersionForUpdate();
|
||||||
|
metadata.setDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getCompositeRoles() {
|
||||||
|
return metadata.getCompositeRoles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCompositeRoles(Set<String> compositeRoles) {
|
||||||
|
checkEntityVersionForUpdate();
|
||||||
|
metadata.setCompositeRoles(compositeRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCompositeRole(String roleId) {
|
||||||
|
checkEntityVersionForUpdate();
|
||||||
|
metadata.addCompositeRole(roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeCompositeRole(String roleId) {
|
||||||
|
checkEntityVersionForUpdate();
|
||||||
|
metadata.removeCompositeRole(roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
Map<String, List<String>> result = new HashMap<>();
|
||||||
|
for (JpaRoleAttributeEntity attribute : attributes) {
|
||||||
|
List<String> values = result.getOrDefault(attribute.getName(), new LinkedList<>());
|
||||||
|
values.add(attribute.getValue());
|
||||||
|
result.put(attribute.getName(), values);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAttribute(String name) {
|
||||||
|
return attributes.stream()
|
||||||
|
.filter(a -> Objects.equals(a.getName(), name))
|
||||||
|
.map(JpaRoleAttributeEntity::getValue)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttributes(Map<String, List<String>> attributes) {
|
||||||
|
checkEntityVersionForUpdate();
|
||||||
|
for (Iterator<JpaRoleAttributeEntity> iterator = this.attributes.iterator(); iterator.hasNext();) {
|
||||||
|
JpaRoleAttributeEntity attr = iterator.next();
|
||||||
|
iterator.remove();
|
||||||
|
attr.setRole(null);
|
||||||
|
}
|
||||||
|
if (attributes != null) {
|
||||||
|
for (Map.Entry<String, List<String>> entry : attributes.entrySet()) {
|
||||||
|
setAttribute(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, List<String> values) {
|
||||||
|
checkEntityVersionForUpdate();
|
||||||
|
removeAttribute(name);
|
||||||
|
for (String value : values) {
|
||||||
|
JpaRoleAttributeEntity attribute = new JpaRoleAttributeEntity(this, name, value);
|
||||||
|
attributes.add(attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
checkEntityVersionForUpdate();
|
||||||
|
for (Iterator<JpaRoleAttributeEntity> iterator = attributes.iterator(); iterator.hasNext();) {
|
||||||
|
JpaRoleAttributeEntity attr = iterator.next();
|
||||||
|
if (Objects.equals(attr.getName(), name)) {
|
||||||
|
iterator.remove();
|
||||||
|
attr.setRole(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof JpaRoleEntity)) return false;
|
||||||
|
return Objects.equals(getId(), ((JpaRoleEntity) obj).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.storage.jpa.role.entity;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
|
import org.keycloak.models.map.role.MapRoleEntityImpl;
|
||||||
|
|
||||||
|
public class JpaRoleMetadata extends MapRoleEntityImpl implements Serializable {
|
||||||
|
|
||||||
|
public JpaRoleMetadata(DeepCloner cloner) {
|
||||||
|
super(cloner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaRoleMetadata() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer entityVersion;
|
||||||
|
|
||||||
|
public Integer getEntityVersion() {
|
||||||
|
return entityVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntityVersion(Integer entityVersion) {
|
||||||
|
this.entityVersion = entityVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -48,9 +48,11 @@ limitations under the License.
|
||||||
<column name="realmid"/>
|
<column name="realmid"/>
|
||||||
<column name="clientid"/>
|
<column name="clientid"/>
|
||||||
</createIndex>
|
</createIndex>
|
||||||
|
<!--
|
||||||
<ext:createJsonIndex tableName="client" indexName="client_scopeMappings">
|
<ext:createJsonIndex tableName="client" indexName="client_scopeMappings">
|
||||||
<ext:column jsonColumn="metadata" jsonProperty="fScopeMappings"/>
|
<ext:column jsonColumn="metadata" jsonProperty="fScopeMappings"/>
|
||||||
</ext:createJsonIndex>
|
</ext:createJsonIndex>
|
||||||
|
-->
|
||||||
|
|
||||||
<createTable tableName="client_attribute">
|
<createTable tableName="client_attribute">
|
||||||
<column name="id" type="UUID">
|
<column name="id" type="UUID">
|
||||||
|
|
|
@ -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-roles-changelog-${org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_ROLE}.xml -->
|
||||||
|
<include file="META-INF/roles/jpa-roles-changelog-1.xml"/>
|
||||||
|
</databaseChangeLog>
|
|
@ -1,7 +1,11 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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 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">
|
<persistence-unit name="keycloak-jpa-default">
|
||||||
|
<!--clients-->
|
||||||
<class>org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity</class>
|
<class>org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity</class>
|
||||||
<class>org.keycloak.models.map.storage.jpa.client.entity.JpaClientAttributeEntity</class>
|
<class>org.keycloak.models.map.storage.jpa.client.entity.JpaClientAttributeEntity</class>
|
||||||
|
<!--roles-->
|
||||||
|
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity</class>
|
||||||
|
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleAttributeEntity</class>
|
||||||
</persistence-unit>
|
</persistence-unit>
|
||||||
</persistence>
|
</persistence>
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?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: roles-${org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_ROLE} -->
|
||||||
|
<changeSet author="keycloak" id="roles-1">
|
||||||
|
|
||||||
|
<createTable tableName="role">
|
||||||
|
<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="role">
|
||||||
|
<ext:column name="entityversion" type="INTEGER" jsonColumn="metadata" jsonProperty="entityVersion"/>
|
||||||
|
<ext:column name="realmid" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fRealmId"/>
|
||||||
|
<ext:column name="name" type="VARCHAR(36)" jsonColumn="metadata" jsonProperty="fName"/>
|
||||||
|
<ext:column name="clientid" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fClientId"/>
|
||||||
|
<ext:column name="description" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fDescription"/>
|
||||||
|
</ext:addGeneratedColumn>
|
||||||
|
<createIndex tableName="role" indexName="role_entityVersion">
|
||||||
|
<column name="entityversion"/>
|
||||||
|
</createIndex>
|
||||||
|
<createIndex tableName="role" indexName="role_realmId_clientid_name" unique="true">
|
||||||
|
<column name="realmid"/>
|
||||||
|
<column name="clientid"/>
|
||||||
|
<column name="name"/>
|
||||||
|
</createIndex>
|
||||||
|
<!--
|
||||||
|
<ext:createJsonIndex tableName="role" indexName="role_gin">
|
||||||
|
<ext:column jsonColumn="metadata" jsonProperty="fName"/>
|
||||||
|
<ext:column jsonColumn="metadata" jsonProperty="fDescription"/>
|
||||||
|
</ext:createJsonIndex>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<createTable tableName="role_attribute">
|
||||||
|
<column name="id" type="UUID">
|
||||||
|
<constraints primaryKey="true" nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="fk_role" type="UUID">
|
||||||
|
<constraints foreignKeyName="role_attr_fk_role_fkey" references="role(id)" deleteCascade="true"/>
|
||||||
|
</column>
|
||||||
|
<column name="name" type="VARCHAR(255)"/>
|
||||||
|
<column name="value" type="text"/>
|
||||||
|
</createTable>
|
||||||
|
<createIndex tableName="role_attribute" indexName="role_attr_fk_role">
|
||||||
|
<column name="fk_role"/>
|
||||||
|
</createIndex>
|
||||||
|
<createIndex tableName="role_attribute" indexName="role_attr_name_value">
|
||||||
|
<column name="name"/>
|
||||||
|
<column name="VALUE(255)" valueComputed="VALUE(255)"/>
|
||||||
|
</createIndex>
|
||||||
|
<modifySql dbms="postgresql">
|
||||||
|
<replace replace="VALUE(255)" with="(value::varchar(250))"/>
|
||||||
|
</modifySql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
|
@ -16,8 +16,6 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models.map.role;
|
package org.keycloak.models.map.role;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
||||||
import org.keycloak.models.map.common.AbstractEntity;
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
@ -47,10 +45,10 @@ public interface MapRoleEntity extends AbstractEntity, UpdatableEntity, EntityWi
|
||||||
this.updated |= id != null;
|
this.updated |= id != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean isClientRole() {
|
||||||
|
return getClientId() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
default Boolean isComposite() {
|
|
||||||
return ! (getCompositeRoles() == null || getCompositeRoles().isEmpty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Boolean isClientRole();
|
Boolean isClientRole();
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class MapRoleProvider implements RoleProvider {
|
||||||
@Override
|
@Override
|
||||||
public RoleModel addRealmRole(RealmModel realm, String id, String name) {
|
public RoleModel addRealmRole(RealmModel realm, String id, String name) {
|
||||||
if (getRealmRole(realm, name) != null) {
|
if (getRealmRole(realm, name) != null) {
|
||||||
throw new ModelDuplicateException("Role exists: " + id);
|
throw new ModelDuplicateException("Role with the same name exists: " + name + " for realm " + realm.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.tracef("addRealmRole(%s, %s, %s)%s", realm, id, name, getShortStackTrace());
|
LOG.tracef("addRealmRole(%s, %s, %s)%s", realm, id, name, getShortStackTrace());
|
||||||
|
@ -118,7 +118,7 @@ public class MapRoleProvider implements RoleProvider {
|
||||||
@Override
|
@Override
|
||||||
public RoleModel addClientRole(ClientModel client, String id, String name) {
|
public RoleModel addClientRole(ClientModel client, String id, String name) {
|
||||||
if (getClientRole(client, name) != null) {
|
if (getClientRole(client, name) != null) {
|
||||||
throw new ModelDuplicateException("Role exists: " + id);
|
throw new ModelDuplicateException("Role with the same name exists: " + name + " for client " + client.getClientId());
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.tracef("addClientRole(%s, %s, %s)%s", client, id, name, getShortStackTrace());
|
LOG.tracef("addClientRole(%s, %s, %s)%s", client, id, name, getShortStackTrace());
|
||||||
|
|
|
@ -122,7 +122,6 @@ public class MapFieldPredicates {
|
||||||
put(ROLE_PREDICATES, RoleModel.SearchableFields.DESCRIPTION, MapRoleEntity::getDescription);
|
put(ROLE_PREDICATES, RoleModel.SearchableFields.DESCRIPTION, MapRoleEntity::getDescription);
|
||||||
put(ROLE_PREDICATES, RoleModel.SearchableFields.NAME, MapRoleEntity::getName);
|
put(ROLE_PREDICATES, RoleModel.SearchableFields.NAME, MapRoleEntity::getName);
|
||||||
put(ROLE_PREDICATES, RoleModel.SearchableFields.IS_CLIENT_ROLE, MapRoleEntity::isClientRole);
|
put(ROLE_PREDICATES, RoleModel.SearchableFields.IS_CLIENT_ROLE, MapRoleEntity::isClientRole);
|
||||||
put(ROLE_PREDICATES, RoleModel.SearchableFields.IS_COMPOSITE_ROLE, MapRoleEntity::isComposite);
|
|
||||||
|
|
||||||
put(USER_PREDICATES, UserModel.SearchableFields.REALM_ID, MapUserEntity::getRealmId);
|
put(USER_PREDICATES, UserModel.SearchableFields.REALM_ID, MapUserEntity::getRealmId);
|
||||||
put(USER_PREDICATES, UserModel.SearchableFields.USERNAME, MapUserEntity::getUsername);
|
put(USER_PREDICATES, UserModel.SearchableFields.USERNAME, MapUserEntity::getUsername);
|
||||||
|
|
|
@ -38,7 +38,6 @@ public interface RoleModel {
|
||||||
public static final SearchableModelField<RoleModel> NAME = new SearchableModelField<>("name", String.class);
|
public static final SearchableModelField<RoleModel> NAME = new SearchableModelField<>("name", String.class);
|
||||||
public static final SearchableModelField<RoleModel> DESCRIPTION = new SearchableModelField<>("description", String.class);
|
public static final SearchableModelField<RoleModel> DESCRIPTION = new SearchableModelField<>("description", String.class);
|
||||||
public static final SearchableModelField<RoleModel> IS_CLIENT_ROLE = new SearchableModelField<>("isClientRole", Boolean.class);
|
public static final SearchableModelField<RoleModel> IS_CLIENT_ROLE = new SearchableModelField<>("isClientRole", Boolean.class);
|
||||||
public static final SearchableModelField<RoleModel> IS_COMPOSITE_ROLE = new SearchableModelField<>("isCompositeRole", Boolean.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
|
|
Loading…
Reference in a new issue