Skip to content

Commit

Permalink
[FIXES JENKINS-17281] Adding configuration options for the filters us…
Browse files Browse the repository at this point in the history
…ed to search for groups.

- It is somewhat confusing that there are two 'group search filters' so I have decided to rename one.
- The new name for the 'groupSearchFilter' that is controlled from  is the
   as this filter is used to determine what groups a specific user is a member of
- That leaves  as a nice clean name for the filter to search for named groups.
- This should still respect any existing configuration, i.e. leaving these fields blank will leave the
  existing defaults or existing overrides in place... but it will make life easier for users going forward
- Took quite some digging to figure out exactly what these filters were for... hopefully I have left things
  in a more obvious framing for anyone else following
- I would like a better way to apply the  override, but this was the cleanest way
  I could maintain backwards compatibility
  • Loading branch information
stephenc committed Jun 14, 2013
1 parent 2faeb21 commit 50ab441
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 3 deletions.
53 changes: 51 additions & 2 deletions src/main/java/hudson/security/LDAPSecurityRealm.java
Expand Up @@ -262,6 +262,23 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm {
*/
public final String groupSearchBase;

/**
* Query to locate an entry that identifies the group, given the group name string. If non-null it will override
* the default specified by {@link #GROUP_SEARCH}
*
* @since 1.5
*/
public final String groupSearchFilter;

/**
* Query to locate the group entries that a user belongs to, given the user object. <code>{0}</code>
* is the user's full DN while {1} is the username. If non-null it will override the default specified in
* {@code LDAPBindSecurityRealm.groovy}
*
* @since 1.5
*/
public final String groupMembershipFilter;

/*
Other configurations that are needed:
Expand Down Expand Up @@ -314,19 +331,35 @@ group target (CN is a reasonable default)
*/
private transient Map<String,CacheEntry<Set<String>>> groupDetailsCache = null;

/**
* @deprecated retained for backwards binary compatibility.
*/
@Deprecated
public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, String userSearch, String groupSearchBase, String managerDN, String managerPassword, boolean inhibitInferRootDN) {
this(server, rootDN, userSearchBase, userSearch, groupSearchBase, managerDN, managerPassword, inhibitInferRootDN, false);
}

/**
* @deprecated retained for backwards binary compatibility.
*/
@Deprecated
public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, String userSearch, String groupSearchBase, String managerDN, String managerPassword, boolean inhibitInferRootDN,
boolean disableMailAddressResolver) {
this(server, rootDN, userSearchBase, userSearch, groupSearchBase, managerDN, managerPassword, inhibitInferRootDN,
disableMailAddressResolver, null);
}

@DataBoundConstructor
/**
* @deprecated retained for backwards binary compatibility.
*/
@Deprecated
public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, String userSearch, String groupSearchBase, String managerDN, String managerPassword, boolean inhibitInferRootDN,
boolean disableMailAddressResolver, CacheConfiguration cache) {
this(server, rootDN, userSearchBase, userSearch, groupSearchBase, null, null, managerDN, managerPassword, inhibitInferRootDN, disableMailAddressResolver, cache);
}

@DataBoundConstructor
public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, String userSearch, String groupSearchBase, String groupSearchFilter, String groupMembershipFilter, String managerDN, String managerPassword, boolean inhibitInferRootDN, boolean disableMailAddressResolver, CacheConfiguration cache) {
this.server = server.trim();
this.managerDN = fixEmpty(managerDN);
this.managerPassword = Scrambler.scramble(fixEmpty(managerPassword));
Expand All @@ -337,6 +370,8 @@ public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, St
userSearch = fixEmptyAndTrim(userSearch);
this.userSearch = userSearch!=null ? userSearch : "uid={0}";
this.groupSearchBase = fixEmptyAndTrim(groupSearchBase);
this.groupSearchFilter = fixEmptyAndTrim(groupSearchFilter);
this.groupMembershipFilter = fixEmptyAndTrim(groupMembershipFilter);
this.disableMailAddressResolver = disableMailAddressResolver;
this.cache = cache;
}
Expand All @@ -357,6 +392,14 @@ public Integer getCacheTTL() {
return cache == null ? null : cache.getTtl();
}

public String getGroupMembershipFilter() {
return groupMembershipFilter;
}

public String getGroupSearchFilter() {
return groupSearchFilter;
}

/**
* Infer the root DN.
*
Expand Down Expand Up @@ -416,6 +459,11 @@ public SecurityComponents createSecurityComponents() {

ldapTemplate = new LdapTemplate(findBean(InitialDirContextFactory.class, appContext));

if (groupMembershipFilter != null) {
AuthoritiesPopulatorImpl authoritiesPopulator = findBean(AuthoritiesPopulatorImpl.class, appContext);
authoritiesPopulator.setGroupSearchFilter(groupMembershipFilter);
}

return new SecurityComponents(
findBean(AuthenticationManager.class, appContext),
new LDAPUserDetailsService(appContext));
Expand Down Expand Up @@ -457,10 +505,11 @@ public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFou

// TODO: obtain a DN instead so that we can obtain multiple attributes later
String searchBase = groupSearchBase != null ? groupSearchBase : "";
String searchFilter = groupSearchFilter != null ? groupSearchFilter : GROUP_SEARCH;
final Set<String> groups = cachedGroups != null
? cachedGroups
: (Set<String>) ldapTemplate
.searchForSingleAttributeValues(searchBase, GROUP_SEARCH, new String[]{groupname}, "cn");
.searchForSingleAttributeValues(searchBase, searchFilter, new String[]{groupname}, "cn");
if (cache != null && cachedGroups == null && !groups.isEmpty()) {
synchronized (this) {
if (groupDetailsCache == null) {
Expand Down
Expand Up @@ -43,6 +43,12 @@ THE SOFTWARE.
<f:entry title="${%Group search base}" help="/help/security/ldap/groupSearchBase.html">
<f:textbox name="ldap.groupSearchBase" value="${instance.groupSearchBase}" />
</f:entry>
<f:entry title="${%Group search filter}" help="/plugin/ldap/help-groupSearchFilter.html">
<f:textbox name="ldap.groupSearchFilter" value="${instance.groupSearchFilter}" />
</f:entry>
<f:entry title="${%Group membership filter}" help="/plugin/ldap/help-groupMembershipFilter.html">
<f:textbox name="ldap.groupMembershipFilter" value="${instance.groupMembershipFilter}" />
</f:entry>
<f:entry title="${%Manager DN}" help="/help/security/ldap/managerDN.html">
<f:textbox name="ldap.managerDN" value="${instance.managerDN}" autocomplete="off"
checkUrl="'${rootURL}/securityRealms/LDAPSecurityRealm/serverCheck?field=managerDN&amp;server='+encodeURIComponent(this.form.elements['ldap.server'].value)+'&amp;managerDN='+encodeURIComponent(this.value)+'&amp;managerPassword='+encodeURIComponent(this.form.elements['ldap.managerPassword'].value)"
Expand All @@ -56,7 +62,7 @@ THE SOFTWARE.
<f:entry title="${%Disable Ldap Email Resolver}">
<f:checkbox name="ldap.disableMailAddressResolver" checked="${instance.disableMailAddressResolver}"></f:checkbox>
</f:entry>
<f:optionalBlock name="ldap.cache" title="${%Enable cache}" checked="${instance.cache != null}">
<f:optionalBlock name="ldap.cache" title="${%Enable cache}" checked="${instance.cache != null}" help="/plugin/ldap/help-cache.html">
<f:entry title="${%Cache size}">
<f:radio name="ldap.cache.size" value="10" checked="${instance.cacheSize == 10}" title="10"/>
<st:nbsp />
Expand Down
File renamed without changes.
30 changes: 30 additions & 0 deletions src/main/webapp/help-groupMembershipFilter.html
@@ -0,0 +1,30 @@
<div>
<p>
When Jenkins resolves a user, the next step in the resolution process is to determine the LDAP groups that
the user belongs to. This field controls the search filter that is used to determine group membership.
If left blank, the default filter will be used.
</p>
<p>
The default default filter is:
</p>
<pre>(| (member={0}) (uniqueMember={0}) (memberUid={1}))</pre>
<p>
This can be overridden by creating a file <code>$JENKINS_HOME/LDAPBindSecurityRealm.groovy</code>. Irrespective
of what the default is, setting this filter to a non-blank value will determine the filter used.

This comment has been minimized.

Copy link
@jglick

jglick Jun 14, 2013

Member

This is very confusing in the context of a GUI control. Better to say that for historical reasons, this used to be configured in Groovy, but now is configured here.

This comment has been minimized.

Copy link
@stephenc

stephenc via email Jun 14, 2013

Author Member

This comment has been minimized.

Copy link
@jglick

jglick Jun 18, 2013

Member

I just meant to talk first in the help text about GUI configuration, then as a footnote mention that the default can still be modified from a Groovy script for compatibility.

</p>
<p>
You are normally safe leaving this field unchanged, however for large LDAP servers where you are seeing messages
such as <code>OperationNotSupportedException - Function Not Implemented</code>,
<code>Administrative Limit Exceeded</code> or similar periodically when trying to login, then that would
indicate that you should change to a more optimum filter for your LDAP server, namely one that queries only
the required field, such as:
</p>
<pre>(member={0})</pre>
<p>
Note: in this field there are two available substitutions:
</p>
<ul>
<li><code>{0}</code> - the fully qualified DN of the user</li>
<li><code>{1}</code> - the username portion of the user</li>
</ul>
</div>
24 changes: 24 additions & 0 deletions src/main/webapp/help-groupSearchFilter.html
@@ -0,0 +1,24 @@
<div>
<p>
When Jenkins is asked to determine if a named group exists, it uses a default filter of:
</p>
<pre>(&amp; (cn={0}) (| (objectclass=groupOfNames) (objectclass=groupOfUniqueNames) (objectclass=posixGroup)))</pre>
<p>
relative to the <code>Group search base</code> to determine if there is a group with the specified name (
<code>{0}</code> is substituted by the name being searched for)
</p>
<p>
If you know your LDAP server only stores group information in one specific object class, then you can improve
group search performance by restricting the filter to just the required <code>objectclass</code>.
</p>
<p>
Note: if you are using the LDAP security realm to connect to Active Directory (as opposed to using the
<a href="https://wiki.jenkins-ci.org/display/JENKINS/Active+Directory+plugin">Active Directory plugin</a>'s
security realm) then you will need to change this filter to:
</p>
<pre>(& (cn={0}) (objectclass=group) )</pre>
<p>
Note: if you leave this empty, the default search filter will be used, unless the
<code>hudson.security.LDAPSecurityRealm.groupSearch</code> has been set to modify the default.
</p>
</div>

0 comments on commit 50ab441

Please sign in to comment.