keycloak-aplcache

ldap port admin console

11/8/2016 3:30:20 PM

Details

diff --git a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
index 2ffc16f..2401b49 100755
--- a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
+++ b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
@@ -19,6 +19,7 @@ package org.keycloak.storage.ldap;
 
 import org.jboss.logging.Logger;
 import org.keycloak.Config;
+import org.keycloak.common.constants.KerberosConstants;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.component.ComponentValidationException;
 import org.keycloak.federation.kerberos.CommonKerberosConfig;
@@ -33,6 +34,8 @@ import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderConfigurationBuilder;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.UserStorageProviderFactory;
 import org.keycloak.storage.UserStorageProviderModel;
@@ -83,10 +86,115 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
 
 
     private static final Logger logger = Logger.getLogger(LDAPStorageProviderFactory.class);
-    public static final String PROVIDER_NAME = "ldap2";//LDAPConstants.LDAP_PROVIDER;
+    public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER;
 
     private LDAPIdentityStoreRegistry ldapStoreRegistry;
 
+    protected static final List<ProviderConfigProperty> configProperties;
+
+    static {
+        configProperties = getConfigProps(null);
+    }
+
+    private static List<ProviderConfigProperty> getConfigProps(ComponentModel parent) {
+        boolean readOnly = false;
+        if (parent != null) {
+            LDAPConfig config = new LDAPConfig(parent.getConfig());
+            readOnly = config.getEditMode() != LDAPStorageProviderFactory.EditMode.WRITABLE;
+        }
+
+
+        return ProviderConfigurationBuilder.create()
+                .property().name(LDAPConstants.EDIT_MODE)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(LDAPConstants.SYNC_REGISTRATIONS)
+                .type(ProviderConfigProperty.BOOLEAN_TYPE)
+                .defaultValue("false")
+                .add()
+                .property().name(LDAPConstants.VENDOR)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(LDAPConstants.USERNAME_LDAP_ATTRIBUTE)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(LDAPConstants.RDN_LDAP_ATTRIBUTE)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(LDAPConstants.UUID_LDAP_ATTRIBUTE)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(LDAPConstants.USER_OBJECT_CLASSES)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(LDAPConstants.CONNECTION_URL)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(LDAPConstants.USERS_DN)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(LDAPConstants.AUTH_TYPE)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .defaultValue("simple")
+                .add()
+                .property().name(LDAPConstants.BIND_DN)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(LDAPConstants.BIND_CREDENTIAL)
+                .type(ProviderConfigProperty.PASSWORD)
+                .secret(true)
+                .add()
+                .property().name(LDAPConstants.CUSTOM_USER_SEARCH_FILTER)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(LDAPConstants.SEARCH_SCOPE)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .defaultValue("1")
+                .add()
+                .property().name(LDAPConstants.USE_TRUSTSTORE_SPI)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .defaultValue("ldapsOnly")
+                .add()
+                .property().name(LDAPConstants.CONNECTION_POOLING)
+                .type(ProviderConfigProperty.BOOLEAN_TYPE)
+                .defaultValue("true")
+                .add()
+                .property().name(LDAPConstants.PAGINATION)
+                .type(ProviderConfigProperty.BOOLEAN_TYPE)
+                .defaultValue("true")
+                .add()
+                .property().name(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION)
+                .type(ProviderConfigProperty.BOOLEAN_TYPE)
+                .defaultValue("false")
+                .add()
+                .property().name(KerberosConstants.SERVER_PRINCIPAL)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(KerberosConstants.KEYTAB)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(KerberosConstants.KERBEROS_REALM)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .property().name(KerberosConstants.DEBUG)
+                .type(ProviderConfigProperty.BOOLEAN_TYPE)
+                .defaultValue("false")
+                .add()
+                .property().name(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION)
+                .type(ProviderConfigProperty.BOOLEAN_TYPE)
+                .defaultValue("false")
+                .add()
+                .property().name(KerberosConstants.SERVER_PRINCIPAL)
+                .type(ProviderConfigProperty.STRING_TYPE)
+                .add()
+                .build();
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
     @Override
     public LDAPStorageProvider create(KeycloakSession session, ComponentModel model) {
         LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
diff --git a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java
index bacac30..a6ba2d2 100755
--- a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java
+++ b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java
@@ -27,7 +27,9 @@ import org.keycloak.provider.ProviderConfigProperty;
 import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation;
 import org.keycloak.storage.ldap.LDAPStorageProvider;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -60,8 +62,12 @@ public abstract class AbstractLDAPStorageMapperFactory implements LDAPStorageMap
     }
 
     @Override
-    public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
-        return new UserFederationMapperSyncConfigRepresentation(false, null, false, null);
+    public Map<String, Object> getTypeMetadata() {
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("fedToKeycloakSyncSupported", false);
+        metadata.put("keycloakToFedSyncSupported", false);
+
+        return metadata;
     }
 
     @Override
diff --git a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperFactory.java b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperFactory.java
index 3b3f58c..d5e8318 100644
--- a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperFactory.java
+++ b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperFactory.java
@@ -44,8 +44,6 @@ public interface LDAPStorageMapperFactory<T extends LDAPStorageMapper> extends S
      */
     T create(KeycloakSession session, ComponentModel model);
 
-    UserFederationMapperSyncConfigRepresentation getSyncConfig();
-
     /**
      * This is the name of the provider and will be showed in the admin console as an option.
      *
diff --git a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java
index cad730e..b3baf50 100644
--- a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java
+++ b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java
@@ -176,10 +176,17 @@ public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFact
     }
 
     @Override
-    public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
-        return new UserFederationMapperSyncConfigRepresentation(true, "sync-ldap-groups-to-keycloak", true, "sync-keycloak-groups-to-ldap");
+    public Map<String, Object> getTypeMetadata() {
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("fedToKeycloakSyncSupported", true);
+        metadata.put("fedToKeycloakSyncMessage", "sync-ldap-groups-to-keycloak");
+        metadata.put("keycloakToFedSyncSupported", true);
+        metadata.put("keycloakToFedSyncMessage", "sync-keycloak-groups-to-ldap");
+
+        return metadata;
     }
 
+
     @Override
     public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
         checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", config);
diff --git a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java
index 7d9a52f..e2da595 100644
--- a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java
+++ b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java
@@ -168,10 +168,17 @@ public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFacto
     }
 
     @Override
-    public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
-        return new UserFederationMapperSyncConfigRepresentation(true, "sync-ldap-roles-to-keycloak", true, "sync-keycloak-roles-to-ldap");
+    public Map<String, Object> getTypeMetadata() {
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("fedToKeycloakSyncSupported", true);
+        metadata.put("fedToKeycloakSyncMessage", "sync-ldap-roles-to-keycloak");
+        metadata.put("keycloakToFedSyncSupported", true);
+        metadata.put("keycloakToFedSyncMessage", "sync-keycloak-roles-to-ldap");
+
+        return metadata;
     }
 
+
     @Override
     public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
         checkMandatoryConfigAttribute(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", config);
diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
index 75c3fb1..d519286 100644
--- a/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
+++ b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
@@ -25,6 +25,7 @@ import org.keycloak.provider.ProviderFactory;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -55,5 +56,17 @@ public interface ComponentFactory<CreatedType, ProviderType extends Provider> ex
         return Collections.EMPTY_LIST;
     }
 
+    /**
+     * This is metadata about this component type.  Its really configuration information about the component type and not
+     * an individual instance
+     *
+     * @return
+     */
+    default
+    Map<String, Object> getTypeMetadata() {
+        return Collections.EMPTY_MAP;
+
+    }
+
 
 }
diff --git a/server-spi/src/main/java/org/keycloak/component/SubComponentFactory.java b/server-spi/src/main/java/org/keycloak/component/SubComponentFactory.java
index 3f5295d..56c012d 100644
--- a/server-spi/src/main/java/org/keycloak/component/SubComponentFactory.java
+++ b/server-spi/src/main/java/org/keycloak/component/SubComponentFactory.java
@@ -25,6 +25,7 @@ import org.keycloak.provider.ProviderFactory;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Useful when you want to describe config properties that are effected by the parent ComponentModel
@@ -37,4 +38,16 @@ public interface SubComponentFactory<CreatedType, ProviderType extends Provider>
     List<ProviderConfigProperty> getConfigProperties(RealmModel realm, ComponentModel parent) {
         return getConfigProperties();
     }
+
+    /**
+     * This is metadata about this component type.  Its really configuration information about the component type and not
+     * an individual instance
+     *
+     * @return
+     */
+    default Map<String, Object> getTypeMetadata(RealmModel realm, ComponentModel parent) {
+        return getTypeMetadata();
+
+    }
+
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java
index ba47784..18e291a 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java
@@ -26,10 +26,13 @@ import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
 import org.keycloak.provider.ProviderConfigProperty;
 import org.keycloak.provider.ProviderConfigurationBuilder;
+import org.keycloak.storage.user.ImportSynchronization;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -108,4 +111,14 @@ public interface UserStorageProviderFactory<T extends UserStorageProvider> exten
     List<ProviderConfigProperty> getCommonProviderConfigProperties() {
         return UserStorageProviderSpi.commonConfig();
     }
+
+    @Override
+    default
+    Map<String, Object> getTypeMetadata() {
+        Map<String, Object> metadata = new HashMap<>();
+        if (this instanceof ImportSynchronization) {
+            metadata.put("synchronizable", true);
+        }
+        return metadata;
+    }
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java
index 625adeb..06bc256 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java
@@ -64,6 +64,8 @@ public class UserStorageProviderSpi implements Spi {
                 .property()
                 .name("lastSync").type(ProviderConfigProperty.STRING_TYPE).add()
                 .property()
+                .name("batchSizeForSync").type(ProviderConfigProperty.STRING_TYPE).add()
+                .property()
                 .name("importEnabled").type(ProviderConfigProperty.BOOLEAN_TYPE).add()
                 .property()
                 .name("cachePolicy").type(ProviderConfigProperty.STRING_TYPE).add()
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java
index bbd2edb..c2bba87 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java
@@ -20,6 +20,7 @@ import org.jboss.logging.Logger;
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.NotFoundException;
 import org.keycloak.common.ClientConnection;
+import org.keycloak.component.ComponentFactory;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.component.ComponentValidationException;
 import org.keycloak.component.SubComponentFactory;
@@ -59,6 +60,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 import java.util.stream.Collectors;
 
@@ -207,7 +209,7 @@ public class ComponentResource {
     @Path("{id}/sub-component-config")
     @Produces(MediaType.APPLICATION_JSON)
     @NoCache
-    public List<ConfigPropertyRepresentation> getSubcomponentConfig(@PathParam("id") String id, @QueryParam("type") String providerType, @QueryParam("id") String providerId) {
+    public ComponentTypeRepresentation getSubcomponentConfig(@PathParam("id") String id, @QueryParam("type") String providerType, @QueryParam("id") String providerId) {
         auth.requireView();
         ComponentModel parent = realm.getComponent(id);
         if (parent == null) {
@@ -224,9 +226,29 @@ public class ComponentResource {
             throw new NotFoundException("Could not find subcomponent factory");
 
         }
-        if (!(factory instanceof SubComponentFactory)) return Collections.EMPTY_LIST;
-        List<ProviderConfigProperty> props = ((SubComponentFactory)factory).getConfigProperties(realm, parent);
-        return ModelToRepresentation.toRepresentation(props);
+        if (!(factory instanceof ComponentFactory)) {
+            throw new NotFoundException("Not a component factory");
+
+        }
+        ComponentFactory componentFactory = (ComponentFactory)factory;
+        ComponentTypeRepresentation rep = new ComponentTypeRepresentation();
+        rep.setId(providerId);
+        rep.setHelpText(componentFactory.getHelpText());
+        List<ProviderConfigProperty> props = null;
+        Map<String, Object> metadata = null;
+        if (factory instanceof SubComponentFactory) {
+            props = ((SubComponentFactory)factory).getConfigProperties(realm, parent);
+            metadata = ((SubComponentFactory)factory).getTypeMetadata(realm, parent);
+
+        } else {
+            props = componentFactory.getConfigProperties();
+            metadata = componentFactory.getTypeMetadata();
+        }
+
+        List<ConfigPropertyRepresentation> propReps =  ModelToRepresentation.toRepresentation(props);
+        rep.setProperties(propReps);
+        rep.setMetadata(metadata);
+        return rep;
     }
 
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
index 8120e7f..8017b04 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
@@ -20,6 +20,7 @@ package org.keycloak.services.resources.admin.info;
 import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.broker.provider.IdentityProviderFactory;
 import org.keycloak.broker.social.SocialIdentityProvider;
+import org.keycloak.component.ComponentFactory;
 import org.keycloak.events.EventType;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
@@ -136,8 +137,8 @@ public class ServerInfoAdminResource {
                         List<ProviderConfigProperty> configProperties = configured.getConfigProperties();
                         if (configProperties == null) configProperties = Collections.EMPTY_LIST;
                         rep.setProperties(ModelToRepresentation.toRepresentation(configProperties));
-                        if (pi instanceof ImportSynchronization) {
-                            rep.getMetadata().put("synchronizable", true);
+                        if (pi instanceof ComponentFactory) {
+                            rep.setMetadata(((ComponentFactory)pi).getTypeMetadata());
                         }
                         List<ComponentTypeRepresentation> reps = info.getComponentTypes().get(spi.getProviderClass().getName());
                         if (reps == null) {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js
index f936fcd..223c94e 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -1469,6 +1469,26 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RealmSessionStatsCtrl'
         })
+        .when('/create/user-storage/:realm/providers/ldap', {
+            templateUrl : resourceUrl + '/partials/user-storage-ldap.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                instance : function() {
+                    return {
+
+                    };
+                },
+                providerId : function($route) {
+                    return $route.current.params.provider;
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
+                }
+            },
+            controller : 'LDAPUserStorageCtrl'
+        })
         .when('/create/user-storage/:realm/providers/:provider', {
             templateUrl : resourceUrl + '/partials/user-storage-generic.html',
             resolve : {
@@ -1489,6 +1509,24 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'GenericUserStorageCtrl'
         })
+        .when('/realms/:realm/user-storage/providers/ldap/:componentId', {
+            templateUrl : resourceUrl + '/partials/user-storage-ldap.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                instance : function(ComponentLoader) {
+                    return ComponentLoader();
+                },
+                providerId : function($route) {
+                    return $route.current.params.provider;
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
+                }
+            },
+            controller : 'LDAPUserStorageCtrl'
+        })
         .when('/realms/:realm/user-storage/providers/:provider/:componentId', {
             templateUrl : resourceUrl + '/partials/user-storage-generic.html',
             resolve : {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index cd72b1e..c55534d 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -627,12 +627,14 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real
     for (var i = 0; i < $scope.providers.length; i++) {
         $scope.providers[i].isUserFederationProvider = false;
     }
+    /*
     UserFederationProviders.query({realm: realm.realm}, function(data) {
         for (var i = 0; i < data.length; i++) {
             data[i].isUserFederationProvider = true;
             $scope.providers.push(data[i]);
         }
     });
+    */
 
     $scope.addProvider = function(provider) {
         console.log('Add provider: ' + provider.id);
@@ -1553,4 +1555,321 @@ module.controller('UserGroupMembershipCtrl', function($scope, $route, realm, gro
 
 });
 
+module.controller('LDAPUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm,
+                                                     serverInfo, instance, Components, UserStorageSync, RealmLDAPConnectionTester) {
+    console.log('LDAPUserStorageCtrl');
+    var providerId = 'ldap';
+    console.log('providerId: ' + providerId);
+    $scope.create = !instance.providerId;
+    console.log('create: ' + $scope.create);
+    var providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider'];
+    console.log('providers length ' + providers.length);
+    var providerFactory = null;
+    for (var i = 0; i < providers.length; i++) {
+        var p = providers[i];
+        console.log('provider: ' + p.id);
+        if (p.id == providerId) {
+            $scope.providerFactory = p;
+            providerFactory = p;
+            break;
+        }
+
+    }
+
+    $scope.provider = instance;
+    $scope.showSync = false;
+
+    $scope.ldapVendors = [
+        { "id": "ad", "name": "Active Directory" },
+        { "id": "rhds", "name": "Red Hat Directory Server" },
+        { "id": "tivoli", "name": "Tivoli" },
+        { "id": "edirectory", "name": "Novell eDirectory" },
+        { "id": "other", "name": "Other" }
+    ];
+
+    $scope.authTypes = [
+        { "id": "none", "name": "none" },
+        { "id": "simple", "name": "simple" }
+    ];
+
+    $scope.searchScopes = [
+        { "id": "1", "name": "One Level" },
+        { "id": "2", "name": "Subtree" }
+    ];
+
+    $scope.useTruststoreOptions = [
+        { "id": "always", "name": "Always" },
+        { "id": "ldapsOnly", "name": "Only for ldaps" },
+        { "id": "never", "name": "Never" }
+    ];
+
+    var DEFAULT_BATCH_SIZE = "1000";
+
+
+    console.log("providerFactory: " + providerFactory.id);
+
+    function initUserStorageSettings() {
+        if ($scope.create) {
+            instance.name = 'ldap';
+            instance.providerId = 'ldap';
+            instance.providerType = 'org.keycloak.storage.UserStorageProvider';
+            instance.parentId = realm.id;
+            instance.config = {
+
+            };
+            instance.config['priority'] = ["0"];
+
+            $scope.fullSyncEnabled = false;
+            $scope.changedSyncEnabled = false;
+            instance.config['fullSyncPeriod'] = ['-1'];
+            instance.config['changedSyncPeriod'] = ['-1'];
+            instance.config['cachePolicy'] = ['DEFAULT'];
+            instance.config['evictionDay'] = [''];
+            instance.config['evictionHour'] = [''];
+            instance.config['evictionMinute'] = [''];
+            instance.config['maxLifespan'] = [''];
+            instance.config['batchSizeForSync'] = [DEFAULT_BATCH_SIZE];
+
+            if (providerFactory.properties) {
+
+                for (var i = 0; i < providerFactory.properties.length; i++) {
+                    var configProperty = providerFactory.properties[i];
+                    if (configProperty.defaultValue) {
+                        instance.config[configProperty.name] = [configProperty.defaultValue];
+                    } else {
+                        instance.config[configProperty.name] = [''];
+                    }
+
+                }
+            }
+
+
+        } else {
+            $scope.fullSyncEnabled = (instance.config['fullSyncPeriod'] && instance.config['fullSyncPeriod'][0] > 0);
+            $scope.changedSyncEnabled = (instance.config['changedSyncPeriod'] && instance.config['changedSyncPeriod'][0]> 0);
+            if (!instance.config['fullSyncPeriod']) {
+                console.log('setting to -1');
+                instance.config['fullSyncPeriod'] = ['-1'];
+
+            }
+            if (!instance.config['changedSyncPeriod']) {
+                console.log('setting to -1');
+                instance.config['changedSyncPeriod'] = ['-1'];
+
+            }
+            if (!instance.config['cachePolicy']) {
+                instance.config['cachePolicy'] = ['DEFAULT'];
+
+            }
+            if (!instance.config['evictionDay']) {
+                instance.config['evictionDay'] = [''];
+
+            }
+            if (!instance.config['evictionHour']) {
+                instance.config['evictionHour'] = [''];
+
+            }
+            if (!instance.config['evictionMinute']) {
+                instance.config['evictionMinute'] = [''];
+
+            }
+            if (!instance.config['maxLifespan']) {
+                instance.config['maxLifespan'] = [''];
+
+            }
+            if (!instance.config['priority']) {
+                instance.config['priority'] = ['0'];
+            }
+
+            if (providerFactory.properties) {
+
+                for (var i = 0; i < providerFactory.properties.length; i++) {
+                    var configProperty = providerFactory.properties[i];
+                    if (!instance.config[configProperty.name]) {
+                        if (configProperty.defaultValue) {
+                            instance.config[configProperty.name] = [configProperty.defaultValue];
+                        } else {
+                            instance.config[configProperty.name] = [''];
+                        }
+                    }
+
+                }
+            }
+
+            for (var i=0 ; i<$scope.ldapVendors.length ; i++) {
+                if ($scope.ldapVendors[i].id === instance.config['vendor'][0]) {
+                    $scope.vendorName = $scope.ldapVendors[i].name;
+                }
+            };
+
+
+
+        }
+        if (instance.config && instance.config['importEnabled']) {
+            $scope.showSync = instance.config['importEnabled'][0] == 'true';
+        } else {
+            $scope.showSync = true;
+        }
+
+        $scope.changed = false;
+    }
+
+    initUserStorageSettings();
+    $scope.instance = angular.copy(instance);
+    $scope.realm = realm;
+
+    $scope.$watch('instance', function() {
+        if (!angular.equals($scope.instance, instance)) {
+            $scope.changed = true;
+        }
+
+        if (!angular.equals($scope.instance.config['vendor'][0], $scope.lastVendor)) {
+            console.log("LDAP vendor changed");
+            $scope.lastVendor = $scope.instance.config['vendor'][0];
+
+            if ($scope.lastVendor === "ad") {
+                $scope.instance.config['usernameLDAPAttribute'][0] = "cn";
+                $scope.instance.config['userObjectClasses'][0] = "person, organizationalPerson, user";
+            } else {
+                $scope.instance.config['usernameLDAPAttribute'][0] = "uid";
+                $scope.instance.config['userObjectClasses'][0] = "inetOrgPerson, organizationalPerson";
+            }
+
+            $scope.instance.config['rdnLDAPAttribute'][0] = $scope.instance.config['usernameLDAPAttribute'][0];
+
+            var vendorToUUID = {
+                rhds: "nsuniqueid",
+                tivoli: "uniqueidentifier",
+                edirectory: "guid",
+                ad: "objectGUID",
+                other: "entryUUID"
+            };
+            $scope.instance.config['uuidLDAPAttribute'][0] = vendorToUUID[$scope.lastVendor];
+        }
+
+
+    }, true);
+
+    $scope.$watch('fullSyncEnabled', function(newVal, oldVal) {
+        if (oldVal == newVal) {
+            return;
+        }
+
+        $scope.instance.config['fullSyncPeriod'][0] = $scope.fullSyncEnabled ? "604800" : "-1";
+        $scope.changed = true;
+    });
+
+    $scope.$watch('changedSyncEnabled', function(newVal, oldVal) {
+        if (oldVal == newVal) {
+            return;
+        }
+
+        $scope.instance.config['changedSyncPeriod'][0] = $scope.changedSyncEnabled ? "86400" : "-1";
+        $scope.changed = true;
+    });
+
+
+    $scope.save = function() {
+        $scope.changed = false;
+        if (!parseInt($scope.instance.config['batchSizeForSync'[0]])) {
+            $scope.instance.config['batchSizeForSync'][0] = DEFAULT_BATCH_SIZE;
+        } else {
+            $scope.instance.config['batchSizeForSync'][0] = parseInt($scope.instance.config.batchSizeForSync).toString();
+        }
+
+        if ($scope.create) {
+            Components.save({realm: realm.realm}, $scope.instance,  function (data, headers) {
+                var l = headers().location;
+                var id = l.substring(l.lastIndexOf("/") + 1);
+
+                $location.url("/realms/" + realm.realm + "/user-storage/providers/" + $scope.instance.providerId + "/" + id);
+                Notifications.success("The provider has been created.");
+            }, function (errorResponse) {
+                if (errorResponse.data && errorResponse.data['error_description']) {
+                    Notifications.error(errorResponse.data['error_description']);
+                }
+            });
+        } else {
+            Components.update({realm: realm.realm,
+                    componentId: instance.id
+                },
+                $scope.instance,  function () {
+                    $route.reload();
+                    Notifications.success("The provider has been updated.");
+                }, function (errorResponse) {
+                    if (errorResponse.data && errorResponse.data['error_description']) {
+                        Notifications.error(errorResponse.data['error_description']);
+                    }
+                });
+        }
+    };
+
+    $scope.reset = function() {
+        initUserStorageSettings();
+        $scope.instance = angular.copy(instance);
+    };
+
+    $scope.cancel = function() {
+        if ($scope.create) {
+            $location.url("/realms/" + realm.realm + "/user-storage");
+        } else {
+            $route.reload();
+        }
+    };
+
+    $scope.triggerFullSync = function() {
+        console.log('GenericCtrl: triggerFullSync');
+        triggerSync('triggerFullSync');
+    }
+
+    $scope.triggerChangedUsersSync = function() {
+        console.log('GenericCtrl: triggerChangedUsersSync');
+        triggerSync('triggerChangedUsersSync');
+    }
+
+    function triggerSync(action) {
+        UserStorageSync.save({ action: action, realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) {
+            $route.reload();
+            Notifications.success("Sync of users finished successfully. " + syncResult.status);
+        }, function() {
+            $route.reload();
+            Notifications.error("Error during sync of users");
+        });
+    }
+
+    var initConnectionTest = function(testAction, ldapConfig) {
+        return {
+            action: testAction,
+            realm: $scope.realm.realm,
+            connectionUrl: ldapConfig.connectionUrl,
+            bindDn: ldapConfig.bindDn,
+            bindCredential: ldapConfig.bindCredential,
+            useTruststoreSpi: ldapConfig.useTruststoreSpi
+        };
+    };
+
+    $scope.testConnection = function() {
+        console.log('LDAPCtrl: testConnection');
+        RealmLDAPConnectionTester.get(initConnectionTest("testConnection", $scope.instance.config), function() {
+            Notifications.success("LDAP connection successful.");
+        }, function() {
+            Notifications.error("Error when trying to connect to LDAP. See server.log for details.");
+        });
+    }
+
+    $scope.testAuthentication = function() {
+        console.log('LDAPCtrl: testAuthentication');
+        RealmLDAPConnectionTester.get(initConnectionTest("testAuthentication", $scope.instance.config), function() {
+            Notifications.success("LDAP authentication successful.");
+        }, function() {
+            Notifications.error("LDAP authentication failed. See server.log for details");
+        });
+    }
+
+
+
+});
+
+
+
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html
new file mode 100755
index 0000000..d81541f
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html
@@ -0,0 +1,449 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/user-federation">{{:: 'user-federation' | translate}}</a></li>
+        <li data-ng-hide="create">{{instance.name|capitalize}}</li>
+        <li data-ng-show="create">{{:: 'add-user-storage-provider' | translate}}</li>
+    </ol>
+
+    <kc-tabs-user-storage></kc-tabs-user-storage>
+
+    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
+        <input type="text" readonly value="this is not a login form" style="display: none;">
+        <input type="password" readonly value="this is not a login form" style="display: none;">
+
+        <fieldset>
+            <legend><span class="text">{{:: 'required-settings' | translate}}</span></legend>
+            <div class="form-group clearfix" data-ng-show="!create">
+                <label class="col-md-2 control-label" for="providerId">{{:: 'provider-id' | translate}} </label>
+                <div class="col-md-6">
+                    <input class="form-control" id="providerId" type="text" ng-model="instance.id" readonly>
+                </div>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="consoleDisplayName">{{:: 'console-display-name' | translate}} </label>
+                <div class="col-md-6">
+                    <input class="form-control" id="consoleDisplayName" type="text" ng-model="instance.name" placeholder="{{:: 'defaults-to-id' | translate}}">
+                </div>
+                <kc-tooltip>{{:: 'console-display-name.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="priority">{{:: 'priority' | translate}} </label>
+                <div class="col-md-6">
+                    <input class="form-control" id="priority" type="text" ng-model="instance.config['priority'][0]">
+                </div>
+                <kc-tooltip>{{:: 'priority.tooltip' | translate}}</kc-tooltip>
+            </div>
+
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="editMode">{{:: 'edit-mode' | translate}}</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="editMode"
+                                ng-model="instance.config['editMode'][0]">
+                            <option>READ_ONLY</option>
+                            <option>WRITABLE</option>
+                            <option>UNSYNCED</option>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'ldap.edit-mode.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix block">
+                <label class="col-md-2 control-label" for="syncRegistrations">{{:: 'sync-registrations' | translate}}</label>
+                <div class="col-md-6">
+                    <input ng-model="instance.config['syncRegistrations'][0]" name="syncRegistrations" id="syncRegistrations" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+                </div>
+                <kc-tooltip>{{:: 'ldap.sync-registrations.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="vendor"><span class="required">*</span> {{:: 'vendor' | translate}}</label>
+                <div class="col-md-6">
+                    <div data-ng-show="create">
+                        <select class="form-control" id="vendor"
+                                ng-model="instance.config['vendor'][0]"
+                                ng-options="vendor.id as vendor.name for vendor in ldapVendors"
+                                required>
+                        </select>
+                    </div>
+                    <div data-ng-show="!create">
+                        <input class="form-control" id="vendor-ro" type="text" ng-model="vendorName" readonly>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'ldap.vendor.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="usernameLDAPAttribute"><span class="required">*</span> {{:: 'username-ldap-attribute' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="usernameLDAPAttribute" type="text" ng-model="instance.config['usernameLDAPAttribute'][0]" placeholder="{{:: 'ldap-attribute-name-for-username' | translate}}" required>
+                </div>
+                <kc-tooltip>{{:: 'username-ldap-attribute.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="rdnLDAPAttribute"><span class="required">*</span> {{:: 'rdn-ldap-attribute' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="rdnLDAPAttribute" type="text" ng-model="instance.config['rdnLDAPAttribute'][0]" placeholder="{{:: 'ldap-attribute-name-for-user-rdn' | translate}}" required>
+                </div>
+                <kc-tooltip>{{:: 'rdn-ldap-attribute.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="uuidLDAPAttribute"><span class="required">*</span> {{:: 'uuid-ldap-attribute' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="uuidLDAPAttribute" type="text" ng-model="instance.config['uuidLDAPAttribute'][0]" placeholder="{{:: 'ldap-attribute-name-for-uuid' | translate}}" required>
+                </div>
+                <kc-tooltip>{{:: 'uuid-ldap-attribute.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="userObjectClasses"><span class="required">*</span> {{:: 'user-object-classes' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="userObjectClasses" type="text" ng-model="instance.config['userObjectClasses'][0]" placeholder="{{:: 'ldap-user-object-classes.placeholder' | translate}}" required>
+                </div>
+                <kc-tooltip>{{:: 'ldap.user-object-classes.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="ldapConnectionUrl"><span class="required">*</span> {{:: 'connection-url' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="ldapConnectionUrl" type="text" ng-model="instance.config['connectionUrl'][0]" placeholder="{{:: 'ldap-connection-url' | translate}}" required>
+                </div>
+                <kc-tooltip>{{:: 'ldap.connection-url.tooltip' | translate}}</kc-tooltip>
+                <div class="col-sm-4" data-ng-show="access.manageRealm">
+                    <a class="btn btn-primary" data-ng-click="testConnection()">{{:: 'test-connection' | translate}}</a>
+                </div>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="ldapUsersDn"><span class="required">*</span> {{:: 'users-dn' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="ldapUsersDn" type="text" ng-model="instance.config['usersDn'][0]" placeholder="{{:: 'ldap-users-dn' | translate}}" required>
+                </div>
+                <kc-tooltip>{{:: 'ldap.users-dn.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="authType"><span class="required">*</span> {{:: 'authentication-type' | translate}}</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="authType"
+                                ng-model="instance.config['authType'][0]"
+                                ng-options="authType.id as authType.name for authType in authTypes"
+                                required>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'ldap.authentication-type.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" data-ng-hide="instance.config['authType'][0] == 'none'">
+                <label class="col-md-2 control-label" for="ldapBindDn"><span class="required">*</span> {{:: 'bind-dn' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="ldapBindDn" type="text" ng-model="instance.config['bindDn'][0]" placeholder="{{:: 'ldap-bind-dn' | translate}}" data-ng-required="instance.config['authType'][0] != 'none'">
+                </div>
+                <kc-tooltip>{{:: 'ldap.bind-dn.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" data-ng-hide="instance.config['authType'][0] == 'none'">
+                <label class="col-md-2 control-label" for="ldapBindCredential"><span class="required">*</span> {{:: 'bind-credential' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="ldapBindCredential" type="password" ng-model="instance.config['bindCredential'][0]" placeholder="{{:: 'ldap-bind-credentials' | translate}}" data-ng-required="instance.config['authType'][0] != 'none'">
+                </div>
+                <kc-tooltip>{{:: 'ldap.bind-credential.tooltip' | translate}}</kc-tooltip>
+                <div class="col-sm-4" data-ng-show="access.manageRealm">
+                    <a class="btn btn-primary" data-ng-click="testAuthentication()">{{:: 'test-authentication' | translate}}</a>
+                </div>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="customUserSearchFilter">{{:: 'custom-user-ldap-filter' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="customUserSearchFilter" type="text" ng-model="instance.config['customUserSearchFilter'][0]" placeholder="{{:: 'ldap-filter' | translate}}">
+                </div>
+                <kc-tooltip>{{:: 'ldap.custom-user-ldap-filter.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="searchScope">{{:: 'search-scope' | translate}}</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="searchScope"
+                                ng-model="instance.config['searchScope'][0]"
+                                ng-options="searchScope.id as searchScope.name for searchScope in searchScopes"
+                                required>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'ldap.search-scope.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="useTruststoreSpi">{{:: 'use-truststore-spi' | translate}}</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="useTruststoreSpi"
+                                ng-model="instance.config['useTruststoreSpi'][0]"
+                                ng-options="useTruststoreSpi.id as useTruststoreSpi.name for useTruststoreSpi in useTruststoreOptions"
+                                required>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'ldap.use-truststore-spi.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="connectionPooling">{{:: 'connection-pooling' | translate}}</label>
+                <div class="col-md-6">
+                    <input ng-model="instance.config['connectionPooling'][0]" name="connectionPooling" id="connectionPooling" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+                </div>
+                <kc-tooltip>{{:: 'ldap.connection-pooling.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="pagination">{{:: 'pagination' | translate}}</label>
+                <div class="col-md-6">
+                    <input ng-model="instance.config['pagination'][0]" name="pagination" id="pagination" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+                </div>
+                <kc-tooltip>{{:: 'ldap.pagination.tooltip' | translate}}</kc-tooltip>
+            </div>
+        </fieldset>
+
+        <fieldset>
+            <legend><span class="text">{{:: 'kerberos-integration' | translate}}</span></legend>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="allowKerberosAuthentication">{{:: 'allow-kerberos-authentication' | translate}} </label>
+                <div class="col-md-6">
+                    <input ng-model="instance.config['allowKerberosAuthentication'][0]" name="allowKerberosAuthentication" id="allowKerberosAuthentication" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+                </div>
+                <kc-tooltip>{{:: 'ldap.allow-kerberos-authentication.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" data-ng-show="instance.config['allowKerberosAuthentication'][0] == 'true'">
+                <label class="col-md-2 control-label" for="kerberosRealm"><span class="required">*</span> {{:: 'kerberos-realm' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="kerberosRealm" type="text" ng-model="instance.config['kerberosRealm'][0]" ng-required="instance.config['allowKerberosAuthentication'][0] == 'true'">
+                </div>
+                <kc-tooltip>{{:: 'kerberos-realm.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" data-ng-show="instance.config['allowKerberosAuthentication'][0] == 'true'">
+                <label class="col-md-2 control-label" for="serverPrincipal"><span class="required">*</span> {{:: 'server-principal' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="serverPrincipal" type="text" ng-model="instance.config['serverPrincipal'][0]" ng-required="instance.config['allowKerberosAuthentication'][0] == 'true'">
+                </div>
+                <kc-tooltip>{{:: 'server-principal.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" data-ng-show="instance.config['allowKerberosAuthentication'][0] == 'true'">
+                <label class="col-md-2 control-label" for="keyTab"><span class="required">*</span> {{:: 'keytab' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="keyTab" type="text" ng-model="instance.config['keyTab'][0]" ng-required="instance.config['allowKerberosAuthentication'][0] == 'true'">
+                </div>
+                <kc-tooltip>{{:: 'keytab.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group" data-ng-show="instance.config['allowKerberosAuthentication'][0] == 'true'">
+                <label class="col-md-2 control-label" for="debug">{{:: 'debug' | translate}} </label>
+                <div class="col-md-6">
+                    <input ng-model="instance.config['debug'][0]" name="debug" id="debug" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+                </div>
+                <kc-tooltip>{{:: 'debug.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group" data-ng-show="instance.config['allowKerberosAuthentication'][0]">
+                <label class="col-md-2 control-label" for="useKerberosForPasswordAuthentication">{{:: 'use-kerberos-for-password-authentication' | translate}} </label>
+                <div class="col-md-6">
+                    <input ng-model="instance.config['useKerberosForPasswordAuthentication'][0]" id="useKerberosForPasswordAuthentication" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+                </div>
+                <kc-tooltip>{{:: 'ldap.use-kerberos-for-password-authentication.tooltip' | translate}}</kc-tooltip>
+            </div>
+        </fieldset>
+
+        <fieldset>
+            <legend><span class="text">{{:: 'sync-settings' | translate}}</span></legend>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="batchSizeForSync">{{:: 'batch-size' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" type="text" ng-model="instance.config['batchSizeForSync'][0]" id="batchSizeForSync" />
+                </div>
+                <kc-tooltip>{{:: 'ldap.batch-size.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="fullSyncEnabled">{{:: 'periodic-full-sync' | translate}}</label>
+                <div class="col-md-6">
+                    <input ng-model="fullSyncEnabled" name="fullSyncEnabled" id="fullSyncEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+                </div>
+                <kc-tooltip>{{:: 'ldap.periodic-full-sync.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" data-ng-show="fullSyncEnabled">
+                <label class="col-md-2 control-label" for="fullSyncPeriod">{{:: 'full-sync-period' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" type="text" ng-model="instance.config['fullSyncPeriod'][0]" id="fullSyncPeriod" />
+                </div>
+                <kc-tooltip>{{:: 'full-sync-period.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="changedSyncEnabled">{{:: 'periodic-changed-users-sync' | translate}}</label>
+                <div class="col-md-6">
+                    <input ng-model="changedSyncEnabled" name="changedSyncEnabled" id="changedSyncEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+                </div>
+                <kc-tooltip>{{:: 'ldap.periodic-changed-users-sync.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" data-ng-show="changedSyncEnabled">
+                <label class="col-md-2 control-label" for="changedSyncPeriod">{{:: 'changed-users-sync-period' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" type="text" ng-model="instance.config['changedSyncPeriod'][0]" id="changedSyncPeriod" />
+                </div>
+                <kc-tooltip>{{:: 'ldap.changed-users-sync-period.tooltip' | translate}}</kc-tooltip>
+            </div>
+        </fieldset>
+
+        <fieldset>
+            <legend><span class="text">{{:: 'user-storage-cache-policy' | translate}}</span></legend>
+            <div class="form-group">
+                <label for="cachePolicy" class="col-md-2 control-label">{{:: 'userStorage.cachePolicy' | translate}}</label>
+                <div class="col-md-2">
+                    <div>
+                        <select id="cachePolicy" ng-model="instance.config['cachePolicy'][0]" class="form-control">
+                            <option value="DEFAULT">{{:: 'userStorage.cachePolicy.option.DEFAULT' | translate}}</option>
+                            <option value="EVICT_DAILY">{{:: 'userStorage.cachePolicy.option.EVICT_DAILY' | translate}}</option>
+                            <option value="EVICT_WEEKLY">{{:: 'userStorage.cachePolicy.option.EVICT_WEEKLY' | translate}}</option>
+                            <option value="MAX_LIFESPAN">{{:: 'userStorage.cachePolicy.option.MAX_LIFESPAN' | translate}}</option>
+                            <option value="NO_CACHE">{{:: 'userStorage.cachePolicy.option.NO_CACHE' | translate}}</option>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'userStorage.cachePolicy.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group" data-ng-show="instance.config['cachePolicy'][0] == 'EVICT_WEEKLY'">
+                <label for="evictionDay" class="col-md-2 control-label">{{:: 'userStorage.evictionDay' | translate}}</label>
+                <div class="col-md-2">
+                    <div>
+                        <select id="evictionDay" ng-model="instance.config['evictionDay'][0]" class="form-control">
+                            <option value="1">{{:: 'Sunday' | translate}}</option>
+                            <option value="2">{{:: 'Monday' | translate}}</option>
+                            <option value="3">{{:: 'Tuesday' | translate}}</option>
+                            <option value="4">{{:: 'Wednesday' | translate}}</option>
+                            <option value="5">{{:: 'Thursday' | translate}}</option>
+                            <option value="6">{{:: 'Friday' | translate}}</option>
+                            <option value="7">{{:: 'Saturday' | translate}}</option>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'userStorage.cachePolicy.evictionDay.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" data-ng-show="instance.config['cachePolicy'][0] == 'EVICT_WEEKLY' || instance.config['cachePolicy'][0] == 'EVICT_DAILY'">
+                <label class="col-md-2 control-label" for="evictionHour">{{:: 'userStorage.cachePolicy.evictionHour' | translate}}</label>
+                <div class="col-md-2">
+                    <div>
+                        <select id="evictionHour" ng-model="instance.config['evictionHour'][0]" class="form-control">
+                            <option value="0">00</option>
+                            <option value="1">01</option>
+                            <option value="2">02</option>
+                            <option value="3">03</option>
+                            <option value="4">04</option>
+                            <option value="5">05</option>
+                            <option value="6">06</option>
+                            <option value="7">07</option>
+                            <option value="8">08</option>
+                            <option value="9">09</option>
+                            <option value="10">10</option>
+                            <option value="11">11</option>
+                            <option value="12">12</option>
+                            <option value="13">13</option>
+                            <option value="14">14</option>
+                            <option value="15">15</option>
+                            <option value="16">16</option>
+                            <option value="17">17</option>
+                            <option value="18">18</option>
+                            <option value="19">19</option>
+                            <option value="20">20</option>
+                            <option value="21">21</option>
+                            <option value="22">22</option>
+                            <option value="23">23</option>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'userStorage.cachePolicy.evictionHour.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" data-ng-show="instance.config['cachePolicy'][0] == 'EVICT_WEEKLY' || instance.config['cachePolicy'][0] == 'EVICT_DAILY'">
+                <label class="col-md-2 control-label" for="evictionMinute">{{:: 'userStorage.cachePolicy.evictionMinute' | translate}}</label>
+                <div class="col-md-2">
+                    <div>
+                        <select id="evictionMinute" ng-model="instance.config['evictionMinute'][0]" class="form-control">
+                            <option value="0">00</option>
+                            <option value="1">01</option>
+                            <option value="2">02</option>
+                            <option value="3">03</option>
+                            <option value="4">04</option>
+                            <option value="5">05</option>
+                            <option value="6">06</option>
+                            <option value="7">07</option>
+                            <option value="8">08</option>
+                            <option value="9">09</option>
+                            <option value="10">10</option>
+                            <option value="11">11</option>
+                            <option value="12">12</option>
+                            <option value="13">13</option>
+                            <option value="14">14</option>
+                            <option value="15">15</option>
+                            <option value="16">16</option>
+                            <option value="17">17</option>
+                            <option value="18">18</option>
+                            <option value="19">19</option>
+                            <option value="20">20</option>
+                            <option value="21">21</option>
+                            <option value="22">22</option>
+                            <option value="23">23</option>
+                            <option value="24">24</option>
+                            <option value="25">25</option>
+                            <option value="26">26</option>
+                            <option value="27">27</option>
+                            <option value="28">28</option>
+                            <option value="29">29</option>
+                            <option value="30">30</option>
+                            <option value="31">31</option>
+                            <option value="32">32</option>
+                            <option value="33">33</option>
+                            <option value="34">34</option>
+                            <option value="35">35</option>
+                            <option value="36">36</option>
+                            <option value="37">37</option>
+                            <option value="38">38</option>
+                            <option value="39">39</option>
+                            <option value="40">40</option>
+                            <option value="41">41</option>
+                            <option value="42">42</option>
+                            <option value="43">43</option>
+                            <option value="44">44</option>
+                            <option value="45">45</option>
+                            <option value="46">46</option>
+                            <option value="47">47</option>
+                            <option value="48">48</option>
+                            <option value="49">49</option>
+                            <option value="50">50</option>
+                            <option value="51">51</option>
+                            <option value="52">52</option>
+                            <option value="53">53</option>
+                            <option value="54">54</option>
+                            <option value="55">55</option>
+                            <option value="56">56</option>
+                            <option value="57">57</option>
+                            <option value="58">58</option>
+                            <option value="59">59</option>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'userStorage.cachePolicy.evictionMinute.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" data-ng-show="instance.config['cachePolicy'][0] == 'MAX_LIFESPAN'">
+                <label class="col-md-2 control-label" for="maxLifespan">{{:: 'userStorage.cachePolicy.maxLifespan' | translate}}</label>
+                <div class="col-md-6">
+                    <input class="form-control" type="text"  ng-model="instance.config['maxLifespan'][0]" id="maxLifespan" />
+                </div>
+                <kc-tooltip>{{:: 'userStorage.cachePolicy.maxLifespan.tooltip' | translate}}</kc-tooltip>
+            </div>
+        </fieldset>
+
+
+        <div class="form-group">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageRealm">
+                <button kc-save>{{:: 'save' | translate}}</button>
+                <button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
+            </div>
+        </div>
+
+        <div class="form-group">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageRealm">
+                <button kc-save  data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
+                <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
+                <button class="btn btn-primary" data-ng-click="triggerChangedUsersSync()" data-ng-hide="changed">{{:: 'synchronize-changed-users' | translate}}</button>
+                <button class="btn btn-primary" data-ng-click="triggerFullSync()" data-ng-hide="changed">{{:: 'synchronize-all-users' | translate}}</button>
+            </div>
+        </div>
+    </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file