From be34fa9e8dadf9cb7a6c77e53af76c3e1d199de5 Mon Sep 17 00:00:00 2001 From: Joern Muehlencord Date: Tue, 28 May 2019 17:41:10 +0200 Subject: [PATCH] added LDAP search support --- .../shared/network/ldap/LDAPConnection.java | 111 ++++++ .../shared/network/ldap/LDAPContact.java | 121 ++++++ .../shared/network/ldap/LDAPException.java | 22 ++ .../shared/network/ldap/LDAPSearch.java | 360 ++++++++++++++++++ 4 files changed, 614 insertions(+) create mode 100644 network/src/main/java/de/muehlencord/shared/network/ldap/LDAPConnection.java create mode 100644 network/src/main/java/de/muehlencord/shared/network/ldap/LDAPContact.java create mode 100644 network/src/main/java/de/muehlencord/shared/network/ldap/LDAPException.java create mode 100644 network/src/main/java/de/muehlencord/shared/network/ldap/LDAPSearch.java diff --git a/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPConnection.java b/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPConnection.java new file mode 100644 index 0000000..de8ece2 --- /dev/null +++ b/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPConnection.java @@ -0,0 +1,111 @@ +package de.muehlencord.shared.network.ldap; + +import java.util.Hashtable; +import javax.naming.Context; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.InitialLdapContext; +import javax.naming.ldap.LdapContext; + +/** + * Inits and holds a connection to an ldap address directory + * @see javax.naming.ldap.LdapContext; + * @author dennis.nobel + */ +public class LDAPConnection { + + public static final String CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; + public static final String CONTROL_FACTORY = "com.sun.jndi.ldap.ControlFactory"; + public static final String AUTHENTICATION_SIMPLE = "simple"; + public static final String SECURITYPROTOCOL_SIMPLE = "simple"; + private String authentication; + private String providerUrl; + private String securityProtocol; + private String username; + private String password; + private LdapContext ldapContext; + + public LDAPConnection(String authentication, String providerUrl, String securityProtocol, String username, String password) { + this.authentication = authentication; + this.providerUrl = providerUrl; + this.securityProtocol = securityProtocol; + this.username = username; + this.password = password; + } + + public String getAuthentication() { + return authentication; + } + + public void setAuthentication(String authentication) { + this.authentication = authentication; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getProviderUrl() { + return providerUrl; + } + + public void setProviderUrl(String providerUrl) { + this.providerUrl = providerUrl; + } + + public String getSecurityProtocol() { + return securityProtocol; + } + + public void setSecurityProtocol(String securityProtocol) { + this.securityProtocol = securityProtocol; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + /** + * intializes connection to AD + * @throws NamingException is thrown if connection to LDAP failed + */ + protected void init() throws NamingException { + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY); + env.put(LdapContext.CONTROL_FACTORIES, CONTROL_FACTORY); + + env.put(Context.PROVIDER_URL, providerUrl); + env.put(Context.SECURITY_PRINCIPAL, username); + env.put(Context.SECURITY_CREDENTIALS, password); + env.put(Context.SECURITY_AUTHENTICATION, authentication); + env.put(Context.SECURITY_PROTOCOL, securityProtocol); + ldapContext = new InitialLdapContext(env, null); + } + + /** + * @see javax.naming.ldap.LdapContext; + */ + public NamingEnumeration search(String name, String filter, SearchControls cons) throws NamingException { + return ldapContext.search(name, filter, cons); + } + + /** + * @see javax.naming.ldap.LdapContext; + */ + public void close() throws NamingException { + if (ldapContext != null) { + ldapContext.close(); + ldapContext = null; + } + } +} diff --git a/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPContact.java b/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPContact.java new file mode 100644 index 0000000..57fca1c --- /dev/null +++ b/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPContact.java @@ -0,0 +1,121 @@ +package de.muehlencord.shared.network.ldap; + +/** + * Represents a contact in ldap address directory + * + * @author Joern Muehlencord + */ +public class LDAPContact { + + /** + * type person + */ + public static final String TYPE_PERSON = "top, person"; + /** + * type group + */ + public static final String TYPE_GROUP = "top, group"; + /** + * type public folder + */ + public static final String TYPE_PUBLICFOLDER = "top, publicFolder"; + private String type = null; + private String lastname = null; + private String firstname = null; + private String crmname = null; + private String emailaddress = null; + private String department = null; + private String phone = null; + private String country = null; + private String countryCode = null; + private boolean isEnabled = false; + private String distinguishedName = null; + + public String getType() { + return type; + } + + public void setType(String t) { + this.type = t; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getCountryCode() { + return countryCode; + } + + public void setCountryCode(String countryCode) { + this.countryCode = countryCode; + } + + public String getCrmname() { + return crmname; + } + + public void setCrmname(String crmname) { + this.crmname = crmname; + } + + public String getDepartment() { + return department; + } + + public void setDepartment(String department) { + this.department = department; + } + + public String getEmailaddress() { + return emailaddress; + } + + public void setEmailaddress(String emailaddress) { + this.emailaddress = emailaddress; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public boolean isEnabled() { + return isEnabled; + } + + public void setEnabled(boolean isEnabled) { + this.isEnabled = isEnabled; + } + + public String getDistinguishedName() { + return this.distinguishedName; + } + + public void setDistinguishedName(String distinguishedName) { + this.distinguishedName = distinguishedName.trim(); + } +} diff --git a/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPException.java b/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPException.java new file mode 100644 index 0000000..f7694b6 --- /dev/null +++ b/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPException.java @@ -0,0 +1,22 @@ +package de.muehlencord.shared.network.ldap; + +/** + * Exception used as wrapper for exepctions in ldap context + * + * @author Joern Muehlencord + */ +public class LDAPException extends Exception { + + /** + * the serial version uid + */ + private static final long serialVersionUID = -6399987658718847425L; + + public LDAPException(String message) { + super(message); + } + + public LDAPException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPSearch.java b/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPSearch.java new file mode 100644 index 0000000..e0fd998 --- /dev/null +++ b/network/src/main/java/de/muehlencord/shared/network/ldap/LDAPSearch.java @@ -0,0 +1,360 @@ +package de.muehlencord.shared.network.ldap; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +/** + * Connection to ldap server to searh by different values + * + * @author Joern Muehlencord + */ +public class LDAPSearch { + + /** + * the ldap connection to use + */ + private LDAPConnection ldapConnection; + /** + * the search base for all queries + */ + private String searchBase; + + /** + * Creates a new instance of a ldap search. + * + *

+ * Important:
If you want to use ldaps - usually port 636 make sure you + * provide a trustkeystore in case your ldap server does not use a + * certificate which can be trusted by the build in root certificates. (e.g. + * self signed certificates)

+ * + *

+ * To provide access to a trust center you can specify the following + * parameter to your application by providing the following parameter + *

+     * -Djavax.net.ssl.trustStore=/path/to/truststore.keystore
+     * 

+ * + * @param url the url of the ldap server to connect to like + * ldap://ldapserver.your.domain:389 + * @param searchBase the search base to use - e.g. + * DC=wincor-nixdorf,DC=com + * @param username the username to connect with + * @param password the password to connect with + */ + public LDAPSearch(String url, String searchBase, String username, String password) { + String authentication = LDAPConnection.AUTHENTICATION_SIMPLE; + String securityProtocol = LDAPConnection.SECURITYPROTOCOL_SIMPLE; + + this.ldapConnection = new LDAPConnection(authentication, url, securityProtocol, username, password); + this.searchBase = searchBase; + } + + /** + * Creates a new instance of a ldap search. + * + *

+ * Important:
If you want to use ldaps - usually port 636 make sure you + * provide a trustkeystore in case your ldap server does not use a + * certificate which can be trusted by the build in root certificates. (e.g. + * self signed certificates)

+ * + *

+ * To provide access to a trust center you can specify the following + * parameter to your application by providing the following parameter + *

+     * -Djavax.net.ssl.trustStore=/path/to/truststore.keystore
+     * 

+ * + * @param authentication the authentification type to use -e.g. "SIMPLE" + * @param url the url of the ldap server to connect to like + * ldap://ldapserver.your.domain:389 + * @param securityProtoco the security protocol to use - e.g. SIMPLE + * @param searchBase the search base to use - e.g. + * DC=wincor-nixdorf,DC=com + * @param username the username to connect with + * @param password the password to connect with + */ + public LDAPSearch(String authentication, String url, String securityProtocol, String searchBase, String username, String password) { + this.ldapConnection = new LDAPConnection(authentication, url, securityProtocol, username, password); + this.searchBase = searchBase; + } + + /** + * execute several init steps, connect to ldap + */ + public void init() throws LDAPException { + try { + ldapConnection.init(); + } catch (NamingException ex) { + throw new LDAPException("Connection refused.", ex); + } + } + + /** + * close the ldap connection + */ + public void close() throws LDAPException { + if (ldapConnection != null) { + try { + ldapConnection.close(); + ldapConnection = null; + } catch (NamingException ex) { + throw new LDAPException("Connection could not be closed.", ex); + } + } + } + + /** + * Returns the search base of the ldap connection + * + * @return the search base of the ldap connection + */ + public String getSearchBase() { + return searchBase; + } + + /** + * Searches a contact according to emailaddress in the address directory + * + * @param email emailaddress to search for + * @return ldap contact or null if nothing could be found + * @throws LDAPException when search fails + */ + public LDAPContact searchContactWithEmail(String email) throws LDAPException { + return searchContact("mail", email); + } + + public LDAPContact searchContact(String searchField, String searchValue) throws LDAPException { + + if (ldapConnection == null) { + throw new LDAPException("No connection established. Please execute init before.", null); + } + + // prepare search parameters + String[] resultattributes = {"objectClass", "name", "givenName", "sn", "department", "co", "telephoneNumber", "sAMAccountName", "c", + "userAccountControl", "managedBy", "distinguishedName", "mail"}; + String searchfilter = "(" + searchField + "=" + searchValue + ")"; + SearchControls searchcontrols = new SearchControls(); + String[] resultAttributes = resultattributes; + searchcontrols.setReturningAttributes(resultAttributes); + searchcontrols.setSearchScope(SearchControls.SUBTREE_SCOPE); + + NamingEnumeration result; + try { + // search + result = ldapConnection.search(searchBase, searchfilter, searchcontrols); + + // process result + Attributes attributes; + if (result.hasMoreElements()) { + SearchResult searchresult = (SearchResult) result.next(); + attributes = searchresult.getAttributes(); + } else { + // clearly nothing found + return null; + } + + // create contact from search attributes + LDAPContact ldapContact = createLDAPContact(attributes); + return ldapContact; + } catch (NamingException ex) { + throw new LDAPException("Search failed for unkown reason.", ex); + } + + } + + /** + * Returns true, if the given email address can be found in the configured + * ldap + * + * @param email the emailaddress to search for + * @return true, if the email address could be found; else false + * @throws LDAPException if the search fails + */ + public boolean emailExists(String email) throws LDAPException { + return searchContactWithEmail(email) != null; + } + + /** + * Returns true, if the given email address is member of the given group, + * specified by the DN + * + * @param email the email to validat + * @param groupDn the group search base - all members must be found as + * "member" in this group + * @return + */ + public boolean isMemberOfGroup(String email, String groupDn) throws LDAPException { + boolean returnValue = false; + + LDAPContact contact = searchContactWithEmail(email); + if (contact == null) { + return false; + } + + // prepare search parameters + String[] resultattributes = {"member"}; + String searchfilter = "(objectClass=*)"; + SearchControls searchcontrols = new SearchControls(); + String[] resultAttributes = resultattributes; + searchcontrols.setReturningAttributes(resultAttributes); + searchcontrols.setSearchScope(SearchControls.SUBTREE_SCOPE); + + NamingEnumeration result; + try { + result = ldapConnection.search(groupDn, searchfilter, searchcontrols); + while (result.hasMoreElements()) { + SearchResult searchresult = (SearchResult) result.next(); + Attributes attributes = searchresult.getAttributes(); + if ((attributes.get("member")) != null) { + String memberList = attributes.get("member").toString(); + if (memberList == null) { + return false; + } + returnValue = memberList.contains(contact.getDistinguishedName()); + } else { + // if + return false; + } + } + } catch (NamingException ex) { + throw new LDAPException(ex.getMessage(), ex); + } + + return returnValue; + } + + private LDAPContact createLDAPContact(Attributes attributes) throws LDAPException { + + LDAPContact ldapContact = new LDAPContact(); + + if (attributes.get("mail") != null) { + ldapContact.setEmailaddress(attributes.get("mail").toString()); + } else { + ldapContact.setEmailaddress(""); + } + + if (attributes.get("objectClass") != null) { + String objectClass = attributes.get("objectClass").toString(); + if (objectClass.contains(":")) { + objectClass = objectClass.substring(objectClass.indexOf(":") + 2); + } + if (objectClass.startsWith(LDAPContact.TYPE_PERSON)) { + ldapContact.setType(LDAPContact.TYPE_PERSON); + } else if (objectClass.startsWith(LDAPContact.TYPE_PUBLICFOLDER)) { + ldapContact.setType(LDAPContact.TYPE_PUBLICFOLDER); + } else if (objectClass.startsWith(LDAPContact.TYPE_GROUP)) { + ldapContact.setType(LDAPContact.TYPE_GROUP); + } else { + throw new LDAPException("Invalid objectClass " + objectClass + " found. ", null); + } + } + switch (ldapContact.getType()) { + case LDAPContact.TYPE_PERSON: + // handle persons + // get mandatory fields from ad entry + if (attributes.get("distinguishedName") != null) { + String distinguishedName = attributes.get("distinguishedName").toString(); + distinguishedName = distinguishedName.substring(distinguishedName.indexOf(":") + 2); + ldapContact.setDistinguishedName(distinguishedName); + } + if (attributes.get("givenName") != null) { + String firstname = attributes.get("givenName").toString(); + firstname = firstname.substring(firstname.indexOf(":") + 2); + ldapContact.setFirstname(firstname); + } + if (attributes.get("sn") != null) { + String lastname = attributes.get("sn").toString(); + lastname = lastname.substring(lastname.indexOf(":") + 2); + ldapContact.setLastname(lastname); + } + if (attributes.get("c") != null) { + String countryCode = attributes.get("c").toString(); + countryCode = countryCode.substring(countryCode.indexOf(":") + 2); + ldapContact.setCountryCode(countryCode); + + } + if (attributes.get("co") != null) { + String country = attributes.get("co").toString(); + country = country.substring(country.indexOf(":") + 2); + ldapContact.setCountry(country); + } + if (attributes.get("department") != null) { + String department = attributes.get("department").toString(); + department = department.substring(department.indexOf(":") + 2); + ldapContact.setDepartment(department); + } + if (attributes.get("telephoneNumber") != null) { + String phone = attributes.get("telephoneNumber").toString(); + phone = phone.substring(phone.indexOf(":") + 2); + ldapContact.setPhone(phone); + } + if (attributes.get("sAMAccountName") != null) { + String crmname = attributes.get("sAMAccountName").toString().toLowerCase(); + crmname = crmname.substring(crmname.indexOf(":") + 2); + ldapContact.setCrmname(crmname); + } + if (attributes.get("userAccountControl") != null) { + String userAccountControl = attributes.get("userAccountControl").toString(); + userAccountControl = userAccountControl.substring(userAccountControl.indexOf(":") + 2); + if (userAccountControl.equals("512")) { + ldapContact.setEnabled(true); + } else { + ldapContact.setEnabled(false); + } + } else { + ldapContact.setEnabled(false); + } + break; + case LDAPContact.TYPE_GROUP: + case LDAPContact.TYPE_PUBLICFOLDER: + // handle groups + ldapContact.setEnabled(true); + ldapContact.setFirstname("Group"); + if (attributes.get("name") != null) { + String name = attributes.get("name").toString().toLowerCase(); + name = name.substring(name.indexOf(":") + 2); + ldapContact.setLastname(name); + } else { + String lastName = ldapContact.getEmailaddress(); + lastName = lastName.substring(0, lastName.indexOf("@") - 1); + ldapContact.setLastname(lastName); + } + if (attributes.get("managedBy") != null) { + try { + String managedBy = attributes.get("managedBy").toString().toLowerCase(); + managedBy = managedBy.substring(managedBy.indexOf(":") + 2); + managedBy = managedBy.substring(managedBy.lastIndexOf("ou=")); + managedBy = managedBy.substring(3, managedBy.indexOf(",")); + ldapContact.setCountry(managedBy); + } catch (Exception ex) { + ldapContact.setCountry(null); + } + } + break; + + default: + throw new LDAPException("Unknown / unsupported ldap type " + ldapContact.getType()); + + } + + return ldapContact; + } +} + +/** + * History: + * + * $$Log: src/main/java/com/wincornixdorf/shared/network/ldap/LDAPSearch.java $ + * Revision 1.1 2013/12/16 16:42:52MEZ Muehlencord, Joern (joern.muehlencord) + * Initial revision Member added to project + * m:/MKS/ESP_Tools/shared/shared-network/shared-network.pj $Revision 1.6 + * 2013/09/05 07:14:33 jomu $fixed ldap search if group setup is not complete. + * (1147451) $ $Revision 1.5 2013/09/04 15:07:26 jomu $fixed ldap search if + * group setup is not complete. (1147451) $$ + * + */