user fed spi querying tests
This commit is contained in:
parent
0315bd0b87
commit
72d134748c
16 changed files with 879 additions and 67 deletions
|
@ -17,7 +17,14 @@
|
||||||
|
|
||||||
package org.keycloak.common.util.reflections;
|
package org.keycloak.common.util.reflections;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.GenericArrayType;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
import java.lang.reflect.TypeVariable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for Types
|
* Utility class for Types
|
||||||
|
@ -68,4 +75,564 @@ public class Types {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the genericType of a certain class?
|
||||||
|
*/
|
||||||
|
public static boolean isA(Class clazz, ParameterizedType pType)
|
||||||
|
{
|
||||||
|
return clazz.isAssignableFrom((Class) pType.getRawType());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the index-th type argument.
|
||||||
|
*/
|
||||||
|
public static Class getArgumentType(ParameterizedType pType, int index)
|
||||||
|
{
|
||||||
|
return (Class) pType.getActualTypeArguments()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class getTemplateParameterOfInterface(Class base, Class desiredInterface)
|
||||||
|
{
|
||||||
|
Object rtn = searchForInterfaceTemplateParameter(base, desiredInterface);
|
||||||
|
if (rtn != null && rtn instanceof Class) return (Class) rtn;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Object searchForInterfaceTemplateParameter(Class base, Class desiredInterface)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < base.getInterfaces().length; i++)
|
||||||
|
{
|
||||||
|
Class intf = base.getInterfaces()[i];
|
||||||
|
if (intf.equals(desiredInterface))
|
||||||
|
{
|
||||||
|
Type generic = base.getGenericInterfaces()[i];
|
||||||
|
if (generic instanceof ParameterizedType)
|
||||||
|
{
|
||||||
|
ParameterizedType p = (ParameterizedType) generic;
|
||||||
|
Type type = p.getActualTypeArguments()[0];
|
||||||
|
Class rtn = getRawTypeNoException(type);
|
||||||
|
if (rtn != null) return rtn;
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (base.getSuperclass() == null || base.getSuperclass().equals(Object.class)) return null;
|
||||||
|
Object rtn = searchForInterfaceTemplateParameter(base.getSuperclass(), desiredInterface);
|
||||||
|
if (rtn == null || rtn instanceof Class) return rtn;
|
||||||
|
if (!(rtn instanceof TypeVariable)) return null;
|
||||||
|
|
||||||
|
String name = ((TypeVariable) rtn).getName();
|
||||||
|
int index = -1;
|
||||||
|
TypeVariable[] variables = base.getSuperclass().getTypeParameters();
|
||||||
|
if (variables == null || variables.length < 1) return null;
|
||||||
|
|
||||||
|
for (int i = 0; i < variables.length; i++)
|
||||||
|
{
|
||||||
|
if (variables[i].getName().equals(name)) index = i;
|
||||||
|
}
|
||||||
|
if (index == -1) return null;
|
||||||
|
|
||||||
|
|
||||||
|
Type genericSuperclass = base.getGenericSuperclass();
|
||||||
|
if (!(genericSuperclass instanceof ParameterizedType)) return null;
|
||||||
|
|
||||||
|
ParameterizedType pt = (ParameterizedType) genericSuperclass;
|
||||||
|
Type type = pt.getActualTypeArguments()[index];
|
||||||
|
|
||||||
|
Class clazz = getRawTypeNoException(type);
|
||||||
|
if (clazz != null) return clazz;
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See if the two methods are compatible, that is they have the same relative signature
|
||||||
|
*
|
||||||
|
* @param method
|
||||||
|
* @param intfMethod
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isCompatible(Method method, Method intfMethod)
|
||||||
|
{
|
||||||
|
if (method == intfMethod) return true;
|
||||||
|
|
||||||
|
if (!method.getName().equals(intfMethod.getName())) return false;
|
||||||
|
if (method.getParameterTypes().length != intfMethod.getParameterTypes().length) return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < method.getParameterTypes().length; i++)
|
||||||
|
{
|
||||||
|
Class rootParam = method.getParameterTypes()[i];
|
||||||
|
Class intfParam = intfMethod.getParameterTypes()[i];
|
||||||
|
if (!intfParam.isAssignableFrom(rootParam)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a method and a root class, find the actual method declared in the root that implements the method.
|
||||||
|
*
|
||||||
|
* @param clazz
|
||||||
|
* @param intfMethod
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Method getImplementingMethod(Class clazz, Method intfMethod)
|
||||||
|
{
|
||||||
|
Class<?> declaringClass = intfMethod.getDeclaringClass();
|
||||||
|
if (declaringClass.equals(clazz)) return intfMethod;
|
||||||
|
|
||||||
|
Class[] paramTypes = intfMethod.getParameterTypes();
|
||||||
|
|
||||||
|
if (declaringClass.getTypeParameters().length > 0 && paramTypes.length > 0)
|
||||||
|
{
|
||||||
|
Type[] intfTypes = findParameterizedTypes(clazz, declaringClass);
|
||||||
|
Map<String, Type> typeVarMap = new HashMap<String, Type>();
|
||||||
|
TypeVariable<? extends Class<?>>[] vars = declaringClass.getTypeParameters();
|
||||||
|
for (int i = 0; i < vars.length; i++)
|
||||||
|
{
|
||||||
|
if (intfTypes != null && i < intfTypes.length)
|
||||||
|
{
|
||||||
|
typeVarMap.put(vars[i].getName(), intfTypes[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Interface type parameters may not have been filled out
|
||||||
|
typeVarMap.put(vars[i].getName(), vars[i].getGenericDeclaration());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type[] paramGenericTypes = intfMethod.getGenericParameterTypes();
|
||||||
|
paramTypes = new Class[paramTypes.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < paramTypes.length; i++)
|
||||||
|
{
|
||||||
|
if (paramGenericTypes[i] instanceof TypeVariable)
|
||||||
|
{
|
||||||
|
TypeVariable tv = (TypeVariable)paramGenericTypes[i];
|
||||||
|
Type t = typeVarMap.get(tv.getName());
|
||||||
|
if (t == null)
|
||||||
|
{
|
||||||
|
throw new RuntimeException("Unable to resolve type variable");
|
||||||
|
}
|
||||||
|
paramTypes[i] = getRawType(t);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
paramTypes[i] = getRawType(paramGenericTypes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return clazz.getMethod(intfMethod.getName(), paramTypes);
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Method tmp = clazz.getMethod(intfMethod.getName(), intfMethod.getParameterTypes());
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
return intfMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Class<?> getRawType(Type type)
|
||||||
|
{
|
||||||
|
if (type instanceof Class<?>)
|
||||||
|
{
|
||||||
|
// type is a normal class.
|
||||||
|
return (Class<?>) type;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (type instanceof ParameterizedType)
|
||||||
|
{
|
||||||
|
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||||
|
Type rawType = parameterizedType.getRawType();
|
||||||
|
return (Class<?>) rawType;
|
||||||
|
}
|
||||||
|
else if (type instanceof GenericArrayType)
|
||||||
|
{
|
||||||
|
final GenericArrayType genericArrayType = (GenericArrayType) type;
|
||||||
|
final Class<?> componentRawType = getRawType(genericArrayType.getGenericComponentType());
|
||||||
|
return Array.newInstance(componentRawType, 0).getClass();
|
||||||
|
}
|
||||||
|
else if (type instanceof TypeVariable)
|
||||||
|
{
|
||||||
|
final TypeVariable typeVar = (TypeVariable) type;
|
||||||
|
if (typeVar.getBounds() != null && typeVar.getBounds().length > 0)
|
||||||
|
{
|
||||||
|
return getRawType(typeVar.getBounds()[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException("unable to determine base class");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Class<?> getRawTypeNoException(Type type)
|
||||||
|
{
|
||||||
|
if (type instanceof Class<?>)
|
||||||
|
{
|
||||||
|
// type is a normal class.
|
||||||
|
return (Class<?>) type;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (type instanceof ParameterizedType)
|
||||||
|
{
|
||||||
|
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||||
|
Type rawType = parameterizedType.getRawType();
|
||||||
|
return (Class<?>) rawType;
|
||||||
|
}
|
||||||
|
else if (type instanceof GenericArrayType)
|
||||||
|
{
|
||||||
|
final GenericArrayType genericArrayType = (GenericArrayType) type;
|
||||||
|
final Class<?> componentRawType = getRawType(genericArrayType.getGenericComponentType());
|
||||||
|
return Array.newInstance(componentRawType, 0).getClass();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type argument from a parameterized type
|
||||||
|
*
|
||||||
|
* @param genericType
|
||||||
|
* @return null if there is no type parameter
|
||||||
|
*/
|
||||||
|
public static Class<?> getTypeArgument(Type genericType)
|
||||||
|
{
|
||||||
|
if (!(genericType instanceof ParameterizedType)) return null;
|
||||||
|
ParameterizedType parameterizedType = (ParameterizedType) genericType;
|
||||||
|
Class<?> typeArg = (Class<?>) parameterizedType.getActualTypeArguments()[0];
|
||||||
|
return typeArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Class getCollectionBaseType(Class type, Type genericType)
|
||||||
|
{
|
||||||
|
if (genericType instanceof ParameterizedType)
|
||||||
|
{
|
||||||
|
ParameterizedType parameterizedType = (ParameterizedType) genericType;
|
||||||
|
Type componentGenericType = parameterizedType.getActualTypeArguments()[0];
|
||||||
|
return getRawType(componentGenericType);
|
||||||
|
}
|
||||||
|
else if (genericType instanceof GenericArrayType)
|
||||||
|
{
|
||||||
|
final GenericArrayType genericArrayType = (GenericArrayType) genericType;
|
||||||
|
Type componentGenericType = genericArrayType.getGenericComponentType();
|
||||||
|
return getRawType(componentGenericType);
|
||||||
|
}
|
||||||
|
else if (type.isArray())
|
||||||
|
{
|
||||||
|
return type.getComponentType();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Class getMapKeyType(Type genericType)
|
||||||
|
{
|
||||||
|
if (genericType instanceof ParameterizedType)
|
||||||
|
{
|
||||||
|
ParameterizedType parameterizedType = (ParameterizedType) genericType;
|
||||||
|
Type componentGenericType = parameterizedType.getActualTypeArguments()[0];
|
||||||
|
return getRawType(componentGenericType);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class getMapValueType(Type genericType)
|
||||||
|
{
|
||||||
|
if (genericType instanceof ParameterizedType)
|
||||||
|
{
|
||||||
|
ParameterizedType parameterizedType = (ParameterizedType) genericType;
|
||||||
|
Type componentGenericType = parameterizedType.getActualTypeArguments()[1];
|
||||||
|
return getRawType(componentGenericType);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type resolveTypeVariables(Class<?> root, Type type)
|
||||||
|
{
|
||||||
|
if (type instanceof TypeVariable)
|
||||||
|
{
|
||||||
|
Type newType = resolveTypeVariable(root, (TypeVariable)type);
|
||||||
|
return (newType == null) ? type : newType;
|
||||||
|
}
|
||||||
|
else if (type instanceof ParameterizedType)
|
||||||
|
{
|
||||||
|
final ParameterizedType param = (ParameterizedType)type;
|
||||||
|
final Type[] actuals = new Type[param.getActualTypeArguments().length];
|
||||||
|
for (int i = 0; i < actuals.length; i++)
|
||||||
|
{
|
||||||
|
Type newType = resolveTypeVariables(root, param.getActualTypeArguments()[i]);
|
||||||
|
actuals[i] = newType == null ? param.getActualTypeArguments()[i] : newType;
|
||||||
|
}
|
||||||
|
return new ParameterizedType() {
|
||||||
|
@Override
|
||||||
|
public Type[] getActualTypeArguments()
|
||||||
|
{
|
||||||
|
return actuals;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type getRawType()
|
||||||
|
{
|
||||||
|
return param.getRawType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type getOwnerType()
|
||||||
|
{
|
||||||
|
return param.getOwnerType();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (type instanceof GenericArrayType)
|
||||||
|
{
|
||||||
|
GenericArrayType arrayType = (GenericArrayType)type;
|
||||||
|
final Type componentType = resolveTypeVariables(root, arrayType.getGenericComponentType());
|
||||||
|
if (componentType == null) return type;
|
||||||
|
return new GenericArrayType()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Type getGenericComponentType()
|
||||||
|
{
|
||||||
|
return componentType;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an actual value of a type variable. The method looks in a class hierarchy for a class defining the variable
|
||||||
|
* and returns the value if present.
|
||||||
|
*
|
||||||
|
* @param root
|
||||||
|
* @param typeVariable
|
||||||
|
* @return actual type of the type variable
|
||||||
|
*/
|
||||||
|
public static Type resolveTypeVariable(Class<?> root, TypeVariable<?> typeVariable)
|
||||||
|
{
|
||||||
|
if (typeVariable.getGenericDeclaration() instanceof Class<?>)
|
||||||
|
{
|
||||||
|
Class<?> classDeclaringTypeVariable = (Class<?>) typeVariable.getGenericDeclaration();
|
||||||
|
Type[] types = findParameterizedTypes(root, classDeclaringTypeVariable);
|
||||||
|
if (types == null) return null;
|
||||||
|
for (int i = 0; i < types.length; i++)
|
||||||
|
{
|
||||||
|
TypeVariable<?> tv = classDeclaringTypeVariable.getTypeParameters()[i];
|
||||||
|
if (tv.equals(typeVariable))
|
||||||
|
{
|
||||||
|
return types[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a class and an interfaces, go through the class hierarchy to find the interface and return its type arguments.
|
||||||
|
*
|
||||||
|
* @param classToSearch
|
||||||
|
* @param interfaceToFind
|
||||||
|
* @return type arguments of the interface
|
||||||
|
*/
|
||||||
|
public static Type[] getActualTypeArgumentsOfAnInterface(Class<?> classToSearch, Class<?> interfaceToFind)
|
||||||
|
{
|
||||||
|
Type[] types = findParameterizedTypes(classToSearch, interfaceToFind);
|
||||||
|
if (types == null) throw new RuntimeException("Unable to find type arguments");
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Type[] EMPTY_TYPE_ARRAY = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the given interface or class within the root's class/interface hierarchy.
|
||||||
|
* If the searched for class/interface is a generic return an array of real types that fill it out.
|
||||||
|
*
|
||||||
|
* @param root
|
||||||
|
* @param searchedFor
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Type[] findParameterizedTypes(Class<?> root, Class<?> searchedFor)
|
||||||
|
{
|
||||||
|
if (searchedFor.isInterface())
|
||||||
|
{
|
||||||
|
return findInterfaceParameterizedTypes(root, null, searchedFor);
|
||||||
|
}
|
||||||
|
return findClassParameterizedTypes(root, null, searchedFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type[] findClassParameterizedTypes(Class<?> root, ParameterizedType rootType, Class<?> searchedForClass)
|
||||||
|
{
|
||||||
|
if (Object.class.equals(root)) return null;
|
||||||
|
|
||||||
|
Map<String, Type> typeVarMap = populateParameterizedMap(root, rootType);
|
||||||
|
|
||||||
|
Class<?> superclass = root.getSuperclass();
|
||||||
|
Type genericSuper = root.getGenericSuperclass();
|
||||||
|
|
||||||
|
if (superclass.equals(searchedForClass))
|
||||||
|
{
|
||||||
|
return extractTypes(typeVarMap, genericSuper);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (genericSuper instanceof ParameterizedType)
|
||||||
|
{
|
||||||
|
ParameterizedType intfParam = (ParameterizedType) genericSuper;
|
||||||
|
Type[] types = findClassParameterizedTypes(superclass, intfParam, searchedForClass);
|
||||||
|
if (types != null)
|
||||||
|
{
|
||||||
|
return extractTypeVariables(typeVarMap, types);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Type[] types = findClassParameterizedTypes(superclass, null, searchedForClass);
|
||||||
|
if (types != null)
|
||||||
|
{
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Type> populateParameterizedMap(Class<?> root, ParameterizedType rootType)
|
||||||
|
{
|
||||||
|
Map<String, Type> typeVarMap = new HashMap<String, Type>();
|
||||||
|
if (rootType != null)
|
||||||
|
{
|
||||||
|
TypeVariable<? extends Class<?>>[] vars = root.getTypeParameters();
|
||||||
|
for (int i = 0; i < vars.length; i++)
|
||||||
|
{
|
||||||
|
typeVarMap.put(vars[i].getName(), rootType.getActualTypeArguments()[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return typeVarMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Type[] findInterfaceParameterizedTypes(Class<?> root, ParameterizedType rootType, Class<?> searchedForInterface)
|
||||||
|
{
|
||||||
|
Map<String, Type> typeVarMap = populateParameterizedMap(root, rootType);
|
||||||
|
|
||||||
|
for (int i = 0; i < root.getInterfaces().length; i++)
|
||||||
|
{
|
||||||
|
Class<?> sub = root.getInterfaces()[i];
|
||||||
|
Type genericSub = root.getGenericInterfaces()[i];
|
||||||
|
if (sub.equals(searchedForInterface))
|
||||||
|
{
|
||||||
|
return extractTypes(typeVarMap, genericSub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < root.getInterfaces().length; i++)
|
||||||
|
{
|
||||||
|
Type genericSub = root.getGenericInterfaces()[i];
|
||||||
|
Class<?> sub = root.getInterfaces()[i];
|
||||||
|
|
||||||
|
Type[] types = recurseSuperclassForInterface(searchedForInterface, typeVarMap, genericSub, sub);
|
||||||
|
if (types != null) return types;
|
||||||
|
}
|
||||||
|
if (root.isInterface()) return null;
|
||||||
|
|
||||||
|
Class<?> superclass = root.getSuperclass();
|
||||||
|
Type genericSuper = root.getGenericSuperclass();
|
||||||
|
|
||||||
|
|
||||||
|
return recurseSuperclassForInterface(searchedForInterface, typeVarMap, genericSuper, superclass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type[] recurseSuperclassForInterface(Class<?> searchedForInterface, Map<String, Type> typeVarMap, Type genericSub, Class<?> sub)
|
||||||
|
{
|
||||||
|
if (genericSub instanceof ParameterizedType)
|
||||||
|
{
|
||||||
|
ParameterizedType intfParam = (ParameterizedType) genericSub;
|
||||||
|
Type[] types = findInterfaceParameterizedTypes(sub, intfParam, searchedForInterface);
|
||||||
|
if (types != null)
|
||||||
|
{
|
||||||
|
return extractTypeVariables(typeVarMap, types);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Type[] types = findInterfaceParameterizedTypes(sub, null, searchedForInterface);
|
||||||
|
if (types != null)
|
||||||
|
{
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type[] extractTypeVariables(Map<String, Type> typeVarMap, Type[] types)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < types.length; j++)
|
||||||
|
{
|
||||||
|
if (types[j] instanceof TypeVariable)
|
||||||
|
{
|
||||||
|
TypeVariable tv = (TypeVariable) types[j];
|
||||||
|
types[j] = typeVarMap.get(tv.getName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
types[j] = types[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type[] extractTypes(Map<String, Type> typeVarMap, Type genericSub)
|
||||||
|
{
|
||||||
|
if (genericSub instanceof ParameterizedType)
|
||||||
|
{
|
||||||
|
ParameterizedType param = (ParameterizedType) genericSub;
|
||||||
|
Type[] types = param.getActualTypeArguments();
|
||||||
|
Type[] returnTypes = new Type[types.length];
|
||||||
|
System.arraycopy(types, 0, returnTypes, 0, types.length);
|
||||||
|
extractTypeVariables(typeVarMap, returnTypes);
|
||||||
|
return returnTypes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return EMPTY_TYPE_ARRAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grabs the parameterized type of fromInterface
|
||||||
|
* that object implements and sees if it is assignable from type.
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @param object
|
||||||
|
* @param fromInterface
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static <T> boolean supports(Class<T> type, Object object, Class<?> fromInterface) {
|
||||||
|
Type providerType = getActualTypeArgumentsOfAnInterface(object.getClass(), fromInterface)[0];
|
||||||
|
Class providerClass = getRawType(providerType);
|
||||||
|
return type.isAssignableFrom(providerClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -147,6 +147,15 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getUsersByUserAttribute(RealmModel realm, String name, String value) {
|
||||||
|
TypedQuery<String> query = em.createNamedQuery("getFederatedAttributesByNameAndValue", String.class)
|
||||||
|
.setParameter("realmId", realm.getId())
|
||||||
|
.setParameter("name", name)
|
||||||
|
.setParameter("value", value);
|
||||||
|
return query.getResultList();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUserByFederatedIdentity(FederatedIdentityModel link, RealmModel realm) {
|
public String getUserByFederatedIdentity(FederatedIdentityModel link, RealmModel realm) {
|
||||||
TypedQuery<String> query = em.createNamedQuery("findUserByBrokerLinkAndRealm", String.class)
|
TypedQuery<String> query = em.createNamedQuery("findUserByBrokerLinkAndRealm", String.class)
|
||||||
|
@ -508,6 +517,15 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getMembership(RealmModel realm, GroupModel group, int firstResult, int max) {
|
||||||
|
TypedQuery<String> query = em.createNamedQuery("fedgroupMembership", String.class)
|
||||||
|
.setParameter("realmId", realm.getId())
|
||||||
|
.setParameter("groupId", group.getId());
|
||||||
|
query.setFirstResult(firstResult);
|
||||||
|
query.setMaxResults(max);
|
||||||
|
return query.getResultList();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getRequiredActions(RealmModel realm, UserModel user) {
|
public Set<String> getRequiredActions(RealmModel realm, UserModel user) {
|
||||||
|
@ -689,7 +707,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
.setParameter("userId", user.getId())
|
.setParameter("userId", user.getId())
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.executeUpdate();
|
.executeUpdate();
|
||||||
em.createNamedQuery("getFederatedUserRequiredActionsByUser")
|
em.createNamedQuery("deleteFederatedUserRequiredActionsByUser")
|
||||||
.setParameter("userId", user.getId())
|
.setParameter("userId", user.getId())
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.executeUpdate();
|
.executeUpdate();
|
||||||
|
|
|
@ -32,7 +32,7 @@ import javax.persistence.Table;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
@NamedQuery(name="getFederatedAttributesByNameAndValue", query="select attr from FederatedUserAttributeEntity attr where attr.name = :name and attr.value = :value and attr.realmId=:realmId"),
|
@NamedQuery(name="getFederatedAttributesByNameAndValue", query="select attr.userId from FederatedUserAttributeEntity attr where attr.name = :name and attr.value = :value and attr.realmId=:realmId"),
|
||||||
@NamedQuery(name="getFederatedAttributesByUser", query="select attr from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.realmId=:realmId"),
|
@NamedQuery(name="getFederatedAttributesByUser", query="select attr from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.realmId=:realmId"),
|
||||||
@NamedQuery(name="deleteUserFederatedAttributesByUser", query="delete from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.realmId=:realmId"),
|
@NamedQuery(name="deleteUserFederatedAttributesByUser", query="delete from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.realmId=:realmId"),
|
||||||
@NamedQuery(name="deleteUserFederatedAttributesByUserAndName", query="delete from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.name=:name and attr.realmId=:realmId"),
|
@NamedQuery(name="deleteUserFederatedAttributesByUserAndName", query="delete from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.name=:name and attr.realmId=:realmId"),
|
||||||
|
|
|
@ -35,7 +35,7 @@ import java.io.Serializable;
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
@NamedQuery(name="feduserMemberOf", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId and m.groupId = :groupId"),
|
@NamedQuery(name="feduserMemberOf", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId and m.groupId = :groupId"),
|
||||||
@NamedQuery(name="feduserGroupMembership", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId"),
|
@NamedQuery(name="feduserGroupMembership", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId"),
|
||||||
@NamedQuery(name="fedgroupMembership", query="select g.userId from FederatedUserGroupMembershipEntity g where g.groupId = :groupId"),
|
@NamedQuery(name="fedgroupMembership", query="select g.userId from FederatedUserGroupMembershipEntity g where g.groupId = :groupId and g.realmId = :realmId"),
|
||||||
@NamedQuery(name="feduserGroupIds", query="select m.groupId from FederatedUserGroupMembershipEntity m where m.userId = :userId"),
|
@NamedQuery(name="feduserGroupIds", query="select m.groupId from FederatedUserGroupMembershipEntity m where m.userId = :userId"),
|
||||||
@NamedQuery(name="deleteFederatedUserGroupMembershipByRealm", query="delete from FederatedUserGroupMembershipEntity mapping where mapping.realmId=:realmId"),
|
@NamedQuery(name="deleteFederatedUserGroupMembershipByRealm", query="delete from FederatedUserGroupMembershipEntity mapping where mapping.realmId=:realmId"),
|
||||||
@NamedQuery(name="deleteFederatedUserGroupMembershipByStorageProvider", query="delete from FederatedUserGroupMembershipEntity e where e.storageProviderId=:storageProviderId"),
|
@NamedQuery(name="deleteFederatedUserGroupMembershipByStorageProvider", query="delete from FederatedUserGroupMembershipEntity e where e.storageProviderId=:storageProviderId"),
|
||||||
|
|
|
@ -37,6 +37,7 @@ import java.io.Serializable;
|
||||||
*/
|
*/
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
@NamedQuery(name="getFederatedUserRequiredActionsByUser", query="select action from FederatedUserRequiredActionEntity action where action.userId = :userId and action.realmId=:realmId"),
|
@NamedQuery(name="getFederatedUserRequiredActionsByUser", query="select action from FederatedUserRequiredActionEntity action where action.userId = :userId and action.realmId=:realmId"),
|
||||||
|
@NamedQuery(name="deleteFederatedUserRequiredActionsByUser", query="delete from FederatedUserRequiredActionEntity action where action.realmId=:realmId and action.userId = :userId"),
|
||||||
@NamedQuery(name="deleteFederatedUserRequiredActionsByRealm", query="delete from FederatedUserRequiredActionEntity action where action.realmId=:realmId"),
|
@NamedQuery(name="deleteFederatedUserRequiredActionsByRealm", query="delete from FederatedUserRequiredActionEntity action where action.realmId=:realmId"),
|
||||||
@NamedQuery(name="deleteFederatedUserRequiredActionsByStorageProvider", query="delete from FederatedUserRequiredActionEntity e where e.storageProviderId=:storageProviderId"),
|
@NamedQuery(name="deleteFederatedUserRequiredActionsByStorageProvider", query="delete from FederatedUserRequiredActionEntity e where e.storageProviderId=:storageProviderId"),
|
||||||
@NamedQuery(name="deleteFederatedUserRequiredActionsByRealmAndLink", query="delete from FederatedUserRequiredActionEntity action where action.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
|
@NamedQuery(name="deleteFederatedUserRequiredActionsByRealmAndLink", query="delete from FederatedUserRequiredActionEntity action where action.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
|
||||||
|
|
|
@ -26,8 +26,7 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface StorageProviderFactory extends ProviderFactory<StorageProvider> {
|
public interface StorageProviderFactory<T extends StorageProvider> extends ProviderFactory<StorageProvider> {
|
||||||
boolean supports(Class<?> type);
|
|
||||||
/**
|
/**
|
||||||
* called per Keycloak transaction.
|
* called per Keycloak transaction.
|
||||||
*
|
*
|
||||||
|
@ -35,14 +34,7 @@ public interface StorageProviderFactory extends ProviderFactory<StorageProvider>
|
||||||
* @param model
|
* @param model
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
StorageProvider getInstance(KeycloakSession session, StorageProviderModel model);
|
T getInstance(KeycloakSession session, StorageProviderModel model);
|
||||||
|
|
||||||
/**
|
|
||||||
* Config options to display in generic admin console page for federated
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
Set<String> getConfigurationOptions();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the name of the provider and will be showed in the admin console as an option.
|
* This is the name of the provider and will be showed in the admin console as an option.
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.storage;
|
package org.keycloak.storage;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.reflections.Types;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
|
@ -42,6 +43,7 @@ import org.keycloak.models.utils.CredentialValidation;
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -79,18 +81,20 @@ public class UserStorageManager implements UserProvider {
|
||||||
protected <T> T getFirstStorageProvider(RealmModel realm, Class<T> type) {
|
protected <T> T getFirstStorageProvider(RealmModel realm, Class<T> type) {
|
||||||
for (StorageProviderModel model : getStorageProviders(realm)) {
|
for (StorageProviderModel model : getStorageProviders(realm)) {
|
||||||
StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
|
StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
|
||||||
if (factory.supports(type)) {
|
|
||||||
|
if (Types.supports(type, factory, StorageProviderFactory.class)) {
|
||||||
return type.cast(factory.getInstance(session, model));
|
return type.cast(factory.getInstance(session, model));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected <T> List<T> getStorageProviders(RealmModel realm, Class<T> type) {
|
protected <T> List<T> getStorageProviders(RealmModel realm, Class<T> type) {
|
||||||
List<T> list = new LinkedList<>();
|
List<T> list = new LinkedList<>();
|
||||||
for (StorageProviderModel model : getStorageProviders(realm)) {
|
for (StorageProviderModel model : getStorageProviders(realm)) {
|
||||||
StorageProviderFactory factory = (StorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
|
StorageProviderFactory factory = (StorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
|
||||||
if (factory.supports(type)) {
|
if (Types.supports(type, factory, StorageProviderFactory.class)) {
|
||||||
list.add(type.cast(factory.getInstance(session, model)));
|
list.add(type.cast(factory.getInstance(session, model)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +134,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeUser(RealmModel realm, UserModel user) {
|
public boolean removeUser(RealmModel realm, UserModel user) {
|
||||||
|
getFederatedStorage().preRemove(realm, user);
|
||||||
StorageId storageId = new StorageId(user.getId());
|
StorageId storageId = new StorageId(user.getId());
|
||||||
if (storageId.getProviderId() == null) {
|
if (storageId.getProviderId() == null) {
|
||||||
return localStorage().removeUser(realm, user);
|
return localStorage().removeUser(realm, user);
|
||||||
|
@ -299,29 +304,33 @@ public class UserStorageManager implements UserProvider {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
interface PaginatedQuery {
|
interface PaginatedQuery {
|
||||||
List<UserModel> query(UserQueryProvider provider, int first, int max);
|
List<UserModel> query(Object provider, int first, int max);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<UserModel> query(PaginatedQuery pagedQuery, RealmModel realm, int firstResult, int maxResults) {
|
protected List<UserModel> query(PaginatedQuery pagedQuery, RealmModel realm, int firstResult, int maxResults) {
|
||||||
List<UserModel> results = new LinkedList<UserModel>();
|
if (maxResults == 0) return Collections.EMPTY_LIST;
|
||||||
if (maxResults == 0) return results;
|
|
||||||
|
|
||||||
|
|
||||||
List<UserQueryProvider> storageProviders = getStorageProviders(realm, UserQueryProvider.class);
|
List<UserQueryProvider> storageProviders = getStorageProviders(realm, UserQueryProvider.class);
|
||||||
LinkedList<UserQueryProvider> providers = new LinkedList<>();
|
// we can skip rest of method if there are no storage providers
|
||||||
if (providers.isEmpty()) {
|
if (storageProviders.isEmpty()) {
|
||||||
return pagedQuery.query(localStorage(), firstResult, maxResults);
|
return pagedQuery.query(localStorage(), firstResult, maxResults);
|
||||||
}
|
}
|
||||||
|
LinkedList<Object> providers = new LinkedList<>();
|
||||||
|
List<UserModel> results = new LinkedList<UserModel>();
|
||||||
providers.add(localStorage());
|
providers.add(localStorage());
|
||||||
providers.addAll(storageProviders);
|
providers.addAll(storageProviders);
|
||||||
|
providers.add(getFederatedStorage());
|
||||||
|
|
||||||
int leftToRead = maxResults;
|
int leftToRead = maxResults;
|
||||||
int leftToFirstResult = firstResult;
|
int leftToFirstResult = firstResult;
|
||||||
|
|
||||||
Iterator<UserQueryProvider> it = providers.iterator();
|
Iterator<Object> it = providers.iterator();
|
||||||
while (it.hasNext() && leftToRead != 0) {
|
while (it.hasNext() && leftToRead != 0) {
|
||||||
UserQueryProvider provider = it.next();
|
Object provider = it.next();
|
||||||
boolean exhausted = false;
|
boolean exhausted = false;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
if (leftToFirstResult > 0) {
|
if (leftToFirstResult > 0) {
|
||||||
|
@ -341,20 +350,22 @@ public class UserStorageManager implements UserProvider {
|
||||||
results.addAll(tmp);
|
results.addAll(tmp);
|
||||||
if (leftToRead > 0) leftToRead -= tmp.size();
|
if (leftToRead > 0) leftToRead -= tmp.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> getUsers(final RealmModel realm, int firstResult, int maxResults, final boolean includeServiceAccounts) {
|
public List<UserModel> getUsers(final RealmModel realm, int firstResult, int maxResults, final boolean includeServiceAccounts) {
|
||||||
return query(new PaginatedQuery() {
|
return query((provider, first, max) -> {
|
||||||
@Override
|
if (provider instanceof UserProvider) { // it is local storage
|
||||||
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
|
return ((UserProvider) provider).getUsers(realm, first, max, includeServiceAccounts);
|
||||||
if (provider instanceof UserProvider) { // it is local storage
|
} else if (provider instanceof UserQueryProvider) {
|
||||||
return ((UserProvider)provider).getUsers(realm, first, max, includeServiceAccounts);
|
return ((UserQueryProvider)provider).getUsers(realm, first, max);
|
||||||
}
|
|
||||||
return provider.getUsers(realm, first, max);
|
|
||||||
}
|
}
|
||||||
}, realm, firstResult, maxResults);
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
, realm, firstResult, maxResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -363,12 +374,13 @@ public class UserStorageManager implements UserProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> searchForUser(final String search, final RealmModel realm, int firstResult, int maxResults) {
|
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||||
return query(new PaginatedQuery() {
|
return query((provider, first, max) -> {
|
||||||
@Override
|
if (provider instanceof UserQueryProvider) {
|
||||||
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
|
return ((UserQueryProvider)provider).searchForUser(search, realm, first, max);
|
||||||
return provider.searchForUser(search, realm, first, max);
|
|
||||||
}
|
}
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
}, realm, firstResult, maxResults);
|
}, realm, firstResult, maxResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,23 +390,36 @@ public class UserStorageManager implements UserProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> searchForUserByAttributes(final Map<String, String> attributes, final RealmModel realm, int firstResult, int maxResults) {
|
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||||
return query(new PaginatedQuery() {
|
return query((provider, first, max) -> {
|
||||||
@Override
|
if (provider instanceof UserQueryProvider) {
|
||||||
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
|
return ((UserQueryProvider)provider).searchForUserByAttributes(attributes, realm, first, max);
|
||||||
return provider.searchForUserByAttributes(attributes, realm, first, max);
|
|
||||||
}
|
}
|
||||||
}, realm, firstResult, maxResults);
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
, realm, firstResult, maxResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> searchForUserByUserAttribute(final String attrName, final String attrValue, RealmModel realm) {
|
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
|
||||||
return query(new PaginatedQuery() {
|
List<UserModel> results = query((provider, first, max) -> {
|
||||||
@Override
|
if (provider instanceof UserQueryProvider) {
|
||||||
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
|
return ((UserQueryProvider)provider).searchForUserByUserAttribute(attrName, attrValue, realm);
|
||||||
return provider.searchForUserByUserAttribute(attrName, attrValue, realm);
|
|
||||||
|
} else if (provider instanceof UserFederatedStorageProvider) {
|
||||||
|
List<String> ids = ((UserFederatedStorageProvider)provider).getUsersByUserAttribute(realm, attrName, attrValue);
|
||||||
|
List<UserModel> rs = new LinkedList<>();
|
||||||
|
for (String id : ids) {
|
||||||
|
UserModel user = getUserById(id, realm);
|
||||||
|
if (user != null) rs.add(user);
|
||||||
|
}
|
||||||
|
return rs;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
}, realm,0, Integer.MAX_VALUE - 1);
|
}, realm,0, Integer.MAX_VALUE - 1);
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -432,12 +457,23 @@ public class UserStorageManager implements UserProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> getGroupMembers(final RealmModel realm, final GroupModel group, int firstResult, int maxResults) {
|
public List<UserModel> getGroupMembers(final RealmModel realm, final GroupModel group, int firstResult, int maxResults) {
|
||||||
return query(new PaginatedQuery() {
|
List<UserModel> results = query((provider, first, max) -> {
|
||||||
@Override
|
if (provider instanceof UserQueryProvider) {
|
||||||
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
|
return ((UserQueryProvider)provider).getGroupMembers(realm, group, first, max);
|
||||||
return provider.getGroupMembers(realm, group, first, max);
|
|
||||||
|
} else if (provider instanceof UserFederatedStorageProvider) {
|
||||||
|
List<String> ids = ((UserFederatedStorageProvider)provider).getMembership(realm, group, first, max);
|
||||||
|
List<UserModel> rs = new LinkedList<UserModel>();
|
||||||
|
for (String id : ids) {
|
||||||
|
UserModel user = getUserById(id, realm);
|
||||||
|
if (user != null) rs.add(user);
|
||||||
|
}
|
||||||
|
return rs;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
}, realm, firstResult, maxResults);
|
}, realm, firstResult, maxResults);
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -402,4 +402,19 @@ public abstract class AbstractUserAdapter implements UserModel {
|
||||||
throw new ReadOnlyException("user is read only for this update");
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || !(o instanceof UserModel)) return false;
|
||||||
|
|
||||||
|
UserModel that = (UserModel) o;
|
||||||
|
return that.getId().equals(getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getId().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -416,4 +416,19 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
||||||
getFederatedStorage().updateCredential(realm, this, cred);
|
getFederatedStorage().updateCredential(realm, this, cred);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || !(o instanceof UserModel)) return false;
|
||||||
|
|
||||||
|
UserModel that = (UserModel) o;
|
||||||
|
return that.getId().equals(getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getId().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -32,4 +33,5 @@ public interface UserAttributeFederatedStorage {
|
||||||
void setAttribute(RealmModel realm, UserModel user, String name, List<String> values);
|
void setAttribute(RealmModel realm, UserModel user, String name, List<String> values);
|
||||||
void removeAttribute(RealmModel realm, UserModel user, String name);
|
void removeAttribute(RealmModel realm, UserModel user, String name);
|
||||||
MultivaluedHashMap<String, String> getAttributes(RealmModel realm, UserModel user);
|
MultivaluedHashMap<String, String> getAttributes(RealmModel realm, UserModel user);
|
||||||
|
List<String> getUsersByUserAttribute(RealmModel realm, String name, String value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,9 +29,8 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public interface UserGroupMembershipFederatedStorage {
|
public interface UserGroupMembershipFederatedStorage {
|
||||||
Set<GroupModel> getGroups(RealmModel realm, UserModel user);
|
Set<GroupModel> getGroups(RealmModel realm, UserModel user);
|
||||||
|
|
||||||
void joinGroup(RealmModel realm,UserModel user, GroupModel group);
|
void joinGroup(RealmModel realm,UserModel user, GroupModel group);
|
||||||
|
|
||||||
void leaveGroup(RealmModel realm,UserModel user, GroupModel group);
|
void leaveGroup(RealmModel realm,UserModel user, GroupModel group);
|
||||||
|
List<String> getMembership(RealmModel realm, GroupModel group, int firstResult, int max);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ import org.keycloak.testsuite.rule.WebResource;
|
||||||
import org.keycloak.testsuite.rule.WebRule;
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,4 +148,89 @@ public class UserFederationStorageTest {
|
||||||
loginSuccessAndLogout("thor", "lightning");
|
loginSuccessAndLogout("thor", "lightning");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQuery() {
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
|
|
||||||
|
// Test paging
|
||||||
|
List<UserModel> localUsers = session.userLocalStorage().getUsers(realm, false);
|
||||||
|
Set<UserModel> queried = new HashSet<>();
|
||||||
|
// tests assumes that local storage is queried first
|
||||||
|
int first = localUsers.size();
|
||||||
|
while (queried.size() < 8) {
|
||||||
|
List<UserModel> results = session.users().getUsers(realm, first, 3);
|
||||||
|
if (results.size() == 0) break;
|
||||||
|
first += results.size();
|
||||||
|
queried.addAll(results);
|
||||||
|
|
||||||
|
}
|
||||||
|
Set<String> usernames = new HashSet<>();
|
||||||
|
for (UserModel user : queried) {
|
||||||
|
usernames.add(user.getUsername());
|
||||||
|
System.out.println(user.getUsername());
|
||||||
|
|
||||||
|
}
|
||||||
|
Assert.assertEquals(8, queried.size());
|
||||||
|
Assert.assertTrue(usernames.contains("thor"));
|
||||||
|
Assert.assertTrue(usernames.contains("zeus"));
|
||||||
|
Assert.assertTrue(usernames.contains("apollo"));
|
||||||
|
Assert.assertTrue(usernames.contains("perseus"));
|
||||||
|
Assert.assertTrue(usernames.contains("tbrady"));
|
||||||
|
Assert.assertTrue(usernames.contains("rob"));
|
||||||
|
Assert.assertTrue(usernames.contains("jules"));
|
||||||
|
Assert.assertTrue(usernames.contains("danny"));
|
||||||
|
|
||||||
|
// test searchForUser
|
||||||
|
List<UserModel> users = session.users().searchForUser("tbrady", realm);
|
||||||
|
Assert.assertTrue(users.size() == 1);
|
||||||
|
Assert.assertTrue(users.get(0).getUsername().equals("tbrady"));
|
||||||
|
|
||||||
|
// test getGroupMembers()
|
||||||
|
GroupModel gods = realm.createGroup("gods");
|
||||||
|
UserModel user = null;
|
||||||
|
user = session.users().getUserByUsername("apollo", realm);
|
||||||
|
user.joinGroup(gods);
|
||||||
|
user = session.users().getUserByUsername("zeus", realm);
|
||||||
|
user.joinGroup(gods);
|
||||||
|
user = session.users().getUserByUsername("thor", realm);
|
||||||
|
user.joinGroup(gods);
|
||||||
|
queried.clear();
|
||||||
|
usernames.clear();
|
||||||
|
|
||||||
|
first = 0;
|
||||||
|
while (queried.size() < 8) {
|
||||||
|
List<UserModel> results = session.users().getGroupMembers(realm, gods, first, 1);
|
||||||
|
if (results.size() == 0) break;
|
||||||
|
first += results.size();
|
||||||
|
queried.addAll(results);
|
||||||
|
|
||||||
|
}
|
||||||
|
for (UserModel u : queried) {
|
||||||
|
usernames.add(u.getUsername());
|
||||||
|
System.out.println(u.getUsername());
|
||||||
|
|
||||||
|
}
|
||||||
|
Assert.assertEquals(3, queried.size());
|
||||||
|
Assert.assertTrue(usernames.contains("apollo"));
|
||||||
|
Assert.assertTrue(usernames.contains("zeus"));
|
||||||
|
Assert.assertTrue(usernames.contains("thor"));
|
||||||
|
|
||||||
|
// search by single attribute
|
||||||
|
System.out.println("search by single attribute");
|
||||||
|
user = session.users().getUserByUsername("thor", realm);
|
||||||
|
user.setSingleAttribute("weapon", "hammer");
|
||||||
|
|
||||||
|
users = session.users().searchForUserByUserAttribute("weapon", "hammer", realm);
|
||||||
|
for (UserModel u : users) {
|
||||||
|
System.out.println(u.getUsername());
|
||||||
|
|
||||||
|
}
|
||||||
|
Assert.assertEquals(1, users.size());
|
||||||
|
Assert.assertEquals("thor", users.get(0).getUsername());
|
||||||
|
|
||||||
|
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,15 +30,19 @@ import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
||||||
import org.keycloak.storage.user.UserLookupProvider;
|
import org.keycloak.storage.user.UserLookupProvider;
|
||||||
|
import org.keycloak.storage.user.UserQueryProvider;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class UserPropertyFileStorage implements UserLookupProvider, StorageProvider, UserCredentialValidatorProvider {
|
public class UserPropertyFileStorage implements UserLookupProvider, StorageProvider, UserCredentialValidatorProvider, UserQueryProvider {
|
||||||
|
|
||||||
protected Properties userPasswords;
|
protected Properties userPasswords;
|
||||||
protected StorageProviderModel model;
|
protected StorageProviderModel model;
|
||||||
|
@ -128,6 +132,85 @@ public class UserPropertyFileStorage implements UserLookupProvider, StorageProvi
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUsersCount(RealmModel realm) {
|
||||||
|
return userPasswords.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getUsers(RealmModel realm) {
|
||||||
|
List<UserModel> users = new LinkedList<>();
|
||||||
|
for (Object username : userPasswords.keySet()) {
|
||||||
|
users.add(createUser(realm, (String)username));
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
|
||||||
|
if (maxResults == 0) return Collections.EMPTY_LIST;
|
||||||
|
List<UserModel> users = new LinkedList<>();
|
||||||
|
int count = 0;
|
||||||
|
for (Object un : userPasswords.keySet()) {
|
||||||
|
if (count++ < firstResult) continue;
|
||||||
|
String username = (String)un;
|
||||||
|
users.add(createUser(realm, username));
|
||||||
|
if (users.size() + 1 > maxResults) break;
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||||
|
if (maxResults == 0) return Collections.EMPTY_LIST;
|
||||||
|
List<UserModel> users = new LinkedList<>();
|
||||||
|
int count = 0;
|
||||||
|
for (Object un : userPasswords.keySet()) {
|
||||||
|
String username = (String)un;
|
||||||
|
if (username.contains(search)) {
|
||||||
|
if (count++ < firstResult) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
users.add(createUser(realm, username));
|
||||||
|
if (users.size() + 1 > maxResults) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||||
|
if (attributes.size() != 1) return Collections.EMPTY_LIST;
|
||||||
|
String username = attributes.get(UserModel.USERNAME);
|
||||||
|
if (username == null) return Collections.EMPTY_LIST;
|
||||||
|
return searchForUser(username, realm, firstResult, maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> searchForUser(String search, RealmModel realm) {
|
||||||
|
return getUsers(realm, 0, Integer.MAX_VALUE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
||||||
|
|
|
@ -34,18 +34,13 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class UserPropertyFileStorageFactory implements StorageProviderFactory {
|
public class UserPropertyFileStorageFactory implements StorageProviderFactory<UserPropertyFileStorage> {
|
||||||
|
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "user-password-props";
|
public static final String PROVIDER_ID = "user-password-props";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(Class<?> type) {
|
public UserPropertyFileStorage getInstance(KeycloakSession session, StorageProviderModel model) {
|
||||||
return type.isAssignableFrom(UserPropertyFileStorage.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StorageProvider getInstance(KeycloakSession session, StorageProviderModel model) {
|
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
try {
|
try {
|
||||||
props.load(getClass().getResourceAsStream(model.getConfig().get("property.file")));
|
props.load(getClass().getResourceAsStream(model.getConfig().get("property.file")));
|
||||||
|
@ -55,11 +50,6 @@ public class UserPropertyFileStorageFactory implements StorageProviderFactory {
|
||||||
return new UserPropertyFileStorage(session, model, props);
|
return new UserPropertyFileStorage(session, model, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getConfigurationOptions() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
tbrady=goat
|
tbrady=goat
|
||||||
|
rob=pw
|
||||||
|
jules=pw
|
||||||
|
danny=pw
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
thor=hammer
|
thor=hammer
|
||||||
|
zeus=pw
|
||||||
|
apollo=pw
|
||||||
|
perseus=pw
|
Loading…
Reference in a new issue