keycloak-uncached

KEYCLOAK-4175 Provide a way to set the connect and read timeout

1/9/2017 6:29:06 PM

Details

diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
index cf421e7..2f07254 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
@@ -507,6 +507,16 @@ public class LDAPOperationManager {
             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
         Properties additionalProperties = this.config.getAdditionalConnectionProperties();
         if (additionalProperties != null) {
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java
index ee2a89f..d664035 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java
@@ -109,6 +109,14 @@ public class LDAPConfig {
         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() {
         // not supported for now
         return null;
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
index d8d0497..524f250 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
@@ -145,6 +145,12 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
                 .type(ProviderConfigProperty.BOOLEAN_TYPE)
                 .defaultValue("true")
                 .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)
                 .type(ProviderConfigProperty.BOOLEAN_TYPE)
                 .defaultValue("true")
@@ -212,6 +218,25 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
         LDAPConfig cfg = new LDAPConfig(config.getConfig());
         String customFilter = cfg.getCustomUserSearchFilter();
         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
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index 0e85682..ed7e071 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -177,7 +177,7 @@ public interface RealmResource {
     @NoCache
     Response testLDAPConnection(@QueryParam("action") String action, @QueryParam("connectionUrl") String connectionUrl,
                                 @QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential,
-                                @QueryParam("useTruststoreSpi") String useTruststoreSpi);
+                                @QueryParam("useTruststoreSpi") String useTruststoreSpi, @QueryParam("connectionTimeout") String connectionTimeout);
 
     @Path("clear-realm-cache")
     @POST
diff --git a/server-spi-private/src/main/java/org/keycloak/models/LDAPConstants.java b/server-spi-private/src/main/java/org/keycloak/models/LDAPConstants.java
index 7bef0cb..7f722bc 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/LDAPConstants.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/LDAPConstants.java
@@ -57,6 +57,8 @@ public class LDAPConstants {
 
     public static final String SEARCH_SCOPE = "searchScope";
     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 EDIT_MODE = "editMode";
diff --git a/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java b/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
index 94eb6b7..29ac5f1 100755
--- a/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
@@ -35,7 +35,7 @@ public class LDAPConnectionTestManager {
     public static final String TEST_CONNECTION = "testConnection";
     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)) {
             ServicesLogger.LOGGER.unknownAction(action);
             return false;
@@ -70,6 +70,10 @@ public class LDAPConnectionTestManager {
 
             LDAPConstants.setTruststoreSpiIfNeeded(useTruststoreSpi, connectionUrl, env);
 
+            if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
+                env.put("com.sun.jndi.ldap.connect.timeout", connectionTimeout);
+            }
+
             ldapContext = new InitialLdapContext(env, null);
             return true;
         } catch (Exception ne) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 248d653..133a903 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -750,14 +750,15 @@ public class RealmAdminResource {
     @NoCache
     public Response testLDAPConnection(@QueryParam("action") String action, @QueryParam("connectionUrl") String connectionUrl,
                                        @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();
 
         if (componentId != null && bindCredential.equals(ComponentRepresentation.SECRET_VALUE)) {
             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);
     }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
index 18277d2..ce2863b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
@@ -271,7 +271,7 @@ public class PermissionsTest extends AbstractKeycloakTest {
 
         invoke(new InvocationWithResponse() {
             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);
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationLdapConnectionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationLdapConnectionTest.java
index 52bfc69..6ee926d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationLdapConnectionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationLdapConnectionTest.java
@@ -36,23 +36,23 @@ public class UserFederationLdapConnectionTest extends AbstractAdminTest {
     @Test
     public void testLdapConnections1() {
         // 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);
 
         // 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);
 
         // 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);
 
         // 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);
 
         // 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);
 
     }
@@ -60,16 +60,16 @@ public class UserFederationLdapConnectionTest extends AbstractAdminTest {
     @Test
     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);
 
-        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);
 
-        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);
 
-        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);
     }
 
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 7480ef8..e6a9a63 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -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
 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
+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.pagination.tooltip=Does the LDAP server support pagination.
 kerberos-integration=Kerberos Integration
diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
index f4044ab..e734c06 100644
--- a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
@@ -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.
 
 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.
 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
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 e99b03e..4f96891 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
@@ -1262,6 +1262,7 @@ module.controller('LDAPUserStorageCtrl', function($scope, $location, Notificatio
             bindDn: ldapConfig.bindDn,
             bindCredential: ldapConfig.bindCredential,
             useTruststoreSpi: ldapConfig.useTruststoreSpi,
+            connectionTimeout: ldapConfig.connectionTimeout,
             componentId: instance.id
         };
     };
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
index 060d7d7..02a3464 100755
--- 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
@@ -187,6 +187,20 @@
                 <kc-tooltip>{{:: 'ldap.connection-pooling.tooltip' | translate}}</kc-tooltip>
             </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">
                 <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}}" />