keycloak-uncached
Changes
core/src/main/java/org/keycloak/representations/idm/UserFederationProviderFactoryRepresentation.java 21(+20 -1)
core/src/main/java/org/keycloak/representations/idm/UserFederationSyncResultRepresentation.java 82(+82 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthenticationManagementResource.java 2(+1 -1)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java 10(+10 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProviderResource.java 103(+103 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProvidersResource.java 66(+66 -0)
services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java 2(+2 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyConfigurableUserFederationProviderFactory.java 62(+62 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProvider.java 149(+149 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProviderFactory.java 108(+108 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory 36(+36 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/AbstractCustomAccountManagementTest.java 10(+2 -8)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java 51(+13 -38)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java 4(+1 -3)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ShiftExecutionTest.java 12(+3 -9)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java 357(+357 -0)
Details
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderFactoryRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderFactoryRepresentation.java
index 1a6fec8..bbf5741 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderFactoryRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderFactoryRepresentation.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.representations.idm;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -25,7 +26,9 @@ import java.util.Set;
 public class UserFederationProviderFactoryRepresentation {
 
     private String id;
-    private Set<String> options;
+    private Set<String> options; // TODO:Remove as configurable providers are more flexible?
+    private String helpText; // Used for configurable providers
+    private List<ConfigPropertyRepresentation> properties; // Used for configurable providers
 
     public String getId() {
         return id;
@@ -43,6 +46,22 @@ public class UserFederationProviderFactoryRepresentation {
         this.options = options;
     }
 
+    public String getHelpText() {
+        return helpText;
+    }
+
+    public void setHelpText(String helpText) {
+        this.helpText = helpText;
+    }
+
+    public List<ConfigPropertyRepresentation> getProperties() {
+        return properties;
+    }
+
+    public void setProperties(List<ConfigPropertyRepresentation> properties) {
+        this.properties = properties;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
                diff --git a/core/src/main/java/org/keycloak/representations/idm/UserFederationSyncResultRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationSyncResultRepresentation.java
new file mode 100644
index 0000000..08509ef
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationSyncResultRepresentation.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationSyncResultRepresentation {
+
+    private boolean ignored;
+
+    private int added;
+    private int updated;
+    private int removed;
+    private int failed;
+
+    private String status;
+
+    public boolean isIgnored() {
+        return ignored;
+    }
+
+    public void setIgnored(boolean ignored) {
+        this.ignored = ignored;
+    }
+
+    public int getAdded() {
+        return added;
+    }
+
+    public void setAdded(int added) {
+        this.added = added;
+    }
+
+    public int getUpdated() {
+        return updated;
+    }
+
+    public void setUpdated(int updated) {
+        this.updated = updated;
+    }
+
+    public int getRemoved() {
+        return removed;
+    }
+
+    public void setRemoved(int removed) {
+        this.removed = removed;
+    }
+
+    public int getFailed() {
+        return failed;
+    }
+
+    public void setFailed(int failed) {
+        this.failed = failed;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+}
                diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthenticationManagementResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthenticationManagementResource.java
index 197aeb6..d5de7be 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthenticationManagementResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthenticationManagementResource.java
@@ -100,7 +100,7 @@ public interface AuthenticationManagementResource {
     @Path("/flows/{flowAlias}/executions")
     @GET
     @Produces(MediaType.APPLICATION_JSON)
-    Response getExecutions(@PathParam("flowAlias") String flowAlias);
+    List<AuthenticationExecutionInfoRepresentation> getExecutions(@PathParam("flowAlias") String flowAlias);
 
     @Path("/flows/{flowAlias}/executions")
     @PUT
                diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index 6b4f368..34ca64a 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -160,4 +160,14 @@ public interface RealmResource {
     @Path("attack-detection")
     AttackDetectionResource attackDetection();
 
+    @Path("user-federation")
+    UserFederationProvidersResource userFederation();
+
+    @Path("testLDAPConnection")
+    @GET
+    @NoCache
+    Response testLDAPConnection(@QueryParam("action") String action, @QueryParam("connectionUrl") String connectionUrl,
+                                @QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential,
+                                @QueryParam("useTruststoreSpi") String useTruststoreSpi);
+
 }
                diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProviderResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProviderResource.java
new file mode 100644
index 0000000..5e1a99f
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProviderResource.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.admin.client.resource;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.keycloak.representations.idm.UserFederationMapperRepresentation;
+import org.keycloak.representations.idm.UserFederationMapperTypeRepresentation;
+import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+import org.keycloak.representations.idm.UserFederationSyncResultRepresentation;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface UserFederationProviderResource {
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    void update(UserFederationProviderRepresentation rep);
+
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    UserFederationProviderRepresentation toRepresentation();
+
+
+    @DELETE
+    void remove();
+
+
+    @POST
+    @Path("sync")
+    @Produces(MediaType.APPLICATION_JSON)
+    UserFederationSyncResultRepresentation syncUsers(@QueryParam("action") String action);
+
+
+    @GET
+    @Path("mapper-types")
+    Map<String, UserFederationMapperTypeRepresentation> getMapperTypes();
+
+
+    @GET
+    @Path("mappers")
+    @Produces(MediaType.APPLICATION_JSON)
+    List<UserFederationMapperRepresentation> getMappers();
+
+
+    @POST
+    @Path("mappers")
+    @Consumes(MediaType.APPLICATION_JSON)
+    Response addMapper(UserFederationMapperRepresentation mapper);
+
+
+    @GET
+    @Path("mappers/{id}")
+    @Produces(MediaType.APPLICATION_JSON)
+    UserFederationMapperRepresentation getMapperById(@PathParam("id") String id);
+
+
+    @PUT
+    @Path("mappers/{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    void updateMapper(@PathParam("id") String id, UserFederationMapperRepresentation rep);
+
+
+    @DELETE
+    @Path("mappers/{id}")
+    void removeMapper(@PathParam("id") String id);
+
+
+    @POST
+    @Path("mappers/{id}/sync")
+    @Produces(MediaType.APPLICATION_JSON)
+    UserFederationSyncResultRepresentation syncMapperData(@PathParam("id") String mapperId, @QueryParam("direction") String direction);
+}
                diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProvidersResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProvidersResource.java
new file mode 100644
index 0000000..e56ea3c
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProvidersResource.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.admin.client.resource;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
+import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface UserFederationProvidersResource {
+
+    @GET
+    @Path("providers")
+    @Produces(MediaType.APPLICATION_JSON)
+    List<UserFederationProviderFactoryRepresentation> getProviderFactories();
+
+
+    @GET
+    @Path("providers/{id}")
+    @Produces(MediaType.APPLICATION_JSON)
+    UserFederationProviderFactoryRepresentation getProviderFactory(@PathParam("id") String id);
+
+
+    @POST
+    @Path("instances")
+    @Consumes(MediaType.APPLICATION_JSON)
+    Response create(UserFederationProviderRepresentation rep);
+
+
+    @GET
+    @Path("instances")
+    @Produces(MediaType.APPLICATION_JSON)
+    List<UserFederationProviderRepresentation> getProviderInstances();
+
+
+    @Path("instances/{id}")
+    UserFederationProviderResource get(@PathParam("id") String id);
+
+}
                diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
index ecfc307..2a56aa0 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
@@ -158,6 +158,7 @@ public class UserFederationProviderResource {
     @POST
     @Path("sync")
     @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
     public UserFederationSyncResult syncUsers(@QueryParam("action") String action) {
         auth.requireManage();
 
@@ -352,6 +353,7 @@ public class UserFederationProviderResource {
     @POST
     @Path("mappers/{id}/sync")
     @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
     public UserFederationSyncResult syncMapperData(@PathParam("id") String mapperId, @QueryParam("direction") String direction) {
         auth.requireManage();
 
                diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyConfigurableUserFederationProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyConfigurableUserFederationProviderFactory.java
new file mode 100644
index 0000000..ec63c89
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyConfigurableUserFederationProviderFactory.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.keycloak.provider.ConfiguredProvider;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DummyConfigurableUserFederationProviderFactory extends DummyUserFederationProviderFactory implements ConfiguredProvider {
+
+    public static final String PROVIDER_NAME = "dummy-configurable";
+
+    @Override
+    public String getId() {
+        return PROVIDER_NAME;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Dummy User Federation Provider Help Text";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+
+        ProviderConfigProperty prop1 = new ProviderConfigProperty();
+        prop1.setName("prop1");
+        prop1.setLabel("Prop1");
+        prop1.setDefaultValue("prop1Default");
+        prop1.setHelpText("Prop1 HelpText");
+        prop1.setType(ProviderConfigProperty.STRING_TYPE);
+
+        ProviderConfigProperty prop2 = new ProviderConfigProperty();
+        prop2.setName("prop2");
+        prop2.setLabel("Prop2");
+        prop2.setDefaultValue("true");
+        prop2.setHelpText("Prop2 HelpText");
+        prop2.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+
+        return Arrays.asList(prop1, prop2);
+    }
+}
                diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProvider.java
new file mode 100644
index 0000000..0ba4816
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProvider.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation;
+
+import org.keycloak.models.CredentialValidationOutput;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserModel;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class DummyUserFederationProvider implements UserFederationProvider {
+
+    private final Map<String, UserModel> users;
+
+    public DummyUserFederationProvider(Map<String, UserModel> users) {
+        this.users = users;
+    }
+
+    @Override
+    public UserModel validateAndProxy(RealmModel realm, UserModel local) {
+        return local;
+    }
+
+    @Override
+    public boolean synchronizeRegistrations() {
+        return true;
+    }
+
+    @Override
+    public UserModel register(RealmModel realm, UserModel user) {
+        users.put(user.getUsername(), user);
+        return user;
+    }
+
+    @Override
+    public boolean removeUser(RealmModel realm, UserModel user) {
+        return users.remove(user.getUsername()) != null;
+    }
+
+    @Override
+    public UserModel getUserByUsername(RealmModel realm, String username) {
+        return users.get(username);
+    }
+
+    @Override
+    public UserModel getUserByEmail(RealmModel realm, String email) {
+        return null;
+    }
+
+    @Override
+    public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void preRemove(RealmModel realm) {
+
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, RoleModel role) {
+
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+
+    }
+
+    @Override
+    public boolean isValid(RealmModel realm, UserModel local) {
+        String username = local.getUsername();
+        return users.containsKey(username);
+    }
+
+    @Override
+    public Set<String> getSupportedCredentialTypes(UserModel user) {
+        // Just user "test-user" is able to validate password with this federationProvider
+        if (user.getUsername().equals("test-user")) {
+            return Collections.singleton(UserCredentialModel.PASSWORD);
+        } else {
+            return Collections.emptySet();
+        }
+    }
+
+    @Override
+    public Set<String> getSupportedCredentialTypes() {
+        return Collections.singleton(UserCredentialModel.PASSWORD);
+    }
+
+    @Override
+    public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
+        if (user.getUsername().equals("test-user") && input.size() == 1) {
+            UserCredentialModel password = input.get(0);
+            if (password.getType().equals(UserCredentialModel.PASSWORD)) {
+                return "secret".equals(password.getValue());
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
+        return validCredentials(realm, user, Arrays.asList(input));
+    }
+
+    @Override
+    public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) {
+        return CredentialValidationOutput.failed();
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
+
                diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProviderFactory.java
new file mode 100644
index 0000000..8bb9e11
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProviderFactory.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation;
+
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderFactory;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.ConfiguredProvider;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class DummyUserFederationProviderFactory implements UserFederationProviderFactory {
+
+    private static final Logger logger = Logger.getLogger(DummyUserFederationProviderFactory.class);
+    public static final String PROVIDER_NAME = "dummy";
+
+    private AtomicInteger fullSyncCounter = new AtomicInteger();
+    private AtomicInteger changedSyncCounter = new AtomicInteger();
+
+    private Map<String, UserModel> users = new HashMap<String, UserModel>();
+
+    @Override
+    public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
+        return new DummyUserFederationProvider(users);
+    }
+
+    @Override
+    public Set<String> getConfigurationOptions() {
+        Set<String> list = new HashSet<String>();
+        list.add("important.config");
+        return list;
+    }
+
+    @Override
+    public UserFederationProvider create(KeycloakSession session) {
+        return new DummyUserFederationProvider(users);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_NAME;
+    }
+
+    @Override
+    public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
+        logger.info("syncAllUsers invoked");
+        fullSyncCounter.incrementAndGet();
+        return UserFederationSyncResult.empty();
+    }
+
+    @Override
+    public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
+        logger.info("syncChangedUsers invoked");
+        changedSyncCounter.incrementAndGet();
+        return UserFederationSyncResult.empty();
+    }
+
+    public int getFullSyncCounter() {
+        return fullSyncCounter.get();
+    }
+
+    public int getChangedSyncCounter() {
+        return changedSyncCounter.get();
+    }
+
+}
                diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
new file mode 100644
index 0000000..015d4cd
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
@@ -0,0 +1,36 @@
+#
+# Copyright 2016 Red Hat, Inc. and/or its affiliates
+# and other contributors as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Copyright 2016 Red Hat, Inc. and/or its affiliates
+# and other contributors as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.keycloak.testsuite.federation.DummyUserFederationProviderFactory
+org.keycloak.testsuite.federation.DummyConfigurableUserFederationProviderFactory
\ No newline at end of file
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/AbstractCustomAccountManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/AbstractCustomAccountManagementTest.java
index 36ff0f6..c8f6d56 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/AbstractCustomAccountManagementTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/AbstractCustomAccountManagementTest.java
@@ -56,14 +56,8 @@ public abstract class AbstractCustomAccountManagementTest extends AbstractAccoun
     }
     
     protected AuthenticationExecutionInfoRepresentation getExecution(String flowAlias, String provider) {
-        Response response = authMgmtResource.getExecutions(flowAlias);
-        
-        List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(
-                new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
-                });
-        
-        response.close();
-        
+        List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions(flowAlias);
+
         for (AuthenticationExecutionInfoRepresentation exec : executionReps) {
             if (provider.equals(exec.getProviderId())) {
                 return exec;
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java
index fb7e966..50b11ac 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java
@@ -63,7 +63,7 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
     }
 
 
-    AuthenticationExecutionInfoRepresentation findExecutionByProvider(String provider, List<AuthenticationExecutionInfoRepresentation> executions) {
+    public static AuthenticationExecutionInfoRepresentation findExecutionByProvider(String provider, List<AuthenticationExecutionInfoRepresentation> executions) {
         for (AuthenticationExecutionInfoRepresentation exec : executions) {
             if (provider.equals(exec.getProviderId())) {
                 return exec;
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
index afe702f..7852e22 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
@@ -68,20 +68,12 @@ public class ExecutionTest extends AbstractAuthenticationTest {
         authMgmtResource.addExecution("Copy of browser", params);
 
         // check execution was added
-        response = authMgmtResource.getExecutions("Copy of browser");
-        AuthenticationExecutionInfoRepresentation exec;
-        AuthenticationExecutionInfoRepresentation authCookieExec;
-        try {
-            List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
-            });
-            exec = findExecutionByProvider("idp-review-profile", executionReps);
-            Assert.assertNotNull("idp-review-profile added", exec);
+        List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions("Copy of browser");
+        AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider("idp-review-profile", executionReps);
+        Assert.assertNotNull("idp-review-profile added", exec);
 
-            // we'll need auth-cookie later
-            authCookieExec = findExecutionByProvider("auth-cookie", executionReps);
-        } finally {
-            response.close();
-        }
+        // we'll need auth-cookie later
+        AuthenticationExecutionInfoRepresentation authCookieExec = findExecutionByProvider("auth-cookie", executionReps);
 
         compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
 
@@ -89,15 +81,9 @@ public class ExecutionTest extends AbstractAuthenticationTest {
         authMgmtResource.removeExecution(exec.getId());
 
         // check execution was removed
-        response = authMgmtResource.getExecutions("Copy of browser");
-        try {
-            List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
-            });
-            exec = findExecutionByProvider("idp-review-profile", executionReps);
-            Assert.assertNull("idp-review-profile removed", exec);
-        } finally {
-            response.close();
-        }
+        executionReps = authMgmtResource.getExecutions("Copy of browser");
+        exec = findExecutionByProvider("idp-review-profile", executionReps);
+        Assert.assertNull("idp-review-profile removed", exec);
 
         // now add the execution again using a different method and representation
 
@@ -130,16 +116,9 @@ public class ExecutionTest extends AbstractAuthenticationTest {
         }
 
         // check execution was added
-        List<AuthenticationExecutionInfoRepresentation> executions;
-        response = authMgmtResource.getExecutions("Copy of browser");
-        try {
-            executions = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
-            });
-            exec = findExecutionByProvider("auth-cookie", executions);
-            Assert.assertNotNull("auth-cookie added", exec);
-        } finally {
-            response.close();
-        }
+        List<AuthenticationExecutionInfoRepresentation> executions = authMgmtResource.getExecutions("Copy of browser");
+        exec = findExecutionByProvider("auth-cookie", executions);
+        Assert.assertNotNull("auth-cookie added", exec);
 
         // Note: there is no checking in addExecution if requirement is one of requirementChoices
         // Thus we can have OPTIONAL which is neither ALTERNATIVE, nor DISABLED
@@ -150,10 +129,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
     public void testUpdateExecution() {
 
         // get current auth-cookie execution
-        Response response = authMgmtResource.getExecutions("browser");
-        List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(
-                new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
-                });
+        List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions("browser");
         AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider("auth-cookie", executionReps);
 
         Assert.assertEquals("auth-cookie set to ALTERNATIVE", ALTERNATIVE, exec.getRequirement());
@@ -163,8 +139,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
         authMgmtResource.updateExecutions("browser", exec);
 
         // make sure the change is visible
-        response = authMgmtResource.getExecutions("browser");
-        executionReps = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {});
+        executionReps = authMgmtResource.getExecutions("browser");
 
         // get current auth-cookie execution
         AuthenticationExecutionInfoRepresentation exec2 = findExecutionByProvider("auth-cookie", executionReps);
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
index 45f538f..0f8487b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
@@ -55,9 +55,7 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
         List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
         for (AuthenticationFlowRepresentation flow : flows) {
             // get all executions for flow
-            Response executions = authMgmtResource.getExecutions(flow.getAlias());
-            List<AuthenticationExecutionInfoRepresentation> executionReps = executions.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
-            });
+            List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions(flow.getAlias());
 
             for (AuthenticationExecutionInfoRepresentation exec : executionReps) {
                 // separately load referenced configurations
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ShiftExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ShiftExecutionTest.java
index b7c3c15..f9d4f00 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ShiftExecutionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ShiftExecutionTest.java
@@ -45,9 +45,7 @@ public class ShiftExecutionTest extends AbstractAuthenticationTest {
         }
 
         // get executions
-        response = authMgmtResource.getExecutions("Copy of browser");
-        List<AuthenticationExecutionInfoRepresentation> executions = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
-        });
+        List<AuthenticationExecutionInfoRepresentation> executions = authMgmtResource.getExecutions("Copy of browser");
 
         AuthenticationExecutionInfoRepresentation last = executions.get(executions.size() - 1);
         AuthenticationExecutionInfoRepresentation oneButLast = executions.get(executions.size() - 2);
@@ -55,9 +53,7 @@ public class ShiftExecutionTest extends AbstractAuthenticationTest {
         // shift last execution up
         authMgmtResource.raisePriority(last.getId());
 
-        response = authMgmtResource.getExecutions("Copy of browser");
-        List<AuthenticationExecutionInfoRepresentation> executions2 = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
-        });
+        List<AuthenticationExecutionInfoRepresentation> executions2 = authMgmtResource.getExecutions("Copy of browser");
 
         AuthenticationExecutionInfoRepresentation last2 = executions2.get(executions.size() - 1);
         AuthenticationExecutionInfoRepresentation oneButLast2 = executions2.get(executions.size() - 2);
@@ -68,9 +64,7 @@ public class ShiftExecutionTest extends AbstractAuthenticationTest {
         // shift one before last down
         authMgmtResource.lowerPriority(oneButLast2.getId());
 
-        response = authMgmtResource.getExecutions("Copy of browser");
-        executions2 = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
-        });
+        executions2 = authMgmtResource.getExecutions("Copy of browser");
 
         last2 = executions2.get(executions.size() - 1);
         oneButLast2 = executions2.get(executions.size() - 2);
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java
new file mode 100644
index 0000000..0cd3508
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.admin;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import org.junit.Test;
+import org.keycloak.admin.client.resource.UserFederationProvidersResource;
+import org.keycloak.common.constants.KerberosConstants;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
+import org.keycloak.representations.idm.ConfigPropertyRepresentation;
+import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
+import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+import org.keycloak.representations.idm.UserFederationSyncResultRepresentation;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
+import org.keycloak.testsuite.util.UserFederationProviderBuilder;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationTest extends AbstractAdminTest {
+
+    @Test
+    public void testProviderFactories() {
+        List<UserFederationProviderFactoryRepresentation> providerFactories = userFederation().getProviderFactories();
+        Assert.assertNames(providerFactories, "ldap", "kerberos", "dummy", "dummy-configurable");
+
+        // Builtin provider without properties
+        UserFederationProviderFactoryRepresentation ldapProvider = userFederation().getProviderFactory("ldap");
+        Assert.assertEquals(ldapProvider.getId(), "ldap");
+        Assert.assertEquals(0, ldapProvider.getOptions().size());
+
+        // Configurable through the "old-way" options
+        UserFederationProviderFactoryRepresentation dummyProvider = userFederation().getProviderFactory("dummy");
+        Assert.assertEquals(dummyProvider.getId(), "dummy");
+        Assert.assertNames(new LinkedList<>(dummyProvider.getOptions()), "important.config");
+
+        // Configurable through the "new-way" ConfiguredProvider
+        UserFederationProviderFactoryRepresentation dummyConfiguredProvider = userFederation().getProviderFactory("dummy-configurable");
+        Assert.assertEquals(dummyConfiguredProvider.getId(), "dummy-configurable");
+        Assert.assertTrue(dummyConfiguredProvider.getOptions() == null || dummyConfiguredProvider.getOptions().isEmpty());
+        Assert.assertEquals("Dummy User Federation Provider Help Text", dummyConfiguredProvider.getHelpText());
+        Assert.assertEquals(2, dummyConfiguredProvider.getProperties().size());
+        assertProviderConfigProperty(dummyConfiguredProvider.getProperties().get(0), "prop1", "Prop1", "prop1Default", "Prop1 HelpText", ProviderConfigProperty.STRING_TYPE);
+        assertProviderConfigProperty(dummyConfiguredProvider.getProperties().get(1), "prop2", "Prop2", "true", "Prop2 HelpText", ProviderConfigProperty.BOOLEAN_TYPE);
+
+        try {
+            userFederation().getProviderFactory("not-existent");
+            Assert.fail("Not expected to find not-existent provider");
+        } catch (NotFoundException nfe) {
+            nfe.getResponse().close();
+        }
+    }
+
+    private UserFederationProvidersResource userFederation() {
+        return realm.userFederation();
+    }
+
+    private void assertProviderConfigProperty(ConfigPropertyRepresentation property, String name, String label, String defaultValue, String helpText, String type) {
+        Assert.assertEquals(name, property.getName());
+        Assert.assertEquals(label, property.getLabel());
+        Assert.assertEquals(defaultValue, property.getDefaultValue());
+        Assert.assertEquals(helpText, property.getHelpText());
+        Assert.assertEquals(type, property.getType());
+    }
+
+
+    @Test
+    public void testCreateProvider() {
+        // create provider without configuration and displayName
+        UserFederationProviderRepresentation dummyRep1 = UserFederationProviderBuilder.create()
+                .providerName("dummy")
+                .displayName("")
+                .priority(2)
+                .fullSyncPeriod(1000)
+                .changedSyncPeriod(500)
+                .lastSync(123)
+                .build();
+
+        String id1 = createUserFederationProvider(dummyRep1);
+
+        // create provider with configuration and displayName
+        UserFederationProviderRepresentation dummyRep2 = UserFederationProviderBuilder.create()
+                .providerName("dummy")
+                .displayName("dn1")
+                .priority(1)
+                .configProperty("prop1", "prop1Val")
+                .configProperty("prop2", "true")
+                .build();
+        String id2 = createUserFederationProvider(dummyRep2);
+
+        // Assert provider instances available
+        assertFederationProvider(userFederation().get(id1).toRepresentation(), id1, id1, "dummy", 2, 1000, 500, 123);
+        assertFederationProvider(userFederation().get(id2).toRepresentation(), id2, "dn1", "dummy", 1, -1, -1, -1, "prop1", "prop1Val", "prop2", "true");
+
+        // Assert sorted
+        List<UserFederationProviderRepresentation> providerInstances = userFederation().getProviderInstances();
+        Assert.assertEquals(providerInstances.size(), 2);
+        assertFederationProvider(providerInstances.get(0), id2, "dn1", "dummy", 1, -1, -1, -1, "prop1", "prop1Val", "prop2", "true");
+        assertFederationProvider(providerInstances.get(1), id1, id1, "dummy", 2, 1000, 500, 123);
+
+        // Remove providers
+        userFederation().get(id1).remove();
+        userFederation().get(id2).remove();
+    }
+
+
+    @Test
+    public void testValidateAndCreateLdapProvider() {
+        // Invalid filter
+        UserFederationProviderRepresentation ldapRep = UserFederationProviderBuilder.create()
+                .displayName("ldap1")
+                .providerName("ldap")
+                .priority(1)
+                .configProperty(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "dc=something")
+                .build();
+        Response resp = userFederation().create(ldapRep);
+        Assert.assertEquals(400, resp.getStatus());
+        resp.close();
+
+        // Invalid filter
+        ldapRep.getConfig().put(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(dc=something");
+        resp = userFederation().create(ldapRep);
+        Assert.assertEquals(400, resp.getStatus());
+        resp.close();
+
+        // Invalid filter
+        ldapRep.getConfig().put(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "dc=something)");
+        resp = userFederation().create(ldapRep);
+        Assert.assertEquals(400, resp.getStatus());
+        resp.close();
+
+        // Assert nothing created so far
+        Assert.assertTrue(userFederation().getProviderInstances().isEmpty());
+
+
+        // Valid filter. Creation success
+        ldapRep.getConfig().put(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(dc=something)");
+        String id1 = createUserFederationProvider(ldapRep);
+
+        // Missing filter is ok too. Creation success
+        UserFederationProviderRepresentation ldapRep2 = UserFederationProviderBuilder.create()
+                .displayName("ldap2")
+                .providerName("ldap")
+                .priority(2)
+                .configProperty(LDAPConstants.BIND_DN, "cn=manager")
+                .configProperty(LDAPConstants.BIND_CREDENTIAL, "password")
+                .build();
+        String id2 = createUserFederationProvider(ldapRep2);
+
+        // Assert both providers created
+        List<UserFederationProviderRepresentation> providerInstances = userFederation().getProviderInstances();
+        Assert.assertEquals(providerInstances.size(), 2);
+        assertFederationProvider(providerInstances.get(0), id1, "ldap1", "ldap", 1, -1, -1, -1, LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(dc=something)");
+        assertFederationProvider(providerInstances.get(1), id2, "ldap2", "ldap", 2, -1, -1, -1, LDAPConstants.BIND_DN, "cn=manager", LDAPConstants.BIND_CREDENTIAL, "password");
+
+        // Cleanup
+        userFederation().get(id1).remove();
+        userFederation().get(id2).remove();
+    }
+
+
+    @Test
+    public void testUpdateProvider() {
+        UserFederationProviderRepresentation ldapRep = UserFederationProviderBuilder.create()
+                .providerName("ldap")
+                .priority(2)
+                .configProperty(LDAPConstants.BIND_DN, "cn=manager")
+                .configProperty(LDAPConstants.BIND_CREDENTIAL, "password")
+                .build();
+        String id = createUserFederationProvider(ldapRep);
+        assertFederationProvider(userFederation().get(id).toRepresentation(), id, id, "ldap", 2, -1, -1, -1, LDAPConstants.BIND_DN, "cn=manager", LDAPConstants.BIND_CREDENTIAL, "password");
+
+        // Assert update with invalid filter should fail
+        ldapRep = userFederation().get(id).toRepresentation();
+        ldapRep.setDisplayName("");
+        ldapRep.getConfig().put(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(dc=something2");
+        ldapRep.getConfig().put(LDAPConstants.BIND_DN, "cn=manager-updated");
+        try {
+            userFederation().get(id).update(ldapRep);
+            Assert.fail("Not expected to successfull update");
+        } catch (BadRequestException bre) {
+            bre.getResponse().close();
+        }
+
+        // Assert nothing was updated
+        assertFederationProvider(userFederation().get(id).toRepresentation(), id, id, "ldap", 2, -1, -1, -1, LDAPConstants.BIND_DN, "cn=manager", LDAPConstants.BIND_CREDENTIAL, "password");
+
+        // Change filter to be valid
+        ldapRep.getConfig().put(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(dc=something2)");
+        userFederation().get(id).update(ldapRep);
+
+        // Assert updated successfully
+        ldapRep = userFederation().get(id).toRepresentation();
+        assertFederationProvider(ldapRep, id, id, "ldap", 2, -1, -1, -1, LDAPConstants.BIND_DN, "cn=manager-updated", LDAPConstants.BIND_CREDENTIAL, "password",
+                LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(dc=something2)");
+
+        // Assert update displayName
+        ldapRep.setDisplayName("ldap2");
+        userFederation().get(id).update(ldapRep);
+        assertFederationProvider(userFederation().get(id).toRepresentation(), id, "ldap2", "ldap", 2, -1, -1, -1, LDAPConstants.BIND_DN, "cn=manager-updated", LDAPConstants.BIND_CREDENTIAL, "password",
+                LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(dc=something2)");
+
+
+
+        // Cleanup
+        userFederation().get(id).remove();
+    }
+
+
+    @Test
+    public void testKerberosAuthenticatorEnabledAutomatically() {
+        // Assert kerberos authenticator DISABLED
+        AuthenticationExecutionInfoRepresentation kerberosExecution = findKerberosExecution();
+        Assert.assertEquals(kerberosExecution.getRequirement(), AuthenticationExecutionModel.Requirement.DISABLED.toString());
+
+        // create LDAP provider with kerberos
+        UserFederationProviderRepresentation ldapRep = UserFederationProviderBuilder.create()
+                .displayName("ldap2")
+                .providerName("ldap")
+                .priority(2)
+                .configProperty(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "true")
+                .build();
+        String id = createUserFederationProvider(ldapRep);
+
+        // Assert kerberos authenticator ALTERNATIVE
+        kerberosExecution = findKerberosExecution();
+        Assert.assertEquals(kerberosExecution.getRequirement(), AuthenticationExecutionModel.Requirement.ALTERNATIVE.toString());
+
+        // Switch kerberos authenticator to DISABLED
+        kerberosExecution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED.toString());
+        realm.flows().updateExecutions("browser", kerberosExecution);
+
+        // update LDAP provider with kerberos
+        ldapRep = userFederation().get(id).toRepresentation();
+        userFederation().get(id).update(ldapRep);
+
+        // Assert kerberos authenticator ALTERNATIVE
+        kerberosExecution = findKerberosExecution();
+        Assert.assertEquals(kerberosExecution.getRequirement(), AuthenticationExecutionModel.Requirement.ALTERNATIVE.toString());
+
+        // Cleanup
+        kerberosExecution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED.toString());
+        realm.flows().updateExecutions("browser", kerberosExecution);
+        userFederation().get(id).remove();
+    }
+
+
+    @Test (expected = NotFoundException.class)
+    public void testLookupNotExistentProvider() {
+        userFederation().get("not-existent").toRepresentation();
+    }
+
+
+    @Test
+    public void testSyncFederationProvider() {
+        // create provider
+        UserFederationProviderRepresentation dummyRep1 = UserFederationProviderBuilder.create()
+                .providerName("dummy")
+                .build();
+        String id1 = createUserFederationProvider(dummyRep1);
+
+
+        // Sync with unknown action shouldn't pass
+        try {
+            userFederation().get(id1).syncUsers("unknown");
+            Assert.fail("Not expected to sync with unknown action");
+        } catch (NotFoundException nfe) {
+            nfe.getResponse().close();
+        }
+
+        // Assert sync didn't happen
+        Assert.assertEquals(-1, userFederation().get(id1).toRepresentation().getLastSync());
+
+        // Sync and assert it happened
+        UserFederationSyncResultRepresentation syncResult = userFederation().get(id1).syncUsers("triggerFullSync");
+        Assert.assertEquals("0 imported users, 0 updated users", syncResult.getStatus());
+        int fullSyncTime = userFederation().get(id1).toRepresentation().getLastSync();
+        Assert.assertTrue(fullSyncTime > 0);
+
+        // Changed sync
+        setTimeOffset(50);
+        syncResult = userFederation().get(id1).syncUsers("triggerChangedUsersSync");
+        Assert.assertEquals("0 imported users, 0 updated users", syncResult.getStatus());
+        int changedSyncTime = userFederation().get(id1).toRepresentation().getLastSync();
+        Assert.assertTrue(fullSyncTime + 50 <= changedSyncTime);
+
+        // Cleanup
+        resetTimeOffset();
+        userFederation().get(id1).remove();
+    }
+
+
+    private String createUserFederationProvider(UserFederationProviderRepresentation rep) {
+        Response resp = userFederation().create(rep);
+        Assert.assertEquals(201, resp.getStatus());
+        resp.close();
+        return ApiUtil.getCreatedId(resp);
+    }
+
+    private void assertFederationProvider(UserFederationProviderRepresentation rep, String id, String displayName, String providerName,
+                                          int priority, int fullSyncPeriod, int changeSyncPeriod, int lastSync,
+                                          String... config) {
+        Assert.assertEquals(id, rep.getId());
+        Assert.assertEquals(displayName, rep.getDisplayName());
+        Assert.assertEquals(providerName, rep.getProviderName());
+        Assert.assertEquals(priority, rep.getPriority());
+        Assert.assertEquals(fullSyncPeriod, rep.getFullSyncPeriod());
+        Assert.assertEquals(changeSyncPeriod, rep.getChangedSyncPeriod());
+        Assert.assertEquals(lastSync, rep.getLastSync());
+        if (config == null) {
+            config = new String[] {};
+        }
+
+        Assert.assertEquals(rep.getConfig().size() * 2, config.length);
+        for (int i=0 ; i<config.length ; i+=2) {
+            String key = config[i];
+            String value = config[i+1];
+            Assert.assertEquals(value, rep.getConfig().get(key));
+        }
+    }
+
+
+    private AuthenticationExecutionInfoRepresentation findKerberosExecution() {
+        AuthenticationExecutionInfoRepresentation kerberosExecution = null;
+        List<AuthenticationExecutionInfoRepresentation> executionReps = realm.flows().getExecutions("browser");
+        kerberosExecution = AbstractAuthenticationTest.findExecutionByProvider("auth-spnego", executionReps);
+
+        Assert.assertNotNull(kerberosExecution);
+        return kerberosExecution;
+    }
+}
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/Assert.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/Assert.java
index 2a82d48..267c832 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/Assert.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/Assert.java
@@ -22,16 +22,13 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
 
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 
-import static org.junit.Assert.assertArrayEquals;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -49,16 +46,6 @@ public class Assert extends org.junit.Assert {
         assertArrayEquals("Expected: " + Arrays.toString(expected) + ", was: " + Arrays.toString(actualNames), expected, actualNames);
     }
 
-    private static <T> List<T> sort(List<T> list) {
-        Collections.sort(list, new Comparator<Object>() {
-            @Override
-            public int compare(Object o1, Object o2) {
-                return name(o1).compareTo(name(o2));
-            }
-        });
-        return list;
-    }
-
     private static <T> String[] names(List<T> list) {
         String[] names = new String[list.size()];
         for (int i = 0; i < list.size(); i++) {
@@ -69,7 +56,9 @@ public class Assert extends org.junit.Assert {
     }
 
     private static String name(Object o1) {
-        if (o1 instanceof RealmRepresentation) {
+        if (o1 instanceof String) {
+            return (String) o1;
+        } else if (o1 instanceof RealmRepresentation) {
             return ((RealmRepresentation) o1).getRealm();
         } else if (o1 instanceof ClientRepresentation) {
             return ((ClientRepresentation) o1).getClientId();
@@ -79,7 +68,10 @@ public class Assert extends org.junit.Assert {
             return ((RoleRepresentation) o1).getName();
         } else if (o1 instanceof UserRepresentation) {
             return ((UserRepresentation) o1).getUsername();
+        } else if (o1 instanceof UserFederationProviderFactoryRepresentation) {
+            return ((UserFederationProviderFactoryRepresentation) o1).getId();
         }
+
         throw new IllegalArgumentException();
     }
 }
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserFederationProviderBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserFederationProviderBuilder.java
new file mode 100644
index 0000000..61f45c7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserFederationProviderBuilder.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationProviderBuilder {
+
+    private String id;
+    private String displayName;
+    private String providerName;
+    private Map<String, String> config;
+    private int priority = 1;
+    private int fullSyncPeriod = -1;
+    private int changedSyncPeriod = -1;
+    private int lastSync = -1;
+
+    private UserFederationProviderBuilder() {};
+
+    public static UserFederationProviderBuilder create() {
+        return new UserFederationProviderBuilder();
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public UserFederationProviderBuilder displayName(String displayName) {
+        this.displayName = displayName;
+        return this;
+    }
+
+    public UserFederationProviderBuilder providerName(String providerName) {
+        this.providerName = providerName;
+        return this;
+    }
+
+    public UserFederationProviderBuilder configProperty(String key, String value) {
+        if (this.config == null) {
+            this.config = new HashMap<>();
+        }
+        this.config.put(key, value);
+        return this;
+    }
+
+    public UserFederationProviderBuilder removeConfigProperty(String key) {
+        if (this.config != null) {
+            this.config.remove(key);
+        }
+        return this;
+    }
+
+    public UserFederationProviderBuilder priority(int priority) {
+        this.priority = priority;
+        return this;
+    }
+
+    public UserFederationProviderBuilder fullSyncPeriod(int fullSyncPeriod) {
+        this.fullSyncPeriod = fullSyncPeriod;
+        return this;
+    }
+
+    public UserFederationProviderBuilder changedSyncPeriod(int changedSyncPeriod) {
+        this.changedSyncPeriod = changedSyncPeriod;
+        return this;
+    }
+
+    public UserFederationProviderBuilder lastSync(int lastSync) {
+        this.lastSync = lastSync;
+        return this;
+    }
+
+    public UserFederationProviderRepresentation build() {
+        UserFederationProviderRepresentation rep = new UserFederationProviderRepresentation();
+        rep.setId(id);
+        rep.setDisplayName(displayName);
+        rep.setProviderName(providerName);
+        rep.setConfig(config);
+        rep.setPriority(priority);
+        rep.setFullSyncPeriod(fullSyncPeriod);
+        rep.setChangedSyncPeriod(changedSyncPeriod);
+        rep.setLastSync(lastSync);
+        return rep;
+    }
+
+
+}