keycloak-memoizeit

Details

diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
index 8053a5c..bf1f0fc 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
@@ -168,7 +168,7 @@ module.config([ '$routeProvider', function($routeProvider) {
                     return RealmLoader();
                 }
             },
-            controller : 'RealmLdapSettingsCtrl'
+            controller : 'RealmLDAPSettingsCtrl'
         })
         .when('/realms/:realm/audit', {
             templateUrl : 'partials/realm-audit.html',
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
index 270b65f..392e726 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
@@ -896,8 +896,8 @@ module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, real
     }
 });
 
-module.controller('RealmLdapSettingsCtrl', function($scope, $location, Notifications, Realm, realm) {
-    console.log('RealmLdapSettingsCtrl');
+module.controller('RealmLDAPSettingsCtrl', function($scope, $location, Notifications, Realm, realm, RealmLDAPConnectionTester) {
+    console.log('RealmLDAPSettingsCtrl');
 
     $scope.ldapVendors = [
         { "id": "ad", "name": "Active Directory" },
@@ -943,6 +943,34 @@ module.controller('RealmLdapSettingsCtrl', function($scope, $location, Notificat
         $scope.changed = false;
         $scope.lastVendor = $scope.realm.ldapServer.vendor;
     };
+
+    var initConnectionTest = function(testAction, ldapConfig) {
+        return {
+            action: testAction,
+            realm: $scope.realm.realm,
+            connectionUrl: ldapConfig.connectionUrl,
+            bindDn: ldapConfig.bindDn,
+            bindCredential: ldapConfig.bindCredential
+        };
+    };
+
+    $scope.testConnection = function() {
+        console.log('RealmLDAPSettingsCtrl: testConnection');
+        RealmLDAPConnectionTester.get(initConnectionTest("testConnection", $scope.realm.ldapServer), 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('RealmLDAPSettingsCtrl: testAuthentication');
+        RealmLDAPConnectionTester.get(initConnectionTest("testAuthentication", $scope.realm.ldapServer), function() {
+            Notifications.success("LDAP authentication successful.");
+        }, function() {
+            Notifications.error("LDAP authentication failed. See server.log for details");
+        });
+    }
 });
 
 module.controller('RealmAuthSettingsCtrl', function($scope, realm) {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
index b88c6b9..415734a 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
@@ -180,6 +180,10 @@ module.factory('RealmAuditEvents', function($resource) {
     });
 });
 
+module.factory('RealmLDAPConnectionTester', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/testLDAPConnection');
+});
+
 module.factory('ServerInfo', function($resource) {
     return $resource(authUrl + '/admin/serverinfo');
 });
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-ldap.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-ldap.html
index 1a657fe..6cbf269 100644
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-ldap.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-ldap.html
@@ -40,6 +40,9 @@
                     <div class="col-sm-4">
                         <input class="form-control" id="ldapConnectionUrl" type="text" ng-model="realm.ldapServer.connectionUrl" placeholder="LDAP connection URL" required>
                     </div>
+                    <div class="col-sm-4" data-ng-show="access.manageRealm">
+                        <a class="btn btn-primary" data-ng-click="testConnection()">Test connection</a>
+                    </div>
                 </div>
                 <div class="form-group clearfix">
                     <label class="col-sm-2 control-label" for="ldapBaseDn">Base DN <span class="required">*</span></label>
@@ -64,6 +67,9 @@
                     <div class="col-sm-4">
                         <input class="form-control" id="ldapBindCredential" type="text" ng-model="realm.ldapServer.bindCredential" placeholder="LDAP Bind Credentials" required>
                     </div>
+                    <div class="col-sm-4" data-ng-show="access.manageRealm">
+                        <a class="btn btn-primary" data-ng-click="testAuthentication()">Test authentication</a>
+                    </div>
                 </div>
             </fieldset>
 
diff --git a/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java b/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java
index 70c149f..354bc86 100644
--- a/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java
+++ b/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java
@@ -17,8 +17,8 @@ import javax.naming.NamingException;
 import javax.naming.directory.DirContext;
 import javax.naming.directory.InitialDirContext;
 
+import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.RealmModel;
-import org.keycloak.picketlink.idm.LdapConstants;
 import org.picketbox.test.ldap.AbstractLDAPTest;
 
 /**
@@ -130,12 +130,12 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
 
     public void setupLdapInRealm(RealmModel realm) {
         Map<String,String> ldapConfig = new HashMap<String,String>();
-        ldapConfig.put(LdapConstants.CONNECTION_URL, getConnectionUrl());
-        ldapConfig.put(LdapConstants.BASE_DN, getBaseDn());
-        ldapConfig.put(LdapConstants.BIND_DN, getBindDn());
-        ldapConfig.put(LdapConstants.BIND_CREDENTIAL, getBindCredential());
-        ldapConfig.put(LdapConstants.USER_DN_SUFFIX, getUserDnSuffix());
-        ldapConfig.put(LdapConstants.VENDOR, getVendor());
+        ldapConfig.put(LDAPConstants.CONNECTION_URL, getConnectionUrl());
+        ldapConfig.put(LDAPConstants.BASE_DN, getBaseDn());
+        ldapConfig.put(LDAPConstants.BIND_DN, getBindDn());
+        ldapConfig.put(LDAPConstants.BIND_CREDENTIAL, getBindCredential());
+        ldapConfig.put(LDAPConstants.USER_DN_SUFFIX, getUserDnSuffix());
+        ldapConfig.put(LDAPConstants.VENDOR, getVendor());
         realm.setLdapServerConfig(ldapConfig);
     }
 
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
index 155b235..a59a12a 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
@@ -5,8 +5,6 @@ import java.util.Collections;
 
 import javax.ws.rs.core.MultivaluedMap;
 
-import org.jboss.resteasy.spi.ResteasyProviderFactory;
-import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.Before;
@@ -78,7 +76,7 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
         MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "password");
 
         // Set password of user in LDAP
-        LdapTestUtils.setLdapPassword(providerSession, realm, "johnkeycloak", "password");
+        LDAPTestUtils.setLdapPassword(providerSession, realm, "johnkeycloak", "password");
 
         // Verify that user doesn't exists in realm2 and can't authenticate here
         Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_USER, am.authenticateForm(null, realm, formData));
@@ -142,7 +140,7 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
         // Add ldap
         setupAuthenticationProviders();
 
-        LdapTestUtils.setLdapPassword(providerSession, realm, "johnkeycloak", "password");
+        LDAPTestUtils.setLdapPassword(providerSession, realm, "johnkeycloak", "password");
 
         // First authenticate successfully to sync john into realm
         MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "password");
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/KeycloakLDAPIdentityStore.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/KeycloakLDAPIdentityStore.java
index 8296854..124bb82 100644
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/KeycloakLDAPIdentityStore.java
+++ b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/KeycloakLDAPIdentityStore.java
@@ -22,7 +22,6 @@ import static org.picketlink.common.constants.LDAPConstants.EQUAL;
  */
 public class KeycloakLDAPIdentityStore extends LDAPIdentityStore {
 
-    public static Method GET_BINDING_DN_METHOD;
     public static Method GET_OPERATION_MANAGER_METHOD;
     public static Method CREATE_SEARCH_FILTER_METHOD;
     public static Method EXTRACT_ATTRIBUTES_METHOD;
@@ -31,7 +30,6 @@ public class KeycloakLDAPIdentityStore extends LDAPIdentityStore {
     public static final String SAM_ACCOUNT_NAME = "sAMAccountName";
 
     static {
-        GET_BINDING_DN_METHOD = getMethodOnLDAPStore("getBindingDN", AttributedType.class);
         GET_OPERATION_MANAGER_METHOD = getMethodOnLDAPStore("getOperationManager");
         CREATE_SEARCH_FILTER_METHOD = getMethodOnLDAPStore("createIdentityTypeSearchFilter", IdentityQuery.class, LDAPMappingConfiguration.class);
         EXTRACT_ATTRIBUTES_METHOD = getMethodOnLDAPStore("extractAttributes", AttributedType.class, boolean.class);
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
index 38827e7..6f13dac 100644
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
+++ b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
@@ -143,7 +143,7 @@ public class LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredenti
         }
     }
 
-    // TODO: remove later... It's needed just because LDAPIdentityStore.getBindingName, which always uses idProperty as first part of DN, but in AD it doesn't work as we may have idProperty 'sAMAccountName'
+    // TODO: remove later... It's needed just because LDAPIdentityStore.getBindingDN, which always uses idProperty as first part of DN, but in AD it doesn't work as we may have idProperty 'sAMAccountName'
     // but DN like: cn=John Doe,OU=foo,DC=bar
     protected String getDNOfUser(User user, IdentityManager identityManager, LDAPIdentityStore ldapStore, LDAPOperationManager operationManager) {
 
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java
index fb40f84..84a62c2 100644
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java
+++ b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java
@@ -6,10 +6,10 @@ import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.jboss.logging.Logger;
+import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.picketlink.idm.KeycloakLDAPIdentityStore;
 import org.keycloak.picketlink.idm.LDAPKeycloakCredentialHandler;
-import org.keycloak.picketlink.idm.LdapConstants;
 import org.picketlink.idm.PartitionManager;
 import org.picketlink.idm.config.AbstractIdentityStoreConfiguration;
 import org.picketlink.idm.config.IdentityConfiguration;
@@ -45,7 +45,7 @@ public class PartitionManagerRegistry {
         // Ldap config might have changed for the realm. In this case, we must re-initialize
         if (context == null || !ldapConfig.equals(context.config)) {
             logger.infof("Creating new partition manager for the realm: %s, LDAP Connection URL: %s, LDAP Base DN: %s, LDAP Vendor: %s", realm.getId(),
-                    ldapConfig.get(LdapConstants.CONNECTION_URL), ldapConfig.get(LdapConstants.BASE_DN), ldapConfig.get(LdapConstants.VENDOR));
+                    ldapConfig.get(LDAPConstants.CONNECTION_URL), ldapConfig.get(LDAPConstants.BASE_DN), ldapConfig.get(LDAPConstants.VENDOR));
             PartitionManager manager = createPartitionManager(ldapConfig);
             context = new PartitionManagerContext(ldapConfig, manager);
             partitionManagers.put(realm.getId(), context);
@@ -71,16 +71,16 @@ public class PartitionManagerRegistry {
         checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain");
         checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
 
-        String vendor = ldapConfig.get(LdapConstants.VENDOR);
+        String vendor = ldapConfig.get(LDAPConstants.VENDOR);
 
         // RHDS is using "nsuniqueid" as unique identifier instead of "entryUUID"
-        if (vendor != null && vendor.equals(LdapConstants.VENDOR_RHDS)) {
+        if (vendor != null && vendor.equals(LDAPConstants.VENDOR_RHDS)) {
             checkSystemProperty(LDAPIdentityStoreConfiguration.ENTRY_IDENTIFIER_ATTRIBUTE_NAME, "nsuniqueid");
         }
 
-        boolean activeDirectory = vendor != null && vendor.equals(LdapConstants.VENDOR_ACTIVE_DIRECTORY);
+        boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
 
-        String ldapLoginNameMapping = ldapConfig.get(LdapConstants.USERNAME_LDAP_ATTRIBUTE);
+        String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
         if (ldapLoginNameMapping == null) {
             ldapLoginNameMapping = activeDirectory ? CN : UID;
         }
@@ -100,14 +100,14 @@ public class PartitionManagerRegistry {
                     .ldap()
                         .connectionProperties(connectionProps)
                         .addCredentialHandler(LDAPKeycloakCredentialHandler.class)
-                        .baseDN(ldapConfig.get(LdapConstants.BASE_DN))
-                        .bindDN(ldapConfig.get(LdapConstants.BIND_DN))
-                        .bindCredential(ldapConfig.get(LdapConstants.BIND_CREDENTIAL))
-                        .url(ldapConfig.get(LdapConstants.CONNECTION_URL))
+                        .baseDN(ldapConfig.get(LDAPConstants.BASE_DN))
+                        .bindDN(ldapConfig.get(LDAPConstants.BIND_DN))
+                        .bindCredential(ldapConfig.get(LDAPConstants.BIND_CREDENTIAL))
+                        .url(ldapConfig.get(LDAPConstants.CONNECTION_URL))
                         .activeDirectory(activeDirectory)
                         .supportAllFeatures()
                         .mapping(User.class)
-                            .baseDN(ldapConfig.get(LdapConstants.USER_DN_SUFFIX))
+                            .baseDN(ldapConfig.get(LDAPConstants.USER_DN_SUFFIX))
                             .objectClasses("inetOrgPerson", "organizationalPerson")
                             .attribute("loginName", ldapLoginNameMapping, true)
                             .attribute("firstName", ldapFirstNameMapping)
diff --git a/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java b/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
new file mode 100644
index 0000000..a11fd74
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
@@ -0,0 +1,64 @@
+package org.keycloak.services.managers;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.ldap.InitialLdapContext;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPConnectionTestManager {
+
+    protected static final Logger logger = Logger.getLogger(LDAPConnectionTestManager.class);
+
+    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) {
+        if (!TEST_CONNECTION.equals(action) && !TEST_AUTHENTICATION.equals(action)) {
+            logger.error("Unknown action: " + action);
+            return false;
+        }
+
+        Context ldapContext = null;
+        try {
+            Hashtable<String, Object> env = new Hashtable<String, Object>();
+            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+            env.put(Context.SECURITY_AUTHENTICATION, "simple");
+
+            env.put(Context.PROVIDER_URL, connectionUrl);
+
+            if (TEST_AUTHENTICATION.equals(action)) {
+                env.put(Context.SECURITY_PRINCIPAL, bindDn);
+
+                char[] bindCredentialChar = null;
+                if (bindCredential != null) {
+                    bindCredentialChar = bindCredential.toCharArray();
+                }
+                env.put(Context.SECURITY_CREDENTIALS, bindCredentialChar);
+            }
+
+            ldapContext = new InitialLdapContext(env, null);
+            return true;
+        } catch (NamingException ne) {
+            String errorMessage = (TEST_AUTHENTICATION.equals(action)) ? "Error when authenticating to LDAP: " : "Error when connecting to LDAP: ";
+            logger.error(errorMessage + ne.getMessage(), ne);
+            return false;
+        } finally {
+            if (ldapContext != null) {
+                try {
+                    ldapContext.close();
+                } catch (NamingException ne) {
+                    logger.warn("Error when closing LDAP connection", 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 cc2903f..0a6059e 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -17,6 +17,7 @@ import org.keycloak.provider.ProviderSession;
 import org.keycloak.representations.adapters.action.SessionStats;
 import org.keycloak.representations.idm.RealmAuditRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.LDAPConnectionTestManager;
 import org.keycloak.services.managers.ModelToRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.ResourceAdminManager;
@@ -359,4 +360,14 @@ public class RealmAdminResource {
         AuditProvider audit = providers.getProvider(AuditProvider.class);
         audit.clear(realm.getId());
     }
+
+    @Path("testLDAPConnection")
+    @GET
+    public Response testLDAPConnection(@QueryParam("action") String action, @QueryParam("connectionUrl") String connectionUrl,
+                                       @QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential) {
+        auth.init(RealmAuth.Resource.REALM).requireManage();
+
+        boolean result = new LDAPConnectionTestManager().testLDAP(action, connectionUrl, bindDn, bindCredential);
+        return result ? Response.noContent().build() : Flows.errors().error("LDAP test error", Response.Status.BAD_REQUEST);
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
index 6f7469a..461b8a8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
@@ -9,7 +9,7 @@ import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 import org.junit.runners.MethodSorters;
 import org.keycloak.OAuth2Constants;
-import org.keycloak.model.test.LdapTestUtils;
+import org.keycloak.model.test.LDAPTestUtils;
 import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
@@ -62,7 +62,7 @@ public class AuthProvidersIntegrationTest {
 
             // Configure LDAP
             ldapRule.getEmbeddedServer().setupLdapInRealm(appRealm);
-            LdapTestUtils.setLdapPassword(providerSession, appRealm, "johnkeycloak", "password");
+            LDAPTestUtils.setLdapPassword(providerSession, appRealm, "johnkeycloak", "password");
         }
     });