KEYCLOAK-4175 Provide a way to set the connect and read timeout for ldap connections
This commit is contained in:
parent
be4f2aff63
commit
c32620b718
13 changed files with 85 additions and 14 deletions
|
@ -109,6 +109,14 @@ public class LDAPConfig {
|
||||||
return config.getFirst(LDAPConstants.CONNECTION_POOLING);
|
return config.getFirst(LDAPConstants.CONNECTION_POOLING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getConnectionTimeout() {
|
||||||
|
return config.getFirst(LDAPConstants.CONNECTION_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReadTimeout() {
|
||||||
|
return config.getFirst(LDAPConstants.READ_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
public Properties getAdditionalConnectionProperties() {
|
public Properties getAdditionalConnectionProperties() {
|
||||||
// not supported for now
|
// not supported for now
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -145,6 +145,12 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
|
||||||
.type(ProviderConfigProperty.BOOLEAN_TYPE)
|
.type(ProviderConfigProperty.BOOLEAN_TYPE)
|
||||||
.defaultValue("true")
|
.defaultValue("true")
|
||||||
.add()
|
.add()
|
||||||
|
.property().name(LDAPConstants.CONNECTION_TIMEOUT)
|
||||||
|
.type(ProviderConfigProperty.STRING_TYPE)
|
||||||
|
.add()
|
||||||
|
.property().name(LDAPConstants.READ_TIMEOUT)
|
||||||
|
.type(ProviderConfigProperty.STRING_TYPE)
|
||||||
|
.add()
|
||||||
.property().name(LDAPConstants.PAGINATION)
|
.property().name(LDAPConstants.PAGINATION)
|
||||||
.type(ProviderConfigProperty.BOOLEAN_TYPE)
|
.type(ProviderConfigProperty.BOOLEAN_TYPE)
|
||||||
.defaultValue("true")
|
.defaultValue("true")
|
||||||
|
@ -212,6 +218,25 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
|
||||||
LDAPConfig cfg = new LDAPConfig(config.getConfig());
|
LDAPConfig cfg = new LDAPConfig(config.getConfig());
|
||||||
String customFilter = cfg.getCustomUserSearchFilter();
|
String customFilter = cfg.getCustomUserSearchFilter();
|
||||||
LDAPUtils.validateCustomLdapFilter(customFilter);
|
LDAPUtils.validateCustomLdapFilter(customFilter);
|
||||||
|
|
||||||
|
String connectionTimeout = cfg.getConnectionTimeout();
|
||||||
|
if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
|
||||||
|
try {
|
||||||
|
Long.parseLong(connectionTimeout);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
throw new ComponentValidationException("ldapErrorConnectionTimeoutNotNumber");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String readTimeout = cfg.getReadTimeout();
|
||||||
|
if (readTimeout != null && !readTimeout.isEmpty()) {
|
||||||
|
try {
|
||||||
|
Long.parseLong(readTimeout);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
throw new ComponentValidationException("ldapErrorReadTimeoutNotNumber");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -507,6 +507,16 @@ public class LDAPOperationManager {
|
||||||
env.put("com.sun.jndi.ldap.connect.pool", connectionPooling);
|
env.put("com.sun.jndi.ldap.connect.pool", connectionPooling);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String connectionTimeout = config.getConnectionTimeout();
|
||||||
|
if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
|
||||||
|
env.put("com.sun.jndi.ldap.connect.timeout", connectionTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
String readTimeout = config.getReadTimeout();
|
||||||
|
if (readTimeout != null && !readTimeout.isEmpty()) {
|
||||||
|
env.put("com.sun.jndi.ldap.read.timeout", readTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
// Just dump the additional properties
|
// Just dump the additional properties
|
||||||
Properties additionalProperties = this.config.getAdditionalConnectionProperties();
|
Properties additionalProperties = this.config.getAdditionalConnectionProperties();
|
||||||
if (additionalProperties != null) {
|
if (additionalProperties != null) {
|
||||||
|
|
|
@ -177,7 +177,7 @@ public interface RealmResource {
|
||||||
@NoCache
|
@NoCache
|
||||||
Response testLDAPConnection(@QueryParam("action") String action, @QueryParam("connectionUrl") String connectionUrl,
|
Response testLDAPConnection(@QueryParam("action") String action, @QueryParam("connectionUrl") String connectionUrl,
|
||||||
@QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential,
|
@QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential,
|
||||||
@QueryParam("useTruststoreSpi") String useTruststoreSpi);
|
@QueryParam("useTruststoreSpi") String useTruststoreSpi, @QueryParam("connectionTimeout") String connectionTimeout);
|
||||||
|
|
||||||
@Path("clear-realm-cache")
|
@Path("clear-realm-cache")
|
||||||
@POST
|
@POST
|
||||||
|
|
|
@ -57,6 +57,8 @@ 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_TIMEOUT = "connectionTimeout";
|
||||||
|
public static final String READ_TIMEOUT = "readTimeout";
|
||||||
public static final String PAGINATION = "pagination";
|
public static final String PAGINATION = "pagination";
|
||||||
|
|
||||||
public static final String EDIT_MODE = "editMode";
|
public static final String EDIT_MODE = "editMode";
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class LDAPConnectionTestManager {
|
||||||
public static final String TEST_CONNECTION = "testConnection";
|
public static final String TEST_CONNECTION = "testConnection";
|
||||||
public static final String TEST_AUTHENTICATION = "testAuthentication";
|
public static final String TEST_AUTHENTICATION = "testAuthentication";
|
||||||
|
|
||||||
public boolean testLDAP(String action, String connectionUrl, String bindDn, String bindCredential, String useTruststoreSpi) {
|
public boolean testLDAP(String action, String connectionUrl, String bindDn, String bindCredential, String useTruststoreSpi, String connectionTimeout) {
|
||||||
if (!TEST_CONNECTION.equals(action) && !TEST_AUTHENTICATION.equals(action)) {
|
if (!TEST_CONNECTION.equals(action) && !TEST_AUTHENTICATION.equals(action)) {
|
||||||
ServicesLogger.LOGGER.unknownAction(action);
|
ServicesLogger.LOGGER.unknownAction(action);
|
||||||
return false;
|
return false;
|
||||||
|
@ -70,6 +70,10 @@ public class LDAPConnectionTestManager {
|
||||||
|
|
||||||
LDAPConstants.setTruststoreSpiIfNeeded(useTruststoreSpi, connectionUrl, env);
|
LDAPConstants.setTruststoreSpiIfNeeded(useTruststoreSpi, connectionUrl, env);
|
||||||
|
|
||||||
|
if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
|
||||||
|
env.put("com.sun.jndi.ldap.connect.timeout", connectionTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
ldapContext = new InitialLdapContext(env, null);
|
ldapContext = new InitialLdapContext(env, null);
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception ne) {
|
} catch (Exception ne) {
|
||||||
|
|
|
@ -750,14 +750,15 @@ public class RealmAdminResource {
|
||||||
@NoCache
|
@NoCache
|
||||||
public Response testLDAPConnection(@QueryParam("action") String action, @QueryParam("connectionUrl") String connectionUrl,
|
public Response testLDAPConnection(@QueryParam("action") String action, @QueryParam("connectionUrl") String connectionUrl,
|
||||||
@QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential,
|
@QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential,
|
||||||
@QueryParam("useTruststoreSpi") String useTruststoreSpi, @QueryParam("componentId") String componentId) {
|
@QueryParam("useTruststoreSpi") String useTruststoreSpi, @QueryParam("connectionTimeout") String connectionTimeout,
|
||||||
|
@QueryParam("componentId") String componentId) {
|
||||||
auth.init(RealmAuth.Resource.REALM).requireManage();
|
auth.init(RealmAuth.Resource.REALM).requireManage();
|
||||||
|
|
||||||
if (componentId != null && bindCredential.equals(ComponentRepresentation.SECRET_VALUE)) {
|
if (componentId != null && bindCredential.equals(ComponentRepresentation.SECRET_VALUE)) {
|
||||||
bindCredential = realm.getComponent(componentId).getConfig().getFirst(LDAPConstants.BIND_CREDENTIAL);
|
bindCredential = realm.getComponent(componentId).getConfig().getFirst(LDAPConstants.BIND_CREDENTIAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean result = new LDAPConnectionTestManager().testLDAP(action, connectionUrl, bindDn, bindCredential, useTruststoreSpi);
|
boolean result = new LDAPConnectionTestManager().testLDAP(action, connectionUrl, bindDn, bindCredential, useTruststoreSpi, connectionTimeout);
|
||||||
return result ? Response.noContent().build() : ErrorResponse.error("LDAP test error", Response.Status.BAD_REQUEST);
|
return result ? Response.noContent().build() : ErrorResponse.error("LDAP test error", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -271,7 +271,7 @@ public class PermissionsTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
invoke(new InvocationWithResponse() {
|
invoke(new InvocationWithResponse() {
|
||||||
public void invoke(RealmResource realm, AtomicReference<Response> response) {
|
public void invoke(RealmResource realm, AtomicReference<Response> response) {
|
||||||
response.set(realm.testLDAPConnection("nosuch", "nosuch", "nosuch", "nosuch", "nosuch"));
|
response.set(realm.testLDAPConnection("nosuch", "nosuch", "nosuch", "nosuch", "nosuch", "nosuch"));
|
||||||
}
|
}
|
||||||
}, Resource.REALM, true);
|
}, Resource.REALM, true);
|
||||||
|
|
||||||
|
|
|
@ -36,23 +36,23 @@ public class UserFederationLdapConnectionTest extends AbstractAdminTest {
|
||||||
@Test
|
@Test
|
||||||
public void testLdapConnections1() {
|
public void testLdapConnections1() {
|
||||||
// Unknown action
|
// Unknown action
|
||||||
Response response = realm.testLDAPConnection("unknown", "ldap://localhost:10389", "foo", "bar", "false");
|
Response response = realm.testLDAPConnection("unknown", "ldap://localhost:10389", "foo", "bar", "false", null);
|
||||||
assertStatus(response, 400);
|
assertStatus(response, 400);
|
||||||
|
|
||||||
// Bad host
|
// Bad host
|
||||||
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_CONNECTION, "ldap://localhostt:10389", "foo", "bar", "false");
|
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_CONNECTION, "ldap://localhostt:10389", "foo", "bar", "false", null);
|
||||||
assertStatus(response, 400);
|
assertStatus(response, 400);
|
||||||
|
|
||||||
// Connection success
|
// Connection success
|
||||||
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_CONNECTION, "ldap://localhost:10389", "foo", "bar", "false");
|
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_CONNECTION, "ldap://localhost:10389", "foo", "bar", "false", null);
|
||||||
assertStatus(response, 204);
|
assertStatus(response, 204);
|
||||||
|
|
||||||
// Bad authentication
|
// Bad authentication
|
||||||
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_AUTHENTICATION, "ldap://localhost:10389", "foo", "bar", "false");
|
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_AUTHENTICATION, "ldap://localhost:10389", "foo", "bar", "false", "10000");
|
||||||
assertStatus(response, 400);
|
assertStatus(response, 400);
|
||||||
|
|
||||||
// Authentication success
|
// Authentication success
|
||||||
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_AUTHENTICATION, "ldap://localhost:10389", "uid=admin,ou=system", "secret", "false");
|
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_AUTHENTICATION, "ldap://localhost:10389", "uid=admin,ou=system", "secret", "false", null);
|
||||||
assertStatus(response, 204);
|
assertStatus(response, 204);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -60,16 +60,16 @@ public class UserFederationLdapConnectionTest extends AbstractAdminTest {
|
||||||
@Test
|
@Test
|
||||||
public void testLdapConnectionsSsl() {
|
public void testLdapConnectionsSsl() {
|
||||||
|
|
||||||
Response response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_CONNECTION, "ldaps://localhost:10636", "foo", "bar", "false");
|
Response response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_CONNECTION, "ldaps://localhost:10636", "foo", "bar", "false", null);
|
||||||
assertStatus(response, 204);
|
assertStatus(response, 204);
|
||||||
|
|
||||||
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_CONNECTION, "ldaps://localhostt:10636", "foo", "bar", "false");
|
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_CONNECTION, "ldaps://localhostt:10636", "foo", "bar", "false", null);
|
||||||
assertStatus(response, 400);
|
assertStatus(response, 400);
|
||||||
|
|
||||||
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_AUTHENTICATION, "ldaps://localhost:10636", "foo", "bar", "false");
|
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_AUTHENTICATION, "ldaps://localhost:10636", "foo", "bar", "false", null);
|
||||||
assertStatus(response, 400);
|
assertStatus(response, 400);
|
||||||
|
|
||||||
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_AUTHENTICATION, "ldaps://localhost:10636", "uid=admin,ou=system", "secret", "true");
|
response = realm.testLDAPConnection(LDAPConnectionTestManager.TEST_AUTHENTICATION, "ldaps://localhost:10636", "uid=admin,ou=system", "secret", "true", null);
|
||||||
assertStatus(response, 204);
|
assertStatus(response, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -744,6 +744,10 @@ ldap.search-scope.tooltip=For one level, we search for users just in DNs specifi
|
||||||
use-truststore-spi=Use Truststore SPI
|
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.
|
||||||
connection-pooling=Connection Pooling
|
connection-pooling=Connection Pooling
|
||||||
|
ldap-connection-timeout=Connection Timeout
|
||||||
|
ldap.connection-timeout.tooltip=LDAP Connection Timeout in milliseconds
|
||||||
|
ldap-read-timeout=Read Timeout
|
||||||
|
ldap.read-timeout.tooltip=LDAP Read Timeout in milliseconds. This timeout applies for LDAP read operations
|
||||||
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.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
|
||||||
|
|
|
@ -8,6 +8,8 @@ invalidPasswordRegexPatternMessage=Invalid password: fails to match regex patter
|
||||||
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
||||||
|
|
||||||
ldapErrorInvalidCustomFilter=Custom configured LDAP filter does not start with "(" or does not end with ")".
|
ldapErrorInvalidCustomFilter=Custom configured LDAP filter does not start with "(" or does not end with ")".
|
||||||
|
ldapErrorConnectionTimeoutNotNumber=Connection Timeout must be a number
|
||||||
|
ldapErrorReadTimeoutNotNumber=Read Timeout must be a number
|
||||||
ldapErrorMissingClientId=Client ID needs to be provided in config when Realm Roles Mapping is not used.
|
ldapErrorMissingClientId=Client ID needs to be provided in config when Realm Roles Mapping is not used.
|
||||||
ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Not possible to preserve group inheritance and use UID membership type together.
|
ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Not possible to preserve group inheritance and use UID membership type together.
|
||||||
ldapErrorCantWriteOnlyForReadOnlyLdap=Can't set write only when LDAP provider mode is not WRITABLE
|
ldapErrorCantWriteOnlyForReadOnlyLdap=Can't set write only when LDAP provider mode is not WRITABLE
|
||||||
|
|
|
@ -1262,6 +1262,7 @@ module.controller('LDAPUserStorageCtrl', function($scope, $location, Notificatio
|
||||||
bindDn: ldapConfig.bindDn,
|
bindDn: ldapConfig.bindDn,
|
||||||
bindCredential: ldapConfig.bindCredential,
|
bindCredential: ldapConfig.bindCredential,
|
||||||
useTruststoreSpi: ldapConfig.useTruststoreSpi,
|
useTruststoreSpi: ldapConfig.useTruststoreSpi,
|
||||||
|
connectionTimeout: ldapConfig.connectionTimeout,
|
||||||
componentId: instance.id
|
componentId: instance.id
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -186,6 +186,20 @@
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'ldap.connection-pooling.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'ldap.connection-pooling.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group clearfix">
|
||||||
|
<label class="col-md-2 control-label" for="connectionTimeout">{{:: 'ldap-connection-timeout' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input class="form-control" id="connectionTimeout" type="text" ng-model="instance.config['connectionTimeout'][0]" placeholder="{{:: 'ldap-connection-timeout' | translate}}">
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'ldap.connection-timeout.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group clearfix">
|
||||||
|
<label class="col-md-2 control-label" for="readTimeout">{{:: 'ldap-read-timeout' | translate}}</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input class="form-control" id="readTimeout" type="text" ng-model="instance.config['readTimeout'][0]" placeholder="{{:: 'ldap-read-timeout' | translate}}">
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'ldap.read-timeout.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="pagination">{{:: 'pagination' | translate}}</label>
|
<label class="col-md-2 control-label" for="pagination">{{:: 'pagination' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
Loading…
Reference in a new issue