[KEYCLOAK-6069] Allow configuration of LDAP connection pooling

This commit is contained in:
Douglas Palmer 2018-02-28 08:23:11 -08:00 committed by Marek Posolda
parent 9ed221b168
commit cf056b3464
8 changed files with 242 additions and 10 deletions

View file

@ -114,6 +114,34 @@ public class LDAPConfig {
return config.getFirst(LDAPConstants.CONNECTION_POOLING); return config.getFirst(LDAPConstants.CONNECTION_POOLING);
} }
public String getConnectionPoolingAuthentication() {
return config.getFirst(LDAPConstants.CONNECTION_POOLING_AUTHENTICATION);
}
public String getConnectionPoolingDebug() {
return config.getFirst(LDAPConstants.CONNECTION_POOLING_DEBUG);
}
public String getConnectionPoolingInitSize() {
return config.getFirst(LDAPConstants.CONNECTION_POOLING_INITSIZE);
}
public String getConnectionPoolingMaxSize() {
return config.getFirst(LDAPConstants.CONNECTION_POOLING_MAXSIZE);
}
public String getConnectionPoolingPrefSize() {
return config.getFirst(LDAPConstants.CONNECTION_POOLING_PREFSIZE);
}
public String getConnectionPoolingProtocol() {
return config.getFirst(LDAPConstants.CONNECTION_POOLING_PROTOCOL);
}
public String getConnectionPoolingTimeout() {
return config.getFirst(LDAPConstants.CONNECTION_POOLING_TIMEOUT);
}
public String getConnectionTimeout() { public String getConnectionTimeout() {
return config.getFirst(LDAPConstants.CONNECTION_TIMEOUT); return config.getFirst(LDAPConstants.CONNECTION_TIMEOUT);
} }

View file

@ -81,21 +81,26 @@ public class LDAPIdentityStoreRegistry {
* Create LDAPIdentityStore to be cached in the local registry * Create LDAPIdentityStore to be cached in the local registry
*/ */
public static LDAPIdentityStore createLdapIdentityStore(LDAPConfig cfg) { public static LDAPIdentityStore createLdapIdentityStore(LDAPConfig cfg) {
checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", "none simple"); checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", cfg.getConnectionPoolingAuthentication(), "none simple");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.initsize", "1"); checkSystemProperty("com.sun.jndi.ldap.connect.pool.initsize", cfg.getConnectionPoolingInitSize(), "1");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.maxsize", "1000"); checkSystemProperty("com.sun.jndi.ldap.connect.pool.maxsize", cfg.getConnectionPoolingMaxSize(), "1000");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.prefsize", "5"); checkSystemProperty("com.sun.jndi.ldap.connect.pool.prefsize", cfg.getConnectionPoolingPrefSize(), "5");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.timeout", "300000"); checkSystemProperty("com.sun.jndi.ldap.connect.pool.timeout", cfg.getConnectionPoolingTimeout(), "300000");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain"); checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", cfg.getConnectionPoolingProtocol(), "plain");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off"); checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", cfg.getConnectionPoolingDebug(), "off");
return new LDAPIdentityStore(cfg); return new LDAPIdentityStore(cfg);
} }
private static void checkSystemProperty(String name, String defaultValue) { private static void checkSystemProperty(String name, String cfgValue, String defaultValue) {
if (System.getProperty(name) == null) { String value = System.getProperty(name);
System.setProperty(name, defaultValue); if(cfgValue != null) {
value = cfgValue;
} }
if(value == null) {
value = defaultValue;
}
System.setProperty(name, value);
} }

View file

@ -154,6 +154,27 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
.type(ProviderConfigProperty.BOOLEAN_TYPE) .type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true") .defaultValue("true")
.add() .add()
.property().name(LDAPConstants.CONNECTION_POOLING_AUTHENTICATION)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.CONNECTION_POOLING_DEBUG)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.CONNECTION_POOLING_INITSIZE)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.CONNECTION_POOLING_MAXSIZE)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.CONNECTION_POOLING_PREFSIZE)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.CONNECTION_POOLING_PROTOCOL)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.CONNECTION_POOLING_TIMEOUT)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.CONNECTION_TIMEOUT) .property().name(LDAPConstants.CONNECTION_TIMEOUT)
.type(ProviderConfigProperty.STRING_TYPE) .type(ProviderConfigProperty.STRING_TYPE)
.add() .add()

View file

@ -57,6 +57,13 @@ public class LDAPConstants {
public static final String SEARCH_SCOPE = "searchScope"; public static final String SEARCH_SCOPE = "searchScope";
public static final String CONNECTION_POOLING = "connectionPooling"; public static final String CONNECTION_POOLING = "connectionPooling";
public static final String CONNECTION_POOLING_AUTHENTICATION = "connectionPoolingAuthentication";
public static final String CONNECTION_POOLING_DEBUG = "connectionPoolingDebug";
public static final String CONNECTION_POOLING_INITSIZE = "connectionPoolingInitSize";
public static final String CONNECTION_POOLING_MAXSIZE = "connectionPoolingMaxSize";
public static final String CONNECTION_POOLING_PREFSIZE = "connectionPoolingPrefSize";
public static final String CONNECTION_POOLING_PROTOCOL = "connectionPoolingProtocol";
public static final String CONNECTION_POOLING_TIMEOUT = "connectionPoolingTimeout";
public static final String CONNECTION_TIMEOUT = "connectionTimeout"; public static final String CONNECTION_TIMEOUT = "connectionTimeout";
public static final String READ_TIMEOUT = "readTimeout"; public static final String READ_TIMEOUT = "readTimeout";
public static final String PAGINATION = "pagination"; public static final String PAGINATION = "pagination";

View file

@ -120,6 +120,30 @@ public class LdapUserProviderForm extends Form {
@FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='changedSyncEnabled']]") @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='changedSyncEnabled']]")
private OnOffSwitch periodicChangedUsersSync; private OnOffSwitch periodicChangedUsersSync;
@FindByJQuery("a:contains('Connection Pooling Settings')")
private WebElement connectionPoolingSettingsButton;
@FindBy(id = "connectionPoolingAuthentication")
private WebElement connectionPoolingAuthenticationInput;
@FindBy(id = "connectionPoolingDebug")
private WebElement connectionPoolingDebugInput;
@FindBy(id = "connectionPoolingInitSize")
private WebElement connectionPoolingInitSizeInput;
@FindBy(id = "connectionPoolingMaxSize")
private WebElement connectionPoolingMaxSizeInput;
@FindBy(id = "connectionPoolingPrefSize")
private WebElement connectionPoolingPrefSizeInput;
@FindBy(id = "connectionPoolingProtocol")
private WebElement connectionPoolingProtocolInput;
@FindBy(id = "connectionPoolingTimeout")
private WebElement connectionPoolingTimeoutInput;
public void setConsoleDisplayNameInput(String name) { public void setConsoleDisplayNameInput(String name) {
setInputValue(consoleDisplayNameInput, name); setInputValue(consoleDisplayNameInput, name);
} }
@ -256,6 +280,38 @@ public class LdapUserProviderForm extends Form {
this.periodicChangedUsersSync.setOn(periodicChangedUsersSyncEnabled); this.periodicChangedUsersSync.setOn(periodicChangedUsersSyncEnabled);
} }
public void connectionPoolingSettings() {
connectionPoolingSettingsButton.click();
}
public void setConnectionPoolingAuthentication(String connectionPoolingAuthentication) {
setInputValue(connectionPoolingAuthenticationInput, connectionPoolingAuthentication);
}
public void setConnectionPoolingDebug(String connectionPoolingDebug) {
setInputValue(connectionPoolingDebugInput, connectionPoolingDebug);
}
public void setConnectionPoolingInitSize(String connectionPoolingInitSize) {
setInputValue(connectionPoolingInitSizeInput, connectionPoolingInitSize);
}
public void setConnectionPoolingMaxSize(String connectionPoolingMaxSize) {
setInputValue(connectionPoolingMaxSizeInput, connectionPoolingMaxSize);
}
public void setConnectionPoolingPrefSize(String connectionPoolingPrefSize) {
setInputValue(connectionPoolingPrefSizeInput, connectionPoolingPrefSize);
}
public void setConnectionPoolingProtocol(String connectionPoolingProtocol) {
setInputValue(connectionPoolingProtocolInput, connectionPoolingProtocol);
}
public void setConnectionPoolingTimeout(String connectionPoolingTimeout) {
setInputValue(connectionPoolingTimeoutInput, connectionPoolingTimeout);
}
public void testConnection() { public void testConnection() {
testConnectionButton.click(); testConnectionButton.click();
} }

View file

@ -3,6 +3,7 @@ package org.keycloak.testsuite.console.federation;
import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.ConfigurationException;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Test; import org.junit.Test;
import org.keycloak.models.LDAPConstants;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.testsuite.console.AbstractConsoleTest; import org.keycloak.testsuite.console.AbstractConsoleTest;
import org.keycloak.testsuite.console.page.federation.CreateLdapUserProvider; import org.keycloak.testsuite.console.page.federation.CreateLdapUserProvider;
@ -176,6 +177,35 @@ public class LdapUserFederationTest extends AbstractConsoleTest {
assertTrue("Vendors list doesn't match", vendorsExpected.containsAll(vendorsActual)); assertTrue("Vendors list doesn't match", vendorsExpected.containsAll(vendorsActual));
} }
@Test
public void configureConnectionPooling() {
createLdapUserProvider.navigateTo();
createLdapUserProvider.form().selectVendor(ACTIVE_DIRECTORY);
createLdapUserProvider.form().setConsoleDisplayNameInput("ldap");
createLdapUserProvider.form().selectEditMode(WRITABLE);
createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:389");
createLdapUserProvider.form().setLdapBindDnInput("KEYCLOAK/Administrator");
createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
createLdapUserProvider.form().setLdapBindCredentialInput("secret");
createLdapUserProvider.form().connectionPoolingSettings();
createLdapUserProvider.form().setConnectionPoolingAuthentication("none");
createLdapUserProvider.form().setConnectionPoolingDebug("fine");
createLdapUserProvider.form().setConnectionPoolingInitSize("10");
createLdapUserProvider.form().setConnectionPoolingMaxSize("12");
createLdapUserProvider.form().setConnectionPoolingPrefSize("11");
createLdapUserProvider.form().setConnectionPoolingProtocol("ssl");
createLdapUserProvider.form().setConnectionPoolingTimeout("500");
createLdapUserProvider.form().save();
assertAlertSuccess();
ComponentRepresentation ufpr = testRealmResource().components()
.query(null, "org.keycloak.storage.UserStorageProvider").get(0);
assertLdapConnectionPoolSettings(ufpr, "none","fine","10","12","11","ssl","500");
}
private void assertLdapProviderSetting(ComponentRepresentation ufpr, String name, String priority, private void assertLdapProviderSetting(ComponentRepresentation ufpr, String name, String priority,
String editMode, String syncRegistrations, String vendor, String searchScope, String connectionPooling, String editMode, String syncRegistrations, String vendor, String searchScope, String connectionPooling,
String pagination, String enableAccountAfterPasswordUpdate) { String pagination, String enableAccountAfterPasswordUpdate) {
@ -216,6 +246,17 @@ public class LdapUserFederationTest extends AbstractConsoleTest {
assertEquals(periodicChangedUsersSync, ufpr.getConfig().get("changedSyncPeriod").get(0)); assertEquals(periodicChangedUsersSync, ufpr.getConfig().get("changedSyncPeriod").get(0));
} }
private void assertLdapConnectionPoolSettings(ComponentRepresentation ufpr, String authentication, String debug,
String initsize, String maxsize, String prefsize, String protocol, String timeout) {
assertEquals(authentication, ufpr.getConfig().get(LDAPConstants.CONNECTION_POOLING_AUTHENTICATION).get(0));
assertEquals(debug, ufpr.getConfig().get(LDAPConstants.CONNECTION_POOLING_DEBUG).get(0));
assertEquals(initsize, ufpr.getConfig().get(LDAPConstants.CONNECTION_POOLING_INITSIZE).get(0));
assertEquals(maxsize, ufpr.getConfig().get(LDAPConstants.CONNECTION_POOLING_MAXSIZE).get(0));
assertEquals(prefsize, ufpr.getConfig().get(LDAPConstants.CONNECTION_POOLING_PREFSIZE).get(0));
assertEquals(protocol, ufpr.getConfig().get(LDAPConstants.CONNECTION_POOLING_PROTOCOL).get(0));
assertEquals(timeout, ufpr.getConfig().get(LDAPConstants.CONNECTION_POOLING_TIMEOUT).get(0));
}
private LDAPEmbeddedServer startEmbeddedLdapServer() throws Exception { private LDAPEmbeddedServer startEmbeddedLdapServer() throws Exception {
Properties defaultProperties = new Properties(); Properties defaultProperties = new Properties();
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY); defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY);

View file

@ -838,12 +838,34 @@ use-truststore-spi=Use Truststore SPI
ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in standalone.xml/domain.xml. 'Always' means that it will always use it. 'Never' means that it won't use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if standalone.xml/domain.xml is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used. ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in standalone.xml/domain.xml. 'Always' means that it will always use it. 'Never' means that it won't use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if standalone.xml/domain.xml is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used.
validate-password-policy=Validate Password Policy validate-password-policy=Validate Password Policy
connection-pooling=Connection Pooling connection-pooling=Connection Pooling
connection-pooling-settings=Connection Pooling Settings
connection-pooling-authentication=Connection Pooling Authentication
connection-pooling-authentication-default=none simple
connection-pooling-debug=Connection Pool Debug Level
connection-pooling-debug-default=off
connection-pooling-initsize=Connection Pool Initial Size
connection-pooling-initsize-default=1
connection-pooling-maxsize=Connection Pool Maximum Size
connection-pooling-maxsize-default=1000
connection-pooling-prefsize=Connection Pool Preferred Size
connection-pooling-prefsize-default=5
connection-pooling-protocol=Connection Pool Protocol
connection-pooling-protocol-default=plain
connection-pooling-timeout=Connection Pool Timeout
connection-pooling-timeout-default=300000
ldap-connection-timeout=Connection Timeout ldap-connection-timeout=Connection Timeout
ldap.connection-timeout.tooltip=LDAP Connection Timeout in milliseconds ldap.connection-timeout.tooltip=LDAP Connection Timeout in milliseconds
ldap-read-timeout=Read Timeout ldap-read-timeout=Read Timeout
ldap.read-timeout.tooltip=LDAP Read Timeout in milliseconds. This timeout applies for LDAP read operations ldap.read-timeout.tooltip=LDAP Read Timeout in milliseconds. This timeout applies for LDAP read operations
ldap.validate-password-policy.tooltip=Does Keycloak should validate the password with the realm password policy before updating it ldap.validate-password-policy.tooltip=Does Keycloak should validate the password with the realm password policy before updating it
ldap.connection-pooling.tooltip=Does Keycloak should use connection pooling for accessing LDAP server ldap.connection-pooling.tooltip=Does Keycloak should use connection pooling for accessing LDAP server
ldap.connection-pooling.authentication.tooltip=A list of space-separated authentication types of connections that may be pooled. Valid types are "none", "simple", and "DIGEST-MD5".
ldap.connection-pooling.debug.tooltip=A string that indicates the level of debug output to produce. Valid values are "fine" (trace connection creation and removal) and "all" (all debugging information).
ldap.connection-pooling.initsize.tooltip=The string representation of an integer that represents the number of connections per connection identity to create when initially creating a connection for the identity.
ldap.connection-pooling.maxsize.tooltip=The string representation of an integer that represents the maximum number of connections per connection identity that can be maintained concurrently.
ldap.connection-pooling.prefsize.tooltip=The string representation of an integer that represents the preferred number of connections per connection identity that should be maintained concurrently.
ldap.connection-pooling.protocol.tooltip=A list of space-separated protocol types of connections that may be pooled. Valid types are "plain" and "ssl".
ldap.connection-pooling.timeout.tooltip=The string representation of an integer that represents the number of milliseconds that an idle connection may remain in the pool without being closed and removed from the pool.
ldap.pagination.tooltip=Does the LDAP server support pagination. ldap.pagination.tooltip=Does the LDAP server support pagination.
kerberos-integration=Kerberos Integration kerberos-integration=Kerberos Integration
allow-kerberos-authentication=Allow Kerberos authentication allow-kerberos-authentication=Allow Kerberos authentication

View file

@ -203,6 +203,58 @@
<input ng-model="instance.config['connectionPooling'][0]" name="connectionPooling" id="connectionPooling" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" /> <input ng-model="instance.config['connectionPooling'][0]" name="connectionPooling" id="connectionPooling" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div> </div>
<kc-tooltip>{{:: 'ldap.connection-pooling.tooltip' | translate}}</kc-tooltip> <kc-tooltip>{{:: 'ldap.connection-pooling.tooltip' | translate}}</kc-tooltip>
<div class="col-sm-4" data-ng-show="instance.config['connectionPooling'][0] == 'true'">
<a class="btn btn-primary" data-ng-init="connectionPoolSettings=false" data-ng-click="connectionPoolSettings=!connectionPoolSettings">{{:: 'connection-pooling-settings' | translate}}</a>
</div>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['connectionPooling'][0] == 'true' && connectionPoolSettings">
<label class="col-md-2 control-label" for="connectionPoolingAuthentication">{{:: 'connection-pooling-authentication' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="connectionPoolingAuthentication" type="text" ng-model="instance.config['connectionPoolingAuthentication'][0]" placeholder="{{:: 'connection-pooling-authentication-default' | translate}}"/>
</div>
<kc-tooltip>{{:: 'ldap.connection-pooling.authentication.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['connectionPooling'][0] == 'true' && connectionPoolSettings">
<label class="col-md-2 control-label" for="connectionPoolingDebug">{{:: 'connection-pooling-debug' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="connectionPoolingDebug" type="text" ng-model="instance.config['connectionPoolingDebug'][0]" placeholder="{{:: 'connection-pooling-debug-default' | translate}}"/>
</div>
<kc-tooltip>{{:: 'ldap.connection-pooling.debug.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['connectionPooling'][0] == 'true' && connectionPoolSettings">
<label class="col-md-2 control-label" for="connectionPoolingInitSize">{{:: 'connection-pooling-initsize' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="connectionPoolingInitSize" type="text" ng-model="instance.config['connectionPoolingInitSize'][0]" placeholder="{{:: 'connection-pooling-initsize-default' | translate}}"/>
</div>
<kc-tooltip>{{:: 'ldap.connection-pooling.initsize.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['connectionPooling'][0] == 'true' && connectionPoolSettings">
<label class="col-md-2 control-label" for="connectionPoolingMaxSize">{{:: 'connection-pooling-maxsize' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="connectionPoolingMaxSize" type="text" ng-model="instance.config['connectionPoolingMaxSize'][0]" placeholder="{{:: 'connection-pooling-maxsize-default' | translate}}"/>
</div>
<kc-tooltip>{{:: 'ldap.connection-pooling.maxsize.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['connectionPooling'][0] == 'true' && connectionPoolSettings">
<label class="col-md-2 control-label" for="connectionPoolingPrefSize">{{:: 'connection-pooling-prefsize' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="connectionPoolingPrefSize" type="text" ng-model="instance.config['connectionPoolingPrefSize'][0]" placeholder="{{:: 'connection-pooling-prefsize-default' | translate}}"/>
</div>
<kc-tooltip>{{:: 'ldap.connection-pooling.prefsize.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['connectionPooling'][0] == 'true' && connectionPoolSettings">
<label class="col-md-2 control-label" for="connectionPoolingProtocol">{{:: 'connection-pooling-protocol' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="connectionPoolingProtocol" type="text" ng-model="instance.config['connectionPoolingProtocol'][0]" placeholder="{{:: 'connection-pooling-protocol-default' | translate}}"/>
</div>
<kc-tooltip>{{:: 'ldap.connection-pooling.protocol.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['connectionPooling'][0] == 'true' && connectionPoolSettings">
<label class="col-md-2 control-label" for="connectionPoolingTimeout">{{:: 'connection-pooling-timeout' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="connectionPoolingTimeout" type="text" ng-model="instance.config['connectionPoolingTimeout'][0]" placeholder="{{:: 'connection-pooling-timeout-default' | translate}}"/>
</div>
<kc-tooltip>{{:: 'ldap.connection-pooling.timeout.tooltip' | translate}}</kc-tooltip>
</div> </div>
<div class="form-group clearfix"> <div class="form-group clearfix">
<label class="col-md-2 control-label" for="connectionTimeout">{{:: 'ldap-connection-timeout' | translate}}</label> <label class="col-md-2 control-label" for="connectionTimeout">{{:: 'ldap-connection-timeout' | translate}}</label>