Support recursive queries against LDAP directories
The top domain of a company might be DC=example,DC=com, while a
sub-domain may be DC=asia,DC=example,DC=com. Assuming all users
in the company will use Gerrit, but their accounts are in different
sub-domains, recursive search under DC=example,DC=com is required.
So ldap.accountScope can be set to subtree for this case.
Change-Id: I9c6f98148b00e7c3f1e197054efcb2899f370d74
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 13384e1..80f5191 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -514,6 +514,17 @@
Root of the tree containing all user accounts. This is typically
of the form `ou=people,dc=example,dc=com`.
+[[ldap.accountScope]]ldap.accountScope::
++
+Scope of the search performed for accounts. Must be one of:
++
+* `one`: Search only one level below accountBase, but not recursive
+* `sub` or `subtree`: Search recursively below accountBase
+* `base` or `object`: Search exactly accountBase; probably not desired
+
++
+Default is `subtree` as many directories have several levels.
+
[[ldap.accountPattern]]ldap.accountPattern::
+
Query pattern to use when searching for a user account. This may be
@@ -572,6 +583,17 @@
Root of the tree containing all group objects. This is typically
of the form `ou=groups,dc=example,dc=com`.
+[[ldap.groupScope]]ldap.groupScope::
++
+Scope of the search performed for group objects. Must be one of:
++
+* `one`: Search only one level below groupBase, but not recursive
+* `sub` or `subtree`: Search recursively below groupBase
+* `base` or `object`: Search exactly groupBase; probably not desired
+
++
+Default is `subtree` as many directories have several levels.
+
[[ldap.groupName]]ldap.groupName::
+
Name of an attribute on the group object which matches to the name
diff --git a/src/main/java/com/google/gerrit/server/ldap/LdapQuery.java b/src/main/java/com/google/gerrit/server/ldap/LdapQuery.java
index 4a03335..b0eb938 100644
--- a/src/main/java/com/google/gerrit/server/ldap/LdapQuery.java
+++ b/src/main/java/com/google/gerrit/server/ldap/LdapQuery.java
@@ -29,14 +29,45 @@
/** Supports issuing parameterized queries against an LDAP data source. */
class LdapQuery {
+ static enum SearchScope {
+ // Search only the base DN
+ //
+ OBJECT(SearchControls.OBJECT_SCOPE), //
+ BASE(SearchControls.OBJECT_SCOPE),
+
+ // Search all entries one level under the base DN
+ //
+ // Does not include the base DN, and does not include items below items
+ // under the base DN.
+ //
+ ONE(SearchControls.ONELEVEL_SCOPE),
+
+ // Search all entries under the base DN, including the base DN.
+ //
+ SUBTREE(SearchControls.SUBTREE_SCOPE), //
+ SUB(SearchControls.SUBTREE_SCOPE);
+
+ private final int scope;
+
+ SearchScope(final int scope) {
+ this.scope = scope;
+ }
+
+ int scope() {
+ return scope;
+ }
+ }
+
private final String base;
+ private final SearchScope searchScope;
private final String pattern;
private final String[] patternArgs;
private final String[] returnAttributes;
- LdapQuery(final String base, final String pattern,
- final Set<String> returnAttributes) {
+ LdapQuery(final String base, final SearchScope searchScope,
+ final String pattern, final Set<String> returnAttributes) {
this.base = base;
+ this.searchScope = searchScope;
final StringBuilder p = new StringBuilder();
final List<String> a = new ArrayList<String>(4);
@@ -76,7 +107,7 @@
final SearchControls sc = new SearchControls();
final NamingEnumeration<SearchResult> res;
- sc.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+ sc.setSearchScope(searchScope.scope());
sc.setReturningAttributes(returnAttributes);
res = ctx.search(base, pattern, bind(params), sc);
try {
diff --git a/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java b/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java
index 2d0d2a0..e127cd1 100644
--- a/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java
+++ b/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java
@@ -26,7 +26,9 @@
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.SelfPopulatingCache;
+import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.ldap.LdapQuery.SearchScope;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
@@ -96,9 +98,11 @@
groupName = reqdef(config, "groupName", "cn");
groupAtts.add(groupName);
final String groupBase = required(config, "groupBase");
+ final SearchScope groupScope = scope(config, "groupScope");
final String groupMemberPattern =
reqdef(config, "groupMemberPattern", "(memberUid=${username})");
- groupMemberQuery = new LdapQuery(groupBase, groupMemberPattern, groupAtts);
+ groupMemberQuery =
+ new LdapQuery(groupBase, groupScope, groupMemberPattern, groupAtts);
if (groupMemberQuery.getParameters().length == 0) {
throw new IllegalArgumentException(
"No variables in ldap.groupMemberPattern");
@@ -140,9 +144,11 @@
}
}
final String accountBase = required(config, "accountBase");
+ final SearchScope accountScope = scope(config, "accountScope");
final String accountPattern =
reqdef(config, "accountPattern", "(uid=${username})");
- accountQuery = new LdapQuery(accountBase, accountPattern, accountAtts);
+ accountQuery =
+ new LdapQuery(accountBase, accountScope, accountPattern, accountAtts);
if (accountQuery.getParameters().length == 0) {
throw new IllegalArgumentException("No variables in ldap.accountPattern");
}
@@ -155,6 +161,10 @@
};
}
+ private static SearchScope scope(final Config c, final String setting) {
+ return ConfigUtil.getEnum(c, "ldap", null, setting, SearchScope.SUBTREE);
+ }
+
private static String optional(final Config config, final String name) {
return config.getString("ldap", null, name);
}