parent
623aaf1e8b
commit
b50b8f883b
27 changed files with 753 additions and 67 deletions
|
@ -26,6 +26,6 @@ import java.lang.annotation.Target;
|
|||
* @author hmlnarik
|
||||
*/
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
|
||||
public @interface IgnoreForEntityImplementationGenerator {
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ import javax.lang.model.SourceVersion;
|
|||
import static org.keycloak.models.map.processor.FieldAccessorType.GETTER;
|
||||
import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
|
||||
import static org.keycloak.models.map.processor.Util.isMapType;
|
||||
import static org.keycloak.models.map.processor.Util.isSetType;
|
||||
import static org.keycloak.models.map.processor.Util.singularToPlural;
|
||||
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||
|
@ -207,6 +208,9 @@ public abstract class AbstractGenerateEntityImplementationsProcessor extends Abs
|
|||
", (o1, o2) -> o1" +
|
||||
", java.util.HashMap::new" +
|
||||
"))";
|
||||
} else if (isCollection(typeElement.asType())) {
|
||||
TypeMirror collectionType = getGenericsDeclaration(fieldType).get(0);
|
||||
return parameterName + " == null ? null : " + parameterName + ".stream().map(entry -> " + deepClone(collectionType, "entry") + ").collect(java.util.stream.Collectors.toCollection(" + (isSetType(typeElement) ? "java.util.HashSet::new" : "java.util.LinkedList::new") + "))";
|
||||
}
|
||||
return "deepClone(" + parameterName + ")";
|
||||
}
|
||||
|
|
|
@ -424,7 +424,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
|||
case SETTER:
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
if (! isImmutableFinalType(fieldType)) {
|
||||
pw.println(" p0 = " + deepClone(fieldType, "p0") + ";");
|
||||
pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";");
|
||||
}
|
||||
pw.println(" updated |= ! Objects.equals(" + fieldName + ", p0);");
|
||||
pw.println(" " + fieldName + " = p0;");
|
||||
|
@ -434,7 +434,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
|||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
pw.println(" if (" + fieldName + " == null) { " + fieldName + " = " + interfaceToImplementation(typeElement, "") + "; }");
|
||||
if (! isImmutableFinalType(firstParameterType)) {
|
||||
pw.println(" p0 = " + deepClone(fieldType, "p0") + ";");
|
||||
pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";");
|
||||
}
|
||||
if (isSetType(typeElement)) {
|
||||
pw.println(" updated |= " + fieldName + ".add(p0);");
|
||||
|
|
|
@ -30,6 +30,7 @@ import javax.lang.model.element.Modifier;
|
|||
import javax.lang.model.element.Name;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
@ -108,6 +109,10 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
|
||||
boolean usingGeneratedCloner = ! hasDeepClone && needsDeepClone;
|
||||
boolean hasId = methodsPerAttribute.containsKey("Id") || allMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
|
||||
boolean hasFieldId = elements.getAllMembers(e).stream()
|
||||
.filter(VariableElement.class::isInstance)
|
||||
.map(VariableElement.class::cast)
|
||||
.anyMatch(variableElement -> variableElement.getSimpleName().toString().equals("id"));
|
||||
|
||||
JavaFileObject file = processingEnv.getFiler().createSourceFile(hotRodImplClassName);
|
||||
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
|
||||
|
@ -218,6 +223,7 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
|
||||
pw.print(" return ");
|
||||
pw.println(elements.getAllMembers(e).stream()
|
||||
.filter(Util::isNotIgnored)
|
||||
.filter(VariableElement.class::isInstance)
|
||||
.map(VariableElement.class::cast)
|
||||
.map(var -> "Objects.equals(e1." + var.getSimpleName().toString() + ", e2." + var.getSimpleName().toString() + ")")
|
||||
|
@ -227,13 +233,13 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
|
||||
pw.println(" public static int entityHashCode(" + className + " e) {");
|
||||
pw.println(" return "
|
||||
+ (hasId
|
||||
+ (hasFieldId
|
||||
? "(e.id == null ? Objects.hash(e) : e.id.hashCode())"
|
||||
: "Objects.hash("
|
||||
+ elements.getAllMembers(e).stream()
|
||||
.filter(VariableElement.class::isInstance)
|
||||
.map(VariableElement.class::cast)
|
||||
.map(var -> var.getSimpleName().toString())
|
||||
.map(var -> "e." + var.getSimpleName().toString())
|
||||
.collect(Collectors.joining(",\n "))
|
||||
+ ")")
|
||||
+ ";"
|
||||
|
@ -268,7 +274,7 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
.filter(variableElement -> variableElement.getSimpleName().toString().equals(hotRodEntityFieldName))
|
||||
.findFirst();
|
||||
|
||||
if (!hotRodVariable.isPresent()) {
|
||||
if (!hasField(e, hotRodEntityFieldName)) {
|
||||
// throw an error when no variable found
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Cannot find " + e.getSimpleName().toString() + " field for methods: [" + me.getValue().stream().map(ee -> ee.getSimpleName().toString()).collect(Collectors.joining(", ")) + "]", parentInterfaceElement);
|
||||
return;
|
||||
|
@ -326,7 +332,7 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
case SETTER:
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
if (! isImmutableFinalType(firstParameterType)) {
|
||||
pw.println(" p0 = " + deepClone(fieldType, "p0") + ";");
|
||||
pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";");
|
||||
}
|
||||
pw.println(" " + hotRodFieldType.toString() + " migrated = " + migrateToType(hotRodFieldType, firstParameterType, "p0") + ";");
|
||||
pw.println(" " + hotRodEntityField("updated") + " |= ! Objects.equals(" + hotRodEntityField(fieldName) + ", migrated);");
|
||||
|
@ -338,7 +344,7 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { " + hotRodEntityField(fieldName) + " = " + interfaceToImplementation(typeElement, "") + "; }");
|
||||
if (! isImmutableFinalType(firstParameterType)) {
|
||||
pw.println(" p0 = " + deepClone(fieldType, "p0") + ";");
|
||||
pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";");
|
||||
}
|
||||
pw.println(" " + collectionItemType.toString() + " migrated = " + migrateToType(collectionItemType, firstParameterType, "p0") + ";");
|
||||
if (isSetType(typeElement)) {
|
||||
|
@ -351,19 +357,22 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
return true;
|
||||
case COLLECTION_DELETE:
|
||||
collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
|
||||
boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID;
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
if (isMapType(typeElement)) {
|
||||
// Maps are stored as sets
|
||||
pw.println(" " + hotRodEntityField("updated") + " |= " + hotRodUtils.getQualifiedName().toString() + ".removeFromSetByMapKey("
|
||||
pw.println(" boolean removed = " + hotRodUtils.getQualifiedName().toString() + ".removeFromSetByMapKey("
|
||||
+ hotRodEntityField(fieldName) + ", "
|
||||
+ "p0, "
|
||||
+ keyGetterReference(collectionItemType) + ");"
|
||||
);
|
||||
pw.println(" " + hotRodEntityField("updated") + " |= removed;");
|
||||
} else {
|
||||
pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { return; }");
|
||||
pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { return" + (needsReturn ? " false" : "") + "; }");
|
||||
pw.println(" boolean removed = " + hotRodEntityField(fieldName) + ".remove(p0);");
|
||||
pw.println(" " + hotRodEntityField("updated") + " |= removed;");
|
||||
}
|
||||
if (needsReturn) pw.println(" return removed;");
|
||||
pw.println(" }");
|
||||
return true;
|
||||
case MAP_ADD:
|
||||
|
@ -409,8 +418,20 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
return e.getSimpleName().toString();
|
||||
}
|
||||
|
||||
private boolean hasField(TypeElement type, String fieldName) {
|
||||
Optional<VariableElement> hotRodVariable = elements.getAllMembers(type).stream()
|
||||
.filter(VariableElement.class::isInstance)
|
||||
.map(VariableElement.class::cast)
|
||||
.filter(variableElement -> variableElement.getSimpleName().toString().equals(fieldName))
|
||||
.findFirst();
|
||||
|
||||
return hotRodVariable.isPresent();
|
||||
}
|
||||
|
||||
private String keyGetterReference(TypeMirror type) {
|
||||
if (types.isAssignable(type, abstractHotRodEntity.asType())) {
|
||||
TypeElement typeElement = elements.getTypeElement(types.erasure(type).toString());
|
||||
|
||||
if (hasField(typeElement, "id")) {
|
||||
return "e -> e.id";
|
||||
}
|
||||
return hotRodUtils.getQualifiedName().toString() + "::getKey";
|
||||
|
@ -423,15 +444,29 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
return hotRodUtils.getQualifiedName().toString() + "::getValue";
|
||||
}
|
||||
|
||||
private boolean isAssignable(TypeMirror fromType, TypeMirror toType) {
|
||||
return types.isAssignable(types.erasure(fromType), types.erasure(toType));
|
||||
}
|
||||
|
||||
private String migrateToType(TypeMirror toType, TypeMirror[] fromType, String[] fieldNames) {
|
||||
// No migration needed, fromType is assignable to toType directly
|
||||
if (fromType.length == 1 && types.isAssignable(types.erasure(fromType[0]), types.erasure(toType))) {
|
||||
if (fromType.length == 1 && isAssignable(fromType[0], toType) && !isCollection(fromType[0])) {
|
||||
return fieldNames[0];
|
||||
}
|
||||
|
||||
// HotRod entities are not allowed to use Maps, therefore we often need to migrate from Map to Set and the other way around
|
||||
// Solve migration of data within collections
|
||||
if (fromType.length == 1) {
|
||||
if (isSetType((TypeElement) types.asElement(types.erasure(toType)))
|
||||
if (isAssignable(fromType[0], toType)) { // First case, the collection is the same
|
||||
TypeMirror fromGeneric = getGenericsDeclaration(fromType[0]).get(0);
|
||||
TypeMirror toGeneric = getGenericsDeclaration(toType).get(0);
|
||||
|
||||
// Generics are assignable too, so we can just assign the same value
|
||||
if (isAssignable(fromGeneric, toGeneric)) return fieldNames[0];
|
||||
|
||||
return hotRodUtils.getQualifiedName().toString() + ".migrate" + toSimpleName(fromType[0]) + "("
|
||||
+ fieldNames[0] + ", "
|
||||
+ "collectionItem -> " + migrateToType(toGeneric, fromGeneric, "collectionItem") + ")";
|
||||
} else if (isSetType((TypeElement) types.asElement(types.erasure(toType)))
|
||||
&& isMapType((TypeElement) types.asElement(types.erasure(fromType[0])))) {
|
||||
TypeMirror setType = getGenericsDeclaration(toType).get(0);
|
||||
|
||||
|
@ -456,19 +491,19 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
return "new " + toType.toString() + "(" + String.join(", ", fieldNames) + ")";
|
||||
}
|
||||
|
||||
// Check if any of parameters is another Map*Entity
|
||||
OptionalInt anotherMapEntityIndex = IntStream.range(0, fromType.length)
|
||||
.filter(i -> types.isAssignable(fromType[i], abstractEntity.asType()))
|
||||
.findFirst();
|
||||
if (isAssignable(toType, abstractHotRodEntity.asType())) {
|
||||
// Check if any of parameters is another Map*Entity
|
||||
OptionalInt anotherMapEntityIndex = IntStream.range(0, fromType.length)
|
||||
.filter(i -> isAssignable(fromType[i], abstractEntity.asType()))
|
||||
.findFirst();
|
||||
|
||||
if (anotherMapEntityIndex.isPresent()) {
|
||||
// If yes, we can be sure that it implements HotRodEntityDelegate (this is achieved by HotRod cloner settings) so we can just call getHotRodEntity method
|
||||
return "((" + generalHotRodDelegate.getQualifiedName().toString() + "<" + toType.toString() + ">) " + fieldNames[anotherMapEntityIndex.getAsInt()] + ").getHotRodEntity()";
|
||||
return "((" + generalHotRodDelegate.getQualifiedName().toString() + "<" + toType.toString() + ">) " + fieldNames[anotherMapEntityIndex.orElse(0)] + ").getHotRodEntity()";
|
||||
}
|
||||
|
||||
// Check if any of parameters is another HotRod*Entity
|
||||
OptionalInt anotherHotRodEntityIndex = IntStream.range(0, fromType.length)
|
||||
.filter(i -> types.isAssignable(fromType[i], abstractHotRodEntity.asType()))
|
||||
.filter(i -> isAssignable(fromType[i], abstractHotRodEntity.asType()))
|
||||
.findFirst();
|
||||
|
||||
if (anotherHotRodEntityIndex.isPresent()) {
|
||||
|
@ -476,7 +511,8 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
|||
return "new " + fromType[anotherHotRodEntityIndex.getAsInt()] + "Delegate(" + String.join(", ", fieldNames) + ")";
|
||||
}
|
||||
|
||||
throw new CannotMigrateTypeException(toType, fromType);
|
||||
int last = fromType.length -1 ;
|
||||
return hotRodUtils.getQualifiedName().toString() + ".migrate" + toSimpleName(fromType[last]) + "To" + toSimpleName(toType) + "(" + fieldNames[last] + ")";
|
||||
}
|
||||
|
||||
private Optional<ExecutableElement> findSuitableConstructor(TypeMirror desiredType, TypeMirror[] parameters) {
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.keycloak.models.map.storage.chm.MapFieldPredicates;
|
|||
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
|
||||
import org.keycloak.storage.SearchableModelField;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Spliterators;
|
||||
|
@ -51,7 +52,7 @@ import java.util.stream.StreamSupport;
|
|||
import static org.keycloak.models.map.storage.hotRod.common.HotRodUtils.paginateQuery;
|
||||
import static org.keycloak.utils.StreamsUtil.closing;
|
||||
|
||||
public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E>, M> implements MapStorage<V, M>, ConcurrentHashMapCrudOperations<V, M> {
|
||||
public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M> implements MapStorage<V, M>, ConcurrentHashMapCrudOperations<V, M> {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(HotRodMapStorage.class);
|
||||
|
||||
|
@ -146,7 +147,7 @@ public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends HotRo
|
|||
|
||||
QueryFactory queryFactory = Search.getQueryFactory(remoteCache);
|
||||
|
||||
Query<V> query = queryFactory.create(queryString);
|
||||
Query<E> query = queryFactory.create(queryString);
|
||||
query.setParameters(iqmcb.getParameters());
|
||||
|
||||
return query.execute().hitCount().orElse(0);
|
||||
|
@ -167,17 +168,18 @@ public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends HotRo
|
|||
|
||||
QueryFactory queryFactory = Search.getQueryFactory(remoteCache);
|
||||
|
||||
Query<V> query = paginateQuery(queryFactory.create(queryString), queryParameters.getOffset(),
|
||||
Query<Object[]> query = paginateQuery(queryFactory.create(queryString), queryParameters.getOffset(),
|
||||
queryParameters.getLimit());
|
||||
|
||||
query.setParameters(iqmcb.getParameters());
|
||||
|
||||
AtomicLong result = new AtomicLong();
|
||||
|
||||
CloseableIterator<V> iterator = query.iterator();
|
||||
CloseableIterator<Object[]> iterator = query.iterator();
|
||||
StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false)
|
||||
.peek(e -> result.incrementAndGet())
|
||||
.map(AbstractEntity::getId)
|
||||
.map(a -> a[0])
|
||||
.map(String.class::cast)
|
||||
.forEach(this::delete);
|
||||
iterator.close();
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ public class HotRodMapStorageProvider implements MapStorageProvider {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E>, M> HotRodMapStorage<String, E, V, M> getHotRodStorage(Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
|
||||
public <E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M> HotRodMapStorage<String, E, V, M> getHotRodStorage(Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
|
||||
HotRodEntityDescriptor<E, V> entityDescriptor = (HotRodEntityDescriptor<E, V>) factory.getEntityDescriptor(modelType);
|
||||
return new HotRodMapStorage<>(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConvertor.StringKey.INSTANCE, entityDescriptor, cloner);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.map.group.MapGroupEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntityDelegate;
|
||||
|
@ -38,6 +39,15 @@ import org.keycloak.models.map.storage.MapStorageProvider;
|
|||
import org.keycloak.models.map.storage.MapStorageProviderFactory;
|
||||
import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserCredentialEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEntityDelegate;
|
||||
import org.keycloak.models.map.user.MapUserConsentEntity;
|
||||
import org.keycloak.models.map.user.MapUserCredentialEntity;
|
||||
import org.keycloak.models.map.user.MapUserEntity;
|
||||
import org.keycloak.models.map.user.MapUserFederatedIdentityEntity;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -49,9 +59,13 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
|||
private static final Logger LOG = Logger.getLogger(HotRodMapStorageProviderFactory.class);
|
||||
|
||||
private final static DeepCloner CLONER = new DeepCloner.Builder()
|
||||
.constructor(MapClientEntity.class, HotRodClientEntityDelegate::new)
|
||||
.constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntityDelegate::new)
|
||||
.constructor(MapGroupEntity.class, HotRodGroupEntityDelegate::new)
|
||||
.constructor(MapClientEntity.class, HotRodClientEntityDelegate::new)
|
||||
.constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntityDelegate::new)
|
||||
.constructor(MapGroupEntity.class, HotRodGroupEntityDelegate::new)
|
||||
.constructor(MapUserEntity.class, HotRodUserEntityDelegate::new)
|
||||
.constructor(MapUserCredentialEntity.class, HotRodUserCredentialEntityDelegate::new)
|
||||
.constructor(MapUserFederatedIdentityEntity.class, HotRodUserFederatedIdentityEntityDelegate::new)
|
||||
.constructor(MapUserConsentEntity.class, HotRodUserConsentEntityDelegate::new)
|
||||
.build();
|
||||
|
||||
public static final Map<Class<?>, HotRodEntityDescriptor<?, ?>> ENTITY_DESCRIPTOR_MAP = new HashMap<>();
|
||||
|
@ -67,6 +81,11 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
|||
new HotRodEntityDescriptor<>(GroupModel.class,
|
||||
HotRodGroupEntity.class,
|
||||
HotRodGroupEntityDelegate::new));
|
||||
// Users descriptor
|
||||
ENTITY_DESCRIPTOR_MAP.put(UserModel.class,
|
||||
new HotRodEntityDescriptor<>(UserModel.class,
|
||||
HotRodUserEntity.class,
|
||||
HotRodUserEntityDelegate::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -31,6 +31,7 @@ import java.util.HashMap;
|
|||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.keycloak.models.map.storage.hotRod.IckleQueryOperators.C;
|
||||
|
@ -43,8 +44,10 @@ public class IckleQueryMapModelCriteriaBuilder<E extends AbstractHotRodEntity, M
|
|||
private final Class<E> hotRodEntityClass;
|
||||
private final StringBuilder whereClauseBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY);
|
||||
private final Map<String, Object> parameters;
|
||||
private static final String NON_ANALYZED_FIELD_REGEX = "[%_\\\\]";
|
||||
private static final String ANALYZED_FIELD_REGEX = "[+!^\"~*?:\\\\]";
|
||||
private static final Pattern NON_ANALYZED_FIELD_REGEX = Pattern.compile("[%_\\\\]");
|
||||
// private static final Pattern ANALYZED_FIELD_REGEX = Pattern.compile("[+!^\"~*?:\\\\]"); // TODO reevaluate once https://github.com/keycloak/keycloak/issues/9295 is fixed
|
||||
private static final Pattern ANALYZED_FIELD_REGEX = Pattern.compile("\\\\"); // escape "\" with extra "\"
|
||||
private static final Pattern SINGLE_PERCENT_CHARACTER = Pattern.compile("^%+$");
|
||||
public static final Map<SearchableModelField<?>, String> INFINISPAN_NAME_OVERRIDES = new HashMap<>();
|
||||
public static final Set<SearchableModelField<?>> ANALYZED_MODEL_FIELDS = new HashSet<>();
|
||||
|
||||
|
@ -55,6 +58,14 @@ public class IckleQueryMapModelCriteriaBuilder<E extends AbstractHotRodEntity, M
|
|||
|
||||
INFINISPAN_NAME_OVERRIDES.put(GroupModel.SearchableFields.PARENT_ID, "parentId");
|
||||
INFINISPAN_NAME_OVERRIDES.put(GroupModel.SearchableFields.ASSIGNED_ROLE, "grantedRoles");
|
||||
|
||||
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT, "serviceAccountClientLink");
|
||||
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.CONSENT_FOR_CLIENT, "userConsents.clientId");
|
||||
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.CONSENT_WITH_CLIENT_SCOPE, "userConsents.grantedClientScopesIds");
|
||||
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.ASSIGNED_ROLE, "rolesMembership");
|
||||
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.ASSIGNED_GROUP, "groupsMembership");
|
||||
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.ATTRIBUTE, "attributes");
|
||||
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.IDP_AND_USER, "federatedIdentities");
|
||||
}
|
||||
|
||||
static {
|
||||
|
@ -192,11 +203,16 @@ public class IckleQueryMapModelCriteriaBuilder<E extends AbstractHotRodEntity, M
|
|||
public static Object sanitize(Object value) {
|
||||
if (value instanceof String) {
|
||||
String sValue = (String) value;
|
||||
|
||||
if(SINGLE_PERCENT_CHARACTER.matcher(sValue).matches()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
boolean anyBeginning = sValue.startsWith("%");
|
||||
boolean anyEnd = sValue.endsWith("%");
|
||||
|
||||
String sanitizedString = sValue.substring(anyBeginning ? 1 : 0, sValue.length() - (anyEnd ? 1 : 0))
|
||||
.replaceAll(NON_ANALYZED_FIELD_REGEX, "\\\\\\\\" + "$0");
|
||||
String sanitizedString = NON_ANALYZED_FIELD_REGEX.matcher(sValue.substring(anyBeginning ? 1 : 0, sValue.length() - (anyEnd ? 1 : 0)))
|
||||
.replaceAll("\\\\\\\\" + "$0");
|
||||
|
||||
return (anyBeginning ? "%" : "") + sanitizedString + (anyEnd ? "%" : "");
|
||||
}
|
||||
|
@ -210,9 +226,13 @@ public class IckleQueryMapModelCriteriaBuilder<E extends AbstractHotRodEntity, M
|
|||
boolean anyBeginning = sValue.startsWith("%");
|
||||
boolean anyEnd = sValue.endsWith("%");
|
||||
|
||||
String sanitizedString = sValue.substring(anyBeginning ? 1 : 0, sValue.length() - (anyEnd ? 1 : 0))
|
||||
.replaceAll("\\\\", "\\\\\\\\"); // escape "\" with extra "\"
|
||||
// .replaceAll(ANALYZED_FIELD_REGEX, "\\\\\\\\" + "$0"); skipped for now because Infinispan is not able to escape
|
||||
if(SINGLE_PERCENT_CHARACTER.matcher(sValue).matches()) {
|
||||
return "*";
|
||||
}
|
||||
|
||||
String sanitizedString = ANALYZED_FIELD_REGEX.matcher(sValue.substring(anyBeginning ? 1 : 0, sValue.length() - (anyEnd ? 1 : 0)))
|
||||
.replaceAll("\\\\\\\\"); // escape "\" with extra "\"
|
||||
// .replaceAll("\\\\\\\\" + "$0"); skipped for now because Infinispan is not able to escape
|
||||
// special characters for analyzed fields
|
||||
// TODO reevaluate once https://github.com/keycloak/keycloak/issues/9295 is fixed
|
||||
|
||||
|
|
|
@ -18,14 +18,17 @@
|
|||
package org.keycloak.models.map.storage.hotRod;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.map.storage.CriterionNotSupportedException;
|
||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||
import org.keycloak.storage.SearchableModelField;
|
||||
import org.keycloak.storage.StorageId;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.keycloak.models.map.storage.hotRod.IckleQueryMapModelCriteriaBuilder.getFieldName;
|
||||
import static org.keycloak.models.map.storage.hotRod.IckleQueryMapModelCriteriaBuilder.sanitizeAnalyzed;
|
||||
import static org.keycloak.models.map.storage.hotRod.IckleQueryOperators.C;
|
||||
|
||||
|
@ -47,7 +50,10 @@ public class IckleQueryWhereClauses {
|
|||
private static final Map<SearchableModelField<?>, WhereClauseProducer> WHERE_CLAUSE_PRODUCER_OVERRIDES = new HashMap<>();
|
||||
|
||||
static {
|
||||
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(ClientModel.SearchableFields.ATTRIBUTE, IckleQueryWhereClauses::whereClauseForClientsAttributes);
|
||||
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(ClientModel.SearchableFields.ATTRIBUTE, IckleQueryWhereClauses::whereClauseForAttributes);
|
||||
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.ATTRIBUTE, IckleQueryWhereClauses::whereClauseForAttributes);
|
||||
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.IDP_AND_USER, IckleQueryWhereClauses::whereClauseForUserIdpAlias);
|
||||
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, IckleQueryWhereClauses::whereClauseForConsentClientFederationLink);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
|
@ -79,7 +85,7 @@ public class IckleQueryWhereClauses {
|
|||
if (IckleQueryMapModelCriteriaBuilder.isAnalyzedModelField(modelField) &&
|
||||
(op.equals(ModelCriteriaBuilder.Operator.ILIKE) || op.equals(ModelCriteriaBuilder.Operator.EQ) || op.equals(ModelCriteriaBuilder.Operator.NE))) {
|
||||
|
||||
String clause = C + "." + fieldName + " : '" + sanitizeAnalyzed(values[0]) + "'";
|
||||
String clause = C + "." + fieldName + " : '" + sanitizeAnalyzed(((String)values[0]).toLowerCase()) + "'";
|
||||
if (op.equals(ModelCriteriaBuilder.Operator.NE)) {
|
||||
return "not(" + clause + ")";
|
||||
}
|
||||
|
@ -90,7 +96,7 @@ public class IckleQueryWhereClauses {
|
|||
return whereClauseProducerForModelField(modelField).produceWhereClause(fieldName, op, values, parameters);
|
||||
}
|
||||
|
||||
private static String whereClauseForClientsAttributes(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
|
||||
private static String whereClauseForAttributes(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
|
||||
if (values == null || values.length != 2) {
|
||||
throw new CriterionNotSupportedException(ClientModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected attribute_name-value pair, got: " + Arrays.toString(values));
|
||||
}
|
||||
|
@ -111,4 +117,41 @@ public class IckleQueryWhereClauses {
|
|||
|
||||
return "(" + nameClause + ")" + " AND " + "(" + valueClause + ")";
|
||||
}
|
||||
|
||||
private static String whereClauseForUserIdpAlias(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
|
||||
if (op != ModelCriteriaBuilder.Operator.EQ) {
|
||||
throw new CriterionNotSupportedException(UserModel.SearchableFields.IDP_AND_USER, op);
|
||||
}
|
||||
if (values == null || values.length == 0 || values.length > 2) {
|
||||
throw new CriterionNotSupportedException(UserModel.SearchableFields.IDP_AND_USER, op, "Invalid arguments, expected (idp_alias) or (idp_alias, idp_user), got: " + Arrays.toString(values));
|
||||
}
|
||||
|
||||
final Object idpAlias = values[0];
|
||||
if (values.length == 1) {
|
||||
return IckleQueryOperators.combineExpressions(op, modelFieldName + ".identityProvider", values, parameters);
|
||||
} else if (idpAlias == null) {
|
||||
final Object idpUserId = values[1];
|
||||
return IckleQueryOperators.combineExpressions(op, modelFieldName + ".userId", new Object[] { idpUserId }, parameters);
|
||||
} else {
|
||||
final Object idpUserId = values[1];
|
||||
// Clause for searching federated identity id
|
||||
String idClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".identityProvider", new Object[]{ idpAlias }, parameters);
|
||||
// Clause for searching federated identity userId
|
||||
String userIdClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".userId", new Object[] { idpUserId }, parameters);
|
||||
|
||||
return "(" + idClause + ")" + " AND " + "(" + userIdClause + ")";
|
||||
}
|
||||
}
|
||||
|
||||
private static String whereClauseForConsentClientFederationLink(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
|
||||
if (op != ModelCriteriaBuilder.Operator.EQ) {
|
||||
throw new CriterionNotSupportedException(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, op);
|
||||
}
|
||||
if (values == null || values.length != 1) {
|
||||
throw new CriterionNotSupportedException(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, op, "Invalid arguments, expected (federation_provider_id), got: " + Arrays.toString(values));
|
||||
}
|
||||
|
||||
String providerId = new StorageId((String) values[0], "").getId();
|
||||
return IckleQueryOperators.combineExpressions(ModelCriteriaBuilder.Operator.LIKE, getFieldName(UserModel.SearchableFields.CONSENT_FOR_CLIENT), new String[] {providerId + "%"}, parameters);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,6 @@ package org.keycloak.models.map.storage.hotRod.common;
|
|||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.common.UpdatableEntity;
|
||||
|
||||
public interface HotRodEntityDelegate<E> extends AbstractEntity, UpdatableEntity {
|
||||
public interface HotRodEntityDelegate<E> extends UpdatableEntity {
|
||||
E getHotRodEntity();
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
package org.keycloak.models.map.storage.hotRod.common;
|
||||
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -91,4 +93,20 @@ public class HotRodTypesUtils {
|
|||
public static String getKey(AbstractEntity entity) {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
public static String getKey(HotRodUserFederatedIdentityEntity hotRodUserFederatedIdentityEntity) {
|
||||
return hotRodUserFederatedIdentityEntity.identityProvider;
|
||||
}
|
||||
|
||||
public static String getKey(HotRodUserConsentEntity hotRodUserConsentEntity) {
|
||||
return hotRodUserConsentEntity.clientId;
|
||||
}
|
||||
|
||||
public static <T, V> List<V> migrateList(List<T> p0, Function<T, V> migrator) {
|
||||
return p0 == null ? null : p0.stream().map(migrator).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, V> Set<V> migrateSet(Set<T> p0, Function<T, V> migrator) {
|
||||
return p0 == null ? null : p0.stream().map(migrator).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
|
|||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserCredentialEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEntity;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||
|
@ -35,6 +39,12 @@ import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity;
|
|||
// Groups
|
||||
HotRodGroupEntity.class,
|
||||
|
||||
// Users
|
||||
HotRodUserEntity.class,
|
||||
HotRodUserConsentEntity.class,
|
||||
HotRodUserCredentialEntity.class,
|
||||
HotRodUserFederatedIdentityEntity.class,
|
||||
|
||||
// Common
|
||||
HotRodPair.class,
|
||||
HotRodAttributeEntity.class,
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.infinispan.protostream.annotations.ProtoDoc;
|
|||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||
import org.keycloak.models.map.group.MapGroupEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed;
|
||||
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
|
||||
|
@ -92,4 +93,14 @@ public class HotRodGroupEntity extends AbstractHotRodEntity {
|
|||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
@ProtoField(number = 8)
|
||||
public Set<String> grantedRoles;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return HotRodGroupEntityDelegate.entityEquals(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HotRodGroupEntityDelegate.entityHashCode(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.hotRod.user;
|
||||
|
||||
import org.infinispan.protostream.annotations.ProtoDoc;
|
||||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.user.MapUserConsentEntity")
|
||||
@ProtoDoc("@Indexed")
|
||||
public class HotRodUserConsentEntity extends AbstractHotRodEntity {
|
||||
@ProtoField(number = 1)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public String clientId;
|
||||
|
||||
@ProtoField(number = 2)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public Set<String> grantedClientScopesIds;
|
||||
|
||||
@ProtoField(number = 3)
|
||||
public Long createdDate;
|
||||
|
||||
@ProtoField(number = 4)
|
||||
public Long lastUpdatedDate;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return HotRodUserConsentEntityDelegate.entityEquals(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HotRodUserConsentEntityDelegate.entityHashCode(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.hotRod.user;
|
||||
|
||||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||
|
||||
@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.user.MapUserCredentialEntity")
|
||||
public class HotRodUserCredentialEntity extends AbstractHotRodEntity {
|
||||
|
||||
@ProtoField(number = 1)
|
||||
public String id;
|
||||
|
||||
@ProtoField(number = 2)
|
||||
public String type;
|
||||
|
||||
@ProtoField(number = 3)
|
||||
public String userLabel;
|
||||
|
||||
@ProtoField(number = 4)
|
||||
public Long createdDate;
|
||||
|
||||
@ProtoField(number = 5)
|
||||
public String secretData;
|
||||
|
||||
@ProtoField(number = 6)
|
||||
public String credentialData;
|
||||
|
||||
@ProtoField(number = 7)
|
||||
public Integer priority;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return HotRodUserCredentialEntityDelegate.entityEquals(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HotRodUserCredentialEntityDelegate.entityHashCode(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* 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.hotRod.user;
|
||||
|
||||
import org.infinispan.protostream.annotations.ProtoDoc;
|
||||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
|
||||
import org.keycloak.models.map.common.UpdatableEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
|
||||
import org.keycloak.models.map.user.MapUserConsentEntity;
|
||||
import org.keycloak.models.map.user.MapUserCredentialEntity;
|
||||
import org.keycloak.models.map.user.MapUserEntity;
|
||||
import org.keycloak.models.map.user.MapUserFederatedIdentityEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@GenerateHotRodEntityImplementation(
|
||||
implementInterface = "org.keycloak.models.map.user.MapUserEntity",
|
||||
inherits = "org.keycloak.models.map.storage.hotRod.user.HotRodUserEntity.AbstractHotRodUserEntityDelegate"
|
||||
)
|
||||
@ProtoDoc("@Indexed")
|
||||
public class HotRodUserEntity extends AbstractHotRodEntity {
|
||||
|
||||
@IgnoreForEntityImplementationGenerator
|
||||
private static final Logger LOG = Logger.getLogger(HotRodUserEntity.class);
|
||||
|
||||
@ProtoField(number = 1, required = true)
|
||||
public int entityVersion = 1;
|
||||
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
@ProtoField(number = 2, required = true)
|
||||
public String id;
|
||||
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
@ProtoField(number = 3)
|
||||
public String realmId;
|
||||
|
||||
@ProtoField(number = 4)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public String username;
|
||||
|
||||
@ProtoField(number = 22)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public String usernameLowercase;
|
||||
|
||||
@ProtoField(number = 5)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = \"filename\"))")
|
||||
public String firstName;
|
||||
|
||||
@ProtoField(number = 6)
|
||||
public Long createdTimestamp;
|
||||
|
||||
@ProtoField(number = 7)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = \"filename\"))")
|
||||
public String lastName;
|
||||
|
||||
@ProtoField(number = 8)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = \"filename\"))")
|
||||
public String email;
|
||||
|
||||
@ProtoField(number = 9)
|
||||
/**
|
||||
* TODO: Workaround for ISPN-8584
|
||||
*
|
||||
* This index shouldn't be there as majority of object will be enabled == true
|
||||
*
|
||||
* When this index is missing Ickle queries like following:
|
||||
* FROM kc.HotRodUserEntity c WHERE (c.realmId = "admin-client-test" AND c.enabled = true AND c.email : "user*")
|
||||
* fail with:
|
||||
* Error: {"error":{"message":"Error executing search","cause":"Unexpected condition type (FullTextTermExpr): PROP(email):'user*'"}}
|
||||
*
|
||||
* In other words it is not possible to combine searching for Analyzed field and non-indexed field in one Ickle query
|
||||
*/
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public Boolean enabled;
|
||||
|
||||
@ProtoField(number = 10)
|
||||
/**
|
||||
* TODO: Workaround for ISPN-8584
|
||||
*
|
||||
* When this index is missing Ickle queries like following:
|
||||
* FROM kc.HotRodUserEntity c WHERE (c.realmId = "admin-client-test" AND c.enabled = true AND c.email : "user*")
|
||||
* fail with:
|
||||
* Error: {"error":{"message":"Error executing search","cause":"Unexpected condition type (FullTextTermExpr): PROP(email):'user*'"}}
|
||||
*
|
||||
* In other words it is not possible to combine searching for Analyzed field and non-indexed field in one Ickle query
|
||||
*/
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public Boolean emailVerified;
|
||||
|
||||
// This is necessary to be able to dynamically switch unique email constraints on and off in the realm settings
|
||||
@ProtoField(number = 11)
|
||||
public String emailConstraint;
|
||||
|
||||
@ProtoField(number = 12)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public Set<HotRodAttributeEntity> attributes;
|
||||
|
||||
@ProtoField(number = 13)
|
||||
public Set<String> requiredActions;
|
||||
|
||||
@ProtoField(number = 14)
|
||||
public List<HotRodUserCredentialEntity> credentials;
|
||||
|
||||
@ProtoField(number = 15)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public Set<HotRodUserFederatedIdentityEntity> federatedIdentities;
|
||||
|
||||
@ProtoField(number = 16)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public Set<HotRodUserConsentEntity> userConsents;
|
||||
|
||||
@ProtoField(number = 17)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public Set<String> groupsMembership = new HashSet<>();
|
||||
|
||||
@ProtoField(number = 18)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public Set<String> rolesMembership = new HashSet<>();
|
||||
|
||||
@ProtoField(number = 19)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public String federationLink;
|
||||
|
||||
@ProtoField(number = 20)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public String serviceAccountClientLink;
|
||||
|
||||
@ProtoField(number = 21)
|
||||
public Integer notBefore;
|
||||
|
||||
public static abstract class AbstractHotRodUserEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodUserEntity> implements MapUserEntity {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return getHotRodEntity().id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
HotRodUserEntity entity = getHotRodEntity();
|
||||
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
|
||||
entity.id = id;
|
||||
entity.updated |= id != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmail(String email, boolean duplicateEmailsAllowed) {
|
||||
this.setEmail(email);
|
||||
this.setEmailConstraint(email == null || duplicateEmailsAllowed ? KeycloakModelUtils.generateId() : email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
HotRodUserEntity entity = getHotRodEntity();
|
||||
entity.updated |= ! Objects.equals(entity.username, username);
|
||||
entity.username = username;
|
||||
entity.usernameLowercase = username == null ? null : username.toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
return getHotRodEntity().updated
|
||||
|| Optional.ofNullable(getUserConsents()).orElseGet(Collections::emptySet).stream().anyMatch(MapUserConsentEntity::isUpdated)
|
||||
|| Optional.ofNullable(getCredentials()).orElseGet(Collections::emptyList).stream().anyMatch(MapUserCredentialEntity::isUpdated)
|
||||
|| Optional.ofNullable(getFederatedIdentities()).orElseGet(Collections::emptySet).stream().anyMatch(MapUserFederatedIdentityEntity::isUpdated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearUpdatedFlag() {
|
||||
getHotRodEntity().updated = false;
|
||||
Optional.ofNullable(getUserConsents()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
|
||||
Optional.ofNullable(getCredentials()).orElseGet(Collections::emptyList).forEach(UpdatableEntity::clearUpdatedFlag);
|
||||
Optional.ofNullable(getFederatedIdentities()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MapUserConsentEntity> getUserConsent(String clientId) {
|
||||
Set<HotRodUserConsentEntity> ucs = getHotRodEntity().userConsents;
|
||||
if (ucs == null || ucs.isEmpty()) return Optional.empty();
|
||||
|
||||
return ucs.stream().filter(uc -> Objects.equals(uc.clientId, clientId)).findFirst().map(HotRodUserConsentEntityDelegate::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean removeUserConsent(String clientId) {
|
||||
Set<HotRodUserConsentEntity> consents = getHotRodEntity().userConsents;
|
||||
boolean removed = consents != null && consents.removeIf(uc -> Objects.equals(uc.clientId, clientId));
|
||||
getHotRodEntity().updated |= removed;
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MapUserCredentialEntity> getCredential(String id) {
|
||||
List<HotRodUserCredentialEntity> uce = getHotRodEntity().credentials;
|
||||
if (uce == null || uce.isEmpty()) return Optional.empty();
|
||||
|
||||
return uce.stream().filter(uc -> Objects.equals(uc.id, id)).findFirst().map(HotRodUserCredentialEntityDelegate::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean removeCredential(String id) {
|
||||
List<HotRodUserCredentialEntity> credentials = getHotRodEntity().credentials;
|
||||
boolean removed = credentials != null && credentials.removeIf(c -> Objects.equals(c.id, id));
|
||||
getHotRodEntity().updated |= removed;
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean moveCredential(String credentialId, String newPreviousCredentialId) {
|
||||
// 1 - Get all credentials from the entity.
|
||||
List<HotRodUserCredentialEntity> credentialsList = getHotRodEntity().credentials;
|
||||
|
||||
// 2 - Find indexes of our and newPrevious credential
|
||||
int ourCredentialIndex = -1;
|
||||
int newPreviousCredentialIndex = -1;
|
||||
HotRodUserCredentialEntity ourCredential = null;
|
||||
int i = 0;
|
||||
for (HotRodUserCredentialEntity credential : credentialsList) {
|
||||
if (credentialId.equals(credential.id)) {
|
||||
ourCredentialIndex = i;
|
||||
ourCredential = credential;
|
||||
} else if(newPreviousCredentialId != null && newPreviousCredentialId.equals(credential.id)) {
|
||||
newPreviousCredentialIndex = i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (ourCredentialIndex == -1) {
|
||||
LOG.warnf("Not found credential with id [%s] of user [%s]", credentialId, getUsername());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newPreviousCredentialId != null && newPreviousCredentialIndex == -1) {
|
||||
LOG.warnf("Can't move up credential with id [%s] of user [%s]", credentialId, getUsername());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3 - Compute index where we move our credential
|
||||
int toMoveIndex = newPreviousCredentialId==null ? 0 : newPreviousCredentialIndex + 1;
|
||||
|
||||
// 4 - Insert our credential to new position, remove it from the old position
|
||||
if (toMoveIndex == ourCredentialIndex) return true;
|
||||
credentialsList.add(toMoveIndex, ourCredential);
|
||||
int indexToRemove = toMoveIndex < ourCredentialIndex ? ourCredentialIndex + 1 : ourCredentialIndex;
|
||||
credentialsList.remove(indexToRemove);
|
||||
|
||||
getHotRodEntity().updated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MapUserFederatedIdentityEntity> getFederatedIdentity(String identityProviderId) {
|
||||
Set<HotRodUserFederatedIdentityEntity> fes = getHotRodEntity().federatedIdentities;
|
||||
if (fes == null || fes.isEmpty()) return Optional.empty();
|
||||
|
||||
return fes.stream().filter(fi -> Objects.equals(fi.identityProvider, identityProviderId)).findFirst().map(HotRodUserFederatedIdentityEntityDelegate::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean removeFederatedIdentity(String identityProviderId) {
|
||||
Set<HotRodUserFederatedIdentityEntity> federatedIdentities = getHotRodEntity().federatedIdentities;
|
||||
boolean removed = federatedIdentities != null && federatedIdentities.removeIf(fi -> Objects.equals(fi.identityProvider, identityProviderId));
|
||||
getHotRodEntity().updated |= removed;
|
||||
return removed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return HotRodUserEntityDelegate.entityEquals(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HotRodUserEntityDelegate.entityHashCode(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.hotRod.user;
|
||||
|
||||
import org.infinispan.protostream.annotations.ProtoDoc;
|
||||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||
|
||||
@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.user.MapUserFederatedIdentityEntity")
|
||||
@ProtoDoc("@Indexed")
|
||||
public class HotRodUserFederatedIdentityEntity extends AbstractHotRodEntity {
|
||||
|
||||
@ProtoField(number = 1)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public String identityProvider;
|
||||
|
||||
@ProtoField(number = 2)
|
||||
public String token;
|
||||
|
||||
@ProtoField(number = 3)
|
||||
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||
public String userId;
|
||||
|
||||
@ProtoField(number = 4)
|
||||
public String userName;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return HotRodUserFederatedIdentityEntityDelegate.entityEquals(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HotRodUserFederatedIdentityEntityDelegate.entityHashCode(this);
|
||||
}
|
||||
}
|
|
@ -20,5 +20,15 @@
|
|||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="users" mode="SYNC">
|
||||
<indexing>
|
||||
<indexed-entities>
|
||||
<indexed-entity>kc.HotRodUserEntity</indexed-entity>
|
||||
<indexed-entity>kc.HotRodAttributeEntity</indexed-entity>
|
||||
<indexed-entity>kc.HotRodUserFederatedIdentityEntity</indexed-entity>
|
||||
</indexed-entities>
|
||||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
</infinispan>
|
|
@ -22,5 +22,15 @@
|
|||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="users" mode="SYNC">
|
||||
<indexing>
|
||||
<indexed-entities>
|
||||
<indexed-entity>kc.HotRodUserEntity</indexed-entity>
|
||||
<indexed-entity>kc.HotRodAttributeEntity</indexed-entity>
|
||||
<indexed-entity>kc.HotRodUserFederatedIdentityEntity</indexed-entity>
|
||||
</indexed-entities>
|
||||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
</infinispan>
|
||||
|
|
|
@ -19,10 +19,11 @@ package org.keycloak.models.map.storage.hotRod;
|
|||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntityDelegate;
|
||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntity;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
|
@ -69,4 +70,23 @@ public class IckleQueryMapModelCriteriaBuilderTest {
|
|||
assertThat(mcb.getParameters().entrySet(), hasSize(2));
|
||||
assertThat(mcb.getParameters(), allOf(hasEntry("clientId0", 4), hasEntry("id0", 5)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUser() {
|
||||
final DefaultModelCriteria<UserModel> mcb = criteria();
|
||||
DefaultModelCriteria<UserModel> criteria = mcb.compare(UserModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, "realm1");
|
||||
criteria = criteria.compare(UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT, ModelCriteriaBuilder.Operator.NOT_EXISTS);
|
||||
criteria = mcb.and(criteria, mcb.or(
|
||||
mcb.compare(UserModel.SearchableFields.USERNAME, ModelCriteriaBuilder.Operator.ILIKE, "a"),
|
||||
mcb.compare(UserModel.SearchableFields.EMAIL, ModelCriteriaBuilder.Operator.ILIKE, "a"),
|
||||
mcb.compare(UserModel.SearchableFields.FIRST_NAME, ModelCriteriaBuilder.Operator.ILIKE, "a"),
|
||||
mcb.compare(UserModel.SearchableFields.LAST_NAME, ModelCriteriaBuilder.Operator.ILIKE, "a")
|
||||
));
|
||||
|
||||
IckleQueryMapModelCriteriaBuilder<HotRodUserEntity, UserModel> ickle = criteria.flashToModelCriteriaBuilder(new IckleQueryMapModelCriteriaBuilder<>(HotRodUserEntity.class));
|
||||
|
||||
assertThat(ickle.getIckleQuery(), is(equalTo("FROM kc.HotRodUserEntity c WHERE ((c.realmId = :realmId0) AND (c.serviceAccountClientLink IS NULL OR c.serviceAccountClientLink IS EMPTY) AND ((c.usernameLowercase LIKE :usernameLowercase0) OR (c.email : 'a') OR (c.firstName : 'a') OR (c.lastName : 'a')))")));
|
||||
assertThat(ickle.getParameters().entrySet(), hasSize(2));
|
||||
assertThat(ickle.getParameters(), allOf(hasEntry("realmId0", "realm1"), hasEntry("usernameLowercase0", "a")));
|
||||
}
|
||||
}
|
|
@ -200,12 +200,6 @@ public interface MapUserEntity extends UpdatableEntity, AbstractEntity, EntityWi
|
|||
String getEmailConstraint();
|
||||
void setEmailConstraint(String emailConstraint);
|
||||
|
||||
Map<String, List<String>> getAttributes();
|
||||
List<String> getAttribute(String name);
|
||||
void setAttributes(Map<String, List<String>> attributes);
|
||||
void setAttribute(String name, List<String> value);
|
||||
void removeAttribute(String name);
|
||||
|
||||
Set<String> getRequiredActions();
|
||||
void setRequiredActions(Set<String> requiredActions);
|
||||
void addRequiredAction(String requiredAction);
|
||||
|
|
|
@ -593,11 +593,11 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
public Stream<UserModel> searchForUserStream(RealmModel realm, Map<String, String> attributes, Integer firstResult, Integer maxResults) {
|
||||
LOG.tracef("searchForUserStream(%s, %s, %d, %d)%s", realm, attributes, firstResult, maxResults, getShortStackTrace());
|
||||
|
||||
DefaultModelCriteria<UserModel> mcb = criteria();
|
||||
mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
|
||||
final DefaultModelCriteria<UserModel> mcb = criteria();
|
||||
DefaultModelCriteria<UserModel> criteria = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
|
||||
|
||||
if (! session.getAttributeOrDefault(UserModel.INCLUDE_SERVICE_ACCOUNT, true)) {
|
||||
mcb = mcb.compare(SearchableFields.SERVICE_ACCOUNT_CLIENT, Operator.NOT_EXISTS);
|
||||
criteria = criteria.compare(SearchableFields.SERVICE_ACCOUNT_CLIENT, Operator.NOT_EXISTS);
|
||||
}
|
||||
|
||||
final boolean exactSearch = Boolean.parseBoolean(attributes.getOrDefault(UserModel.EXACT, Boolean.FALSE.toString()));
|
||||
|
@ -614,47 +614,54 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
|
||||
switch (key) {
|
||||
case UserModel.SEARCH:
|
||||
DefaultModelCriteria<UserModel> searchCriteria = null;
|
||||
for (String stringToSearch : value.split("\\s+")) {
|
||||
mcb = addSearchToModelCriteria(stringToSearch, mcb);
|
||||
if (searchCriteria == null) {
|
||||
searchCriteria = addSearchToModelCriteria(stringToSearch, mcb);
|
||||
} else {
|
||||
searchCriteria = mcb.and(searchCriteria, addSearchToModelCriteria(stringToSearch, mcb));
|
||||
}
|
||||
}
|
||||
|
||||
criteria = mcb.and(criteria, searchCriteria);
|
||||
break;
|
||||
case USERNAME:
|
||||
mcb = mcb.compare(SearchableFields.USERNAME, Operator.ILIKE, searchedString);
|
||||
criteria = criteria.compare(SearchableFields.USERNAME, Operator.ILIKE, searchedString);
|
||||
break;
|
||||
case FIRST_NAME:
|
||||
mcb = mcb.compare(SearchableFields.FIRST_NAME, Operator.ILIKE, searchedString);
|
||||
criteria = criteria.compare(SearchableFields.FIRST_NAME, Operator.ILIKE, searchedString);
|
||||
break;
|
||||
case LAST_NAME:
|
||||
mcb = mcb.compare(SearchableFields.LAST_NAME, Operator.ILIKE, searchedString);
|
||||
criteria = criteria.compare(SearchableFields.LAST_NAME, Operator.ILIKE, searchedString);
|
||||
break;
|
||||
case EMAIL:
|
||||
mcb = mcb.compare(SearchableFields.EMAIL, Operator.ILIKE, searchedString);
|
||||
criteria = criteria.compare(SearchableFields.EMAIL, Operator.ILIKE, searchedString);
|
||||
break;
|
||||
case EMAIL_VERIFIED: {
|
||||
boolean booleanValue = Boolean.parseBoolean(value);
|
||||
mcb = mcb.compare(SearchableFields.EMAIL_VERIFIED, Operator.EQ, booleanValue);
|
||||
criteria = criteria.compare(SearchableFields.EMAIL_VERIFIED, Operator.EQ, booleanValue);
|
||||
break;
|
||||
}
|
||||
case UserModel.ENABLED: {
|
||||
boolean booleanValue = Boolean.parseBoolean(value);
|
||||
mcb = mcb.compare(SearchableFields.ENABLED, Operator.EQ, booleanValue);
|
||||
criteria = criteria.compare(SearchableFields.ENABLED, Operator.EQ, booleanValue);
|
||||
break;
|
||||
}
|
||||
case UserModel.IDP_ALIAS: {
|
||||
if (!attributes.containsKey(UserModel.IDP_USER_ID)) {
|
||||
mcb = mcb.compare(SearchableFields.IDP_AND_USER, Operator.EQ, value);
|
||||
criteria = criteria.compare(SearchableFields.IDP_AND_USER, Operator.EQ, value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UserModel.IDP_USER_ID: {
|
||||
mcb = mcb.compare(SearchableFields.IDP_AND_USER, Operator.EQ, attributes.get(UserModel.IDP_ALIAS),
|
||||
criteria = criteria.compare(SearchableFields.IDP_AND_USER, Operator.EQ, attributes.get(UserModel.IDP_ALIAS),
|
||||
value);
|
||||
break;
|
||||
}
|
||||
case UserModel.EXACT:
|
||||
break;
|
||||
default:
|
||||
mcb = mcb.compare(SearchableFields.ATTRIBUTE, Operator.EQ, key, value);
|
||||
criteria = criteria.compare(SearchableFields.ATTRIBUTE, Operator.EQ, key, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -680,10 +687,10 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
return resourceStore.findByResourceServer(values, null, 0, 1).isEmpty();
|
||||
});
|
||||
|
||||
mcb = mcb.compare(SearchableFields.ASSIGNED_GROUP, Operator.IN, authorizedGroups);
|
||||
criteria = criteria.compare(SearchableFields.ASSIGNED_GROUP, Operator.IN, authorizedGroups);
|
||||
}
|
||||
|
||||
return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.USERNAME))
|
||||
return tx.read(withCriteria(criteria).pagination(firstResult, maxResults, SearchableFields.USERNAME))
|
||||
.map(entityToAdapterFunc(realm))
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
|
|
@ -205,4 +205,5 @@ public class DefaultModelCriteriaTest {
|
|||
.partiallyEvaluate((field, operator, operatorArguments) -> field == CLIENT_ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(6) ? true : null),
|
||||
hasToString("(clientId EQ [4] && id EQ [5])"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -260,7 +260,7 @@
|
|||
"username": "${keycloak.connectionsHotRod.username:myuser}",
|
||||
"password": "${keycloak.connectionsHotRod.password:qwer1234!}",
|
||||
"enableSecurity": "${keycloak.connectionsHotRod.enableSecurity:true}",
|
||||
"reindexCaches": "${keycloak.connectionsHotRod.reindexCaches:clients,groups}"
|
||||
"reindexCaches": "${keycloak.connectionsHotRod.reindexCaches:clients,groups,users}"
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1498,6 +1498,7 @@
|
|||
<systemPropertyVariables>
|
||||
<keycloak.client.map.storage.provider>hotrod</keycloak.client.map.storage.provider>
|
||||
<keycloak.group.map.storage.provider>hotrod</keycloak.group.map.storage.provider>
|
||||
<keycloak.user.map.storage.provider>hotrod</keycloak.user.map.storage.provider>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
|
|
@ -18,5 +18,15 @@
|
|||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="users" mode="SYNC">
|
||||
<indexing>
|
||||
<indexed-entities>
|
||||
<indexed-entity>kc.HotRodUserEntity</indexed-entity>
|
||||
<indexed-entity>kc.HotRodAttributeEntity</indexed-entity>
|
||||
<indexed-entity>kc.HotRodUserFederatedIdentityEntity</indexed-entity>
|
||||
</indexed-entities>
|
||||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
</infinispan>
|
||||
|
|
|
@ -75,12 +75,12 @@ public class HotRodMapStorage extends KeycloakModelParameters {
|
|||
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("group").provider(MapGroupProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("group").provider(MapGroupProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("realm").provider(MapRealmProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("role").provider(MapRoleProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(DeploymentStateSpi.NAME).provider(MapDeploymentStateProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(StoreFactorySpi.NAME).provider(MapAuthorizationStoreFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("user").provider(MapUserProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("user").provider(MapUserProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID).config("storage-user-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.config("storage-client-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
|
|
Loading…
Reference in a new issue