Support for test of LDAP connection and authentication

This commit is contained in:
mposolda 2014-06-23 23:19:15 +02:00
parent 159a5e20bf
commit b1cfab34fd
14 changed files with 142 additions and 33 deletions

View file

@ -168,7 +168,7 @@ module.config([ '$routeProvider', function($routeProvider) {
return RealmLoader();
}
},
controller : 'RealmLdapSettingsCtrl'
controller : 'RealmLDAPSettingsCtrl'
})
.when('/realms/:realm/audit', {
templateUrl : 'partials/realm-audit.html',

View file

@ -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) {

View file

@ -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');
});

View file

@ -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>

View file

@ -1,9 +1,9 @@
package org.keycloak.picketlink.idm;
package org.keycloak.models;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LdapConstants {
public class LDAPConstants {
public static final String VENDOR = "vendor";
public static final String VENDOR_RHDS = "rhds";

View file

@ -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);
}

View file

@ -12,7 +12,7 @@ import org.picketlink.idm.model.basic.User;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LdapTestUtils {
public class LDAPTestUtils {
public static void setLdapPassword(ProviderSession providerSession, RealmModel realm, String username, String password) {
// Update password directly in ldap. It's workaround, but LDIF import doesn't seem to work on windows for ApacheDS

View file

@ -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");

View file

@ -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);

View file

@ -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) {

View file

@ -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)

View file

@ -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);
}
}
}
}
}

View file

@ -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);
}
}

View file

@ -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");
}
});