keycloak-aplcache

Changes

examples/pom.xml 1(+1 -0)

pom.xml 11(+7 -4)

testsuite/integration/src/test/java/org/keycloak/testsuite/ldap/EmbeddedServersFactory.java 110(+0 -110)

testsuite/integration/src/test/java/org/keycloak/testsuite/ldap/LDAPEmbeddedServer.java 196(+0 -196)

util/pom.xml 22(+22 -0)

Details

diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
index e66ad65..06c1bdc 100755
--- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
+++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
@@ -70,55 +70,56 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
 
                     Connection connection = null;
 
-                    String unitName = config.get("unitName");
                     String databaseSchema = config.get("databaseSchema");
 
                     Map<String, Object> properties = new HashMap<String, Object>();
 
-                    // Only load config from keycloak-server.json if unitName is not specified
-                    if (unitName == null) {
-                        unitName = "keycloak-default";
+                    String unitName = "keycloak-default";
 
-                        String dataSource = config.get("dataSource");
-                        if (dataSource != null) {
-                            if (config.getBoolean("jta", false)) {
-                                properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
-                            } else {
-                                properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
-                            }
+                    String dataSource = config.get("dataSource");
+                    if (dataSource != null) {
+                        if (config.getBoolean("jta", false)) {
+                            properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
                         } else {
-                            properties.put(AvailableSettings.JDBC_URL, config.get("url"));
-                            properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
-
-                            String user = config.get("user");
-                            if (user != null) {
-                                properties.put(AvailableSettings.JDBC_USER, user);
-                            }
-                            String password = config.get("password");
-                            if (password != null) {
-                                properties.put(AvailableSettings.JDBC_PASSWORD, password);
-                            }
+                            properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
                         }
+                    } else {
+                        properties.put(AvailableSettings.JDBC_URL, config.get("url"));
+                        properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
 
-                        String driverDialect = config.get("driverDialect");
-                        if (driverDialect != null && driverDialect.length() > 0) {
-                            properties.put("hibernate.dialect", driverDialect);
+                        String user = config.get("user");
+                        if (user != null) {
+                            properties.put(AvailableSettings.JDBC_USER, user);
                         }
-
-                        if (databaseSchema != null) {
-                            if (databaseSchema.equals("development-update")) {
-                                properties.put("hibernate.hbm2ddl.auto", "update");
-                                databaseSchema = null;
-                            } else if (databaseSchema.equals("development-validate")) {
-                                properties.put("hibernate.hbm2ddl.auto", "validate");
-                                databaseSchema = null;
-                            }
+                        String password = config.get("password");
+                        if (password != null) {
+                            properties.put(AvailableSettings.JDBC_PASSWORD, password);
                         }
+                    }
 
-                        properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
-                        properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
+                    String driverDialect = config.get("driverDialect");
+                    if (driverDialect != null && driverDialect.length() > 0) {
+                        properties.put("hibernate.dialect", driverDialect);
                     }
 
+                    String schema = config.get("schema");
+                    if (schema != null) {
+                        properties.put("hibernate.default_schema", schema);
+                    }
+
+                    if (databaseSchema != null) {
+                        if (databaseSchema.equals("development-update")) {
+                            properties.put("hibernate.hbm2ddl.auto", "update");
+                            databaseSchema = null;
+                        } else if (databaseSchema.equals("development-validate")) {
+                            properties.put("hibernate.hbm2ddl.auto", "validate");
+                            databaseSchema = null;
+                        }
+                    }
+
+                    properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
+                    properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
+
                     if (databaseSchema != null) {
                         logger.trace("Updating database");
 
@@ -140,12 +141,12 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
                             }
 
                             if (currentVersion == null || !JpaUpdaterProvider.LAST_VERSION.equals(currentVersion)) {
-                                updater.update(session, connection);
+                                updater.update(session, connection, schema);
                             } else {
                                 logger.debug("Database is up to date");
                             }
                         } else if (databaseSchema.equals("validate")) {
-                            updater.validate(connection);
+                            updater.validate(connection, schema);
                         } else {
                             throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
                         }
diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
index 809f2f3..4c67ada 100755
--- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
+++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
@@ -16,8 +16,8 @@ public interface JpaUpdaterProvider extends Provider {
 
     public String getCurrentVersionSql();
 
-    public void update(KeycloakSession session, Connection connection);
+    public void update(KeycloakSession session, Connection connection, String defaultSchema);
 
-    public void validate(Connection connection);
+    public void validate(Connection connection, String defaultSchema);
 
 }
diff --git a/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
index a611c3c..65e09da 100644
--- a/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
+++ b/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
@@ -51,14 +51,14 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
     }
 
     @Override
-    public void update(KeycloakSession session, Connection connection) {
+    public void update(KeycloakSession session, Connection connection, String defaultSchema) {
         logger.debug("Starting database update");
 
         // Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks
         ThreadLocalSessionContext.setCurrentSession(session);
 
         try {
-            Liquibase liquibase = getLiquibase(connection);
+            Liquibase liquibase = getLiquibase(connection, defaultSchema);
 
             List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
             if (!changeSets.isEmpty()) {
@@ -93,9 +93,9 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
     }
 
     @Override
-    public void validate(Connection connection) {
+    public void validate(Connection connection, String defaultSchema) {
         try {
-            Liquibase liquibase = getLiquibase(connection);
+            Liquibase liquibase = getLiquibase(connection, defaultSchema);
 
             liquibase.validate();
         } catch (Exception e) {
@@ -103,7 +103,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
         }
     }
 
-    private Liquibase getLiquibase(Connection connection) throws Exception {
+    private Liquibase getLiquibase(Connection connection, String defaultSchema) throws Exception {
         ServiceLocator sl = ServiceLocator.getInstance();
 
         if (!System.getProperties().containsKey("liquibase.scan.packages")) {
@@ -125,6 +125,9 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
 
         LogFactory.setInstance(new LogWrapper());
         Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
+        if (defaultSchema != null) {
+            database.setDefaultSchemaName(defaultSchema);
+        }
         return new Liquibase(CHANGELOG, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
     }
 
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBObjectMapper.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBObjectMapper.java
index e592df9..b93e2b9 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBObjectMapper.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBObjectMapper.java
@@ -89,13 +89,6 @@ public class BasicDBObjectMapper<S> implements Mapper<BasicDBObject, S> {
             Type[] genericTypeArguments = parameterized.getActualTypeArguments();
 
             List<Type> genericTypes = Arrays.asList(genericTypeArguments);
-            /*for (Type genericType : genericTypeArguments) {
-                if (genericType instanceof Class) {
-                    genericTypes.add((Class<?>) genericType);
-                } else {
-                    System.out.println("foo");
-                }
-            }*/
 
             Class<?> expectedReturnType = (Class<?>)parameterized.getRawType();
             context = new MapperContext<Object, Object>(valueFromDB, expectedReturnType, genericTypes);
diff --git a/docbook/reference/en/en-US/modules/server-installation.xml b/docbook/reference/en/en-US/modules/server-installation.xml
index 214d88a..fd75be0 100755
--- a/docbook/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/reference/en/en-US/modules/server-installation.xml
@@ -193,17 +193,14 @@
                         </listitem>
                     </varlistentry>
                     <varlistentry>
-                        <term>unitName</term>
+                        <term>schema</term>
                         <listitem>
                             <para>
-                                Allow you to specify name of persistence unit if you want to provide your own persistence.xml file for JPA configuration.
-                                If this option is used, then all other configuration options are ignored as you are expected to configure
-                                all JPA/DB properties in your own persistence.xml file. Hence you can remove properties "dataSource" and "databaseSchema" in this case.
+                                Specify the default database schema to use
                             </para>
                         </listitem>
                     </varlistentry>
                 </variablelist>
-                For more info about Hibernate properties, see <ulink url="http://hibernate.org/orm/documentation/">Hibernate and JPA documentation</ulink> .
             </para>
             <section>
                 <title>Tested databases</title>
diff --git a/examples/kerberos/README.md b/examples/kerberos/README.md
index 80e1ff8..5993a1f 100644
--- a/examples/kerberos/README.md
+++ b/examples/kerberos/README.md
@@ -19,7 +19,7 @@ cp http.keytab /tmp/http.keytab
 ```
 
 Alternative is to configure different location for `keyTab` property in `kerberosrealm.json` configuration file (On Windows this will be needed).
-Note that in production, keytab file should be in secured location accessible just to the user under which is Keycloak server running.
+WARNING: In production, keytab file should be in secured location accessible just to the user under which is Keycloak server running.
 
 
 **3)** Run Keycloak server and import `kerberosrealm.json` into it through admin console. This will import realm with sample application
@@ -37,12 +37,13 @@ Also if you are on Linux, make sure that record like:
 ```
 is in your `/etc/hosts` before other records for the 127.0.0.1 host to avoid issues related to incompatible reverse lookup (Ensure the similar for other OS as well)
 
+**4)** Install kerberos client. This is platform dependent. If you are on Fedora, Ubuntu or RHEL, you can install package `freeipa-client`, which contains Kerberos client and bunch of other stuff. 
 
-**4)** Configure Kerberos client (On linux it's in file `/etc/krb5.conf` ). You need to configure `KEYCLOAK.ORG` realm and enable `forwardable` flag, which is needed 
+**5)** Configure Kerberos client (On linux it's in file `/etc/krb5.conf` ). You need to configure `KEYCLOAK.ORG` realm for host `localhost` and enable `forwardable` flag, which is needed 
 for credential delegation example, as application needs to forward Kerberos ticket and authenticate with it against LDAP server. 
 See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/src/test/resources/kerberos/test-krb5.conf) for inspiration.
 
-**5)**  Run ApacheDS based Kerberos server embedded in Keycloak. Easiest is to checkout keycloak sources, build and then run KerberosEmbeddedServer 
+**6)**  Run ApacheDS based Kerberos server embedded in Keycloak. Easiest is to checkout keycloak sources, build and then run KerberosEmbeddedServer 
 as shown here: 
 
 ```
@@ -55,12 +56,12 @@ mvn exec:java -Pkerberos
 More details about embedded Kerberos server in [testsuite README](https://github.com/keycloak/keycloak/blob/master/misc/Testsuite.md#kerberos-server).
 
   
-**6)** Configure browser (Firefox, Chrome or other) and enable SPNEGO authentication and credential delegation for `localhost` . 
+**7)** Configure browser (Firefox, Chrome or other) and enable SPNEGO authentication and credential delegation for `localhost` . 
 In Firefox it can be done by adding `localhost` to both `network.negotiate-auth.trusted-uris` and `network.negotiate-auth.delegation-uris` . 
 More info in [testsuite README](https://github.com/keycloak/keycloak/blob/master/misc/Testsuite.md#kerberos-server).  
  
  
-**7)** Test the example. Obtain kerberos ticket by running command from CMD (on linux):
+**8)** Test the example. Obtain kerberos ticket by running command from CMD (on linux):
 ```
 kinit hnelson@KEYCLOAK.ORG
 ```
diff --git a/examples/kerberos/users.ldif b/examples/kerberos/users.ldif
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/kerberos/users.ldif
diff --git a/examples/ldap/embedded-ldap/assembly.xml b/examples/ldap/embedded-ldap/assembly.xml
new file mode 100644
index 0000000..58afeca
--- /dev/null
+++ b/examples/ldap/embedded-ldap/assembly.xml
@@ -0,0 +1,21 @@
+<assembly>
+    <id>embedded-ldap</id>
+
+    <formats>
+        <format>dir</format>
+    </formats>
+
+    <includeBaseDirectory>false</includeBaseDirectory>
+
+    <dependencySets>
+        <dependencySet>
+            <unpack>false</unpack>
+            <useTransitiveDependencies>true</useTransitiveDependencies>
+            <useTransitiveFiltering>true</useTransitiveFiltering>
+            <includes>
+                <include>org.keycloak:keycloak-util-embedded-ldap</include>
+                <include>org.slf4j:slf4j-log4j12</include>
+            </includes>
+        </dependencySet>
+    </dependencySets>
+</assembly>
\ No newline at end of file
diff --git a/examples/ldap/embedded-ldap/pom.xml b/examples/ldap/embedded-ldap/pom.xml
new file mode 100644
index 0000000..b981072
--- /dev/null
+++ b/examples/ldap/embedded-ldap/pom.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>keycloak-examples-ldap-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.4.0.Final-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.keycloak.example.demo</groupId>
+    <artifactId>keycloak-examples-embedded-ldap</artifactId>
+    <packaging>jar</packaging>
+    <name>LDAP Demo Application</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-util-embedded-ldap</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>embedded-ldap</finalName>
+        <plugins>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>assemble</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <descriptors>
+                                <descriptor>assembly.xml</descriptor>
+                            </descriptors>
+                            <outputDirectory>
+                                target
+                            </outputDirectory>
+                            <workDirectory>
+                                target/assembly/work
+                            </workDirectory>
+                            <appendAssemblyId>false</appendAssemblyId>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.keycloak.example.ldap.embedded.EmbeddedLDAPLauncher</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/examples/ldap/embedded-ldap/src/main/java/org/keycloak/example/ldap/embedded/EmbeddedLDAPLauncher.java b/examples/ldap/embedded-ldap/src/main/java/org/keycloak/example/ldap/embedded/EmbeddedLDAPLauncher.java
new file mode 100644
index 0000000..b3941d5
--- /dev/null
+++ b/examples/ldap/embedded-ldap/src/main/java/org/keycloak/example/ldap/embedded/EmbeddedLDAPLauncher.java
@@ -0,0 +1,127 @@
+package org.keycloak.example.ldap.embedded;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This is supposed to be executed from JAR file (java -jar target/embedded-ldap.jar ). For executing from IDE or Maven use directly
+ * the proper class (LDAPEmbeddedServer, KerberosEmbeddedServer or KerberosKeytabCreator)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class EmbeddedLDAPLauncher {
+
+    public static void main(String[] args) throws Exception {
+        String arg = args.length == 0 ? null : args[0];
+        if (arg == null) {
+            System.err.println("Missing argument: either 'kerberos', 'ldap' or 'keytabCreator' must be passed as argument");
+            System.exit(1);
+        }
+
+        String clazz = null;
+        File home = getHome();
+        Properties defaultProperties = new Properties();
+        if (arg.equalsIgnoreCase("ldap")) {
+
+            clazz = "org.keycloak.util.ldap.LDAPEmbeddedServer";
+            File ldapLdif = file(home, "..", "ldap-app", "users.ldif");
+            defaultProperties.put("ldap.ldif", ldapLdif.getAbsolutePath());
+        } else if (arg.equalsIgnoreCase("kerberos")) {
+
+            clazz = "org.keycloak.util.ldap.KerberosEmbeddedServer";
+            File kerberosLdif = file(home, "..", "..", "kerberos", "users.ldif");
+            defaultProperties.put("ldap.ldif", kerberosLdif.getAbsolutePath());
+        } else if (arg.equalsIgnoreCase("keytabCreator")) {
+
+            clazz = "org.keycloak.util.ldap.KerberosKeytabCreator";
+        } else {
+
+            System.err.println("Invalid argument: '" + arg + "' . Either 'kerberos', 'ldap' or 'keytabCreator' must be passed as argument");
+            System.exit(1);
+        }
+
+        // Remove first argument
+        String[] newArgs = new String[args.length - 1];
+        for (int i=0 ; i<(args.length - 1) ; i++) {
+            newArgs[i] = args[i + 1];
+        }
+
+        System.out.println("Executing " + clazz);
+        runClass(clazz, newArgs, defaultProperties);
+    }
+
+
+    private static void runClass(String className, String[] args, Properties defaultProperties) throws Exception {
+        File home = getHome();
+        File lib = file(home, "target", "embedded-ldap");
+
+        if (!lib.exists()) {
+            System.err.println("Could not find lib directory: " + lib.toString());
+            System.exit(1);
+        } else {
+            System.out.println("Found directory to load jars: " + lib.getAbsolutePath());
+        }
+
+        List<URL> jars = new ArrayList<URL>();
+        for (File file : lib.listFiles()) {
+            jars.add(file.toURI().toURL());
+        }
+        URL[] urls = jars.toArray(new URL[jars.size()]);
+        URLClassLoader loader = new URLClassLoader(urls, EmbeddedLDAPLauncher.class.getClassLoader());
+
+        Class mainClass = loader.loadClass(className);
+        Method executeMethod = null;
+        for (Method m : mainClass.getMethods()) if (m.getName().equals("execute")) { executeMethod = m; break; }
+        Object obj = args;
+        executeMethod.invoke(null, obj, defaultProperties);
+    }
+
+
+    private static File getHome() {
+        String launcherPath = EmbeddedLDAPLauncher.class.getName().replace('.', '/') + ".class";
+        URL jarfile = EmbeddedLDAPLauncher.class.getClassLoader().getResource(launcherPath);
+        if (jarfile != null) {
+            Matcher m = Pattern.compile("jar:(file:.*)!/" + launcherPath).matcher(jarfile.toString());
+            if (m.matches()) {
+                try {
+                    File jarPath = new File(new URI(m.group(1)));
+                    File libPath = jarPath.getParentFile().getParentFile();
+                    System.out.println("Home directory: " + libPath.toString());
+                    if (!libPath.exists()) {
+                        System.exit(1);
+
+                    }
+                    return libPath;
+                } catch (URISyntaxException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        } else {
+            System.err.println("jar file null: " + launcherPath);
+        }
+        return null;
+    }
+
+    private static File file(File home, String... pathItems) {
+        File current = home;
+
+        for (String item : pathItems) {
+            if (item.equals("..")) {
+                current = current.getParentFile();
+            } else {
+                current = new File(current, item);
+            }
+        }
+        return current;
+    }
+}
diff --git a/examples/ldap/ldap-app/pom.xml b/examples/ldap/ldap-app/pom.xml
new file mode 100644
index 0000000..d080ba6
--- /dev/null
+++ b/examples/ldap/ldap-app/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>keycloak-examples-ldap-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.4.0.Final-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.keycloak.example.demo</groupId>
+    <artifactId>keycloak-examples-ldap-app</artifactId>
+    <packaging>war</packaging>
+    <name>LDAP Demo Application</name>
+
+    <repositories>
+        <repository>
+            <id>jboss</id>
+            <name>jboss repo</name>
+            <url>http://repository.jboss.org/nexus/content/groups/public/</url>
+        </repository>
+    </repositories>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>ldap-portal</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.jboss.as.plugins</groupId>
+                <artifactId>jboss-as-maven-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.wildfly.plugins</groupId>
+                <artifactId>wildfly-maven-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/examples/ldap/ldap-app/users.ldif b/examples/ldap/ldap-app/users.ldif
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/ldap/ldap-app/users.ldif
diff --git a/examples/ldap/pom.xml b/examples/ldap/pom.xml
new file mode 100644
index 0000000..d506efc
--- /dev/null
+++ b/examples/ldap/pom.xml
@@ -0,0 +1,20 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-examples-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.4.0.Final-SNAPSHOT</version>
+    </parent>
+    <name>Keycloak LDAP Examples - Parent</name>
+    <description/>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-examples-ldap-parent</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>embedded-ldap</module>
+        <module>ldap-app</module>
+    </modules>
+
+</project>
\ No newline at end of file

examples/pom.xml 1(+1 -0)

diff --git a/examples/pom.xml b/examples/pom.xml
index b2f7f2a..4815c39 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -44,5 +44,6 @@
         <module>kerberos</module>
         <module>themes</module>
         <module>saml</module>
+        <module>ldap</module>
     </modules>
 </project>
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
index 67e92c7..141ae38 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
@@ -3,6 +3,7 @@ package org.keycloak.federation.ldap.idm.store.ldap;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -437,18 +438,26 @@ public class LDAPIdentityStore implements IdentityStore {
 
             // ldapObject.getReadOnlyAttributeNames() are lower-cased
             if (!ldapObject.getReadOnlyAttributeNames().contains(attrName.toLowerCase()) && (isCreate || !ldapObject.getRdnAttributeName().equalsIgnoreCase(attrName))) {
-                BasicAttribute attr = new BasicAttribute(attrName);
+
                 if (attrValue == null) {
-                    // Adding empty value as we don't know if attribute is mandatory in LDAP
-                    attr.add(LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
-                } else {
-                    for (String val : attrValue) {
-                        if (val == null || val.toString().trim().length() == 0) {
-                            val = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
-                        }
-                        attr.add(val);
+                    // Shouldn't happen
+                    logger.warnf("Attribute '%s' is null on LDAP object '%s' . Using empty value to be saved to LDAP", attrName, ldapObject.getDn().toString());
+                    attrValue = Collections.emptySet();
+                }
+
+                // Ignore empty attributes during create
+                if (isCreate && attrValue.isEmpty()) {
+                    continue;
+                }
+
+                BasicAttribute attr = new BasicAttribute(attrName);
+                for (String val : attrValue) {
+                    if (val == null || val.toString().trim().length() == 0) {
+                        val = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
                     }
+                    attr.add(val);
                 }
+
                 entryAttributes.put(attr);
             }
         }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 66665bf..b5598e9 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -13,7 +13,9 @@ import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
 import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
 import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionTask;
 import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
@@ -26,6 +28,7 @@ import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserFederationSyncResult;
 import org.keycloak.models.UserModel;
 import org.keycloak.constants.KerberosConstants;
+import org.keycloak.models.utils.KeycloakModelUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -176,7 +179,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
         for (LDAPObject ldapUser : ldapUsers) {
             String ldapUsername = LDAPUtils.getUsername(ldapUser, this.ldapIdentityStore.getConfig());
             if (session.userStorage().getUserByUsername(ldapUsername, realm) == null) {
-                UserModel imported = importUserFromLDAP(realm, ldapUser);
+                UserModel imported = importUserFromLDAP(session, realm, ldapUser);
                 searchResults.add(imported);
             }
         }
@@ -249,10 +252,10 @@ public class LDAPFederationProvider implements UserFederationProvider {
             return null;
         }
 
-        return importUserFromLDAP(realm, ldapUser);
+        return importUserFromLDAP(session, realm, ldapUser);
     }
 
-    protected UserModel importUserFromLDAP(RealmModel realm, LDAPObject ldapUser) {
+    protected UserModel importUserFromLDAP(KeycloakSession session, RealmModel realm, LDAPObject ldapUser) {
         String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
 
         if (ldapUsername == null) {
@@ -298,7 +301,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
             return null;
         }
 
-        return importUserFromLDAP(realm, ldapUser);
+        return importUserFromLDAP(session, realm, ldapUser);
     }
 
     @Override
@@ -383,38 +386,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
     public void close() {
     }
 
-    protected UserFederationSyncResult importLDAPUsers(RealmModel realm, List<LDAPObject> ldapUsers, UserFederationProviderModel fedModel) {
-        UserFederationSyncResult syncResult = new UserFederationSyncResult();
-
-        for (LDAPObject ldapUser : ldapUsers) {
-            String username = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
-            UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
-
-            if (currentUser == null) {
-                // Add new user to Keycloak
-                importUserFromLDAP(realm, ldapUser);
-                syncResult.increaseAdded();
-            } else {
-                if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getFirstAttribute(LDAPConstants.LDAP_ID)))) {
-
-                    // Update keycloak user
-                    Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
-                    for (UserFederationMapperModel mapperModel : federationMappers) {
-                        LDAPFederationMapper ldapMapper = getMapper(mapperModel);
-                        ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, currentUser, realm, false);
-                    }
-
-                    logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
-                    syncResult.increaseUpdated();
-                } else {
-                    logger.warnf("User '%s' is not updated during sync as he is not linked to federation provider '%s'", username, fedModel.getDisplayName());
-                }
-            }
-        }
-
-        return syncResult;
-    }
-
     /**
      * Called after successful kerberos authentication
      *
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
index 2b60ff8..a71b84d 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -14,12 +14,15 @@ import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilde
 import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
 import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
 import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
+import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
 import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
 import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.KeycloakSessionTask;
 import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationEventAwareProviderFactory;
 import org.keycloak.models.UserFederationMapperModel;
@@ -94,7 +97,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
                 UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
                 UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute,
                 UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
-                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
+                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
+                UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
         realm.addUserFederationMapper(mapperModel);
 
         // CN is typically used as RDN for Active Directory deployments
@@ -107,7 +111,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
                         UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
                         UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
                         UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
-                        UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
+                        UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
+                        UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
                 realm.addUserFederationMapper(mapperModel);
 
             } else {
@@ -118,14 +123,16 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
                             UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
                             UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
                             UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
-                            UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
+                            UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
+                            UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
                     realm.addUserFederationMapper(mapperModel);
 
                     mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
                             UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
                             UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
                             UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
-                            UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
+                            UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
+                            UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
                     realm.addUserFederationMapper(mapperModel);
                 } else {
 
@@ -141,7 +148,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
                     UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
                     UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
                     UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
-                    UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
+                    UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
+                    UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
             realm.addUserFederationMapper(mapperModel);
         }
 
@@ -149,14 +157,16 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
                 UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME,
                 UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN,
                 UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
-                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
+                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
+                UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
         realm.addUserFederationMapper(mapperModel);
 
         mapperModel = KeycloakModelUtils.createUserFederationMapperModel("email", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
                 UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL,
                 UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL,
                 UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
-                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
+                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
+                UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
         realm.addUserFederationMapper(mapperModel);
 
         String createTimestampLdapAttrName = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
@@ -167,7 +177,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
                 UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP,
                 UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName,
                 UserAttributeLDAPFederationMapper.READ_ONLY, "true",
-                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
+                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
+                UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
         realm.addUserFederationMapper(mapperModel);
 
         // map modifyTimeStamp as read-only
@@ -175,7 +186,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
                 UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP,
                 UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName,
                 UserAttributeLDAPFederationMapper.READ_ONLY, "true",
-                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
+                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
+                UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
         realm.addUserFederationMapper(mapperModel);
     }
 
@@ -226,29 +238,14 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
                 userQuery.setLimit(pageSize);
                 final List<LDAPObject> users = userQuery.getResultList();
                 nextPage = userQuery.getPaginationContext() != null;
-
-                KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
-
-                    @Override
-                    public void run(KeycloakSession session) {
-                        UserFederationSyncResult currentPageSync = importLdapUsers(session, realmId, fedModel, users);
-                        syncResult.add(currentPageSync);
-                    }
-
-                });
+                UserFederationSyncResult currentPageSync = importLdapUsers(sessionFactory, realmId, fedModel, users);
+                syncResult.add(currentPageSync);
             }
         } else {
             // LDAP pagination not available. Do everything in single transaction
             final List<LDAPObject> users = userQuery.getResultList();
-            KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
-
-                @Override
-                public void run(KeycloakSession session) {
-                    UserFederationSyncResult currentSync = importLdapUsers(session, realmId, fedModel, users);
-                    syncResult.add(currentSync);
-                }
-
-            });
+            UserFederationSyncResult currentSync = importLdapUsers(sessionFactory, realmId, fedModel, users);
+            syncResult.add(currentSync);
         }
 
         return syncResult;
@@ -273,11 +270,81 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
         return queryHolder.query;
     }
 
+    protected UserFederationSyncResult importLdapUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel fedModel, List<LDAPObject> ldapUsers) {
+        final UserFederationSyncResult syncResult = new UserFederationSyncResult();
+
+        class BooleanHolder {
+            private boolean value = true;
+        }
+        final BooleanHolder exists = new BooleanHolder();
+
+        for (final LDAPObject ldapUser : ldapUsers) {
+
+            try {
+
+                // Process each user in it's own transaction to avoid global fail
+                KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+                    @Override
+                    public void run(KeycloakSession session) {
+                        LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
+                        RealmModel currentRealm = session.realms().getRealm(realmId);
+
+                        String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig());
+                        UserModel currentUser = session.userStorage().getUserByUsername(username, currentRealm);
+
+                        if (currentUser == null) {
+
+                            // Add new user to Keycloak
+                            exists.value = false;
+                            ldapFedProvider.importUserFromLDAP(session, currentRealm, ldapUser);
+                            syncResult.increaseAdded();
+
+                        } else {
+                            if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getFirstAttribute(LDAPConstants.LDAP_ID)))) {
+
+                                // Update keycloak user
+                                Set<UserFederationMapperModel> federationMappers = currentRealm.getUserFederationMappersByFederationProvider(fedModel.getId());
+                                for (UserFederationMapperModel mapperModel : federationMappers) {
+                                    LDAPFederationMapper ldapMapper = ldapFedProvider.getMapper(mapperModel);
+                                    ldapMapper.onImportUserFromLDAP(mapperModel, ldapFedProvider, ldapUser, currentUser, currentRealm, false);
+                                }
+
+                                logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
+                                syncResult.increaseUpdated();
+                            } else {
+                                logger.warnf("User '%s' is not updated during sync as he already exists in Keycloak database but is not linked to federation provider '%s'", username, fedModel.getDisplayName());
+                                syncResult.increaseFailed();
+                            }
+                        }
+                    }
+
+                });
+            } catch (ModelException me) {
+                logger.error("Failed during import user from LDAP", me);
+                syncResult.increaseFailed();
+
+                // Remove user if we already added him during this transaction
+                if (!exists.value) {
+                    KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+                        @Override
+                        public void run(KeycloakSession session) {
+                            LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
+                            RealmModel currentRealm = session.realms().getRealm(realmId);
+                            String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig());
+                            UserModel existing = session.userStorage().getUserByUsername(username, currentRealm);
+                            if (existing != null) {
+                                session.userStorage().removeUser(currentRealm, existing);
+                            }
+                        }
+
+                    });
+                }
+            }
+        }
 
-    protected UserFederationSyncResult importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<LDAPObject> ldapUsers) {
-        RealmModel realm = session.realms().getRealm(realmId);
-        LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
-        return ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel);
+        return syncResult;
     }
 
     protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) {
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
index 4565f88..e29ab6f 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
@@ -239,7 +239,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
         Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
         memberships.remove(ldapUser.getDn().toString());
 
-        // Some membership placeholder needs to be always here as "member" is mandatory attribute on some LDAP servers. But on active directory! (Empty membership is not allowed here)
+        // Some membership placeholder needs to be always here as "member" is mandatory attribute on some LDAP servers. But not on active directory! (Empty membership is not allowed here)
         if (memberships.size() == 0 && !ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory()) {
             memberships.add(LDAPConstants.EMPTY_MEMBER_ATTRIBUTE_VALUE);
         }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
index 24bf450..283089a 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
@@ -3,6 +3,7 @@ package org.keycloak.federation.ldap.mappers;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -15,6 +16,7 @@ import org.keycloak.federation.ldap.idm.model.LDAPObject;
 import org.keycloak.federation.ldap.idm.query.Condition;
 import org.keycloak.federation.ldap.idm.query.QueryParameter;
 import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
+import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProvider;
@@ -58,6 +60,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
     public static final String LDAP_ATTRIBUTE = "ldap.attribute";
     public static final String READ_ONLY = "read.only";
     public static final String ALWAYS_READ_VALUE_FROM_LDAP = "always.read.value.from.ldap";
+    public static final String IS_MANDATORY_IN_LDAP = "is.mandatory.in.ldap";
 
 
     @Override
@@ -88,6 +91,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
     public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
         String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
         String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
+        boolean isMandatoryInLdap = parseBooleanParameter(mapperModel, IS_MANDATORY_IN_LDAP);
 
         Property<Object> userModelProperty = userModelProperties.get(userModelAttrName.toLowerCase());
 
@@ -95,15 +99,27 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
 
             // we have java property on UserModel. Assuming we support just properties of simple types
             Object attrValue = userModelProperty.getValue(localUser);
-            String valueAsString = (attrValue == null) ? null : attrValue.toString();
-            ldapUser.setSingleAttribute(ldapAttrName, valueAsString);
+
+            if (attrValue == null) {
+                if (isMandatoryInLdap) {
+                    ldapUser.setSingleAttribute(ldapAttrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
+                } else {
+                    ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<String>());
+                }
+            } else {
+                ldapUser.setSingleAttribute(ldapAttrName, attrValue.toString());
+            }
         } else {
 
             // we don't have java property. Let's set attribute
             List<String> attrValues = localUser.getAttribute(userModelAttrName);
 
             if (attrValues.size() == 0) {
-                ldapUser.setAttribute(ldapAttrName, null);
+                if (isMandatoryInLdap) {
+                    ldapUser.setSingleAttribute(ldapAttrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
+                } else {
+                    ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<String>());
+                }
             } else {
                 ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<>(attrValues));
             }
@@ -119,6 +135,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
         final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
         final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
         boolean isAlwaysReadValueFromLDAP = parseBooleanParameter(mapperModel, ALWAYS_READ_VALUE_FROM_LDAP);
+        final boolean isMandatoryInLdap = parseBooleanParameter(mapperModel, IS_MANDATORY_IN_LDAP);
 
         // For writable mode, we want to propagate writing of attribute to LDAP as well
         if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
@@ -170,12 +187,20 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
                         ensureTransactionStarted();
 
                         if (value == null) {
-                            ldapUser.setAttribute(ldapAttrName, null);
+                            if (isMandatoryInLdap) {
+                                ldapUser.setSingleAttribute(ldapAttrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
+                            } else {
+                                ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<String>());
+                            }
                         } else if (value instanceof String) {
                             ldapUser.setSingleAttribute(ldapAttrName, (String) value);
                         } else {
                             List<String> asList = (List<String>) value;
-                            ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<>(asList));
+                            if (asList.isEmpty() && isMandatoryInLdap) {
+                                ldapUser.setSingleAttribute(ldapAttrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
+                            } else {
+                                ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<>(asList));
+                            }
                         }
                     }
                 }
@@ -203,7 +228,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
                     if (name.equalsIgnoreCase(userModelAttrName)) {
                         Collection<String> ldapAttrValue = ldapUser.getAttributeAsSet(ldapAttrName);
                         if (ldapAttrValue == null) {
-                            return null;
+                            return Collections.emptyList();
                         } else {
                             return new ArrayList<>(ldapAttrValue);
                         }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
index 1b1b44d..c14d0e8 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
@@ -31,9 +31,13 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera
                 "Read-only attribute is imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", ProviderConfigProperty.BOOLEAN_TYPE, "false");
         configProperties.add(readOnly);
 
-        ProviderConfigProperty alwaysReadValueFromLDAP = createConfigProperty(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "Always read value from LDAP",
+        ProviderConfigProperty alwaysReadValueFromLDAP = createConfigProperty(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "Always Read Value From LDAP",
                 "If on, then during reading of the user will be value of attribute from LDAP always used instead of the value from Keycloak DB", ProviderConfigProperty.BOOLEAN_TYPE, "false");
         configProperties.add(alwaysReadValueFromLDAP);
+
+        ProviderConfigProperty isMandatoryInLdap = createConfigProperty(UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "Is Mandatory In LDAP",
+                "If true, attribute is mandatory in LDAP. Hence if there is no value in Keycloak DB, the empty value will be set to be propagated to LDAP", ProviderConfigProperty.BOOLEAN_TYPE, "false");
+        configProperties.add(isMandatoryInLdap);
     }
 
     @Override
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index 7d9e7ca..8bca31c 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -128,7 +128,7 @@ public class UserFederationManager implements UserProvider {
         if (link != null) {
             UserModel validatedProxyUser = link.validateAndProxy(realm, user);
             if (validatedProxyUser != null) {
-                managedUsers.put(user.getId(), user);
+                managedUsers.put(user.getId(), validatedProxyUser);
                 return validatedProxyUser;
             } else {
                 deleteInvalidUser(realm, user);
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java b/model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java
index e6d465b..b06348d 100644
--- a/model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java
@@ -8,6 +8,7 @@ public class UserFederationSyncResult {
     private int added;
     private int updated;
     private int removed;
+    private int failed;
 
     public int getAdded() {
         return added;
@@ -33,6 +34,14 @@ public class UserFederationSyncResult {
         this.removed = removed;
     }
 
+    public int getFailed() {
+        return failed;
+    }
+
+    public void setFailed(int failed) {
+        this.failed = failed;
+    }
+
     public void increaseAdded() {
         added++;
     }
@@ -45,14 +54,23 @@ public class UserFederationSyncResult {
         removed++;
     }
 
+    public void increaseFailed() {
+        failed++;
+    }
+
     public void add(UserFederationSyncResult other) {
         added += other.added;
         updated += other.updated;
         removed += other.removed;
+        failed += other.failed;
     }
 
     public String getStatus() {
-        return String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
+        String status = String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
+        if (failed != 0) {
+            status += String.format(", %d users failed sync! See server log for more details", failed);
+        }
+        return status;
     }
 
     @Override

pom.xml 11(+7 -4)

diff --git a/pom.xml b/pom.xml
index 33794fa..ca9d823 100755
--- a/pom.xml
+++ b/pom.xml
@@ -155,6 +155,7 @@
         <module>testsuite</module>
         <module>timer</module>
         <module>export-import</module>
+        <module>util</module>
     </modules>
 
     <dependencyManagement>
@@ -431,25 +432,21 @@
                 <groupId>org.apache.directory.server</groupId>
                 <artifactId>apacheds-core-annotations</artifactId>
                 <version>${apacheds.version}</version>
-                <scope>test</scope>
             </dependency>
             <dependency>
                 <groupId>org.apache.directory.server</groupId>
                 <artifactId>apacheds-interceptor-kerberos</artifactId>
                 <version>${apacheds.version}</version>
-                <scope>test</scope>
             </dependency>
             <dependency>
                 <groupId>org.apache.directory.server</groupId>
                 <artifactId>apacheds-server-annotations</artifactId>
                 <version>${apacheds.version}</version>
-                <scope>test</scope>
             </dependency>
             <dependency>
                 <groupId>org.apache.directory.api</groupId>
                 <artifactId>api-ldap-codec-standalone</artifactId>
                 <version>${apacheds.codec.version}</version>
-                <scope>test</scope>
             </dependency>
 
             <!-- Selenium -->
@@ -1122,6 +1119,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-util-embedded-ldap</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-docs-dist</artifactId>
                 <version>${project.version}</version>
                 <type>zip</type>
@@ -1184,6 +1186,7 @@
                 <version>${project.version}</version>
                 <classifier>classes</classifier>
             </dependency>
+
             <dependency>
                 <groupId>org.keycloak</groupId>
                 <artifactId>federation-properties-example</artifactId>
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
index 75f6096..c39bc8a 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
@@ -1,5 +1,6 @@
 package org.keycloak.services;
 
+import org.jboss.logging.Logger;
 import org.keycloak.models.KeycloakTransaction;
 import org.keycloak.models.KeycloakTransactionManager;
 
@@ -11,6 +12,8 @@ import java.util.List;
  */
 public class DefaultKeycloakTransactionManager implements KeycloakTransactionManager {
 
+    public static final Logger logger = Logger.getLogger(DefaultKeycloakTransactionManager.class);
+
     private List<KeycloakTransaction> transactions = new LinkedList<KeycloakTransaction>();
     private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
     private boolean active;
@@ -57,13 +60,26 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
                 exception = exception == null ? e : exception;
             }
         }
-        for (KeycloakTransaction tx : afterCompletion) {
-            try {
-                tx.commit();
-            } catch (RuntimeException e) {
-                exception = exception == null ? e : exception;
+
+        // Don't commit "afterCompletion" if commit of some main transaction failed
+        if (exception == null) {
+            for (KeycloakTransaction tx : afterCompletion) {
+                try {
+                    tx.commit();
+                } catch (RuntimeException e) {
+                    exception = exception == null ? e : exception;
+                }
+            }
+        } else {
+            for (KeycloakTransaction tx : afterCompletion) {
+                try {
+                    tx.rollback();
+                } catch (RuntimeException e) {
+                    logger.error("Exception during rollback", e);
+                }
             }
         }
+
         active = false;
         if (exception != null) {
             throw exception;
diff --git a/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java
index 1da7bfb..7b6b3ed 100644
--- a/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java
+++ b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java
@@ -29,7 +29,13 @@ public class AdminMessagesProvider implements MessagesProvider {
     @Override
     public String getMessage(String messageKey, Object... parameters) {
         String message = messagesBundle.getProperty(messageKey, messageKey);
-        return new MessageFormat(message, locale).format(parameters);
+
+        try {
+            return new MessageFormat(message, locale).format(parameters);
+        } catch (Exception e) {
+            logger.warnf("Failed to format message due to: %s", e.getMessage());
+            return message;
+        }
     }
 
     @Override
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 0673780..cd7c98d 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -190,40 +190,8 @@
 
         <!-- Apache DS -->
         <dependency>
-            <groupId>org.apache.directory.server</groupId>
-            <artifactId>apacheds-core-annotations</artifactId>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.slf4j</groupId>
-                    <artifactId>slf4j-log4j12</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.directory.jdbm</groupId>
-                    <artifactId>apacheds-jdbm1</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.directory.server</groupId>
-            <artifactId>apacheds-interceptor-kerberos</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.directory.server</groupId>
-            <artifactId>apacheds-server-annotations</artifactId>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.slf4j</groupId>
-                    <artifactId>slf4j-log4j12</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.directory.jdbm</groupId>
-                    <artifactId>apacheds-jdbm1</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.directory.api</groupId>
-            <artifactId>api-ldap-codec-standalone</artifactId>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-util-embedded-ldap</artifactId>
         </dependency>
 
         <dependency>
@@ -344,7 +312,7 @@
                         <groupId>org.codehaus.mojo</groupId>
                         <artifactId>exec-maven-plugin</artifactId>
                         <configuration>
-                            <mainClass>org.keycloak.testsuite.ldap.LDAPEmbeddedServer</mainClass>
+                            <mainClass>org.keycloak.util.ldap.LDAPEmbeddedServer</mainClass>
                             <classpathScope>test</classpathScope>
                         </configuration>
                     </plugin>
@@ -359,7 +327,7 @@
                         <groupId>org.codehaus.mojo</groupId>
                         <artifactId>exec-maven-plugin</artifactId>
                         <configuration>
-                            <mainClass>org.keycloak.testsuite.ldap.KerberosEmbeddedServer</mainClass>
+                            <mainClass>org.keycloak.util.ldap.KerberosEmbeddedServer</mainClass>
                             <classpathScope>test</classpathScope>
                         </configuration>
                     </plugin>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
index c4a028c..1945701 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
@@ -68,10 +68,10 @@ public class FederationProvidersIntegrationTest {
             LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
             FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
 
-            LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
+            LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
             ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
 
-            LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", "5678");
+            LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678");
         }
     });
 
@@ -270,7 +270,7 @@ public class FederationProvidersIntegrationTest {
             RealmModel appRealm = new RealmManager(session).getRealmByName("test");
 
             LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
-            LDAPObject johnZip = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnzip", "John", "Zip", "johnzip@email.org", "12398");
+            LDAPObject johnZip = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnzip", "John", "Zip", "johnzip@email.org", null, "12398");
 
             // Remove default zipcode mapper and add the mapper for "POstalCode" to test case sensitivity
             UserFederationMapperModel currentZipMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "zipCodeMapper");
@@ -295,7 +295,7 @@ public class FederationProvidersIntegrationTest {
             RealmModel appRealm = new RealmManager(session).getRealmByName("test");
 
             LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
-            LDAPObject johnDirect = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johndirect", "John", "Direct", "johndirect@email.org", "12399");
+            LDAPObject johnDirect = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johndirect", "John", "Direct", "johndirect@email.org", null, "12399");
 
             // Fetch user from LDAP and check that postalCode is filled
             UserModel user = session.users().getUserByUsername("johndirect", appRealm);
@@ -307,9 +307,18 @@ public class FederationProvidersIntegrationTest {
             johnDirect.setSingleAttribute(LDAPConstants.SN, "DirectLDAPUpdated");
             ldapFedProvider.getLdapIdentityStore().update(johnDirect);
 
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+            UserModel user = session.users().getUserByUsername("johndirect", appRealm);
+
             // Verify that postalCode is still the same as we read it's value from Keycloak DB
             user = session.users().getUserByUsername("johndirect", appRealm);
-            postalCode = user.getFirstAttribute("postal_code");
+            String postalCode = user.getFirstAttribute("postal_code");
             Assert.assertEquals("12399", postalCode);
 
             // Check user.getAttributes()
@@ -370,7 +379,7 @@ public class FederationProvidersIntegrationTest {
 
             // Add the user with some fullName into LDAP directly. Ensure that fullName is saved into "cn" attribute in LDAP (currently mapped to model firstName)
             LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
-            FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James Dee", "Dee", "fullname@email.org", "4578");
+            FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James Dee", "Dee", "fullname@email.org", null, "4578");
 
             // add fullname mapper to the provider and remove "firstNameMapper". For this test, we will simply map full name to the LDAP attribute, which was before firstName ( "givenName" on active directory, "cn" on other LDAP servers)
             firstNameMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "first name");
@@ -381,9 +390,6 @@ public class FederationProvidersIntegrationTest {
                     FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
                     UserAttributeLDAPFederationMapper.READ_ONLY, "false");
             appRealm.addUserFederationMapper(fullNameMapperModel);
-
-            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
-            FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
         } finally {
             keycloakRule.stopSession(session, true);
         }
@@ -392,6 +398,9 @@ public class FederationProvidersIntegrationTest {
         try {
             RealmModel appRealm = new RealmManager(session).getRealmByName("test");
 
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
+
             // Remove "fullnameUser" to assert he is removed from LDAP. Revert mappers to previous state
             UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
             session.users().removeUser(appRealm, fullnameUser);
@@ -485,10 +494,10 @@ public class FederationProvidersIntegrationTest {
             RealmModel appRealm = session.realms().getRealmByName("test");
             LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
 
-            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", "121");
-            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username2", "John2", "Doel2", "user2@email.org", "122");
-            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username3", "John3", "Doel3", "user3@email.org", "123");
-            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username4", "John4", "Doel4", "user4@email.org", "124");
+            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", null, "121");
+            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username2", "John2", "Doel2", "user2@email.org", null, "122");
+            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username3", "John3", "Doel3", "user3@email.org", null, "123");
+            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username4", "John4", "Doel4", "user4@email.org", null, "124");
 
             // Users are not at local store at this moment
             Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm));
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
index 56c4a70..939fe24 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
@@ -34,7 +34,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
 class FederationTestUtils {
 
     public static UserModel addLocalUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
-        UserModel user = session.users().addUser(realm, username);
+        UserModel user = session.userStorage().addUser(realm, username);
         user.setEmail(email);
         user.setEnabled(true);
 
@@ -47,7 +47,7 @@ class FederationTestUtils {
     }
 
     public static LDAPObject addLDAPUser(LDAPFederationProvider ldapProvider, RealmModel realm, final String username,
-                                            final String firstName, final String lastName, final String email, final String postalCode) {
+                                            final String firstName, final String lastName, final String email, final String street, final String... postalCode) {
         UserModel helperUser = new UserModelDelegate(null) {
 
             @Override
@@ -72,8 +72,10 @@ class FederationTestUtils {
 
             @Override
             public List<String> getAttribute(String name) {
-                if ("postal_code".equals(name)) {
+                if ("postal_code".equals(name) && postalCode != null && postalCode.length > 0) {
                     return Arrays.asList(postalCode);
+                } else if ("street".equals(name) && street != null) {
+                    return Arrays.asList(street);
                 } else {
                     return Collections.emptyList();
                 }
@@ -105,7 +107,8 @@ class FederationTestUtils {
                 UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, userModelAttributeName,
                 UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, ldapAttributeName,
                 UserAttributeLDAPFederationMapper.READ_ONLY, "false",
-                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
+                UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
+                UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
         realm.addUserFederationMapper(mapperModel);
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPMultipleAttributesTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPMultipleAttributesTest.java
index e1e9aff..edd210c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPMultipleAttributesTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPMultipleAttributesTest.java
@@ -1,6 +1,9 @@
 package org.keycloak.testsuite.federation;
 
 import java.net.URL;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 
@@ -17,6 +20,7 @@ import org.junit.runners.MethodSorters;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.federation.ldap.LDAPFederationProvider;
 import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.LDAPConstants;
@@ -60,6 +64,19 @@ public class LDAPMultipleAttributesTest {
             FederationTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
             FederationTestUtils.addUserAttributeMapper(appRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET);
 
+            // Remove current users and add default users
+            LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+            FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+            LDAPObject james = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", null, "88441");
+            ldapFedProvider.getLdapIdentityStore().updatePassword(james, "password");
+
+            // User for testing duplicating surname and postalCode
+            LDAPObject bruce = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "bwilson", "Bruce", "Wilson", "bwilson@keycloak.org", "Elm 5", "88441", "77332");
+            bruce.setAttribute("sn", new LinkedHashSet<>(Arrays.asList("Wilson", "Schneider")));
+            ldapFedProvider.getLdapIdentityStore().update(bruce);
+            ldapFedProvider.getLdapIdentityStore().updatePassword(bruce, "password");
+
             // Create ldap-portal client
             ClientModel ldapClient = appRealm.addClient("ldap-portal");
             ldapClient.addRedirectUri("/ldap-portal");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java
index 4802105..c0a8cd6 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java
@@ -76,13 +76,13 @@ public class LDAPRoleMappingsTest {
             FederationTestUtils.removeAllLDAPRoles(manager.getSession(), appRealm, ldapModel, "financeRolesMapper");
 
             // Add some users for testing
-            LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
+            LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
             ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
 
-            LDAPObject mary = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", "5678");
+            LDAPObject mary = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678");
             ldapFedProvider.getLdapIdentityStore().updatePassword(mary, "Password1");
 
-            LDAPObject rob = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", "8910");
+            LDAPObject rob = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910");
             ldapFedProvider.getLdapIdentityStore().updatePassword(rob, "Password1");
 
             // Add some roles for testing
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
index b463bcd..e29a2b8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
@@ -61,7 +61,7 @@ public class SyncProvidersTest {
             FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
 
             for (int i=1 ; i<=5 ; i++) {
-                LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", "12" + i);
+                LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", null, "12" + i);
                 ldapFedProvider.getLdapIdentityStore().updatePassword(ldapUser, "Password1");
             }
 
@@ -81,7 +81,7 @@ public class SyncProvidersTest {
 //    }
 
     @Test
-    public void testLDAPSync() {
+    public void test01LDAPSync() {
         UsersSyncManager usersSyncManager = new UsersSyncManager();
 
         // wait a bit
@@ -91,7 +91,7 @@ public class SyncProvidersTest {
         try {
             KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
             UserFederationSyncResult syncResult = usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel);
-            assertSyncEquals(syncResult, 5, 0, 0);
+            assertSyncEquals(syncResult, 5, 0, 0, 0);
         } finally {
             keycloakRule.stopSession(session, false);
         }
@@ -123,7 +123,7 @@ public class SyncProvidersTest {
 
             // Add user to LDAP and update 'user5' in LDAP
             LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
-            FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
+            FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", null, "126");
             LDAPObject ldapUser5 = ldapFedProvider.loadLDAPUserByUsername(testRealm, "user5");
             // NOTE: Changing LDAP attributes directly here
             ldapUser5.setSingleAttribute(LDAPConstants.EMAIL, "user5Updated@email.org");
@@ -137,7 +137,7 @@ public class SyncProvidersTest {
             // Trigger partial sync
             KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
             UserFederationSyncResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel);
-            assertSyncEquals(syncResult, 1, 1, 0);
+            assertSyncEquals(syncResult, 1, 1, 0, 0);
         } finally {
             keycloakRule.stopSession(session, false);
         }
@@ -155,6 +155,67 @@ public class SyncProvidersTest {
     }
 
     @Test
+    public void test02duplicateUsernameSync() {
+        LDAPObject duplicatedLdapUser;
+
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel testRealm = session.realms().getRealm("test");
+
+            FederationTestUtils.addLocalUser(session, testRealm, "user7", "user7@email.org", "password");
+            LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+
+            // Add user to LDAP with duplicated username "user7"
+            duplicatedLdapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7", "User7FN", "User7LN", "user7-something@email.org", null, "126");
+
+            // Add user to LDAP with duplicated email "user7@email.org"
+            //FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7-something", "User7FNN", "User7LNL", "user7@email.org", null, "126");
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            RealmModel testRealm = session.realms().getRealm("test");
+
+            // Assert syncing from LDAP fails due to duplicated username
+            UserFederationSyncResult result = new UsersSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel);
+            Assert.assertEquals(1, result.getFailed());
+
+            // Remove "user7" from LDAP
+            LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+            ldapFedProvider.getLdapIdentityStore().remove(duplicatedLdapUser);
+
+            // Add user to LDAP with duplicated email "user7@email.org"
+            duplicatedLdapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7-something", "User7FNN", "User7LNL", "user7@email.org", null, "126");
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            RealmModel testRealm = session.realms().getRealm("test");
+
+            // Assert syncing from LDAP fails due to duplicated email
+            UserFederationSyncResult result = new UsersSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel);
+            Assert.assertEquals(1, result.getFailed());
+            Assert.assertNull(session.userStorage().getUserByUsername("user7-something", testRealm));
+
+            // Update LDAP user to avoid duplicated email
+            duplicatedLdapUser.setSingleAttribute(LDAPConstants.EMAIL, "user7-changed@email.org");
+            LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+            ldapFedProvider.getLdapIdentityStore().update(duplicatedLdapUser);
+
+            // Assert user successfully synced now
+            result = new UsersSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel);
+            Assert.assertEquals(0, result.getFailed());
+            FederationTestUtils.assertUserImported(session.userStorage(), testRealm, "user7-something", "User7FNN", "User7LNL", "user7-changed@email.org", "126");
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+    @Test
     public void testPeriodicSync() {
         KeycloakSession session = keycloakRule.startSession();
         try {
@@ -193,9 +254,10 @@ public class SyncProvidersTest {
         }
     }
 
-    private void assertSyncEquals(UserFederationSyncResult syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved) {
+    private void assertSyncEquals(UserFederationSyncResult syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved, int expectedFailed) {
         Assert.assertEquals(syncResult.getAdded(), expectedAdded);
         Assert.assertEquals(syncResult.getUpdated(), expectedUpdated);
         Assert.assertEquals(syncResult.getRemoved(), expectedRemoved);
+        Assert.assertEquals(syncResult.getFailed(), expectedFailed);
     }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java
index 9026557..57bf79a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java
@@ -2,11 +2,12 @@ package org.keycloak.testsuite.rule;
 
 import java.io.File;
 import java.net.URL;
+import java.util.Properties;
 
 import org.jboss.logging.Logger;
-import org.keycloak.testsuite.ldap.EmbeddedServersFactory;
-import org.keycloak.testsuite.ldap.LDAPTestConfiguration;
-import org.keycloak.testsuite.ldap.LDAPEmbeddedServer;
+import org.keycloak.testsuite.federation.LDAPTestConfiguration;
+import org.keycloak.util.ldap.KerberosEmbeddedServer;
+import org.keycloak.util.ldap.LDAPEmbeddedServer;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -33,7 +34,11 @@ public class KerberosRule extends LDAPRule {
     }
 
     @Override
-    protected LDAPEmbeddedServer createServer(EmbeddedServersFactory factory) {
-        return factory.createKerberosServer();
+    protected LDAPEmbeddedServer createServer() {
+        Properties defaultProperties = new Properties();
+        defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY);
+        defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_LDIF_FILE, "classpath:kerberos/users-kerberos.ldif");
+
+        return new KerberosEmbeddedServer(defaultProperties);
     }
 }
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 1bf9b8e..add9708 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,11 +1,11 @@
 package org.keycloak.testsuite.rule;
 
 import java.util.Map;
+import java.util.Properties;
 
 import org.junit.rules.ExternalResource;
-import org.keycloak.testsuite.ldap.EmbeddedServersFactory;
-import org.keycloak.testsuite.ldap.LDAPTestConfiguration;
-import org.keycloak.testsuite.ldap.LDAPEmbeddedServer;
+import org.keycloak.testsuite.federation.LDAPTestConfiguration;
+import org.keycloak.util.ldap.LDAPEmbeddedServer;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -23,8 +23,7 @@ public class LDAPRule extends ExternalResource {
         ldapTestConfiguration = LDAPTestConfiguration.readConfiguration(connectionPropsLocation);
 
         if (ldapTestConfiguration.isStartEmbeddedLdapLerver()) {
-            EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
-            ldapEmbeddedServer = createServer(factory);
+            ldapEmbeddedServer = createServer();
             ldapEmbeddedServer.init();
             ldapEmbeddedServer.start();
         }
@@ -47,8 +46,12 @@ public class LDAPRule extends ExternalResource {
         return LDAP_CONNECTION_PROPERTIES_LOCATION;
     }
 
-    protected LDAPEmbeddedServer createServer(EmbeddedServersFactory factory) {
-        return factory.createLdapServer();
+    protected LDAPEmbeddedServer createServer() {
+        Properties defaultProperties = new Properties();
+        defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY);
+        defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_LDIF_FILE, "classpath:ldap/users.ldif");
+
+        return new LDAPEmbeddedServer(defaultProperties);
     }
 
     public Map<String, String> getConfig() {
diff --git a/testsuite/integration/src/test/resources/ldap/users.ldif b/testsuite/integration/src/test/resources/ldap/users.ldif
index 4d6d87e..176e19b 100644
--- a/testsuite/integration/src/test/resources/ldap/users.ldif
+++ b/testsuite/integration/src/test/resources/ldap/users.ldif
@@ -18,30 +18,3 @@ dn: ou=FinanceRoles,dc=keycloak,dc=org
 objectclass: top
 objectclass: organizationalUnit
 ou: FinanceRoles
-
-dn: uid=jbrown,ou=People,dc=keycloak,dc=org
-objectclass: top
-objectclass: person
-objectclass: organizationalPerson
-objectclass: inetOrgPerson
-uid: jbrown
-cn: James
-sn: Brown
-mail: jbrown@keycloak.org
-postalCode: 88441
-userPassword: password
-
-dn: uid=bwilson,ou=People,dc=keycloak,dc=org
-objectclass: top
-objectclass: person
-objectclass: organizationalPerson
-objectclass: inetOrgPerson
-uid: bwilson
-cn: Bruce
-sn: Wilson
-sn: Schneider
-mail: bwilson@keycloak.org
-postalCode: 88441
-postalCode: 77332
-street: Elm 5
-userPassword: password
diff --git a/util/embedded-ldap/pom.xml b/util/embedded-ldap/pom.xml
new file mode 100644
index 0000000..5a1e927
--- /dev/null
+++ b/util/embedded-ldap/pom.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.4.0.Final-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-util-embedded-ldap</artifactId>
+    <name>Keycloak Util Embedded LDAP</name>
+    <description/>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-core-annotations</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.directory.jdbm</groupId>
+                    <artifactId>apacheds-jdbm1</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-interceptor-kerberos</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-server-annotations</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.directory.jdbm</groupId>
+                    <artifactId>apacheds-jdbm1</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.api</groupId>
+            <artifactId>api-ldap-codec-standalone</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/FileDirectoryServiceFactory.java b/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/FileDirectoryServiceFactory.java
new file mode 100644
index 0000000..f0153ec
--- /dev/null
+++ b/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/FileDirectoryServiceFactory.java
@@ -0,0 +1,267 @@
+package org.keycloak.util.ldap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.directory.api.ldap.model.constants.SchemaConstants;
+import org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException;
+import org.apache.directory.api.ldap.model.exception.LdapException;
+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.schemaextractor.SchemaLdifExtractor;
+import org.apache.directory.api.ldap.schemaextractor.impl.DefaultSchemaLdifExtractor;
+import org.apache.directory.api.ldap.schemaloader.LdifSchemaLoader;
+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.interceptor.context.AddOperationContext;
+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.DefaultDirectoryServiceFactory;
+import org.apache.directory.server.core.factory.DirectoryServiceFactory;
+import org.apache.directory.server.core.factory.JdbmPartitionFactory;
+import org.apache.directory.server.core.factory.LdifPartitionFactory;
+import org.apache.directory.server.core.factory.PartitionFactory;
+import org.apache.directory.server.core.partition.ldif.LdifPartition;
+import org.apache.directory.server.i18n.I18n;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Slightly modified version of {@link DefaultDirectoryServiceFactory} which allows persistence among restarts and uses LDIF partitions by default
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class FileDirectoryServiceFactory implements DirectoryServiceFactory {
+
+    /** A logger for this class */
+    private static final Logger LOG = LoggerFactory.getLogger(FileDirectoryServiceFactory.class);
+
+    /** The directory service. */
+    private DirectoryService directoryService;
+
+    /** The partition factory. */
+    private PartitionFactory partitionFactory;
+
+
+    public FileDirectoryServiceFactory()
+    {
+        try
+        {
+            // creating the instance here so that
+            // we we can set some properties like accesscontrol, anon access
+            // before starting up the service
+            directoryService = new DefaultDirectoryService();
+
+            // no need to register a shutdown hook during tests because this
+            // starts a lot of threads and slows down test execution
+            directoryService.setShutdownHookEnabled( false );
+        }
+        catch ( Exception e )
+        {
+            throw new RuntimeException( e );
+        }
+
+        try
+        {
+            String typeName = System.getProperty( "apacheds.partition.factory" );
+
+            if ( typeName != null )
+            {
+                Class<? extends PartitionFactory> type = ( Class<? extends PartitionFactory> ) Class.forName( typeName );
+                partitionFactory = type.newInstance();
+            }
+            else
+            {
+                // partitionFactory = new JdbmPartitionFactory();
+                partitionFactory = new LdifPartitionFactory();
+            }
+        }
+        catch ( Exception e )
+        {
+            LOG.error( "Error instantiating custom partiton factory", e );
+            throw new RuntimeException( e );
+        }
+    }
+
+
+    public FileDirectoryServiceFactory( DirectoryService directoryService, PartitionFactory partitionFactory )
+    {
+        this.directoryService = directoryService;
+        this.partitionFactory = partitionFactory;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void init( String name ) throws Exception
+    {
+        if ( ( directoryService != null ) && directoryService.isStarted() ) {
+            return;
+        }
+
+        build(name);
+    }
+
+
+    /**
+     * Build the working directory
+     */
+    private void buildInstanceDirectory( String name ) throws IOException
+    {
+        String instanceDirectory = System.getProperty( "workingDirectory" );
+
+        if ( instanceDirectory == null )
+        {
+            instanceDirectory = System.getProperty( "java.io.tmpdir" ) + "/server-work-" + name;
+        }
+
+        InstanceLayout instanceLayout = new InstanceLayout( instanceDirectory );
+
+        /*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 );
+    }
+
+
+    /**
+     * Inits the schema and schema partition.
+     */
+    private void initSchema() throws Exception
+    {
+        File workingDirectory = directoryService.getInstanceLayout().getPartitionsDirectory();
+
+        // Extract the schema on disk (a brand new one) and load the registries
+        File schemaRepository = new File( workingDirectory, "schema" );
+        SchemaLdifExtractor extractor = new DefaultSchemaLdifExtractor( workingDirectory );
+
+        try
+        {
+            extractor.extractOrCopy();
+        }
+        catch ( IOException ioe )
+        {
+            // The schema has already been extracted, bypass
+        }
+
+        SchemaLoader loader = new LdifSchemaLoader( schemaRepository );
+        SchemaManager schemaManager = new DefaultSchemaManager( loader );
+
+        // We have to load the schema now, otherwise we won't be able
+        // to initialize the Partitions, as we won't be able to parse
+        // and normalize their suffix Dn
+        schemaManager.loadAllEnabled();
+
+        // Tell all the normalizer comparators that they should not normalize anything
+        ComparatorRegistry comparatorRegistry = schemaManager.getComparatorRegistry();
+
+        for ( LdapComparator<?> comparator : comparatorRegistry )
+        {
+            if ( comparator instanceof NormalizingComparator)
+            {
+                ( ( NormalizingComparator ) comparator ).setOnServer();
+            }
+        }
+
+        directoryService.setSchemaManager( schemaManager );
+
+        // Init the LdifPartition
+        LdifPartition ldifPartition = new LdifPartition( schemaManager, directoryService.getDnFactory() );
+        ldifPartition.setPartitionPath( new File( workingDirectory, "schema" ).toURI() );
+        SchemaPartition schemaPartition = new SchemaPartition( schemaManager );
+        schemaPartition.setWrappedPartition( ldifPartition );
+        directoryService.setSchemaPartition( schemaPartition );
+
+        List<Throwable> errors = schemaManager.getErrors();
+
+        if ( errors.size() != 0 )
+        {
+            throw new Exception( I18n.err(I18n.ERR_317, Exceptions.printErrors(errors)) );
+        }
+    }
+
+
+    /**
+     * Inits the system partition.
+     *
+     * @throws Exception the exception
+     */
+    private void initSystemPartition() throws Exception
+    {
+        // change the working directory to something that is unique
+        // on the system and somewhere either under target directory
+        // or somewhere in a temp area of the machine.
+
+        // Inject the 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 );
+    }
+
+
+    /**
+     * Builds the directory server instance.
+     *
+     * @param name the instance name
+     */
+    private void build( String name ) throws Exception
+    {
+        directoryService.setInstanceId( name );
+        buildInstanceDirectory( name );
+
+        CacheService cacheService = new CacheService();
+        cacheService.initialize( directoryService.getInstanceLayout() );
+
+        directoryService.setCacheService( cacheService );
+
+        // Init the service now
+        initSchema();
+        initSystemPartition();
+
+        directoryService.startup();
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public DirectoryService getDirectoryService() throws Exception
+    {
+        return directoryService;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public PartitionFactory getPartitionFactory() throws Exception
+    {
+        return partitionFactory;
+    }
+}
diff --git a/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java b/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java
new file mode 100644
index 0000000..18a1a30
--- /dev/null
+++ b/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java
@@ -0,0 +1,272 @@
+package org.keycloak.util.ldap;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+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.DirectoryServiceFactory;
+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.FindFile;
+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);
+
+    public static final String PROPERTY_BASE_DN = "ldap.baseDN";
+    public static final String PROPERTY_BIND_HOST = "ldap.host";
+    public static final String PROPERTY_BIND_PORT = "ldap.port";
+    public static final String PROPERTY_LDIF_FILE = "ldap.ldif";
+    public static final String PROPERTY_SASL_PRINCIPAL = "ldap.saslPrincipal";
+    public static final String PROPERTY_DSF = "ldap.dsf";
+
+    private static final String DEFAULT_BASE_DN = "dc=keycloak,dc=org";
+    private static final String DEFAULT_BIND_HOST = "localhost";
+    private static final String DEFAULT_BIND_PORT = "10389";
+    private static final String DEFAULT_LDIF_FILE = "classpath:ldap/default-users.ldif";
+
+    public static final String DSF_INMEMORY = "mem";
+    public static final String DSF_FILE = "file";
+    public static final String DEFAULT_DSF = DSF_FILE;
+
+    protected Properties defaultProperties;
+
+    protected String baseDN;
+    protected String bindHost;
+    protected int bindPort;
+    protected String ldifFile;
+    protected String ldapSaslPrincipal;
+    protected String directoryServiceFactory;
+
+    protected DirectoryService directoryService;
+    protected LdapServer ldapServer;
+
+
+    public static void main(String[] args) throws Exception {
+        Properties defaultProperties = new Properties();
+        defaultProperties.put(PROPERTY_DSF, DSF_FILE);
+
+        execute(args, defaultProperties);
+    }
+
+    public static void execute(String[] args, Properties defaultProperties) throws Exception {
+        final LDAPEmbeddedServer ldapEmbeddedServer = new LDAPEmbeddedServer(defaultProperties);
+        ldapEmbeddedServer.init();
+        ldapEmbeddedServer.start();
+
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+
+            @Override
+            public void run() {
+                try {
+                    ldapEmbeddedServer.stop();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+
+        });
+    }
+
+    public LDAPEmbeddedServer(Properties defaultProperties) {
+        this.defaultProperties = defaultProperties;
+
+        this.baseDN = readProperty(PROPERTY_BASE_DN, DEFAULT_BASE_DN);
+        this.bindHost = readProperty(PROPERTY_BIND_HOST, DEFAULT_BIND_HOST);
+        String bindPort = readProperty(PROPERTY_BIND_PORT, DEFAULT_BIND_PORT);
+        this.bindPort = Integer.parseInt(bindPort);
+        this.ldifFile = readProperty(PROPERTY_LDIF_FILE, DEFAULT_LDIF_FILE);
+        this.ldapSaslPrincipal = readProperty(PROPERTY_SASL_PRINCIPAL, null);
+        this.directoryServiceFactory = readProperty(PROPERTY_DSF, DEFAULT_DSF);
+    }
+
+    protected String readProperty(String propertyName, String defaultValue) {
+        String value = System.getProperty(propertyName);
+
+        if (value == null || value.isEmpty()) {
+            value = (String) this.defaultProperties.get(propertyName);
+        }
+
+        if (value == null || value.isEmpty()) {
+            value = defaultValue;
+        }
+
+        return value;
+    }
+
+
+    public void init() throws Exception {
+        log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort +
+                ", ldapSaslPrincipal=" + ldapSaslPrincipal + ", directoryServiceFactory=" + directoryServiceFactory + ", ldif=" + ldifFile);
+
+        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];
+        dcName = dcName.substring(dcName.indexOf("=") + 1);
+
+        DirectoryServiceFactory dsf;
+        if (this.directoryServiceFactory.equals(DSF_INMEMORY)) {
+            dsf = new InMemoryDirectoryServiceFactory();
+        } else if (this.directoryServiceFactory.equals(DSF_FILE)) {
+            dsf = new FileDirectoryServiceFactory();
+        } else {
+            throw new IllegalStateException("Unknown value of directoryServiceFactory: " + this.directoryServiceFactory);
+        }
+
+        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";
+        importLdifContent(service, entryLdif);
+
+        return service;
+    }
+
+
+    protected LdapServer createLdapServer() {
+        LdapServer ldapServer = new LdapServer();
+
+        ldapServer.setServiceName("DefaultLdapServer");
+        ldapServer.setSearchBaseDn(this.baseDN);
+
+        // 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);
+
+        return ldapServer;
+    }
+
+
+    private void importLdif() throws Exception {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("hostname", this.bindHost);
+        if (this.ldapSaslPrincipal != null) {
+            map.put("ldapSaslPrincipal", this.ldapSaslPrincipal);
+        }
+
+        // Find LDIF file on filesystem or classpath ( if it's like classpath:ldap/users.ldif )
+        InputStream is = FindFile.findFile(ldifFile);
+        if (is == null) {
+            throw new IllegalStateException("LDIF file not found on classpath or on file system. Location was: " + ldifFile);
+        }
+
+        final String ldifContent = StrSubstitutor.replace(StreamUtil.readString(is), map);
+        log.info("Content of LDIF: " + ldifContent);
+        final SchemaManager schemaManager = directoryService.getSchemaManager();
+
+        importLdifContent(directoryService, ldifContent);
+    }
+
+    private static void importLdifContent(DirectoryService directoryService, String ldifContent) throws Exception {
+        LdifReader ldifReader = new LdifReader(IOUtils.toInputStream(ldifContent));
+
+        try {
+            for (LdifEntry ldifEntry : ldifReader) {
+                try {
+                    directoryService.getAdminSession().add(new DefaultEntry(directoryService.getSchemaManager(), ldifEntry.getEntry()));
+                } catch (LdapEntryAlreadyExistsException ignore) {
+                    log.info("Entry " + ldifEntry.getDn() + " already exists. Ignoring");
+                }
+            }
+        } finally {
+            ldifReader.close();
+        }
+    }
+
+
+    public void stop() throws Exception {
+        stopLdapServer();
+        shutdownDirectoryService();
+    }
+
+
+    protected void stopLdapServer() {
+        log.info("Stopping LDAP server.");
+        ldapServer.stop();
+    }
+
+
+    protected void shutdownDirectoryService() throws Exception {
+        log.info("Stopping Directory service.");
+        directoryService.shutdown();
+
+        // Delete workfiles just for 'inmemory' implementation used in tests. Normally we want LDAP data to persist
+        File instanceDir = directoryService.getInstanceLayout().getInstanceDirectory();
+        if (this.directoryServiceFactory.equals(DSF_INMEMORY)) {
+            log.infof("Removing Directory service workfiles: %s", instanceDir.getAbsolutePath());
+            FileUtils.deleteDirectory(instanceDir);
+        } else {
+            log.info("Working LDAP directory not deleted. Delete it manually if you want to start with fresh LDAP data. Directory location: " + instanceDir.getAbsolutePath());
+        }
+    }
+
+}
diff --git a/util/embedded-ldap/src/main/resources/kerberos/default-users.ldif b/util/embedded-ldap/src/main/resources/kerberos/default-users.ldif
new file mode 100644
index 0000000..fd9936c
--- /dev/null
+++ b/util/embedded-ldap/src/main/resources/kerberos/default-users.ldif
@@ -0,0 +1,90 @@
+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: ${ldapSaslPrincipal}
+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
+sn: Nelson
+mail: hnelson@keycloak.org
+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
+sn: Duke
+mail: jduke@keycloak.org
+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/util/embedded-ldap/src/main/resources/ldap/default-users.ldif b/util/embedded-ldap/src/main/resources/ldap/default-users.ldif
new file mode 100644
index 0000000..4d6d87e
--- /dev/null
+++ b/util/embedded-ldap/src/main/resources/ldap/default-users.ldif
@@ -0,0 +1,47 @@
+dn: dc=keycloak,dc=org
+objectclass: dcObject
+objectclass: organization
+o: Keycloak
+dc: Keycloak
+
+dn: ou=People,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: People
+
+dn: ou=RealmRoles,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: RealmRoles
+
+dn: ou=FinanceRoles,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: FinanceRoles
+
+dn: uid=jbrown,ou=People,dc=keycloak,dc=org
+objectclass: top
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+uid: jbrown
+cn: James
+sn: Brown
+mail: jbrown@keycloak.org
+postalCode: 88441
+userPassword: password
+
+dn: uid=bwilson,ou=People,dc=keycloak,dc=org
+objectclass: top
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+uid: bwilson
+cn: Bruce
+sn: Wilson
+sn: Schneider
+mail: bwilson@keycloak.org
+postalCode: 88441
+postalCode: 77332
+street: Elm 5
+userPassword: password
diff --git a/util/embedded-ldap/src/main/resources/log4j.properties b/util/embedded-ldap/src/main/resources/log4j.properties
new file mode 100644
index 0000000..37d89b1
--- /dev/null
+++ b/util/embedded-ldap/src/main/resources/log4j.properties
@@ -0,0 +1,9 @@
+log4j.rootLogger=info, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
+
+log4j.logger.org.keycloak=info
+log4j.logger.org.apache.directory.api=warn
+log4j.logger.org.apache.directory.server.core=warn
\ No newline at end of file

util/pom.xml 22(+22 -0)

diff --git a/util/pom.xml b/util/pom.xml
new file mode 100644
index 0000000..3026307
--- /dev/null
+++ b/util/pom.xml
@@ -0,0 +1,22 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.4.0.Final-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <name>Keycloak Util Parent</name>
+    <description/>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.keycloak</groupId>
+    <artifactId>keycloak-util-parent</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>embedded-ldap</module>
+    </modules>
+
+</project>
\ No newline at end of file