summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/gitblit/auth/LdapAuthProvider.java275
-rw-r--r--src/main/java/com/gitblit/ldap/LdapConnection.java288
-rw-r--r--src/test/java/com/gitblit/tests/LdapConnectionTest.java248
3 files changed, 543 insertions, 268 deletions
diff --git a/src/main/java/com/gitblit/auth/LdapAuthProvider.java b/src/main/java/com/gitblit/auth/LdapAuthProvider.java
index 19fd4632..8a326cdc 100644
--- a/src/main/java/com/gitblit/auth/LdapAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/LdapAuthProvider.java
@@ -16,9 +16,6 @@
*/
package com.gitblit.auth;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
@@ -33,28 +30,20 @@ import com.gitblit.Constants.AccountType;
import com.gitblit.Constants.Role;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.ldap.LdapConnection;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.service.LdapSyncService;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.unboundid.ldap.sdk.Attribute;
-import com.unboundid.ldap.sdk.BindRequest;
import com.unboundid.ldap.sdk.BindResult;
-import com.unboundid.ldap.sdk.DereferencePolicy;
-import com.unboundid.ldap.sdk.ExtendedResult;
-import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
-import com.unboundid.ldap.sdk.LDAPSearchException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
-import com.unboundid.ldap.sdk.SimpleBindRequest;
-import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
-import com.unboundid.util.ssl.SSLUtil;
-import com.unboundid.util.ssl.TrustAllTrustManager;
/**
* Implementation of an LDAP user service.
@@ -109,7 +98,7 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
if (enabled) {
logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server));
final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.removeDeletedUsers, true);
- LdapConnection ldapConnection = new LdapConnection();
+ LdapConnection ldapConnection = new LdapConnection(settings);
if (ldapConnection.connect()) {
if (ldapConnection.bind() == null) {
ldapConnection.close();
@@ -265,7 +254,7 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
public UserModel authenticate(String username, char[] password) {
String simpleUsername = getSimpleUsername(username);
- LdapConnection ldapConnection = new LdapConnection();
+ LdapConnection ldapConnection = new LdapConnection(settings);
if (ldapConnection.connect()) {
// Try to bind either to the "manager" account,
@@ -288,7 +277,7 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
// Find the logging in user's DN
String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
- accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
+ accountPattern = StringUtils.replace(accountPattern, "${username}", LdapConnection.escapeLDAPSearchFilter(simpleUsername));
SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
if (result != null && result.getEntryCount() == 1) {
@@ -441,12 +430,12 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
String groupBase = settings.getString(Keys.realm.ldap.groupBase, "");
String groupMemberPattern = settings.getString(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))");
- groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", escapeLDAPSearchFilter(loggingInUserDN));
- groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
+ groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", LdapConnection.escapeLDAPSearchFilter(loggingInUserDN));
+ groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", LdapConnection.escapeLDAPSearchFilter(simpleUsername));
// Fill in attributes into groupMemberPattern
for (Attribute userAttribute : loggingInUser.getAttributes()) {
- groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", escapeLDAPSearchFilter(userAttribute.getValue()));
+ groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", LdapConnection.escapeLDAPSearchFilter(userAttribute.getValue()));
}
SearchResult teamMembershipResult = searchTeamsInLdap(ldapConnection, groupBase, true, groupMemberPattern, Arrays.asList("cn"));
@@ -553,34 +542,6 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
return username;
}
- // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
- private static final String escapeLDAPSearchFilter(String filter) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < filter.length(); i++) {
- char curChar = filter.charAt(i);
- switch (curChar) {
- case '\\':
- sb.append("\\5c");
- break;
- case '*':
- sb.append("\\2a");
- break;
- case '(':
- sb.append("\\28");
- break;
- case ')':
- sb.append("\\29");
- break;
- case '\u0000':
- sb.append("\\00");
- break;
- default:
- sb.append(curChar);
- }
- }
- return sb.toString();
- }
-
private void configureSyncService() {
LdapSyncService ldapSyncService = new LdapSyncService(settings, this);
if (ldapSyncService.isReady()) {
@@ -593,226 +554,4 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
logger.info("Ldap sync service is disabled.");
}
}
-
-
-
- private class LdapConnection {
- private LDAPConnection conn;
- private SimpleBindRequest currentBindRequest;
- private SimpleBindRequest managerBindRequest;
- private SimpleBindRequest userBindRequest;
-
-
- public LdapConnection() {
- String bindUserName = settings.getString(Keys.realm.ldap.username, "");
- String bindPassword = settings.getString(Keys.realm.ldap.password, "");
- if (StringUtils.isEmpty(bindUserName) && StringUtils.isEmpty(bindPassword)) {
- this.managerBindRequest = new SimpleBindRequest();
- }
- this.managerBindRequest = new SimpleBindRequest(bindUserName, bindPassword);
- }
-
-
- boolean connect() {
- try {
- URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server));
- String ldapHost = ldapUrl.getHost();
- int ldapPort = ldapUrl.getPort();
-
- if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) {
- // SSL
- SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
- conn = new LDAPConnection(sslUtil.createSSLSocketFactory());
- if (ldapPort == -1) {
- ldapPort = 636;
- }
- } else if (ldapUrl.getScheme().equalsIgnoreCase("ldap") || ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
- // no encryption or StartTLS
- conn = new LDAPConnection();
- if (ldapPort == -1) {
- ldapPort = 389;
- }
- } else {
- logger.error("Unsupported LDAP URL scheme: " + ldapUrl.getScheme());
- return false;
- }
-
- conn.connect(ldapHost, ldapPort);
-
- if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
- SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
- ExtendedResult extendedResult = conn.processExtendedOperation(
- new StartTLSExtendedRequest(sslUtil.createSSLContext()));
- if (extendedResult.getResultCode() != ResultCode.SUCCESS) {
- throw new LDAPException(extendedResult.getResultCode());
- }
- }
-
- return true;
-
- } catch (URISyntaxException e) {
- logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e);
- } catch (GeneralSecurityException e) {
- logger.error("Unable to create SSL Connection", e);
- } catch (LDAPException e) {
- logger.error("Error Connecting to LDAP", e);
- }
-
- return false;
- }
-
-
- void close() {
- if (conn != null) {
- conn.close();
- }
- }
-
-
- SearchResult search(SearchRequest request) {
- try {
- return conn.search(request);
- } catch (LDAPSearchException e) {
- logger.error("Problem Searching LDAP [{}]", e.getResultCode());
- return e.getSearchResult();
- }
- }
-
-
- SearchResult search(String base, boolean dereferenceAliases, String filter, List<String> attributes) {
- try {
- SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter);
- if (dereferenceAliases) {
- searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING);
- }
- if (attributes != null) {
- searchRequest.setAttributes(attributes);
- }
- SearchResult result = search(searchRequest);
- return result;
-
- } catch (LDAPException e) {
- logger.error("Problem creating LDAP search", e);
- return null;
- }
- }
-
-
-
- /**
- * Bind using the manager credentials set in realm.ldap.username and ..password
- * @return A bind result, or null if binding failed.
- */
- BindResult bind() {
- BindResult result = null;
- try {
- result = conn.bind(managerBindRequest);
- currentBindRequest = managerBindRequest;
- } catch (LDAPException e) {
- logger.error("Error authenticating to LDAP with manager account to search the directory.");
- logger.error(" Please check your settings for realm.ldap.username and realm.ldap.password.");
- logger.debug(" Received exception when binding to LDAP", e);
- return null;
- }
- return result;
- }
-
-
- /**
- * Bind using the given credentials, by filling in the username in the given {@code bindPattern} to
- * create the DN.
- * @return A bind result, or null if binding failed.
- */
- BindResult bind(String bindPattern, String simpleUsername, String password) {
- BindResult result = null;
- try {
- String bindUser = StringUtils.replace(bindPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
- SimpleBindRequest request = new SimpleBindRequest(bindUser, password);
- result = conn.bind(request);
- userBindRequest = request;
- currentBindRequest = userBindRequest;
- } catch (LDAPException e) {
- logger.error("Error authenticating to LDAP with user account to search the directory.");
- logger.error(" Please check your settings for realm.ldap.bindpattern.");
- logger.debug(" Received exception when binding to LDAP", e);
- return null;
- }
- return result;
- }
-
-
- boolean rebindAsUser() {
- if (userBindRequest == null || currentBindRequest == userBindRequest) {
- return false;
- }
- try {
- conn.bind(userBindRequest);
- currentBindRequest = userBindRequest;
- } catch (LDAPException e) {
- conn.close();
- logger.error("Error rebinding to LDAP with user account.", e);
- return false;
- }
- return true;
- }
-
-
- boolean isAuthenticated(String userDn, String password) {
- verifyCurrentBinding();
-
- // If the currently bound DN is already the DN of the logging in user, authentication has already happened
- // during the previous bind operation. We accept this and return with the current bind left in place.
- // This could also be changed to always retry binding as the logging in user, to make sure that the
- // connection binding has not been tampered with in between. So far I see no way how this could happen
- // and thus skip the repeated binding.
- // This check also makes sure that the DN in realm.ldap.bindpattern actually matches the DN that was found
- // when searching the user entry.
- String boundDN = currentBindRequest.getBindDN();
- if (boundDN != null && boundDN.equals(userDn)) {
- return true;
- }
-
- // Bind a the logging in user to check for authentication.
- // Afterwards, bind as the original bound DN again, to restore the previous authorization.
- boolean isAuthenticated = false;
- try {
- // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN
- SimpleBindRequest ubr = new SimpleBindRequest(userDn, password);
- conn.bind(ubr);
- isAuthenticated = true;
- userBindRequest = ubr;
- } catch (LDAPException e) {
- logger.error("Error authenticating user ({})", userDn, e);
- }
-
- try {
- conn.bind(currentBindRequest);
- } catch (LDAPException e) {
- logger.error("Error reinstating original LDAP authorization (code {}). Team information may be inaccurate for this log in.",
- e.getResultCode(), e);
- }
- return isAuthenticated;
- }
-
-
-
- private boolean verifyCurrentBinding() {
- BindRequest lastBind = conn.getLastBindRequest();
- if (lastBind == currentBindRequest) {
- return true;
- }
- logger.debug("Unexpected binding in LdapConnection. {} != {}", lastBind, currentBindRequest);
-
- String lastBoundDN = ((SimpleBindRequest)lastBind).getBindDN();
- String boundDN = currentBindRequest.getBindDN();
- logger.debug("Currently bound as '{}', check authentication for '{}'", lastBoundDN, boundDN);
- if (boundDN != null && ! boundDN.equals(lastBoundDN)) {
- logger.warn("Unexpected binding DN in LdapConnection. '{}' != '{}'.", lastBoundDN, boundDN);
- logger.warn("Updated binding information in LDAP connection.");
- currentBindRequest = (SimpleBindRequest)lastBind;
- return false;
- }
- return true;
- }
- }
}
diff --git a/src/main/java/com/gitblit/ldap/LdapConnection.java b/src/main/java/com/gitblit/ldap/LdapConnection.java
new file mode 100644
index 00000000..b7f07a1e
--- /dev/null
+++ b/src/main/java/com/gitblit/ldap/LdapConnection.java
@@ -0,0 +1,288 @@
+package com.gitblit.ldap;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.GeneralSecurityException;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.utils.StringUtils;
+import com.unboundid.ldap.sdk.BindRequest;
+import com.unboundid.ldap.sdk.BindResult;
+import com.unboundid.ldap.sdk.DereferencePolicy;
+import com.unboundid.ldap.sdk.ExtendedResult;
+import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.LDAPSearchException;
+import com.unboundid.ldap.sdk.ResultCode;
+import com.unboundid.ldap.sdk.SearchRequest;
+import com.unboundid.ldap.sdk.SearchResult;
+import com.unboundid.ldap.sdk.SearchScope;
+import com.unboundid.ldap.sdk.SimpleBindRequest;
+import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
+import com.unboundid.util.ssl.SSLUtil;
+import com.unboundid.util.ssl.TrustAllTrustManager;
+
+public class LdapConnection implements AutoCloseable {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private IStoredSettings settings;
+
+ private LDAPConnection conn;
+ private SimpleBindRequest currentBindRequest;
+ private SimpleBindRequest managerBindRequest;
+ private SimpleBindRequest userBindRequest;
+
+
+ // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
+ public static final String escapeLDAPSearchFilter(String filter) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < filter.length(); i++) {
+ char curChar = filter.charAt(i);
+ switch (curChar) {
+ case '\\':
+ sb.append("\\5c");
+ break;
+ case '*':
+ sb.append("\\2a");
+ break;
+ case '(':
+ sb.append("\\28");
+ break;
+ case ')':
+ sb.append("\\29");
+ break;
+ case '\u0000':
+ sb.append("\\00");
+ break;
+ default:
+ sb.append(curChar);
+ }
+ }
+ return sb.toString();
+ }
+
+
+
+ public LdapConnection(IStoredSettings settings) {
+ this.settings = settings;
+
+ String bindUserName = settings.getString(Keys.realm.ldap.username, "");
+ String bindPassword = settings.getString(Keys.realm.ldap.password, "");
+ if (StringUtils.isEmpty(bindUserName) && StringUtils.isEmpty(bindPassword)) {
+ this.managerBindRequest = new SimpleBindRequest();
+ }
+ this.managerBindRequest = new SimpleBindRequest(bindUserName, bindPassword);
+ }
+
+
+
+ public boolean connect() {
+ try {
+ URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server));
+ String ldapHost = ldapUrl.getHost();
+ int ldapPort = ldapUrl.getPort();
+
+ if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) {
+ // SSL
+ SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
+ conn = new LDAPConnection(sslUtil.createSSLSocketFactory());
+ if (ldapPort == -1) {
+ ldapPort = 636;
+ }
+ } else if (ldapUrl.getScheme().equalsIgnoreCase("ldap") || ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
+ // no encryption or StartTLS
+ conn = new LDAPConnection();
+ if (ldapPort == -1) {
+ ldapPort = 389;
+ }
+ } else {
+ logger.error("Unsupported LDAP URL scheme: " + ldapUrl.getScheme());
+ return false;
+ }
+
+ conn.connect(ldapHost, ldapPort);
+
+ if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
+ SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
+ ExtendedResult extendedResult = conn.processExtendedOperation(
+ new StartTLSExtendedRequest(sslUtil.createSSLContext()));
+ if (extendedResult.getResultCode() != ResultCode.SUCCESS) {
+ throw new LDAPException(extendedResult.getResultCode());
+ }
+ }
+
+ return true;
+
+ } catch (URISyntaxException e) {
+ logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e);
+ } catch (GeneralSecurityException e) {
+ logger.error("Unable to create SSL Connection", e);
+ } catch (LDAPException e) {
+ logger.error("Error Connecting to LDAP", e);
+ }
+
+ return false;
+ }
+
+
+ public void close() {
+ if (conn != null) {
+ conn.close();
+ }
+ }
+
+
+
+ /**
+ * Bind using the manager credentials set in realm.ldap.username and ..password
+ * @return A bind result, or null if binding failed.
+ */
+ public BindResult bind() {
+ BindResult result = null;
+ try {
+ result = conn.bind(managerBindRequest);
+ currentBindRequest = managerBindRequest;
+ } catch (LDAPException e) {
+ logger.error("Error authenticating to LDAP with manager account to search the directory.");
+ logger.error(" Please check your settings for realm.ldap.username and realm.ldap.password.");
+ logger.debug(" Received exception when binding to LDAP", e);
+ return null;
+ }
+ return result;
+ }
+
+
+ /**
+ * Bind using the given credentials, by filling in the username in the given {@code bindPattern} to
+ * create the DN.
+ * @return A bind result, or null if binding failed.
+ */
+ public BindResult bind(String bindPattern, String simpleUsername, String password) {
+ BindResult result = null;
+ try {
+ String bindUser = StringUtils.replace(bindPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
+ SimpleBindRequest request = new SimpleBindRequest(bindUser, password);
+ result = conn.bind(request);
+ userBindRequest = request;
+ currentBindRequest = userBindRequest;
+ } catch (LDAPException e) {
+ logger.error("Error authenticating to LDAP with user account to search the directory.");
+ logger.error(" Please check your settings for realm.ldap.bindpattern.");
+ logger.debug(" Received exception when binding to LDAP", e);
+ return null;
+ }
+ return result;
+ }
+
+
+ public boolean rebindAsUser() {
+ if (userBindRequest == null || currentBindRequest == userBindRequest) {
+ return false;
+ }
+ try {
+ conn.bind(userBindRequest);
+ currentBindRequest = userBindRequest;
+ } catch (LDAPException e) {
+ conn.close();
+ logger.error("Error rebinding to LDAP with user account.", e);
+ return false;
+ }
+ return true;
+ }
+
+
+
+ public SearchResult search(SearchRequest request) {
+ try {
+ return conn.search(request);
+ } catch (LDAPSearchException e) {
+ logger.error("Problem Searching LDAP [{}]", e.getResultCode());
+ return e.getSearchResult();
+ }
+ }
+
+
+ public SearchResult search(String base, boolean dereferenceAliases, String filter, List<String> attributes) {
+ try {
+ SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter);
+ if (dereferenceAliases) {
+ searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING);
+ }
+ if (attributes != null) {
+ searchRequest.setAttributes(attributes);
+ }
+ SearchResult result = search(searchRequest);
+ return result;
+
+ } catch (LDAPException e) {
+ logger.error("Problem creating LDAP search", e);
+ return null;
+ }
+ }
+
+
+
+ public boolean isAuthenticated(String userDn, String password) {
+ verifyCurrentBinding();
+
+ // If the currently bound DN is already the DN of the logging in user, authentication has already happened
+ // during the previous bind operation. We accept this and return with the current bind left in place.
+ // This could also be changed to always retry binding as the logging in user, to make sure that the
+ // connection binding has not been tampered with in between. So far I see no way how this could happen
+ // and thus skip the repeated binding.
+ // This check also makes sure that the DN in realm.ldap.bindpattern actually matches the DN that was found
+ // when searching the user entry.
+ String boundDN = currentBindRequest.getBindDN();
+ if (boundDN != null && boundDN.equals(userDn)) {
+ return true;
+ }
+
+ // Bind a the logging in user to check for authentication.
+ // Afterwards, bind as the original bound DN again, to restore the previous authorization.
+ boolean isAuthenticated = false;
+ try {
+ // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN
+ SimpleBindRequest ubr = new SimpleBindRequest(userDn, password);
+ conn.bind(ubr);
+ isAuthenticated = true;
+ userBindRequest = ubr;
+ } catch (LDAPException e) {
+ logger.error("Error authenticating user ({})", userDn, e);
+ }
+
+ try {
+ conn.bind(currentBindRequest);
+ } catch (LDAPException e) {
+ logger.error("Error reinstating original LDAP authorization (code {}). Team information may be inaccurate for this log in.",
+ e.getResultCode(), e);
+ }
+ return isAuthenticated;
+ }
+
+
+
+ private boolean verifyCurrentBinding() {
+ BindRequest lastBind = conn.getLastBindRequest();
+ if (lastBind == currentBindRequest) {
+ return true;
+ }
+ logger.debug("Unexpected binding in LdapConnection. {} != {}", lastBind, currentBindRequest);
+
+ String lastBoundDN = ((SimpleBindRequest)lastBind).getBindDN();
+ String boundDN = currentBindRequest.getBindDN();
+ logger.debug("Currently bound as '{}', check authentication for '{}'", lastBoundDN, boundDN);
+ if (boundDN != null && ! boundDN.equals(lastBoundDN)) {
+ logger.warn("Unexpected binding DN in LdapConnection. '{}' != '{}'.", lastBoundDN, boundDN);
+ logger.warn("Updated binding information in LDAP connection.");
+ currentBindRequest = (SimpleBindRequest)lastBind;
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/test/java/com/gitblit/tests/LdapConnectionTest.java b/src/test/java/com/gitblit/tests/LdapConnectionTest.java
new file mode 100644
index 00000000..f8d2fed0
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/LdapConnectionTest.java
@@ -0,0 +1,248 @@
+package com.gitblit.tests;
+
+import static org.junit.Assume.assumeTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import com.gitblit.Keys;
+import com.gitblit.ldap.LdapConnection;
+import com.unboundid.ldap.sdk.BindResult;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.ResultCode;
+import com.unboundid.ldap.sdk.SearchRequest;
+import com.unboundid.ldap.sdk.SearchResult;
+import com.unboundid.ldap.sdk.SearchResultEntry;
+import com.unboundid.ldap.sdk.SearchScope;
+
+/*
+ * Test for the LdapConnection
+ *
+ * @author Florian Zschocke
+ *
+ */
+@RunWith(Parameterized.class)
+public class LdapConnectionTest extends LdapBasedUnitTest {
+
+ @Test
+ public void testEscapeLDAPFilterString() {
+ // This test is independent from authentication mode, so run only once.
+ assumeTrue(authMode == AuthMode.ANONYMOUS);
+
+ // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
+ assertEquals("No special characters to escape", "Hi This is a test #çà", LdapConnection.escapeLDAPSearchFilter("Hi This is a test #çà"));
+ assertEquals("LDAP Christams Tree", "Hi \\28This\\29 = is \\2a a \\5c test # ç à ô", LdapConnection.escapeLDAPSearchFilter("Hi (This) = is * a \\ test # ç à ô"));
+
+ assertEquals("Injection", "\\2a\\29\\28userPassword=secret", LdapConnection.escapeLDAPSearchFilter("*)(userPassword=secret"));
+ }
+
+
+ @Test
+ public void testConnect() {
+ // This test is independent from authentication mode, so run only once.
+ assumeTrue(authMode == AuthMode.ANONYMOUS);
+
+ LdapConnection conn = new LdapConnection(settings);
+ try {
+ assertTrue(conn.connect());
+ } finally {
+ conn.close();
+ }
+ }
+
+
+ @Test
+ public void testBindAnonymous() {
+ // This test tests for anonymous bind, so run only in authentication mode ANONYMOUS.
+ assumeTrue(authMode == AuthMode.ANONYMOUS);
+
+ LdapConnection conn = new LdapConnection(settings);
+ try {
+ assertTrue(conn.connect());
+
+ BindResult br = conn.bind();
+ assertNotNull(br);
+ assertEquals(ResultCode.SUCCESS, br.getResultCode());
+ assertEquals("", authMode.getBindTracker().getLastSuccessfulBindDN(br.getMessageID()));
+
+ } finally {
+ conn.close();
+ }
+ }
+
+
+ @Test
+ public void testBindAsAdmin() {
+ // This test tests for anonymous bind, so run only in authentication mode DS_MANAGER.
+ assumeTrue(authMode == AuthMode.DS_MANAGER);
+
+ LdapConnection conn = new LdapConnection(settings);
+ try {
+ assertTrue(conn.connect());
+
+ BindResult br = conn.bind();
+ assertNotNull(br);
+ assertEquals(ResultCode.SUCCESS, br.getResultCode());
+ assertEquals(settings.getString(Keys.realm.ldap.username, "UNSET"), authMode.getBindTracker().getLastSuccessfulBindDN(br.getMessageID()));
+
+ } finally {
+ conn.close();
+ }
+ }
+
+
+ @Test
+ public void testBindToBindpattern() {
+ LdapConnection conn = new LdapConnection(settings);
+ try {
+ assertTrue(conn.connect());
+
+ String bindPattern = "CN=${username},OU=Canada," + ACCOUNT_BASE;
+
+ BindResult br = conn.bind(bindPattern, "UserThree", "userThreePassword");
+ assertNotNull(br);
+ assertEquals(ResultCode.SUCCESS, br.getResultCode());
+ assertEquals("CN=UserThree,OU=Canada," + ACCOUNT_BASE, authMode.getBindTracker().getLastSuccessfulBindDN(br.getMessageID()));
+
+ br = conn.bind(bindPattern, "UserFour", "userThreePassword");
+ assertNull(br);
+
+ br = conn.bind(bindPattern, "UserTwo", "userTwoPassword");
+ assertNull(br);
+
+ } finally {
+ conn.close();
+ }
+ }
+
+
+ @Test
+ public void testRebindAsUser() {
+ LdapConnection conn = new LdapConnection(settings);
+ try {
+ assertTrue(conn.connect());
+
+ assertFalse(conn.rebindAsUser());
+
+ BindResult br = conn.bind();
+ assertNotNull(br);
+ assertFalse(conn.rebindAsUser());
+
+
+ String bindPattern = "CN=${username},OU=Canada," + ACCOUNT_BASE;
+ br = conn.bind(bindPattern, "UserThree", "userThreePassword");
+ assertNotNull(br);
+ assertFalse(conn.rebindAsUser());
+
+ br = conn.bind();
+ assertNotNull(br);
+ assertTrue(conn.rebindAsUser());
+ assertEquals(ResultCode.SUCCESS, br.getResultCode());
+ assertEquals("CN=UserThree,OU=Canada," + ACCOUNT_BASE, authMode.getBindTracker().getLastSuccessfulBindDN());
+
+ } finally {
+ conn.close();
+ }
+ }
+
+
+
+ @Test
+ public void testSearchRequest() throws LDAPException {
+ LdapConnection conn = new LdapConnection(settings);
+ try {
+ assertTrue(conn.connect());
+ BindResult br = conn.bind();
+ assertNotNull(br);
+
+ SearchRequest req;
+ SearchResult result;
+ SearchResultEntry entry;
+
+ req = new SearchRequest(ACCOUNT_BASE, SearchScope.BASE, "(CN=UserOne)");
+ result = conn.search(req);
+ assertNotNull(result);
+ assertEquals(0, result.getEntryCount());
+
+ req = new SearchRequest(ACCOUNT_BASE, SearchScope.ONE, "(CN=UserTwo)");
+ result = conn.search(req);
+ assertNotNull(result);
+ assertEquals(0, result.getEntryCount());
+
+ req = new SearchRequest(ACCOUNT_BASE, SearchScope.SUB, "(CN=UserThree)");
+ result = conn.search(req);
+ assertNotNull(result);
+ assertEquals(1, result.getEntryCount());
+ entry = result.getSearchEntries().get(0);
+ assertEquals("CN=UserThree,OU=Canada," + ACCOUNT_BASE, entry.getDN());
+
+ req = new SearchRequest(ACCOUNT_BASE, SearchScope.SUBORDINATE_SUBTREE, "(CN=UserFour)");
+ result = conn.search(req);
+ assertNotNull(result);
+ assertEquals(1, result.getEntryCount());
+ entry = result.getSearchEntries().get(0);
+ assertEquals("CN=UserFour,OU=Canada," + ACCOUNT_BASE, entry.getDN());
+
+ } finally {
+ conn.close();
+ }
+ }
+
+
+ @Test
+ public void testSearch() throws LDAPException {
+ LdapConnection conn = new LdapConnection(settings);
+ try {
+ assertTrue(conn.connect());
+ BindResult br = conn.bind();
+ assertNotNull(br);
+
+ SearchResult result;
+ SearchResultEntry entry;
+
+ result = conn.search(ACCOUNT_BASE, false, "(CN=UserOne)", null);
+ assertNotNull(result);
+ assertEquals(1, result.getEntryCount());
+ entry = result.getSearchEntries().get(0);
+ assertEquals("CN=UserOne,OU=US," + ACCOUNT_BASE, entry.getDN());
+
+ result = conn.search(ACCOUNT_BASE, true, "(&(CN=UserOne)(surname=One))", null);
+ assertNotNull(result);
+ assertEquals(1, result.getEntryCount());
+ entry = result.getSearchEntries().get(0);
+ assertEquals("CN=UserOne,OU=US," + ACCOUNT_BASE, entry.getDN());
+
+ result = conn.search(ACCOUNT_BASE, true, "(&(CN=UserOne)(surname=Two))", null);
+ assertNotNull(result);
+ assertEquals(0, result.getEntryCount());
+
+ result = conn.search(ACCOUNT_BASE, true, "(surname=Two)", Arrays.asList("givenName", "surname"));
+ assertNotNull(result);
+ assertEquals(1, result.getEntryCount());
+ entry = result.getSearchEntries().get(0);
+ assertEquals("CN=UserTwo,OU=US," + ACCOUNT_BASE, entry.getDN());
+ assertEquals(2, entry.getAttributes().size());
+ assertEquals("User", entry.getAttributeValue("givenName"));
+ assertEquals("Two", entry.getAttributeValue("surname"));
+
+ result = conn.search(ACCOUNT_BASE, true, "(personalTitle=Mr*)", null);
+ assertNotNull(result);
+ assertEquals(3, result.getEntryCount());
+ ArrayList<String> names = new ArrayList<>(3);
+ names.add(result.getSearchEntries().get(0).getAttributeValue("surname"));
+ names.add(result.getSearchEntries().get(1).getAttributeValue("surname"));
+ names.add(result.getSearchEntries().get(2).getAttributeValue("surname"));
+ assertTrue(names.contains("One"));
+ assertTrue(names.contains("Two"));
+ assertTrue(names.contains("Three"));
+
+ } finally {
+ conn.close();
+ }
+ }
+
+}