diff --git a/.gitignore b/.gitignore index 171daff..1d8fcfa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.class .idea +*.iml target diff --git a/README.md b/README.md index 9756e09..ccc3024 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ for testing secure connections, LDAPS and certificate configuration. ## Configuration -See [`Test.properties`](https://github.com/mmoayyed/java-ldap-ssl-test/blob/master/src/main/resources/Test.properties). +See [`application.properties`](java-ldap-ssl-test/blob/master/src/main/resources/application.properties). ## Build @@ -17,13 +17,23 @@ mvn clean package ## Usage -- Download the JAR from [here](https://github.com/UniconLabs/java-ldap-ssl-test/releases) +- Download the JAR from the [releases page](java-ldap-ssl-test/releases) - Run: ``` java -jar ``` +## Custom Configuration + +The configuration can be customized without rebuilding the project. A simple approach is to +create a [`application.properties`](java-ldap-ssl-test/blob/master/src/main/resources/application.properties) +file, with your required values, in the same directory as the jar before running. + +The jar is built on [Spring Boot](https://projects.spring.io/spring-boot/) so any of their +[Externalized Configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html) +options will work also like environment variables or java system properties from the command line. + ##Sample output The log below demonstrates a sample of the program output configured to hit 5 ldap urls. diff --git a/pom.xml b/pom.xml index 373ae44..463335f 100644 --- a/pom.xml +++ b/pom.xml @@ -3,15 +3,19 @@ 4.0.0 net.unicon java-ldap-keystore-test - 0.0.1 + 0.0.2 Java LDAP Keystore Test Utility jar Java CLI utility to execute LDAP connections. + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.RELEASE + org.springframework.boot spring-boot-starter - 1.4.3.RELEASE @@ -19,23 +23,15 @@ org.springframework.boot spring-boot-maven-plugin - 1.4.3.RELEASE - - - - repackage - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - + + + mmoayyed + + + danlangford + + diff --git a/src/main/java/net/unicon/LdapOptions.java b/src/main/java/net/unicon/LdapOptions.java new file mode 100644 index 0000000..20c5f75 --- /dev/null +++ b/src/main/java/net/unicon/LdapOptions.java @@ -0,0 +1,104 @@ +package net.unicon; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import javax.naming.spi.InitialContextFactory; + +// Component ensures Spring will manage a bean of this class +// and ConfigurationProperties is an easy way to map all the properties under "ldap.*" to type strict values +@Component +@ConfigurationProperties("ldap") +public class LdapOptions { + + private String[] urls; + private String userId; + private String password; + private String baseDn; + private String filter; + private String authnPassword; + private String[] attributes; + private Class factory; + private String authentication; + private Integer timeout; + + public String[] getUrls() { + return urls; + } + + public void setUrls(String[] urls) { + this.urls = urls; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getBaseDn() { + return baseDn; + } + + public void setBaseDn(String baseDn) { + this.baseDn = baseDn; + } + + public String getFilter() { + return filter; + } + + public void setFilter(String filter) { + this.filter = filter; + } + + public String getAuthnPassword() { + return authnPassword; + } + + public void setAuthnPassword(String authnPassword) { + this.authnPassword = authnPassword; + } + + public String[] getAttributes() { + return attributes; + } + + public void setAttributes(String[] attributes) { + this.attributes = attributes; + } + + public Class getFactory() { + return factory; + } + + public void setFactory(Class factory) { + this.factory = factory; + } + + public String getAuthentication() { + return authentication; + } + + public void setAuthentication(String authentication) { + this.authentication = authentication; + } + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } +} diff --git a/src/main/java/net/unicon/Test.java b/src/main/java/net/unicon/Test.java index 5e3b54e..6d2e147 100644 --- a/src/main/java/net/unicon/Test.java +++ b/src/main/java/net/unicon/Test.java @@ -2,77 +2,77 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.boot.CommandLineRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; import javax.naming.Context; import javax.naming.NamingEnumeration; +import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; +import java.util.AbstractMap.SimpleEntry; import java.util.Arrays; import java.util.Enumeration; import java.util.Hashtable; -import java.util.Properties; -public class Test implements CommandLineRunner { - protected final Logger logger = LoggerFactory.getLogger(this.getClass()); +@SpringBootApplication +public class Test implements ApplicationRunner { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { SpringApplication.run(Test.class, args); } - public void run(final String... args) throws Exception { - final Properties props = new Properties(); - props.load(Test.class.getResourceAsStream("/Test.properties")); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final LdapOptions ldap; + @Autowired + public Test(LdapOptions ldap){ + this.ldap=ldap; + } + + @Override + public void run(ApplicationArguments args) { try { - connect(props); - } catch (final IllegalArgumentException e) { - logger.error(e.getMessage()); + connect(); } catch (final Exception e) { - logger.error(e.getMessage()); - e.printStackTrace(); + logger.error(e.getMessage(), e); } } - private Pair getContext(final Properties props) { - for (int i = 0; i < 5; i++) { - String ldapUrl = props.getProperty("ldap.url" + (i + 1)); - - if (ldapUrl != null && !ldapUrl.isEmpty()) { - logger.info("\nAttempting connect to LDAP instance #" + (i + 1) + ": [" + ldapUrl.trim() + "].\n"); - ldapUrl = ldapUrl.trim(); - final Hashtable env = new Hashtable<>(6); - env.put(Context.INITIAL_CONTEXT_FACTORY, props.getProperty("ldap.factory")); - env.put(Context.PROVIDER_URL, ldapUrl.trim()); - env.put(Context.SECURITY_AUTHENTICATION, props.getProperty("ldap.authentication")); - env.put(Context.SECURITY_PRINCIPAL, props.getProperty("ldap.userId")); - env.put(Context.SECURITY_CREDENTIALS, props.getProperty("ldap.password")); - env.put("com.sun.jndi.ldap.connect.timeout", props.getProperty("ldap.timeout")); - - printConfig(env); - try { - return new Pair<>(ldapUrl, new InitialDirContext(env)); - } catch (Exception e) { - logger.info("\nFailed to connect to ldap instance #" + (i + 1) + ": [" + ldapUrl.trim() + "].\n"); - } + private SimpleEntry getContext() { + + String[] urls = ldap.getUrls(); + for (int i = 0; i < urls.length; i++) { + String ldapUrl = urls[i]; + if(ldapUrl==null || ldapUrl.trim().isEmpty()) { + continue; + } + ldapUrl = ldapUrl.trim(); + logger.info("\nAttempting connect to LDAP instance {}: [{}].\n", (i + 1), ldapUrl); + final Hashtable env = buildEnv(ldapUrl, ldap.getUserId(), ldap.getPassword()); + printConfig(env); + try { + return new SimpleEntry<>(ldapUrl, new InitialDirContext(env)); + } catch (Exception e) { + logger.info("\nFailed to connect to ldap instance #{}: [{}].\n", (i + 1), ldapUrl); } } return null; } - private void connect(final Properties props) throws Exception { - final String[] attrIDs = props.getProperty("ldap.attributes").split(","); - + private void connect() throws NamingException { final SearchControls ctls = new SearchControls(); ctls.setDerefLinkFlag(true); - ctls.setTimeLimit(new Integer(props.getProperty("ldap.timeout"))); - ctls.setReturningAttributes(attrIDs); + ctls.setTimeLimit(ldap.getTimeout()); + ctls.setReturningAttributes(ldap.getAttributes()); ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); - final Pair pair = getContext(props); + final SimpleEntry pair = getContext(); if (pair == null) { throw new IllegalArgumentException("\nCould not connect to any of the provided LDAP urls based on the given credentials."); } @@ -80,48 +80,42 @@ private void connect(final Properties props) throws Exception { DirContext ctx = null; try { - ctx = pair.getSecond(); + ctx = pair.getValue(); - String log = "Successfully connected to the LDAP url [" + pair.getFirst().trim() + "] "; + String log = "Successfully connected to the LDAP url [{}] "; if (ctx.getNameInNamespace() != null && !ctx.getNameInNamespace().isEmpty()) { - log += "with namespace [" + ctx.getNameInNamespace() + "]."; + log += "with namespace [{}]."; } log += "\n"; - logger.info(log); + logger.info(log, pair.getKey(), ctx.getNameInNamespace()); logger.info("******* Ldap Search *******"); - logger.info("Ldap filter: " + props.getProperty("ldap.filter")); - logger.info("Ldap search base: " + props.getProperty("ldap.baseDn")); - logger.info("Returning attributes: " + Arrays.toString(attrIDs)); + logger.info("Ldap filter: {}", ldap.getFilter()); + logger.info("Ldap search base: {}", ldap.getBaseDn()); + logger.info("Returning attributes: {}", Arrays.toString(ldap.getAttributes())); logger.info("***************************\n"); - final NamingEnumeration answer = ctx.search(props.getProperty("ldap.baseDn"), props.getProperty("ldap.filter"), ctls); + final NamingEnumeration answer = ctx.search(ldap.getBaseDn(), ldap.getFilter(), ctls); if (answer.hasMoreElements()) { logger.info("******* Ldap Search Results *******"); while (answer.hasMoreElements()) { final SearchResult result = answer.nextElement(); - logger.info("User name: " + result.getName()); - logger.info("User full name: " + result.getNameInNamespace()); + logger.info("User name: {}", result.getName()); + logger.info("User full name: {}", result.getNameInNamespace()); - String authnPsw = props.getProperty("ldap.authn.password"); + String authnPsw = ldap.getAuthnPassword(); if (authnPsw != null) { - logger.info("Attempting to authenticate " + result.getName() + " with password " + authnPsw); - - final Hashtable env = new Hashtable<>(6); - env.put(Context.INITIAL_CONTEXT_FACTORY, props.getProperty("ldap.factory")); - env.put(Context.PROVIDER_URL, pair.getFirst().trim()); - env.put(Context.SECURITY_AUTHENTICATION, props.getProperty("ldap.authentication")); - env.put(Context.SECURITY_PRINCIPAL, result.getNameInNamespace()); - env.put(Context.SECURITY_CREDENTIALS, authnPsw); - env.put("com.sun.jndi.ldap.connect.timeout", props.getProperty("ldap.timeout")); + logger.info("Attempting to authenticate {} with password {}",result.getName(), authnPsw); + + final Hashtable env = buildEnv(pair.getKey(), result.getNameInNamespace(), authnPsw); new InitialDirContext(env); - logger.info("Successfully authenticated " + result.getName() + " with password " + authnPsw + " at " + pair.getFirst()); + logger.info("Successfully authenticated {} with password {} at {}", result.getName(), authnPsw, pair.getKey()); } final NamingEnumeration attrs = result.getAttributes().getIDs(); while (attrs.hasMoreElements()) { final String id = attrs.nextElement(); - logger.info(id + " => " + result.getAttributes().get(id)); + logger.info("{} => {} ", id, result.getAttributes().get(id)); } } logger.info("************************************\n"); @@ -137,31 +131,25 @@ private void connect(final Properties props) throws Exception { } } + private Hashtable buildEnv(String url, String principal, String credentials) { + final Hashtable env = new Hashtable<>(6); + env.put(Context.INITIAL_CONTEXT_FACTORY, ldap.getFactory().getName()); + env.put(Context.PROVIDER_URL, url); + env.put(Context.SECURITY_AUTHENTICATION, ldap.getAuthentication()); + env.put(Context.SECURITY_PRINCIPAL, principal); + env.put(Context.SECURITY_CREDENTIALS, credentials); + env.put("com.sun.jndi.ldap.connect.timeout", ldap.getTimeout().toString()); + return env; + } + private void printConfig(final Hashtable table) { logger.info("******* LDAP Instance Configuration *******"); final Enumeration names = table.keys(); while (names.hasMoreElements()) { final String str = names.nextElement(); - logger.info(str + ": " + table.get(str)); + logger.info("{}: {}", str, table.get(str)); } logger.info("********************************************\n"); } - private static class Pair { - private F first; - private S second; - - public Pair(F f, S s) { - this.first = f; - this.second = s; - } - - public F getFirst() { - return this.first; - } - - public S getSecond() { - return this.second; - } - } } diff --git a/src/main/resources/application-default.properties b/src/main/resources/application-default.properties new file mode 100644 index 0000000..726a1d2 --- /dev/null +++ b/src/main/resources/application-default.properties @@ -0,0 +1,7 @@ +# spring boot properties +# https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# + +logging.level.root=error +logging.level.net.unicon=debug +logging.pattern.console=%msg%n diff --git a/src/main/resources/Test.properties b/src/main/resources/application.properties similarity index 80% rename from src/main/resources/Test.properties rename to src/main/resources/application.properties index 98c1ad3..f244237 100644 --- a/src/main/resources/Test.properties +++ b/src/main/resources/application.properties @@ -1,8 +1,5 @@ -ldap.url1 = ldaps://1.2.3.4 -ldap.url2 = -ldap.url3 = -ldap.url4 = -ldap.url5 = +# as many urls as you like all comma separated +ldap.urls = ldaps://1.2.3.4,ldaps://5.6.7.8 # Bind credentials ldap.userId = casadmin@org.edu @@ -15,7 +12,7 @@ ldap.baseDn = OU=some,DC=org,DC=edu # Authenticate via the provided password # Retrieve attributes for the account. ldap.filter = (&(objectClass=*) (sAMAccountName=casadmin)) -ldap.authn.password = password! +ldap.authnPassword = password! ldap.attributes = cn,givenName # Do not modify, unless you know what you're doing. diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml deleted file mode 100644 index 300f678..0000000 --- a/src/main/resources/logback.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - %msg%n - - - - - - - - - - - - -