package io.cattle.platform.iaas.api.auth.integration.ldap; import io.cattle.platform.iaas.api.auth.SecurityConstants; import io.cattle.platform.iaas.api.auth.integration.ldap.interfaces.LDAPConstants; import io.github.ibuildthecloud.gdapi.exception.ClientVisibleException; import io.github.ibuildthecloud.gdapi.util.ResponseCodes; import java.net.ConnectException; import java.util.Hashtable; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.Context; import javax.naming.CommunicationException; import javax.naming.NamingException; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import javax.naming.ldap.LdapName; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class LDAPUtils { private static final Log logger = LogFactory.getLog(LDAPUtils.class); private static final String INVALID_OPEN_LDAP_CONFIG = "InvalidLDAPConfig"; private static final Pattern LDAP_ERROR_CODE = Pattern.compile("data +[57][2307][01235e]"); /** * @throws ClientVisibleException In the event that the provided configuration is invalid. This allows the user to fix the config * and resubmit it, without locking them out of cattle. */ public static void validateConfig(LDAPConstants ldapConfig) { if (ldapConfig.getEnabled() == null || !ldapConfig.getEnabled() || SecurityConstants.SECURITY.get()) { return; } if (StringUtils.isBlank(ldapConfig.getServiceAccountUsername()) || StringUtils.isBlank(ldapConfig.getServiceAccountPassword())) { throw new ClientVisibleException(ResponseCodes.BAD_REQUEST, "InvalidLDAPConfig", "Must define service account.", "Cannot have a config with blank service account user name or password."); } LdapContext userContext = null; try { Hashtable<String, String> props = new Hashtable<>(); props.put(Context.SECURITY_AUTHENTICATION, "simple"); String username = ldapConfig.getServiceAccountUsername(); if (StringUtils.isNotBlank(ldapConfig.getLoginDomain()) && !username.contains("\\")) { username = ldapConfig.getLoginDomain() + '\\' +username; } props.put(Context.SECURITY_PRINCIPAL, username); props.put(Context.SECURITY_CREDENTIALS, ldapConfig.getServiceAccountPassword()); props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); props.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(ldapConfig.getConnectionTimeout())); String url = "ldap://" + ldapConfig.getServer() + ':' + String.valueOf(ldapConfig.getPort()) + '/'; props.put(Context.PROVIDER_URL, url); if (ldapConfig.getTls()) { props.put(Context.SECURITY_PROTOCOL, "ssl"); } userContext = new InitialLdapContext(props, null); try { userContext.getAttributes(new LdapName(ldapConfig.getDomain())); } catch (NamingException e) { throw new ClientVisibleException(ResponseCodes.BAD_REQUEST, INVALID_OPEN_LDAP_CONFIG, "Invalid ldap search base.", "Unable to get provided Ldap Search base: " + ldapConfig.getDomain()); } } catch (NamingException e) { if (e.getRootCause() instanceof ConnectException){ throw new ClientVisibleException(ResponseCodes.BAD_REQUEST, INVALID_OPEN_LDAP_CONFIG, "Unable to talk to ldap.", "Provided server and port refused connection."); } throw new ClientVisibleException(ResponseCodes.BAD_REQUEST, INVALID_OPEN_LDAP_CONFIG, "Unable to authenticate service account.", "Unable to create context with service account credentials." + "Username(DN):" + ldapConfig.getServiceAccountUsername() + " Password:" + ldapConfig.getServiceAccountPassword()); }finally { if (userContext != null) { try { userContext.close(); } catch (NamingException e) { logger.info("Failed to close Test service context.", e); } } } } public static String errorCodeToDescription(NamingException code){ String errorCode = code.getExplanation(); Matcher m = LDAP_ERROR_CODE.matcher(errorCode); if (m.find()) { errorCode = m.group(0).substring(m.group(0).length()-3); } switch (errorCode) { case "525": return "User not found"; case "52e": return "Invalid credentials"; case "530": return "Not permitted to logon at this time"; case "531": return "Not permitted to logon at this workstation"; case "532": return "Password expired (remember to check the user set in osuser.xml also)"; case "533": return "Account disabled"; case "701": return "Account expired"; case "773": return "User must reset password"; case "775": return "User account locked"; default: return errorCode; } } public static boolean isRecoverable(NamingException e) { if (e instanceof CommunicationException) { return false; } return true; } }