Improve error messages when testing LDAP connection (#21013)

Closes #15434
This commit is contained in:
Stijn Last 2023-07-01 19:45:49 +02:00 committed by GitHub
parent 0fc4d6120b
commit 91e543f415
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 20 deletions

View file

@ -71,11 +71,52 @@ public class LDAPServerCapabilitiesManager {
return new LDAPIdentityStore(session, ldapConfig).queryServerCapabilities(); return new LDAPIdentityStore(session, ldapConfig).queryServerCapabilities();
} }
public static boolean testLDAP(TestLdapConnectionRepresentation config, KeycloakSession session, RealmModel realm) { public static class InvalidBindDNException extends javax.naming.NamingException {
public InvalidBindDNException(String s) {
super(s);
}
}
public static String getErrorCode(Throwable throwable) {
String errorMsg = "UnknownError";
if (throwable instanceof javax.naming.NamingException)
errorMsg = "NamingError";
if (throwable instanceof javax.naming.AuthenticationException)
errorMsg = "AuthenticationFailure";
if (throwable instanceof javax.naming.CommunicationException)
errorMsg = "CommunicationError";
if (throwable instanceof javax.naming.ServiceUnavailableException)
errorMsg = "ServiceUnavailable";
if (throwable instanceof javax.naming.InvalidNameException)
errorMsg = "InvalidName";
if (throwable instanceof javax.naming.ServiceUnavailableException)
errorMsg = "ServiceUnavailable";
if (throwable instanceof InvalidBindDNException)
errorMsg = "InvalidBindDN";
if (throwable instanceof javax.naming.NamingException) {
Throwable rootCause = ((javax.naming.NamingException)throwable).getRootCause();
if (rootCause instanceof java.net.MalformedURLException)
errorMsg = "MalformedURL";
if (rootCause instanceof java.net.NoRouteToHostException)
errorMsg = "NoRouteToHost";
if (rootCause instanceof java.net.ConnectException)
errorMsg = "ConnectionFailed";
if (rootCause instanceof java.net.UnknownHostException)
errorMsg = "UnknownHost";
if (rootCause instanceof javax.net.ssl.SSLHandshakeException)
errorMsg = "SSLHandshakeFailed";
if (rootCause instanceof java.net.SocketException)
errorMsg = "SocketReset";
}
return errorMsg;
}
public static void testLDAP(TestLdapConnectionRepresentation config, KeycloakSession session, RealmModel realm) throws javax.naming.NamingException {
if (!TEST_CONNECTION.equals(config.getAction()) && !TEST_AUTHENTICATION.equals(config.getAction())) { if (!TEST_CONNECTION.equals(config.getAction()) && !TEST_AUTHENTICATION.equals(config.getAction())) {
ServicesLogger.LOGGER.unknownAction(config.getAction()); ServicesLogger.LOGGER.unknownAction(config.getAction());
return false; throw new javax.naming.NamingException("testLDAP unknown action");
} }
if (TEST_AUTHENTICATION.equals(config.getAction())) { if (TEST_AUTHENTICATION.equals(config.getAction())) {
@ -83,8 +124,7 @@ public class LDAPServerCapabilitiesManager {
// LDAPContextManager is responsible for correct order of addition of credentials to context in case // LDAPContextManager is responsible for correct order of addition of credentials to context in case
// tls is true // tls is true
if ((config.getBindDn() == null || config.getBindDn().isEmpty()) && LDAPConstants.AUTH_TYPE_SIMPLE.equals(config.getAuthType())) { if ((config.getBindDn() == null || config.getBindDn().isEmpty()) && LDAPConstants.AUTH_TYPE_SIMPLE.equals(config.getAuthType())) {
logger.error("Unknown bind DN"); throw new InvalidBindDNException("Unknown bind DN");
return false;
} }
} else { } else {
// only test the connection. // only test the connection.
@ -97,14 +137,11 @@ public class LDAPServerCapabilitiesManager {
// is not needed anymore // is not needed anymore
try (LDAPContextManager ldapContextManager = LDAPContextManager.create(session, ldapConfig)) { try (LDAPContextManager ldapContextManager = LDAPContextManager.create(session, ldapConfig)) {
ldapContextManager.getLdapContext(); ldapContextManager.getLdapContext();
// Connection was successful, no exception was raised returning true
return true;
} catch (Exception ne) { } catch (Exception ne) {
String errorMessage = (TEST_AUTHENTICATION.equals(config.getAction())) ? "Error when authenticating to LDAP: " String errorMessage = (TEST_AUTHENTICATION.equals(config.getAction())) ? "Error when authenticating to LDAP: "
: "Error when connecting to LDAP: "; : "Error when connecting to LDAP: ";
ServicesLogger.LOGGER.errorAuthenticating(ne, errorMessage + ne.getMessage()); ServicesLogger.LOGGER.errorAuthenticating(ne, errorMessage + ne.getMessage());
return false; throw ne;
} }
} }
} }

View file

@ -72,10 +72,13 @@ public class TestLdapConnectionResource {
TestLdapConnectionRepresentation config = new TestLdapConnectionRepresentation(action, connectionUrl, bindDn, bindCredential, useTruststoreSpi, connectionTimeout, startTls, LDAPConstants.AUTH_TYPE_SIMPLE); TestLdapConnectionRepresentation config = new TestLdapConnectionRepresentation(action, connectionUrl, bindDn, bindCredential, useTruststoreSpi, connectionTimeout, startTls, LDAPConstants.AUTH_TYPE_SIMPLE);
config.setComponentId(componentId); config.setComponentId(componentId);
if (! LDAPServerCapabilitiesManager.testLDAP(config, session, realm)) { try {
throw ErrorResponse.error("LDAP test error", Response.Status.BAD_REQUEST); LDAPServerCapabilitiesManager.testLDAP(config, session, realm);
}
return Response.noContent().build(); return Response.noContent().build();
} catch(Exception e) {
String errorMsg = LDAPServerCapabilitiesManager.getErrorCode(e);
throw ErrorResponse.error(errorMsg, Response.Status.BAD_REQUEST);
}
} }
/** /**
@ -86,10 +89,13 @@ public class TestLdapConnectionResource {
@NoCache @NoCache
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Response testLDAPConnection(TestLdapConnectionRepresentation config) { public Response testLDAPConnection(TestLdapConnectionRepresentation config) {
if (! LDAPServerCapabilitiesManager.testLDAP(config, session, realm)) { try {
throw ErrorResponse.error("LDAP test error", Response.Status.BAD_REQUEST); LDAPServerCapabilitiesManager.testLDAP(config, session, realm);
}
return Response.noContent().build(); return Response.noContent().build();
} catch(Exception e) {
String errorMsg = LDAPServerCapabilitiesManager.getErrorCode(e);
throw ErrorResponse.error(errorMsg, Response.Status.BAD_REQUEST);
}
} }
} }

View file

@ -123,7 +123,9 @@ public final class LDAPContextManager implements AutoCloseable {
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Could not negotiate TLS", e); logger.error("Could not negotiate TLS", e);
throw new AuthenticationException("Could not negotiate TLS"); NamingException ne = new AuthenticationException("Could not negotiate TLS");
ne.setRootCause(e);
throw ne;
} }
// throws AuthenticationException when authentication fails // throws AuthenticationException when authentication fails

View file

@ -93,8 +93,7 @@ const userImportingDisabledFailMessage =
"User federation provider could not be saved: Can not disable Importing users when LDAP provider mode is UNSYNCED"; "User federation provider could not be saved: Can not disable Importing users when LDAP provider mode is UNSYNCED";
const ldapTestSuccessMsg = "Successfully connected to LDAP"; const ldapTestSuccessMsg = "Successfully connected to LDAP";
const ldapTestFailMsg = const ldapTestFailMsg = "Error when trying to connect to LDAP: 'SocketReset'";
"Error when trying to connect to LDAP. See server.log for details. LDAP test error";
describe("User Federation LDAP tests", () => { describe("User Federation LDAP tests", () => {
beforeEach(() => { beforeEach(() => {

View file

@ -80,7 +80,7 @@
"queryExtensions": "Query Supported Extensions", "queryExtensions": "Query Supported Extensions",
"testAuthentication": "Test authentication", "testAuthentication": "Test authentication",
"testSuccess": "Successfully connected to LDAP", "testSuccess": "Successfully connected to LDAP",
"testError": "Error when trying to connect to LDAP. See server.log for details. {{error}}", "testError": "Error when trying to connect to LDAP: '{{error}}'",
"learnMore": "Learn more", "learnMore": "Learn more",
"managePriorities": "Manage priorities", "managePriorities": "Manage priorities",
"managePriorityOrder": "Manage priority order", "managePriorityOrder": "Manage priority order",

View file

@ -26,6 +26,8 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.storage.managers.UserStorageSyncManager; import org.keycloak.storage.managers.UserStorageSyncManager;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.managers.LDAPServerCapabilitiesManager;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.UserStorageProviderModel;
@ -42,6 +44,7 @@ import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -74,6 +77,15 @@ public class UserStorageProviderResource {
this.headers = session.getContext().getRequestHeaders(); this.headers = session.getContext().getRequestHeaders();
} }
public static String getErrorCode(Throwable throwable) {
if (throwable instanceof org.keycloak.models.ModelException) {
if (throwable.getCause() != null) {
return getErrorCode(throwable.getCause());
}
}
return LDAPServerCapabilitiesManager.getErrorCode(throwable);
}
/** /**
* Need this for admin console to display simple name of provider when displaying user detail * Need this for admin console to display simple name of provider when displaying user detail
* *
@ -138,9 +150,19 @@ public class UserStorageProviderResource {
UserStorageSyncManager syncManager = new UserStorageSyncManager(); UserStorageSyncManager syncManager = new UserStorageSyncManager();
SynchronizationResult syncResult; SynchronizationResult syncResult;
if ("triggerFullSync".equals(action)) { if ("triggerFullSync".equals(action)) {
try {
syncResult = syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), providerModel); syncResult = syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), providerModel);
} catch(Exception e) {
String errorMsg = getErrorCode(e);
throw ErrorResponse.error(errorMsg, Response.Status.BAD_REQUEST);
}
} else if ("triggerChangedUsersSync".equals(action)) { } else if ("triggerChangedUsersSync".equals(action)) {
try {
syncResult = syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), providerModel); syncResult = syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), providerModel);
} catch(Exception e) {
String errorMsg = getErrorCode(e);
throw ErrorResponse.error(errorMsg, Response.Status.BAD_REQUEST);
}
} else if (action == null || action == "") { } else if (action == null || action == "") {
logger.debug("Missing action"); logger.debug("Missing action");
throw new BadRequestException("Missing action"); throw new BadRequestException("Missing action");