keycloak-memoizeit

Merge pull request #3063 from patriot1burke/master user

7/22/2016 6:51:20 PM

Changes

Details

diff --git a/common/src/main/java/org/keycloak/common/util/reflections/Types.java b/common/src/main/java/org/keycloak/common/util/reflections/Types.java
index e6787be..adf8626 100644
--- a/common/src/main/java/org/keycloak/common/util/reflections/Types.java
+++ b/common/src/main/java/org/keycloak/common/util/reflections/Types.java
@@ -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);
+    }
+
+
 }
\ No newline at end of file
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 5ceab0b..2cf74ad 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -34,6 +34,7 @@ import org.keycloak.models.jpa.entities.GroupEntity;
 import org.keycloak.models.jpa.entities.RealmEntity;
 import org.keycloak.models.jpa.entities.RoleEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageProviderModel;
 
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
@@ -127,6 +128,9 @@ public class JpaRealmProvider implements RealmProvider {
         em.refresh(realm);
         final RealmAdapter adapter = new RealmAdapter(session, em, realm);
         session.users().preRemove(adapter);
+        for (StorageProviderModel provider : adapter.getStorageProviders()) {
+            adapter.removeStorageProvider(provider);
+        }
 
         realm.getDefaultGroups().clear();
         em.flush();
@@ -153,6 +157,7 @@ public class JpaRealmProvider implements RealmProvider {
             session.realms().removeRole(adapter, role);
         }
 
+
         em.remove(realm);
 
         em.flush();
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java
index 071eacd..c5e6193 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java
@@ -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"),
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java
index ecfaa49..3116a0a 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java
@@ -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"),
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java
index f555628..f30aee6 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java
@@ -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)")
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
index 4ec58bd..d64a3ea 100644
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
@@ -148,6 +148,15 @@ public class JpaUserFederatedStorageProvider implements
     }
 
     @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)
                 .setParameter("realmId", realm.getId())
@@ -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();
diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
index 1d3ff73..45c6168 100644
--- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
+++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
@@ -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();
+    }
+
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java
index bcc2651..b09ceae 100644
--- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java
+++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java
@@ -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();
+    }
+
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java
index 12bcabf..08755da 100644
--- a/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java
@@ -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);
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java
index fef3cee..dad2399 100644
--- a/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java
@@ -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);
 
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java
index 3369da2..e4f2717 100755
--- a/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java
@@ -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.
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
index 24f3c3d..db39190 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -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)));
             }
 
@@ -118,11 +122,6 @@ public class UserStorageManager implements UserProvider {
         return localStorage().addUser(realm, username.toLowerCase());
     }
 
-    public StorageProvider getStorageProvider(StorageProviderModel model) {
-        StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
-        return factory.getInstance(session, model);
-    }
-
     public StorageProvider getStorageProvider(RealmModel realm, String providerId) {
         StorageProviderModel model = realm.getStorageProvider(providerId);
         if (model == null) return null;
@@ -135,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);
@@ -304,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) {
@@ -346,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
@@ -368,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);
     }
 
@@ -383,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
@@ -437,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;
     }
 
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserFederationStorageTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserFederationStorageTest.java
index 2da8799..58d0ed0 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserFederationStorageTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserFederationStorageTest.java
@@ -21,7 +21,13 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.infinispan.UserAdapter;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.storage.StorageProviderModel;
 import org.keycloak.testsuite.OAuthClient;
@@ -32,6 +38,10 @@ 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;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
@@ -43,9 +53,17 @@ public class UserFederationStorageTest {
         @Override
         public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
             StorageProviderModel model = new StorageProviderModel();
-            model.setDisplayName("user-props");
+            model.setDisplayName("read-only-user-props");
             model.setPriority(1);
             model.setProviderName(UserPropertyFileStorageFactory.PROVIDER_ID);
+            model.getConfig().put("property.file", "/storage-test/read-only-user-password.properties");
+            appRealm.addStorageProvider(model);
+            model = new StorageProviderModel();
+            model.setDisplayName("user-props");
+            model.setPriority(2);
+            model.setProviderName(UserPropertyFileStorageFactory.PROVIDER_ID);
+            model.getConfig().put("property.file", "/storage-test/user-password.properties");
+            model.getConfig().put("USER_FEDERATED_STORAGE", "true");
             appRealm.addStorageProvider(model);
         }
     });
@@ -82,7 +100,142 @@ public class UserFederationStorageTest {
     @Test
     public void testLoginSuccess() {
         loginSuccessAndLogout("tbrady", "goat");
+        loginSuccessAndLogout("thor", "hammer");
         loginBadPassword("tbrady");
     }
 
+    @Test
+    public void testUpdate() {
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.realms().getRealmByName("test");
+        UserModel thor = session.users().getUserByUsername("thor", realm);
+        thor.setFirstName("Stian");
+        thor.setLastName("Thorgersen");
+        thor.setEmailVerified(true);
+        long thorCreated = System.currentTimeMillis() - 100;
+        thor.setCreatedTimestamp(thorCreated);
+        thor.setEmail("thor@hammer.com");
+        thor.setSingleAttribute("test-attribute", "value");
+        RoleModel role = realm.addRole("foo-role");
+        thor.grantRole(role);
+        GroupModel group = realm.createGroup("my-group");
+        thor.joinGroup(group);
+        thor.addRequiredAction("POOP");
+        keycloakRule.stopSession(session, true);
+
+        session = keycloakRule.startSession();
+        realm = session.realms().getRealmByName("test");
+        thor = session.users().getUserByUsername("thor", realm);
+        Assert.assertEquals("Stian", thor.getFirstName());
+        Assert.assertEquals("Thorgersen", thor.getLastName());
+        Assert.assertEquals("thor@hammer.com", thor.getEmail());
+        Assert.assertEquals("value", thor.getFirstAttribute("test-attribute"));
+        Assert.assertTrue(thor.isEmailVerified());
+        Assert.assertTrue(thor instanceof UserAdapter);
+        Set<RoleModel> roles = thor.getRoleMappings();
+        System.out.println("num roles " + roles.size());
+        Assert.assertTrue(roles.size() > 1);
+        role = realm.getRole("foo-role");
+        Assert.assertTrue(thor.hasRole(role));
+
+        Set<GroupModel> groups = thor.getGroups();
+        boolean foundGroup = false;
+        for (GroupModel g : groups) {
+            if (g.getName().equals("my-group")) foundGroup = true;
+
+        }
+        Assert.assertTrue(foundGroup);
+        System.out.println("num groups " + groups.size());
+        Assert.assertTrue(thor.getRequiredActions().iterator().next().equals("POOP"));
+        thor.removeRequiredAction("POOP");
+        thor.updateCredential(UserCredentialModel.password("lightning"));
+        keycloakRule.stopSession(session, true);
+        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);
+    }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
index 82a10c1..cf237e4 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
@@ -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;
@@ -62,13 +66,27 @@ public class UserPropertyFileStorage implements UserLookupProvider, StorageProvi
         return createUser(realm, username);
     }
 
-    private UserModel createUser(final RealmModel realm, final String username) {
-        return new AbstractUserAdapter(session, realm, model) {
-            @Override
-            public String getUsername() {
-                return username;
-            }
-        };
+    private UserModel createUser(RealmModel realm, String username) {
+        if (federatedStorageEnabled) {
+            return new AbstractUserAdapterFederatedStorage(session, realm,  model) {
+                @Override
+                public String getUsername() {
+                    return username;
+                }
+
+                @Override
+                public void setUsername(String username) {
+                    throw new RuntimeException("Unsupported");
+                }
+            };
+        } else {
+            return new AbstractUserAdapter(session, realm, model) {
+                @Override
+                public String getUsername() {
+                    return username;
+                }
+            };
+        }
     }
 
     @Override
@@ -115,6 +133,85 @@ public class UserPropertyFileStorage implements UserLookupProvider, StorageProvi
     }
 
     @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() {
 
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java
index 0ebbce2..82d91cf 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java
@@ -34,21 +34,16 @@ 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("/storage-test/user-password.properties"));
+            props.load(getClass().getResourceAsStream(model.getConfig().get("property.file")));
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
@@ -56,11 +51,6 @@ public class UserPropertyFileStorageFactory implements StorageProviderFactory {
     }
 
     @Override
-    public Set<String> getConfigurationOptions() {
-        return null;
-    }
-
-    @Override
     public String getId() {
         return PROVIDER_ID;
     }
diff --git a/testsuite/integration/src/test/resources/storage-test/read-only-user-password.properties b/testsuite/integration/src/test/resources/storage-test/read-only-user-password.properties
new file mode 100644
index 0000000..c0b76ab
--- /dev/null
+++ b/testsuite/integration/src/test/resources/storage-test/read-only-user-password.properties
@@ -0,0 +1,4 @@
+tbrady=goat
+rob=pw
+jules=pw
+danny=pw
diff --git a/testsuite/integration/src/test/resources/storage-test/user-password.properties b/testsuite/integration/src/test/resources/storage-test/user-password.properties
index 1672680..a6e28c1 100644
--- a/testsuite/integration/src/test/resources/storage-test/user-password.properties
+++ b/testsuite/integration/src/test/resources/storage-test/user-password.properties
@@ -1 +1,4 @@
-tbrady=goat
\ No newline at end of file
+thor=hammer
+zeus=pw
+apollo=pw
+perseus=pw
\ No newline at end of file