diff --git a/federation/ldap/src/main/java/org/keycloak/services/managers/LDAPServerCapabilitiesManager.java b/federation/ldap/src/main/java/org/keycloak/services/managers/LDAPServerCapabilitiesManager.java index 36d955e80b..817009f013 100755 --- a/federation/ldap/src/main/java/org/keycloak/services/managers/LDAPServerCapabilitiesManager.java +++ b/federation/ldap/src/main/java/org/keycloak/services/managers/LDAPServerCapabilitiesManager.java @@ -71,11 +71,52 @@ public class LDAPServerCapabilitiesManager { 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())) { ServicesLogger.LOGGER.unknownAction(config.getAction()); - return false; + throw new javax.naming.NamingException("testLDAP unknown action"); } 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 // tls is true if ((config.getBindDn() == null || config.getBindDn().isEmpty()) && LDAPConstants.AUTH_TYPE_SIMPLE.equals(config.getAuthType())) { - logger.error("Unknown bind DN"); - return false; + throw new InvalidBindDNException("Unknown bind DN"); } } else { // only test the connection. @@ -97,14 +137,11 @@ public class LDAPServerCapabilitiesManager { // is not needed anymore try (LDAPContextManager ldapContextManager = LDAPContextManager.create(session, ldapConfig)) { ldapContextManager.getLdapContext(); - - // Connection was successful, no exception was raised returning true - return true; } catch (Exception ne) { String errorMessage = (TEST_AUTHENTICATION.equals(config.getAction())) ? "Error when authenticating to LDAP: " : "Error when connecting to LDAP: "; ServicesLogger.LOGGER.errorAuthenticating(ne, errorMessage + ne.getMessage()); - return false; + throw ne; } } } diff --git a/federation/ldap/src/main/java/org/keycloak/services/resources/admin/TestLdapConnectionResource.java b/federation/ldap/src/main/java/org/keycloak/services/resources/admin/TestLdapConnectionResource.java index 058ff652f5..a14cb7be34 100644 --- a/federation/ldap/src/main/java/org/keycloak/services/resources/admin/TestLdapConnectionResource.java +++ b/federation/ldap/src/main/java/org/keycloak/services/resources/admin/TestLdapConnectionResource.java @@ -72,10 +72,13 @@ public class TestLdapConnectionResource { TestLdapConnectionRepresentation config = new TestLdapConnectionRepresentation(action, connectionUrl, bindDn, bindCredential, useTruststoreSpi, connectionTimeout, startTls, LDAPConstants.AUTH_TYPE_SIMPLE); config.setComponentId(componentId); - if (! LDAPServerCapabilitiesManager.testLDAP(config, session, realm)) { - throw ErrorResponse.error("LDAP test error", Response.Status.BAD_REQUEST); + try { + LDAPServerCapabilitiesManager.testLDAP(config, session, realm); + return Response.noContent().build(); + } catch(Exception e) { + String errorMsg = LDAPServerCapabilitiesManager.getErrorCode(e); + throw ErrorResponse.error(errorMsg, Response.Status.BAD_REQUEST); } - return Response.noContent().build(); } /** @@ -86,10 +89,13 @@ public class TestLdapConnectionResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public Response testLDAPConnection(TestLdapConnectionRepresentation config) { - if (! LDAPServerCapabilitiesManager.testLDAP(config, session, realm)) { - throw ErrorResponse.error("LDAP test error", Response.Status.BAD_REQUEST); + try { + LDAPServerCapabilitiesManager.testLDAP(config, session, realm); + return Response.noContent().build(); + } catch(Exception e) { + String errorMsg = LDAPServerCapabilitiesManager.getErrorCode(e); + throw ErrorResponse.error(errorMsg, Response.Status.BAD_REQUEST); } - return Response.noContent().build(); } } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPContextManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPContextManager.java index 045b0ddfd4..50c37fb275 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPContextManager.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPContextManager.java @@ -123,7 +123,9 @@ public final class LDAPContextManager implements AutoCloseable { } } catch (Exception 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 diff --git a/js/apps/admin-ui/cypress/e2e/user_fed_ldap_test.spec.ts b/js/apps/admin-ui/cypress/e2e/user_fed_ldap_test.spec.ts index 262626d021..124af90004 100644 --- a/js/apps/admin-ui/cypress/e2e/user_fed_ldap_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/user_fed_ldap_test.spec.ts @@ -93,8 +93,7 @@ const userImportingDisabledFailMessage = "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 ldapTestFailMsg = - "Error when trying to connect to LDAP. See server.log for details. LDAP test error"; +const ldapTestFailMsg = "Error when trying to connect to LDAP: 'SocketReset'"; describe("User Federation LDAP tests", () => { beforeEach(() => { diff --git a/js/apps/admin-ui/public/locales/en/user-federation.json b/js/apps/admin-ui/public/locales/en/user-federation.json index cb178ffc9f..a6510d3276 100644 --- a/js/apps/admin-ui/public/locales/en/user-federation.json +++ b/js/apps/admin-ui/public/locales/en/user-federation.json @@ -80,7 +80,7 @@ "queryExtensions": "Query Supported Extensions", "testAuthentication": "Test authentication", "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", "managePriorities": "Manage priorities", "managePriorityOrder": "Manage priority order", diff --git a/model/legacy-services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java b/model/legacy-services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java index 19137d493f..aa47bb26f0 100644 --- a/model/legacy-services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java +++ b/model/legacy-services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java @@ -26,6 +26,8 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.services.ServicesLogger; 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.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProviderModel; @@ -42,6 +44,7 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.util.HashMap; import java.util.Map; @@ -74,6 +77,15 @@ public class UserStorageProviderResource { 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 * @@ -138,9 +150,19 @@ public class UserStorageProviderResource { UserStorageSyncManager syncManager = new UserStorageSyncManager(); SynchronizationResult syncResult; if ("triggerFullSync".equals(action)) { - syncResult = syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), providerModel); + try { + 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)) { - syncResult = syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), providerModel); + try { + 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 == "") { logger.debug("Missing action"); throw new BadRequestException("Missing action");