Skip to content

Commit e95d923

Browse files
author
Chris Gray
committed
0.0.1
1 parent 7754fdc commit e95d923

File tree

8 files changed

+333
-0
lines changed

8 files changed

+333
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.iml
2+
.idea/*
3+
*.swp
4+
target/*

keystore

201 KB
Binary file not shown.

pom.xml

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<groupId>com.yammer</groupId>
6+
<artifactId>dropwizard-authenticator-ldap</artifactId>
7+
<version>0.0.1-SNAPSHOT</version>
8+
9+
<scm>
10+
<connection>scm:git:git://github.com/yammer/dropwizard-authenticator-ldap.git</connection>
11+
<developerConnection>scm:git:[email protected]:yammer/dropwizard-authenticator-ldap.git</developerConnection>
12+
<url>http://github.com/yammer/dropwizard-authenticator-ldap/</url>
13+
</scm>
14+
15+
<issueManagement>
16+
<system>github</system>
17+
<url>http://github.com/yammer/dropwizard-authenticator-ldap/issues</url>
18+
</issueManagement>
19+
20+
<properties>
21+
<dropwizard.version>0.4.3</dropwizard.version>
22+
</properties>
23+
24+
<developers>
25+
<developer>
26+
<name>Chris Gray</name>
27+
<email>[email protected]</email>
28+
</developer>
29+
</developers>
30+
31+
<distributionManagement>
32+
<repository>
33+
<id>maven.int.yammer.com-releases</id>
34+
<url>http://maven.int.yammer.com/nexus/content/repositories/releases/</url>
35+
</repository>
36+
<snapshotRepository>
37+
<id>maven.int.yammer.com-snapshots</id>
38+
<url>http://maven.int.yammer.com/nexus/content/repositories/snapshots/</url>
39+
</snapshotRepository>
40+
</distributionManagement>
41+
42+
<repositories>
43+
<repository>
44+
<id>sonatype-nexus-snapshots</id>
45+
<name>Sonatype Nexus Snapshots</name>
46+
<url>http://oss.sonatype.org/content/repositories/snapshots</url>
47+
</repository>
48+
</repositories>
49+
50+
<dependencies>
51+
<dependency>
52+
<groupId>com.yammer.dropwizard</groupId>
53+
<artifactId>dropwizard-auth</artifactId>
54+
<version>${dropwizard.version}</version>
55+
</dependency>
56+
<dependency>
57+
<groupId>com.yammer.dropwizard</groupId>
58+
<artifactId>dropwizard-testing</artifactId>
59+
<version>${dropwizard.version}</version>
60+
<scope>test</scope>
61+
</dependency>
62+
</dependencies>
63+
64+
<build>
65+
<plugins>
66+
<plugin>
67+
<groupId>org.apache.maven.plugins</groupId>
68+
<artifactId>maven-compiler-plugin</artifactId>
69+
<version>2.3.2</version>
70+
<configuration>
71+
<source>1.7</source>
72+
<target>1.7</target>
73+
<encoding>UTF-8</encoding>
74+
</configuration>
75+
</plugin>
76+
<plugin>
77+
<groupId>org.apache.maven.plugins</groupId>
78+
<artifactId>maven-source-plugin</artifactId>
79+
<version>2.1.2</version>
80+
<executions>
81+
<execution>
82+
<id>attach-sources</id>
83+
<goals>
84+
<goal>jar</goal>
85+
</goals>
86+
</execution>
87+
</executions>
88+
</plugin>
89+
<plugin>
90+
<groupId>org.apache.maven.plugins</groupId>
91+
<artifactId>maven-resources-plugin</artifactId>
92+
<version>2.5</version>
93+
<configuration>
94+
<outputDirectory />
95+
<encoding>UTF-8</encoding>
96+
</configuration>
97+
</plugin>
98+
<plugin>
99+
<groupId>org.apache.maven.plugins</groupId>
100+
<artifactId>maven-release-plugin</artifactId>
101+
<version>2.3.1</version>
102+
<configuration>
103+
<autoVersionSubmodules>true</autoVersionSubmodules>
104+
<mavenExecutorId>forked-path</mavenExecutorId>
105+
<tagNameFormat>v@{project.version}</tagNameFormat>
106+
</configuration>
107+
</plugin>
108+
<plugin>
109+
<groupId>org.apache.maven.plugins</groupId>
110+
<artifactId>maven-surefire-plugin</artifactId>
111+
<version>2.12</version>
112+
<configuration>
113+
<argLine>
114+
-Djavax.net.ssl.keyStore=keystore
115+
-Djavax.net.ssl.keyStorePassword=deploymacy
116+
-Djavax.net.ssl.trustStore=keystore
117+
-Djavax.net.ssl.trustStorePassword=deploymacy
118+
</argLine>
119+
</configuration>
120+
</plugin>
121+
</plugins>
122+
</build>
123+
</project>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.yammer.dropwizard.authenticator;
2+
3+
import com.google.common.base.Optional;
4+
import com.google.common.net.HostAndPort;
5+
import com.yammer.dropwizard.auth.basic.BasicCredentials;
6+
import com.yammer.dropwizard.logging.Log;
7+
import com.yammer.metrics.Metrics;
8+
import com.yammer.metrics.core.Timer;
9+
import com.yammer.metrics.core.TimerContext;
10+
11+
import javax.naming.*;
12+
import javax.naming.directory.DirContext;
13+
import javax.naming.directory.InitialDirContext;
14+
import javax.naming.directory.SearchControls;
15+
import javax.naming.directory.SearchResult;
16+
import java.util.Hashtable;
17+
18+
import static com.google.common.base.Preconditions.checkNotNull;
19+
20+
public class LdapAuthenticator {
21+
private final HostAndPort hostAndPort;
22+
private static final int DEFAULT_LDAP_SSL_PORT = 636;
23+
private static final Log LOG = Log.forClass(LdapAuthenticator.class);
24+
private static final Timer LDAP_AUTHENTICATION_TIMER = Metrics.newTimer(LdapAuthenticator.class, "authenticate");
25+
26+
public LdapAuthenticator(HostAndPort hostAndPort) {
27+
this.hostAndPort = checkNotNull(hostAndPort, "hostAndPort cannot be null");
28+
}
29+
30+
private String providerUrl() {
31+
return String.format("ldaps://%s:%d/",
32+
hostAndPort.getHostText(),
33+
hostAndPort.getPortOrDefault(DEFAULT_LDAP_SSL_PORT));
34+
}
35+
36+
public boolean canAuthenticate() {
37+
final Hashtable<String, String> env = new Hashtable<>();
38+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
39+
env.put(Context.PROVIDER_URL, providerUrl());
40+
41+
try {
42+
new InitialDirContext(env).close();
43+
return true;
44+
} catch (CommunicationException err) {
45+
//can't authenticate
46+
} catch (NamingException err) {
47+
//anything else is bad
48+
}
49+
return false;
50+
}
51+
52+
//Under ou=people the entries are either
53+
//1.cn=cgray
54+
//2.uid=cgray
55+
//This determines which by searching for that person and then returns a formatted string
56+
//cn=username,ou=people,dc=yammer,dc=com (replace cn with uid if necessary)
57+
protected Optional<String> getBindDN(String username) {
58+
final Hashtable<String, String> env = new Hashtable<String, String>();
59+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
60+
env.put(Context.PROVIDER_URL, providerUrl());
61+
62+
try {
63+
final DirContext ctx = new InitialDirContext(env);
64+
final SearchControls controls = new SearchControls();
65+
controls.setReturningAttributes(new String[] {"givenName", "sn"});
66+
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
67+
final NamingEnumeration<SearchResult> answers = ctx.search("dc=yammer,dc=com",
68+
String.format("(uid=%s)", username), controls);
69+
ctx.close();
70+
if (answers.hasMore()) {
71+
final String result = answers.next().getNameInNamespace();
72+
return result.isEmpty() ? Optional.<String>absent() : Optional.of(result);
73+
}
74+
} catch (AuthenticationException err) {
75+
LOG.debug(username + " failed to authenticate", err);
76+
} catch (NamingException err) {
77+
LOG.warn("Authentication failure", err);
78+
}
79+
80+
return Optional.absent();
81+
}
82+
83+
public boolean authenticate(BasicCredentials basicCredentials) {
84+
final TimerContext ldapAuthenticationTimer = LDAP_AUTHENTICATION_TIMER.time();
85+
try {
86+
final Optional<String> bindDN = getBindDN(basicCredentials.getUsername());
87+
if (bindDN.isPresent()) {
88+
final Hashtable<String, String> env = new Hashtable<String, String>();
89+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
90+
env.put(Context.PROVIDER_URL, providerUrl());
91+
env.put(Context.SECURITY_PRINCIPAL, bindDN.get());
92+
env.put(Context.SECURITY_CREDENTIALS, basicCredentials.getPassword());
93+
94+
try {
95+
new InitialDirContext(env).close();
96+
return true;
97+
} catch (AuthenticationException err) {
98+
LOG.debug(basicCredentials.getUsername() + " failed to authenticate", err);
99+
} catch (NamingException err) {
100+
LOG.warn("Authentication failure", err);
101+
}
102+
}
103+
return false;
104+
} finally {
105+
ldapAuthenticationTimer.stop();
106+
}
107+
}
108+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.yammer.dropwizard.authenticator.healthchecks;
2+
3+
import com.yammer.dropwizard.authenticator.LdapAuthenticator;
4+
import com.yammer.metrics.core.HealthCheck;
5+
6+
import static com.google.common.base.Preconditions.checkNotNull;
7+
8+
public class LdapHealthCheck extends HealthCheck {
9+
private final LdapAuthenticator ldapAuthenticator;
10+
11+
public LdapHealthCheck(LdapAuthenticator ldapAuthenticator) {
12+
super("ldap");
13+
this.ldapAuthenticator = checkNotNull(ldapAuthenticator, "ldapAuthenticator cannot be null");
14+
}
15+
16+
@Override
17+
public Result check() {
18+
if (ldapAuthenticator.canAuthenticate()) {
19+
return Result.healthy();
20+
} else {
21+
return Result.unhealthy("Cannot contact authentication service");
22+
}
23+
}
24+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.yammer.dropwizard.authenticator.healthchecks.tests;
2+
3+
import com.google.common.net.HostAndPort;
4+
import com.yammer.dropwizard.authenticator.LdapAuthenticator;
5+
import com.yammer.dropwizard.authenticator.healthchecks.LdapHealthCheck;
6+
import com.yammer.metrics.core.HealthCheck;
7+
import org.junit.Test;
8+
9+
import static org.hamcrest.Matchers.is;
10+
import static org.hamcrest.Matchers.not;
11+
import static org.junit.Assert.assertThat;
12+
13+
public class LdapHealthCheckTest {
14+
private final LdapHealthCheck ldapHealthCheck = new LdapHealthCheck(
15+
new LdapAuthenticator(HostAndPort.fromParts("ns-001.sjc1.yammer.com", 636)));
16+
17+
@Test
18+
public void healthy() throws Exception {
19+
assertThat(ldapHealthCheck.check(), is(HealthCheck.Result.healthy()));
20+
}
21+
22+
@Test
23+
public void unhealthy() throws Exception {
24+
final LdapAuthenticator badLdapAuthenticator = new LdapAuthenticator(HostAndPort.fromParts("badHost", 1234));
25+
final LdapHealthCheck badHealthCheck = new LdapHealthCheck(badLdapAuthenticator);
26+
assertThat(badHealthCheck.check(), not(HealthCheck.Result.healthy()));
27+
}
28+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.yammer.dropwizard.authenticator.tests;
2+
3+
import com.google.common.net.HostAndPort;
4+
import com.yammer.dropwizard.auth.basic.BasicCredentials;
5+
import com.yammer.dropwizard.authenticator.LdapAuthenticator;
6+
import org.junit.Test;
7+
8+
import static org.hamcrest.Matchers.is;
9+
import static org.junit.Assert.assertThat;
10+
11+
public class LdapAuthenticatorTest {
12+
private final LdapAuthenticator ldapAuthenticator = new LdapAuthenticator(
13+
HostAndPort.fromParts("ns-001.sjc1.yammer.com", 636));
14+
15+
@Test
16+
public void badUser() {
17+
assertThat(ldapAuthenticator.authenticate(new BasicCredentials("yo", "dog")), is(false));
18+
}
19+
20+
@Test
21+
public void noPassword() {
22+
assertThat(ldapAuthenticator.authenticate(new BasicCredentials("yo", "")), is(false));
23+
}
24+
25+
@Test
26+
public void noUser() {
27+
assertThat(ldapAuthenticator.authenticate(new BasicCredentials("", "password")), is(false));
28+
}
29+
30+
@Test
31+
public void badServer() {
32+
final LdapAuthenticator badAuthenticator = new LdapAuthenticator(HostAndPort.fromParts("localhost", 1234));
33+
assertThat(badAuthenticator.authenticate(new BasicCredentials("yo", "dog")), is(false));
34+
}
35+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder>
4+
<outputPatternAsPresentationHeader>false</outputPatternAsPresentationHeader>
5+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
6+
</encoder>
7+
</appender>
8+
<root level="error">
9+
<appender-ref ref="STDOUT"/>
10+
</root>
11+
</configuration>

0 commit comments

Comments
 (0)