parent
5d3fbbb158
commit
589606b1c1
25 changed files with 800 additions and 44 deletions
|
@ -19,5 +19,6 @@ package org.keycloak.models.map.storage.jpa;
|
|||
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_ROLE = 1;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import org.keycloak.component.AmphibianProviderFactory;
|
|||
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;
|
||||
|
@ -62,6 +63,8 @@ import org.keycloak.models.map.storage.MapStorageProviderFactory;
|
|||
import org.keycloak.models.map.storage.jpa.client.JpaClientMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.clientscope.JpaClientScopeMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEntity;
|
||||
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.role.JpaRoleMapKeycloakTransaction;
|
||||
|
@ -88,6 +91,8 @@ public class JpaMapStorageProviderFactory implements
|
|||
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
|
||||
//client-scope
|
||||
.constructor(JpaClientScopeEntity.class, JpaClientScopeEntity::new)
|
||||
//group
|
||||
.constructor(JpaGroupEntity.class, JpaGroupEntity::new)
|
||||
//role
|
||||
.constructor(JpaRoleEntity.class, JpaRoleEntity::new)
|
||||
.build();
|
||||
|
@ -96,6 +101,7 @@ public class JpaMapStorageProviderFactory implements
|
|||
static {
|
||||
MODEL_TO_TX.put(ClientScopeModel.class, JpaClientScopeMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(ClientModel.class, JpaClientMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(GroupModel.class, JpaGroupMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,9 @@ public class JpaClientDelegateProvider extends JpaDelegateProvider<JpaClientEnti
|
|||
default:
|
||||
setDelegate(em.find(JpaClientEntity.class, UUID.fromString(getDelegate().getId())));
|
||||
}
|
||||
} else throw new IllegalStateException("Not a valid client field: " + field);
|
||||
} else {
|
||||
throw new IllegalStateException("Not a valid client field: " + field);
|
||||
}
|
||||
} else {
|
||||
setDelegate(em.find(JpaClientEntity.class, UUID.fromString(getDelegate().getId())));
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.keycloak.models.map.storage.jpa.client.entity;
|
|||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -536,12 +535,7 @@ public class JpaClientEntity extends AbstractClientEntity implements JpaRootEnti
|
|||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
for (Iterator<JpaClientAttributeEntity> iterator = attributes.iterator(); iterator.hasNext();) {
|
||||
JpaClientAttributeEntity attr = iterator.next();
|
||||
if (Objects.equals(attr.getName(), name)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
attributes.removeIf(attr -> Objects.equals(attr.getName(), name));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -574,9 +568,7 @@ public class JpaClientEntity extends AbstractClientEntity implements JpaRootEnti
|
|||
|
||||
@Override
|
||||
public void setAttributes(Map<String, List<String>> attributes) {
|
||||
for (Iterator<JpaClientAttributeEntity> iterator = this.attributes.iterator(); iterator.hasNext();) {
|
||||
iterator.remove();
|
||||
}
|
||||
this.attributes.clear();
|
||||
if (attributes != null) {
|
||||
for (Map.Entry<String, List<String>> attrEntry : attributes.entrySet()) {
|
||||
setAttribute(attrEntry.getKey(), attrEntry.getValue());
|
||||
|
|
|
@ -64,7 +64,9 @@ public class JpaClientScopeDelegateProvider extends JpaDelegateProvider<JpaClien
|
|||
default:
|
||||
setDelegate(em.find(JpaClientScopeEntity.class, UUID.fromString(getDelegate().getId())));
|
||||
}
|
||||
} else throw new IllegalStateException("Not a valid client scope field: " + field);
|
||||
} else {
|
||||
throw new IllegalStateException("Not a valid client scope field: " + field);
|
||||
}
|
||||
} else {
|
||||
setDelegate(em.find(JpaClientScopeEntity.class, UUID.fromString(getDelegate().getId())));
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.keycloak.models.map.storage.jpa.clientscope.entity;
|
|||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -213,12 +212,7 @@ public class JpaClientScopeEntity extends AbstractClientScopeEntity implements J
|
|||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
for (Iterator<JpaClientScopeAttributeEntity> iterator = attributes.iterator(); iterator.hasNext();) {
|
||||
JpaClientScopeAttributeEntity attr = iterator.next();
|
||||
if (Objects.equals(attr.getName(), name)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
attributes.removeIf(attr -> Objects.equals(attr.getName(), name));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -251,9 +245,7 @@ public class JpaClientScopeEntity extends AbstractClientScopeEntity implements J
|
|||
|
||||
@Override
|
||||
public void setAttributes(Map<String, List<String>> attributes) {
|
||||
for (Iterator<JpaClientScopeAttributeEntity> iterator = this.attributes.iterator(); iterator.hasNext();) {
|
||||
iterator.remove();
|
||||
}
|
||||
this.attributes.clear();
|
||||
if (attributes != null) {
|
||||
for (Map.Entry<String, List<String>> attrEntry : attributes.entrySet()) {
|
||||
setAttribute(attrEntry.getKey(), attrEntry.getValue());
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.map.storage.jpa.group;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.criteria.Selection;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.map.group.MapGroupEntity;
|
||||
import org.keycloak.models.map.group.MapGroupEntityDelegate;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP;
|
||||
import org.keycloak.models.map.storage.jpa.group.delegate.JpaGroupDelegateProvider;
|
||||
import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity;
|
||||
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||
|
||||
public class JpaGroupMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaGroupEntity, MapGroupEntity, GroupModel> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public JpaGroupMapKeycloakTransaction(EntityManager em) {
|
||||
super(JpaGroupEntity.class, em);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Selection<JpaGroupEntity> selectCbConstruct(CriteriaBuilder cb, Root<JpaGroupEntity> root) {
|
||||
return cb.construct(JpaGroupEntity.class,
|
||||
root.get("id"),
|
||||
root.get("version"),
|
||||
root.get("entityVersion"),
|
||||
root.get("realmId"),
|
||||
root.get("name"),
|
||||
root.get("parentId")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntityVersion(JpaRootEntity entity) {
|
||||
entity.setEntityVersion(CURRENT_SCHEMA_VERSION_GROUP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() {
|
||||
return new JpaGroupModelCriteriaBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapGroupEntity mapToEntityDelegate(JpaGroupEntity original) {
|
||||
return new MapGroupEntityDelegate(new JpaGroupDelegateProvider(original, em));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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.group;
|
||||
|
||||
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.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.map.common.StringKeyConvertor;
|
||||
import org.keycloak.models.map.storage.CriterionNotSupportedException;
|
||||
import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity;
|
||||
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||
import org.keycloak.storage.SearchableModelField;
|
||||
|
||||
public class JpaGroupModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaGroupEntity, GroupModel, JpaGroupModelCriteriaBuilder> {
|
||||
|
||||
public JpaGroupModelCriteriaBuilder() {
|
||||
super(JpaGroupModelCriteriaBuilder::new);
|
||||
}
|
||||
|
||||
private JpaGroupModelCriteriaBuilder(BiFunction<CriteriaBuilder, Root<JpaGroupEntity>, Predicate> predicateFunc) {
|
||||
super(JpaGroupModelCriteriaBuilder::new, predicateFunc);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public JpaGroupModelCriteriaBuilder compare(SearchableModelField<? super GroupModel> modelField, Operator op, Object... value) {
|
||||
switch (op) {
|
||||
case EQ:
|
||||
if (modelField.equals(GroupModel.SearchableFields.REALM_ID) ||
|
||||
modelField.equals(GroupModel.SearchableFields.NAME)) {
|
||||
validateValue(value, modelField, op, String.class);
|
||||
|
||||
return new JpaGroupModelCriteriaBuilder((cb, root) ->
|
||||
cb.equal(root.get(modelField.getName()), value[0])
|
||||
);
|
||||
} else if (modelField.equals(GroupModel.SearchableFields.PARENT_ID)) {
|
||||
if (value.length == 1 && Objects.isNull(value[0])) {
|
||||
return new JpaGroupModelCriteriaBuilder((cb, root) ->
|
||||
cb.isNull(root.get("parentId"))
|
||||
);
|
||||
}
|
||||
|
||||
validateValue(value, modelField, op, String.class);
|
||||
|
||||
return new JpaGroupModelCriteriaBuilder((cb, root) ->
|
||||
cb.equal(root.get("parentId"), value[0])
|
||||
);
|
||||
} else if (modelField.equals(GroupModel.SearchableFields.ASSIGNED_ROLE)) {
|
||||
validateValue(value, modelField, op, String.class);
|
||||
|
||||
return new JpaGroupModelCriteriaBuilder((cb, root) ->
|
||||
cb.isTrue(cb.function("@>",
|
||||
Boolean.TYPE,
|
||||
cb.function("->", JsonbType.class, root.get("metadata"), cb.literal("fGrantedRoles")),
|
||||
cb.literal(convertToJson(value[0]))))
|
||||
);
|
||||
} else {
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
case IN:
|
||||
if (modelField.equals(GroupModel.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));
|
||||
}
|
||||
|
||||
if (collectionValues.isEmpty()) {
|
||||
return new JpaGroupModelCriteriaBuilder((cb, root) -> cb.or());
|
||||
}
|
||||
|
||||
return new JpaGroupModelCriteriaBuilder((cb, root) -> {
|
||||
CriteriaBuilder.In<UUID> in = cb.in(root.get("id"));
|
||||
for (Object id : collectionValues) {
|
||||
try {
|
||||
in.value(StringKeyConvertor.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(GroupModel.SearchableFields.NAME)) {
|
||||
|
||||
validateValue(value, modelField, op, String.class);
|
||||
|
||||
return new JpaGroupModelCriteriaBuilder((cb, root) ->
|
||||
cb.like(cb.lower(root.get(modelField.getName())), value[0].toString().toLowerCase())
|
||||
);
|
||||
} else {
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
case NOT_EXISTS:
|
||||
if (modelField.equals(GroupModel.SearchableFields.PARENT_ID)) {
|
||||
|
||||
return new JpaGroupModelCriteriaBuilder((cb, root) ->
|
||||
cb.isNull(root.get("parentId"))
|
||||
);
|
||||
} else {
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
default:
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.group.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.group.MapGroupEntity;
|
||||
import org.keycloak.models.map.group.MapGroupEntityFields;
|
||||
import org.keycloak.models.map.storage.jpa.JpaDelegateProvider;
|
||||
import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity;
|
||||
|
||||
public class JpaGroupDelegateProvider extends JpaDelegateProvider<JpaGroupEntity> implements DelegateProvider<MapGroupEntity> {
|
||||
|
||||
private final EntityManager em;
|
||||
|
||||
public JpaGroupDelegateProvider(JpaGroupEntity delegate, EntityManager em) {
|
||||
super(delegate);
|
||||
this.em = em;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapGroupEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapGroupEntity>> field, Object... parameters) {
|
||||
if (getDelegate().isMetadataInitialized()) return getDelegate();
|
||||
if (isRead) {
|
||||
if (field instanceof MapGroupEntityFields) {
|
||||
switch ((MapGroupEntityFields) field) {
|
||||
case ID:
|
||||
case REALM_ID:
|
||||
case NAME:
|
||||
case PARENT_ID:
|
||||
return getDelegate();
|
||||
|
||||
case ATTRIBUTES:
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<JpaGroupEntity> query = cb.createQuery(JpaGroupEntity.class);
|
||||
Root<JpaGroupEntity> root = query.from(JpaGroupEntity.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(JpaGroupEntity.class, UUID.fromString(getDelegate().getId())));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Not a valid group field: " + field);
|
||||
}
|
||||
} else {
|
||||
setDelegate(em.find(JpaGroupEntity.class, UUID.fromString(getDelegate().getId())));
|
||||
}
|
||||
return getDelegate();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.group.entity;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import org.keycloak.models.map.storage.jpa.JpaAttributeEntity;
|
||||
|
||||
@Entity
|
||||
@Table(name = "kc_group_attribute")
|
||||
public class JpaGroupAttributeEntity extends JpaAttributeEntity<JpaGroupEntity> {
|
||||
|
||||
public JpaGroupAttributeEntity() {
|
||||
}
|
||||
|
||||
public JpaGroupAttributeEntity(JpaGroupEntity root, String name, String value) {
|
||||
super(root, name, value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* 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.group.entity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
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.group.MapGroupEntity.AbstractGroupEntity;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP;
|
||||
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||
import org.keycloak.models.map.storage.jpa.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 = "kc_group", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "name", "parentId"})})
|
||||
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||
public class JpaGroupEntity extends AbstractGroupEntity implements JpaRootEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
private UUID id;
|
||||
|
||||
//used for implicit optimistic locking
|
||||
@Version
|
||||
@Column
|
||||
private int version;
|
||||
|
||||
@Type(type = "jsonb")
|
||||
@Column(columnDefinition = "jsonb")
|
||||
private final JpaGroupMetadata 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 name;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String parentId;
|
||||
|
||||
@OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true)
|
||||
private final Set<JpaGroupAttributeEntity> attributes = new HashSet<>();
|
||||
|
||||
/**
|
||||
* No-argument constructor, used by hibernate to instantiate entities.
|
||||
*/
|
||||
public JpaGroupEntity() {
|
||||
this.metadata = new JpaGroupMetadata();
|
||||
}
|
||||
|
||||
public JpaGroupEntity(DeepCloner cloner) {
|
||||
this.metadata = new JpaGroupMetadata(cloner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
|
||||
* It is used to select group without metadata(json) field.
|
||||
*/
|
||||
public JpaGroupEntity(UUID id, int version, Integer entityVersion, String realmId,
|
||||
String name, String parentId) {
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
this.entityVersion = entityVersion;
|
||||
this.realmId = realmId;
|
||||
this.name = name;
|
||||
this.parentId = parentId;
|
||||
this.metadata = null;
|
||||
}
|
||||
|
||||
public boolean isMetadataInitialized() {
|
||||
return metadata != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getEntityVersion() {
|
||||
if (isMetadataInitialized()) return metadata.getEntityVersion();
|
||||
return entityVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getCurrentSchemaVersion() {
|
||||
return CURRENT_SCHEMA_VERSION_GROUP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntityVersion(Integer entityVersion) {
|
||||
metadata.setEntityVersion(entityVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id == null ? null : id.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
this.id = id == null ? null : UUID.fromString(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealmId() {
|
||||
if (isMetadataInitialized()) return metadata.getRealmId();
|
||||
return realmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRealmId(String realmId) {
|
||||
metadata.setRealmId(realmId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
if (isMetadataInitialized()) return metadata.getName();
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
metadata.setName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParentId(String parentId) {
|
||||
metadata.setParentId(parentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentId() {
|
||||
if (isMetadataInitialized()) return metadata.getParentId();
|
||||
return parentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getGrantedRoles() {
|
||||
return metadata.getGrantedRoles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGrantedRoles(Set<String> grantedRoles) {
|
||||
metadata.setGrantedRoles(grantedRoles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGrantedRole(String role) {
|
||||
metadata.addGrantedRole(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGrantedRole(String role) {
|
||||
metadata.removeGrantedRole(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
Map<String, List<String>> result = new HashMap<>();
|
||||
for (JpaGroupAttributeEntity attribute : attributes) {
|
||||
List<String> values = result.getOrDefault(attribute.getName(), new LinkedList<>());
|
||||
values.add(attribute.getValue());
|
||||
result.put(attribute.getName(), values);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttributes(Map<String, List<String>> attributes) {
|
||||
this.attributes.clear();
|
||||
if (attributes != null) {
|
||||
for (Map.Entry<String, List<String>> entry : attributes.entrySet()) {
|
||||
setAttribute(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
return attributes.stream()
|
||||
.filter(a -> Objects.equals(a.getName(), name))
|
||||
.map(JpaGroupAttributeEntity::getValue)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
removeAttribute(name);
|
||||
for (String value : values) {
|
||||
JpaGroupAttributeEntity attribute = new JpaGroupAttributeEntity(this, name, value);
|
||||
attributes.add(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
attributes.removeIf(attr -> Objects.equals(attr.getName(), name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof JpaGroupEntity)) return false;
|
||||
return Objects.equals(getId(), ((JpaGroupEntity) 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.group.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import org.keycloak.models.map.group.MapGroupEntityImpl;
|
||||
|
||||
public class JpaGroupMetadata extends MapGroupEntityImpl implements Serializable {
|
||||
|
||||
public JpaGroupMetadata(DeepCloner cloner) {
|
||||
super(cloner);
|
||||
}
|
||||
|
||||
public JpaGroupMetadata() {
|
||||
super();
|
||||
}
|
||||
|
||||
private Integer entityVersion;
|
||||
|
||||
public Integer getEntityVersion() {
|
||||
return entityVersion;
|
||||
}
|
||||
|
||||
public void setEntityVersion(Integer entityVersion) {
|
||||
this.entityVersion = entityVersion;
|
||||
}
|
||||
|
||||
}
|
|
@ -24,11 +24,14 @@ import java.util.function.BiFunction;
|
|||
import java.util.function.Function;
|
||||
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.client.entity.JpaClientMetadata;
|
||||
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeMetadata;
|
||||
import org.keycloak.models.map.storage.jpa.group.entity.JpaGroupMetadata;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientMigration;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientScopeMigration;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaGroupMigration;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRoleMigration;
|
||||
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata;
|
||||
|
||||
|
@ -40,6 +43,7 @@ public class JpaEntityMigration {
|
|||
static {
|
||||
MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaClientScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SCOPE, tree, JpaClientScopeMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
|
||||
}
|
||||
|
||||
|
|
|
@ -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 JpaGroupMigration {
|
||||
|
||||
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
|
||||
o -> o // no migration yet
|
||||
);
|
||||
|
||||
}
|
|
@ -98,6 +98,10 @@ public class JpaRoleModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaRole
|
|||
collectionValues = new HashSet(Arrays.asList(value));
|
||||
}
|
||||
|
||||
if (collectionValues.isEmpty()) {
|
||||
return new JpaRoleModelCriteriaBuilder((cb, root) -> cb.or());
|
||||
}
|
||||
|
||||
return new JpaRoleModelCriteriaBuilder((cb, root) -> {
|
||||
In<UUID> in = cb.in(root.get("id"));
|
||||
for (Object id : collectionValues) {
|
||||
|
|
|
@ -64,7 +64,9 @@ public class JpaRoleDelegateProvider extends JpaDelegateProvider<JpaRoleEntity>
|
|||
default:
|
||||
setDelegate(em.find(JpaRoleEntity.class, UUID.fromString(getDelegate().getId())));
|
||||
}
|
||||
} else throw new IllegalStateException("Not a valid role field: " + field);
|
||||
} else {
|
||||
throw new IllegalStateException("Not a valid role field: " + field);
|
||||
}
|
||||
} else {
|
||||
setDelegate(em.find(JpaRoleEntity.class, UUID.fromString(getDelegate().getId())));
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package org.keycloak.models.map.storage.jpa.role.entity;
|
|||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -242,9 +241,7 @@ public class JpaRoleEntity extends AbstractRoleEntity implements JpaRootEntity {
|
|||
|
||||
@Override
|
||||
public void setAttributes(Map<String, List<String>> attributes) {
|
||||
for (Iterator<JpaRoleAttributeEntity> iterator = this.attributes.iterator(); iterator.hasNext();) {
|
||||
iterator.remove();
|
||||
}
|
||||
this.attributes.clear();
|
||||
if (attributes != null) {
|
||||
for (Map.Entry<String, List<String>> entry : attributes.entrySet()) {
|
||||
setAttribute(entry.getKey(), entry.getValue());
|
||||
|
@ -263,12 +260,7 @@ public class JpaRoleEntity extends AbstractRoleEntity implements JpaRootEntity {
|
|||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
for (Iterator<JpaRoleAttributeEntity> iterator = attributes.iterator(); iterator.hasNext();) {
|
||||
JpaRoleAttributeEntity attr = iterator.next();
|
||||
if (Objects.equals(attr.getName(), name)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
attributes.removeIf(attr -> Objects.equals(attr.getName(), name));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<?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: groups-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP} -->
|
||||
<changeSet author="keycloak" id="groups-1">
|
||||
|
||||
<createTable tableName="kc_group">
|
||||
<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_group">
|
||||
<ext:column name="entityversion" type="INTEGER" jsonColumn="metadata" jsonProperty="entityVersion"/>
|
||||
<ext:column name="realmid" type="VARCHAR(36)" jsonColumn="metadata" jsonProperty="fRealmId"/>
|
||||
<ext:column name="name" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fName"/>
|
||||
<ext:column name="parentid" type="VARCHAR(36)" jsonColumn="metadata" jsonProperty="fParentId"/>
|
||||
</ext:addGeneratedColumn>
|
||||
<createIndex tableName="kc_group" indexName="group_entityVersion">
|
||||
<column name="entityversion"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_group" indexName="group_realmId_name_parentId" unique="true">
|
||||
<column name="realmid"/>
|
||||
<column name="name"/>
|
||||
<column name="parentid"/>
|
||||
</createIndex>
|
||||
|
||||
<createTable tableName="kc_group_attribute">
|
||||
<column name="id" type="UUID">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="fk_root" type="UUID">
|
||||
<constraints foreignKeyName="group_attr_fk_root_fkey" references="kc_group(id)" deleteCascade="true"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(255)"/>
|
||||
<column name="value" type="text"/>
|
||||
</createTable>
|
||||
<createIndex tableName="kc_group_attribute" indexName="group_attr_fk_root">
|
||||
<column name="fk_root"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="kc_group_attribute" indexName="group_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>
|
|
@ -18,6 +18,6 @@ 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-client-scopes-changelog-${org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT_SCOPE}.xml -->
|
||||
<!-- format of id of changelog file names: jpa-client-scopes-changelog-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SCOPE}.xml -->
|
||||
<include file="META-INF/client-scopes/jpa-client-scopes-changelog-1.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -18,6 +18,6 @@ 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-clients-changelog-${org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT}.xml -->
|
||||
<!-- format of id of changelog file names: jpa-clients-changelog-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT}.xml -->
|
||||
<include file="META-INF/clients/jpa-clients-changelog-1.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -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-groups-changelog-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP.xml -->
|
||||
<include file="META-INF/groups/jpa-groups-changelog-1.xml"/>
|
||||
</databaseChangeLog>
|
|
@ -18,6 +18,6 @@ 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 -->
|
||||
<!-- format of id of changelog file names: jpa-roles-changelog-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE}.xml -->
|
||||
<include file="META-INF/roles/jpa-roles-changelog-1.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
<!--clients-->
|
||||
<class>org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity</class>
|
||||
<class>org.keycloak.models.map.storage.jpa.client.entity.JpaClientAttributeEntity</class>
|
||||
<!--groups-->
|
||||
<class>org.keycloak.models.map.storage.jpa.group.entity.JpaGroupEntity</class>
|
||||
<class>org.keycloak.models.map.storage.jpa.group.entity.JpaGroupAttributeEntity</class>
|
||||
<!--roles-->
|
||||
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity</class>
|
||||
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleAttributeEntity</class>
|
||||
|
|
|
@ -36,8 +36,8 @@ limitations under the License.
|
|||
</createTable>
|
||||
<ext:addGeneratedColumn tableName="kc_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="realmid" type="VARCHAR(36)" jsonColumn="metadata" jsonProperty="fRealmId"/>
|
||||
<ext:column name="name" type="VARCHAR(255)" 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>
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.keycloak.models.map.storage.QueryParameters;
|
|||
|
||||
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
@ -122,7 +121,7 @@ public class MapGroupProvider implements GroupProvider {
|
|||
mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
|
||||
|
||||
if (Objects.equals(onlyTopGroups, Boolean.TRUE)) {
|
||||
mcb = mcb.compare(SearchableFields.PARENT_ID, Operator.EQ, (Object) null);
|
||||
mcb = mcb.compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS);
|
||||
}
|
||||
|
||||
return tx.getCount(withCriteria(mcb));
|
||||
|
@ -185,12 +184,14 @@ public class MapGroupProvider implements GroupProvider {
|
|||
public GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent) {
|
||||
LOG.tracef("createGroup(%s, %s, %s, %s)%s", realm, id, name, toParent, getShortStackTrace());
|
||||
// Check Db constraint: uniqueConstraints = { @UniqueConstraint(columnNames = {"REALM_ID", "PARENT_GROUP", "NAME"})}
|
||||
String parentId = toParent == null ? null : toParent.getId();
|
||||
DefaultModelCriteria<GroupModel> mcb = criteria();
|
||||
mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
|
||||
.compare(SearchableFields.PARENT_ID, Operator.EQ, parentId)
|
||||
.compare(SearchableFields.NAME, Operator.EQ, name);
|
||||
|
||||
mcb = toParent == null ?
|
||||
mcb.compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS) :
|
||||
mcb.compare(SearchableFields.PARENT_ID, Operator.EQ, toParent.getId());
|
||||
|
||||
if (tx.getCount(withCriteria(mcb)) > 0) {
|
||||
throw new ModelDuplicateException("Group with name '" + name + "' in realm " + realm.getName() + " already exists for requested parent" );
|
||||
}
|
||||
|
@ -254,12 +255,14 @@ public class MapGroupProvider implements GroupProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
String parentId = toParent == null ? null : toParent.getId();
|
||||
DefaultModelCriteria<GroupModel> mcb = criteria();
|
||||
mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
|
||||
.compare(SearchableFields.PARENT_ID, Operator.EQ, parentId)
|
||||
.compare(SearchableFields.NAME, Operator.EQ, group.getName());
|
||||
|
||||
mcb = toParent == null ?
|
||||
mcb.compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS) :
|
||||
mcb.compare(SearchableFields.PARENT_ID, Operator.EQ, toParent.getId());
|
||||
|
||||
try (Stream<MapGroupEntity> possibleSiblings = tx.read(withCriteria(mcb))) {
|
||||
if (possibleSiblings.findAny().isPresent()) {
|
||||
throw new ModelDuplicateException("Parent already contains subgroup named '" + group.getName() + "'");
|
||||
|
|
Loading…
Reference in a new issue