Skip to content

Commit 8922bb7

Browse files
committed
Use UnboundID LDAP SDK for validate login/password
1 parent d1918be commit 8922bb7

File tree

3 files changed

+74
-68
lines changed

3 files changed

+74
-68
lines changed

build.gradle

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ dependencies {
3838
compile "org.ini4j:ini4j:0.5.2"
3939
compile "org.atteo.classindex:classindex:3.1"
4040
compile "org.mapdb:mapdb:1.0.6"
41+
compile "com.unboundid:unboundid-ldapsdk:2.3.8"
4142

42-
testCompile "org.apache.directory.server:apacheds-protocol-ldap:2.0.0-M17"
43-
testCompile "org.apache.directory.api:api-ldap-codec-standalone:1.0.0-M23"
43+
testCompile "org.apache.directory.server:apacheds-protocol-ldap:2.0.0-M19"
44+
testCompile "org.apache.directory.api:api-ldap-codec-standalone:1.0.0-M26"
4445
testCompile "org.testng:testng:6.8.8"
4546
}
4647

src/main/java/svnserver/auth/LDAPUserDB.java

+71-45
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
*/
88
package svnserver.auth;
99

10+
import com.unboundid.ldap.sdk.DIGESTMD5BindRequest;
11+
import com.unboundid.ldap.sdk.LDAPConnection;
12+
import com.unboundid.ldap.sdk.LDAPException;
13+
import com.unboundid.ldap.sdk.ResultCode;
14+
import com.unboundid.util.ssl.SSLUtil;
15+
import com.unboundid.util.ssl.TrustAllTrustManager;
1016
import org.jetbrains.annotations.NotNull;
1117
import org.jetbrains.annotations.Nullable;
1218
import org.slf4j.Logger;
@@ -16,15 +22,13 @@
1622
import org.tmatesoft.svn.core.SVNException;
1723
import svnserver.config.LDAPUserDBConfig;
1824

19-
import javax.naming.AuthenticationException;
20-
import javax.naming.Context;
21-
import javax.naming.NamingEnumeration;
2225
import javax.naming.NamingException;
23-
import javax.naming.directory.*;
26+
import javax.net.SocketFactory;
27+
import java.net.URI;
28+
import java.security.GeneralSecurityException;
2429
import java.text.MessageFormat;
2530
import java.util.Collection;
2631
import java.util.Collections;
27-
import java.util.Hashtable;
2832

2933
/**
3034
* Authenticates a user by binding to the directory with the DN of the entry for that user and the password
@@ -42,64 +46,86 @@ public final class LDAPUserDB implements UserDB, PasswordChecker {
4246

4347
@NotNull
4448
private final LDAPUserDBConfig config;
49+
@NotNull
50+
private final String baseDn;
51+
@NotNull
52+
private final String ldapHost;
53+
private final int ldapPort;
54+
@Nullable
55+
private final SocketFactory socketFactory;
4556

4657
public LDAPUserDB(@NotNull LDAPUserDBConfig config) {
58+
URI ldapUri = URI.create(config.getConnectionUrl());
59+
SocketFactory factory;
60+
int defaultPort;
61+
switch (ldapUri.getScheme().toLowerCase()) {
62+
case "ldap":
63+
factory = null;
64+
defaultPort = 389;
65+
break;
66+
case "ldaps":
67+
factory = createSslFactory(config);
68+
defaultPort = 636;
69+
break;
70+
default:
71+
throw new IllegalStateException("Unknown ldap scheme: " + ldapUri.getScheme());
72+
}
73+
this.socketFactory = factory;
74+
this.baseDn = ldapUri.getPath().isEmpty() ? "" : ldapUri.getPath().substring(1);
4775
this.config = config;
76+
this.ldapPort = ldapUri.getPort() > 0 ? ldapUri.getPort() : defaultPort;
77+
this.ldapHost = ldapUri.getHost();
78+
}
79+
80+
private static SocketFactory createSslFactory(@NotNull LDAPUserDBConfig config) {
81+
try {
82+
return new SSLUtil(null, new TrustAllTrustManager()).createSSLSocketFactory();
83+
} catch (GeneralSecurityException e) {
84+
throw new IllegalStateException("Can't create SSL Socket Factory", e);
85+
}
4886
}
4987

5088
@Nullable
5189
@Override
5290
public User check(@NotNull String username, @NotNull String password) throws SVNException {
53-
final Hashtable<String, Object> env = new Hashtable<>();
54-
env.put(Context.INITIAL_CONTEXT_FACTORY, config.getContextFactory());
55-
env.put(Context.PROVIDER_URL, config.getConnectionUrl());
56-
env.put(Context.SECURITY_AUTHENTICATION, config.getAuthentication());
57-
env.put(Context.SECURITY_PRINCIPAL, username);
58-
env.put(Context.SECURITY_CREDENTIALS, password);
59-
60-
InitialDirContext context = null;
6191
try {
62-
context = new InitialDirContext(env);
63-
64-
final SearchControls searchControls = new SearchControls();
65-
searchControls.setSearchScope(config.isUserSubtree() ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE);
66-
searchControls.setReturningAttributes(new String[]{config.getNameAttribute(), config.getEmailAttribute()});
67-
searchControls.setCountLimit(2);
68-
69-
final NamingEnumeration<SearchResult> search = context.search("", MessageFormat.format(config.getUserSearch(), username), searchControls);
70-
if (!search.hasMore()) {
71-
log.debug("Failed to find LDAP entry for {}", username);
72-
return null;
92+
LDAPConnection ldap = new LDAPConnection(socketFactory, ldapHost, ldapPort);
93+
try {
94+
ldap.bind(new DIGESTMD5BindRequest(username, password));
95+
com.unboundid.ldap.sdk.SearchResult search = ldap.search(
96+
baseDn,
97+
config.isUserSubtree() ? com.unboundid.ldap.sdk.SearchScope.SUB : com.unboundid.ldap.sdk.SearchScope.ONE,
98+
MessageFormat.format(config.getUserSearch(), username),
99+
config.getNameAttribute(), config.getEmailAttribute()
100+
);
101+
if (search.getEntryCount() == 0) {
102+
log.debug("Failed to find LDAP entry for {}", username);
103+
return null;
104+
} else if (search.getEntryCount() > 1) {
105+
log.error("Multiple LDAP entries found for {}", username);
106+
return null;
107+
}
108+
final com.unboundid.ldap.sdk.SearchResultEntry entry = search.getSearchEntries().get(0);
109+
final String realName = getAttribute(entry, config.getNameAttribute());
110+
final String email = getAttribute(entry, config.getEmailAttribute());
111+
return new User(username, realName != null ? realName : username, email);
112+
} finally {
113+
ldap.close();
73114
}
74-
75-
final Attributes attributes = search.next().getAttributes();
76-
77-
if (search.hasMore()) {
78-
log.error("Multiple LDAP entries found for {}", username);
115+
} catch (LDAPException e) {
116+
if (e.getResultCode() == ResultCode.INVALID_CREDENTIALS) {
79117
return null;
80118
}
81-
82-
final String realName = getAttribute(attributes, config.getNameAttribute());
83-
final String email = getAttribute(attributes, config.getEmailAttribute());
84-
return new User(username, realName != null ? realName : username, email);
85-
} catch (AuthenticationException e) {
86-
return null;
119+
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.AUTHN_NO_PROVIDER, e.getMessage()), e);
87120
} catch (NamingException e) {
88121
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.AUTHN_NO_PROVIDER, e.getMessage()), e);
89-
} finally {
90-
if (context != null)
91-
try {
92-
context.close();
93-
} catch (NamingException e) {
94-
log.error(e.getMessage(), e);
95-
}
96122
}
97123
}
98124

99125
@Nullable
100-
private String getAttribute(@NotNull Attributes attributes, @NotNull String name) throws NamingException {
101-
Attribute attribute = attributes.get(name);
102-
return attribute == null ? null : String.valueOf(attribute.get());
126+
private String getAttribute(@NotNull com.unboundid.ldap.sdk.SearchResultEntry entry, @NotNull String name) throws NamingException {
127+
com.unboundid.ldap.sdk.Attribute attribute = entry.getAttribute(name);
128+
return attribute == null ? null : attribute.getValue();
103129
}
104130

105131
@NotNull

src/main/java/svnserver/config/LDAPUserDBConfig.java

-21
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,6 @@ public final class LDAPUserDBConfig implements UserDBConfig {
2626
*/
2727
@NotNull
2828
private String connectionUrl = "ldap://localhost:389/ou=groups,dc=mycompany,dc=com";
29-
/**
30-
* The JNDI context factory used to acquire our InitialContext. By
31-
* default, assumes use of an LDAP server using the standard JNDI LDAP
32-
* provider.
33-
*/
34-
@NotNull
35-
private String contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
36-
/**
37-
* The type of authentication to use.
38-
*/
39-
@NotNull
40-
private String authentication = "DIGEST-MD5";
4129
/**
4230
* The search scope. Set to <code>true</code> if you wish to search the entire subtree rooted at the
4331
* <code>userBase</code> entry. The default value of <code>false</code> requests a single-level search
@@ -69,15 +57,6 @@ public void setConnectionUrl(@NotNull String connectionUrl) {
6957
this.connectionUrl = connectionUrl;
7058
}
7159

72-
@NotNull
73-
public String getContextFactory() {
74-
return contextFactory;
75-
}
76-
@NotNull
77-
public String getAuthentication() {
78-
return authentication;
79-
}
80-
8160
public boolean isUserSubtree() {
8261
return userSubtree;
8362
}

0 commit comments

Comments
 (0)