user fed spi querying tests

This commit is contained in:
Bill Burke 2016-07-22 11:42:07 -04:00
parent 0315bd0b87
commit 72d134748c
16 changed files with 879 additions and 67 deletions

View file

@ -17,7 +17,14 @@
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.TypeVariable;
import java.util.HashMap;
import java.util.Map;
/**
* Utility class for Types
@ -68,4 +75,564 @@ public class Types {
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);
}
}

View file

@ -147,6 +147,15 @@ public class JpaUserFederatedStorageProvider implements
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
public String getUserByFederatedIdentity(FederatedIdentityModel link, RealmModel realm) {
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
public Set<String> getRequiredActions(RealmModel realm, UserModel user) {
@ -689,7 +707,7 @@ public class JpaUserFederatedStorageProvider implements
.setParameter("userId", user.getId())
.setParameter("realmId", realm.getId())
.executeUpdate();
em.createNamedQuery("getFederatedUserRequiredActionsByUser")
em.createNamedQuery("deleteFederatedUserRequiredActionsByUser")
.setParameter("userId", user.getId())
.setParameter("realmId", realm.getId())
.executeUpdate();

View file

@ -32,7 +32,7 @@ import javax.persistence.Table;
* @version $Revision: 1 $
*/
@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="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"),

View file

@ -35,7 +35,7 @@ import java.io.Serializable;
@NamedQueries({
@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="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="deleteFederatedUserGroupMembershipByRealm", query="delete from FederatedUserGroupMembershipEntity mapping where mapping.realmId=:realmId"),
@NamedQuery(name="deleteFederatedUserGroupMembershipByStorageProvider", query="delete from FederatedUserGroupMembershipEntity e where e.storageProviderId=:storageProviderId"),

View file

@ -37,6 +37,7 @@ import java.io.Serializable;
*/
@NamedQueries({
@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="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)")

View file

@ -26,8 +26,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface StorageProviderFactory extends ProviderFactory<StorageProvider> {
boolean supports(Class<?> type);
public interface StorageProviderFactory<T extends StorageProvider> extends ProviderFactory<StorageProvider> {
/**
* called per Keycloak transaction.
*
@ -35,14 +34,7 @@ public interface StorageProviderFactory extends ProviderFactory<StorageProvider>
* @param model
* @return
*/
StorageProvider getInstance(KeycloakSession session, StorageProviderModel model);
/**
* Config options to display in generic admin console page for federated
*
* @return
*/
Set<String> getConfigurationOptions();
T getInstance(KeycloakSession session, StorageProviderModel model);
/**
* This is the name of the provider and will be showed in the admin console as an option.

View file

@ -18,6 +18,7 @@
package org.keycloak.storage;
import org.jboss.logging.Logger;
import org.keycloak.common.util.reflections.Types;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
@ -42,6 +43,7 @@ import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -79,18 +81,20 @@ public class UserStorageManager implements UserProvider {
protected <T> T getFirstStorageProvider(RealmModel realm, Class<T> type) {
for (StorageProviderModel model : getStorageProviders(realm)) {
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 null;
}
protected <T> List<T> getStorageProviders(RealmModel realm, Class<T> type) {
List<T> list = new LinkedList<>();
for (StorageProviderModel model : getStorageProviders(realm)) {
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)));
}
@ -130,6 +134,7 @@ public class UserStorageManager implements UserProvider {
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
getFederatedStorage().preRemove(realm, user);
StorageId storageId = new StorageId(user.getId());
if (storageId.getProviderId() == null) {
return localStorage().removeUser(realm, user);
@ -299,29 +304,33 @@ public class UserStorageManager implements UserProvider {
return size;
}
@FunctionalInterface
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) {
List<UserModel> results = new LinkedList<UserModel>();
if (maxResults == 0) return results;
if (maxResults == 0) return Collections.EMPTY_LIST;
List<UserQueryProvider> storageProviders = getStorageProviders(realm, UserQueryProvider.class);
LinkedList<UserQueryProvider> providers = new LinkedList<>();
if (providers.isEmpty()) {
// we can skip rest of method if there are no storage providers
if (storageProviders.isEmpty()) {
return pagedQuery.query(localStorage(), firstResult, maxResults);
}
LinkedList<Object> providers = new LinkedList<>();
List<UserModel> results = new LinkedList<UserModel>();
providers.add(localStorage());
providers.addAll(storageProviders);
providers.add(getFederatedStorage());
int leftToRead = maxResults;
int leftToFirstResult = firstResult;
Iterator<UserQueryProvider> it = providers.iterator();
Iterator<Object> it = providers.iterator();
while (it.hasNext() && leftToRead != 0) {
UserQueryProvider provider = it.next();
Object provider = it.next();
boolean exhausted = false;
int index = 0;
if (leftToFirstResult > 0) {
@ -341,20 +350,22 @@ public class UserStorageManager implements UserProvider {
results.addAll(tmp);
if (leftToRead > 0) leftToRead -= tmp.size();
}
return results;
}
@Override
public List<UserModel> getUsers(final RealmModel realm, int firstResult, int maxResults, final boolean includeServiceAccounts) {
return query(new PaginatedQuery() {
@Override
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
if (provider instanceof UserProvider) { // it is local storage
return ((UserProvider)provider).getUsers(realm, first, max, includeServiceAccounts);
}
return provider.getUsers(realm, first, max);
return query((provider, first, max) -> {
if (provider instanceof UserProvider) { // it is local storage
return ((UserProvider) provider).getUsers(realm, first, max, includeServiceAccounts);
} else if (provider instanceof UserQueryProvider) {
return ((UserQueryProvider)provider).getUsers(realm, first, max);
}
}, realm, firstResult, maxResults);
return Collections.EMPTY_LIST;
}
, realm, firstResult, maxResults);
}
@Override
@ -363,12 +374,13 @@ public class UserStorageManager implements UserProvider {
}
@Override
public List<UserModel> searchForUser(final String search, final RealmModel realm, int firstResult, int maxResults) {
return query(new PaginatedQuery() {
@Override
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
return provider.searchForUser(search, realm, first, max);
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
return query((provider, first, max) -> {
if (provider instanceof UserQueryProvider) {
return ((UserQueryProvider)provider).searchForUser(search, realm, first, max);
}
return Collections.EMPTY_LIST;
}, realm, firstResult, maxResults);
}
@ -378,23 +390,36 @@ public class UserStorageManager implements UserProvider {
}
@Override
public List<UserModel> searchForUserByAttributes(final Map<String, String> attributes, final RealmModel realm, int firstResult, int maxResults) {
return query(new PaginatedQuery() {
@Override
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
return provider.searchForUserByAttributes(attributes, realm, first, max);
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
return query((provider, first, max) -> {
if (provider instanceof UserQueryProvider) {
return ((UserQueryProvider)provider).searchForUserByAttributes(attributes, realm, first, max);
}
}, realm, firstResult, maxResults);
return Collections.EMPTY_LIST;
}
, realm, firstResult, maxResults);
}
@Override
public List<UserModel> searchForUserByUserAttribute(final String attrName, final String attrValue, RealmModel realm) {
return query(new PaginatedQuery() {
@Override
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
return provider.searchForUserByUserAttribute(attrName, attrValue, realm);
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
List<UserModel> results = query((provider, first, max) -> {
if (provider instanceof UserQueryProvider) {
return ((UserQueryProvider)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);
return results;
}
@Override
@ -432,12 +457,23 @@ public class UserStorageManager implements UserProvider {
@Override
public List<UserModel> getGroupMembers(final RealmModel realm, final GroupModel group, int firstResult, int maxResults) {
return query(new PaginatedQuery() {
@Override
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
return provider.getGroupMembers(realm, group, first, max);
List<UserModel> results = query((provider, first, max) -> {
if (provider instanceof UserQueryProvider) {
return ((UserQueryProvider)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);
return results;
}

View file

@ -402,4 +402,19 @@ public abstract class AbstractUserAdapter implements UserModel {
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();
}
}

View file

@ -416,4 +416,19 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
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();
}
}

View file

@ -22,6 +22,7 @@ import org.keycloak.models.UserModel;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @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 removeAttribute(RealmModel realm, UserModel user, String name);
MultivaluedHashMap<String, String> getAttributes(RealmModel realm, UserModel user);
List<String> getUsersByUserAttribute(RealmModel realm, String name, String value);
}

View file

@ -20,6 +20,7 @@ import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import java.util.List;
import java.util.Set;
/**
@ -28,9 +29,8 @@ import java.util.Set;
*/
public interface UserGroupMembershipFederatedStorage {
Set<GroupModel> getGroups(RealmModel realm, UserModel user);
void joinGroup(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);
}

View file

@ -38,6 +38,8 @@ import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
@ -146,4 +148,89 @@ public class UserFederationStorageTest {
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);
}
}

View file

@ -30,15 +30,19 @@ import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
import org.keycloak.storage.user.UserCredentialValidatorProvider;
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.Map;
import java.util.Properties;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserPropertyFileStorage implements UserLookupProvider, StorageProvider, UserCredentialValidatorProvider {
public class UserPropertyFileStorage implements UserLookupProvider, StorageProvider, UserCredentialValidatorProvider, UserQueryProvider {
protected Properties userPasswords;
protected StorageProviderModel model;
@ -128,6 +132,85 @@ public class UserPropertyFileStorage implements UserLookupProvider, StorageProvi
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
public void close() {

View file

@ -34,18 +34,13 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserPropertyFileStorageFactory implements StorageProviderFactory {
public class UserPropertyFileStorageFactory implements StorageProviderFactory<UserPropertyFileStorage> {
public static final String PROVIDER_ID = "user-password-props";
@Override
public boolean supports(Class<?> type) {
return type.isAssignableFrom(UserPropertyFileStorage.class);
}
@Override
public StorageProvider getInstance(KeycloakSession session, StorageProviderModel model) {
public UserPropertyFileStorage getInstance(KeycloakSession session, StorageProviderModel model) {
Properties props = new Properties();
try {
props.load(getClass().getResourceAsStream(model.getConfig().get("property.file")));
@ -55,11 +50,6 @@ public class UserPropertyFileStorageFactory implements StorageProviderFactory {
return new UserPropertyFileStorage(session, model, props);
}
@Override
public Set<String> getConfigurationOptions() {
return null;
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -1 +1,4 @@
tbrady=goat
tbrady=goat
rob=pw
jules=pw
danny=pw

View file

@ -1 +1,4 @@
thor=hammer
thor=hammer
zeus=pw
apollo=pw
perseus=pw