From 4880c0443c4b60bcd62565b4bc748095681cfad2 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Tue, 8 Nov 2016 12:30:20 -0500 Subject: [PATCH] ldap port admin console --- .../ldap/LDAPStorageProviderFactory.java | 110 ++++- .../AbstractLDAPStorageMapperFactory.java | 10 +- .../mappers/LDAPStorageMapperFactory.java | 2 - .../group/GroupLDAPStorageMapperFactory.java | 11 +- .../role/RoleLDAPStorageMapperFactory.java | 11 +- .../keycloak/component/ComponentFactory.java | 13 + .../component/SubComponentFactory.java | 13 + .../storage/UserStorageProviderFactory.java | 13 + .../storage/UserStorageProviderSpi.java | 2 + .../resources/admin/ComponentResource.java | 30 +- .../admin/info/ServerInfoAdminResource.java | 5 +- .../theme/base/admin/resources/js/app.js | 38 ++ .../admin/resources/js/controllers/users.js | 319 +++++++++++++ .../resources/partials/user-storage-ldap.html | 449 ++++++++++++++++++ 14 files changed, 1011 insertions(+), 15 deletions(-) create mode 100755 themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html 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 2ffc16f7b1..2401b49b21 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 configProperties; + + static { + configProperties = getConfigProps(null); + } + + private static List 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 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 bacac30a1e..a6ba2d2651 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 Marek Posolda @@ -60,8 +62,12 @@ public abstract class AbstractLDAPStorageMapperFactory implements LDAPStorageMap } @Override - public UserFederationMapperSyncConfigRepresentation getSyncConfig() { - return new UserFederationMapperSyncConfigRepresentation(false, null, false, null); + public Map getTypeMetadata() { + Map 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 3b3f58cd0e..d5e8318ed3 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 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 cad730e8f8..b3baf50d35 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 getTypeMetadata() { + Map 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 7d9a52ff18..e2da595d2a 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 getTypeMetadata() { + Map 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 75c3fb130e..d519286dcb 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 Bill Burke @@ -55,5 +56,17 @@ public interface ComponentFactory 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 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 3f5295da63..56c012d4be 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 List 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 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 ba47784d6e..18e291ac6d 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 Bill Burke @@ -108,4 +111,14 @@ public interface UserStorageProviderFactory exten List getCommonProviderConfigProperties() { return UserStorageProviderSpi.commonConfig(); } + + @Override + default + Map getTypeMetadata() { + Map 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 625adeb23a..06bc256979 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 bbd2edb095..c2bba875ac 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 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 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 props = null; + Map 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 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 8120e7f0b4..8017b04a08 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 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 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 f936fcdbe6..223c94e557 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 cd72b1ed26..c55534d50e 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 0000000000..d81541f8e9 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html @@ -0,0 +1,449 @@ +
+ + + + +
+ + + +
+ {{:: 'required-settings' | translate}} +
+ +
+ +
+
+
+ +
+ +
+ {{:: 'console-display-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'priority.tooltip' | translate}} +
+ +
+ +
+
+ +
+
+ {{:: 'ldap.edit-mode.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.sync-registrations.tooltip' | translate}} +
+
+ +
+
+ +
+
+ +
+
+ {{:: 'ldap.vendor.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'username-ldap-attribute.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'rdn-ldap-attribute.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'uuid-ldap-attribute.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.user-object-classes.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.connection-url.tooltip' | translate}} + +
+
+ +
+ +
+ {{:: 'ldap.users-dn.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'ldap.authentication-type.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.bind-dn.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.bind-credential.tooltip' | translate}} + +
+
+ +
+ +
+ {{:: 'ldap.custom-user-ldap-filter.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'ldap.search-scope.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'ldap.use-truststore-spi.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.connection-pooling.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.pagination.tooltip' | translate}} +
+
+ +
+ {{:: 'kerberos-integration' | translate}} +
+ +
+ +
+ {{:: 'ldap.allow-kerberos-authentication.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'kerberos-realm.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'server-principal.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'keytab.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'debug.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.use-kerberos-for-password-authentication.tooltip' | translate}} +
+
+ +
+ {{:: 'sync-settings' | translate}} +
+ +
+ +
+ {{:: 'ldap.batch-size.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.periodic-full-sync.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'full-sync-period.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.periodic-changed-users-sync.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.changed-users-sync-period.tooltip' | translate}} +
+
+ +
+ {{:: 'user-storage-cache-policy' | translate}} +
+ +
+
+ +
+
+ {{:: 'userStorage.cachePolicy.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'userStorage.cachePolicy.evictionDay.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'userStorage.cachePolicy.evictionHour.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'userStorage.cachePolicy.evictionMinute.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'userStorage.cachePolicy.maxLifespan.tooltip' | translate}} +
+
+ + +
+
+ + +
+
+ +
+
+ + + + +
+
+
+
+ + \ No newline at end of file