Support for test of LDAP connection and authentication
This commit is contained in:
parent
159a5e20bf
commit
b1cfab34fd
14 changed files with 142 additions and 33 deletions
|
@ -168,7 +168,7 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmLdapSettingsCtrl'
|
||||
controller : 'RealmLDAPSettingsCtrl'
|
||||
})
|
||||
.when('/realms/:realm/audit', {
|
||||
templateUrl : 'partials/realm-audit.html',
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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";
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue