keycloak-aplcache

Merge pull request #977 from mposolda/master LDAP testing

2/16/2015 9:12:55 AM

Changes

pom.xml 36(+24 -12)

Details

diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java
index 35a7e42..aded77e 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java
@@ -18,6 +18,7 @@ import org.keycloak.connections.mongo.api.types.Mapper;
 import org.keycloak.connections.mongo.api.types.MapperContext;
 import org.keycloak.connections.mongo.api.types.MapperRegistry;
 import org.keycloak.connections.mongo.impl.types.BasicDBListMapper;
+import org.keycloak.connections.mongo.impl.types.BasicDBListToSetMapper;
 import org.keycloak.connections.mongo.impl.types.BasicDBObjectMapper;
 import org.keycloak.connections.mongo.impl.types.BasicDBObjectToMapMapper;
 import org.keycloak.connections.mongo.impl.types.EnumToStringMapper;
@@ -35,8 +36,10 @@ import org.keycloak.models.utils.reflection.PropertyQueries;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
@@ -71,6 +74,10 @@ public class MongoStoreImpl implements MongoStore {
         mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, List.class));
         mapperRegistry.addDBObjectMapper(new BasicDBListMapper(mapperRegistry));
 
+        mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, HashSet.class));
+        mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, Set.class));
+        mapperRegistry.addDBObjectMapper(new BasicDBListToSetMapper(mapperRegistry));
+
         mapperRegistry.addAppObjectMapper(new MapMapper(HashMap.class));
         mapperRegistry.addAppObjectMapper(new MapMapper(Map.class));
         mapperRegistry.addDBObjectMapper(new BasicDBObjectToMapMapper());
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListMapper.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListMapper.java
index f44f545..cc229c6 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListMapper.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListMapper.java
@@ -6,6 +6,7 @@ import org.keycloak.connections.mongo.api.types.MapperContext;
 import org.keycloak.connections.mongo.api.types.MapperRegistry;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 /**
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListToSetMapper.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListToSetMapper.java
new file mode 100644
index 0000000..d43781a
--- /dev/null
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListToSetMapper.java
@@ -0,0 +1,44 @@
+package org.keycloak.connections.mongo.impl.types;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.mongodb.BasicDBList;
+import org.keycloak.connections.mongo.api.types.Mapper;
+import org.keycloak.connections.mongo.api.types.MapperContext;
+import org.keycloak.connections.mongo.api.types.MapperRegistry;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class BasicDBListToSetMapper implements Mapper<BasicDBList, Set> {
+
+    private final MapperRegistry mapperRegistry;
+
+    public BasicDBListToSetMapper(MapperRegistry mapperRegistry) {
+        this.mapperRegistry = mapperRegistry;
+    }
+
+    @Override
+    public Set convertObject(MapperContext<BasicDBList, Set> context) {
+        BasicDBList dbList = context.getObjectToConvert();
+        Set<Object> appObjects = new HashSet<Object>();
+        Class<?> expectedListElementType = context.getGenericTypes().get(0);
+
+        for (Object dbObject : dbList) {
+            MapperContext<Object, Object> newContext = new MapperContext<Object, Object>(dbObject, expectedListElementType, null);
+            appObjects.add(mapperRegistry.convertDBObjectToApplicationObject(newContext));
+        }
+        return appObjects;
+    }
+
+    @Override
+    public Class<? extends BasicDBList> getTypeOfObjectToConvert() {
+        return BasicDBList.class;
+    }
+
+    @Override
+    public Class<Set> getExpectedReturnType() {
+        return Set.class;
+    }
+}
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/ListMapper.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/ListMapper.java
index 3274fe3..dc649d6 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/ListMapper.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/ListMapper.java
@@ -5,12 +5,12 @@ import org.keycloak.connections.mongo.api.types.Mapper;
 import org.keycloak.connections.mongo.api.types.MapperContext;
 import org.keycloak.connections.mongo.api.types.MapperRegistry;
 
-import java.util.List;
+import java.util.Collection;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public class ListMapper<T extends List> implements Mapper<T, BasicDBList> {
+public class ListMapper<T extends Collection> implements Mapper<T, BasicDBList> {
 
     private final MapperRegistry mapperRegistry;
     private final Class<T> listType;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
index 7ce4935..691bce4 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
@@ -27,11 +27,9 @@ public class MongoRealmProvider implements RealmProvider {
 
     private final MongoStoreInvocationContext invocationContext;
     private final KeycloakSession session;
-    private final MongoStore mongoStore;
 
-    public MongoRealmProvider(KeycloakSession session, MongoStore mongoStore, MongoStoreInvocationContext invocationContext) {
+    public MongoRealmProvider(KeycloakSession session, MongoStoreInvocationContext invocationContext) {
         this.session = session;
-        this.mongoStore = mongoStore;
         this.invocationContext = invocationContext;
     }
 
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java
index fceeb82..9606753 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java
@@ -27,7 +27,7 @@ public class MongoRealmProviderFactory implements RealmProviderFactory {
     @Override
     public RealmProvider create(KeycloakSession session) {
         MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
-        return new MongoRealmProvider(session, connection.getMongoStore(), connection.getInvocationContext());
+        return new MongoRealmProvider(session, connection.getInvocationContext());
     }
 
     @Override
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 52d4c98..9422711 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -33,11 +33,9 @@ public class MongoUserProvider implements UserProvider {
 
     private final MongoStoreInvocationContext invocationContext;
     private final KeycloakSession session;
-    private final MongoStore mongoStore;
 
-    public MongoUserProvider(KeycloakSession session, MongoStore mongoStore, MongoStoreInvocationContext invocationContext) {
+    public MongoUserProvider(KeycloakSession session, MongoStoreInvocationContext invocationContext) {
         this.session = session;
-        this.mongoStore = mongoStore;
         this.invocationContext = invocationContext;
     }
 
@@ -311,6 +309,7 @@ public class MongoUserProvider implements UserProvider {
 
     @Override
     public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
+        federatedUser = getUserById(federatedUser.getId(), realm);
         MongoUserEntity userEntity = ((UserAdapter) federatedUser).getUser();
         FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, federatedIdentityModel.getIdentityProvider());
 
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProviderFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProviderFactory.java
index 083d0c0..a117048 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProviderFactory.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProviderFactory.java
@@ -27,7 +27,7 @@ public class MongoUserProviderFactory implements UserProviderFactory {
     @Override
     public UserProvider create(KeycloakSession session) {
         MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
-        return new MongoUserProvider(session, connection.getMongoStore(), connection.getInvocationContext());
+        return new MongoUserProvider(session, connection.getInvocationContext());
     }
 
     @Override

pom.xml 36(+24 -12)

diff --git a/pom.xml b/pom.xml
index 4aada1a..d43ce03 100755
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,8 @@
 
     <properties>
         <aesh.version>0.33.12</aesh.version>
+        <apacheds.version>2.0.0-M17</apacheds.version>
+        <apacheds.codec.version>1.0.0-M23</apacheds.codec.version>
         <base64.version>2.3.8</base64.version>
         <bouncycastle.crypto.version>1.50</bouncycastle.crypto.version>
         <bouncycastle.mail.version>1.50</bouncycastle.mail.version>
@@ -23,7 +25,6 @@
         <!-- <undertow.version>1.1.0.Final</undertow.version> -->
         <undertow.version>1.1.1.Final</undertow.version>
         <picketlink.version>2.7.0.CR3</picketlink.version>
-        <picketbox.ldap.version>1.0.2.Final</picketbox.ldap.version>
         <mongo.driver.version>2.11.3</mongo.driver.version>
         <jboss.logging.version>3.1.4.GA</jboss.logging.version>
         <syslog4j.version>0.9.30</syslog4j.version>
@@ -308,17 +309,6 @@
                 <version>${picketlink.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.picketbox</groupId>
-                <artifactId>picketbox-ldap</artifactId>
-                <version>${picketbox.ldap.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.picketbox</groupId>
-                <artifactId>picketbox-ldap</artifactId>
-                <version>${picketbox.ldap.version}</version>
-                <type>test-jar</type>
-            </dependency>
-            <dependency>
                 <groupId>org.jboss.logging</groupId>
                 <artifactId>jboss-logging</artifactId>
                 <version>${jboss.logging.version}</version>
@@ -422,6 +412,28 @@
                 <version>${winzipaes.version}</version>
             </dependency>
 
+            <!-- Apache DS -->
+            <dependency>
+                <groupId>org.apache.directory.server</groupId>
+                <artifactId>apacheds-core-annotations</artifactId>
+                <version>${apacheds.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.directory.server</groupId>
+                <artifactId>apacheds-interceptor-kerberos</artifactId>
+                <version>${apacheds.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.directory.server</groupId>
+                <artifactId>apacheds-server-annotations</artifactId>
+                <version>${apacheds.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.directory.api</groupId>
+                <artifactId>api-ldap-codec-standalone</artifactId>
+                <version>${apacheds.codec.version}</version>
+            </dependency>
+
             <!-- Selenium -->
             <dependency>
                 <groupId>org.seleniumhq.selenium</groupId>
diff --git a/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java b/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
index 2b78406..fff0bfb 100755
--- a/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
@@ -27,11 +27,10 @@ public class LDAPConnectionTestManager {
         try {
             Hashtable<String, Object> env = new Hashtable<String, Object>();
             env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
-            env.put(Context.SECURITY_AUTHENTICATION, "simple");
-
             env.put(Context.PROVIDER_URL, connectionUrl);
 
             if (TEST_AUTHENTICATION.equals(action)) {
+                env.put(Context.SECURITY_AUTHENTICATION, "simple");
                 env.put(Context.SECURITY_PRINCIPAL, bindDn);
 
                 char[] bindCredentialChar = null;
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 5800d98..98697e5 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -114,10 +114,6 @@
             <artifactId>javase</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.bouncycastle</groupId>
-            <artifactId>bcprov-jdk15on</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
             <version>${keycloak.apache.httpcomponents.version}</version>
@@ -212,16 +208,32 @@
             <groupId>org.seleniumhq.selenium</groupId>
             <artifactId>selenium-chrome-driver</artifactId>
         </dependency>
+
+        <!-- Apache DS -->
         <dependency>
-            <groupId>org.picketbox</groupId>
-            <artifactId>picketbox-ldap</artifactId>
-            <type>test-jar</type>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-core-annotations</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
-            <groupId>org.picketbox</groupId>
-            <artifactId>picketbox-ldap</artifactId>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-interceptor-kerberos</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-server-annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.api</groupId>
+            <artifactId>api-ldap-codec-standalone</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.picketlink</groupId>
             <artifactId>picketlink-wildfly-common</artifactId>
             <scope>test</scope>
@@ -270,6 +282,12 @@
                     <workingDirectory>${project.basedir}</workingDirectory>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+            </plugin>
         </plugins>
     </build>
 
@@ -316,6 +334,34 @@
                 </plugins>
             </build>
         </profile>
+        <profile>
+            <id>ldap</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testutils.ldap.LDAPEmbeddedServer</mainClass>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>kerberos</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testutils.ldap.KerberosEmbeddedServer</mainClass>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
 
         <profile>
             <id>jpa</id>
diff --git a/testsuite/integration/README.md b/testsuite/integration/README.md
index 9de055c..a8d0671 100644
--- a/testsuite/integration/README.md
+++ b/testsuite/integration/README.md
@@ -90,4 +90,34 @@ To configure Keycloak to use the above server add the following system propertie
 For example if using the test utils Keycloak server start it with:
 
     mvn exec:java -Pkeycloak-server -Dkeycloak.mail.smtp.from=auto@keycloak.org -Dkeycloak.mail.smtp.host=localhost -Dkeycloak.mail.smtp.port=3025
+    
+LDAP server
+-----------
+
+To start a ApacheDS based LDAP server for testing LDAP sending run:
+    
+    mvn exec:java -Pldap
+    
+There are additional system properties you can use to configure (See EmbeddedServersFactory class for details). Once done, you can create LDAP Federation provider
+in Keycloak admin console with the settings like:
+Vendor: Other
+Connection URL: ldap://localhost:10389
+Base DN: dc=keycloak,dc=org
+User DN Suffix: ou=People,dc=keycloak,dc=org
+Bind DN: uid=admin,ou=system
+Bind credential: secret
+
+Kerberos server
+---------------
+
+To start a ApacheDS based Kerberos server for testing Kerberos + LDAP sending run:
+    
+    mvn exec:java -Pkerberos
+    
+There are additional system properties you can use to configure (See EmbeddedServersFactory class for details). Once done, you can create LDAP Federation provider
+in Keycloak admin console with same settings like mentioned in previous LDAP section. And you can enable Kerberos with the settings like:
+
+Server Principal: HTTP/localhost@KEYCLOAK.ORG
+KeyTab: $KEYCLOAK_SOURCES/testsuite/integration/src/main/resources/kerberos/http.keytab
+
 
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
index 9b0c200..d5e1c37 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
@@ -104,27 +104,8 @@ public class KeycloakServer {
     }
 
     public static void main(String[] args) throws Throwable {
-        //bootstrapLdap();  Can't seem to get this to work.
         bootstrapKeycloakServer(args);
     }
-    /*private static LDAPEmbeddedServer embeddedServer;
-    public static void bootstrapLdap() throws Exception {
-        embeddedServer = new LDAPEmbeddedServer();
-        embeddedServer.setup();
-        embeddedServer.importLDIF("ldap/users.ldif");
-        Runtime.getRuntime().addShutdownHook(new Thread() {
-            @Override
-            public void run() {
-                try {
-                    embeddedServer.tearDown();
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        });
-
-
-    } */
 
     public static KeycloakServer bootstrapKeycloakServer(String[] args) throws Throwable {
         KeycloakServerConfig config = new KeycloakServerConfig();
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java
new file mode 100644
index 0000000..a377d9b
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java
@@ -0,0 +1,82 @@
+package org.keycloak.testutils.ldap;
+
+/**
+ * Factory for ApacheDS based LDAP and Kerberos servers
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class EmbeddedServersFactory {
+
+    private static final String DEFAULT_BASE_DN = "dc=keycloak,dc=org";
+    private static final String DEFAULT_BIND_HOST = "localhost";
+    private static final int DEFAULT_BIND_PORT = 10389;
+    private static final String DEFAULT_LDIF_FILE = "ldap/users.ldif";
+
+    private static final String DEFAULT_KERBEROS_LDIF_FILE = "kerberos/users-kerberos.ldif";
+
+    private static final String DEFAULT_KERBEROS_REALM = "KEYCLOAK.ORG";
+    private static final int DEFAULT_KDC_PORT = 6088;
+
+    private String baseDN;
+    private String bindHost;
+    private int bindPort;
+    private String ldifFile;
+    private String kerberosRealm;
+    private int kdcPort;
+
+
+    public static EmbeddedServersFactory readConfiguration() {
+        EmbeddedServersFactory factory = new EmbeddedServersFactory();
+        factory.readProperties();
+        return factory;
+    }
+
+
+    protected void readProperties() {
+        this.baseDN = System.getProperty("ldap.baseDN");
+        this.bindHost = System.getProperty("ldap.host");
+        String bindPort = System.getProperty("ldap.port");
+        this.ldifFile = System.getProperty("ldap.ldif");
+
+        this.kerberosRealm = System.getProperty("kerberos.realm");
+        String kdcPort = System.getProperty("kerberos.port");
+
+        if (baseDN == null || baseDN.isEmpty()) {
+            baseDN = DEFAULT_BASE_DN;
+        }
+        if (bindHost == null || bindHost.isEmpty()) {
+            bindHost = DEFAULT_BIND_HOST;
+        }
+        this.bindPort = (bindPort == null || bindPort.isEmpty()) ? DEFAULT_BIND_PORT : Integer.parseInt(bindPort);
+        if (ldifFile == null || ldifFile.isEmpty()) {
+            ldifFile = DEFAULT_LDIF_FILE;
+        }
+
+        if (kerberosRealm == null || kerberosRealm.isEmpty()) {
+            kerberosRealm = DEFAULT_KERBEROS_REALM;
+        }
+        this.kdcPort = (kdcPort == null || kdcPort.isEmpty()) ? DEFAULT_KDC_PORT : Integer.parseInt(kdcPort);
+    }
+
+
+    public LDAPEmbeddedServer createLdapServer() {
+
+        // Override LDIF file with default for embedded LDAP
+        if (ldifFile.equals(DEFAULT_KERBEROS_LDIF_FILE)) {
+            ldifFile = DEFAULT_LDIF_FILE;
+        }
+
+        return new LDAPEmbeddedServer(baseDN, bindHost, bindPort, ldifFile);
+    }
+
+
+    public KerberosEmbeddedServer createKerberosServer() {
+
+        // Override LDIF file with default for embedded Kerberos
+        if (ldifFile.equals(DEFAULT_LDIF_FILE)) {
+            ldifFile = DEFAULT_KERBEROS_LDIF_FILE;
+        }
+
+        return new KerberosEmbeddedServer(baseDN, bindHost, bindPort, ldifFile, kerberosRealm, kdcPort);
+    }
+}
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemoryDirectoryServiceFactory.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemoryDirectoryServiceFactory.java
new file mode 100644
index 0000000..bba4954
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemoryDirectoryServiceFactory.java
@@ -0,0 +1,144 @@
+package org.keycloak.testutils.ldap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.config.CacheConfiguration;
+import net.sf.ehcache.config.Configuration;
+import org.apache.commons.io.FileUtils;
+import org.apache.directory.api.ldap.model.constants.SchemaConstants;
+import org.apache.directory.api.ldap.model.schema.LdapComparator;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.api.ldap.model.schema.comparators.NormalizingComparator;
+import org.apache.directory.api.ldap.model.schema.registries.ComparatorRegistry;
+import org.apache.directory.api.ldap.model.schema.registries.SchemaLoader;
+import org.apache.directory.api.ldap.schemaloader.JarLdifSchemaLoader;
+import org.apache.directory.api.ldap.schemamanager.impl.DefaultSchemaManager;
+import org.apache.directory.api.util.exception.Exceptions;
+import org.apache.directory.server.constants.ServerDNConstants;
+import org.apache.directory.server.core.DefaultDirectoryService;
+import org.apache.directory.server.core.api.CacheService;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.InstanceLayout;
+import org.apache.directory.server.core.api.partition.Partition;
+import org.apache.directory.server.core.api.schema.SchemaPartition;
+import org.apache.directory.server.core.factory.AvlPartitionFactory;
+import org.apache.directory.server.core.factory.DirectoryServiceFactory;
+import org.apache.directory.server.core.factory.PartitionFactory;
+import org.apache.directory.server.i18n.I18n;
+import org.jboss.logging.Logger;
+
+/**
+ * Factory for a fast (mostly in-memory-only) ApacheDS DirectoryService. Use only for tests!!
+ *
+ * @author Josef Cacek
+ */
+class InMemoryDirectoryServiceFactory implements DirectoryServiceFactory {
+
+    private static final Logger log = Logger.getLogger(InMemoryDirectoryServiceFactory.class);
+
+    private final DirectoryService directoryService;
+    private final PartitionFactory partitionFactory;
+
+    /**
+     * Default constructor which creates {@link DefaultDirectoryService} instance and configures {@link AvlPartitionFactory} as
+     * the {@link PartitionFactory} implementation.
+     */
+    public InMemoryDirectoryServiceFactory() {
+        try {
+            directoryService = new DefaultDirectoryService();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        directoryService.setShutdownHookEnabled(false);
+        partitionFactory = new AvlPartitionFactory();
+    }
+
+    /**
+     * Constructor which uses provided {@link DirectoryService} and {@link PartitionFactory} implementations.
+     *
+     * @param directoryService must be not-<code>null</code>
+     * @param partitionFactory must be not-<code>null</code>
+     */
+    public InMemoryDirectoryServiceFactory(DirectoryService directoryService, PartitionFactory partitionFactory) {
+        this.directoryService = directoryService;
+        this.partitionFactory = partitionFactory;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void init(String name) throws Exception {
+        if ((directoryService != null) && directoryService.isStarted()) {
+            return;
+        }
+        directoryService.setInstanceId(name);
+
+        // instance layout
+        InstanceLayout instanceLayout = new InstanceLayout(System.getProperty("java.io.tmpdir") + "/server-work-" + name);
+        if (instanceLayout.getInstanceDirectory().exists()) {
+            try {
+                FileUtils.deleteDirectory(instanceLayout.getInstanceDirectory());
+            } catch (IOException e) {
+                log.warn("couldn't delete the instance directory before initializing the DirectoryService", e);
+            }
+        }
+        directoryService.setInstanceLayout(instanceLayout);
+
+        // EhCache in disabled-like-mode
+        Configuration ehCacheConfig = new Configuration();
+        CacheConfiguration defaultCache = new CacheConfiguration("default", 1).eternal(false).timeToIdleSeconds(30)
+                .timeToLiveSeconds(30).overflowToDisk(false);
+        ehCacheConfig.addDefaultCache(defaultCache);
+        CacheService cacheService = new CacheService(new CacheManager(ehCacheConfig));
+        directoryService.setCacheService(cacheService);
+
+        // Init the schema
+        // SchemaLoader loader = new SingleLdifSchemaLoader();
+        SchemaLoader loader = new JarLdifSchemaLoader();
+        SchemaManager schemaManager = new DefaultSchemaManager(loader);
+        schemaManager.loadAllEnabled();
+        ComparatorRegistry comparatorRegistry = schemaManager.getComparatorRegistry();
+        for (LdapComparator<?> comparator : comparatorRegistry) {
+            if (comparator instanceof NormalizingComparator) {
+                ((NormalizingComparator) comparator).setOnServer();
+            }
+        }
+        directoryService.setSchemaManager(schemaManager);
+        InMemorySchemaPartition inMemorySchemaPartition = new InMemorySchemaPartition(schemaManager);
+        SchemaPartition schemaPartition = new SchemaPartition(schemaManager);
+        schemaPartition.setWrappedPartition(inMemorySchemaPartition);
+        directoryService.setSchemaPartition(schemaPartition);
+        List<Throwable> errors = schemaManager.getErrors();
+        if (errors.size() != 0) {
+            throw new Exception(I18n.err(I18n.ERR_317, Exceptions.printErrors(errors)));
+        }
+
+        // Init system partition
+        Partition systemPartition = partitionFactory.createPartition(directoryService.getSchemaManager(),
+                directoryService.getDnFactory(), "system",
+                ServerDNConstants.SYSTEM_DN, 500,
+                new File(directoryService.getInstanceLayout().getPartitionsDirectory(),
+                        "system"));
+
+        systemPartition.setSchemaManager(directoryService.getSchemaManager());
+        partitionFactory.addIndex(systemPartition, SchemaConstants.OBJECT_CLASS_AT, 100);
+        directoryService.setSystemPartition(systemPartition);
+        directoryService.startup();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public DirectoryService getDirectoryService() throws Exception {
+        return directoryService;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PartitionFactory getPartitionFactory() throws Exception {
+        return partitionFactory;
+    }
+}
\ No newline at end of file
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemorySchemaPartition.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemorySchemaPartition.java
new file mode 100644
index 0000000..a5fed44
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemorySchemaPartition.java
@@ -0,0 +1,75 @@
+package org.keycloak.testutils.ldap;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.regex.Pattern;
+import javax.naming.InvalidNameException;
+import org.apache.directory.api.ldap.model.constants.SchemaConstants;
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.ldif.LdifEntry;
+import org.apache.directory.api.ldap.model.ldif.LdifReader;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.api.ldap.schemaextractor.impl.DefaultSchemaLdifExtractor;
+import org.apache.directory.api.ldap.schemaextractor.impl.ResourceMap;
+import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
+import org.apache.directory.server.core.partition.ldif.AbstractLdifPartition;
+import org.jboss.logging.Logger;
+
+/**
+ * In-memory schema-only partition which loads the data in the similar way as the
+ * {@link org.apache.directory.api.ldap.schemaloader.JarLdifSchemaLoader}.
+ *
+ * @author Josef Cacek
+ */
+class InMemorySchemaPartition extends AbstractLdifPartition {
+
+    private static final Logger log = Logger.getLogger(InMemorySchemaPartition.class);
+
+    /**
+     * Filesystem path separator pattern, either forward slash or backslash. java.util.regex.Pattern is immutable so only one
+     * instance is needed for all uses.
+     */
+    public InMemorySchemaPartition(SchemaManager schemaManager) {
+        super(schemaManager);
+    }
+
+    /**
+     * Partition initialization - loads schema entries from the files on classpath.
+     *
+     * @see org.apache.directory.server.core.partition.impl.avl.AvlPartition#doInit()
+     */
+    @Override
+    protected void doInit() throws InvalidNameException, Exception {
+        if (initialized)
+            return;
+        log.debug("Initializing schema partition " + getId());
+        suffixDn.apply(schemaManager);
+        super.doInit();
+
+        // load schema
+        final Map<String, Boolean> resMap = ResourceMap.getResources(Pattern.compile("schema[/\\Q\\\\E]ou=schema.*"));
+        for (String resourcePath : new TreeSet<String>(resMap.keySet())) {
+            if (resourcePath.endsWith(".ldif")) {
+                URL resource = DefaultSchemaLdifExtractor.getUniqueResource(resourcePath, "Schema LDIF file");
+                LdifReader reader = new LdifReader(resource.openStream());
+                LdifEntry ldifEntry = reader.next();
+                reader.close();
+                Entry entry = new DefaultEntry(schemaManager, ldifEntry.getEntry());
+
+                // add mandatory attributes
+                if (entry.get(SchemaConstants.ENTRY_CSN_AT) == null) {
+                    entry.add(SchemaConstants.ENTRY_CSN_AT, defaultCSNFactory.newInstance().toString());
+                }
+                if (entry.get(SchemaConstants.ENTRY_UUID_AT) == null) {
+                    entry.add(SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString());
+                }
+                AddOperationContext addContext = new AddOperationContext(null, entry);
+                super.add(addContext);
+            }
+        }
+    }
+
+}
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java
new file mode 100644
index 0000000..635c817
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java
@@ -0,0 +1,183 @@
+package org.keycloak.testutils.ldap;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
+import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor;
+import org.apache.directory.server.kerberos.KerberosConfig;
+import org.apache.directory.server.kerberos.kdc.KdcServer;
+import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
+import org.apache.directory.server.ldap.LdapServer;
+import org.apache.directory.server.ldap.handlers.sasl.cramMD5.CramMd5MechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.digestMD5.DigestMd5MechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.gssapi.GssapiMechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmMechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.plain.PlainMechanismHandler;
+import org.apache.directory.server.protocol.shared.transport.UdpTransport;
+import org.apache.directory.shared.kerberos.KerberosTime;
+import org.jboss.logging.Logger;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KerberosEmbeddedServer extends LDAPEmbeddedServer {
+
+    private static final Logger log = Logger.getLogger(LDAPEmbeddedServer.class);
+
+    private final String kerberosRealm;
+    private final int kdcPort;
+
+    private KdcServer kdcServer;
+
+
+    public static void main(String[] args) throws Exception {
+        EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
+        KerberosEmbeddedServer kerberosEmbeddedServer = factory.createKerberosServer();
+        kerberosEmbeddedServer.init();
+        kerberosEmbeddedServer.start();
+    }
+
+
+    protected KerberosEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile, String kerberosRealm, int kdcPort) {
+        super(baseDN, bindHost, bindPort, ldifFile);
+        this.kerberosRealm = kerberosRealm;
+        this.kdcPort = kdcPort;
+    }
+
+
+    @Override
+    public void init() throws Exception {
+        super.init();
+
+        log.info("Creating KDC server");
+        createAndStartKdcServer();
+    }
+
+
+    @Override
+    protected DirectoryService createDirectoryService() throws Exception {
+        DirectoryService directoryService = super.createDirectoryService();
+
+        directoryService.addLast(new KeyDerivationInterceptor());
+        return directoryService;
+    }
+
+
+    @Override
+    protected LdapServer createLdapServer() {
+        LdapServer ldapServer = super.createLdapServer();
+
+        ldapServer.setSaslHost( this.bindHost );
+        ldapServer.setSaslPrincipal( "ldap/" + this.bindHost + "@" + this.kerberosRealm);
+
+        ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.PLAIN, new PlainMechanismHandler());
+        ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.CRAM_MD5, new CramMd5MechanismHandler());
+        ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.DIGEST_MD5, new DigestMd5MechanismHandler());
+        ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.GSSAPI, new GssapiMechanismHandler());
+        ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.NTLM, new NtlmMechanismHandler());
+        ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.GSS_SPNEGO, new NtlmMechanismHandler());
+
+        return ldapServer;
+    }
+
+
+    protected KdcServer createAndStartKdcServer() throws Exception {
+        KerberosConfig kdcConfig = new KerberosConfig();
+        kdcConfig.setServicePrincipal("krbtgt/" + this.kerberosRealm + "@" + this.kerberosRealm);
+        kdcConfig.setPrimaryRealm(this.kerberosRealm);
+        kdcConfig.setMaximumTicketLifetime(60000 * 1440);
+        kdcConfig.setMaximumRenewableLifetime(60000 * 10080);
+        kdcConfig.setPaEncTimestampRequired(false);
+
+        kdcServer = new NoReplayKdcServer(kdcConfig);
+        kdcServer.setSearchBaseDn(this.baseDN);
+
+        UdpTransport udp = new UdpTransport(this.bindHost, this.kdcPort);
+        kdcServer.addTransports(udp);
+
+        kdcServer.setDirectoryService(directoryService);
+
+        // Launch the server
+        kdcServer.start();
+
+        return kdcServer;
+    }
+
+
+    public void stop() throws Exception {
+        stopLdapServer();
+        stopKerberosServer();
+        shutdownDirectoryService();
+    }
+
+
+    protected void stopKerberosServer() {
+        log.info("Stoping Kerberos server.");
+        kdcServer.stop();
+    }
+
+
+    /**
+     * Replacement of apacheDS KdcServer class with disabled ticket replay cache.
+     *
+     * @author Dominik Pospisil <dpospisi@redhat.com>
+     */
+    class NoReplayKdcServer extends KdcServer {
+
+        NoReplayKdcServer(KerberosConfig kdcConfig) {
+            super(kdcConfig);
+        }
+
+        /**
+         *
+         * Dummy implementation of the ApacheDS kerberos replay cache. Essentially disables kerbores ticket replay checks.
+         * https://issues.jboss.org/browse/JBPAPP-10974
+         *
+         * @author Dominik Pospisil <dpospisi@redhat.com>
+         */
+        private class DummyReplayCache implements ReplayCache {
+
+            @Override
+            public boolean isReplay(KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal, KerberosTime clientTime,
+                                    int clientMicroSeconds) {
+                return false;
+            }
+
+            @Override
+            public void save(KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal, KerberosTime clientTime,
+                             int clientMicroSeconds) {
+                return;
+            }
+
+            @Override
+            public void clear() {
+                return;
+            }
+
+        }
+
+        /**
+         * @throws java.io.IOException if we cannot bind to the sockets
+         */
+        @Override
+        public void start() throws IOException, LdapInvalidDnException {
+            super.start();
+
+            try {
+
+                // override initialized replay cache with a dummy implementation
+                Field replayCacheField = KdcServer.class.getDeclaredField("replayCache");
+                replayCacheField.setAccessible(true);
+                replayCacheField.set(this, new DummyReplayCache());
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+
+        }
+    }
+}
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosKeytabCreator.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosKeytabCreator.java
new file mode 100644
index 0000000..aa26153
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosKeytabCreator.java
@@ -0,0 +1,70 @@
+package org.keycloak.testutils.ldap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
+import org.apache.directory.server.kerberos.shared.keytab.Keytab;
+import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
+import org.apache.directory.shared.kerberos.KerberosTime;
+import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
+import org.apache.directory.shared.kerberos.components.EncryptionKey;
+
+/**
+ * Helper utility for creating Keytab files.
+ *
+ * @author Josef Cacek
+ */
+public class KerberosKeytabCreator {
+
+    // Public methods --------------------------------------------------------
+
+    /**
+     * The main.
+     *
+     * @param args
+     * @throws java.io.IOException
+     */
+    public static void main(String[] args) throws IOException {
+        if (args == null || args.length != 3) {
+            System.out.println("Kerberos keytab generator");
+            System.out.println("-------------------------");
+            System.out.println("Arguments missing or invalid. Required arguments are: <principalName> <passPhrase> <outputKeytabFile>");
+            System.out.println("Example of usage:");
+            System.out.println("mvn exec:java -Dexec.mainClass=\"org.keycloak.testutils.ldap.KerberosKeytabCreator\" -Dexec.args=\"HTTP/localhost@KEYCLOAK.ORG httppwd src/main/resources/kerberos/http.keytab\"");
+        } else {
+            final File keytabFile = new File(args[2]);
+            createKeytab(args[0], args[1], keytabFile);
+            System.out.println("Keytab file was created: " + keytabFile.getAbsolutePath() + ", principal: " + args[0] + ", passphrase: " + args[1]);
+        }
+    }
+
+    /**
+     * Creates a keytab file for given principal.
+     *
+     * @param principalName
+     * @param passPhrase
+     * @param keytabFile
+     * @throws IOException
+     */
+    public static void createKeytab(final String principalName, final String passPhrase, final File keytabFile)
+            throws IOException {
+        final KerberosTime timeStamp = new KerberosTime();
+        final int principalType = 1; // KRB5_NT_PRINCIPAL
+
+        final Keytab keytab = Keytab.getInstance();
+        final List<KeytabEntry> entries = new ArrayList<KeytabEntry>();
+        for (Map.Entry<EncryptionType, EncryptionKey> keyEntry : KerberosKeyFactory.getKerberosKeys(principalName, passPhrase)
+                .entrySet()) {
+            System.out.println("Adding keytab entry of type: " + keyEntry.getKey().getName());
+            final EncryptionKey key = keyEntry.getValue();
+            final byte keyVersion = (byte) key.getKeyVersion();
+            entries.add(new KeytabEntry(principalName, principalType, timeStamp, keyVersion, key));
+        }
+        keytab.setEntries(entries);
+        keytab.write(keytabFile);
+    }
+}
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java
new file mode 100644
index 0000000..9c18288
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java
@@ -0,0 +1,186 @@
+package org.keycloak.testutils.ldap;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException;
+import org.apache.directory.api.ldap.model.ldif.LdifEntry;
+import org.apache.directory.api.ldap.model.ldif.LdifReader;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.partition.Partition;
+import org.apache.directory.server.core.factory.DSAnnotationProcessor;
+import org.apache.directory.server.core.factory.PartitionFactory;
+import org.apache.directory.server.ldap.LdapServer;
+import org.apache.directory.server.protocol.shared.transport.TcpTransport;
+import org.apache.directory.server.protocol.shared.transport.Transport;
+import org.jboss.logging.Logger;
+import org.keycloak.util.StreamUtil;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPEmbeddedServer {
+
+    private static final Logger log = Logger.getLogger(LDAPEmbeddedServer.class);
+
+    protected final String baseDN;
+    protected final String bindHost;
+    protected final int bindPort;
+    protected final String ldifFile;
+
+    protected DirectoryService directoryService;
+    protected LdapServer ldapServer;
+
+
+    public static void main(String[] args) throws Exception {
+        EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
+        LDAPEmbeddedServer ldapEmbeddedServer = factory.createLdapServer();
+        ldapEmbeddedServer.init();
+        ldapEmbeddedServer.start();
+    }
+
+    public LDAPEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile) {
+        this.baseDN = baseDN;
+        this.bindHost = bindHost;
+        this.bindPort = bindPort;
+        this.ldifFile = ldifFile;
+    }
+
+
+    public void init() throws Exception {
+        log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort);
+        this.directoryService = createDirectoryService();
+
+        log.info("Importing LDIF: " + ldifFile);
+        importLdif();
+
+        log.info("Creating LDAP Server");
+        this.ldapServer = createLdapServer();
+    }
+
+
+    public void start() throws Exception {
+        log.info("Starting LDAP Server");
+        ldapServer.start();
+        log.info("LDAP Server started");
+    }
+
+
+    protected DirectoryService createDirectoryService() throws Exception {
+        // Parse "keycloak" from "dc=keycloak,dc=org"
+        String dcName = baseDN.split(",")[0].substring(3);
+
+        InMemoryDirectoryServiceFactory dsf = new InMemoryDirectoryServiceFactory();
+
+        DirectoryService service = dsf.getDirectoryService();
+        service.setAccessControlEnabled(false);
+        service.setAllowAnonymousAccess(false);
+        service.getChangeLog().setEnabled(false);
+
+        dsf.init(dcName + "DS");
+
+        SchemaManager schemaManager = service.getSchemaManager();
+
+        PartitionFactory partitionFactory = dsf.getPartitionFactory();
+        Partition partition = partitionFactory.createPartition(
+                schemaManager,
+                service.getDnFactory(),
+                dcName,
+                this.baseDN,
+                1000,
+                new File(service.getInstanceLayout().getPartitionsDirectory(), dcName));
+        partition.setCacheService( service.getCacheService() );
+        partition.initialize();
+
+        partition.setSchemaManager( schemaManager );
+
+        // Inject the partition into the DirectoryService
+        service.addPartition( partition );
+
+        // Last, process the context entry
+        String entryLdif =
+                "dn: " + baseDN + "\n" +
+                        "dc: " + dcName + "\n" +
+                        "objectClass: top\n" +
+                        "objectClass: domain\n\n";
+        DSAnnotationProcessor.injectEntries(service, entryLdif);
+
+        return service;
+    }
+
+
+    protected LdapServer createLdapServer() {
+        LdapServer ldapServer = new LdapServer();
+
+        ldapServer.setServiceName("DefaultLdapServer");
+
+        // Read the transports
+        Transport ldap = new TcpTransport(this.bindHost, this.bindPort, 3, 50);
+        ldapServer.addTransports( ldap );
+
+        // Associate the DS to this LdapServer
+        ldapServer.setDirectoryService( directoryService );
+
+        // Propagate the anonymous flag to the DS
+        directoryService.setAllowAnonymousAccess(false);
+
+        ldapServer.setSaslHost( this.bindHost );
+        ldapServer.setSaslPrincipal( "ldap/" + this.bindHost + "@KEYCLOAK.ORG");
+        ldapServer.setSaslRealms(new ArrayList<String>());
+        return ldapServer;
+    }
+
+
+    private void importLdif() throws Exception {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("hostname", this.bindHost);
+
+        // For now, assume that LDIF file is on classpath
+        InputStream is = getClass().getClassLoader().getResourceAsStream(ldifFile);
+        if (is == null) {
+            throw new IllegalStateException("LDIF file not found on classpath. Location was: " + ldifFile);
+        }
+
+        final String ldifContent = StrSubstitutor.replace(StreamUtil.readString(is), map);
+        log.info("Importing LDIF: " + ldifContent);
+        final SchemaManager schemaManager = directoryService.getSchemaManager();
+
+        for (LdifEntry ldifEntry : new LdifReader(IOUtils.toInputStream(ldifContent))) {
+            try {
+                directoryService.getAdminSession().add(new DefaultEntry(schemaManager, ldifEntry.getEntry()));
+            } catch (LdapEntryAlreadyExistsException ignore) {
+                log.debug("Entry " + ldifEntry.getNewRdn() + " already exists. Ignoring");
+            }
+        }
+    }
+
+
+    public void stop() throws Exception {
+        stopLdapServer();
+        shutdownDirectoryService();
+    }
+
+
+    protected void stopLdapServer() {
+        log.info("Stoping LDAP server.");
+        ldapServer.stop();
+    }
+
+
+    protected void shutdownDirectoryService() throws Exception {
+        log.info("Stoping Directory service.");
+        directoryService.shutdown();
+
+        log.info("Removing Directory service workfiles.");
+        FileUtils.deleteDirectory(directoryService.getInstanceLayout().getInstanceDirectory());
+    }
+
+}
diff --git a/testsuite/integration/src/main/resources/kerberos/http.keytab b/testsuite/integration/src/main/resources/kerberos/http.keytab
new file mode 100644
index 0000000..c156500
Binary files /dev/null and b/testsuite/integration/src/main/resources/kerberos/http.keytab differ
diff --git a/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif b/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif
new file mode 100644
index 0000000..fcde10e
--- /dev/null
+++ b/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif
@@ -0,0 +1,88 @@
+dn: dc=keycloak,dc=org
+objectclass: dcObject
+objectclass: organization
+o: Keycloak
+dc: Keycloak
+
+dn: ou=People,dc=keycloak,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: People
+
+dn: uid=krbtgt,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: KDC Service
+sn: Service
+uid: krbtgt
+userPassword: secret
+krb5PrincipalName: krbtgt/KEYCLOAK.ORG@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=ldap,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: LDAP
+sn: Service
+uid: ldap
+userPassword: randall
+krb5PrincipalName: ldap/${hostname}@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=HTTP,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: HTTP
+sn: Service
+uid: HTTP
+userPassword: httppwd
+krb5PrincipalName: HTTP/${hostname}@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=hnelson,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: Horatio Nelson
+sn: Nelson
+uid: hnelson
+userPassword: secret
+krb5PrincipalName: hnelson@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=jduke,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: Java Duke
+sn: duke
+uid: jduke
+userPassword: theduke
+krb5PrincipalName: jduke@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=gsstestserver,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: gsstestserver
+sn: Service
+uid: gsstestserver
+userPassword: gsstestpwd
+krb5PrincipalName: gsstestserver/xxx@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
diff --git a/testsuite/integration/src/main/resources/log4j.properties b/testsuite/integration/src/main/resources/log4j.properties
index 573c238..5e78b31 100755
--- a/testsuite/integration/src/main/resources/log4j.properties
+++ b/testsuite/integration/src/main/resources/log4j.properties
@@ -19,6 +19,11 @@ log4j.logger.org.keycloak=info
 # Enable to view kerberos/spnego logging
 # log4j.logger.org.keycloak.broker.kerberos=trace
 
+# Enable to view detailed AS REQ and TGS REQ requests to embedded Kerberos server
+log4j.logger.org.apache.directory.server.kerberos=debug
+
 log4j.logger.org.xnio=off
 log4j.logger.org.hibernate=off
-log4j.logger.org.jboss.resteasy=warn
\ No newline at end of file
+log4j.logger.org.jboss.resteasy=warn
+log4j.logger.org.apache.directory.api=warn
+log4j.logger.org.apache.directory.server.core=warn
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
index 1146257..833e56c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
@@ -33,7 +33,6 @@ import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.LDAPRule;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
-import org.keycloak.testutils.LDAPEmbeddedServer;
 import org.openqa.selenium.WebDriver;
 import org.picketlink.idm.PartitionManager;
 import org.picketlink.idm.model.basic.User;
@@ -56,8 +55,7 @@ public class FederationProvidersIntegrationTest {
         public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
             addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
 
-            LDAPEmbeddedServer ldapServer = ldapRule.getEmbeddedServer();
-            Map<String,String> ldapConfig = ldapServer.getLDAPConfig();
+            Map<String,String> ldapConfig = ldapRule.getLdapConfig();
             ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true");
             ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
index f256ee0..917a544 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
@@ -22,7 +22,6 @@ import org.keycloak.services.managers.UsersSyncManager;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.LDAPRule;
 import org.keycloak.testutils.DummyUserFederationProviderFactory;
-import org.keycloak.testutils.LDAPEmbeddedServer;
 import org.keycloak.timer.TimerProvider;
 import org.keycloak.util.Time;
 import org.picketlink.idm.PartitionManager;
@@ -49,8 +48,7 @@ public class SyncProvidersTest {
             // Other tests may left Time offset uncleared, which could cause issues
             Time.setOffset(0);
 
-            LDAPEmbeddedServer ldapServer = ldapRule.getEmbeddedServer();
-            Map<String,String> ldapConfig = ldapServer.getLDAPConfig();
+            Map<String,String> ldapConfig = ldapRule.getLdapConfig();
             ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "false");
             ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java
index 1c631bb..5b12037 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java
@@ -1,37 +1,46 @@
 package org.keycloak.testsuite.rule;
 
+import java.util.Map;
+
 import org.junit.rules.ExternalResource;
-import org.keycloak.testutils.LDAPEmbeddedServer;
+import org.keycloak.testutils.ldap.EmbeddedServersFactory;
+import org.keycloak.testutils.ldap.LDAPConfiguration;
+import org.keycloak.testutils.ldap.LDAPEmbeddedServer;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class LDAPRule extends ExternalResource {
 
-    private LDAPEmbeddedServer embeddedServer;
+    private LDAPConfiguration ldapConfiguration;
+    private LDAPEmbeddedServer ldapEmbeddedServer;
 
     @Override
     protected void before() throws Throwable {
-        try {
-            embeddedServer = new LDAPEmbeddedServer();
-            embeddedServer.setup();
-            embeddedServer.importLDIF("ldap/users.ldif");
-        } catch (Exception e) {
-            throw new RuntimeException("Error starting Embedded LDAP server.", e);
+        ldapConfiguration = LDAPConfiguration.readConfiguration();
+
+        if (ldapConfiguration.isStartEmbeddedLdapLerver()) {
+            EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
+            ldapEmbeddedServer = factory.createLdapServer();
+            ldapEmbeddedServer.init();
+            ldapEmbeddedServer.start();
         }
     }
 
     @Override
     protected void after() {
         try {
-            embeddedServer.tearDown();
-            embeddedServer = null;
+            if (ldapEmbeddedServer != null) {
+                ldapEmbeddedServer.stop();
+                ldapEmbeddedServer = null;
+                ldapConfiguration = null;
+            }
         } catch (Exception e) {
             throw new RuntimeException("Error tearDown Embedded LDAP server.", e);
         }
     }
 
-    public LDAPEmbeddedServer getEmbeddedServer() {
-        return embeddedServer;
+    public Map<String, String> getLdapConfig() {
+        return ldapConfiguration.getLDAPConfig();
     }
 }